[英]NSMutable Array Leak
我正在用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,所以我現在發送了兩次發布消息,這似乎已經解決了問題。
這是發布它的最佳方法,有人可以解釋為什么需要這樣做嗎?
謝謝
由於這一行而發生了:
sonicBoom.damagedObjects = [[NSMutableArray alloc]init];
初始化將引用計數增加1,然后設置保留屬性也將其增加。
更改為:
NSMutableArray *array = [[NSMutableArray alloc]init];
sonicBoom.damagedObjects = array;
[array release];
或者,您可以使用:
sonicBoom.damagedObjects = [NSMutableArray array];
它返回一個自動釋放的對象,並且您的類擁有的唯一對象是它與setter一起保留的對象。
同樣,FWIW通過在dealloc中釋放兩次來修復泄漏絕對不是一個好主意。 如果有一天您決定使用其他方法(返回一個自動釋放的數組)來設置damagedObjects
,則您的應用程序將開始崩潰並跟蹤崩潰,這可能會很痛苦。
這行:
sonicBoom.damagedObjects = [[NSMutableArray alloc]init];
是問題。 粗略地說,這是編譯器將其擴展為:
[sonicBoom setDamagedObjects:[[[NSMutableArray alloc]init]retain]];
sonicBoom通過讓其damagedObjects
的damagedObjects
屬性使用保留限定符來嘗試對您在方法中創建的數組提出主張,使它的保留計數相對於alloc和init對已固有返回的1增加1。 (最終分配呼叫
因此,由於沒有遵循標准的可可內存管理准則或MVC,因此對數組有兩個引用。 自動釋放數組或使用便利構造函數[NSMutableArray array];
(它會為您自動發布,因為可可只允許方法的嚴格子集返回對對象的+1引用)。 更好的是,使聲波繁榮對象創建自己的數組,以便在內存方面“擁有”它。
Edit(對於那些誰覺得我已經提供了詳細的水平不足< 咳嗽 >)。
使用標准的編譯器通過使用@synthesize指令生成的setter,在Objective-C對象(特別是從NSObject或NSProxy派生的對象)的“手動引用計數”環境中retain
為內存限定符,它由一組標准調用組成可能會或可能不會以以下形式出現(在生成的setter中如何平衡保留計數的一般概念與下面的偽代碼幾乎相同):
- (void)setMyObject:(NSObject*)newMyObject {
[_myObject release];
_myObject = [newMyObject retain];
}
當然,這是二傳手的nonatomic
(可以被另一個線程打斷)變化。 atomic
版本的實現與
- (void)setMyObject:(NSObject*)newMyObject {
@synchronized(self) {
[_myObject release];
_myObject = [newMyObject retain];
}
}
顯然,這些方法省略了Cocoa設置程序固有的鍵-值編碼機制,但是這些操作員通常不會被Apple公開使用,並且不受諸如內存管理之類的主題的限制,因此將其實現留為練習給讀者。
但是,我們可以通過評估NSObject的開放源代碼版本發出的調用來仔細研究內存管理。
*請注意,此版本的NSObject與Mac OS X SDK中提供的版本相對應,因此,並非所有NSObject的基礎實現都可以保證與提供的內容相匹配。
-retain
在撰寫本文時,由最新的開放源代碼版本實現的NSObject(運行時版本532,修訂版2)實現了對3個獨立內部構造的調用,如果前一個內部構造失敗,則逐個調用,最后以根NSObject的“緩慢保留”結尾。 重要的是要注意:NSObject是用Objective-C ++實現的,它對內部LLVM庫進行了免費調用。 這就是我們對NSObject的分析將在遇到的地方結束的地方。
與所有根Objective-C根內存管理調用一樣, -retain
被實現為16字節對齊的方法,一旦成功,該方法將返回對象本身。 根據NSObject.mm,保留看起來像這樣:
- (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版本一起在同一目錄中自由使用。
首先,保留檢查指針是否已被標記。 當然,標簽可能有任何含義,但是對於NSObject而言,它意味着指針的最后一位是否包含0x1的地址(如果您還記得,由於16字節對齊,除char *外的所有類型的地址都由Mac OS X保證其地址末尾帶有0)。 因此, OBJC_IS_TAGGED_PTR(PTR)
擴展為
((uintptr_t)(PTR) & 0x1)
如果標記了指針,則NSObject會輕松退出並簡單地返回自身(因為通常帶標記的指針指示無效的地址)。
接下來, -retain
在給定的指向self的指針上嘗試旋轉鎖定(請注意, OS
前綴的方法在iOS上不可用)。 在NSObject的意義上,表是跟蹤對象的保留計數的對象。 它們是與根NSObject一起分配的非常簡單的C ++類。 一個有趣的技巧在於該DISCGUISE(x)
宏。 它擴展為:
#define DISGUISE(x) ((id)~(uintptr_t)(x))
如果您會注意到,它就是翻轉給定對象的指針。 我只能假設這樣做是為了將SideTable
的對象的引用計數的兩倍增量隱藏在工具的下一行,因為任何一次調用中保留計數加倍的對象都可能被視為未定義的行為(當-release
已發送,因此通知SideTable將其保留計數減2)。 引用計數增加2,以保持該地址的最低位可用於檢查對象是否正在釋放過程中(再次循環回加標簽的指針)。 如果一切順利,則釋放自旋鎖,並且NSObject會返回自身。
如果自旋鎖碰巧無法使用,則NSObject會SideTable
所謂的“緩慢保留”(因為鎖定和解鎖SideTable
的自旋鎖的過程非常昂貴),在這種情況下,也會發生相同的過程,但NSObject會SideTable
竊取並鎖定自旋鎖,將其引用計數增加2,然后解鎖自旋鎖。 整個過程由一個C函數_objc_rootRetain_slow
,如下所示:
id _objc_rootRetain_slow(id obj) {
SideTable *table = SideTable::tableForPointer(obj);
OSSpinLockLock(&table->slock);
table->refcnts[DISGUISE(obj)] += 2;
OSSpinLockUnlock(&table->slock);
return obj;
}
當不使用ARC(或編寫可在ARC或非ARC模式下使用的代碼)時,我更喜歡以下樣式:
在-createSonicBoom,
sonicBoom.damagedObjects = [NSMutableArray array]; // or e.g. arrayWithCapacity:10?
在dealloc中
self.damagedObjects = nil;
有一個小陷阱:如果您重寫了setter方法來做一些聰明的事情,那么它可能會在-dealloc
做錯事情。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.