Objective-Cの弱参照について


Objective-Cにはweakという識別子があるが、内容を理解していなかったので少し調べた。
ここではiOSのARM64環境を前提としている。また、weakまたは__weakを付与して宣言した変数をweak変数とする。
Appleが公開しているObjective-Cのランタイムライブラリのソースコードを読んだが、実機のライブラリがこの通りなのかどうかは不明である。

参考1: https://opensource.apple.com/source/objc4/objc4-709/runtime/ のNSObject.mmなど
参考2: https://github.com/opensource-apple/objc4/blob/master/runtime/ こちらは少し古い?
参考3: https://github.com/gnustep/libobjc2/blob/master/arc.m
参考4: [Objective-C Automatic Reference Counting (ARC)](https://clang.llvm.org/docs/AutomaticReferenceCounting.html)
参考5: [Objective-CのランタイムAPIをReactiveCocoaで解説する](http://qiita.com/ikesyo/items/9c6b00e2b00d8f5e3e11)
参考6: [ダイナミックObjective-C](http://news.mynavi.jp/column/objc/)

### weakの実現方法の概要

書籍では「[エキスパートObjective-Cプログラミング](https://www.amazon.co.jp/dp/4844331094)」に載っている。
大まかには、あるオブジェクト```obj```について、それを参照するweak変数```wvar```を記憶しておく専用の領域を用意することにより実現されている。

### 代入時の処理

weak変数 ```wvar``` を宣言すると、その格納領域としてidのサイズ(ARM64では8バイト)が確保される。
```wvar``` にオブジェクト ```obj``` を代入する文は、関数 ```objc_storeWeak(wvarのポインタ,obj)``` への呼び出しとしてコンパイルされる。
```objc_storeWeak``` の内部処理では、 ```weak_unregister_no_lock``` と ```weak_register_no_lock``` の呼び出しが行われる。
Objective-Cのランタイム関数には、weak変数を管理するためのテーブルが用意されている。
```wvar``` に値が入っているときは、 ```weak_unregister_no_lock(テーブル,obj,wvarのポインタ)``` を呼び出し、テーブルの ```obj``` に対応する場所から ```wvar``` を削除する。
続いて、 ```obj``` がnilでなければ、 ```weak_register_no_lock(テーブル,obj,wvarのポインタ)``` を呼び出す。これによりテーブルの ```obj``` に対応する場所に、参照先として ```wvar``` (のポインタ)が追加される。(テーブルのキーは ```obj``` である。 ```obj``` に対するweak変数は複数存在できる)
また、 ```wvar``` の値は ```obj``` となる。

### メソッド呼び出し時の処理

weak変数にてメソッドを呼び出す時、単純に値 ```obj``` により ```objc_msgSend``` を呼び出すと、メソッドの処理の途中で ```obj``` が解放される危険性がある。
そのため、atomicにretainする関数が用意されている。(普通のretainは、retainを呼び出した直後に ```obj``` が解放される可能性があるので使えない)

```wvar``` のメソッド ```hoge``` の呼び出し ```[wvar hoge]``` は、

1. objc_loadWeakRetained
2. objc_msgSend
3. objc_autorelease

にコンパイルされる。テーブルに ```wvar``` の情報がある場合( ```obj``` のライフサイクルが終了していない場合)にのみ、 ```objc_loadWeakRetained``` によりretainされたオブジェクトを取得することができる。利用後は(普通のオブジェクトと同様に)releaseで参照カウンタを1減らす。

### 参照先のオブジェクトが解放される時の処理

```obj``` の参照カウンタがゼロになり、オブジェクトが破棄されるとき、最後の後片付けの処理として ```clearDeallocating``` が呼び出される。
```clearDeallocating``` の内部では、 ```obj``` を参照しているすべてのweak変数の値がnilに書き換えられ、 ```obj``` の登録情報が削除される。つまり、 ```obj``` が解放されるとただちに ```wvar``` が保持する値がnilになる。

参考: [automatic ref counting - How can the Objective-C runtime know whether a weakly referenced object is still alive? - Stack Overflow](http://stackoverflow.com/questions/14854635/how-can-the-objective-c-runtime-know-whether-a-weakly-referenced-object-is-still)

### tagged pointer

idがtagged pointerの場合は、参照カウンタの管理を行わないようになっている(retain時に参照カウンタが変化しない)。tagged pointerとは、idの一部に値が入っているもので、このときはヒープを使わないので、参照カウンタが不要になる。この場合でもテーブルへの登録はしていて、ライフサイクルには従うようである。
ARM64環境の場合は、最上位bitが1ならtagged pointerの模様。

参考1: [64bit環境におけるObjective-Cのポインタ](http://labs.gree.jp/blog/2015/01/13437/)
参考2: [Tagged pointers and fast-pathed CFNumber integers in Lion](http://objectivistc.tumblr.com/post/7872364181/tagged-pointers-and-fast-pathed-cfnumber-integers)

### objc_loadWeakRetainedの処理

```wvar``` が ```obj``` を参照していて、retainする処理は以下のような流れ。

1. ```wvar``` が保持するidを取得する。もしnilなら終了。(ここでは ```obj``` が取得されたものとする)
2. ```obj``` に関連するweak変数用のテーブルのポインタを取得する
3. テーブルをロックする
4. ロック後、 ```wvar``` の値が変わっていれば、1へ戻る。(テーブルの取得からロックまでの間にオブジェクトが解放された場合など)
5. ```obj``` のクラス(isa)を取得する
6. hasCustomRR()が偽のとき(独自のretainやreleaseを持たない場合)、tryRetainによりretainする
7. そうでない場合、retainWeakReferenceを呼び出す。(retainWeakReferenceが未実装であれば失敗する)
8. テーブルをアンロックする

※ getMethodImplementationは、メソッドが未実装の場合に_objc_msgForwardを返す模様 ([objc-class.mm](https://github.com/opensource-apple/objc4/blob/master/runtime/objc-class.mm#L691))

### 余談

なぜこれを調べようと思ったかというと、アプリからUIActivityViewControllerで他のアプリにシェアすると、投稿完了時に(どちら側のバグがわからないが)objc_loadWeakRetainedの内部で死んで、謎だったからである。
ちなみにだいたいhasCustomRR()のところで死ぬ。([同じではないが、このへん](https://github.com/opensource-apple/objc4/blob/master/runtime/objc-weak.mm#L534)) どうやらviewのクラスが解放されたあとにアクセスしているようである。
NSObjectの先頭に入っている値はisaなのだが、下位3bitにはタグなどの情報が入っているらしく、ビットマスクしたものをポインタとして使用している。([objc-object.h](https://github.com/opensource-apple/objc4/blob/master/runtime/objc-object.h#L84))
hasCustomRR()はクラスオブジェクトのオフセット0x20(64bit環境の場合)のflagsのbit1に入っている。