自動引用計數
? 自動引用計數:指內存管理中對引用采取自動計數的技術。
內存管理/引用計數
? 持有對象引起引用計數加一 釋放對象引起引用計數減一 引用計數為零釋放對象
內存管理的思考方式
1、自己生成的對象,自己持有
? ?? ? 已 alloc,new,copy,mutableCopy 開頭的方法
2、非自己生成的對象,自己也能持有
? ? ?? 使用 retain 方法
3、不需要自己持有的對象時釋放
? ? ? 是有遍歷構造器生成的對象,被加入自動釋放池中的,使用 autorelease
4、非自己持有的對象無法釋放
? ? 多次 release,釋放已經釋放過的對象
alloc/retain/release/dealloc 實現
alloc
+ (id)alloc {
? ? ? ?return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id)allocWithZone:(NSZone *)z {
? ? ? ?return NSAllocateObject(self, 0 ,z);
}
類方法調用NSAllocateObject函數分配了對象。
struct obj_layout {
? ? ?NSUInteger ratained; // 用來記錄引用計數
};
inline id NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone) ?{
? ? ? int size = 計算容納對象所需內存大小;
? ? ?id new = NSZoneMalloc(zone, size); //分配對象所需內存
? ? ?memset(new, 0, size)
? ? ?new = (id) & ((struct obj_layout *)new) [1]; //將指針指向對象大小內存首地址,
}
alloc類方法用struct obj_layout中的retained整數來保存引用計數,并將其結構體寫入對象內存頭部。
retainCount
- (NSUInteger) retainCount {
? ? ? return NSExtraRefCount(self) + 1;
}
inline NSUInteger NSExtraRefCount(id anObject) {
? ? ?return ((struct obj_layout *)anObject)[-1].retain; // 將指針向上移1,指向 struct obj_layout
}
由對象尋址找到對象內部頭部,從而訪問其中的retained變量。
retain
- (id) retain {
? ? ? NSIncrementExtraRefCount(self);
? ? ? return self;
}
inline void NSIncrementExtraRefCount(id anObject) {
? ? ? ? ?if(((struct obj_layout *)anObject[-1]).retained == UINT_MAX - 1) { // 是否超出最大值
? ? ? ? ? ? ?[NSException raise:NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];
? ? ? ? }
? ? ? ? ((struct obj_layout *)anObject)[-1].retained++;
}
當retained變量超出最大值時發生異常,實際上只運行了使retained數量加 1
release
- (void)release {
? ? ? if (NSDecrementExtraRefCountWasZero(self)) {
? ? ? ? ? ? [self dealloc];
? ? ? ?}
}
BOOL NSDecrementExtraRefCountWasZero(id anObject) {
? ? ? ? ?if (((struct obj_layout *)anObject).retained == 0) {
? ? ? ? ? ? ? ?return YES;?
? ? ? ? ?} else {
? ? ? ? ? ? ?((struct obj_layout *)anObject).retained--;
? ? ? ? ? ? ? return NO;
? ? ? ? }
}
當retainde變量大于 0 時減 1,等于 0 時調用 dealloc 實例方法,廢棄對象。
dealloc
- (void)dealloc {
? ? ? ? ? NSDeallocateObject(self);
}
inline void NSDeallocateObject(id anObject) {
? ? ? ?struct obj_layout *o = &((struct obj_layout *)anObject)[-1];
? ? ? ?free(o);
}
廢棄由 alloc 分配的內存塊。
① 在 Objective-C 的對象中存在引用計數這一整數值。 ② 調用 alloc 或是 retain 方法后,引用計數值加1。 ③ 調用 release 后,引用計數值減 1。 ④ 引用計數值為 0 時,調用 dealloc 方法廢棄對象。
蘋果的實現
NSObject 類的 alloc 類方法
+alloc
+allocWithZone:
class_createInstance
calloc
通過calloc來分配內存塊
retainCount/retain/release實例方法實現
- retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
- retain
__CFDoExternRefOperation
CFBasicHashAddValue
-release
__CFDoExternRefOperation
CFBasicHashRemoveValue
(CFBasicHashRemoveValue 返回0時,-release 調用 dealloc)
通過調用__CFDoExternRefOperation函數,來選擇調用哪個函數
int __CFDoExternRefOperation(uintptr_t op, id obj) {
? ? ? ? ? ?CFBasicHashRef table = 取得對象對應的散列表(obj);
? ? ? ? ? ? int count;
? ? ? ? ? ? switch(op) {
? ? ? ? ? ? ? case OPERATION_retainCount:
? ? ? ? ? ? ? ? ? count = CFBasicHashGetCountOfKey(table, obj);
? ? ? ? ? ? ? ? ? return count;
? ? ? ? ? ? ? case OPERATION_retain:
? ? ? ? ? ? ? ? ? CFBasicHashAddValue(table, obj)
? ? ? ? ? ? ? ? ? ?return obj;
? ? ? ? ? ? ? case OPERATION_release:
? ? ? ? ? ? ? ? ? count = CFBasicHashRemoveValue(table, obj);
? ? ? ? ? ? ? ? ? return 0 == count;
? ? ? ? ? ? ? ?}
}
__CFDoExternRefOperation函數按retainCount/retain/release操作進行分發,調用不同的函數。
- (NSUInteger)retainCount {
? ? ? ? ? return (NSUInteger)__CFDoExternRefOperation(OPERATION_retainCount, self);
}
- (id)retain {
? ? ? ? ?return (id)__CFDoExternRefOperation(OPERATION_retain, self);
}
- (void)release {
? ? ? ? return __CFDoExternRefOperation(OPERATION_release, self);
}
蘋果的實現是采用散列表(引用計數表)來管理引用計數。
GNUstep 將引用計數保存在對象占用內存塊頭部的變量中,而蘋果的實現,則是保存在引用計數表的記錄中。
通過內存塊頭部管理引用計數的好處: ① 少量代碼即可完成 ② 能夠統一管理引用計數用內存塊與對象用內存塊
通過引用計數表管理引用計數的好處如下: ① 對象用內存塊的分配無需考慮內存塊頭部 ② 引用計數表各記錄中存有內存塊地址,可從各個記錄追溯到個對象的內存 塊
autorelease
autorelease會像 C 語言的自動變量那樣來對待對象事例,當超出其作用于時,對象事例的release實例方法被調用。
autorelease的具體使用方法如下:
(1)生成并持有NSAutoreleasePool對象
(2)調用已分配對象的autorelease實例方法
(3)廢棄NSAutoreleasePool對象
NSAutoreleasePool對象的生命周期相當于 C 語言變量的作用域。對于所有調用過autorelease實例方法的對象,在廢棄NSAutoreleasePool對象時,都將調用release事例方法。
在Cocoa框架中,相當于程序主循環的NSRunLoop或者在其他程序可以運行的地方,對NSAutoreleasePool對象進行生成,持有和廢棄處理。
盡管如此,但在大量產生autorelease的對象時,只要不廢棄NSAutoreleasePook對象,那么生成的對象就不能被釋放,因此有時會產生內存不足的現象。
autorelease 實現
[obj autorelease];
- (id)autorelease {
? ? ? ? [NSAutoreleasePool addObject:self];
}
autorelease實例方法的本質就是調用NSAutoreleasePool對象的addObject類方法
+ (void)addObject:(id)anObj {
? ? ? ? NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 對象;
? ? ? ?if (pool != nil) {
? ? ? ? ? ? [pool addObject:anObj];
? ? ? ?} else {
? ? ? ? ? NSLog(@"NSAutoreleasePool 對象非存在狀態下調用 autorelease");
? ? ? }
}
addObject類方法調用正在使用的NSAutoreleasePool對象的addObject實例方法。
- (void)addObject:(id)anObj {
? ? ? ? ?[array addObject:anObj];
}
實際的是將對象添加到連接列表中。 如果調用NSObject類的autorelease實例方法,該對象被追加到正在使用的NSAutoreleasePool對象中的數組里。
[pool drain];
-----------------------
- (void)drain
{
? ? ? [self dealloc];
}
- (void)dealloc
{
? ? ?[self emptyPool];
? ? ? [array release];
}
- (void)emptyPool
{
? ? for (id obj in array) {
? ? ? ?[obj release];
? ? ?}
}
專欄 提高調用 Objective-C 方法的速度
IMP Caching:在進行方法調用時,為了解決類名/方法名以及取得方法運行時的函數指針,要在框架初始化時對其結果進行緩存。
蘋果的實現
蘋果中autorelease的實現
class AutoreleasePoolPage
{
? ? ? ?static inline void *push ()
? ? ? {
? ? ? ? ? ? 相當于生成或持有 NSAutoreleasePool 類對象
? ? ? ?}
? ? ? ? static inline void *pop (void *token)
? ? ? ?{
? ? ? ? ? ? ?相當于廢棄 NSAutoreleasePool 類對象;
? ? ? ? ? ? ? releaseAll();
? ? ? ? ? }
? ? ? ? ?static inline id autorelease(id obj)
? ? ? ? {
? ? ? ? ? ? ? ? 相當于 NSAutoreleasePool 類的 addObject 類方法?
? ? ? ? ? ? ? ? AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的
? ? ? ? ? ? ? ? ?AutoreleasePoolPage 實例;
? ? ? ? ? ? ? ? ?autoreleasePoolPage -> add(obj);
? ? ? ? ? ? }
? ? ? ? ?id *add(id obj)
? ? ? ? ?{
? ? ? ? ? ? ? ?將對象追加到內部數組中;
? ? ? ? ? }
? ? ? ? void releaseAll()
? ? ? ?{
? ? ? ? ? ? ?調用內部數組中對象的 release 實例方法
? ? ? ? }
? ? ?void *objc_autoreleasePoolPush(void)
? ? ?{
? ? ? ? ? ? ?return AutoreleasePoolPage::push();
? ? ? ?}
? ? ?void objc_autoreleasePoolPop(void *ctxt) ?
? ? ?{
? ? ? ? ? ? AutoreleasePoolPage::pop(ctxt);
? ? ? }
? ? ? ?id *objc_autorelease(id obj)
? ? ?{?
? ? ? ? ? ?return AutoreleasePoolPage::autorelease(obj);
? ? ? ? }
}
ARC 規則
所有權修飾符
ARC 有效時,所有權修飾符共有 4 種
__strong修飾符
__weak修飾符
__unsafe_unretained修飾符
_autoreleasing修飾符
__strong 修飾符
__strong修飾符是id類型和對象類型默認的所有權修飾符
ARC 有效時
id oj = [[NSObject alloc] init];
相當于
id __strong obj = [[NSObject alloc] init];
id和對象類型在沒有明確指定所有權修飾符時,默認為__strong修飾符。
附有__strong修飾符的變量在超出其變量作用域時,即在該變量被廢棄時,會釋放其被賦予的對象。
__strong修飾符表示對對象的“強引用”,持有強引用的變量在超出其作用域時被廢棄,隨著強引用的失效,引用的對象會隨之釋放。
ARC有效時
{
? ? ? ?/*
? ? ? ?* 自己生成并持有對象
? ? ? ? */
? ? ? ?id __strong obj = [[NSObject alloc] init];
? ? ? /*
? ? ? ?* 因為變量 obj 為強引用
? ? ? ?* 所以自己持有對象
? ? ? ?*/
} /*
? ?* 因為變量 obj 超出其作用域,強引用失效,
? ?* 所以自動釋放自己持有的對象。
? ?* ?對象的所有者不存在,因此廢棄該對象
? ?*/
此源碼明確指定了變量的作用域。ARC無效時,該源代碼可記述:
ARC 無效
{
? ? ? ?id obj = [[NSObject alloc] init];
? ? ? ? [obj release];
}
取得非自己生成并持有的對象
{
? ? ?/*
? ? ?* 取得非自己生成并持有的對象
? ? ? */
? ? ? ?id __strong obj = [NSMutableArray array];
? ? ?/*
? ? ? ?* 因為變量 obj 為強引用
? ? ? * 所以自己持有對象
? ? ? ?*/
} /*
* 因為變量 obj 為強引用,
* 所以自己持有對象
*/
__strong,__weak,__autoreleasing修飾的,可以保證將富有這些修飾符的自動變量初始化為 nil。
通過__strong修飾符,不必再次鍵入 retain 或者 release,完美滿足“引用計數式內存管理的思考方式”
“自己生成的對象,自己持有"和”非自己生成的對象,自己也能持有“,只需要通過對待__strong修飾符的變量賦值便可達成。
通過廢棄帶__strong修飾符的變量(變量作用域結束或是成員變量所屬對象廢棄)或者對變量賦值,都可以做到”不在需要自己持有的對象時釋放“
__weak 修飾符
用來解決循環引用。
循環引用容易發生內存泄漏,所謂內存泄漏就是應當廢棄的對象在超出其生存周期后繼續存在。
__weak修飾符的變量不持有對象。若該對象被廢棄,則此弱引用將自動失效且處于 nil 被賦值的狀態。
__unsafe_unretained 修飾符
這如其名,是不安全的所有權修飾符。附有__unsafe_unretained修飾符的變量不屬于編譯器的內存管理對象。
__unsafe_unretained 修飾的變量通附有 __weak 修飾符的變量一樣,因為自己生成并持有的對象不能繼續自己所有,所以生成的對象會立即被釋放。
weak會自動 nil,unsafe_unretained 不會自動 nil。
__autoreleasing 修飾符
ARC 有效時不能使用NSAutoreleasePool,也不能使用autorelease。
ARC有效時使用
@autoreleasepool { // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id __autoreleasing obj = [[NSObject alloc] init]; // [obj autorelease]
}// [pool drain]
非顯示使用__autoreleasing修飾符,編譯器會檢查方法名是否已alloc/new/copy/mutableCopy開始,如果不是則自動將返回值得對象注冊到autoreleasepool。
__weak修飾符為了避免循環引用而使用的,但在訪問富有__weak修飾符的變量時,實際上必定要訪問注冊到autoreleasepool的對象。
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
相當于:
id __weak obj1 = obj0; // 使用__weak 相當于將對象放入自動釋放池
id __autoreleasing tmp = obj1;
NSLog(@"class=%@",[tmp class]);
id的指針或對象的指針在沒有顯示指定所有權修飾符時,會被默認附加上__autoreleasing修飾符。
- (BOOL) performOperationWithError:(NSError **)error;
默認是 (NSError *__autoreleasing *)
NSError *error = nil;
NSError **pError = &error; // 報錯,原因:默認對象指針類型是 __autoreleasing ,而對象默認是 __strong 類型,兩種所有權類型不匹配,不能直接復制。
對象指針型賦值時,其所有權修飾符必須一致。
NSError __strong *error = nil;
BOOL result = [obj performOperationWithError:&error]; //需要傳一個 __autoreleasing 修飾的變量。但此時 __strong 為什么也可以呢?
編譯器自動轉化了:
NSError __strong *error = nil;
NSError __autroreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;
在顯示指定__autoreleasing修飾符時,必須注意對象變量要為自動變量(包括局部變量,函數或方法參數)。
規則
不能使用retain/release/releaseCount/autorelease
不能使用NSAllocateObject/NSDeallocateObject
須遵守內存管理的方法命名規則
不要顯示調用dealloc
使用@autoreleasepool塊替代NSAutoreleasePool
不能使用區域NSZone
對象型變量不能作為 C 語言結構體的成員(C語言的規約上沒有方法來管理結構體成員的生存周期)
顯示轉化”id“和"void *"
ARC 有效時,通過__bridge來轉換,id和void *就能夠相互轉換了。
但是轉換為void *的轉換,其安全性與賦值給_unsafeunretained修飾符相近。如果管理時不注意賦值對象的所有者,就會因懸垂指針而導致程序崩潰。
__bridge轉換中海油另外兩種轉換,分別是"_bridgeretained"和bridge_transfer轉換。
ARC有效:
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
__bridge_retained 轉換可使要轉換賦值的變量也持有所賦值的對象。
ARC無效:
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
_bridgetransfer轉換提供與bridgeretained相反的動作,被轉換的變量所持有的對象在該變量被賦值給轉換目標變量后隨之釋放。
ARC有效:
id obj = (__bridge_transfer id)p;
ARC 無效
id obj = (id)p;
[obj retain];
[(id)p release];
_bridgeretained轉換與retain類似。_bridgetransfer轉換與release相似。 在給 id obj 賦值時retain相當于__strong修飾符的變量。
屬性
數組
strong/weak/__autoreleasing 修飾符保證其指定的變量初始化為nil。
{
? ? ? ?id objs[2];
? ? ? ?objs[0] = [[NSObject alloc] init];
? ? ? ?objs[1] = [NSMutableArray array];
}
數組超出其變量作用域時,數組中各個附有__strong 修飾符的變量也隨之失效,其強引用消失,所賦值的對象也隨之釋放。
靜態數組中,編譯器能夠根據變量的作用域自動插入釋放賦值對象的代碼,而在動態數組中,編譯器不能確定數組的生存周期,所以無從處理。
ARC的實現
__strong 修飾符
{
? ? ? id __strong obj = [[NSObject alloc] init];
}
/*編譯器的模擬代碼*/
id obj = objc_msgSend(NSObject,@selector(alloc));
obj_msgSend(obj,@selector(init));
objc_release(obj);
// 變量作用域結束時通過 objc_release 釋放對象。編譯器自動插入 release
{
? ? ? ?id __strong obj = [NSMutableArray array];
}
/*編譯器的模擬代碼*/
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue用于自己持有對象的函數,但它持有的對象應為返回注冊在 autoreleasepool 中對象的方法,或是函數的返回值。
objc_retainAutoreleasedReturnValue函數是成對出現的,與之相對的函數是objc_autoreleaseReturnValue。用于 alloc/new/copy/mutableCopy 方法以外的方法。
+ (id)array {
? ? ? ? ?return [[NSMutableArray alloc] init];
}
/*編譯器的模擬代碼*/
+ (id)array {
? ? ? ? id obj = objc_msgSend(NSMutableArray, @selector(alloc));
? ? ? ?objc_msgSend(obj, @selector(init));
? ? ? ? return obj_autoreleaseReturnValue(obj);
}
obj_autoreleaseReturnValue返回注冊到 autoreleasepool 中對象的方法使用了obj_autoreleaseReturnValue函數返回注冊到 autoreleasepool 中的對象。
__weak 修飾符
附有 __weak 修飾符的變量所應用的對象被廢棄,則將 nil 賦值給該變量。
{
? ? id __weak obj1 = obj;
}
/*編譯器的模擬代碼*/
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
objc_initWeak函數如下:
obj1 = 0;
objc_storeWeak(&obj1//變量地址, obj//對象地址); //把第二參數的賦值對象的地址作為鍵值,將第一個參數的附有 __weak 修飾的變量的地址注冊到 weak 表中。
objc_destroyWeak函數如下:
objc_storeWeak(&obj1, 0); // 如果第二個參數為 0,則把變量的地址從 weak 表中刪除
weak 表與引用計數表相同,作為散列表被實現。如果使用 weak 表,將廢棄對象的地址作為鍵值進行檢索,就能高速地獲取對應的附有 __weak 修飾符的變量的地址。
釋放對象,程序的動作
(1)objc_release (調用 release 方法,減少引用計數至 0 ,對象正被銷毀,聲明周期即將結束。)
(2)因為引用計數為 0 所以執行 dealloc (調用 dealloc,繼承關系中每一層都調用 dealloc)
(3)objcrootDealloc (調用最底層 dealloc, NSObject調用執行 object_dispose 方法)
(4)objc_dispose (解除 Associate 方法關聯的對象)
(5)objc_destructInstance (為 C++ 實例變量調用)
(6)objccleardeallocating
最后調用 objccleardeallocating 的函數動作如下:
① 從 weak 表中獲取廢棄對象的地址為鍵值的記錄。 ② 將包含在記錄中的所有附有 __weak 修飾符變量的地址,賦值為 nil。 ③ 從 weak 表中刪除該記錄。 ④ 從引用計數表中刪除廢棄對象的地址為鍵值的記錄。
附有 __weak 修飾符的變量,即是使用注冊到 autoreleasepool 中的對象
{
? ? ?id __weak obj1 = obj;
? ? ?NSLog(@"%@", obj1);
}
/*編譯器的模擬代碼*/
id obj1;
objc_initWeak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destoryWeak(&obj1);
(1)objc_loadWeakRetained 函數取出附有 __weak 修飾符變量所引用的對象并 retain。 (2)objc_autorelease 函數將對象注冊到 autoreleasepool 中。
由此可知,因為附有 __weak 修飾符變量所引用的對象被注冊到 autoreleasepool 中,大量使用附有 __weak 修飾符的變量,autoreleasepool 的對象也會大量地增加。
__autoreleasing 修飾符
在 ARC 無效時調用對象 autorelease 方法。
@autoreleasepool {
? ? ?id __autoreleaseing obj = [[NSObject alloc] init]; ?
}
/*編譯器的模擬代碼*/
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);