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

这将被初始化,并且损坏的对象数组被分配到主游戏类中,粒子系统在完成时通过设置 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];

}

出于某种原因,只有一个发布消息到阵列我得到了泄漏。我检查了保留计数(我通常从不考虑这些),它是 2,所以我现在发送了两次发布消息,这似乎已经解决了问题。

这是发布它的最佳方式吗,有人可以解释为什么需要这样做吗?

谢谢

4

3 回答 3

5

It's happening because of this line:

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

The init increases the reference count by 1, and then setting the retained property also increases it.

Change it to:

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

Alternatively you could have used:

sonicBoom.damagedObjects = [NSMutableArray array];

Which returns an autoreleased object, and the only object your class owns is the one it retained with the setter.

Also, FWIW, fixing a leak by releasing something twice in dealloc is definitely not a good idea. If one day you decided to set damagedObjects using some other method that was returning an autoreleased array, your app would start crashing and tracking down crashes like that can be a pain.

于 2013-03-01T17:36:51.553 回答
3

This line:

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

Is the problem. Roughly put, here's what the compiler expands that out to:

[sonicBoom setDamagedObjects:[[[NSMutableArray alloc]init]retain]];

sonicBoom, by having it's damagedObjects property use a retain qualifier, tries to stake a claim to the array that you've created in your method, incrementing it's retain count by 1 over the 1 that is already inherently returned by the alloc and init pair. (alloc eventually calls

Thus, you have two references to the array because you're not following standard cocoa memory management guidelines or MVC. Either autorelease the array or use the convenience constructor [NSMutableArray array]; (which autoreleases for you because cocoa allows only a strict subset of methods to return +1 references to objects) . Better yet, make the sonic boom object create its own array, so that it "owns" it memory-wise.

Edit (for those who feel I have provided an insufficient level of detail <cough>).

retain as a memory qualifier in a Manual Reference Counting environment for Objective-C objects (specifically those that descend from NSObject, or NSProxy) with a standard, compiler generated setter through the use of the @synthesize directive, consists of a standard set of calls which may or may not appear in the following form (the general concept of how retain counts are balanced in the generated setter is nearly the identical to the pseudo-code below):

- (void)setMyObject:(NSObject*)newMyObject {
    [_myObject release];
    _myObject = [newMyObject retain];
}

Of course, this is an nonatomic (able to be interrupted by another thread) variation of the setter. The atomic version, is implemented very similarly to

- (void)setMyObject:(NSObject*)newMyObject {
    @synchronized(self) {
        [_myObject release];
        _myObject = [newMyObject retain];
    }
}

Obviously, these omit the Key-Value-Coding mechanism inherent in Cocoa setters, but those operators are not generally exposed to the public by Apple, and are out of bounds for a subject such as memory management, so it’s implementation is left as an exercise to the reader.

We can, however, take a closer look at memory management by evaluating the calls the Open Source version of NSObject puts out*.

*Note that this version of NSObject corresponds to the version present in the Mac OS X SDK, and therefore, not all underlying implementations of NSObject will be guaranteed to match what is provided.

-retain, as implemented by the latest open source version, at the time of this writing, of NSObject (runtime version 532, rev. 2), calls down to 3 individual internal constructs, one after another, should the previous one fail, finally ending in a "slow retain" of the root NSObject. It is important to note: NSObject is implemented in Objective-C++, with gratuitous calls to the internal LLVM library. Such is where our analysis of NSObject will end should it be encountered.

-retain is implemented, like all root Objective-C memory management calls, as a 16-byte aligned method returning the object itself upon success. According to NSObject.mm, retain looks like this:

- (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 is replaced with ObjectAlloc when the appropriate flag is passed at launch for easier debugging. An analysis of ObjectAlloc is not provided at this time.

To examine each in pieces, it is also necessary to access the file objc-private.h, which is also freely along with the NSObject version tagged in this post in the same directory.

To begin, retain checks whether the pointer has been tagged. Of course, a tag could mean anything, but to NSObject, it means whether or not the pointer contains the address of 0x1 in it’s last bit (if you’ll recall, because of 16 byte alignments, all types except char* have addresses that are guaranteed by Mac OS X to have a 0 at the end of their addresses). Thus, OBJC_IS_TAGGED_PTR(PTR) expands out to

((uintptr_t)(PTR) & 0x1) 

If the pointer is tagged, NSObject takes the easy way out and simply returns itself (because usually tagged pointers indicate invalid addresses).

Next, -retain tries a spin lock (note that OS-prefixed methods are unavailable on iOS) on the table for the given pointer to self. Tables, in the sense of NSObject, are what keep track of the retain count of the object. They are very simple C++ classes allocated along with the root NSObject. An interesting trick lies in that DISCGUISE(x) macro. It expands out to:

#define DISGUISE(x) ((id)~(uintptr_t)(x))

If you’ll notice, it’s flipping the pointer of the given object. I can only assume that this is done to hide SideTable’s double increment of the reference count of the object on the next line from instruments, as any object with a doubled retain count from one call could likely be seen as undefined behavior (when -release is sent, so to is the SideTable told to decrement it’s retain count by 2). The reference count is increased by two so as to keep the lowest bit of the address available to check if the object is in the process of being deallocated (which again, circles back around to tagged pointers). If all goes well, then the spin lock is released, and NSObject returns itself.

If the spin-lock happens to be unavailable for the taking, NSObject resorts to what it calls a “slow retain” (because the process of locking and unlocking the SideTable’s spin lock is mildly expensive), in which case the same process occurs, but NSObject steals and locks down the spin lock for it’s SideTable, increments it’s reference count by 2, then unlocks the spin lock. The entire process is represented by one C-function _objc_rootRetain_slow, which looks like this:

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?

在dealloc中,

self.damagedObjects = nil;

有一个小问题:如果你重写了 setter 方法来做一些聪明的事情,它可能会在-dealloc.

于 2013-03-01T17:54:11.017 回答