https://www.youtube.com/watch?v=1HOPnczZir8
今回は、前回に プロパティーの追加処理
を眺める中で思うように動かなかったところがありましたけれど、その辺りの詳細がわかったので、まずはそれについて見ていきますね。そしてせっかくの機会ですので、今回は A Swift Tour
から少し離れて、その先に綴られている inout
周りの特徴のところを眺めてみようと思います。それが終わったら再び A Swift Tour
の続きに戻っていきますね。どうぞよろしくお願いします。
———————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #52
00:00 開始 00:53 willSet と didSet のおさらい 01:26 前回に検証失敗したコード 07:25 入出力変数への非同期アクセス 13:15 入出力引数まわりの公式見解 14:20 言語ガイドにみる入出力引数 14:53 関数とは 17:23 入出力引数 18:49 呼び出し元への予期しない影響を防ぐのが狙い 20:04 入出力引数の使い方 22:39 言語リファレンスにみる入出力引数 22:59 宣言とは 25:48 入出力変数についてもう少し詳しく 26:26 Copy-In Copy-Out 28:45 By Reference と By Value Result 30:31 以前はライトバックと呼ばれていた 32:18 参照渡しは最適化の副産物 35:15 Call by Copy Restore 37:08 最適化に影響されないコードを書くこと 38:49 適切な動作はプログラマーの務め 41:08 入出力引数に渡された元の値は使えない 43:16 & マークは入出力引数として渡す目印 45:21 入出力引数の重要事項 47:44 入出力引数の使用例 48:19 入出力引数のまとめ 50:25 質疑応答 54:25 次回の展望 ————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #52
はい、では今日は前回お話しした willSet
とdidSet
について、試してみた結果と思ったように動かない部分を調べたので、その辺りをお話しします。せっかくの機会なので、inout
修飾子に関してももう少し具体的に見ていこうと思います。ですので、若干順番を変えて、今日は「A Swift Tour」からは少し離れて、別のランゲージリファレンスなども見ていこうと思います。
まず、willSet
とdidSet
について、前回いらっしゃらなかった方もいるので軽く紹介します。
保存型プロパティには、willSet
やdidSet
を設定することができ、これを使用すると値を保存する前後、つまり直前と直後で任意のコードを実行することができます。この仕組みがマルチスレッドで使ったときに挙動が変わるので、セッターとゲッター的な動きを見せるよという話を検証してみたところ、思ったのと違う挙動になっていました。しかし、これは結局、自分の勘違いでした。なので、今まで通りの感覚で捉えてもらって大丈夫です。その確認をちょっとしていきます。
先日のストラクトでdidSet
とwillSet
を定義しましたが、全く問題はないです。しかし検証をシンプルにするために、ここでグローバルな変数を使うと分かりやすいので例として紹介します。
例えば、Value
というInt
型の変数があります。これをマルチスレッドでアクセスするような場合について考えます。例えば、mutating
な関数modifyValue
でinout Int
を使います。この関数内でValue
に100を代入するようなコードを書き、その変更をマルチスレッドで検証するために、Dispatch
を使って待ち時間を作ります。
inout
の話をしていますが、didSet
やwillSet
とは関係ない話も含まれます。inout
を使うと、引数を通じて値の読み書きが可能になるという仕組みです。例えば、Value
に最初0
を入れて、定義したmodifyValue
関数にValue
を渡すと、その前後で戻り値を受け取らずに値が変化します。
プレイグラウンドが動くか試してみます。結果として、10行目で0
が得られて、12行目では100
が得られるという動きを見せるはずです。しかし、プレイグラウンドが動かないので、Xcodeを再起動しますね。
inout
の特徴として、引数に渡したときにローカル変数に代入され、関数が終了するときにその値を元の場所に書き戻すという動きをします。しかし、これは最適化の関係で動きが変わる場合があります。didSet
やwillSet
が伴う変数では、その最適化のされ方が変わり、最適化されないなどの動きを見せます。
プレイグラウンドがやはり動かないので、新しいプロジェクトを作ってグローバル変数で試してみます。このパターンで試すと最適化の違いが見えやすいので、その辺りを検証します。
プレイグラウンドがなかなか動かないのは不思議です。何もしていないのに動くこともあれば動かないこともあるので、その差がどこから生まれるのかまだ分かりません。 とりあえずこちら。最初が0、次が100、バリュー戻り値を受け取っていないのにこんなふうに変化します。これをマルチスレッドでやったときに、例えば DispatchQueue.global()
ここはスレッドで普通に待てばいいんだと思います。これは下で使うから持っていっておいて、ここでは純粋にスレッドをスリープさせて、例えば5秒ぐらいこうやって待たせます。そして、その待たせてる間に、例えば1回念のためスレッドを1秒ぐらい止めて、ここで止めます。ここは関係ないね。
とりあえずここで非同期で modify
を読んで、それで value
を渡してあげて、ここでね、とりあえず実行が始まることを確実にしたいのでちょっと待ってあげて、ここでその modify
スタートと modify
ウェイト、 modify
エンド。こうしてあげて、それでね、ここでね、 modify
を読んだから value
を表示する。終わりという風な実行をあげると modify
が始まって、 modify
待ちの状態でこのプリントが実行されていると分かりにくいね、このプリントが明らかに実行されたことがね分かるように。これで分かりやすくなります。
プログラムが終わっちゃってちょっと分かりにくいので、ここで一応5秒待ちますかね。こうしてあげるとね、こうするとスタートで待ってる間にプリントが表示されて、あ、 wait
関係なかった。あ、終わった終わった。関数が end
を迎える前にプリントした値が100になっている。これはここの wait
の前に value
を書いたからという風な動きになっています。これがね、人によっては直感的じゃないかもしれないですけど、ここにね bib set
みたいなのがあったりすると、特に何にもしていなくてもいいんですけど、ここの100って出てくるところが別の値になる、別の値というか0になるっていうね。
これがマルチスレッドを通して同時アクセスした時の bib set
があるかないかで挙動が変わるという例です。結構ね注意が必要そうな動きが見られると思うんですけど、こんな感じでね、この bib set
を使うことによって、このインアウトの挙動がね、インアウトに対して bib set
を伴う変数を渡した時に、その時には value
の値がローカル変数に確保されて、その値が modify
が終わった後に反映される。それで bib set
がない時にはそのまま直接書き込まれるよっていうね。
そういったお話がここにもプリントしておいた方がわかりやすかったのかな。そうね、すごい初心者殺しですよね。初心者殺しだからイノセントに使います。でその後に100っていうのが出てくるという風な bib set
のパターン。とにかく modify
が終わるまで変更が得られない。でも bib set
が単になくなるだけで modify
が終わる前から得られちゃうっていうね、100がね、そういう動きを見せる。
で、こうやってね試してみるとすごい罠だなぁと思うじゃないですか。The Swift Programming Language をね今回改めて勉強会、前回終わった後に復習してみたら、なんかねこのあたりしっかり書いてあるのよ。それでそれを読んでみると、この感覚がいかに間違いなのかっていうことがわかる、とても面白い文章がありましたので、それをねせっかくなので見ていきたいなと思います。
じゃあね、どれから行こうかな。いろんな場所に書いてあるんですよ。インアウトについてね。このあたりの一番最初に出てくるところから、とりあえず見ていきます。まずは、こちらか。はい、The Swift Programming Language の中の Functions
ランゲージガイドの中にね Functions
があって、その中にねインアウトの話が書かれてるのを。とりあえずそこだけピックアップしてきたんですけど、とりあえず関数ね、関数の紹介も一応ざっくりと喋っていきますか。
特定のタスクを実行するための自己完結型のコードの塊ですよみたいな、ちょっとわかりにくい紹介がされてましたけど、役割を持った関数、役割を持った機能の集合体。それをまとめるのが関数で、それに名前をつけてあげることで、このコードの塊はどんな機能だよっていうことが説明付けられて、それを呼び出すことでそのコードの塊を実行できるよっていう機能ね。関数っていうのが。その関数を呼び出すっていうことをね、言葉では関数呼び出し、コールっていう風に言います。みたいなことが書かれていました。 それでね、関数の構文はいろんな表現ができるということが書かれていて、引数ラベルを省略することもできるし、引数ラベルを付けることもできるし、引数に既定値を添えることもできるし、入出力引数を使って渡した変数を関数の実行完了時に変更できるといったことが書かれていました。この一番最後の入出力引数というのが、今回注目しているインアウトのお話になります。
入出力引数のポイントとしては、渡した変数を関数の実行完了時に変更することができる点で、ここがとても大事なポイントです。そして、これが基本原則になっているということを意識して使わないといけないよ、ということがこの後お話しされていました。
関数は関数型があるよ、とかはまた今度でいいですね。これは飛ばして、インアウトパラメータ、ここですね。関数の引数と戻り値のセクションにちょっとしたことが書かれていまして、インアウトパラメータ、入出力引数というものがあります。基本的に関数の引数は定数として扱われるんですけど、これを書き換えたいとき、とりわけ受け取った変数そのものを書き換えたいときには、定数になっていてできないので、入出力引数というものを使ってそれを実現するという方法が取られます。
こういった仕様になっている理由としては、関数の本体の中でこの引数の値を間違って書き換えてしまって、それが関数呼び出し元に対して影響を与えてしまうというミスを防ぐために引数が定数になっているというポイントがあります。これが特に今までのObjective-Cのオブジェクト思考や他のオブジェクト思考でも結構同じ感じですね。引数に渡したオブジェクトの内容をうっかり書き換えておかしくなってしまう、全体を見たときにそういった問題が多発するわけです。
このあたりが保護されていないと、それで今までのプログラマーはイミュータブルクラスというものを生み出して、不要意に書き換えられないインスタンスを渡してあげる。それによってマルチスレッドの安全性を確保する、みたいな方法を取っていたわけです。それで構造体を使うことによってこの効果で、定数を平数とすることによってうっかり書き換えることを防いでいます。ただし、意図的にそれをやりたいときに入出力引数というものが使えますといった仕様になっています。
それをするためには引数の型の前に inout
を記載します。そうすると入出力引数になります。入出力引数の扱われ方はとても意識しておかないといけません。順番としては3つのフェーズで構成されています。まず関数内に渡した引数の値が得られます。関数内でその値を変更します。入出力引数ということで、通常は変更します。そしてその後、元の値を書き換えるために関数外に渡されます。この1番目が関数呼び出しの直後に行われ、3番目が関数呼び出しが終わった時に実行されるという動きになります。
このあたりを捉え違えないようにすることが重要です。この詳細については、Language ReferencesのDeclarationsセクションを見てくださいと書かれていますので、ここも見ていきたいなと思います。
それではまず見ていきますかね、これを。Declarationsセクション、Language Referenceの同じくSwift Programming Languageに書かれているんですけど、そのLanguage Referenceの中にDeclarationsという項目があり、ここで触れられています。せっかく見ていくから、宣言(Declaration)についても軽く見ていこうかと思いますが、まずプログラムに名前や構造を導入するもの、要は変数宣言だったり型の定義だったり、そういったものを宣言と呼びます。他にも他のモジュールに入ってる名前や構造を取り込むときに使うインポート文も一応宣言に入る、といったことが書かれていました。 そして、この「宣言」という言葉と「定義」という言葉の二つが存在します。この二つを区別する方もいますが、中には「宣言」という言葉で両方を指す場合もあります。
この二つの言葉の違いは何かというと、宣言は実装を伴わないのに対して、定義は実装を伴うという差があります。C言語やObjective-Cをやったことのある人だと、イメージしやすいかと思いますが、ヘッダーファイルに書かれているものが宣言で、Cファイルや.mファイルに書かれるものが定義になります。
Swiftでは、ヘッダーと実装ファイルに分かれていないため、例えばプロトコルのボディの部分は実装を伴わないため、厳密には宣言となり、プロトコルエクステンションのように実装が含まれる部分は定義と捉えられます。ただし、Swiftではこの区別はあまり重要ではなく、一色他に「宣言」として良いでしょう、と書かれてありました。
その中で、inoutパラメーターについても関数宣言の部分で触れられていて、次のように入出力引数inoutが渡されると書かれています。関数の呼び出し時に引数に渡された値が複製され、関数内でその複製に対して値を変更し、最後に関数から戻る際に複製された値が元の引数に代入されるという基本原則があります。
具体的には、関数内で引数に渡された値がまずコピーされ、このコピーに対し変更が行われ、関数が終了する際に元の値に変更が反映されます。これが基本的な動作原則です。
例えば、コードの21行目のvalue
が最適化されずに動作すると、本来であればvalue
は0のままであるべきですが、実際には異なる動作をします。これは、最適化による動きの違いです。
この一連の動作を「コピーイン・コピーアウト」または「コールバイバリューリザルト」と呼びます。一方、渡された値をそのままコピーせずに書き換える動作を「コールバイリファレンス」と言います。
まとめると、コールバイバリューリザルトとコールバイリファレンスでは動きが全く異なる点が重要です。コメントでも指摘された通り、inout引数は参照渡しではなく、値結果渡しとなります。これは、値を直接返すのではなく、一度コピーしてその複製に対して操作を行い、最後に元の値に反映されるという動作です。この動作原理をしっかり理解しておくことが重要です。 そのわけで、このあたりの日本語訳がしっくりくるものを作れると、日本人同士で話しやすいなと思うんですけど、「コールバイバリューリザルト」って意味が分かりにくいですよね。多分、英語が分かる人には分かるかもしれないですが、なんとなく「バリューリザルト」って特別な言葉のような気がします。
プログラミング界隈では、とにかくこの「値を渡して最後に書き戻す」というのが「バリューリザルト」なんですね。余談ですが、昔この動作のことを「ライトバック」ってこの本で表現していたはずなんです。でも、その表記がなくなって、今では「コピーインコピーアウト」と言うようです。
こんな風に、説明で使う言葉が変わることが時折見られます。以前は「変換イニシャライザー」のところでもラベル名を省略して値が完全に同等のものとして型変換が行われることを「フルウィズタイプコンバージョン」、つまり「全幅変換」と呼んでいたんです。でも、今ではこの呼び方が「バリュープリザービングタイプコンバージョン」、つまり「値を保全する変換」と言うように変わっていて、より分かりやすい言葉になっています。
時折、こうやって説明の言葉がブラッシュアップされることがありますので、この本を読み返して新しい言葉に置き換えていくのも大事です。些細なことですが、大事なところです。
そうそう、コメントでいただいた通り、これから話にじっくり出てくるんですが、「インアウトパラメータ」の結果として「コールバイバリューリザルト」が原則であって、端的に言えばそうです。このあたりのお話がこれから出てくるんですが、これがまた知っていないと無理という話になってきます。
「ライトバック」の方がイメージしやすいですよね。自分もそう思います。でも、SQLとかの分野では「バリューリザルト」という言葉が出てくるようです。「バイバリューリザルト」「バイリファレンス」「バイバリュー」というふうに、ソフトウェア工学的にはおなじみの言葉らしいので、慣れていくのがいいと思います。
今回の冒頭に挙げた「挙動が変わる」というお話は、入出力の最適化が働くことによって起こっている事象になっています。引数がメモリーの物理アドレスに保存されている値のとき、これがキーポイントです。要は、willSet
やdidSet
を挟むと暗黙的にゲッターやセッターが生成されるといったお話で、挙動が変わるよということです。
そういった状況になると最適化は行われないのですが、具体的にメモリーに直接読み書きを働かせれば済むような場合には最適化が起こります。具体的には、copy by reference
になるという最適化が図られます。その前にZoomのコメントに反応していきたいと思います。
「アンセーフポインター渡し」と「コールバイリファレンス」はほぼ同じ感じですよね。また、分散システムの授業でお話があった「クライアントサーバー間での値のやり取りでインアウト的な、でもコピーストアでも呼び出し」ですね。なるほど、「コールバイコピーリストア」。確かに「リストア」という言葉を使うと「ライトバック」的なイメージが浮かびますね。分かりやすい言葉ですね。
こうやって言葉が乱立しているところをみると、分野ごとに好き放題やっている感じがしますね。でも、だんだんと「コピーバイバリューリザルト」という言葉に寄っていってるのかなという感じがします。それでも、結局のところは何かと補足しないといけない感じも残っているので、適切な言葉を使えばいいかもしれないですね。「コピーバイバリューリザルト」か「ライトバック」か「コールバイコピーリストア」。でも、「コピーリストア」っていい言葉ですね。 「コピーするよ、それを戻すよっていうのがわかりやすいので、そっちを使っていきたい気もしますけれど、Appleに習うと『コピーバイバリューリザルト』。まあいいや。その辺りはそれぞれが使い心地のいいものを選んでいったらいいでしょう、きっとね。
ここの今表示中のスライドにとても重要なことが書かれています。この最適化動作は『コピーバイリファレンス』ですよ。要は参照渡しっていう形に最適化が図られます。そうすることによって、コピーイン、コピーアウトの要件を満たしつつ。この要件って何かというと、呼び出し時に値が関数内に渡されて、その関数内で書き換えられた値は呼び出し後に渡した元の値が変わっているという要件を満たしているということしか言っていない。
要は、このデクラレーションの説明の前に見せた資料がありますよね。ちょっと切り替えてみますか。もっとシンプルだった説明のところ、こちら。関数内に渡されるよ、関数内で変更されるよ、それが関数外に渡されるよっていうところまで、これを満たしているよって言っているだけで、その中で複製が取られるよみたいなところはここで言われてないじゃないですか。あくまでもこのレベルのことが要件を満たしているという説明になっていて、最適化が図られることによってオーバーヘッド、複製のオーバーヘッドを削減できるよっていう最適化が図られる。ただしですよ、この参照渡しによる最適化には依存されないで、コピーイン、コピーアウトの原則にのっとって正しく動作すること、それをルールとして課されているっていうところが知らなかったところですよ、自分の中では。
なので、要はコピーイン、コピーアウトとして的確に動くコードを書かなければならないっていうルールがプログラマーに課せられているので、インアウトを使うときには注意しましょうというお話になるけど、これを課せられているのに『フォールバイリファレンス』はそれが行われていくっていうのは結構無茶なことを言ってるなっていう感じがして。これが課せられているっていうことは、最適化が図られていてもちゃんとコピーイン、コピーアウトを成り立たせるために内部でコピーしないといけなくなるじゃないですか。それが課せられているということは、それって最適化が無駄だし、もしそこでディープコピーを伴う変数が渡されてきちゃったとすると、2回コピーしないといけない状況になってきてしまって、それって無意味な最適化と無意味なコーディングルールだなというような印象を持ったんですけど。
これはまた別のルールが課せられていることによって、最適化が生きてくるっていうちょっとややこしいお話が次に書いてある。ここが超大事なことがさらっと本の中で書かれてるんですけど、『入出力引数として渡された値の元の値には、その関数内で使えたとしてもアクセスしないこと』っていうのがルールとして明記されている。これしか書かれていない。あくまでもマルチスレッドについては触れられていないですね。でもこの辺りをなんとなく拡大解釈していくとなのか、それとも自分の中で勝手に想像膨らませているだけなのかわかんないですけれど、同じというか、入出力引数に渡した元の値には、その関数が有効である範囲限りはアクセスしないことっていうのが求められている。それによって、関数内で自由に値を書き換えたときに、それが最適化が図られたとしても安全に値を扱っていけるっていうお話になってくるんですよ。
これを守ってあげないと、値の同時アクセスが発生することになって、メモリの安全性の保証が違反されてきてしまうっていう問題が起こってきて、その辺りについてはメモリの安全性や配達性について、言語ガイドの『メモリセーフティ』のところに書かれていますよっていうことが書かれてあった。今、コメントにもらったことが次に書かれてるんですけれど、インアウトキーワードって &
を使って渡すわけですけれど、参照渡しのイメージすごい強いじゃないですか。でも、Swiftでは『コールバイバリューリザルト』なんだよっていう。ここを意識しないとSwift的におかしいインアウトの使い方になるよっていうことが、この今見てるスライドに一生懸命書かれてるわけですよ。これ読んでないとわからないしみたいなことを思いながら自分はちょっと読んでたんですけどね、今回。
なので特に、参照渡しを知っている人、とりわけ他の言語で使っていた人、さらに言うと、とりわけC言語で使っていて『参照渡しといえば &
ですよ』みたいな人にとっては、この辺りが認識の違いが生まれてきて気をつけたいポイントになってる。
あと、キャプチャーのお話か。キャプチャーのお話をしているとちょっと長くなりそうだから、インアウトの &
の方、その辺りの方が今回は大事だな。Swift的にはどこかに具体的な話が書いてあって、ここかな。さっきのファンクションズの話に戻るんですけど、デクラレーションに行きましたけどその続き、さっき見た &
でしたっけ?とりあえず変数の前に &
を期待するよっていうことですけど、この &
を書くっていうのは、あくまでもその関数内で変更される可能性を示すために &
を書いているだけであって、『参照渡しをしているよ』っていう表現ではないっていうのがSwiftでは大事なところになってきます。
&
を書くことによって、入出力引数として渡しているよっていうことを呼び出し元で明記しているに過ぎないっていうね。で、渡した値はインアウト、入出力引数、コピーイン、コピーアウトにのっとって処理されるよっていうことがうたわれている。
けれどそのインアウトを扱うときには、同時アクセスをしないように配慮しないといけない。その配慮は基本的には関数側でコピーイン、コピーアウトにのっとることによって、本来だったら行われるはずなんですけど、Swiftでは渡した変数がメモリに直接読み書きするものであったときにはコピーバイリファレンスに最適化されるため、呼び出し元でそれに対して配慮しないといけないよっていうのが、読み書きの競合が起こっておかしなことになるので注意してくださいねということが書かれているので気をつけてください、というのが今日の大事な伝えたいことになります。」 こうやってinout
っていうキーワードを使ってパラメータを取ると、そのコピーイン・コピーアウトが動くよっていう話で使うときには&
を添えるけど、それはあくまでもinout
パラメータに変数を渡しているよっていう主張に過ぎないよというのを今日は覚えて帰ってもらう範囲になると。
inout
パラメータはあくまで補足事項ですけど、関数の戻り値とは異なるものとして存在しているよって書かれています。具体的にどう違うかっていうところまでは書かれていなかったんですけど、多分3行目のことを言いたいんですかね。戻り値リターンと同じようにinout
パラメータも関数がスコープ外に影響を与える、要は副作用を及ぼす手段の一つですよっていう感じ。どっちが優れているとか、そういう話ではなくて、リターンバリュー戻り値のほうが直感的かなとは思うんですけど、いろんな手段のうちの一つですよということです。
こんなふうに書かれていまして、メモリーセーフティのところ、さっき競合についてはメモリーセーフティのところを見てねみたいに書かれてましたけど、そっちもちょこちょこっと簡単なことが詳しく書かれている程度で、またいずれそれを見る機会があったときに見ていけばいいかなと思うんですけど、こんな感じです。inout
なので総まとめ的な話になると、モディファイに渡したバリュー、これを呼び出しが終わる前に使うっていうことは避けましょう。なのでこの検証、コピーバイリファレンスになるよねみたいなことを言いながらこういう検証をするっていうのはSwift的には間違いだということがとりあえず分かった。こういうふうな挙動の変化に依存することのないコードを書くっていうのがプログラマーの役割になっていったということが分かったので、inout
は結構怖い機能ですねというのが分かったので、これを踏まえて使っていくようにしましょうという話なんですけど、個人的にはあんまり使いたくないなというふうに思いました。
どうですかね、こんな話を受けてinout
を使っていきたくなる人がいるか。個人的には、inout
を使う設計をした時点で負けよう感じです。負けまでいきましたね。そうですね、やっぱり与え方で与え方を受けて、加工した与え方を変えるみたいな感じが一番良くて、直接インスタンスしなきゃいけない例ってそんなにないような気もしていて、楽したい場合とかにたまにありますけど、それでも与え方は変えて、改変する場合は新しいものを作るっていうほうが全てにおいてシンプルなので、多少コストがかかってもそっちやっちゃうかなって感じですね。
そうですね、なのでこういったところもモディファイドにしてinout
じゃないにして、それでin
と返す。それを使うときに受け取るよっていうことですよね。こうやって非同期の場合でも、今コメントでいただいたようにコンカレンシーを活用してあげて、async
にしてそれでawait
で受け取るみたいな、そういったふうにも今は特にできるようになったので。なのでパラメーターに渡したものを戻り値として受け取るよみたいな、いうふうにしてあげるっていうのが、コンカレンシーによってさらに書きやすくなってきたので、確かにinout
を使うよりも戻り値にしましょう。それが時間のかかるものだったとしたら、await
やasync
を使って、この書き方はまたいろいろとあるんでしょうけど、もう少しブラッシュアップはしていくにしても、こういった形で戻り値で受けましょう。もし複数inout
で受けたいものがあったとしても、マルチプルリターンタイプ、要はタプルで値を複数返せるんで、そしたら受け手としてもバリューともう一つとっていうのをタプルで受けられるし、みたいなふうに工夫していったほうがinout
を使うよりは確かに賢そうですよね。しかもこの書き方でちゃんとライトバックができている、しかもライトバックが保証できているっていうことを考えると適切だなと、そんなふうにちょっと思えたりも確かにしますね。
じゃあ時間になりましたので今日はこの辺りで終わりにして、次回はまた引き続きSwiftツアーの続きに戻って別のことを見ていこうと思います。はい、ではお疲れ様でした。ありがとうございました。