
1.1 什么是自動引用計數(shù)
- 概念:在 LLVM 編譯器中設(shè)置 ARC(Automaitc Reference Counting) 為有效狀態(tài),就無需再次鍵入
retain或release代碼。
1.2 內(nèi)存管理 / 引用計數(shù)
1.2.1 概要
-
引用計數(shù)就像辦公室的燈的照明
對照明設(shè)備所做的動作 對OC對象所做的動作 開燈 生成對象 需要照明 持有對象 不需要照明 釋放對象 關(guān)燈 廢棄對象
- 其中,A生成對象時,引用計數(shù)為 1, 當(dāng)多一個人需要照明,如B需要照明,則引用計數(shù) +1, 以此類推。當(dāng)A不需要對象,A釋放對象,引用計數(shù) -1.當(dāng)最后一個持有對象的人都不要這個對象了,則引用計數(shù)變?yōu)?0,丟棄對象。
1.2.2 內(nèi)存管理的思考方式
-
客觀正確的思考方式:
- 自己生成的對象,自己所持有
- 非自己生成的對象,自己也能持有
- 不再需要自己持有的對象時釋放該對象
- 非自己持有的對象無法釋放
對象操作 OC方法 生成并持有對象 alloc/new/copy/mutableCopy等 持有對象 retain 釋放對象 release 廢棄對象 dealloc
-
自己生成的對象,自己所持有:持有對象
- (id) allocObject { // 自己生成并持有對象 id obj = [[NSObject alloc] init]; return obj; }
-
需要注意的是: NSMutableArray 類的 array 方法取得的對象不是自己所持有的。其內(nèi)部實現(xiàn)原理為:
- (id)object { // 自己生成并持有對象 id obj = [[NSObject alloc] init]; // 將對象注冊到 autoreleasepool 中, pool結(jié)束時會自動調(diào)用 release,這樣的方法自己就不會持有對象。 [obj autorelease]; // 返回這個自己不持有的對象。 return obj; }
-
非自己生成的對象,自己也能持有:雖然一開始是不持有的,但是可以使用 retain 使其變成被自己所持有的,然后也可以使用 release 方法釋放對象。
// 取得非自己生成的對象 id obj = [NSMutableArray array]; // 取得的對象存在了,但是并非自己所持有的,引用計數(shù)還為 0, 但是該對象被放到了autoreleasepool 中,可以自動釋放 [obj retain]; // 此時,自己就持有了這個對象,引用計數(shù)為 1 [obj release]; // 此時釋放了這個對象,引用計數(shù)變?yōu)?0 ,對象就不可以再被訪問了,但是對象也沒有被立即廢棄
-
無法釋放非自己持有的對象:例如
// 取得非自己持有的對象 id obj = [NSMutableArray array]; [obj release]; // 會導(dǎo)致程序崩潰

1.2.3 alloc/retain/release/dealloc 實現(xiàn)
-
分析 GNU 源碼來理解 NSObject 類中的方法。
-
首先是 alloc
id obj = [[NSObject alloc] init];+ (id)alloc { // alloc 在內(nèi)部調(diào)用 allocWithZone return [self allocWithZone:NSDefaultMallocZone()]; } + (id)allocWithZone:(NSZone *)zone { // allocWithZone 在內(nèi)部調(diào)用 NSAllocateObject return NSAllocateObject(self, 0, z); } struct obj_layout { NSUInteger retained; }; inline id NSAllocateObject (Class aClass, NSUInteger extreBytes, NSZone *zone) { int size = 計算容納對象所需內(nèi)存的大小; // 分配內(nèi)存空間 id new = NSZoneMalloc(zone, size); // 將該內(nèi)存空間中的值初始化為 0 memset(new, 0, size); // 返回作為對象而使用的指針 new = (id)&((struct obj_layout *) new)[1]; } /** 其中, NSZoneMalloc, NSDefaultMallocZone() 等名稱中包含的 Zone 是為了防止內(nèi)存碎片化而引入的結(jié)構(gòu)。對內(nèi)存分配的區(qū)域本身進(jìn)行多重化管理,根據(jù)對象使用的目的,大小,分配內(nèi)存,從而提高內(nèi)存管理的效率。 但是現(xiàn)在的運行時系統(tǒng)知識簡單的忽略了區(qū)域的概念,運行時系統(tǒng)中的內(nèi)存管理本身已經(jīng)機具效率,再使用區(qū)域來管理內(nèi)存反而會引起內(nèi)存使用效率低下的問題。 */ -
去掉NSZone后簡化的代碼
struct obj_layout { NSUInteger retained; }; + (id)alloc { int size = sizeof(struct obj_layout) + 對象大小; // 這句的意思是,為 struct obj_layout 這個結(jié)構(gòu)體分配一個 size 大小的內(nèi)存空間,并且函數(shù)calloc()會將所分配的內(nèi)存空間中的每一位都初始化為零,也就是這塊內(nèi)存中所有的值都為 0 struct obj_layout *p = (struct obj_layout *)calloc(1, size); // 返回該對象指針 return (id)(p + 1); } -
[obj retain];的實現(xiàn)- (id)retain { NSIncrementExtraRefCount(self); } inline void NSIncrementExtraRefCount(id anObject) { // 首先 (struct obj_layout *) anObject 找到的是這個對象的尾部, 所以需要 [-1] 減去該對象的大小,來尋址到該對象的頭部,然后再判斷該結(jié)構(gòu)體中 retained 這個變量的值是否已經(jīng)大于了系統(tǒng)最大值,如果沒有,就 retained++, 使得引用計數(shù) +1. 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++; } } -
[obj release]的實現(xiàn)- (void)release { if (NSDecrementExtraRefCountWasZero(self)) { [self delloc]; } } BOOL NSDecrementExtraRefCountWasZero(id anObject) { if (((struct obj_layout *) anObject)[-1].retained == 0) { return YES; } else { ((struct obj_layout *) anObject)[-1].retained--; return NO; } } -
[obj dealloc];的實現(xiàn)- (void)dealloc { NSDeallocateObject(self); } inLine void NSDeallocateObject (id anObject) { // 指針 o 指向 anObject 的內(nèi)存地址,然后釋放這個指針指向的內(nèi)存 struct obj_layout *o = &((struct obj_layout *) anObject) [-1]; free(o); }
-
1.2.4 蘋果的實現(xiàn)
-
首先看 alloc 的實現(xiàn):
// 依次調(diào)用這四個方法 + alloc + allocWithZone: class_Instance calloc
-
retainCount / retain / release 的實現(xiàn)
- retainCount __CFDoExtrernRefOperation CFBaseicHashGetCountOfKey - retain __CFDoExternRefOperation CFBasicHashAddValue; - release __CFDoExternRefOperation CFBasicHashRemoveValue // 這些函數(shù)的前綴 CF 表示他們都包含于 Core Foundation 框架的源代碼中所以其內(nèi)部實現(xiàn)可能如下:
int __CFDoExternRefOperation(uintptr_r 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 = CFBasicHashRmoveValue(table, obj); // 如果count == 0, 返回 YES, 則會調(diào)用 dealloc return 0 == count; } } // 舉例說明 retainCount - (NSUInteger)retainCount { return (NSUInteger)__CFDExternRefOperation(OPERATION_retainCount, self); }
- 由此可看出,蘋果在計數(shù)內(nèi)部大概是以散列表的方式來管理引用計數(shù)的。復(fù)習(xí)散列表
- 比較
-
通過內(nèi)存塊頭部管理引用計數(shù)的好處:
少量代碼即可完成
能夠統(tǒng)一管理引用計數(shù)需要的內(nèi)存塊和對象所用的內(nèi)存塊
-
通過引用計數(shù)表管理引用計數(shù)的好處
- 對象所用的內(nèi)存塊的分配不需要考慮它的頭部(跟內(nèi)存塊頭部管理引用計數(shù)相比,就是少了一個用來計數(shù)的頭部)
- 引用計數(shù)表各記錄中存有內(nèi)存塊的地址,可以從各個記錄追溯到各個對象的內(nèi)存塊。這使得計時出現(xiàn)故障導(dǎo)致了對象所占用的內(nèi)存塊損壞了,在 內(nèi)存塊頭部管理引用計數(shù) 時,我們這樣就沒有辦法訪問這塊內(nèi)存了,但是在 引用計數(shù)表管理引用計數(shù) 時,我們就可以通過這個計數(shù)表來尋址內(nèi)存塊的位置。
- 另外,在利用工具檢測內(nèi)存泄漏時,引用計數(shù)表也可以用來檢測各個對象是否有持有者
-
1.2.5 autorelease
autorelease 會像 C語言 的自動變量一樣來對待對象實例。當(dāng)其超出作用域時,就會對對象進(jìn)行release 的調(diào)用。
-
autorelease 的具體使用方法:
生成并持有 NSAutoreleasePool 對象
調(diào)用已經(jīng)分配對象的 autorelease 實例方法
-
廢棄 NSAutoreleasePool 對象(對對象自動調(diào)用 release)
// 代碼如下 NSAutoreleasePool pool = [[NSAutoreleasePool alloc] init]; id obj = [[NSObject alloc] init]; [obj autorelease]; [pool drain]; => 等價于 [obj release];
我們在編程中,并不需要顯式的調(diào)用 pool 對象,因為在 RunLoop 中,這一切都為我們處理好了。在一個 RunLoop 循環(huán)中,會進(jìn)行 NSAutoreleasePool 對象的生成,應(yīng)用程序的主線程進(jìn)行處理,廢棄 NSAutoreleasePool 對象。
-
盡管是這樣,我們有的時候也需要顯式的調(diào)用 NSAutoreleasePool 對象,因為有時會產(chǎn)生大量的 autorelease 對象,只要不廢棄 NSAutoreleasePool 對象,那么這些生成的對象就不能被釋放,會導(dǎo)致內(nèi)存瘋長的現(xiàn)象。最典型的例子就是在讀取大量圖像的同時改變它的尺寸。
- 圖像文件讀到 NSData 對象,并且從中生成 UIImage 對象,改變這個對象的尺寸后,就會生成新的 UIIamge 對象。這種情況下就會產(chǎn)生大量的 autorelease 對象。這時就有必要在合適的地方生成,持有或廢棄 NSAutoreleasePool 對象。
-
另外,在 Cocoa 框架中也有很多類方法用于返回 autorelease 對象。比如
id array = [NSMutableArray arrayWithCapasity:1]; // 等價于 id array = [[[NSMuatbleArray alloc] initWithCapasity:1] autorelease];
1.2.6 autorelease 的實現(xiàn)
首先來看 GNU 的源代碼
-
首先看一下 autorelease 方法的實現(xiàn)
[obj autorelease]; // 表面上的實現(xiàn)方法 - (id)autorelease { [NSAutoreleasePool addObject:self]; } /** 實際上, autorelease 內(nèi)部是用 Runtime 的 IMP Caching 方法實現(xiàn)的。在進(jìn)行方法調(diào)用時,為了解決類名/方法名幾區(qū)的方法運行是的函數(shù)指針,要在框架初始化時對他們進(jìn)行緩存 */ id autorelease_class = [NSAutoreleasePool class]; SEL autorelease_sel = @selector(addObject:); IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel]; // 實際的方法調(diào)用時使用緩存的結(jié)果值 - (id)autorelease { (*autorelease_imp)(autorelease_class, autorelease_sel, self); }
-
再看 NSAutoreleasePool 的 addObject 類方法實現(xiàn)
+ (void)addObject:(id)obj { NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 對象; if (pool) { [pool addObject:anObj]; } else { NSLog("不存在正在使用的 NSAutoreleasePool 對象"); } }- 注意:當(dāng)多個 NSAutoreleasePool 對象嵌套使用時,理所當(dāng)然會調(diào)用最里層的 NSAutoreleasePool 對象
-
addObject 實例方法實現(xiàn)
// 當(dāng)調(diào)用 NSObject類的 autorelease 實例方法時,這個對象就會被加到 NSAutoreleasePool 對象數(shù)組中 - (void)addObject:(id)obj { [array addObject:obj]; } -
drain 實例方法廢棄正在使用的 NSAutoreleasePool 對象的過程
// 執(zhí)行順序: drain() -> dealloc() -> emptyPool() -> [obj release] -> [emptyPool release] - (void)drain { [self dealloc]; } - (void)dealloc { [self emptyPool]; [array release]; } - (void)emptyPool { for (id obj in array) { [obj release]; } }
1.2.7 蘋果的實現(xiàn)
-
C++的實現(xiàn)
class AutoreleasePoolPage { static inline void *push() { // 生成或持有 NSAutoreleasePool 對象 } static inline id autorelease(id obj) { // 對應(yīng) NSAutoreleasePool 類的 addObject 類方法 AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的 AutoreleasePoolPage 實例; autoreleasePoolPage -> add(obj); } static inline void *pop(void *token) { // 廢棄 NSAutoreleasePool 對象 releaseAll(); } id *add(id obj) { // 添加對象到 AutoreleasePoolPage 的內(nèi)部數(shù)組中 } void releaseAll() { // 調(diào)用內(nèi)部數(shù)組對象的 release 類方法 } }; // 具體調(diào)用 void *objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } void *objc_autoreleasePoolPop(void *ctxt) { return AutoreleasePoolPage::push(ctxt); } id *objc_autorelease(void) { return AutoreleasePoolPage::autorelease(obj); }
-
觀察 NSAutoreleasePool 類方法和 autorelease 方法的運行過程
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // == objc_autoreleasePoolPush() id obj = [[NSObject alloc] init]; [obj autorelease]; // == objc_autorelease(obj) [pool drain]; // == objc_autoreleasePoolPop(pool);
另外:
[[NSAutoreleasePool showPools]];可以用來確認(rèn)已經(jīng)被 autorelease 的對象的狀況。-
問題: 如果
autorelease NSAutoreleasePool對象會如何?- 答: 會崩潰。因為通常在使用 Foundation 框架時,無論調(diào)用哪個對象的 autorelease 方法,本質(zhì)都是調(diào)用 NSObject 類的 autorelease 方法。 但是 autorelease 方法已經(jīng)被 NSAutoreleasePool 類所重載。所以運行時會出現(xiàn)錯誤。
1.3 ARC 規(guī)則
1.3.1概要
- 實際上 引用計數(shù)式內(nèi)存管理 的本質(zhì)部分在 ARC 中并沒有改變,就像 自動引用計數(shù) 這個名稱一樣,ARC 所做的,只是自動的幫助我們處理了 引用計數(shù) 相關(guān)部分。
1.3.2內(nèi)存管理的思考方式
- 引用計數(shù)式內(nèi)存的思考方式就是思考 ARC 所引起的變化
- 自己生成的對象,自己所持有
- 非自己生成的對象,自己也能持有
- 不再需要自己持有的對象時釋放該對象
- 非自己持有的對象無法釋放
- 本質(zhì)上和內(nèi)存管理的思考方式一樣,只是實現(xiàn)方式上有些許不同
1.3.3所有權(quán)修飾符
-
ARC 有效時,id 類型和對象類型與 C語言 中的其他類型不同,其類型必須附加 所有權(quán)修飾符 。共有以下四種
- __strong 修飾符
- __weak 修飾符
- __unsafe_unretained 修飾符
- __autoreleasing 修飾符
-
__strong 修飾符
-
__strong 修飾符是 id 類型和對象類型默認(rèn)的所有權(quán)修飾符
// 在 ARC 有效的環(huán)境下 id obj = [[NSObject alloc] init] <==> id __strong obj = [[NSObject alloc] init]; // 在 ARC 無效的環(huán)境下 { id obj = [[NSObject alloc] init] [obj release]; } -
當(dāng)被__strong 修飾符修飾的,自己生成的,對象在超過其作用域時:
{ // 自己生成并持有對象 id __strong obj = [[NSObject alloc] init]; /** 因為變量 obj 為強引用,所以自己持有對象 */ } // 因為變量超出作用域,強引用失效,所以釋放對象,因為對象此時沒有其他的所有者了,對象被廢棄。 // 正好遵循內(nèi)存管理的原則 -
當(dāng)對象的所有者和對象的生命周期是明確的,取得非自己生成并持有的對象時:
{ // 首先取得非自己生成的對象,但是由于__strong修飾符修飾著這個對象,所以自己持有這個對象 id __strong obj = [NSMutableArray array]; } /** 當(dāng)超出對象的作用域時,強引用失效,所以釋放對象。 但是由于 [NSMutableArray array] 所生成的對象并非自己所持有的,而是自動的加到 autoreleasePool 中,所以會在一個 RunLoop 周期結(jié)束后,自動廢棄對象。 */ -
有 __strong修飾符的變量之間可以相互賦值
// 首先 obj0 強引用指向 對象A , obj1 強引用指向 對象B,表示 obj1 持有 B, obj2 不持有任何對象 id __strong obj0 = [[NSObject alloc] init]; // 對象A id __strong obj1 = [[NSObject alloc] init]; // 對象B id __strong obj2 = nil; // 此時 obj0 與 obj1 強引用同一個對象 B, 沒有人持有 對象A 了,所以 對象A 被廢棄。 obj0 = obj1; // 此時 obj2 指向 obj0 所持有的對象, 所以 對象B 現(xiàn)在被三個引用所持有。 obj2 = obj0; // 現(xiàn)在 obj1 對 對象B 的強引用失效,所以現(xiàn)在持有 對象B 的強引用變量為 obj0,obj2 obj1 = nil; // 同理,現(xiàn)在只有 obj2 持有對象B obj0 = nil; // 沒有引用指向 對象B 了,廢棄 對象B obj2 = nil; -
__strong修飾符 也可以修飾 OC類成員變量,也可以在方法的參數(shù)上,使用附有 _strong 修飾符的變量
@interface Test : NSObject { id __strong obj_; } - (void)setObject:(id __strong)obj; @end @implementation Test - (instancetype)init { self = [super init]; return self; } - (void)setObject:(id)obj { obj_ = obj; } @end // 調(diào)用函數(shù) { // 首先test 持有 Test 對象的強引用 id __strong test = [[Test alloc] init]; // Test對象 的 obj_ 成員,持有 NSObject 對象的強引用 [test setObject:[[NSObject alloc] init]]; } /** 此時test強引用超出了其作用域,它失效了。 所以此時沒有強引用指向 Test對象 了, Test對象會被廢棄 廢棄 Test 對象的同時, Test對象 的 obj_ 成員也被廢棄。 所以它釋放了指向 NSObject 的強引用 因為 NSObject 沒有其他所有者了,所以 NSObject 對象也被廢棄。 */ _strong修飾符 與 _weak, _autoreleasing 修飾符一樣,初始化時,即使不明確指出,他們也都會自動將該引用指向nil。通過 _strong修飾符,完美的滿足了 引用計數(shù)的思考方式
id類型和對象類型的所有權(quán)修飾符默認(rèn)都為 __strong 所以不需要再顯式的指明修飾對象的修飾符為 _strong
-
-
__weak 修飾符
-
_weak修飾符 的出現(xiàn)就是為了解決 _strong修飾符在內(nèi)存管理中所帶來的循環(huán)引用問題。如上例:
@interface Test : NSObject { id __strong obj_; } - (void)setObject:(id __strong)obj; @end @implementation Test - (instancetype)init { self = [super init]; return self; } - (void)setObject:(id)obj { obj_ = obj; } @end // 調(diào)用函數(shù),打印變量結(jié)果如下: { // 首先test1 持有 Test 對象的強引用, test2 持有 Test 對象的強引用 id __strong test1 = [[Test alloc] init]; // 對象A id __strong test2 = [[Test alloc] init]; // 對象B /** TestA 對象中的 obj_ 成員變量持有著 test2指向的 對象B, 同時,test2指向的對象B中的 obj_又強引用著 對象A, 所以造成了循環(huán)引用。 */ [test1 setObject:test2]; [test2 setObject:test1]; } /** 當(dāng)跳出作用域后,test1釋放它對 對象A 的強引用 test2釋放它對 對象B 的強引用 但是此時 對象A中的 obj_A 對 對象B 的強引用本應(yīng)該被釋放,但是由于在 對象B 中強引用了對象A,所以 obj_A 不會被釋放,會一直強引用 對象B, 而同理,對象B 中的 obj_B 也不會被釋放,所以它將一直強引用著 對象A, 所以此時外部沒有誰引用著 對象A 和 對象B, 但是他們自己在互相引用著,這樣就造成了內(nèi)存泄漏!(所謂內(nèi)存泄漏,指的就是應(yīng)該被廢棄的對象,卻在超出其生存周期變量作用域時還繼續(xù)存在著) */ /** 打印變量結(jié)果如下: 其中 test1 對象中強引用 test2 對象, test2對象 又強引用 test1 對象,造成無盡的循環(huán)。 */
-


-
而下面這種情況:
{ id test = [[Test alloc] init]; [test setObject:test]; } /** 當(dāng)強引用test 的作用域結(jié)束后,它釋放了對 Test 對象的引用。 但是 Test對象 內(nèi)部還保留著 對 Test對象 的強引用,所以 Test對象 被引用著,所以不會被回收 */ // 也會發(fā)生內(nèi)存泄漏!

-
所以此時,就非常需要一個 __weak修飾符 來避免循環(huán)引用
// 弱引用與強引用正好相反,不能夠持有對象實例。 // 這樣寫會發(fā)出警告:Assigning retained object to weak variable; object will be released after assignment // 表示因為沒有人持有著 NSObject 對象,所以該對象一旦被創(chuàng)建就會立即被銷毀 id __weak obj = [[NSObject alloc] init]; // 正確的使用弱引用的方式 { // 自己生成并持有 NSObject 對象 id obj = [[NSObject alloc] init]; // 因為 NSObject 對象已經(jīng)被 obj 強引用著, 所以此時 obj1 對它使用弱引用也沒有關(guān)系, // 不會使它的引用計數(shù) +1 id __weak obj1 = obj; } /** 當(dāng)超出變量的作用域時, obj 對 NSObject對象 的強引用消失, 此時沒有人持有 NSObject對象 了。 NSObject對象 被廢棄 */ -
對上述循環(huán)引用的例子進(jìn)行修改如下:
@interface Test : NSObject { id __weak obj_; } - (void)setObject:(id __strong)obj; @end @implementation Test - (instancetype)init { self = [super init]; return self; } - (void)setObject:(id)obj { obj_ = obj; } @end // 調(diào)用函數(shù),打印變量結(jié)果如下: { // 首先test1 持有 Test 對象的強引用, test2 持有 Test 對象的強引用 id __strong test1 = [[Test alloc] init]; // 對象A id __strong test2 = [[Test alloc] init]; // 對象B /** TestA 對象中的 obj_ 成員變量弱引用著 test2指向的 對象B, 同時,test2指向的 對象B 中的 obj_又弱引用著 對象A。 */ [test1 setObject:test2]; [test2 setObject:test1]; } /** 當(dāng)跳出作用域后,test1釋放它對 對象A 的強引用 test2釋放它對 對象B 的強引用 此時,由于 對象中的 obj_變量只擁有對對象的弱引用,所以 沒有誰持有著 對象A,和對象B,他們被釋放,沒有造成循環(huán)引用! */ -
__weak修飾符 的另一優(yōu)點:當(dāng)持有某個對象的弱引用時,如果該對象被廢棄,則弱引用將自動失效,并且會被置為 nil的狀態(tài)(空弱引用)
id __weak obj1 = nil; { // 自己生成并持有對象 id __strong obj0 = [[NSObject alloc] init]; // obj1 現(xiàn)在也指向 NSObject對象 obj1 = obj0; // 此時打印 obj1 有值 NSLog(@"A = %@", obj1); } /** 當(dāng)變量 obj0 超出作用域,它不再持有 NSObject對象, 由于 obj1 是弱引用,所以它也不持有 NSObject對象 由于沒人持有 NSObject對象, NSObject對象被廢棄 被廢棄的同時, obj1 變量的弱引用失效, obj1 被重新賦值為 nil */ NSLog(@"B = %@", obj1); /** 結(jié)果打印如下: 2017-12-14 15:16:39.859875+0800 littleTest[10071:1377629] A = <NSObject: 0x10054da70> 2017-12-14 15:16:39.860432+0800 littleTest[10071:1377629] B = (null) */

-
__unsafe_unretained 修飾符
-
_unsafe_unretained 修飾符 是不安全的修飾符,在 iOS4 以前用來代替 _weak修飾符
id __unsafe__unretained obj1 = nil; { id __strong obj0 = [[NSObject alloc] init]; obj1 = obj0; NSLog(@"A = %@", obj1); } NSLog(@"B = %@", obj1); /** 該源碼無法正確執(zhí)行,因為 __unsafe_unretained修飾符 使變量既不強引用對象,也不弱引用對象。 當(dāng) obj0 超出作用域時, NSObject 無引用,所以被釋放 在此同時, obj1 有時會錯誤訪問對象,形成下面這種打印 2017-12-14 15:38:21.462724+0800 littleTest[10140:1399554] A = <NSObject: 0x10044eea0> 2017-12-14 15:38:21.463007+0800 littleTest[10140:1399554] B = <NSObject: 0x10044eea0> 有時會發(fā)生錯誤,直接使程序崩潰。 造成這兩種情況的本質(zhì)為: obj1 訪問的對象已經(jīng)被廢棄了,造成了 垂懸指針! */ 所以,需要注意的是,當(dāng)使用 _unsafe_unretained 修飾符 訪問對象時,必須要確保該對象確實是真實存在的。
-
-
__autoreleasing 修飾符
-
在 ARC 有效時,我們不可以使用如下代碼,因為這些代碼是在非 ARC 環(huán)境下使用的:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; id obj = [[NSObject alloc] init]; [obj autorelease]; [pool drain]; -
作為替換,在 ARC 有效時, 我們會使用
@autoreleasepool { id __autoreleasing obj = [[NSObject alloc] init]; } 他們的對應(yīng)關(guān)系如圖:
-

我們可以非顯式的使用 __autoreleasing 修飾符 。
-
情況一:當(dāng)取得非自己生成并持有的對象時,雖然可以使用 alloc/new/copy/mutableCopy 以外的方法來取得對象,但是該對象已經(jīng)被注冊到 autoreleasePool 中。這和在 ARC無效 時,調(diào)用 autorelease 方法取得的結(jié)果相同。這是因為編譯器會自動檢查方法名是否以alloc/new/copy/mutableCopy 開始,如果不是,則自動將對象注冊到 autoreleasePool 中。
@autoreleasepool { // 首先取得非自己生成的對象,因為 obj 的強引用,所以它持有這個對象 // 因為這個對象的方法名不是以 alloc/new/copy/mutableCopy 開頭的,所以他被自動注冊到 autoreleasePool中了。 { id __strong obj = [NSMutableArray array]; } /** 當(dāng)變量超出其作用域時,他失去對這個對象的強引用。所以它會釋放自己所持有的對象 但是此時 autoreleasePool 中還持有著對這個對象的引用,所以它不會立即被廢棄 */ } /** 當(dāng) autoreleasePool 的作用域也結(jié)束后,沒有人持有這個對象了,所以它被廢棄了。 */-
驗證上述說法,首先:創(chuàng)建對象的方式為
[NSMutableArray array]:// 在 autoreleasepool 的作用域外定義一個 obj1 持有弱引用 id __weak obj1 = nil; @autoreleasepool { { id __strong obj = [NSMutableArray array]; obj1 = obj; NSLog(@"%@", obj1); } NSLog(@"%@", obj1); } NSLog(@"%@", obj1); /** 打印結(jié)果: 2017-12-14 16:32:07.118513+0800 littleTest[10242:1444479] ( ) 2017-12-14 16:32:07.118819+0800 littleTest[10242:1444479] ( ) 2017-12-14 16:32:07.118850+0800 littleTest[10242:1444479] (null) 結(jié)果表明:當(dāng)obj0超出其作用域時,它失去了對對象的引用。但是由于該對象被自動注冊到 autoreleasepool 中,使得第二個 NSLog 打印時 obj1 依舊弱引用著這個對象,當(dāng)?shù)谌齻€ NSLog 打印時,由于 autoreleasepool 已經(jīng)被清空,所以這個對象也被銷毀了, obj1 又被重置為 nil */
-
-
此時,創(chuàng)建對象的方式為:
[[NSMutableArray alloc] init]id __weak obj1 = nil; @autoreleasepool { { id __strong obj = [[NSMutableArray alloc] init]; obj1 = obj; NSLog(@"%@", obj1); } NSLog(@"%@", obj1); } NSLog(@"%@", obj1); /** 打印結(jié)果如下: 2017-12-14 16:36:09.584864+0800 littleTest[10257:1449554] ( ) 2017-12-14 16:36:09.585131+0800 littleTest[10257:1449554] (null) 2017-12-14 16:36:09.585149+0800 littleTest[10257:1449554] (null) 這是因為,使用 alloc/new/copy/mutableCopy 方法創(chuàng)建對象時,不會將該對象自動的放入 autoreleasePool 中,這就使得當(dāng) obj0 超出其作用域后,就沒有人強引用著 NSMutableArray 對象了,該對象也就被廢棄了。 */
-
以下為 取得非自己生成并持有的對象 時所調(diào)用方法:
+ (id)array { return [[NSMutableArray alloc] init]; } /** 這段代碼也沒有使用 __autorelease修飾符,所以這個方法內(nèi)部的對象不會被注冊到 autoreleasePool 中。 */ // 上述方法也可以寫成如下形式: + (id)array { id obj = [[NSMutableArray alloc] init]; return obj; } /** 因為 return 使得 obj 超出作用域,所以它所指向的對象 NSMutableArray 會被自動釋放,但是因為 return 將這個對象作為函數(shù)的返回值返回給主調(diào)函數(shù),所以這個對象不會被廢棄。并且由于這個對象的生成方法是將其作為返回值,不是由alloc/new/copy/mutableCopy 方法創(chuàng)建的,所以 NSMutableArray 對象會被自動添加到 autoreleasePool 中 */
-
情況二: 在訪問有 __weak修飾符 的變量時,實際上必定會訪問注冊到 autoreleasePool 中的對象
id __weak obj1 = obj0; NSLog(@"class=%@", [obj1 class]); // 等價于 id __weak obj1 = obj0; id _autoreleasing temp = obj1; NSLog(@"class=%@", [temp class]); /** 出現(xiàn)這種情況的原因是因為:__weak修飾符 只持有對象的弱引用,這樣沒法保證它訪問對象的過程中,對象不被廢棄。所以我們將他要訪問的對象放到 autoreleasePool 中,這樣就會使得 @autoreleasePool塊 結(jié)束之前都能保證該對象的存在。 */ -
情況三:由于
id obj <==> id __strong obj所以我們希望能推出id *obj <==> id __strong *obj但是實際上并非如此,實際情況是id *obj <==> id __autoreleasing obj同理:NSObject **obj <==> NSObject * __autoreleasing *obj,像這樣的,id 的指針或?qū)ο蟮闹羔樤跊]有顯示的指定時,會被附加上 __autoreleasing修飾符// 例如 NSString 中的這個方法 stringWithContentsOfFile:(nonnull NSString *) encoding:(NSStringEncoding) error:(NSError * _Nullable __autoreleasing * _Nullable) // 使用這個方式的源代碼如下: NSError *error = nil; BOOL result = [obj performOperationWithError:&error]; // 函數(shù)聲明如下 - (BOOL)performOperationWithError:(NSError **)error; // 等價于 - (BOOL)performOperationWithError:(NSError * __autoreleasing *)error; /** 之所以使用 __autoreleasing 作為修飾符,是因為我們這個方法聲明的參數(shù)的是 error 這個指針變量的指針,也就是 需要傳遞 error 的地址。而在這個方法內(nèi)部的執(zhí)行如下,它改變了 error 這個指針?biāo)赶虻膬?nèi)容。使其指向了一個由 alloc 生成的對象。而我們需要明白的內(nèi)存管理的思考方式為:除了由 alloc/new/copy/mutableCopy 生成的對象外,其他方式生成的對象都必需要注冊到 autoreleasePool 中。并取得非自己所持有的對象。所以將變量聲明為 (NSError * __autoreleasing *)error 就可以實現(xiàn)這一目的。 */ - (BOOL)performOperationWithError:(NSError * __autoreleasing *)error { *error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil]; return NO; } /** 可能對于不熟悉 C語言 的小伙伴來說,不是很明白為什么這里非要將函數(shù)參數(shù)聲明為 “指針的指針”,這是因為當(dāng)我們僅僅把參數(shù)聲明為指針時,方法就變?yōu)槿缦拢?dāng)我們給函數(shù)傳遞指針時,默認(rèn)會生成跟指針類型相同的實例變量。當(dāng)我們在這個方法中操作指針時,我們以為操作的是指針,實際上只是這復(fù)制的實例變量。也就是說,在這個例子中 error 就是這個復(fù)制的實例變量。當(dāng)這個方法結(jié)束時,error 會被釋放,其所指向的內(nèi)容也會一并被釋放。所以此時外部的 error 依舊指向 nil。沒有任何改變。 而當(dāng)我們使用 (NSError * __autoreleasing *)error 作為參數(shù)時,雖然復(fù)制的實例變量情況還是存在,但是這次復(fù)制的是“指針的指針”,也就是說,它指向跟參數(shù)指針相同指針地址, 在函數(shù)內(nèi)部使用 *error 獲取到了指針地址,使其指向了 NSError對象 。這樣,雖然函數(shù)當(dāng)出了其作用域時,那個復(fù)制的實例變量被銷毀了,但是它改變了函數(shù)外部 error 指針?biāo)赶虻膶ο?,使其?nil 變成了 NSError對象。 */ - (BOOL)performOperationWithError:(NSError *)error { error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil]; return NO; }
對于函數(shù)傳遞指針及指針的指針 還不明白的請看這里
-
??的代碼會產(chǎn)生編譯錯誤:
NSError *error = nil; NSError **perror = &error; //Pointer to non-const type 'NSError *' with no explicit ownership -
因為
// 需要改變perror的所有權(quán)修飾符 NSError *error = nil; NSError *__strong *perror = &error; // 對于其他類型的所有權(quán)修飾符也一樣 NSError __weak *error = nil; NSError *__weak *perror = &error; /** 可是我們剛剛在調(diào)用 - (BOOL)performOperationWithError:(NSError * __autoreleasing *)error 方法時,并沒有將 NSError *error 轉(zhuǎn)化為 __autoreleasing修飾符修飾的,這是為什么? */ // 實際上,編譯器自動幫我們轉(zhuǎn)化了修飾符 NSError *error = nil; NSError *_autoreleasing *temp = nil; BOOL result = [obj performOperationWithError:&temp]; error = temp;
像 NSAutoreleasePool 一樣, autoreleasepool 也可以嵌套使用。例如在 iOS 程序中,整個程序都被包含在 @autoreleasepool塊 中。
NSRunLoop等實現(xiàn)無論 ARC 是否有效,都能夠隨時釋放注冊到 autoreleasepool 中的對象。
1.3.4規(guī)則
-
在 ARC 有效時編譯代碼,必須遵守以下規(guī)則:
- 不能使用 retain/release/retainCount/autorelease
- 不能使用 NSAllocateObject/NSDeallocateObject
- 必須遵守內(nèi)存管理方法命名規(guī)則
- 不能顯示的調(diào)用 dealloc
- 使用 @autoreleasepool塊 代替 NSAutoreleasePool
- 不能使用區(qū)域 NSZone
- 對象型變量不能作為 C語言 結(jié)構(gòu)體的成員
- 顯示的轉(zhuǎn)換 id 和 void *
-
不能使用 retain/release/retainCount/autorelease
- 摘自蘋果的官方說明 "設(shè)置ARC有效時,無需再次鍵入release或retain代碼" 否則就會編譯錯誤。
-
不能使用 NSAllocateObject/NSDeallocateObject
- 我們已經(jīng)知道了當(dāng)我們在調(diào)用 NSObject 類的 alloc 方法時,會生成并持有 OC 對象,如 GNUstep 所示,實際上 alloc 就是直接調(diào)用 NSAllocateObject 函數(shù)來生成持有對象的,但是在 ARC 環(huán)境下,如果我們也顯示的調(diào)用 NSAllocateObject 會產(chǎn)生編譯錯誤。
-
必須遵守內(nèi)存管理方法命名規(guī)則
在 ARC 無效時,用于對象生成/持有需要遵循如下規(guī)則:alloc/new/copy/mutableCopy。以上述名稱開始的方法在返回對象時,必須得返回給調(diào)用方應(yīng)當(dāng)持有的對象。這點在 ARC 有效時也是一樣。
-
在 ARC 有效時,追加的一條命名規(guī)則:init
-
以 init 開始的方法規(guī)則要比 alloc/new/copy/mutableCopy 更加嚴(yán)格。該方法必須得是實例方法。不允許是類方法。并且返回對象應(yīng)該為 id 類型或者該方法聲明類的對象類型,或者是該類的超類或子類。該返回對象并不注冊到 autoreleasepool 中?;旧现皇菍?alloc 方法返回值的對象進(jìn)行初始化操作并返回該對象。
// 以下為使用該方法的源代碼: init 方法會初始化alloc 方法返回值,并且返回該對象 id obj = [[NSObject alloc] init]; // 下列是不允許的,因為它沒有返回對象 - (void)initTheData:(id)data; // 另外,??方法雖然也有init, 但它不包含在命名規(guī)則里,因為他是一個單詞 initialize - (void)initialize;
-
-
不能顯示的調(diào)用 dealloc
- 因為當(dāng)對象廢棄時,無論如何都會調(diào)用對象的 dealloc 方法,所以不需要我們手動調(diào)用。而當(dāng)我們手賤一下的去調(diào)用時,就會產(chǎn)生編譯錯誤
- dealloc 方法在大多數(shù)情況下用于刪除已經(jīng)注冊的代理或者觀察者對象
- 在 ARC 無效時,必須在 dealloc 方法內(nèi)部顯式的調(diào)用其父類的 dealloc 方法
[super dealloc]; - 在 ARC 有效時,這一切都是自動處理的。
-
使用 @autoreleasepool塊 代替 NSAutoreleasePool
- 在 ARC 中使用 NSAutoreleasePool 會引起編譯錯誤
-
不能使用區(qū)域 NSZone
- 無論 ARC 是否有效,NSZone在現(xiàn)在的運行時系統(tǒng)已經(jīng)被完全忽略了。
-
對象型變量不能作為 C語言 結(jié)構(gòu)體的成員
C語言 的結(jié)構(gòu)體中如果存在 OC對象型變量 會引起編譯錯誤
-
如果非要將對象加入結(jié)構(gòu)體,則可強制轉(zhuǎn)化為 void * 或者附加 _unsafe_unretained修飾符 ,因為被 _unsafe_unretained修飾符所修飾的對象,已經(jīng)不屬于編譯器的內(nèi)存管理對象了。
struct Data { NSMutableArray __unsafe__unretained *array }
-
顯示的轉(zhuǎn)換 id 和 void *
-
當(dāng) ARC 無效時,將 id變量 強制轉(zhuǎn)化為 void *變量 不會出現(xiàn)問題
id obj = [[NSObject alloc] init]; void *p = obj; // 將 void * 賦給 id變量 中,調(diào)用他的實例方法,運行時也不會出現(xiàn)問題 id o = p; [o release]; -
當(dāng) ARC 有效時,會引起編譯錯誤。此時,id型 或 對象型變量 賦值給 void * 或者逆向賦值時都需要進(jìn)行特定的轉(zhuǎn)換,如果只是想單純的賦值則可以使用 bridge轉(zhuǎn)換
id obj = [[NSObject alloc] init]; // id轉(zhuǎn)化為 void *,它的安全性比 __unsafe__unretained 還要低,一不小心就會有垂懸指針 void *p = (__bridge void *)obj; // void * 轉(zhuǎn)換為 id id o = (__bridge id)p; -
__bridge轉(zhuǎn)換 中還包括 _bridge_retained轉(zhuǎn)換, _bridge_transfer轉(zhuǎn)換
id obj = [[NSObject alloc] init]; void *p = (__bridge_retained void *)obj-
該代碼在 非ARC 環(huán)境下
id obj = [[NSObject alloc] init]; void *p = obj; // __bridge__retained轉(zhuǎn)變?yōu)榱?retain,使得 p 和 obj 都持有了這個對象 [(id)p retain];
-
-
一個其他的例子:
void *p = 0; { id obj = [[NSObject alloc] init]; p = (__bridge_retained void *)obj; } NSLog(@"class = %@", [(__bridge_retained)p class]);-
該代碼在 非ARC 環(huán)境下
void *p = 0; { id obj = [[NSObject alloc] init]; p = [obj retain]; [obj release]; } /** 此時 p 依舊持有對 NSObject對象 的引用 */ NSLog(@"class = %@", [(__bridge_retained)p class]);
-
-
__bridge_transfer,被轉(zhuǎn)換的變量所持有的對象在該變量被賦值給轉(zhuǎn)換目標(biāo)變量后釋放
id obj = (__bridge_transfer id)p;-
非ARC 環(huán)境下
id obj = (id)p; [obj retain]; [(id)p release];
-
-
Objective-C 對象 與 Foundation對象
- Core Foundation 對象主要使用在用 C語言 編寫的 Core Foundation 框架中,并使用引用計數(shù)對象。在 ARC無效 時, Core Foundation 框架中的 retain、release 分別是 CFRetain,CFRelease
- Core Foundation 對象與 OC 對象的區(qū)別只在于是 Core Foundation 框架 還是 Foundation 框架所生成的。無論是由哪種框架生成的對象,一旦生成之后,就能在其它框架上使用。比如 Foundation 框架的 API 生成并持有的對象可以由 Core Foundation 框架的 API 進(jìn)行釋放。
- Core Foundation 對象與 Objective-C 對象沒有區(qū)別,所以在 ARC無效 時,只用簡單的 C語言的轉(zhuǎn)換也能實現(xiàn)互換。另外這種互換不需要占用 CPU 資源,所以也叫做 "免費橋"(Toll-Free Bridge)
-
1.3.5屬性
-
屬性聲明的屬性 與 所有權(quán)修飾符 對應(yīng)的關(guān)系
屬性聲明的屬性 所有權(quán)修飾符 assign _unsafe_unretained copy __strong(賦值的是被復(fù)制的對象) retain __strong strong __strong unsafe_unretained _unsafe_unretained weak __weak
1.3.6數(shù)組
-
靜態(tài)數(shù)組的情況:
// 將附有各種修飾符的變量作為靜態(tài)數(shù)組的使用情況 // 比如 id __weak obj[10]; // 除了 __unsafe__unretained修飾符之外的其他修飾符都是會將數(shù)組元素的值默認(rèn)初始化為nil // 當(dāng)數(shù)組超出其變量作用域時,內(nèi)存管理也同樣適用于他之中的各個對象 動態(tài)數(shù)組:在這種情況下,根據(jù)不同的目的選擇使用 NSMutableArray, NSMutableDictionary, NSMutableSet 等 Foundation 框架中的容器,這些容器會恰當(dāng)?shù)某钟凶芳拥膶ο蟛槲覀児芾磉@些對象。
-
看一下動態(tài)數(shù)組在 C語言 中的實現(xiàn)
// 首先,聲明一個動態(tài)數(shù)組需要使用指針。來表示指針的地址 id __strong *array = nil; //這里是由于 id * 類型的指針默認(rèn)修飾符為 id __autoreleasing * 類型, 所以有必要顯示的指定為 __strong 修飾符。另外,雖然保證了附有 __strong修飾符 的 id 類型變量被初始化為 nil, 但是不保證 array變量, 也就是 id指針型變量 被初始化為 nil // 當(dāng)類型是其他類型時,如下: NSObject * __strong *array = nil; // 之后,使用 calloc函數(shù) 確保想分配的,附有 __strong修飾符變量 的容量占有的內(nèi)存塊 array = (id __strong *)calloc(entries, sizeof(id)); // 其中 entries 表示內(nèi)存塊的數(shù)量。并且 calloc 函數(shù)將數(shù)組中的每個變量指向的對象都自動初始化為 nil // 注意這里如果使用了 malloc函數(shù) 來分配內(nèi)存, 則需要手動的將每個變量所指向的對象都初始化為 0,注意這里只能使用 memset等函數(shù) 來進(jìn)行初始化賦值 // 然后,通過 calloc函數(shù) 分配的動態(tài)數(shù)組就能完全按照靜態(tài)數(shù)組的方法使用 array[0] = [[NSObject alloc] init]; // 但是在動態(tài)數(shù)組中操作 __strong修飾符 的變量與靜態(tài)數(shù)組有很大差異,需要自己手動釋放數(shù)組,但是當(dāng)它釋放時,必須手動的先將數(shù)組的每個變量都置為nil,此時不能使用 memset等函數(shù) 將數(shù)組中的元素值設(shè)為 0 。這也會內(nèi)存泄漏 for (NSInteger i = 0; i < entries; ++i) { array[i] = nil; } free(array);
1.4 ARC 的實現(xiàn)
1.4.1 __strong修飾符
-
觀察賦值給附有 __strong修飾符 的變量在實際程序中到底是如何運行的,??代碼(首先是正常的會使引用計數(shù) +1 的 alloc/new/copy/mutableCopy 方法):
{ id __strong obj = [[NSObject alloc] init]; }-
該段代碼轉(zhuǎn)化為匯編代碼后,為(具體如何轉(zhuǎn)化為匯編代碼,請看我的另一篇文章):
.section __TEXT,__text,regular,pure_instructions .macosx_version_min 10, 13 .globl _main .p2align 4, 0x90 _main: ## @main .cfi_startproc ## BB#0: pushq %rbp Lcfi0: .cfi_def_cfa_offset 16 Lcfi1: .cfi_offset %rbp, -16 movq %rsp, %rbp Lcfi2: .cfi_def_cfa_register %rbp subq $16, %rsp movl $0, -4(%rbp) movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi movq %rax, %rdi callq _objc_msgSend leaq -16(%rbp), %rdi xorl %ecx, %ecx movl %ecx, %esi movq %rax, -16(%rbp) callq _objc_storeStrong xorl %eax, %eax addq $16, %rsp popq %rbp retq .cfi_endproc .section __DATA,__objc_classrefs,regular,no_dead_strip .p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_" L_OBJC_CLASSLIST_REFERENCES_$_: .quad _OBJC_CLASS_$_NSObject .section __TEXT,__objc_methname,cstring_literals L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_ .asciz "alloc" .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip .p2align 3 ## @OBJC_SELECTOR_REFERENCES_ L_OBJC_SELECTOR_REFERENCES_: .quad L_OBJC_METH_VAR_NAME_ .section __TEXT,__objc_methname,cstring_literals L_OBJC_METH_VAR_NAME_.1: ## @OBJC_METH_VAR_NAME_.1 .asciz "init" .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip .p2align 3 ## @OBJC_SELECTOR_REFERENCES_.2 L_OBJC_SELECTOR_REFERENCES_.2: .quad L_OBJC_METH_VAR_NAME_.1 .section __DATA,__objc_imageinfo,regular,no_dead_strip L_OBJC_IMAGE_INFO: .long 0 .long 64 .subsections_via_symbols
-
-
簡化后的模擬代碼為:
// 首先發(fā)送消息給 NSObject 類,消息內(nèi)容為 alloc 指令,然后將結(jié)果賦值給 obj id obj = objc_msgSend(NSObject, @selector(alloc)); // 然后將 init 消息發(fā)送給 obj objc_msgSend(obj, @selector(init)); // 最后釋放 obj objc_release(obj); // 由此可知,在 ARC 有效時,自動插入了 release 方法 -
取得 非自己生成的,但是自己持有的 對象:
id __strong obj = [NSMutableArray array];-
轉(zhuǎn)化成匯編語言:
.section __TEXT,__text,regular,pure_instructions .macosx_version_min 10, 13 .globl _main .p2align 4, 0x90 _main: ## @main .cfi_startproc ## BB#0: pushq %rbp Lcfi0: .cfi_def_cfa_offset 16 Lcfi1: .cfi_offset %rbp, -16 movq %rsp, %rbp Lcfi2: .cfi_def_cfa_register %rbp subq $16, %rsp movl $0, -4(%rbp) movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rax movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi movq %rax, %rdi callq _objc_msgSend movq %rax, %rdi callq _objc_retainAutoreleasedReturnValue leaq -16(%rbp), %rdi xorl %ecx, %ecx movl %ecx, %esi movq %rax, -16(%rbp) callq _objc_storeStrong xorl %eax, %eax addq $16, %rsp popq %rbp retq .cfi_endproc .section __DATA,__objc_classrefs,regular,no_dead_strip .p2align 3 ## @"OBJC_CLASSLIST_REFERENCES_$_" L_OBJC_CLASSLIST_REFERENCES_$_: .quad _OBJC_CLASS_$_NSMutableArray .section __TEXT,__objc_methname,cstring_literals L_OBJC_METH_VAR_NAME_: ## @OBJC_METH_VAR_NAME_ .asciz "array" .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip .p2align 3 ## @OBJC_SELECTOR_REFERENCES_ L_OBJC_SELECTOR_REFERENCES_: .quad L_OBJC_METH_VAR_NAME_ .section __DATA,__objc_imageinfo,regular,no_dead_strip L_OBJC_IMAGE_INFO: .long 0 .long 64 .subsections_via_symbols
-
-
簡化后的匯編語言:
// 首先發(fā)送 array 消息給接收者 NSMutableArray, 然后將結(jié)果的返回值賦給 obj id obj = objc_msgSend(NSMutableArray, @selector(array)); /** obj_retainAutoreleasedReturnValue 函數(shù)主要用于最優(yōu)化程序運行,顧名思義 obj_retainAutoreleasedReturnValue 表示的是 “持有 Autorelease 的返回值”,表示的是,它是用于自己持有對象的函數(shù),但他持有的對象應(yīng)為返回注冊在 autoreleasepool 中對象的方法,,或是函數(shù)的返回值。像這段源代碼一樣,也就是 obj 需要被 __strong 所修飾在調(diào)用 alloc/new/copy/mutableCopy 以外的方法時,由編譯器插入該函數(shù) */ objc_retainAutoreleasedReturnValue(obj); // 釋放 obj objc_release(obj); -
由于,objc_retainAutoreleasedReturnValue 函數(shù)總是成對出現(xiàn)的,所以實際上它還有一個姐妹:objc_autoreleaseReturnValue, 它主要用在 alloc/new/copy/mutableCopy 以外的方法生成對象時的返回對象上,也就是如??所示
+ (id)array { return [[NSMutableArray alloc] init]; } // 轉(zhuǎn)化成匯編后的簡化代碼 + (id)array { id obj = objc_msgSend(NSMutableArray, @selector(alloc)); objc_msgSend(obj, @selector(init)); // 此時,返回注冊到 autoreleasepool 中對象的方法:使用了 obj_autoreleaseReturnValue 函數(shù)來返回注冊到 autoreleasepool 中的對象,但是 obj_autoreleaseReturnValue 方法與 obj_autorelease 方法不同,一般不僅限于注冊對象到 autoreleasepool 中 return objc_autoreleaseReturnValue(obj); } /** objc_autoreleaseReturnValue 方法會檢查使用該函數(shù)的方法或調(diào)用方的執(zhí)行命令列表, 1.如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著調(diào)用了 objc_retainAutoreleasedReturnValue() 函數(shù),那么就不會將返回的對象注冊到 autoreleasepool 中,而是直接傳遞到方法或函數(shù)的調(diào)用方去。 2.如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著沒調(diào)用objc_retainAutoreleasedReturnValue() 函數(shù),那么就會將返回對象注冊到 autoreleasepool 中。 而 objc_retainAutoreleasedReturnValue() 函數(shù)與 objc_retain 函數(shù)不同,他即便不注冊到 autoreleasepool 中,也能正確的獲取對象。 通過 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue 方法的協(xié)作,可以不將對象注冊到 autoreleasepool 中二直接傳遞,這一過程達(dá)到最優(yōu)化 */
1.4.2 __weak修飾符
- 就像我們之前看到的:__weak修飾符 所提供的功能如魔法一般
- 若附有 __weak修飾符 的變量所引用的對象被廢棄,則將 nil 賦值給該變量
- 使用附有 __weak修飾符 的變量,即是使用注冊到 autoreleasepool 中的對象
若附有 __weak修飾符 的變量所引用的對象被廢棄,則將 nil 賦值給該變量 原理驗證:
-
下面我們來看看 __weak修飾符 原理實現(xiàn):
{ id __weak obj1 = obj; } /** 編譯器的模擬代碼 */ id obj1; // 首先通過 obj_initWeak 函數(shù)初始化附有 __weak 修飾符的變量 objc_initWeak(&obj1, obj); // 然后在變量作用域結(jié)束時,通過 obj_destroyWeak 函數(shù)釋放該變量 objc_destroyWeak(&obj1); /** 其中,objc_initWeak 函數(shù)的作用是:將附有 __weak修飾符 的變量初始化為 0 后,會將賦值的對象作為參數(shù)調(diào)用 objc_storeWeak 函數(shù) obj_destroyWeak 函數(shù)的作用是:將 0 作為參數(shù)調(diào)用 obj_storeWeak 函數(shù) */ objc_initWeak(&obj1, obj); <==> obj1 = 0; objc_storeWeak(&obj1, obj); objc_destroyWeak(&obj1) <==> objc_storeWeak(&obj1, 0); /** objc_storeWeak 函數(shù)把 第二個參數(shù) 的賦值對象的 地址 作為 "鍵值",將 第一個參數(shù) 的附有 __weak修飾符 的變量的"地址"注冊到 weak 表 中。如果第二個參數(shù)為 0 ,則把變量的地址從 weak 表中刪除 weak 表與引用計數(shù)表相同,實現(xiàn)方式都為"散列表"。如果使用 weak 表,將廢棄對象的地址作為鍵值進(jìn)行搜索,就能高速的獲取對應(yīng)的附有 weak修飾符 的變量的地址。另外,由于一個對象可以同時賦值給多個附有 weak修飾符 的變量中,所以對于一個鍵值,可注冊多個變量的地址。 */
-
釋放對象時,廢棄沒人持有的對象的同時,程序是如何操作的,下面我們來跟蹤觀察,對象將通過 objc_release 方法釋放
- obj_release
- 引用計數(shù)為 0, 所以執(zhí)行 dealloc
- _objc_RootDealloc
- object_dispose
- objc_destrctInstance
- objc_clear_deallocating
- 其中,objc_clear_deallocating 的動作如下
- 從 weak 表中獲取廢棄對象的地址作為鍵值的記錄
- 將包含在記錄中的所有附有 __weak修飾符 變量的地址賦值為 nil
- 從 weak 表中刪除該記錄
- 從引用計數(shù)表中刪除廢棄對象的地址作為鍵值的記錄
- 其中,objc_clear_deallocating 的動作如下
根據(jù)以上步驟可知:__weak修飾符 所修飾的變量所引用的對象被廢棄,該變量被置為 nil 得到實現(xiàn)。但是由此可知,如果大量使用附有 _weak修飾符修飾變量,將會產(chǎn)生性能問題。
-
在使用 __weak修飾符 時, 如果如下方式,會引起警告
id __weak obj = [[NSObject alloc] init]; // 因為該對象剛被創(chuàng)建就會被釋放 // 編譯器的模擬代碼 id obj; id tmp = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(tmp, @selector(init)); // 雖然自己生成并持有的對象通過 objc_initWeak 函數(shù)被賦值給附有 __weak修飾符 的變量中,但是編譯器判斷它沒有持有者,所以該對象立即通過 objc_release 方法釋放 objc_initWeak(&obj, tmp); objc_release(tmp); objc_destroyWeak(&obj); // 這樣一來, nil 就會被賦值給引用廢棄對象的附有 __weak修飾符 的變量 -
關(guān)于立即釋放對象的一些思考
// 已知以下代碼會引起編譯器的警告,這是因為編譯器判斷生成并持有的對象不能繼續(xù)持有,因為沒有強引用指向它 id __weak obj = [[NSObject alloc] init]; // --------------------------------------------------------------------------------- // 附有 __unsafe_unretained 修飾符的變量會怎樣? 也會產(chǎn)生警告 id __unsafe_unretained obj = [[NSObject alloc] init]; // 轉(zhuǎn)換成編譯器的模擬代碼: id obj = obj_msgSend(NSObject, @selector(alloc)); objc_msgSend(obj, @selector(init)); // obj_release 函數(shù)立刻釋放了生成并持有的對象,這樣該對象的垂懸指針被賦給 obj objc_release(obj); // --------------------------------------------------------------------------------- // 如果在生成對象的時候不把它賦給變量會怎樣? // 在 非ARC 環(huán)境下,必然會發(fā)生內(nèi)存泄漏 // 但是在 ARC 環(huán)境下,由于不能繼續(xù)持有該對象,會立即調(diào)用 obj_release 函數(shù),由于 ARC 的處理,這樣的代碼不會產(chǎn)生內(nèi)存泄漏 [[NSObject alloc] init]; // ARC 下生成的代碼 id tmp = obj_msgSend(NSObject, @selector(alloc)); objc_msgSend(tmp, @selector(init)); objc_release(tmp); // --------------------------------------------------------------------------------- // 是否可以調(diào)用被立即釋放掉的對象的實例方法? (void)[[[NSObject alloc] init] hash]; // 該代碼會變成如下形式: id tmp = obj_msgSend(NSObject, @selector(alloc)); objc_msgSend(tmp, @selector(init)); objc_msgSend(tmp, @selector(hash)); objc_release(tmp); // 所以,obj_release 方法是在該對象實例方法調(diào)用完成后才會被調(diào)用,所以可以調(diào)用被立即釋放的對象的實例方法
使用附有 __weak修飾符 的變量,即是使用注冊到 autoreleasepool 中的對象 ,原理驗證:
-
看??代碼
{ id __weak obj1 = obj; NSLog(@"%@", obj1); } // 該源碼可轉(zhuǎn)化為如下形式 id obj1; objc_initWeak(&obj1, obj); // objc_loadWeakRetained 取出附有 __weak修飾符 變量所引用的對象并 retain id tmp = objc_loadWeakRetained(&obj1); // 將對象注冊到 autoreleasepool 中 objc_autorelease(tmp); NSLog(@"%@", tmp); objc_destroyWeak(&obj1); -
由此可知:因為附有 __weak修飾符 的變量所引用的對象像這樣被注冊到 autoreleasepool 中,所以在 @autoreleasepool 塊結(jié)束之前都可以放心的使用 _weak修飾的變量。但是,不能大量的使用附有 _weak修飾符 修飾的變量,否則會引起注冊到 autoreleasepool 中的對象大大增加,因此在使用附有 _weak修飾符 的變量時,最好先暫時賦給附有 _strong修飾符 的變量后再使用。若看不太懂則可以只看下面代碼:
// 下面這段代碼會使變量 o 所賦值的對象被注冊到 autoreleasepool 中 5 次 { id __weak o = obj; for (int i = 0; i < 5; ++i) { NSLog(@"%d -- %@", i, o); } } // 而下面這段代碼只會使變量 o 所賦值的對象被注冊到 autoreleasepool 中 1 次 { id __weak o = obj; id tmp = o; for (int i = 0; i < 5; ++i) { NSLog(@"%d -- %@", i, tmp); } }
不支持 __weak修飾符 的情況
- 在 iOS4 和 OS X Snow Leopard 不支持 __weak修飾符 。
- 不支持 __weak修飾符 的類:NSMachPort等, 這個類重寫了 retain/release 方法,并且實現(xiàn)了自己的引用計數(shù)。
- 不支持 __weak修飾符 的類在其類中聲明附加了 --attribute—((objc_arc_weak_reference_unavailable)) 這一屬性,同時定義了 NS_AUTOMATED_REFCOUNT_WEAK_UNAVAALIBLE
- 還有一種情況也不能使用 __weak修飾符 ,那就是當(dāng)
allocWeakReference/retainWeakReference實例方法返回 NO 的情況。(這種情況沒有被寫入 NSObject 類的接口說明文檔中),也就是說,這兩個方法我們一般不會接觸到。
1.4.3 __autoreleasing修飾符
將對象賦值給附有 __autoreleasing修飾符 的變量等同于 ARC 無效時,調(diào)用對象的 autorelease 方法。
-
首先看一下使用 alloc/new/copy/mutableCopy 時的情況
@autoreleasepool { id __autoreleasing obj = [[NSObject alloc] init]; } // 模擬代碼 id pool = objc_autoreleasePoolPush(); id obj = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(obj, @selector(init)); objc_autorelease(obj); objc_autoreleasPoolPop(pool);
-
再看一下使用 alloc/new/copy/mutableCopy 以外的方法時的情況
@autoreleasepool { id __autoreleasing obj = [NSMutableArray array]; } // 模擬代碼 id pool = objc_autoreleasePoolPush(); id obj = objc_msgSend(NSMutableArray, @selector(array)); objc_retainAutoreleasedReturnValue(obj); objc_autorelease(obj); objc_autoreleasPoolPop(pool); // 雖然 obj 持有對象的方法變?yōu)?objc_retainAutoreleasedReturnValue, 但是將 obj 所引用的對象注冊到 autoreleasepool 中的方法并沒有改變
1.4.4 引用計數(shù)
-
引用計數(shù)數(shù)值本身到底是什么?
// 這個函數(shù)為獲得引用計數(shù)的函數(shù)數(shù)值 uintptr_t _objc_rootRetainCount(id obj); { id __strong obj = [[NSObject alloc] init]; NSLog(@"retain count = %d", _objc_rootRetainCount); } // 打印結(jié)果:retain count = 1
- 我們實際上并不能完全信任 _objc_rootRetainCount 這個函數(shù)所取得的數(shù)值,因為有時對于已經(jīng)釋放的對象以及不正確的對象地址,有時也會返回 1 。 并且在多線程中使用對象的引用計數(shù)數(shù)值,因為有競爭狀態(tài)的問題,所以取得的數(shù)值并不一定完全可信
1.5 總結(jié)
- 至此,我們所探究的 自動引用計數(shù) 已經(jīng)完全講解完畢,如有疏漏或不正確,不準(zhǔn)確的地方,還望大家批評指正,共同進(jìn)步。