今回は少し前回の補足をしてから、引き続き、個人的にお気に入りな技術ブログ「その Swift コード、こう書き換えてみないか」を眺めていくことにします。その中でも今回は「@ViewLoading
」についての話題から見ていく見込みです。自分自身は初めて知る機能でしたけれども、世間的には知られているものかどうなのか、そんなところも窺いながら観察できたらいいなと思っています。よろしくお願いしますね。
———————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #274
00:00 開始 00:23 今回は幽霊型の補足から 02:58 メソッドチェーンなら mutating 不要 05:08 メソッドチェーンで自分自身を書き換えたいなら、クラスを利用 06:03 所有権でコピーコスト削減可能かもしれない 08:19 Swift 5.9 のインストール 09:47 所有権を使ってコピーコストを削減してみる 17:02 何を以って「消費」とするのか、まだわからない 19:33 所有権の動きがまだ掴めない 22:27 所有権を活かせるのは、まだ少し先かもしれない 23:56 暗黙コピーするか、明示コピーするかの制御? 24:32 メソッドチェーンが流行ってくるかも? 28:10 所有権の動きは、内部に参照型を持つかどうかで変わってきそう? 33:37 クロージングと次回の展望 ————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #274
では、引き続き進めていきますね。「A1のブログ」の田中亜佑太さんの投稿に目を留め、非常に興味深かったので、その続きを見ていくことにします。ただ、その前に前回の内容について簡単に補足したいと思います。前回は「ファントムタイプ」について話しました。この概念は聞き慣れない人には少し異質かもしれません。要するに、ジェネリック型と型パラメーターを活用して型安全性を高めるという話です。難しい名前で説明するよりも簡単に説明すると理解しやすいかと思います。
具体的なコード例を見ていくと、型パラメーターとして Prepared
と BeforeInitializing
を用意し、prepare
が呼ばれた時に型パラメーターを Prepared
に変更し、最初の initialize
の段階では BeforeInitializing
として動作するようにしていました。このようにしてメソッドチェーンのように動作させて、必ず prepare
が通ったものを返すような安全なコードが書けるという話でした。
以前、ミューテートを使う必要があると話しましたが、これは勘違いでした。その関数型プログラミングの観点から言うと、ミューテーションは基本的に必要ないんです。ちょうど70行目のようなコードでは、ミューテートなしで動作します。戻り値も全てイミュータブルですので、このように自分自身を返すのではなく、新しいインスタンスを作り直す感じになります。
ここで、コピーコストの話に移ります。コピーコストを考慮するために、コピーをする場合も、init
を使ってプライベートな初期化を行う方法があります。例えば、self.value = value
のようにしてインスタンスをコピーします。
Swift 5.9 から導入された新機能、オーナーシップモデルを使うと、さらに効率的にできるかもしれません。具体的には consuming self
ファンクションのように使用し、性能を向上させることが考えられます。実際に試してみても、動作が安定するかどうか確認が必要です。
では、このツールチェーンを使って最新のSwift 5.9をインストールしてみましょう。ターミナルで swiftm
を使用してインストールします。具体的には、Swift5.9 の一番新しいバージョンをインストールします。それでは、インストール作業を進めていきます。 とりあえず待っている間に、ここから出てきてみますか。これでどうしようかな、状態を変えたいわけだから、とりあえずフォントを設定したときに x
の値が別の値になればいいみたいな風にすればいいですね。x
が 7 になるようにします。こうしたときにビルドが通るか確認しましょう。アンダースコア付きの x
でビルドが通りますね。
このコンシューミングをつけるかつけないかで、self
が書き換えられるかが変わります。これでビルドをかけると、self
がイミュータブルな状態から、コンシューミングにすると self
がミュータブルになります。なので、新しいインスタンスを作らなくても self
を返せるようになります。ツールチェーンの新しいバージョンにすると、フォントを通したときに self
が 7 になるはずです。
makeValue
で戻ってきた値が 7 になっているか確認しましょう。let value = makeValue
のようにしてビルドをかけると、ツールチェーンが変わった関係で違いが分かるかもしれません。ビルドが通るか確認したいですね。
ビルドが通らない理由を確認しましょう。プレイグラウンドの設定に関係するか確認して、makeValue
を使用している部分をチェックします。バリューのx
の値が 7 になっていることをプリントして確認しましょう。
具体的には、print
で値を表示してビルドを実行します。通常、期待通りの 7 が出るはずです。ビルドの結果を見ることで確認できます。もし0だった場合は、フォント設定のタイミングや順序を見直します。プリペアを行う前にフォントをセットしていれば問題ないはずです。
次に、カラーもコンシューミングファンクションにしました。すべてのコンシューミングファンクションでビルドをかけると、任意の戻り値をリターンできるようになります。リターンセルフのようにしてビルドをかけると、ビルドが成功するか確認できます。結果として、self
がミュータブルであれば構造体がコピーされずにそのまま返せるので、効率的です。
コンシューミングファンクションによって、self
を直接書き換えて戻り値にすることができます。戻り値の最適化によってコピー不要で値が返せるのがメリットです。このようにして、コードの効率を上げることが可能です。
他の例として、let tmpValue = value
のように一時変数に値を保持し、二段階で処理を行うことがあります。この場合でも問題なく、それぞれの関数呼び出し結果が期待通り動作するか確認しました。全体としてコンシューミングを使うことで、オブジェクトのコピーを避け、パフォーマンスを向上させる設計となります。
まだ理解が完全でない部分もあるかもしれませんが、基本的には全体の流れはこのようです。コンシューミングやself
の操作に関して疑問や詳しい人がいたら教えていただけると助かります。 このコンパイラーの動作が正しいのか、まだ実装が不完全なのかについてですが、私自身もまだ完全には理解していません。例えば、let result = result
と書いている部分についてですが、これはコピーされるので問題ないと思います。逆に、let result
を var
に変えたり、result
を使ってコンシュームすると、その後 result
はもう使えなくなります。
例えば、変数 result
に対して result = 100
などの操作を行うと値が上書きされる可能性があります。しかし、コピーされてしまうので、その123だなんだのが想定通り動かない場合もあります。まだ理解が完全に進んでいないので、試行錯誤してみる必要があります。
コンパイルが通らない場合や意図した動作をしない場合、それはまだ実装が進んでいないか、私の理解が間違っている可能性もあります。しかし、consuming
関数を使うことで、変数を消費してその後は使えなくするという機能が売りになります。この機能は、コピーができない値に対しては特に有効です。
Swift 6に向けて、このオーナーシップの概念が全面的に整備されると、もっと本領発揮するのではないかという期待があります。現在でもオーナーシップを設定しなくても、パフォーマンスが若干悪くなる程度なので、急いで学ぶ必要はないですが、知っておくと面白い機能です。
consuming
関数を使うことで、自分自身のメモリを再確保しないで済むため、パフォーマンスが高くなると言われています。しかし、中途半端なコピーをしてしまうとあまり意味がありません。
所有権とメソッドチェーン、ファントムタイプなどについては、この所有権を活かすやり方として今後再び注目されるかもしれません。最近では SwiftUI
でもメソッドチェーンがよく使われています。
例えば、swift
var body: some View {
// 具体例がここに入る
}
のように、View
やBody
を連携させてコードを書くことが多いです。
このように、Swiftの機能やオーナーシップを活かすことで、今後の開発がもっと効率的になる可能性があります。まだ完全に理解していない部分もありますが、これからも勉強を続けていきたいと思います。 実際のボディの定義は「ボディ」の部分で、先ほど説明したボディ型のことです。ボディ型をアソシエイトタイプとして使用することで、不透明な型some View
をボディに適用するという構造になっています。この方法でsome View
をボディに適用することで、チェーンのように返すことが可能です。これにより、不透明な型を巧みに使うことができ、多分良い感じに実現できるのではないかと思います。
具体的には、メソッドチェーンを使ったファントムタイプのようなものがうまくいきそうだと感じるのですが、これは確証のない直感に近いものです。ただ、非常に興味深いことになりそうですので、試してみる価値はあるでしょう。
一度、Xcodeだけでなくコマンドラインでもコンパイルしてみることにします。まずSwiftのバージョンを確認しますが、4.8.1と5.8.1があり、バージョンの違いによってはうまくいかない可能性もあります。実際に試してみるといくつかのエラーが出るかもしれませんが、以下のようにコードを調整して試してみます。
例えば、String
にエクステンションを追加し、コンシューミングアンプ(consuming amp)を使います。
extension String {
mutating func appendCustom(_ value: Int) {
self += String(value)
}
}
また、以下のようなコードで試してみます:
var value = 0
value += 1
print(value)
このコードが正しく動作するか確認します。もしエラーが出た場合は、公式ドキュメントやプロポーザルのサンプルコードを参照して、正しい書き方を再確認します。
ここで紹介した方法であれば、おそらくイメージした通りにチェーンのように処理が進むはずですが、メソッドチェーンに関してもう少し詳しく調査することが必要かもしれません。今のところ、パフォーマンスに関しても大きな問題はないとは思いますが、確証は得られていませんので、さらに検証する必要があります。
今回はここまでにしておきます。次回は新たな話題であるビューローディングの話に進みたいと思います。それではお疲れ様でした。ありがとうございました。