Swift/オプショナル型 †整数や文字列などのデータを持っている「通常の状態」に加えて、「扱うデータがない」 という状態を取り得る型が "オプショナル型" である。 オプショナル型とnil †扱うべきデータがないという状況を表すために、Swiftでは nil を用意している。 var a: Int = 0 var b: Int? = 10 b = nil // 代入可能 a = nil // エラーとなる データ型のイニシャライザの返り値として使われる。 let olympic = "2020" var year: Int? = Int(olympic) // 2020 が返される var year2: Optional<Int> = Int(olympic) // こう書くこともできる var city: Int? = Int("Tokyo") // エラーとなる オプショナル型から値を取り出す(unwrapする)。 †let year:Int? = Int("2020") var a:Int = year! - 2016 // 値が必要なので、式の右に!をつけてunwrapする。 var b:Int = year - 2016 // コンパイルエラーとなる let y:Int? = Int("Tokyo") // 返り値はnil var c:Int = y! - 2016 // 実行時エラーとなる オプショナル型をunwrapして、複合演算子を適用することができる。 let year:Int? = Int("5"); year! += 1; // yearは6となる。 let y:Int? = nil y! += 1 // 実行時エラーとなる 条件判定とオプショナル型 †値を利用するにはunwrapする必要があるが、nilを保持しているときにunwrapすると実行時エラーとなる。 そのためunwrapする前に、nilではないか確認する必要がある。 オプショナル型の変数は unwrap せずに == または != を使って、格納されている値を調べることができる。 var nagano:Int? = Int("1998") if nagano != nil { // unwrapは指定しない print("Nagano: \(nagano!)") // unwrapする必要がある } if nagano == 1998 { print(1998) } else { print("when?"); } オプション型は unwrap せずに、大小比較できる。 ただし、値がnilであった場合はどのような値よりも小さいという結果が返されるだけで nil であるかないかは判定できない。 var x: Int? = nil x > 2000 // false x < 2000 // true オプショナル束縛構文 †if-let文 †let year:Int? = Int("2020") if let y = year { // yearがnil以外の場合 print("\(y - 2016)") // yはInt型なのでunwrapは必要ない。 } else { // year がnilの場合 print("error" } 変数の値を変更する場合は var を使える。 let year:Int? = Int("2020") if var y = year { // yearがnil以外の場合 y = y - 2016 // yはInt型なのでunwrapは必要ない。値を変更できる。 print("\(y)") } else { // year がnilの場合 print("error" } 複数の変数を扱う場合は次のようにも書けます。 if let a = Int("1972") { if let b = Int("1998") { ... } } if let a = Int("1972"), b = Int("1998") { .. } if let a = Int("1972"), var b = Int("1998") { b -= 2016 // 値を変更するので var で宣言しておいた .. } オプショナル束縛と条件式 †オプショナル束縛において if の後ろに記述出来る条件式は次の形式を持つ。 [式,] let 定数名 = 式 where 式 最も左の式は普通の条件式で省略できる。これが真の場合にだけ カンマ "," の右の評価に進む。 オプショナル式の評価がすべてnil以外だった場合、whereの右側の式が評価される。 var nagano = 1998 if nagano < 2020, let tokyo = Int("2020") where tokyo > 2000 { print("Tokyo \(tokyo)") } guard文 †想定外の状況が発生した場合にその処理から抜け出すための構文が guard文である。 条件節には、上で述べたような条件式、オプショナル束縛やwhere節を記述できる。 else節は必ず記述する必要があり、通常の文も記述できるが、必ず実行される break, return, throw, 制御が戻らない関数の呼び出しが必要である。 guard 条件節 else { /* break や return */ } let stock = [ "1", "02", "4", "x", "5" ] for str in stock { guard let v = Int(str) else { print(str + "??") break } print(v,terminator:" ") } // 1 2 4 x?? と表示される nil合体演算子 †オプショナル型の値がnilでなければunwrapしてその値を使うが、 nilの場合は他の値を用いることができる。 var x:Int? = Int("tokyo") var y:Int = (x != nil) x! : 0 var z:Int = x ?? 0 // nil合体演算子。上の行と同じ意味。 var a = b ?? c ?? d ?? 0 // (b ?? (c ?? (d ?? 0))) と解釈される readLine関数 †標準入力から1行分の文字列を読み込んで String? 型の値( UTF-8) を返す。 EOFの場合に nil を返す。 デフォルト値を持つ引数 stripNewLine?: があり、bool型の値で文末の改行文字を取り除くかどうか指定できる。 var num = 0 while let line = readLine() { print("\(++num) "+ line) // nil以外の場合 } 有値オプショナル型 †オプショナル型の変数であるが、確実にnil以外の値を持っているとプログラマが判断できる場合は、 有値オプショナル型 (implicitly unwrapped optional) を使える。 型宣言の際に ! を用い、値を使う場合には明示的に unwrap を記述する必要はない(記述してもよい)。 let year: Int! = Int("2020") print("\(year - 2016)") // unwrapを記述する必要はない。 let y: Int! = Int("next) print("\(y - 2016)") // 実行時エラーとなる let year: Int! let year: ImplicitlyUnwrappedOptional<Int> // 上の行と同じ意味 関数の引数や返り値 †関数の引数や返り値の型にオプショナル型や有値オプショナル型を使うこともできる。 func nickname(name:String?, age:Int) -> String { let s = name ?? "名無しさん" return "浪速の"+s+"(\age)歳" } nickname("カウボーイ", 30) // "浪速のカウボーイ35歳" nickname(nil, 25) // "名無しさん25歳" var name:String? = "カウボーイ" nickname(name, 30) // "浪速のカウボーイ35歳" name = nil nickname(name, 25) // "名無しさん25歳" 失敗のあるイニシャライザ †初期化されたインスタンスを返す通常のイニシャライザ init の他に、 インスタンスまたはnilを返すイニシャライザ(失敗のあるイニシャライザ) init? を定義できる。 init と init? が同じシグニチャ(引数の並び)を持ってはいけない。 struct Time { let in24h: Bool var hour = 0, minute = 0 init?(_ h:Int, _m:Int, in24h:Bool = false) { let maxh = in24h ? 23 : 11 if h<0 || h>maxh || m<0 || m>59 { return nil } self.in24h = in24 hour = h minute = m } init(time:Time, in24h:Bool) { var h = time.hour if !in24h && time.hour > 11 { h -= 12 } self.in24h = in24h hour = h minute = time.min } | if let w = Time(22,10,in24h:true) { print("\(w.hour):\(w.minute)") // 22:10 と表示される } var u:Time = Time(23,40)! // nilをunwrapして実行時エラー発生 var t:Time? = Time(20,0,in24h:true) // init? が返すインスタンスが値となる u = Time(time:t1!, in24h: false) // initが動作するので、実引数には!が必要 |