@propertyの属性 strong と weak

 

よくよく考えると?だった、@propertyの属性の「strong」と「weak」について

あっちこっちの資料を調べた結果、一応、こういうことなんじゃないかということを整理してみました。

間違っていたら、ごめんなさい。

 

確認環境:Xcode 7.2

 

まずは、動かして確認しましょう。

 

strong属性の「strongProperty」weak属性の「weakProperty」という2つのプロパティを持つ

「TESTClass001」クラスのクラスインターフェースを定義します。

なお、@propertyの属性は特に何も指定しなければ、「strong」になります。

 

TESTClass001.h

「viewDidLoad」で以下のような処理を行います。

 

ViewController.m

実行結果は、以下の通りです。

2回目のNSLogの結果で、「weakProperty」の値が Null になってます。


Tip_01_01_008a

 

行ごとに説明していきます。

 

12行目: TESTClass001 *testClass = [[TESTClass001 alloc]init];

「testClass」という「TESTClass001」クラスのインスタンスを生成します。

 

14行目: NSString *string = [[NSString alloc]initWithFormat:@”変更前”];

NSString型「string」オブジェクトを「alloc」して「init」します。

「alloc」すると、オブジェクトは、メモリ領域に割り当てられます。

ついでですが、割り当てたメモリ領域は、たまたまその領域に残っていた「ゴミ」の影響がないように0が埋めらます。

そして「init」で初期値 ”変更前” を設定します。

 

おそらくですが、イメージとして、メモリ領域にこんな感じで「string」オブジェクトが割り当てられます。

(アドレスは適当です。)


Tip_01_01_008b

 

ここで注目する点は、ARC(Automatic Reference Counting、自動参照カウント)では、

オブジェクトは「参照カウンタ」を持っているということです。

オブジェクトを生成すると、「参照カウンタ」は1になります。

なので、「string」オブジェクトの参照先 ”変更前”の「参照カウンタ」は、この時点で1になります。

 

16行目: testClass.strongProperty = string;

「testClass.strongProperty」に「string」オブジェクトの参照先 ”変更前” のアドレスがセットされます。

「testClass.strongProperty」は「strong」属性のプロパティです。

「strong」属性のプロパティからの参照は参照カウンタを+1します。

なので、「string」オブジェクトの参照先 ”変更前” の「参照カウンタ」は、この時点で2になります。

 

このことを「testClass.strongProperty」が ”変更前” を所有している、とか

「testClass.strongProperty」から ”変更前” への強い参照がある、と言います。


Tip_01_01_008c

 

17行目: testClass.weakProperty = testClass.strongProperty;

「testClass.weakProperty」に「testClass.strongProperty」の参照先 ”変更前” のアドレスがセットされます。

「testClass.weakProperty」は「weak」属性のプロパティです。

「weak」属性のプロパティからの参照は参照カウンタを変化させません。

なので、testClass.strongProperty」の参照先 ”変更前” の「参照カウンタ」は、この時点で2のままです。

 

このことを「testClass.weakProperty」は ”変更前” を所有していない、とか

「testClass.weakProperty」から ”変更前” への弱い参照がある、と言います。


Tip_01_01_008d

 

19行目: NSLog(@”strongProperty = %@ weakProperty = %@”, testClass.strongProperty, testClass.weakProperty);

「testClass.strongProperty」と「「testClass.weakProperty」」の値を確認します。

strongProperty = 変更前 weakProperty = 変更前

となっています。

 

22行目: string = [[NSString alloc]initWithFormat:@”変更後”];

再度、NSString型「string」オブジェクトを「alloc」して「init」します。

14行目で、「alloc」したのと別のメモリ領域にオブジェクトを生成します。

22行目で、「string」オブジェクトを新たに生成したので「string」オブジェクトの参照先 ”変更後”の「参照カウンタ」は、1になります。

一方、14行目で生成した「string」オブジェクトの参照先 ”変更前”の「参照カウンタ」は、

14行目で、生成した「string」オブジェクトの参照がなくなるので、−1され1になります。


Tip_01_01_008e

 

23行目: testClass.strongProperty = string;

「testClass.strongProperty」に「string」オブジェクトの参照先 ”変更後” のアドレスがセットされます。

「testClass.strongProperty」は「strong」属性のプロパティです。

「strong」属性のプロパティからの参照は参照カウンタを+1します。

なので、「string」オブジェクトの参照先 ”変更後” の「参照カウンタ」は、この時点で2になります。

一方、14行目で生成した「string」オブジェクトの参照先 ”変更前” の「参照カウンタ」は、

「testClass.strongProperty」からの参照がなくなるので、−1され0になります。


Tip_01_01_008f

 

25行目: NSLog(@”strongProperty = %@ weakProperty = %@”, testClass.strongProperty, testClass.weakProperty);

「testClass.strongProperty」と「「testClass.weakProperty」」の値を確認します。

strongProperty = 変更後 weakProperty = (null)

となっています。

 

ここまでの処理で、14行目で、生成した「string」オブジェクトの参照先 ”変更前”の「参照カウンタ」は、0になりました。

ARCでは、オブジェクトは「参照カウンタ」が0になると、割り当てが解放されます。

「testClass.weakProperty」からの参照が残っていますが、

「testClass.weakProperty」はweak属性のプロパティなので、

弱い参照があるだけでは、オブジェクトの寿命を保つことはできません。

なので、14行目で、生成した「string」オブジェクトの参照先 ”変更前” は解放され、その結果、「testClass.weakProperty」はnilになります。

 

補足:

このように、プログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する機能のことを  ガベージコレクション(garbage collection; GC)といいます。

Objective-Cでは、ガベージコレクションとして、参照カウント方式を採用しています。

参照カウント方式には、

・MRR(Manual Retain-Release、明示的に記述したコードによる獲得と解放)

・ARC(Automatic Reference Counting、自動参照カウント)

の2つの手段があります。

「strong」と「weak」プロパティの属性は、ARCで使用する属性です。

 

@propertyの属性 atomic と nonatomic

 

@propertyの属性の「atomic」と「nonatomic」について整理してみました。

 

確認環境:Xcode 7.2

 

Appleの文書には、「atomic」プロパティについて、以下のように記載されています。

なお、@propertyの属性は特に何も指定しなければ、「atomic」になります。

参考文書:

Objective-Cによるプログラミング

自動生成されたアクセサは、異なるスレッドからいくつか同時に呼び出したとしても、完全な形で値を取得/設定できる。

注意: プロパティのアトミック性は、スレッドセーフであるかどうかとは別の概念です。

あるスレッドが、アトミックなアクセサを使って、XYZPersonオブジェクトの姓と名前を変更する、

という状況を考えてみましょう。

同時に別のスレッドが姓と名前を取得しようとすれば、アトミックなゲッタは(クラッシュすることなく)完全な文字列を返しますが、

それが正しい組み合わせである保証はありません。

変更前に姓にアクセスし、変更後に名前にアクセスすると、

正しく対応していない氏名が得られることになるのです。

 

これを検証してみます。

まず、「firstProperty」という「atomic」属性のプロパティを1つ持つ「TESTClass002」クラスを作成します。

 

TESTClass002.h

「ViewController.m」の「viewDidLoad」で、

「TESTClass002」クラスのインスタンス「testClass」を作成し、

メインスレッドとバックグラウンドスレッドで「firstProperty」に文字列を追加していく処理をしてみます。

 

ViewController.m

デバッグナビゲータでは、バックグラウンドのスレッドが生成されているのを確認できます。


Tip_01_01_006b

実行結果をログで確認します。


Tip_01_01_006a

この結果をみると、メインスレッドとバックグラウンドスレッドで同時に「firstProperty」プロパティにアクセスしているのがわかります。

 

また、赤い囲みの箇所ですが、時系列で処理の流れをみると、

1.メインスレッド:処理開始「・・・back96」という文字列を取得。

2.バックグラウンドスレッド:処理開始「・・・back96」という文字列を取得。

3.メインスレッド:処理終了「・・・back96」に「99」という文字列を追加。

4.バックグラウンドスレッド:処理終了「・・・back96」に「back97」という文字列を追加。

となっていることがわかります。

注意する点は、3.でメインスレッドが追加した「99」という文字列が、書き換えられてしまっているというところです。

これは、@propertyで属性を「atomic」にしても、この点は保証されないようです。

つまり、

注意: プロパティのアトミック性は、スレッドセーフであるかどうかとは別の概念です。

ということなんでしょうか。

並列処理やマルチスレッドを使う場合、この点に注意して、プログラムを実装しないとダメですね。

 

次は、「nonatomic」の検証をしてみます。

Appleの文書には、「nonatomic」プロパティについて、以下のように記載されています。

参考文書:

Objective-Cによるプログラミング

nonatomicというプロパティ属性を指定すれば、自動生成されるアクセサは、単に値を直接取得/設定するだけのものになります。

異なるスレッドから同時にアクセスしたときの結果は保証されません。

その代わりnonatomicなプロパティはatomicなプロパティと違ってアクセス処理が高速であり、また、たとえばゲッタのみ独自に実装して組み合わせることも可能です。

 

「TESTClass002」クラスの「firstProperty」のプロパティの属性を「nonatomic」に変更します。

 

TESTClass002.h

実行してみます。

すると、半々くらいの確率で、正常に実行できたり、エラーになったりします。

正常に実行できた時のログはこんな感じです。

これは、@propertyで属性を「atomic」の時と同様です。


Tip_01_01_006c

 

エラーになった時は、こんなメッセージがでます。

ケース1 malloc: *** error for object 0x7fb861668d20: pointer being freed was not allocated

ケース2 *** -[CFString release]: message sent to deallocated instance 0x7fe821d80af0

 

つまり、

異なるスレッドから同時にアクセスしたときの結果は保証されません。

「nonatomic」なんで、クラッシュしちゃうということなんでしょうか。

 

結論としては、

・並列処理やマルチスレッドを使う場合、「atomic」か「nonatomic」を意識すること。

・複数のスレッドから同時にアクセスされる可能性があるプロパティ(インスタンス変数)は、「atomic」にする。

・さらに、同時アクセスされた時にどうするかを考えて実装する。

という感じでしょうか。

 

なお、wikiによると、「atomic」とは「不可分操作」とのこと。

「不可分操作」とは以下の2つの条件を満たさなければならないらしい。

1.全操作が完了するまで、他のプロセスはその途中の状態を観測できない。

2.一部操作が失敗したら組合せ全体が失敗し、システムの状態は不可分操作を行う前の状態に戻る。

 

All or Nothingということですね。