Swift を学び始めるとき、Apple の公式チュートリアル 'A Swift Tour' に取り組んだ方も多いのではないでしょうか。 そして、時を経て、Swift に慣れ親しんだ今だからこそ理解し、得られるものがあります。もう少し深堀りした Swift の世界、'A Swift Tour' の裏面に取り組んでみましょう。
A slide and a note I talked are here!https://t.co/YnnJUGXldU#tryswiftconf #swiftlang
— ezura (@eduraaa) 2018年3月1日
裏 Swift Tour
おはようございます。
LINEでiOSエンジニアをしています。
皆さんが日々書くコードをお見せしますが、いつもと少し違う顔を見せるかもしれません。面白いかもしれないし、バグを生み出すものかもしれません
いつもと違う視点で見ていきます
Swift ツアーの入り口に出てくるコードです
Voidです
こちらは
Void のOptionalです
このような動作は珍しいものではないですよね
OptionalChainingです
演算子はすべてこのような挙動でしたでしょうか
えらー
二項演算子にしてみました
どこにもオプショナルはいません。
+演算子でエラーに鳴った式を実行してみましょう
同じエラー。
AssignMentをTrueに
IntのOptionalを返すようになりました。
冒頭とおなじになりました
私たちはこれらを使い分けられます。これらの実装は自然と我々を助けてくれています
ではこの右辺はなにが入りますか?
たとえば
値を返さないクロージャーの即時実行
Returnを使わない、値を返さない状態にすることです。
Neverと同じです。fatalErrorも返り値がNeverです。
NeverはIntのサブタイプではありません。
実際に試してもTypeErrorになります
ではこのクロージャーはなにでしょう
型が解決された段階のASTをみてみましょう
返り値はどんな型としても扱えるのです
これがなにを解決するでしょう
IBOutletを!にすることがありますね
実際に使う段階ではnilにならないはず。非オプショナルで扱いたいですよね。
nilではなく意味のない値でもない
ここに先程のものを使ってみましょう
Closure
ファンクションもクロージャーの一種ですしあまりにも大きな話題です
関数オブジェクトの生成に焦点を当てます
このように作ることはありますね
1つめ
引数を指定していません
NestedFunctionも同様。
2つめ
インスタンスメソッドにも書けますね。実行主が未定でも書けます。
イニシャライザでも書けます
オペレーターメソッド
EnumのAssosiatedValue
AssosiatedValueを持つEnumを呼ぶのはEnumのstatic function を呼ぶようなもの AssosiatedValueを持たなければstatic propertyを呼ぶようなもの
これも自然に見えるのでは
クロージャーを引数に受け取るものを息をするように私たちは使います
様々な場所に存在していました。
関数の引数にクロージャーを渡すのは、@escapeを付けない限り、非同期で実行できなかったり関数の外の変数に保存できなかったりします。 クロージャーが関数より長く存在しないことを保証してくれます。
このように生存期間の制限がある引数があります。Inoutです
+=は内部で引数を書き換えるのでinoutが指定されています
関数が終わった後もこの引数を使うような書き方をするとコンパイルエラーになります
2回書き換えます。 関数に初期値が卵の変数を渡します
変数の値が変化すると変更後の値をプリントするようにしています
結果は
関数の中では使われるが、関数の最後に変数の値をinout指定の変数に渡します
関数の中のargは渡したtestStringとは別物です
スリープさせて間に値を覗きます
ところがdidSetを取り除くと
関数が終わるまで書き換わっていなかったのが、書き換わってしまいました
DidSetがなければコスト削減のために参照が渡されます。
引数として渡した変数の種類によって動作が異なるのです
書き込み中の変数を読みに行くことは避けるべきことなので、本来はこのような動作は気にしなくてよいのかもしれません。
しかし非同期処理で意図せずそうなることもあります。遭遇した不思議な挙動を探るのは楽しいですよね
inoutを使うとfunctionの終わりに値が代入されますが、終わりとはいつでしょう
思い浮かぶのはdeferとreturnです。
deferとは、すべての処理が終わった後に実行されるブロック、で、
...本当にあっているのでしょうか?
思考の過程を大事にしながら得意性を探って終わりにしましょう
return inout defer のオンパレード
defer はスコープを抜ける直前に実行されます return する値を決定する処理を含みます
0を渡すと、return x で0の返却が確定します
そしてdeferが実行され+1されます
そしてinputの値が1 になります
これは成功するでしょうか
定義前の変数を使うとコンパイルに成功します
しかしこれはコンパイルが通ります
deferまでに初期化されていれば変数を使えるのです。
これはブロックが最後に異動しているのと同じです
では
didSetは何回呼ばれるでしょうか。 1回です。deferでの代入が呼びます。
initializerの中で値をセットしてもdidSetは呼ばれないはずですが、呼ばれてしまいます。
この挙動はバグとして報告されています。
初期化、開放処理をdidSet, WillSetにまかせていたら、または呼ばれない前提で描いていたら、意図しない動作になるかもしれません。
ほかにファンクションやクロージャー内で値を変更するとdidSet, willSetが呼ばれます。 deferはスコープを抜けると最後に実行される、では留められない存在に思われてきました
慣れ親しんだからこそ見落としていたもの、慣れ親しんだからこそ理解できるものがあります。その楽しさをお話しました
素晴らしい3日間になりますように。ご清聴ありがとうございました。