❣️

[Swift] Property Wrapperで「強制的」アンラップ型を作る

2021/09/10に公開

前置き

Swiftの暗黙的アンラップ型は使いどころが難しいうえに、挙動も一見わかりにくいところがあります。「勝手に強制アンラップされる型」だと思っていると、次のようなコードが理解しづらいです。

let x: Int! = nil
if let x = x {
    print("here")
} else {
    print("nil")
}

本当に「勝手に強制アンラップされる型」ならif letの右辺を評価した時点でクラッシュするはずですが、そうはならず「nil」と出力されます。なぜかというと暗黙的アンラップ型は「強制」ではなく「都合のいいときだけアンラップするオプショナル型」だからです。

なので、こういうコードは普通に書けます。

let x: Int! = nil
print(x ?? 0)
print(x?.description)
print(x!)

「強制的」アンラップ型を作る

「暗黙的アンラップ型」が「勝手に強制アンラップされる型」ではない、ということはわかりました。でも「勝手に強制アンラップされる型」も欲しいので、これをProperty Wrapperで作ってみましょう。

@propertyWrapper
struct ForcedUnwrapped<T> {
    var projectedValue: T?

    var wrappedValue: T {
        get { projectedValue! }
        set { self.projectedValue = newValue }
    }

    init() {
        self.projectedValue = nil
    }

    init(wrappedValue: T) {
        self.projectedValue = wrappedValue
    }

    init(projectedValue: T?) {
        self.projectedValue = projectedValue
    }
}

これが意外にいい感じに動きます。

// 暗黙的アンラップ型
var a: Int! = 0     // 0で初期化
var b: Int!         // nilで初期化
// 強制的アンラップ型
@ForcedUnwrapped var c: Int = 0     // 0で初期化
@ForcedUnwrapped var d: Int         // nilで初期化

projectedValueとしてアンラップしていないオプショナルを公開しているので、安全なアクセスをしたい場合はこれを使うことができます。

// 暗黙的アンラップ型
var a: Int! = 0
if let a = a { print(a) }         // 暗黙的にオプショナルとして扱われる
// 強制的アンラップ型
@ForcedUnwrapped var b: Int = 0
if let b = $b { print(b) }        // 明示的にオプショナルとして扱う

Property Wrapperの制約上、宣言時にletが使えなかったり、@IBOutletなどの属性とは併用できなかったりするのが残念ですが、個人的には明示的にオプショナルとして扱うかどうかを選択できる方が直感的でわかりやすいと思いました。

Discussion