SwiftのOptionalまわりについて

  • Swiftの変数・定数はnilが発生しないようになっていて、nilを代入したい変数は、型をOptional型にする必要がある。
  • String?のように型の後ろに?をつけるとnilを代入できるOptional型にWrapされる
let nums = [3, 4, 6]
let lastNum = nums.last
let ans = lastNum * 2 // Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?' ?
print(ans)

lastNum に数値ではなくnilが入ってる可能性があり、もしnilだった場合、lastNum * 2 が計算できずバグの要因になる可能性がある。したがって、Swiftはこういう場合、エラーを防ぐため、変数・定数にnilが代入されることを黙っては許可しない。

var num:Int
num = 5
num = nil // Nil cannot be assigned to type 'Int'

nilを変数に代入しようとすると上記のようなエラーになる。

- var num:Int
+ var num:Int?
num = 5
- num = nil // Nil cannot be assigned to type 'Int'
+ num = nil
print(num) // nil

OptionalValue とは、nilの可能性がある値のこと。

上記のように、型の後ろに ? を付けることで、numは、OptionalValue を代入できる Optional型の変数になり、num = nil の式はエラーではなくなる。

var num:Int?
num = 5
print(num) // Optional(5)
var num:Int?
num = 5
print(num * 3) // Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?' ?

型に ? を付けて宣言した変数にはnilを代入できることがわかったが、Optional 型の変数にnil以外の値を代入したときには注意が必要

上記の num は、Int型の5が、Optional(Int) のようにWrapされている状態。OptionalValueは、このままでは使えない。

var num:Int?
num = 5
- print(num * 3) // Value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?' ?
+ print(num! * 3) // 15

Optional(Int) を計算式で使うには、Optional() のラップを取り除く、つまりunwrapする必要がある。

unwrapの最も簡単な方法は、OptionalValue に ! をつけること。

var num:Int?
num = nil
print(num! * 3) // Fatal error: Unexpectedly found nil while unwrapping an Optional value

ただOptionalValueが本当にnilだった場合、強制unwrapするとエラーになる。

var num:Int?
num = nil
if num != nil {
    print(num! * 2)
} else {
    print("num is empty")
}

したがって、上記のようにnumがnilでないか確認する必要がある。 それをSwift側でやってくれるのが、Optional Binding。 Optional Binding とは、Optional Valuenilでなければ値をunwrap して、一時変数に代入し、nilの場合はfalseを返す機能。(if, while, guard-else)

  • if
var str:String?
str = "Swift"
if let msg = str { // Optional Valueがnilでなければ、値をunwrapして一時変数に代入
    print(msg + "world")
} else { //  Optional Valueがnilのときは、else blockが実行される
    print("hello world")
}
  • if-where
let year1:String = "2001"
let year2:String = "2016"

if let startYear = Int(year1), let endYear = Int(year2), startYear < endYear { // year1, year2がnilだった場合、`startYear < endYear` の式は実行されない
    let years = endYear - startYear
    print("\(years)年間") // 15年間
}
  • dictionary
var sum = 0
let dic:[String:Int?] = ["a": 24, "b": nil, "c": 10, "d": nil]

for (_, value) in dic {
    if let num = value {
        sum+=num // dicから順に取り出したvalueがnilでないときだけ実行される
    }
}
print("数値の合計は\(sum)") // 34
  • while
var str:String? = "★"
var repeatString:String = ""
var i = 0

while let stamp = str { // strがnilじゃないときunwrapしてstampに代入。nilならwhileループを実行しない。
    repeatString += stamp
    i += 1
    if(i >= 10) {
        break
    }
}
print(repeatString) // ★★★★★★★★★★
  • guard-else
let who = "さくら"
var level:Int?

func hello() {
    guard let theLevel = level else {
        print("level is nil")
        return
    }
    
    if theLevel<10 {
        print(who+"隊員")
    } else {
        print(who+"上級隊員")
    }
}

hello() // level is nil levelがnilなので、処理が中断されている

level = 5

hello() // サクラ隊員
print(count) // nil
print(count ?? 2) // 2

price = (count ?? 2) * 250
print(price) // 500

count = 3

print(count) // Optional(3)
print(count ?? 2) // 3

price = (count ?? 2) * 250
print(price) // 750

OptionalValueがnilのとき、?? を使うとnilに代わる値を指定できる

var balls:[(size:Int, color:String)] =[]
- var ballSize = balls.first.size // Value of optional type '(size:Int, color:String)?' not unwrapped; did you mean to use '!' or '?' ?
+ var ballSize = balls.first?.size
print(ballSize)  // nil
var balls:[(size:Int, color:String)] =[(size:2, color:"red"), (size:4, color:"green") ]
- var ballSize = balls.first.size // Value of optional type '(size:Int, color:String)?' not unwrapped; did you mean to use '!' or '?' ?
+ var ballSize = balls.first?.size
print(ballSize) // Optional(2)

.でアクセスするときに対象に?をつけることで実行時エラーを回避できる記述方法をOptional chainと呼ぶ

上記だと、Optional型で、値を使うことができず、unwrapする必要があるので、Optional Bindingと組み合わせる。

var balls:[(size:Int, color:String)] =[(size:2, color:"red"), (size:4, color:"green") ]
var ballSize = balls.first?.size
+ if let ballSize = balls.first?.size {
+     print(ballSize) // 2
+ }

reference from

https://www.amazon.co.jp/dp/4800711843