前回に引き続き 強参照循環
を打開するための手段のもうひとつ、 無所有参照
についてを眺めていきます。今回はその特徴のおさらいから、実際の動きの様子をスライドに沿って 弱参照
と比べる感じで見ていくことになりそうです。よろしくお願いしますね。
——————————————————————————— 熊谷さんのやさしい Swift 勉強会 #232
00:00 開始 00:36 無所有参照のふりかえり 01:10 弱参照が使える場面 01:38 無所有参照が使える場面 02:09 値が常にあることを期待する 03:11 自動で nil になることはない 04:03 輪読会という言葉について 06:36 値の存在が確実なときを見定めるのも良い練習 09:48 無所有参照の使用例 11:54 無所有参照が使えるかは、前提の仕様にも依る 13:44 仕様変更に伴う影響も気になる 14:48 無所有参照が使える場面は考えられる? 17:17 無所有参照を使えそうな場面 19:06 階層構造が確実なときに無所有参照を使えそう 20:15 実際に試して体験するのが吉 21:37 無所有参照の例の解説 22:13 仕様から見る無所有参照の妥当性 25:09 クロージング ———————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #232
では始めましょう。今日は103章が終わり、無所有参照の章に移りましたね。前回の勉強会でも少し触れましたが、今日もこれについて詳しく見ていきます。
今、表示しているスライドを元に、無所有参照についてSwiftのプレイグラウンドでいろいろ試しながら解説します。前回の復習も兼ねて進めていきますので、理解が深まるといいですね。
まず、前回話した通り、無所有参照は生存期間が同じか長いときに使用するものです。逆に、生存期間が異なる場合には逆参照(Weak References)がよく使われます。逆参照は、生存期間が短くても長くても問題がないため、非常に使いやすいのです。
無所有参照を定義する方法ですが、unowned
キーワードを使います。このキーワードを使用することで、常に値があることを期待することがプログラムの側でも保障されます。これにより、Swiftは値が常に存在すると仮定します。そのため、例えばNULLチェックやNULLポインタ例外などのリスクが排除されているわけです。
ただし、この利便性にはリスクもあります。無所有参照は解放されたときにもOptional(オプショナル)型にならないため、場合によっては危険な動作をすることもあり得ます。例えば、無所有参照が解放されたにも関わらずアクセスしようとすると、そのままクラッシュする可能性があります。
また、この点に関して、SwiftのNULL安全性を犠牲にしていると言えるかもしれませんが、逆参照と無所有参照の使い分けによって、最適なパフォーマンスとメモリ管理のバランスを取ることができます。Swiftのこのメカニズムは、C言語や他の古いスタイルのプログラミング言語で抱えていたNULL参照によるエラーを避けるための進化した手法です。
これまで話した内容を再度確認しておきます。無所有参照は、unowned
キーワードを使用し、生存期間が同じか長いときに使用します。また、NULLになることはなく、危険なアクセスを避けるための十分な考慮が必要です。
最後になりますが、この勉強会で行っているような議論や深掘りは非常に重要です。一通り読んでみるのと、詳細な分析を行うのとでは、得られる理解が全く異なることが多いです。それを通して新たな視点を得たり、他の人と意見を交換することで、さらなる知識の深まりが期待できます。
今日は以上です。次回は引き続き無所有参照の具体的な使用例を見ていきたいと思います。質問がありましたら、次の勉強会でお気軽にどうぞ。それでは、お疲れさまでした。 さて、Swiftの言語仕様についてですが、最初に時間がかかるけれど有益だと思いました。前回も見ましたが、この補足技巧については、値が確実に存在する時だけ使うという点が本当に重要です。この確実な時というのが滅多にないので、プロパティとしてもし unowned
を使う場合には特に注意が必要です。
ここを見極めることがプログラミングの面白さであり、コードをより深く理解してレベルアップするための訓練にもつながります。特に、アソシエイトレベルにいる人にはこれに挑戦してもらいたいです。業務で使うには不安かもしれませんが、練習や個人開発のプロジェクトなら安全です。
一部の機能や注意点について述べますが、例えば、危険だからといって全く挑戦しないのは良くありません。個人開発なら問題があっても自己責任なので、ぜひ試してみてください。
次に、具体例として unowned
を使ったコードを見てみましょう。カスタマークラスとクレジットカードクラスを定義します。カスタマークラスには顧客の名前と、その顧客が持っているクレジットカードに関するプロパティがあります。以下のようなコードになります。
class Customer {
let name: String
// 顧客が持っているクレジットカード
var creditCard: CreditCard?
init(name: String) {
self.name = name
}
}
class CreditCard {
let number: String
// クレジットカードを持つ顧客
unowned let customer: Customer
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
}
// 使用例
let customer = Customer(name: "John Doe")
let card = CreditCard(number: "1234-5678-9012-3456", customer: customer)
customer.creditCard = card
上記コードでは、CreditCard
クラスの customer
プロパティが unowned
で定義されています。これにより、Customer
が先に生成され、CreditCard
がそれに対して参照を持つ形になります。これによって、循環参照によるメモリリークを防止することが可能です。
この例では、イニシャライザーの中でカード番号と顧客を受け取って、カードを初期化します。そして顧客がカードを持っている状態を維持します。
それでは、次のトピックに進みましょう。このような技術を使うことで、より複雑なシナリオでもしっかり動作するプログラムを作れるようになります。 これでも問題なさそうですね。で、これについては前回のウィークの時にもお話ししましたが、これが成り立つのもやはり全体的なコードや仕様がどうなっているかによって違ってきますよね。この定義の仕方を普通にしたとして、例えば銀行が発行するクレジットカードで、銀行がアカウントを作って、その時にカスタマーがアカウントとして登録され、クレジットカードを持っている人としても登録されて、さらに銀行によるクレジットカードが発行されたとします。
そのときに、そのカスタマーがアカウントを解約したときには、当然のようにクレジットカードも解除するというのをちゃんとやらないと、ここがクラッシュの原因になりますよね。で、これを仮に weak
にしておけば、カスタマーのアカウントを解約したときに、クレジットカードの協力もなくなり、そのカードナンバーのカスタマーが nil
になったよ、という形でこのカードが宙に浮いたよっていう状況が作れます。これは一つの安全策とされるかもしれませんが、解釈が分かれる部分ではあります。
結局のところ、そのライフサイクル管理がどうかっていうのは全体の仕様にも依存しますよね。そして問題なのは、その全体の仕様が何か変わったときに他に影響しちゃう部分が出てくる可能性があるということです。そういったことを考えると、やはり unowned
を使うのは慎重になりがちです。
仕様が完全に固定されている場合なら安心ですが、実際に動的に変わることもあるので、そんなふうに考えていくと、なかなか unowned
というプロパティを使うことは難しいのです。
ここまで話してみますが、実際の場面で unowned
プロパティが使えそうだとか、過去に使ったことがあるとか、そういった経験はありますかね?使いたいのですが、スライドを読んでいっても、なかなかその使いどころが見つからないのです。実務で使うのは難しいのでしょうか。キャプチャリストの方が多用されますね。
キャプチャリストだとスコープが広がりますし、把握もしやすいから、余計に広がるのが厳しいですよね。プロパティだと外側からもアクセスできてしまうので、プロジェクトは内側からのアクセスに限られています。それがパブリックなインターフェースという感じだから、なおさら影響を把握しにくいのです。
確かに、パブリックなインターフェースは unowned
に限らず、どこから呼ばれるか分かりません。インターフェースが内部での使用を前提にしていたり、パブリックなストラクトでもメンバーイニシャライザーが内部的にしか使用しないようにするなどの配慮が必要です。そういった意味で unowned
もパブリックな場面では出てこないことが多いです。
過去に実際に unowned
を使った例として、例えば以前作った ModeLayer
というライブラリーがありました。このときはビューのレイヤーを決めるもので、基本的に Layer
と Subviews
にしか使わないのでビューレイヤーの生存期間が確実に短いことが前提でした。ビューと Subviews
が終わればそのレイヤーは消えるため、 unowned
を使うことができたのです。
こういう場面なら問題ないですね、今回はそのバグを除けば、ということです。 なるほど、このとき自分は暗黙アンラップのオプショナルを書きがちな気がします。でも、「guard」でやればいいんだということですね。プログラムの構造上、階層がしっかりと約束されているときには、このやり方は業務でもいけそうです。どんなに外側の仕様が変わっても、ここは崩れないですからね、確かに。
そうですね、本当にそうです。例えば根本的にどこかの間違いでサブビューを忘れているとか、そういう話です。忘れていたら、そもそもビューの中でサブビューが存在しないですね。なるほど、いいですね。これが完璧でないということは、そういった階層関係になり得る可能性があるということになりますね。他にも、「resultFilter」の中だけが入れ子の中が落ちたようなことがあるかもしれません。なるほど、いいですね。
暗黙アンラップを使っていた気がするので、これからは気をつけようという話ですね。こういったことは練習しないと身につかないですからね。ミスをして痛い目を見れば良いのですよ、とりあえず。なのでこの点に注意することが大切なんです。
例えば、全体の仕様が変わると、クレジットカードに関するチェックなどを押し忘れるとダメなときがあります。こうしたイニシャライザでクレジットカードの処理をちゃんと行わないと、ランタイムエラーになるといった問題が起こることがあります。これは暗黙アンラップを使うべきではないという気がします。サンプルコードだからしょうがないですが、業務ではもっと注意が必要でしょう。
今のサンプルコードの解説についてです。クラス「CreditCard」と「Customer」が互いにプロパティとしてお互いを保持している状態だと、共通環境を形成する可能性があるため、プロパティに「weak」を付けました。このとき、生存期間が長い方を保持するプロパティに「weak」を付けました。クレジットカードは常に顧客に結びついているため、クレジットカード側のプロパティに「weak」を付けました。
こうすれば、クレジットカードが顧客に結びついているという定義がしっかりしているので、顧客の契約が終わったときにはクレジットカードも解消されるというロジックをどこかに埋め込む必要が生じる仕様です。これを自動化するために、例えばnsマップテーブルなどを使って顧客が解放されたら自動でクレジットカードも解放されるという連動システムを設けることが考えられます。これによって、クレジットカードのインスタンスが顧客よりも長く生存することはないという約束が守られることになります。
このクラス設計はなかなか面白いですね。考え方がしっかりしていて、勉強になります。このように顧客はオプショナルなカードプロパティを持つことができる。クレジットカードは必ず値を持つプロパティを持つように設計されています。
時間がかかりそうなので、今日はここで終わりにしましょうか。お疲れ様でした。