sos の 作業メモ

プログラミングや英会話学習、マイルや旅行、日常生活など。最近はWebFormなASP.NETのお守りがお仕事です。

日々の生活にhappyをプラスする|ハピタス Gポイント

Objective-Cフレーズブック 第3章

Objective‐Cフレーズブック―使いこなすためのコード&イディオム100+

Objective‐Cフレーズブック―使いこなすためのコード&イディオム100+

前回の続き

第3章 メモリ管理

  • ObjCには言語レベルでメモリの割当/解放を行う機能はなく、Cの機構を使っている
  • 一般にクラスに+allocを送信することでオブジェクトを割り当てる。+alloc内でmalloc()等が呼ばれ、オブジェクトの領域が割り当てられてインスタンスが生成される。インスタンスに-deallocを送信すると領域が解放される
  • FoundationFrameworkによってメモリ管理に参照カウント機能が追加された。新しいコンパイラではさらに支援機能が提供されており、自分で参照カウントのコードを各必要はなくなっている

保持と解放

  • NSObjectから派生する全てのオブジェクトには、参照カウントが関連付けられている
  • 参照カウントが0になるとオブジェクトが破棄される。+allocや+new等で作成されたオブジェクトは、作成時に参照カウントが1に設定される
  • オブジェクトの参照カウントを制御するには、-retainと-releaseを使う
  • -retainCountで参照カウントの値を取得できる。参照カウントはオブジェクトが保持されている数であり、retainを伴わない単なる参照数は含まないことに注意する
  • ObjCのポインタは、所有と非所有の2種類の参照に分類される
  • インスタンス変数とグローバル変数は、一般に所有のポインタなので所有の参照を割り当てる必要がある。一時変数は非所有の参照が多い

インスタンス変数への代入

- (void) setStringValue: (NSString*)aString
{
    [_string release];
    _string = [aString retain];
}

このような単純な設定メソッドでも、aStringと_stringが同じオブジェクトを指している場合は正常に動作しなくなるため、以下のようにまず最初に新しいオブジェクトを保持し、それから古いオブジェクトを解放するようにする

- (void) setStringValue: (NSString*)aString
{
    id tmp = [aString retain];
    [_string release];
    _string = tmp;
}

なお、このメソッドはThread Safeではないが、自分でatomicな操作を行うコードを記述するよりもatomicな@propertyを利用した方がよい

自動的な参照カウント(Automatic Reference Count)

  • Appleは、iOS5/Mac OS X 10.7でARCを採用した。概念的には-retainと-releaseを呼び出す時期をコンパイラが自動的に判断して呼び出すものだが、実装はもう少し複雑になる
  • メッセージ送信を直接コードに挿入せず、objc_retain()やobjc_release()を挿入し、オプティマイザがこれらを結合/除去する。一般的なケースでは、これらの関数は非常に効率が良い
  • 単純な用途では、ARCを使用すればメモリ管理が不要となるが、CとObjCオブジェクトの間の違いが明確になる
  • ARCはポインタを、_strong, _week, _unsafe_unretainedの3つのカテゴリに分類する

ポインタ引数によるオブジェクトの返却

ARCでは、ポインタへのポインタの引数は多少複雑になる。配列を渡したりオブジェクトを返したりするときにこれを使うが、配列を渡す場合にはその配列をconstとして宣言すると効率がよくなる。

__weak id weak;
int writeBack(id* aValue)
{
    *aValue = [NSObject new];
    weak = *aValue;
    return 0;
}

int main(void)
{
    @autoreleasepool {
        id object;
        writeBack(&object);
        NSLog(@"Object: %@", object);
        object = nil;
        NSLog(@"Object: %@", weak);
    }
    NSLog(@"Object: %@", weak);
    return 0;
}

このサンプルでは、自動解放プールが破棄される時に_weakな参照がnilになる。

オブジェクトを返す場合、ARCはかなり複雑なコードを生成する。上のwriteBack()への呼び出しは、下のコードのように解釈される。

    id tmp = [object retain];
    writeBack(&tmp);
    [tmp retain];
    [object release];
    object = tmp;

writeBack()は、いかなる場合でも渡されたポインタに自動解放オブジェクトを格納し、渡された値を解放しない。呼び出し元は、渡された値が非所有の参照であることを常に保証する責任がある。そのためには、自動解放オブジェクトへのポインタか、保持されたオブジェクトへのポインタのコピーである必要がある。

objectを _autoreleasing として宣言した場合は、最初にobjectに格納されている値が自動的に解放されるため、生成されるコードはもっと単純になる。

また、パラメーターをoutとしてマークした場合、呼び出された側が値を読み取らないことを保証するため、コンパイラは呼び出し前にobjectにポインタのコピーを作成するステップを省略する。

複数のオブジェクトを渡す必要がある場合にはNSArrayインスタンスを返せばよい。他にもいくつかの代替案はあるが、複雑で労力にあわないのでやめた方がよい。

配列を使う場合には、id*の代わりに _unsafe_unretained id[]を使う。これによって書き戻しメカニズムが使用されない事が保証される。

保持の循環の回避

  • 参照カウントの問題は循環が検出されないことだが、大きな問題ではなく回避が可能
  • 最も一般的なのがデリゲートパターン。オブジェクトはデリゲートへの参照を必要とし、デリゲートはオブジェクトへの参照を必要とするが、オブジェクトがデリゲートを保持しないようにすることで対処する
  • setDelegateに引数としてdelegateオブジェクトを渡す時に、他のオブジェクトがその参照を保持するようにしないと、途中でdelegateオブジェクトが削除されてしまう可能性がある
  • ARCを使用する場合、_weak または _unsafe_unretainedとしてインスタンスをマーク可能。dangling pointer(不正な領域を指すポインタ)になる可能性がない_weakの方が安全。_weakならdelegateオブジェクトが破棄されるとき、インスタンス変数はnilに設定される
  • _weak使用の欠点としてiOS5, OS X 10.7, GNUstepより以前の環境では動かないことと、パフォーマンスの低下があげられる。 _weakなポインタにアクセスすると、必ずヘルパー関数が呼び出され、オブジェクトの有効性が検査される
  • 妥協策としては、デバッグ時には_weak, リリース時には_unsafe_unretaindedを使用すること。ポインタにメッセージを送信する前に、nilかどうかをチェックするアサート分を挿入する

個人的には、古い環境のことなんてもう考慮する必要もないし、デバッグとリリースで処理を変えるなんて、よほどパフォーマンスを求められている時しかやらない方が良いと思います。

ARCへの移行

  • Clangには、古いObjCコードをARCに対応するように書き換える移行ツールが提供されている
  • 移行ツールは、-retain,-release,autoreleaseの送信を全て削除し、[super dealloc]を-deallocメソッドから削除する(ARC下では、それらはコンパイラによって自動的に挿入される)
  • malloc()で割り当てたメモリ等を解放する場合にのみ-deallocを実装する必要がある
  • カスタム参照カウントメソッドを実装していた場合、それらを削除する必要がある
  • 最も大きな問題が生じるのは、Cの構造体にObjCポインタを格納しようとする場合だが、構造体の代わりにパプリックなインスタンス変数を備えたObjCオブジェクトを使用する事で回避すればよい
  • ObjCオブジェクトを参照する構造体の使用が安全だと考えられる唯一のケースは、引数として呼び出し先に渡すときのみ。呼び出し元において有効な限りは、_unsafe_unretainedで修飾することが可能
  • 移行ツールを使用すると、assignプロパティがunsafe_unretainedかweakに書き換えられる
  • 移行ツールは、必要な場所に_bridgeキャストを挿入しようとするが、これらは確認する必要がある

自動解放プール

  • 参照カウントに関する循環以外の大きな問題に、どのオブジェクトにも特定の参照を所有しない短い期間が存在することがある
  • Cでは呼び出し元と呼び出し先のどちらが領域を割り当てるのかは大きな問題だが、ObjCではより重大。メソッドからオブジェクトが返された時に、一時的なオブジェクトなら呼び出し側で解放する必要があるが、インスタンス変数へのポインタが返された場合には逆に解放してはならないという、判断に困る事態となる
  • これに対する解決策が自動解放プールであり、オブジェクトに-autoreleaseを送信すると、現在アクティブなNSAutoreleasePoolインスタンスに追加され、これが破壊される時に登録された全てのオブジェクトに-relaseが送信される
  • -autorelaseは延期された-releaseメッセージとも言える
  • ただし、ARC環境化ではNSAutorelasePoolは無効とされるため、 @autoreleasepool構造を使用する

 

@autoreleasepool{
     hogehoge;
}

自動解放コンストラクタの使用

+ (id)object
{
    return [[[self alloc] init] autorelease];
}
  • 名前付コンストラクタを使用してオブジェクトを作成した場合、自動解放されることを期待するため、-autorelaseを送信しておく
  • ARCでは、これらの規約はメソッドファミリで形式化されており、alloc,new,copy,mutableCopyのいずれかで始まるメソッドは、所有の参照(保存されていない場合は解放する必要がある参照)を返し、他のメソッドは非所有の参照(自動解放されるか、解放されないことが保証される参照)を返す

アクセサでのオブジェクトの自動解放

  • アクセサでは、新しい値を設定する時には古い値を自動解放するか、自動解放を行うアクセサを書くか

自動ガベージコレクションのサポート

iOSでは利用できないので省略

Cとの相互動作

  • ObjCオブジェクトと非オブジェクトポインタ間のキャストは3つのブリッジキャストの何れかを使う必要がある
  • 所有権の移動しないキャスト。someObjectへの全ての参照がなくなると、aPointerは不正な領域を指すdangling pointerになる

    void aPointer = (__bridge void)someObject;

  • 所有権をARCの制御化から移動するキャスト

    void aPointer = (__bridge_retained void)someObject;

  • 所有権をARCの制御化に移動するキャスト

    id anotherObject = (__bridge_transfer void*)aPointer;

オブジェクトの破棄

  • モードに応じて破棄の際に起動されるメソッドが3つある
  • -.cxx_destructメソッドは、ObjCランタイムによって常に呼ばれ、コンパイラが責任を持つ全てのフィールドの破棄を扱う。Objective-C++モードのC++オブジェクトと、ARCモードのObjective-Cオブジェクトポインタも含まれる
  • ガベージコレクションモードでは、-finalizeでファイルハンドルのクローズや、mallocで確保した領域の解放を行う
  • ガベージコレクションを使用しない場合は、-deallocでクリーンアップを行う。ARCを使用するなら特になにかをする必要はない

弱い参照の使用

  • _weakでオブジェクトのポインタを宣言すればzeroing weak reference(弱い参照)となる。ARCモードでオブジェクトが割り当てられたとき、参照カウントがインクリメントされない(割り当てが無くなった時もデクリメントされない)
  • 参照カウントが0になったときには、オブジェクトは破棄される(弱い参照しか無い場合も破棄)。オブジェクトが破棄された後に、弱い参照を読み取ると、ヘルパー関数によってnilが返される

スキャンされるメモリの割り当て

  • malloc()を使用するとガベージコレクタから見えないため、CFRetainとCFReleaseを使うか、NSAllocateCollectable()を使うとよいらしいが、iOSには関係ないので省略

3章終了

雑な翻訳でわかりにくい割に、凄く有用なことが書いてあるというわけでもないため、 この本を読むのに時間を割く位なら、

Objective-C プログラミング

を何度か読み直した方がうんと勉強になりそうというのが正直な感想です。 次章からパターンの解説なので、もう少しだけつきあってみる予定。

次回へ続く