はじめに
Swiftでは、ディクショナリの値に nil
を代入するとキーごと削除されます。
var dict: [String: Int] = [:]
dict["foo"] = 1
dict["foo"] = nil
print(dict) // [:]
この動き自体は問題ありませんが、値がオプショナル型のときに nil
を代入しようとしてもキーごと削除されます。
var dict: [String: Int?] = [:]
dict["foo"] = 1
dict["foo"] = nil
print(dict) // [:]
ではどうしたらキーごと削除せずに nil
を代入できるでしょうか。
本記事では値がオプショナル型のディクショナリに nil
を代入する方法を紹介します。
環境
- OS: macOS Sonoma 14.3
- Xcode: 15.3
- Swift: 5.10
結論
.some(nil)
で nil
を代入できます。
var dict: [String: Int?] = [:]
dict["foo"] = 1
dict["foo"] = .some(nil)
print(dict) // ["foo": nil]
他の方法
Optional<Int>.none
や Int?.none
でも nil
を代入できますが、値の型に依存する書き方なので好みは分かれそうです。
var dict: [String: Int?] = [:]
dict["foo"] = 1
dict["foo"] = Optional<Int>.none
print(dict) // ["foo": nil]
dict["foo"] = Int?.none
print(dict) // ["foo": nil]
ちなみに Int?
は Optional<Int>
のシンタックスシュガーなので、当然同じ結果になります。
解説
オプショナル型の実態は .none
と .some
からなる列挙型のためです。
// 参考: Xcodeから参照した `Optional` 型の定義
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
public init(_ some: Wrapped)
}
そのため .some(nil)
や Int?.none
で nil
を代入できます。
.none
だとディクショナリで nil
とみなされてキーごと削除されるので、 .none
を使う場合は明示的に型を指定する必要があります。
注意
ディクショナリからオプショナル型の値を取り出す場合、最初のアンラップは値を取り出せるかどうかの判定になり、オプショナル型自体はアンラップされないのに注意です。
var dict: [String: Int?] = [:]
dict["foo"] = 1
if let foo = dict["foo"] {
print(foo) // Optional(1)
}
オプショナル型を取り出すには2回アンラップします。
var dict: [String: Int?] = [:]
dict["foo"] = 1
- if let foo = dict["foo"] {
- print(foo) // Optional(1)
+ if let foo = dict["foo"], let foo {
+ print(foo) // 1
}
おわりに
ディクショナリでオプショナル型を扱うときの参考になると嬉しいです