この行:
sonicBoom.damagedObjects = [[NSMutableArray alloc]init];
問題です。大まかに言えば、コンパイラがそれを次のように拡張したものです。
[sonicBoom setDamagedObjects:[[[NSMutableArray alloc]init]retain]];
sonicBoomは、damagedObjects
プロパティに保持修飾子を使用させることにより、メソッドで作成した配列に対するクレームを取得しようとし、allocとinitのペアによって既に本質的に返される1よりも保持カウントを1つ増やします。(allocは最終的に
したがって、標準のcocoaメモリ管理ガイドラインまたはMVCに準拠していないため、配列への参照が2つあります。配列を自動解放するか、コンビニエンスコンストラクター[NSMutableArray array];
を使用します(cocoaではメソッドの厳密なサブセットのみがオブジェクトへの+1参照を返すことができるため、自動解放されます)。さらに良いことに、ソニックブームオブジェクトに独自の配列を作成させて、メモリ的に「所有」するようにします。
編集(私が不十分なレベルの詳細<咳>を提供したと感じる人のために)。
retain
@synthesizeディレクティブを使用してコンパイラで生成された標準のセッターを使用したObjective-Cオブジェクト(特にNSObjectまたはNSProxyから派生したオブジェクト)の手動参照カウント環境でのメモリ修飾子として、標準の呼び出しセットで構成されます。次の形式で表示される場合と表示されない場合があります(生成されたセッターで保持カウントのバランスをとる一般的な概念は、以下の擬似コードとほぼ同じです)。
- (void)setMyObject:(NSObject*)newMyObject {
[_myObject release];
_myObject = [newMyObject retain];
}
もちろん、これはnonatomic
セッターの(別のスレッドによって中断される可能性がある)バリエーションです。バージョンはatomic
、と非常によく似て実装されています
- (void)setMyObject:(NSObject*)newMyObject {
@synchronized(self) {
[_myObject release];
_myObject = [newMyObject retain];
}
}
明らかに、これらはCocoaセッターに固有のKey-Value-Codingメカニズムを省略していますが、これらの演算子は一般にAppleによって公開されておらず、メモリ管理などの主題の範囲外であるため、その実装は演習として残されています読者に。
ただし、オープンソースバージョンのNSObjectが出力する呼び出しを評価することで、メモリ管理を詳しく調べることができます*。
*このバージョンのNSObjectは、Mac OS X SDKに存在するバージョンに対応しているため、NSObjectのすべての基盤となる実装が提供されているものと一致することが保証されるわけではないことに注意してください。
-retain
、この記事の執筆時点で、NSObject(ランタイムバージョン532、リビジョン2)の最新のオープンソースバージョンによって実装されているように、前の構造が失敗した場合、3つの個別の内部構造を次々に呼び出し、最終的にルートNSObjectの「遅い保持」。注意することが重要です:NSObjectはObjective-C ++で実装されており、内部LLVMライブラリへの無償の呼び出しがあります。NSObjectの分析は、これに遭遇した場合に終了します。
-retain
すべてのルートObjective-Cメモリ管理呼び出しと同様に、成功時にオブジェクト自体を返す16バイトの整列されたメソッドとして実装されます。NSObject.mmによると、retainは次のようになります。
- (id)retain __attribute__((aligned(16))) {
if (OBJC_IS_TAGGED_PTR(self)) return self;
SideTable *table = SideTable::tableForPointer(self);
if (OSSpinLockTry(&table->slock)) {
table->refcnts[DISGUISE(self)] += 2;
OSSpinLockUnlock(&table->slock);
return self;
}
return _objc_rootRetain_slow(self);
}
*デバッグを容易にするために、起動時に適切なフラグが渡されると、retainはObjectAllocに置き換えられます。現時点では、ObjectAllocの分析は提供されていません。
それぞれを個別に調べるには、ファイルにアクセスする必要もあります。このファイルobjc-private.h
は、同じディレクトリにあるこの投稿でタグ付けされたNSObjectバージョンと一緒に自由に使用できます。
まず、retainは、ポインターがタグ付けされているかどうかを確認します。もちろん、タグは何を意味する場合もありますが、NSObjectにとっては、ポインターの最後のビットに0x1のアドレスが含まれているかどうかを意味します(16バイトのアラインメントのため、char*を除くすべてのタイプのアドレスはMac OS Xでは、アドレスの末尾に0が付いていることが保証されています)。したがって、に OBJC_IS_TAGGED_PTR(PTR)
拡張します
((uintptr_t)(PTR) & 0x1)
ポインタがタグ付けされている場合、NSObjectは簡単な方法で自分自身を返します(通常、タグ付けされたポインタは無効なアドレスを示すため)。
次に、自分自身への指定されたポインターに対して、テーブルで-retain
スピンロックを試行します(OS
iOSでは-prefixedメソッドは使用できないことに注意してください)。NSObjectの意味でのテーブルは、オブジェクトの保持カウントを追跡するものです。これらは、ルートNSObjectとともに割り当てられる非常に単純なC++クラスです。興味深いトリックはそのDISCGUISE(x)
マクロにあります。それは次のように拡張されます。
#define DISGUISE(x) ((id)~(uintptr_t)(x))
お気づきの方もいらっしゃると思いますが、指定されたオブジェクトのポインタが反転しています。これは、次の行のオブジェクトの参照カウントの2倍の増分を機器から隠すためにのみ行われると想定できます。これはSideTable
、1回の呼び出しで保持カウントが2倍になったオブジェクトは、未定義の動作と見なされる可能性があるため-release
です(送信時、つまり、SideTableは、保持カウントを2だけデクリメントするように指示されています。オブジェクトの割り当てが解除されているかどうかを確認するために、アドレスの最下位ビットを使用できるように、参照カウントが2つ増えます(これも、タグ付きポインターに戻ります)。すべてがうまくいけば、スピンロックが解除され、NSObjectが戻ります。
スピンロックがたまたま取得に利用できない場合、NSObjectは「スローリテンション」と呼ばれるものに頼ります(SideTable
スピンロックのロックとロック解除のプロセスはやや費用がかかるため)。この場合、同じプロセスが発生します。ただし、NSObjectは、スピンロックを盗んでロックダウンし、SideTable
参照カウントを2増やしてから、スピンロックのロックを解除します。_objc_rootRetain_slow
プロセス全体は、次のような1つのC関数で表されます。
id _objc_rootRetain_slow(id obj) {
SideTable *table = SideTable::tableForPointer(obj);
OSSpinLockLock(&table->slock);
table->refcnts[DISGUISE(obj)] += 2;
OSSpinLockUnlock(&table->slock);
return obj;
}