Page icon

第312回

2023/10/23
Empty
Automatic Reference Counting
Empty
Empty
Empty
Empty
Empty
12 more properties
本日は、ここ数回に渡って見てきたサンプルコードをもとにして、いよいよその 
クロージャー
 における 
強参照循環
 の解消方法についてのところを見ていく回になりそうです。よろしくお願いしますね。
——————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #312
00:00 Start 01:37 キャプチャーリストを使った強参照循環の回避 02:32 キャプチャーリスト、使ってる? 03:01 キャプチャーリストとは 04:30 キャプチャーリストと生存期間 06:56 self の明記が求められる場面 08:03 基本的には self の省略が推奨される 09:06 平均値を計算するメソッドを作ってみる 11:23 Sequence の count は取れる? 14:17 underestimatedCount は大雑把な値を返す 16:16 整数値で一般化すると 16:48 BinaryInteger に準拠した型は相互変換が可能 17:43 数値で一般化してみる 22:27 1 から n までの合計を算出する方法 26:52 コードで self を省略したときの読み心地 30:10 クロージングと次回の展望 ———————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #312
始めていきましょう。今日は、今までサンプルコードを書きながらクロージャーにおける強参照が原因で循環が起こる可能性について見てきました。実際に循環が発生した際にどのような手法でこの強参照循環を解消していくのかというセクションに進んでいきます。これは有名なトピックなので、プログラムを書いている人にはそんなに難しくないと思いますが、まだ経験がない方にとっては重要な内容です。最近は自動的に処理されることが多く、あまり目に見える形で勉強することはないかもしれませんが、そういった機会も得られるかと思います。
まずはクロージャー定義の一部として、キャプチャリストを定義することで強参照循環を解消する方法を見ていきます。つまり、クロージャーとクラスインスタンスの間で強参照循環が発生し、クラスインスタンスがクロージャーを保持している場合に、このキャプチャリストを使用することで解消できるということです。キャプチャリストは、クロージャーにうまく馴染む機能ですが、最近ではあまりキャプチャリストを使わずに閉じ込められる場合が多いですね。それでも明示的にキャプチャリストを書くことがありますが、強参照循環を解消するために用いることが多いです。
キャプチャリストでは、クロージャー本体内で一つ以上の変数をキャプチャする際のルールを定義します。キャプチャした時点で、変数が複製され、別の変数に代入するのと同じような動作をします。このように単にルールを決めるだけでなく、定数に保持しルールを同時に決めることで、より自由にキャプチャリストを利用できるようになります。
また、キャプチャリストの大きな特徴として、キャプチャするそれぞれの参照を強参照ではなく、弱参照や無参照に変えることが可能です。どちらが適切かは、コードの状況や環境に依存しますが、基本的にはライフサイクルが重要です。クロージャーがキャプチャしているインスタンスが長寿命か、クロージャー自身が長寿命であるかによって異なります。クロージャーが途中で消える可能性がある場合には、ウィーク参照が理にかなっています。これに対して、キャプチャしているインスタンスが先に消える可能性がある場合には、弱参照を使います。
そして、全く異なる場面で、クロージャーを識別しやすくするために実際のコードを書くことも考えていますが、まずは基本の考え方を押さえることが重要です。 Swiftの言語仕様に関するお話ですね。クロージャー内で
self
のメンバーを参照する時には
self
を明示的に記述することが必要です。これは、コード内で
self
に依存していることを再認識させるためのコンパイラーの仕組みですね。Swift APIデザインガイドラインにもあるように、通常は
self
を省略するのが一般的です。ただし、クロージャの中ではコンパイラーに
self
を書け、と指示されるのでそこは区別が必要です。
Swift以外の言語、例えばC++やJava、C#などでは
self
this
を明示的に書くことが推奨される場合が多いです。私自身もそういった他言語での経験から、Swiftでしばらく
self.
をつけて書いていましたが、後にそれをやめてから、
self
がない方がすっきりしていると感じました。
iOSの例を借りて、累計を計算するコードを書いてみましょう。より一般的なジェネリックなものとして
Sequence
を使いましょうか。
Element
がある場合、例えば
Int
型の場合、累計を求める際には、
reduce
を使うことがあります。
reduce
を使って初期値0から足していく書き方が思い浮かびます。
reduce(into:)
を使うと効率的な場合があるので、それを採用することも考えられますね。
累計を求める際には、このように書けます:
extension Sequence where Element: Numeric { var sum: Element { return reduce(0, +) } }
これはジェネリックにしているので、どんな数値でも対応できます。ただし、平均を求めるためには
Collection
でないと
count
が使えない問題があります。
Collection
であれば
count
プロパティを利用できますが、
Sequence
でのカウント取得は少しやっかいかもしれません。
また、何かとコーディング支援のツールが動いていないように感じることがあります。例えば、時々Copilotのようなツールが動作していないときがありますね。それを再起動してみたりして、うまく使えるように工夫していきましょう。 準備ができたようですね。さて、プログラムの話を続けましょう。基本的に、普通に
value
とカウントを取ることができます。ただ、動作させられなかった部分もあります。例えば、
MacOS
で行うことを考えます。コンパイラーの負担を少しだけ軽減して、プログラムを進めていきます。また、
エスティメイティブカウント
がちゃんと取れるか試してみます。たとえば、
Equence
などにした場合には、どうなるでしょうか。おそらく、カウント的な結果になりますね。
不透明なパターンであれば大丈夫そうです。というのも、
Any
AnySequence
を使えば、このカウントが取れるか試してみることができます。他に、
シーケンス
で何ができるかというと、例えば、手軽なものとして何がありますかね。
AnySequence
のインタレーターを取るなどがあります。
makeIterator
を使ってインタレーターを生成し、何かしらの理由でカウントが隠されてしまうと、0が取れることがあります。これでは意味がないので、確実にカウントが取れることが重要です。
estimate
が取れれば、要素数が確実に把握できるということですが、本当に取れているかは確認が必要です。仕様によれば、
厳密に
要素数を保証するものではないため、例えば、キャッシュ用のメモリ割り当ての事前計画など、パフォーマンス向上のために利用することがあります。しかし、規定の実装では0が返されることもあるため、使い道が限られます。これを平均算として使うのは適していないので、シーケンスでできることはサム程度です。
上記を踏まえて、エクステンションとして、例えば
Collection
Element
Int
の場合に
average
を計算する方法を考えます。以下のようにコパイロットが書いてくれたコードを参考にします。
extension Collection where Element == Int { var average: Double { return isEmpty ? 0 : Double(reduce(0, +)) / Double(count) } }
このように、エレメントが整数である時に平均を求める処理を追加することができます。これにより、シーケンスの制約を超えて、より具体的な計算が可能になりますね。 申し訳ございませんが、文字起こしされたテキストがありません。テキストを提供いただければ、それを基に整えた文章を作成しますので、もう一度お送りください。 Swiftの言語仕様について、シーケンスなどを使った汎用的なコードの書き方について説明します。特に整数型については、Swiftの標準型は全てバイナリーインテージャーに準拠しています。これにより、様々な整数型で互換性があり、効率的に操作が可能です。
例えば、バイナリーインテージャー型では、他の全てのバイナリーインテージャー型からの変換が保証されています。しかし、コンパイルエラーになるケースがあるなど、完全に落とし穴はないわけではありません。それでも、エレメント型でうまく揃えるようにすることが重要です。
数を扱う際、特に平均を計算する場合には、型変換が必要です。整数で計算すると精度が失われるため、ダブルに変換して計算することが多いです。計算の途中で要素数も考慮したくなります。例えば、整数同士の割り算をした場合、結果の精度が悪いときにはダブル型に変換して対応することがあります。
空のコレクションで平均を計算しようとした場合、クラッシュしてしまうことがあります。この点に注意しつつ、計算の際にはエラーチェックも忘れずに行いましょう。
また、汎用的なプログラミングを行う場合、Swiftのプロトコルを活用します。例えば、
Numeric
AdditiveArithmetic
といったプロトコルは、それぞれ足し算や数値操作ができる保証を持っています。これらを適用することで、さらに汎用性の高いコードを書くことができます。
このようにして、コードを汎用化することによって、特定の型に依存しない実装が可能になります。ただし、目的によって最適な型変換やプロトコルの選択を行う必要が出てきますので、開発の際には細かく設計を見直すことが重要です。 さて、ここまでで一旦区切りですね。今回はコレクションについて話しているような気がします。途中で少し長くなりましたが、合計を求める際のリデュースについて触れました。リデュース以外にも整数の合計を求める方法がありますね。これは高校の頃に習ったことで、すごく印象に残っています。例えば、1、2、3、4、5を足す場合、順番を逆にして5足す4足す3足す2足す1とすると、それぞれの要素を足すと6が5つになります。これを観察すると効率的な計算方法が見えてきます。
簡単に計算できるように、例えば1から5までの合計では両端1と5を足すと6になります。これを計算すると、6に該当する要素が複数ある場合、その合計を2で割ると単純な計算になります。もう少し汎用的な説明があった気がするのですが、記憶が曖昧なので、また調べておこうと思います。
操作数列の話題も出ました。操作数列というのは、一定の差がある数列のことですね。具体的には、数列の各要素間の差が一定であるものです。
セルフを書いていく例について話を戻しますと、リデュースを使って自分自身の要素を合計し、その合計を要素数で割るという手続きを紹介しました。リターンとしては、リデュースを使って合計を計算し、それを要素数で割って平均を求めます。セルフを使うと少し読みにくいので、セルフを省いてスッキリ書くのが個人的には好みですね。マップを使ったときも、セルフを抜くことで印象が変わるものです。
次回はチャートの書き方やそれを使った問題解決の方法について続けます。今回はここまでにしましょう。お疲れ様でした。ありがとうございました。