1

私はcocos2dでiPhoneアプリを作成していますが、リークインスツルメントがNSMutableArrayにフラグを立てて、アプリがリークしているように見えるという問題が発生しています。問題は修正しましたが、そもそもなぜ発生したのかよくわからないので、誰かに説明してもらえるといいのですが。

CCParticleSystemQuadをサブクラス化して、「damagedObjects」と呼ばれるNSMutable配列を含むいくつかのインスタンス変数を追加できるようにしました。

@interface SonicBoom : CCParticleSystemQuad{

NSMutableArray *damagedObjects;
HelloWorldLayer *gameClass;
CGPoint radiusPoint;
CGPoint origin;    
}

@property(nonatomic, retain) NSMutableArray *damagedObjects;
@property(nonatomic, retain) HelloWorldLayer *gameClass;
@property CGPoint radiusPoint;
@property CGPoint origin;

@end

これは初期化され、damageedObjects配列がメインゲームクラスに割り当てられます。パーティクルシステムは、autoRemoveOnFinishプロパティを設定することにより、完了時に削除されます。

-(void)createSonicBoom{

sonicBoom = [SonicBoom particleWithFile:@"SonicBoom.plist"];

sonicBoom.damagedObjects = [[NSMutableArray alloc]init];

sonicBoom.gameClass = self;

sonicBoom.autoRemoveOnFinish = YES;

//etc..........

次に、「SonicBoom」クラスのdeallocメソッドをオーバーライドして、「damagedObjects」配列も解放しました。

-(void)dealloc{

NSLog(@"Removing SonicBoom");

NSLog(@"damaged objects retain count = %i", damagedObjects.retainCount);

gameClass.sonicBoomActive = NO;

[damagedObjects removeAllObjects];

[damagedObjects release];
[damagedObjects release];

[super dealloc];

}

何らかの理由で、アレイへのリリースメッセージが1つだけで、リークが発生していました。保持数を確認しましたが(普段は気にしないでください)、2でしたので、リリースメッセージを2回送信しましたが、問題は解決したようです。

これはリリースするための最良の方法ですか、そして誰かがこれが必要になる理由を説明できますか?

ありがとう

4

3 に答える 3

5

それはこの行のために起こっています:

sonicBoom.damagedObjects = [[NSMutableArray alloc]init];

initは参照カウントを1増やし、次に保持プロパティを設定すると参照カウントも増えます。

次のように変更します。

NSMutableArray *array = [[NSMutableArray alloc]init];
sonicBoom.damagedObjects = array;
[array release];

または、次を使用することもできます。

sonicBoom.damagedObjects = [NSMutableArray array];

これは自動解放されたオブジェクトを返し、クラスが所有する唯一のオブジェクトは、セッターで保持されているオブジェクトです。

また、FWIW、deallocで何かを2回リリースしてリークを修正することは、絶対に良い考えではありません。ある日、自動解放された配列を返す他の方法を使用して設定することにした場合damagedObjects、アプリがクラッシュし始め、そのようなクラッシュを追跡するのは面倒な場合があります。

于 2013-03-01T17:36:51.553 に答える
3

この行:

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スピンロックを試行します(OSiOSでは-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; 
} 
于 2013-03-01T17:35:46.027 に答える
2

ARCを使用しない場合(またはARCモードまたは非ARCモードで使用できるコードを作成する場合)、私は次のスタイルを好みます。

-createSonicBoom,

sonicBoom.damagedObjects = [NSMutableArray array]; // or e.g. arrayWithCapacity:10?

デアロックでは、

self.damagedObjects = nil;

ちょっとした落とし穴があります。セッターメソッドをオーバーライドして何か賢いことをした場合、で間違ったことをする可能性があります-dealloc

于 2013-03-01T17:54:11.017 に答える