これまでは 自動で強制アンラップされるオプショナル
についての基礎的な概要と、そこから脱線してそれを使う際に留意したいことや「初期化を遅らせる」観点でみた代替策などをみていきました。今回は再びその機能そのものに視点を戻して、より詳細な 自動で強制アンラップされるオプショナル
自体の特色を詳しく眺めていこうと思います。
今回はゆめみ社外な人の参加なしでの開催になります。どうぞよろしくお願いしますね。
———————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #176
00:00 開始 01:28 レシーバーが自身を自身のクロージャー経由で編集するとエラー 03:19 保存型プロパティーがないときと、あっても didSet がないときは正常動作 04:25 didSet により Copy-in Copy-out が省略されない 05:08 局所変数であれば重複アクセスエラーとして検出 06:09 大域変数のときにエラーが報告されないのが不思議 07:23 内部状態を変更しつつコールバックしたい場面 08:48 lazy var まわりの挙動が気になるところ 11:03 改めて lazy var を見直してみても良いかもしれない 11:46 暗黙アンラップなオプショナルのクラスでの利用は先送り 13:22 暗黙アンラップなオプショナルは通常のオプショナルと同等 14:11 普通のオプショナルみたいに扱ってみる 16:29 実際の型を確認してみる 16:59 等価演算子まわりの様子を見てみる 18:04 メタタイプについても確認してみる 18:20 値が必ずあるものとして扱う必要はない 18:43 普段は nil チェックしないし、必要ない 19:49 オプショナルを解消するときにみるコードの違い 20:47 アンラップする権限がオプショナルに与えられている 21:26 暗黙アンラップのされ方 23:12 型推論で普通のオプショナルになる 25:09 オーバーロードでもオプショナル優先 28:04 nil チェックについてのおさらい 28:42 スライドの間違い修正 29:40 クロージングと次回の展望 ————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #176
では、始めていきます。今日は引き続きオプショナル、特に自動的にアンラップされるオプショナルについてお話しします。この機能についてもう少し集中的に説明していきますが、その前に前回の最後の方で話したレイジーバーについて簡単に触れておきます。
前回、レイジーバーの動きが微妙で怖いという話が出たと思います。そして、そのコードが今はアクセス違反になるから動かないと言っていたコードがありましたが、それがどういう仕組みでエラーになるのかを理解するために調査してみました。結果として、その場面でエラーになる仕組みが少し分かってきましたので、軽く紹介しますね。あまり重要ではない話なので、聞き流していただければと思います。
レイジーバーの基本的な仕組みは、必要になるまで初期化しないというものです。しかし、ある変数がレイジーで、その変数に対して何らかの操作を行ったときにアクセスエラーが発生することがあります。このエラーの原因としては、ミューテーティングファンクションがクロージャーを受け取るような形になっており、そのクロージャーに対して自分自身を渡そうとするとエラーが発生することが確認できました。
例えば、以下のようなコードを考えます。
class SomeClass {
var value: Int = 0
lazy var computed: () -> Int = {
return self.value
}
mutating func mutateAndNotify() {
self.value += 1
self.computed()
}
}
ここで、value
プロパティがレイジーではない場合、問題なく動作しますが、これがレイジーな変数であるとき、クロージャー内のself
アクセスによってエラーが発生します。このようなコードでは、プロパティにdidSet
がある場合も影響を受けることが確認されました。
つまり、レイジーバーがポインターを直で渡すことでの影響があるようです。そして、ミューテーティングファンクション内で自分自身を渡すとランタイムエラーが発生することがある、これが観察された点です。
これについては、グローバルスコープではなくレイジーバーのときに、コンパイラーがエラーを出してくれないため、注意が必要です。レイジーバーを使っていると、ランタイムエラーが発生してしまう可能性があります。
一方、didSet
があるとコピーイン・コピーアウトの問題も浮上するため、こんな注意が必要な部分があることに留意しておくとよいでしょう。
以上、レイジーバーの動作について簡単に整理した内容となります。次に、オプショナルについての詳細な話に進んでいきましょう。
オプショナルのアンラップ機能に関して、例えば以下のように定義された変数があります。
var optionalString: String?
optionalString = "Hello, Swift"
このoptionalString
を利用する際に、自動的にアンラップされるような状況を考えます。Swiftでは、オプショナルバインディングや強制アンラップを使うことで安全に値を利用することが推奨されていますが、自動アンラップされるオプショナル(!
)も存在します。
var implicitlyUnwrappedOptional: String!
implicitlyUnwrappedOptional = "Hello, Swift"
print(implicitlyUnwrappedOptional) // "Hello, Swift"が表示されます
このように、!
をつけることで、値がある場合には自動的にアンラップされ、値が表示されるという特徴がありますが、値がnil
の場合はクラッシュを引き起こす可能性があります。
また、オプショナルチェーンという機能も存在し、例えば以下のように書くことで、オプショナルの中身がnil
かどうかに応じて安全にアクセスすることが可能です。
var optionalString2: String?
optionalString2 = "Hi"
let uppercasedString = optionalString2?.uppercased()
print(uppercasedString) // Optional("HI")が表示されます
このように、Swiftではオプショナルの扱い方が非常に重要となります。今日はこの辺りまでとし、次回は実際のコード例をもう少し多く含めて説明していきたいと思います。よろしくお願いいたします。 なるほど、そう考えると自然ですね。この件についてのミーティングを考えていました。デリゲートを呼び出すとき、もしその手法が適切ならうまくいくと思いますが、そうでない場合もあります。万が一関係ない場合でも、ノーティフィケーションなど別の手段を検討することもあるでしょうが、それも最良の手ではないかもしれません。とりあえずは注意する必要があります。
前回お話ししたレイジーバー(Lazy var)のビッグセットについても触れておきます。このレイジーバーの特徴として、以前は存在しなかった動きが確認できるようになっています。例えば、willSet
で値が取れるようになったことです。これは新たな仕様で、レイジーバーを使っている場合には注意が必要かもしれません。
昔のコードを再利用する際に予期しない動作をする可能性があるので、特にレイジーバーを使用している場合は注意が必要です。最新の仕様により、初期化フェーズが異なって代入フェーズに変更されています。レイジーバーを普段使っていない場合でも、こういった仕様変更を頭に入れておくと役立つことがあるかもしれません。
では、本題に戻ります。現在のスライドは前回か前々回にお話しした内容です。「暗黙的にアンラップされるオプショナルプロパティは初期化の際によく使われる」という内容について説明しています。具体的には循環参照の話も含まれており、これについても今後詳しく紹介しようと思います。「循環参照」について詳しく学ぶと理解が深まるので、次回以降に取り上げたいと思います。
前回の勉強会は私にとって非常に有意義でした。他の参加者にとってもそれぞれ得るものがあったのではないでしょうか。さて、次のトピックに進む前に、もう少しこの循環参照の部分を見ていこうかと考えています。以上を踏まえて、今回の説明を終わりにし、次回に進みたいと思います。 次は「暗黙的にアンラップされるオプショナル」について説明します。この話は少し循環参照の話にもつながりがありますので、視野を広げて聞いていただけると面白いかもしれません。
まず、暗黙的にアンラップされるオプショナルについてですが、これは他のオプショナルとは少し特徴が異なります。以前は型が異なっていたこともあり、別々の型として強く意識されていました。しかし、使い勝手は異なるものの、基本的には通常のオプショナルと同じように扱えます。
例えば、変数 value
が Int の暗黙的にアンラップされるオプショナルとして定義されているとします。この場合、関数に引数として渡す際に、アンラップせずとも通常の Int として渡すことができるように見えますが、実はその裏で通常のオプショナルと同じように扱うことができます。具体的には、以下のように if 文や switch 文で通常のオプショナルと同じように判定が可能です。
if let x = value {
print("値は \\(x) です")
} else {
print("値は nil です")
}
switch value {
case .some(let x):
print("値は \\(x) です")
case .none:
print("値は nil です")
}
さらに、オプショナルバインディングも使えます。
if let x = value {
print("値は \\(x) です")
} else {
print("値は nil です")
}
また、??
演算子も使用可能です。
let defaultValue = value ?? 0
このように、暗黙的にアンラップされるオプショナルは、通常のオプショナルと同じように扱えます。
複数の変数を比較する際も、型が一致していれば同じように扱えます。
let a: Int? = 5
let b: Int! = 5
if a == b {
print("同じ値です")
}
暗黙的にアンラップされるオプショナルは、その値が nil であるかどうかもチェックできるため、通常のオプショナルと同じ機能を持ちます。これにより、使用時に注意が必要ですが、基本的な使い方は他のオプショナルと変わりません。
重要なのは、暗黙的にアンラップされるオプショナルは一度初期化されたら、その後は nil になることがないように使用するという方針です。コンパイラが自動でチェックしてくれるわけではありませんが、この方針に従って使用することで安全性が保たれます。
これが暗黙的にアンラップされるオプショナルの基本的な使い方と注意点です。次に進む前に、この点を押さえておいてください。 なので、今紹介したオプショナルの支援機能は、使うことがあまりないかもしれませんが、使っても間違いではありません。通常のオプショナルと同じなので、そこは押さえておくと良いでしょう。
次の例では、オプショナルにアンラップされるときの挙動の違いがあります。オプショナルになっている変数を、通常のオプショナルではない変数に代入しようとする場合、通常は強制アンラップが必要です。しかし、事前にビックリマークを付けておくと、そのまま普通の名前だけで代入できるということです。これは知っておくと便利でしょう。
次に、暗黙的にアンラップされるオプショナルの特徴についてです。このタイプのオプショナルは、必要に応じて強制アンラップする権限が与えられたオプショナルといえます。普通のオプショナルなんだけど、必要に応じて強制アンラップする権限がこのオプショナルにあるという感じです。個人的には、この表現が上手だなと思っていて、お気に入りの説明文です。
ここから少し面白い部分に入ります。実際にどういう動きをするのかという説明です。使っている分には当たり前ですが、確かにこういう動きなのだと改めて認識できました。次に、アンラップされるオプショナルの特徴についてもう少し詳しく説明します。
通常のオプショナルとして扱おうとしますが、それができないときには強制アンラップするという動きです。この仕様は昔は違っていましたが、途中で仕様変更があったことを覚えている方もいるでしょう。この説明がうまくその点を表現しています。
例えば、ある変数がString
型の暗黙的にアンラップされるオプショナルになっている場合、それを代入するとき、必要に応じて強制的にアンラップされます。この特権を使ってアンラップして、オプショナルではない変数に入れましょうという風に動いてくれます。 ただし、これが次の場面では強制アンラップが必要になることがあります。次の例を見てみましょう。
var a: Int! = 5
このように強制アンラップ付きのオプショナルを定義すると、型注釈がない状況でこの例を適用すると、まずオプショナルとして扱われます。つまり、型注釈がない代入先の場合、他辺の値はオプショナル型の定数として認識されます。この特徴をしっかり説明できているのは良い点ですね。
例えば、以下のようなコードがあります。
var a: Int! = 5
var b = a
print(type(of: b))
この場合、b
の型は何でしょうか?b
は Int?
型、つまりオプショナル型となります。通常、強制アンラップ付きのオプショナルはそのままオプショナルとして使われます。しかし、オプショナルではない型に代入する場合には、強制的にアンラップされて使用されます。これについては少し疑問が湧きました。
例えば、以下のような状況を考えてみましょう。
func action(value: Int) {
print(value)
}
var a: Int! = 5
action(value: a)
このとき、a
はオプショナルですが、action
が Int
型を取るため、強制的にアンラップされます。これは当たり前かもしれませんが、念のため確認します。この場合、a
は強制的にアンラップされて、5
が渡されます。
さらに、次のように強制アンラップ付きのオプショナルを直接使う例も考えてみましょう。
var a: Int! = 5
print(a)
この場合、a
は自動的にアンラップされて5
が表示されます。この特性を理解しておくことが重要です。
次に、通常のオプショナルと同様に、nil
チェックやオプショナルバインディングもできます。
if let value = a {
print(value)
} else {
print("a is nil")
}
このとき、a
が nil
でなければvalue
にアンラップされた値が代入され、その値が表示されます。
強制アンラップ付きのオプショナルについて、少し注意が必要ですが、その使い方を理解すれば便利に使うことができます。今日はこれで終わりにしましょう。次回はもう少しオプショナルの話を進め、その後エラーハンドリングについて学んでいく予定です。
ではこれで終わりにします。お疲れさまでした。ありがとうございました。