本日から Language Guide
の新たな節に進んで見ていく節目になりまして。本来の順番的には次は Basic Operators
なのですけれど、これまでに話してきた中で気になった 循環参照
周りをきっかけに、それに関係の深い Automatic Reference Counting
の節を先に眺めて行ってみようと思います。どうぞよろしくお願いしますね。
———————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #208
00:00 開始 00:14 今回からの展望 02:49 循環参照解消のために unowned が使われる 04:12 今回からは自動参照カウントの話 04:59 unowned はクロージャーと合わせて使われる印象 05:53 所有権が導入されるらしい 08:34 borrowing と consuming 10:38 ARC を知らない人もいるかもしれない 12:07 所有権は値型にも作用する 14:32 ARC の理解を深めるための参考文献 15:28 参照カウントの自動管理 16:49 値型は参照として扱えない 17:41 裏では参照として扱われる場面も 18:57 ARC の様子を見てみる 20:15 ヒープ領域とアドレス 24:05 参照のカウントのされ方 27:20 多重ポインターの話 30:24 クロージング ————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #208
では始めますね。今日は新しいセクションに入ります。前回まではアサーションとプレコンディションを見ていましたが、それが一通り終わったので、今日は「The Swift Programming Language」の本来の流れだと次のセクションであるBasic Operators、要は演算子の話に移ります。
これまでの話の中で循環参照の仕様などが気になったという話がありました。順番を変えて、オートマティックリファレンスカウンティング(Automatic Reference Counting: ARC)、つまり循環参照と関係の深いセクションを先に見ていこうと思います。
場所としては「The Programming Language」のセクション、「The Basics」に続く部分になります。具体的には、ランゲージガイドの中です。今回は、オートマティックリファレンスカウンティングのところから見ていきます。何回か話にも出てきましたし、思い出せる方もいるかもしれませんが、意外と複雑な内容です。アンオウンド(unowned)というキーワードについての話も関連しています。これがプラスの循環参照を解消するために使われるという話が出てきましたね。
思い出せる限りで話していきますが、これまで取り上げたことがあるかどうか忘れましたので、その時話できなかった部分を補足する意味も含めて、今日はこのあたりを詳しく見ていこうと思います。
ということで、まずは参照カウントの概要から入っていきます。具体的なところを見ていく前に、概要をざっくりと話してみますが、Swiftではウィーク(weak)やストロング(strong)といったオーナーシップの話がよく出てきます。
また、新たに「オーナーシップ」の概念が加わろうとしているという話があります。これは、Rustという言語から導入されたものがベースにあり、Swiftにも適用される予定のようです。例えば、C++11でも導入されたムーブコンストラクタなどの概念が関連しています。
新たに導入されるのは、パラメータに対してウィークやストロングのようにフォローイング(following)やコンシューミング(consuming)というキーワードが追加されるということです。これによって、参照渡しやムーブ渡しが言語レベルでサポートされるようになります。
今回お話しするARC、オートマティックリファレンスカウンティングそのものがどう使われるかについても見ていきますが、この新しいオーナーシップの概念も覚えつつ進めていきたいと思います。では、具体的な解説に入っていきますね。 メモリーのオーナーシップについての話ですが、少し難しく感じるかもしれませんね。Arcについて理解を深めるために、まずは基本的な概念から見ていきましょう。
自動参照カウント(ARC)はメモリー管理のための仕組みです。メモリーの使用状況を追跡管理するもので、基本的に自動で行われます。Objective-Cを使用していた人にとってはなじみ深い概念です。ARCは途中から追加されたものですが、それによりプログラマーがメモリー管理を手動で行う必要が減りました。
ARCについて知っている人にとっては精密に理解しているかもしれませんが、最近プログラミングを始めた人の中にはARC自体を知らない方もいるかもしれません。しかし、あまり難しい概念ではないので、感覚として理解している人も多いのではないかと思います。
ほとんどの場面ではメモリー管理機構が自動的に動作します。このため、メモリー管理について深く考える必要がないというのが利点です。ただし、新しいオーナーシップの話や、特にAPIを提供する側のプログラマーは、もう少し詳しく考える必要があります。たとえば、高品質で安全なコードを作るためには、メモリー管理について深く理解していることが重要です。
新しいオーナーシップの話は参照型に限らず、与える型(値型)についても考慮している可能性があります。ただし、今回のARCに関しては主に参照型に焦点を当てています。将来的にはもっと詳しく考える必要があるかもしれませんが、現時点ではそこまで考える必要はありません。
続いて、ウィークとストロングの話に移る前に、もう少し詳細にARCについて見ていきます。Objective-CでARCを使用する方法はSwiftと非常に似ています。ARCが登場する前のコードと比較して、ARCを導入することでどのようにメモリー管理が変わったのか理解することができます。興味がある方は詳細な資料を確認することをお勧めします。
次のスライドでは、ARCが利用できる場面について説明しています。参照カウントはインスタンスごとに適用されます。つまり、インスタンスを参照しているすべての場所で参照カウントが管理され、どれかが存在している限り、そのインスタンスはメモリーから解放されません。この参照カウントを管理するのがリファレンスカウンティングです。
例えば、以下のようなコードがあります:
class MyClass {
// クラスの定義
}
var myObject: MyClass? = MyClass()
myObject
はMyClass
のインスタンスを参照していますが、他にもそのインスタンスを参照している変数やプロパティがあれば、そのすべての参照が解放されるまではメモリーから解放されません。これが参照カウントの基本的な仕組みです。
このように、ARCを使うことでメモリー管理が自動化され、開発者が手動で管理する必要が減るという利点があります。続きの説明では、もう少し具体的な例を挙げてARCの動作を詳しく見ていきたいと思います。 参照カウントを自動的に管理する話題に移ります。昔は手動で管理していたものが、自動で管理されるようになったという話です。
次に、構造体と列挙型について説明します。これらは値型であり、参照型ではありません。参照型だけが参照カウントで管理されていますので、構造体や列挙型はARCとは関係ありません。今回の話題は、参照型であるクラスのインスタンスに限った話です。
以前の勉強会で、クロージャも参照型だけどリファレンスカウンティングはやっていないという話がありました。クロージャはクラスと同じく参照型ですが、それについての詳細はまだ不明な部分があります。要するに、参照型とはクラスだけに限定された話ということですね。
また、構造体と列挙型についても、特定の条件下で参照型のように扱われる場合があります。例えば、inout
やクロージャを使うときです。ただし、これらについても参照カウントで管理されるわけではありません。ARCで管理されるのはクラスだけです。
ARCについての概要を今ざっと見てきましたが、より具体的な例をプログラミングしながら説明していきます。ARCは非常に便利な機能ですが、その背後にある仕組みを理解しようとすると意外に難しいこともあります。
次に、参照カウントをプラスでどう扱うかについて見ていきます。例えば、以下のようにクラスのインスタンスを作成するとします。
let x = Object()
ここでは、メモリ領域がシステムによってあらかじめ確保され、その中にオブジェクトのインスタンスを作成し、そのアドレスが変数に保存されます。このアドレスを知るにはどうしたらよいでしょうか?
以下のような方法を試すことができます。
print(Unmanaged.passUnretained(x).toOpaque())
この方法で、インスタンスのアドレスを取得することができます。見た目は少し直感的ではありませんが、確かにアドレスを表示することができます。
ARCの仕組みを理解するために、このように実際にコードを書いてみると、より具体的な部分が分かりやすくなります。もし分からない点があれば、随時質問してください。 とりあえず「そういうものだ」と思ってプリントしたら、ちょっとそれっぽくなります。アドレスが取れて、本当に同じポインターが取れているか少し自信がないですが、y = x
とかやって y
を表示すると、全然違うものが出てくることがあります。ポインターのポインターを取っているんですね。この辺りは少し分かりづらいかもしれません。
やはりアンセーフポインターにしないとダメなのでしょうか。キャストして x
を Int
に変換しないと、中身を取れないのかもしれません。ポインターのポインターに関しては、C言語の頃からの概念で、よく知られていますよね。C言語の難点としてよく名前が挙げられます。
要は共有されている変数に代入が1回されると、参照カウントは1になります。もう1回同じ参照が他の変数に代入されると、参照カウントが2になります。これをオプショナルで説明しましょう。オブジェクトが入っているかもしれないし、入っていないかもしれないという状況です。このとき、例えば x
が nil
になってしまうと、オブジェクトは x
から参照されなくなり、参照カウントが1減って1になります。しかし、まだ参照カウントが1なので、オブジェクトは開放されません。
y
でも参照されている場合、ヒープ領域にあるオブジェクトのインスタンスを開放することはできません。参照カウントが0になると、このヒープ領域に作られたオブジェクトのインスタンスはどこからも参照されない状況になります。そこで初めて開放してよい状況になるのです。システムが自動的に開放してくれます。
このクラスにはデイニット(deinit
)という機能がついていて、ここで処理を行います。例えば、プリント文を1つ、2つ、3つと追加すると、順番に処理されます。要するに、x
が開放される前と開放された後の状態ですね。y
が開放されると、その直後にデイニットが呼ばれます。参照カウントが0になると、オブジェクトのデイニシャライザーが呼ばれ、開放が終わります。その後に次の行の処理が実行されます。ARC(自動参照カウント)の仕組みがこのタイミングをうまく管理してくれます。
ポインターに関して苦い経験をした人は、避けたくなるのかもしれません。私はポインターが好きなので、触りたくなる立場ですが、確かに大変なこともあります。ポインターやポインターのポインターについて理解するのは確かに難しいところです。 時々、重要な場面でアスタリスクとアンパーサンドが混ざって出てくることがあるんですよね。そういう時に、「どっちがどっちだっけ?」と迷うことがあります。特に、新しい言語では厄介です。たくさんのものが入り混じるので、「void *
なのか int &
なのか分からなくなる」なんてこともありますね。
APIがポインターを渡すことを要求してくるのは本当に嫌なものです。こういう方法を捨てざるを得ない状況にもなります。「これは何の問題なんだろうか?」と考えると、ポインターのアドレスを取っているのか、アドレスの値を取っているのか、その区別が必要です。また、int
がポインターになってしまうと、「どうすればいいんだろう?」と悩むこともありますね。
そういう場合、コンパイラーの助けを借りないと解決できないこともあります。どのようにしても、答えが得られないこともあります。また、UnsafeBitCast
を使うべきか、UnsafeRawPointer
を使うべきかなど、入力が変わると同じ問題に直面することもあります。
真面目に考えなくても、ARC(Automatic Reference Counting)や今後導入されるオーナーシップモデルがうまく処理してくれますので、そんなに心配することはありません。論理的にはシステマティックで、納得できる難しさです。しかし、新しい言語のポインターは、論理的にも直感的にも混乱を招きがちです。その点、心配せずに見ていけると思います。
今日はややこしい話が中心になってしまいましたが、次回以降はそんなに難しくない話を取り上げていくつもりです。焦らず、ゆっくりと見ていってください。
時間が来ましたので、今日はARCに関する概要をまとめて終わりにしようと思います。次回は具体的な内容を見ていきましょう。では、今日はこれで終わりにします。