@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ということですね。