前回から引き続き Automatic Reference Counting
の節を眺めていきます。前回はそもそもの参照カウンターとは何かみたいなところを大まかに見た感じでしたけれど、今回は Automatic Reference Counter
がどのように働くかみたいなところを読み進めていこうと思ってます。どうぞよろしくお願いしますね。
—————————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #209
00:00 開始 00:29 参照カウントって知られている? 01:42 オートリリースプールはホットな話題? 05:00 ARC と @autoreleasepool を Objective-C で検証 13:13 オートリリースプールとスコープの違い 14:08 Objective-C で ARC を無効化 15:07 Compiler Flags は swiftc には無効らしい 16:09 MRC を体験する 20:16 オートリリースの効能 23:10 MRC ではルールで足並みを揃える 26:18 クロージング ——————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #209
では始めていきますね。今日はARC(Automatic Reference Counting)の話をしていこうと思います。データに戻ってやりましたが、今回はこの仕組みについてざっくりと紹介されているスライドがあるので、それを見ていこうと思います。
最近気になっていることがありまして、特にiOS界隈では、リファレンスカウンティングを意識することが少なくなりました。例えば、リファレンスカウンティングを知らないとか、聞いたことはあるけど自信がないという人がどれぐらいいるのかなと思ったのです。前回の説明がうまくいかなかったこともあり、リファレンスカウンティングがそれなりに複雑なことをしているので、気になりました。
もう一つ気になることがあります。最近、自分のブログでARC周りの話、とりわけオートリリースプール周りの話があまり反応されないことです。もうオートリリースプールなんて使わない時代ですよね。これに対しての反応を見て、驚きました。
Xcodeでプロジェクトを作成した際、自動で付いてくるAuto-release poolについてブログで紹介していましたが、今はもう付いてこないです。Objective-CやARCのプロジェクトでも、オートリリースプールが話題になることは少ないです。最近のプロジェクトでは、自動的にオートリリースするか、リテインするか、何もしないかを切り替える記事が昔に書かれていましたが、今でも反応があって驚きました。この話題が今でもホットなのかなと思いました。
昔はリファレンスカウンティングの話題が盛り上がっていましたが、今ではあまり聞かなくなりました。それも不思議に感じています。例えばObjective-Cでは、メイン関数のオートリリースプールを手動で管理する必要がありました。でも今はそうではありません。
とりあえず、リファレンスカウンティングやオートマティックリファレンスカウンティングの周りの話を見てみたいと思います。これらの周りの技術も重要なので、少し見てみましょう。
では、プロジェクトを作ってみましょう。XcodeでコマンドラインツールをObjective-Cで作成してみました。メイン関数が明示されていて、そこにはオートリリースプールが付いていないことに気付きます。コマンドラインツールの場合、リファレンスカウンティングを手動で管理しないと、メモリリークなどの問題が発生します。
少しやってみましょう。Objective-Cでインターフェースを書く場所も忘れてしまいましたが、ここから始めてみましょう。 次に進みましょう。今回はSwiftでイニシャライザー(init
)について学びます。初めて見る方もいらっしゃるかもしれないので、ゆっくりと説明していきますね。
まず、イニシャライザーはインスタンスを生成するための特別なメソッドで、クラスや構造体のインスタンスを初期化するために使われます。例えば、以下のように書けます。
class MyClass {
var myProperty: String
init(myProperty: String) {
self.myProperty = myProperty
}
}
このコードは、MyClass
というクラスのインスタンスを生成するときにプロパティmyProperty
を初期化します。
ところで、Objective-Cでのイニシャライザーはどうだったかというと、[super init]
を呼ぶことでスーパークラスの初期化メソッドを呼びますね。また、その後でself
に代入している部分は以下のようになっています。
self = [super init];
if (self) {
// プロパティの初期化
}
Swiftでは、同様の処理が非常に簡潔になります。さて、さきほどの例に戻りますが、Swiftのイニシャライザーには一つの重大なポイントがあります。それは「デザインイニシャライザー」の存在です。このイニシャライザーが全てのプロパティを初期化する役割を持ちます。
また、構造体の場合も同様にイニシャライザーが必要です。しかし、構造体にはデフォルトのイニシャライザーが自動生成されるという利点があります。ただし、自分でカスタムイニシャライザーを定義すると、デフォルトイニシャライザーは上書きされます。
次にsuper.init()
ですが、これを使うことでスーパークラスの初期化を呼び出します。もし独自の初期化処理が必要な場合、super.init()
の後に行います。例えば、サブクラスの初期化は以下のようになります。
class SubClass: SuperClass {
var newProperty: Int
init(newProperty: Int) {
self.newProperty = newProperty
super.init()
}
}
このようにしてスーパークラスのイニシャライザーを呼ぶことができます。そして重要なポイントとして、スーパークラスのイニシャライザーの呼び出しは、このようにサブクラスのプロパティを初期化する前に行われます。
ここまではイニシャライザーについてですが、リリースに関しても触れておきましょう。Objective-Cではautorelease
といったメモリ管理の手法がありましたが、Swiftでは自動参照カウント(ARC)が管理するため、手動でリリースする必要はありません。
ARCが導入されたことで、メモリ管理の多くが自動化され、メモリリークの心配も少なくなりました。しかし、クロージャや自己参照など、注意すべき点は依然として存在します。それゆえ、ARCの仕組みを理解しておくことは非常に重要です。
次に具体的なリリース処理について見ていきます。リリースやデイニット(deinit
)は、インスタンスがメモリから解放されるタイミングで呼ばれます。以下はその例です。
class MyClass {
var myProperty: String
init(myProperty: String) {
self.myProperty = myProperty
}
deinit {
print("MyClass instance is being deallocated")
}
}
deinit
メソッドはインスタンスが解放される際に呼ばれるので、リソースの解放処理をここに書いておくと便利です。
まとめると、今回はSwiftのイニシャライザーやリリースについて学びました。これらの基本的な知識はソフトウェア開発において非常に重要ですので、しっかりと理解しておきましょう。質問があれば、どんどん聞いてくださいね。 オートリリースプールについて話す前に、オートリリース自体が対象になるタイミングについて言及しましょう。ARC(Automatic Reference Counting)の場合、オートリリースプールはないかもしれませんが、Objective-Cではオートリリースのインスタンスは特定のタイミングで対象となります。
例えば、ビルド設定を見直すことで、ARCを無効化することが可能です。ビルドセッティングやビルドルールの設定をして、フラグを設定します。例えば、-fobjc-arc
フラグを設定すると、Objective-CのファイルでARCを無効化することができます。設定はグローバルに行っても良いのですが、特定のソースファイルに対して行うことも可能です。
このフラグ設定により、ビルドするとObjective-CのファイルでARCが有効・無効になります。Objective-Cのファイルの場合、ARCを有効にする、無効にするという設定が便利です。ただし、コンパイラによってはこのフラグが影響を及ぼさないこともあるため、調査や検証が必要です。
ARCを無効化することで、手動でメモリ管理を行います。例えば、オブジェクトの後始末を自動ではなく手動で行うことで、プログラムの挙動を理解しやすくなるかもしれません。親クラスの後始末をしてから自分の後始末を行う必要があるため、その記述が必要となる場面もあります。
手動でメモリ管理を行う例として、以下のようにリリースを行います。例えば、メイン関数でオブジェクトを作成し、それが不要になったタイミングで解放する場合です。
// インスタンス作成
let obj = MyObject()
// オブジェクトが不要になったら解放
obj.release()
このように書くことで、メモリ管理を手動で行い、メモリリークを防ぐことができます。
また、参照型の変数を別の変数に代入するときも注意が必要です。リファレンスカウントを適切に管理しなければ、メモリが開放されないという問題が生じます。
let obj1 = MyObject()
var obj2 = obj1
obj1.retain() // リファレンスカウントを増加
obj1.release() // 先に解放
obj2.release() // 最後に解放
このように、自分で定義したオブジェクトを適切に管理して、不要になったタイミングで正しく解放することが重要です。リファレンスカウンティングを正確に行うことで、メモリリークを防ぎ、プログラムが正しく動作するようにできます。
最後に、ARC(Automatic Reference Counting)が登場する前は、手動でメモリ管理を行うためにこうした方法が使用されていました。ARCにより自動化された今でも、基礎を理解するためにこうした方法を知っておくことは有益です。 ローカルスコープで作成されたオブジェクトは、そのスコープ内で使用が終われば大体解放されます。しかし、リファレンスカウンティングの管理を手動で行うと、リリースを忘れたためにメモリリークが発生することがあります。この問題を回避するために、例えばC++ではスマートポインタを使用しますが、Swiftではautorelease
という仕組みがあります。
オブジェクトを初期化して、その後自動的に解放するように設定することで、適切にメモリが管理されます。たとえばautorelease pool
を使って、そのスコープ内から抜けたタイミングで自動的に解放してくれます。具体的なコード例として、次のようなものがあります。
autoreleasepool {
let instance = SomeClass()
// インスタンスの使用
} // ここで自動的に解放される
これにより、手動でリリースする必要がなくなり、メモリ管理が非常に楽になります。Objective-Cではメソッドの名前付け規則(例えばnewObject
やinitObject
など)を利用してメモリ管理を行っていましたが、SwiftのARC(Automatic Reference Counting)ではこのような名前付けやリリース操作が不要になります。
ARCのおかげで、メモリ管理のためのコードが少なくなるので、プログラミングが非常に簡単になりました。マニュアルでリファレンスカウンティングを行っていたころに比べて、メモリ管理の手間が大幅に軽減されました。
このようにして、ARCが導入されたことで、プログラムを書くときのメモリ管理が非常に楽になりました。次回は、これに関連したさらに深い話や他のトピックについても触れていこうと思います。
今日はこれで勉強会を終わります。お疲れ様でした、ありがとうございました。