今回も引き続き本編に戻って 強参照循環
の中でもとりわけ クロージャー
まわりについてのそれを眺めていきます。前回は「 クロージャー でも循環参照が起こり得ることと、世間的にはその過剰な回避がセオリーになっている感じがするので学習のときには必要最小限に留めてみよう」みたいな話をしましたけれど、今回は「どうするとクロージャーで循環参照が発生するのか」といった仕組みを把握する回になりそうです。いつ回避すべきかを見極める上でも大切な知識になりそうですので、しっかり眺めていきましょう。よろしくお願いしますね。
————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #309
00:00 Start 00:09 ARC によって理解は難しくなったかも? 01:58 今回の展望 02:58 クロージャーにおける強参照循環が発生する場面のおさらい 04:21 関数型は型エイリアスを使うと表現しやすい 07:01 クロージャーにおける強参照循環は難しい問題 08:35 クロージャーで強参照循環が起こる具体例 14:06 self をキャプチャーするクロージャー定義 18:54 if let 省略表記 19:56 基本 self は省略する 22:40 保存型プロパティーは拡張できない 23:30 ここでは self の明記が必須 25:43 self が解放されなくなっている 27:01 if 式が登場 29:01 switch 式も登場 29:58 クロージング —————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #309
今回は、前回の続きとして、Swiftの「オートマティックリファレンスカウンティング」(ARC)についての勉強を続けていきます。この概念は、iOSなどでプログラムを組む際に非常に重要です。ARCは自動的にメモリ管理を行うため、普段あまり意識することはありません。しかし、昔はリファレンスカウンティングを手動で行うのが常識だったため、概念がより可視化されていて理解しやすかったです。現在では、ARCがデフォルトとなっており、裏側で動作するため、初学者には捉えづらい部分もあるかもしれません。
5ヶ月前には、クラス内でのARCの挙動について詳しく話しました。そして、今回はクロージャーにおける循環参照の発生とその回避方法に焦点を当てます。循環参照、または循環参照を持つ関係が発生すると、ARCはメモリを適切に解放できなくなります。これをどのように回避するかが今回の勉強の肝です。
まずは、クロージャーをクラスのインスタンスプロパティとして参照した場合、特にそのクロージャー内で自身のクラスインスタンスをキャプチャするとどのように循環参照が発生するかを見ていきます。以下はその例です:
class SomeClass {
var closure: (() -> Void)?
func doSomething() {
closure = {
print("Doing something with \\(self)")
}
}
}
このコードでは、closure
プロパティがクラスのインスタンスをキャプチャします。これにより、クロージャーとインスタンスがお互いを参照し合い、循環参照が発生します。その結果、どちらもメモリから解放されなくなります。
循環参照を解消するための主要な手法の一つとして、クロージャーキャプチャリストが提供されています。クロージャーキャプチャリストを使えば、キャプチャされた参照を「弱く」することで、循環参照を断ち切ることができます。これにより、メモリが正しく管理され、期待通りに解放されるようになります。この技術を使って、先ほどの循環参照を解消します。
続いて、循環参照の例を理解した上で、具体的な解消手段としてのクロージャーキャプチャリストの使い方について学んでいきます。これらの概念を理解すれば、なぜこの方法で解決できるのかをより深く理解できるでしょう。また、HTMLエレメントクラスという例を通じて、このような循環がどのように発生するかをさらに具体的に見ていきます。 実際にどのような定義になるかを見ていきましょう。ここでは、クロージャーによる共通処理の例を示しています。何気なく書いているようですが、意外と奥が深いものです。この例では、シンプルなHTMLモデルを提供する仕組みを考えます。それでは、少しずつコードを打ちながら進めてみましょう。
HTMLを知っている方は多いと思いますが、例えばHTMLにはタグがあり、タイトルタグのように内容を定義します。このようなHTMLタグの一つひとつをモデル化していくという話になります。例えば、タイトルタグがあり、その中のテキストが「テスト」といった形で記述します。もう少し複雑な例として、ヘッドタグ内に他のHTML要素が含まれることもありますが、今回の例では単純なパターンを対象にします。
具体的には、3行目の部分を取り上げて名前(タグ名)とテキストを持つ構造を作るという感じです。具体例として、以下のような構造が考えられます。
let name: String
let text: String?
テキストが含まれていない場合、それを許容する構造も必要です。HTMLでは、テキストがないタグも存在します。このため、テキストはオプショナルとして扱われます。クラスを使う場合はイニシャライザーが必要なので、それを定義します。その上で、タグ名やテキストを受け取ったパラメータとして設定します。
サンプルコードでは、テキストが省略可能とされているため以下のように書きます。
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
これにより、クラスのインスタンスが解放されたかどうかを確認することもできます。例えば、ローカルスコープから外れたタイミングで解放されることを想定しています。このように管理しているのがAutomatic Reference Counting(ARC)です。
実際にコードを実行すると、「メタ」タグがイニシャライザされ、適切に解放される様子が確認できます。このように、ARCが管理するリソースの解放が行われています。このプロセスを理解することで、メモリ管理についてより深く理解できるでしょう。 Swiftの勉強会で、HTMLエレメントを表現するコードについて話をしていたところです。このコードでは、asHTML
というクロージャーを持たせて、指定されたHTMLテキストを生成するような形にしています。クロージャーは引数を取らずに文字列を返すように設定されています。これは例として書かれたコードなので、実際のアプローチとは異なる部分もあるかもしれません。
このクロージャーをレイジーバーとして持たせる理由がはっきりしないところもありますが、後から書き換えて別の形式のHTMLを出力することが可能な設計になっています。HTMLエレメントを拡張する方法として、クラスを継承するのが一般的ですが、インターナルなケースではそれが適切かは一概には言えません。将来的に継承を考慮するなら、ファイナルクラスにしてしまうのも手です。
また、ここではオプショナルバインディングの新しい書き方も出てきました。Swift 5からの新機能で、if let テキスト
と書くことで、オプショナルなプロパティから値を取り出しやすくなっています。この書き方を使えばコードを簡潔にできます。
self
に関連してですが、Swiftでは普通self
を省略することが推奨されています。ただし、プロジェクトによってはself
を明示的に書くケースもあり、その場合はプロジェクトの方針に従う必要があります。
最終的に、自分で書くときは読みやすさを優先して、上記のような形式を選択するのが良いかもしれません。個人的には省略したほうがコードが読みやすく感じることが多いですね。 この話では、Swiftのコードを書く際のテクニックについて説明しています。特に、オプショナルバインディングやクロージャーでのself
の使用に関する考え方を詳しく見ていきます。self
をわざわざ書くケースについて、特にクロージャー内での注意が必要です。なぜなら、クロージャーがself
をキャプチャする際に循環参照(リファレンスサイクル)が起こる可能性があるからです。このような状況を避けるために、明示的にself
を記述する必要があります。
また、エクステンションにおけるプロパティの定義については、エクステンションでは保存型プロパティを追加することができないという制限があります。この制限はプログラムのメモリ管理や安全性に関わる重要なポイントです。クロージャーにおけるキャプチャリストの活用方法や、それに伴うメリットについても触れられていますが、詳細は今後の話題になるようです。
さらに、if let
構文や、短いリターン構文の使用方法についても述べられており、コードの読みやすさやメンテナンス性に配慮した設計を心がけることが推奨されています。これらのテクニックを活用することで、安全かつ効率的なSwiftプログラミングが可能になりますね。 今回は、Swiftの最新バージョン5.9での新機能について説明しました。特に注目したのが、if
文やswitch
文の使い方の変化です。
まず、従来のif
文は条件を満たすと次のステートメントが実行される形でしたが、Swift 5.9ではif
文全体を式として書けるようになりました。これは、各ブロックが1ステートメントの際に、if
全体を一つの式としてまとめて書くことができるということです。これにより、以前はそれぞれreturn
を記載していた場面でも、条件に応じた処理を省略可能な形で簡潔に書けるようになりました。
さらに、switch
文においても同様に、式として書けるようになり、各ケースに対する処理にreturn
を書かずに済むようになりました。この新しい記述法によって、コードがより見やすく、書く際にも一貫性が保てる形になりました。
今後、こういった新しい記法を使ったコードを目にする機会も増えると思いますが、落ち着いて理解することで、よりスムーズにコードを読み解けるようになるでしょう。
今回は時間になりましたので、この辺りで終わります。次回は、この内容に関連する詳細についてさらに掘り下げて見ていく予定です。お疲れ様でした。ありがとうございました。