寄付窓口はこちら

裏 Swift Tour | try! Swift Tokyo 2018 Day1-1

Swift を学び始めるとき、Apple の公式チュートリアル 'A Swift Tour' に取り組んだ方も多いのではないでしょうか。 そして、時を経て、Swift に慣れ親しんだ今だからこそ理解し、得られるものがあります。もう少し深堀りした Swift の世界、'A Swift Tour' の裏面に取り組んでみましょう。


裏 Swift Tour

f:id:niwatako:20180301101225j:plain

おはようございます。

LINEでiOSエンジニアをしています。

皆さんが日々書くコードをお見せしますが、いつもと少し違う顔を見せるかもしれません。面白いかもしれないし、バグを生み出すものかもしれません

いつもと違う視点で見ていきます

f:id:niwatako:20180301101458j:plain

Swift ツアーの入り口に出てくるコードです

f:id:niwatako:20180301101525j:plain Voidです

f:id:niwatako:20180301101527j:plain こちらは

f:id:niwatako:20180301101541j:plain

Void のOptionalです

このような動作は珍しいものではないですよね

OptionalChainingです

f:id:niwatako:20180301101610j:plain

演算子はすべてこのような挙動でしたでしょうか

f:id:niwatako:20180301101634j:plain

f:id:niwatako:20180301101637j:plain

えらー

f:id:niwatako:20180301101656j:plain

二項演算子にしてみました

どこにもオプショナルはいません。

演算子でエラーに鳴った式を実行してみましょう

f:id:niwatako:20180301101721j:plain

同じエラー。

AssignMentをTrueに

f:id:niwatako:20180301101731j:plain

f:id:niwatako:20180301101745j:plain

IntのOptionalを返すようになりました。

冒頭とおなじになりました

私たちはこれらを使い分けられます。これらの実装は自然と我々を助けてくれています

f:id:niwatako:20180301101844j:plain

ではこの右辺はなにが入りますか?

たとえば

値を返さないクロージャーの即時実行

f:id:niwatako:20180301101904j:plain

Returnを使わない、値を返さない状態にすることです。

Neverと同じです。fatalErrorも返り値がNeverです。

f:id:niwatako:20180301101950j:plain

NeverはIntのサブタイプではありません。

実際に試してもTypeErrorになります

f:id:niwatako:20180301102016j:plain

ではこのクロージャーはなにでしょう

型が解決された段階のASTをみてみましょう

f:id:niwatako:20180301102031j:plain

f:id:niwatako:20180301102035j:plain

f:id:niwatako:20180301102059j:plain

f:id:niwatako:20180301102103j:plain

返り値はどんな型としても扱えるのです

これがなにを解決するでしょう

f:id:niwatako:20180301102118j:plain

IBOutletを!にすることがありますね

実際に使う段階ではnilにならないはず。非オプショナルで扱いたいですよね。

nilではなく意味のない値でもない

f:id:niwatako:20180301102218j:plain

ここに先程のものを使ってみましょう

Closure

ファンクションもクロージャーの一種ですしあまりにも大きな話題です

関数オブジェクトの生成に焦点を当てます

このように作ることはありますね

f:id:niwatako:20180301102302j:plain

1つめ

f:id:niwatako:20180301102317j:plain

引数を指定していません

NestedFunctionも同様。

2つめ

f:id:niwatako:20180301102340j:plain

インスタンスメソッドにも書けますね。実行主が未定でも書けます。

f:id:niwatako:20180301102357j:plain

イニシャライザでも書けます

オペレーターメソッド

f:id:niwatako:20180301102409j:plain

EnumのAssosiatedValue

f:id:niwatako:20180301102429j:plain

f:id:niwatako:20180301102442j:plain

AssosiatedValueを持つEnumを呼ぶのはEnumのstatic function を呼ぶようなもの AssosiatedValueを持たなければstatic propertyを呼ぶようなもの

f:id:niwatako:20180301102525j:plain

これも自然に見えるのでは

f:id:niwatako:20180301102538j:plain

クロージャーを引数に受け取るものを息をするように私たちは使います

様々な場所に存在していました。

関数の引数にクロージャーを渡すのは、@escapeを付けない限り、非同期で実行できなかったり関数の外の変数に保存できなかったりします。 クロージャーが関数より長く存在しないことを保証してくれます。

このように生存期間の制限がある引数があります。Inoutです

f:id:niwatako:20180301102642j:plain

f:id:niwatako:20180301102650j:plain

+=は内部で引数を書き換えるのでinoutが指定されています

関数が終わった後もこの引数を使うような書き方をするとコンパイルエラーになります

f:id:niwatako:20180301102730j:plain

f:id:niwatako:20180301102733j:plain

2回書き換えます。 関数に初期値が卵の変数を渡します

変数の値が変化すると変更後の値をプリントするようにしています

結果は 

f:id:niwatako:20180301102833j:plain

関数の中では使われるが、関数の最後に変数の値をinout指定の変数に渡します

関数の中のargは渡したtestStringとは別物です

f:id:niwatako:20180301102930j:plain

スリープさせて間に値を覗きます

f:id:niwatako:20180301102947j:plain

ところがdidSetを取り除くと

f:id:niwatako:20180301103011j:plain

関数が終わるまで書き換わっていなかったのが、書き換わってしまいました

f:id:niwatako:20180301103036j:plain

DidSetがなければコスト削減のために参照が渡されます。

引数として渡した変数の種類によって動作が異なるのです

書き込み中の変数を読みに行くことは避けるべきことなので、本来はこのような動作は気にしなくてよいのかもしれません。

しかし非同期処理で意図せずそうなることもあります。遭遇した不思議な挙動を探るのは楽しいですよね

inoutを使うとfunctionの終わりに値が代入されますが、終わりとはいつでしょう

f:id:niwatako:20180301103154j:plain

思い浮かぶのはdeferとreturnです。

deferとは、すべての処理が終わった後に実行されるブロック、で、

...本当にあっているのでしょうか?

思考の過程を大事にしながら得意性を探って終わりにしましょう

f:id:niwatako:20180301103240j:plain

f:id:niwatako:20180301103251j:plain

return inout defer のオンパレード

defer はスコープを抜ける直前に実行されます return する値を決定する処理を含みます

f:id:niwatako:20180301103331j:plain

0を渡すと、return x で0の返却が確定します

そしてdeferが実行され+1されます

f:id:niwatako:20180301103347j:plain

そしてinputの値が1 になります

これは成功するでしょうか

f:id:niwatako:20180301103425j:plain

定義前の変数を使うとコンパイルに成功します

しかしこれはコンパイルが通ります

deferまでに初期化されていれば変数を使えるのです。

f:id:niwatako:20180301103455j:plain

これはブロックが最後に異動しているのと同じです

f:id:niwatako:20180301103512j:plain

では

f:id:niwatako:20180301103529j:plain

didSetは何回呼ばれるでしょうか。 1回です。deferでの代入が呼びます。

initializerの中で値をセットしてもdidSetは呼ばれないはずですが、呼ばれてしまいます。

この挙動はバグとして報告されています。

初期化、開放処理をdidSet, WillSetにまかせていたら、または呼ばれない前提で描いていたら、意図しない動作になるかもしれません。

ほかにファンクションやクロージャー内で値を変更するとdidSet, willSetが呼ばれます。 deferはスコープを抜けると最後に実行される、では留められない存在に思われてきました

f:id:niwatako:20180301103713j:plain

f:id:niwatako:20180301103716j:plain

f:id:niwatako:20180301103729j:plainf:id:niwatako:20180301103753j:plain

慣れ親しんだからこそ見落としていたもの、慣れ親しんだからこそ理解できるものがあります。その楽しさをお話しました

f:id:niwatako:20180301103821j:plain

素晴らしい3日間になりますように。ご清聴ありがとうございました。

f:id:niwatako:20180301103824j:plain

面白かったら、ためになったら

  • はてなブックマークSwift タグをつけてブックマーク!
  • 「インターネットで生活を楽しく豊かにしたい」仲間を募集しています
  • Bitcoin: 3KGqXtR1ZaGVdkvcw8CCNrkDxDhdbZBYHL