
前言
很久前看了《Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理》這本書,但當(dāng)時看起來晦澀難懂。最近利用下班時間重讀了一遍,覺得還是得記錄一下。畢竟往后階段對相同的東西會有更深刻的理解。溫故知新!
系列文章:
1、《Objective-C高級編程》溫故知新之"自動引用計數(shù)"
2、《Objective-C高級編程》溫故知新之"Blocks"
從自動引用計數(shù)概念開始
概念:自動引用計數(shù)是指內(nèi)存管理中對內(nèi)存管理中對引用采取自動計數(shù)的計數(shù)。
工具:Clang是一個C語言、C++、Objective-C、Objective-C++語言的輕量級編
Clang使用: clang -rewrite-objc (文件名)
說一下clang工具的使用。比如我有一個類叫dwyane.m。里面代碼如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
id __strong obj = [NSMutableArray array];
}
return 0;
}
id add()
{
id __strong obj2 = [[NSMutableArray alloc] init];
return obj2;
}
在終端,進(jìn)入dwyane.m目錄,
clang -rewrite-objc dwyane.m,然后,系統(tǒng)會為我們生成dwyane.cpp(C++文件),可以看到下列c++源碼
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
id __attribute__((objc_ownership(strong))) obj = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
}
return 0;
}
id add()
{
id __attribute__((objc_ownership(strong))) obj2 = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
return obj2;
}
Objective-C中的內(nèi)存管理
也就是引用計數(shù)。
文中利用開關(guān)燈事件解釋得非常完美。引用數(shù)隨著人員進(jìn)屋離去隨之加減。引用數(shù)0時關(guān)燈

轉(zhuǎn)換到Objective-C程序中,其實就是下圖

內(nèi)存管理的思考方式
- 自己生成的對象,自己所持有。
- 非自己生成的對象,自己也能持有。
- 不再需要自己持有的對象時釋放。
- 非自己持有的對象無法釋放。
上面出現(xiàn)的“生成” “持有” “釋放” 再加上個 “廢棄” 對應(yīng)的OC方法如下
1、使用alloc、new、copy、mutableCopy的意味著自己生成的對象只有自己持有
eg:
id obj = [NSObject alloc] init];//自己生成并持有對象
其中自己可理解為“對象的使用環(huán)境”或者理解改變世界的程序員本身
2、用alloc、new、copy、mutableCopy外的方法取得的對象,因非自己生成并持有,so不是該對象的持有者。比如NSMutableArray類中的 array類方法
id obj = [NSMutableArray array];//取得的對象存在,但自己不持有對象
使用retain可持有對象
[obj retain];//這樣跟上述的alloc等生成持有對象的方法就一樣了。
3、自己持有的對象,不需要請用release釋放對象
id obj = [NSMutableArray array];//取得的對象存在,但自己不持有對象
[obj release];//釋放對象
指向?qū)ο蟮闹羔樔匀槐槐A粼谧兞縪bj中,貌似可訪問,但對象一經(jīng)釋放,絕對不可訪問。
命名規(guī)則:如果不是自己生成并持有的方法,不得用alloc、new、copy、mutableCopy開頭的方法名。比如下面方法
id add()
{
id obj = [[NSObject alloc] init]; //自己生成并持有對象
[obj autorelease]; //釋放,取得對象存在,但自己不持有對象
return obj;
}
autorelease使對象在超出指定的生存范圍時能夠自動并正確地釋放(調(diào)用release方法),如圖
release 和 autorelease 的區(qū)別
4、無法釋放非自己持有的對象,如果釋放非自己持有的對象就會造成崩潰
alloc/retain/release/dealloc 實現(xiàn)
1、GNUstep的實現(xiàn)
由于NSObject類的源代碼沒有公開,所以借助與蘋果的Cocoa框架類似的GNUstep來理解蘋果的Cocoa實現(xiàn)。
GUNstep的中NSObject類的alloc類方法間接調(diào)用
NSZoneMalloc函數(shù)來分配存放對象所需的內(nèi)存空間,之后將內(nèi)存空間置0,最后返回作為對象而使用的指針。區(qū)域:
NSZoneMalloc的NSZone是什么呢?它是為防止內(nèi)存碎片化而引入的結(jié)構(gòu)。堆內(nèi)存分配本身進(jìn)行多重化管理,根據(jù)使用對象的目的、對象的大小分配內(nèi)存,從而提高了內(nèi)存管理的效率。
但是現(xiàn)在運行時系統(tǒng)只是簡單地忽略區(qū)域的概念。運行時系統(tǒng)中的內(nèi)存管理本身已極具效率,使用區(qū)域來管理內(nèi)存反而會引起內(nèi)存使用效率低下以及源代碼復(fù)雜化等問題。
image.png
alloc類方法用struct obj_layout 中的 retain 整數(shù)來保存引用計數(shù),并將其寫入內(nèi)存頭部,該對象內(nèi)存塊全部置0后返回。過程如圖

對象的引用計數(shù)可通過 retainCount 實例方法取得(非ARC下)
id obj = [[NSObject alloc] init];
NSLog(@"retainCount=%lu", (unsigned long)[obj retainCount]);
/** 結(jié)果為 retainCount=1 */
由此可見,執(zhí)行 alloc 后對象 retainCount 是 “1”??梢酝ㄟ^GNUstep的源代碼確認(rèn)一下

由對象尋址到對象內(nèi)存頭部,從而訪問其中的 retained 變量。

因為分配時全部置0,所以 retained 為0.由
NSExtaRefCount(self) + 1;得出,retainCount 為1.從而推測出,retain方法使retained變量加1,而 release方法使retained變量減1。
2、蘋果的實現(xiàn)
alloc類方法首先調(diào)用allocWithZone:類方法,這和GNUstep的實現(xiàn)相同,然后調(diào)用class_createInstance 函數(shù),最后通過調(diào)用 calloc 來分配內(nèi)存塊。class_createInstance 函數(shù)的源碼可以通過obj4庫中的源碼進(jìn)行確認(rèn)
從源代碼的函數(shù)來看,蘋果的實現(xiàn)大概就是采用散列表(引用計數(shù)表)來管理引用計數(shù)。如圖

GNUstep將引用計數(shù)保存在對象占用內(nèi)存塊頭部的變量中,而蘋果的實現(xiàn),則是保存在引用計數(shù)表中的記錄中。
CGUstep的實現(xiàn)和蘋果的實現(xiàn)好處區(qū)別如下:
通過內(nèi)存塊頭部管理引用計數(shù)的好處如下:
- 少量代碼即刻完成
- 能夠統(tǒng)一管理引用計數(shù)用內(nèi)存塊與對象用內(nèi)存塊。
通過引用計數(shù)表管理計數(shù)的好處如下:
- 對象用內(nèi)存塊的分配無需考慮內(nèi)存塊頭部。
- 引用計數(shù)表各記錄中存有內(nèi)存塊地址,可從各個記錄追溯到各對象內(nèi)存塊。
其中第二條最重要。即使出現(xiàn)故障導(dǎo)致對象占用的內(nèi)存塊損壞,但只要引用計數(shù)表沒有被破壞,就能夠確認(rèn)各內(nèi)存塊的位置。如圖
另外,在利用工具檢測內(nèi)存泄漏時,引用計數(shù)表的各記錄也有助于檢測各對象的持有者是否存在。
autorelease
顧名思義,autorelease 就是自動釋放,看起來像ARC,但實際上更類似C語言中的自動變量(局部變量)特性。
C語言的自動變量:程序執(zhí)行時,某自動變量超過其作用域,該自動變量將自動被廢棄。
{
int a;
} //超過變量 a 的作用域,所以"()"外不可訪問
區(qū)別在于 autorelease 可以被編程人員設(shè)定變量的作用域。
autorelease 的具體使用方法如下:
(1)生成并持有 NSAutoreleasePool 對象;
(2)調(diào)用已分配對象的 autorelease 實例方法;
(3)廢棄 NSAutoreleasePool 對象
用代碼來表示上圖流程
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; //等同于 "[pool release]"
NSAutoreleasePool 對象的生存周期相當(dāng)于C語言的作用域。**對于所有調(diào)用過 autorelease 實例方法的對象,在廢棄 NSAutoreleasePool 對象時,都將調(diào)用 release 實例方法。
在大量產(chǎn)生 autorelease 的對象時, 只要不放棄 NSAutoreleasePool 對象,那么生成的對象就不能釋放,因此有時會產(chǎn)生內(nèi)存不足現(xiàn)象。eg:讀入大量圖像的同時改變其尺寸。圖像文件讀入到 NSData 對象,并從中生成新的 UIImage 對象。這種情況下,就會大量產(chǎn)生 autorelease 的對象。
所以,需要在適當(dāng)?shù)牡胤缴伞⒊钟谢驈U棄 NSAutoreleasePool 對象。
所有權(quán)修飾符
- __strong
- __weak
- __unsafe_unretained修飾符
- __autoreleasing修飾符
“ __strong ” 修飾符
__strong 修飾符是id類型和對象類型默認(rèn)的所有權(quán)修飾符。即下面等號左右兩邊相等
id obj = [NSObject alloc] init]; <==>id __strong obj = [NSObject alloc] init];
下面代碼在ARC和非ARC狀態(tài)下的樣子
ARC:
{
id __strong obj = [NSObject alloc] init];
}
非ARC:
{
id obj = [NSObject alloc] init];
[obj release];
}
可以看出,為了釋放生成并持有的對象,增加了調(diào)用release方法的代碼。該源代碼進(jìn)行的動作同先前ARC有效時的動作完全一樣。
如此源代碼所示,__strong 修飾符修飾的變量obj在超出其變量作用域時,即在該變量被廢棄時,會釋放其被賦予的對象。
{
/** obj0 持有對象A的強(qiáng)引用 */
id __strong obj0 = [[NSObject alloc] init]; //對象A
/** obj1 持有對象B的強(qiáng)引用 */
id __strong obj1 = [[NSObject alloc] init]; //對象B
/** obj2不持有對象 */
id __strong obj2 = nil;
/** obj0 持有賦值給obj2 的對象B的強(qiáng)引用
* 同時obj0丟失原先對對象A的強(qiáng)引用,即
* 對象A的所有者不存在,所以廢棄對象A
* 此時,持有對象B的強(qiáng)引用的變量為
* obj0和obj1.
*/
obj0 = obj1;
/** obj2持有由obj0賦值的對象B的強(qiáng)引用
* 此時,持有對象B的強(qiáng)引用的變量為
* obj0, obj1和obj2。
*/
obj2 = obj0;
/** obj1對對象B的強(qiáng)引用失效,此時
* 持有對象B的強(qiáng)引用變量為 obj2.
*/
obj1 = nil;
/** 對對象B的強(qiáng)引用失效,對象B的所有者不存在,因此廢棄對象B */
obj2 = nil;
}
**__strong修飾符的變量,不僅只在變量作用域,在賦值上也能夠正確地管理其對象的所有者。
@implementation Test
- (instancetype)init {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj;
}
- (void)testMethod {
id __strong test = [[Test alloc] init]; //test生成并持有Test對象的強(qiáng)引用
[test setObject:[[NSObject alloc] init]]; //Test對象的obj_成員持有NSObject對象的強(qiáng)引用
/** 因為test變量超出其作用域,強(qiáng)引用失效,所以自動釋放Test對象。Test對象的所以者不存在,所以廢棄該對象。
* 廢棄Test對象的同時,Test對象的成員obj_也被廢棄,
*同時自動釋放NSObject對象,NSObject對象的所有者不存在,所以廢棄該對象 */
}
@end
“__weak ” 修飾符
看起來,蘋果內(nèi)存管理擁有__strong就足夠,然而,不是這樣的,遇到引用計數(shù)式內(nèi)存管理中必然會發(fā)生的“循環(huán)引用”的問題,就需要用到 __weak 修飾符了

我們修改下上面例子
testMethod函數(shù)的代碼。
- (void)testMethod {
id test0 = [[Test alloc] init]; //對象A
/** test0生成并持有Test對象A的強(qiáng)引用 */
id test1 = [[Test alloc] init]; //對象B
/** test1生成并持有Test對象B的強(qiáng)引用 */
[test0 setObject:test1]; //Test對象的obj_成員持有賦值給test1的Test對象B的強(qiáng)引用
/** 此時,持有Test對象B的變量有
* 對象A的obj_成員以及test1 */
[test1 setObject:test0]; //Test對象的obj_成員持有賦值給test1的Test對象的強(qiáng)引用
/** 此時,持有Test對象A的變量有
* 對象B的obj_成員以及test0 */
}

循環(huán)引用容易發(fā)生內(nèi)存泄漏,所謂內(nèi)存泄漏就是應(yīng)當(dāng)廢棄對象在超出其生命周期后繼續(xù)存在。
還有,只有一個對象,其持有其自身,也會內(nèi)存泄漏。
id test = [[Test alloc] init]; //生成并持有NSObject對象
[test setObject:test]; //NSObject對象被NSObject的obj_成員強(qiáng)引用
如圖

接下來利用__weak修飾符解決循環(huán)問題,再修改上面例子
{
id __strong obj0 = [[NSObject alloc] init]; //obj0生成并持有NSObject對象的強(qiáng)引用
id __weak obj1 = obj0;
/** obj1 變量持有 NSObject 的弱引用 */
}
/** 因為obj0 變量超出其作用域,強(qiáng)引用失效,所以自動釋放自己持有NSObject對象
* 又因為__weak修飾符的變量(即弱引用)不持有對象
* 對象持有者全部不存在,所以被廢棄
如圖

__weak修飾符還有個優(yōu)點:持有某對象弱引用時,若該對象被廢棄,則此弱引用將自動失效且處于nil被賦值的狀態(tài)(空弱引用)
“__unsafe_unretained”修飾符
__weak 修飾符只能用于iOS5以上及OS X Lion以上版本的應(yīng)用程序,在iOS4以及OS X Snow Leopard 的應(yīng)用程序可使用 __unsafe_unretained 代替
__unsafe_unretained 修飾符是不安全的修飾符,盡管ARC式的內(nèi)存管理是編譯器的工作,但附有__unsafe_unretained 修飾符的變量不屬于編譯器的內(nèi)存管理對象。
“__autoreleasing 修飾符”
ARC有效時,用@autoreleasepool 塊替代 非ARC的 NSAutoreleasePool 類,用附有 __autoreleasing 修飾符的變量替代autoreleasing 方法。如圖

注意:但是,顯式地附加 __autoreleasing 修飾符同顯式地附加 __strong 修飾符一樣罕見。這是因為編譯器會檢查方法名是否以alloc/new/copy/utableCopy開始,如果不是則自動將返回值的對象注冊到 autoreleasepool。比如
+ (id) array
{
id obj = [[NSMutableArray alloc] init];
return obj;
}
<上述代碼沒有顯示使用__autoreleasing 修飾符,但是與不附加,結(jié)果完全一樣,因為,return使得obj對象超出其作用域,所以該強(qiáng)應(yīng)用對應(yīng)的自己持有的對象會被自動釋放,但該對象作為函數(shù)的返回值,編譯器會自動將其注冊到 autoreleasepool中。
而,在訪問附有 __weak 修飾符的變量時,實際上必定要訪問注冊到autoreleasepool的對象。為什么?請先看下列代碼
id __weak obj1 = obj0;
NSLog(@"class = %@", [obj1 class]);
以下源代碼與此相同
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@", [tmp class]);
因為__weak 修飾符只持有對象的弱引用,而在訪問引用對象的過程中,該對象有可能被廢棄。如果把要訪問的對象注冊到 autoreleasepool 中,那么在autoreleasepool塊結(jié)束前都能確保變量存在。
**注意:最后一個可非顯示 __autoreleasing 修飾符的例子,id *obj 我們可能會類推出 id __strong *obj,但結(jié)果卻是 id __autoreleasing *obj。同樣,NSObject **obj 則是 NSObject *__autoreleasing *obj。
ARC規(guī)則
- 不能使用 retain/release/retain/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 須遵守內(nèi)存管理的方法命名規(guī)則
- 不要顯示調(diào)用dealloc
- 使用@autoreleasepool 塊替代 NSAutoreleasePool
- 不能使用區(qū)域(NSZone)
- 對象型變量不能作為C語言結(jié)構(gòu)體(struct/union)的成員
- 顯示轉(zhuǎn)換“id”和“void”
不要顯示調(diào)用dealloc
- (void)dealloc {
[super dealloc];
}
/** 這樣會報錯 */
對象型變量不能作為C語言結(jié)構(gòu)體(struct/union)的成員
struct Data {
NSMutableArray *array;
};
/** error:ARC forbids Objective-C objects in struct */
如果一定要把對象型變量加入到結(jié)構(gòu)體成員中,可強(qiáng)制轉(zhuǎn)換為 void * 或者附加 __unsafe_unretained修飾符
struct Data {
NSMutableArray __unsafe_unretained *array;
};
顯示轉(zhuǎn)換“id”和“void”
非ARC
id obj = [[NSObject alloc] init];
void *p = obj;
ARC下
則會報錯
Implicit conversion of Objective-C pointer type 'id' to C pointer type 'void *' requires a bridged cast
錯誤提示了我們,可用bridge,我們修改下代碼即可,如下:
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj; //id 轉(zhuǎn) void *
id o = (__bridge id)p; //void * 轉(zhuǎn) o
注意:前關(guān)注下Objective-C 對象與 Core Foundation 對象的互換以及免費橋
(Toll-Free Bridge)的使用
__bridge_retained 和 __bridge_transfer轉(zhuǎn)換
__bridge_retained
/** ARC: */
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
//等同于下面
/** 非ARC */
// __bridge_retained轉(zhuǎn)變成了retain。變量obj和變量p同時持有對象。
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
__bridge_transfer
/** ARC */
void *p = (__bridge_retained void *)[[NSObject alloc] init];
NSLog(@"class=%@", [(__bridge id)p class]);
(void)(__bridge_transfer id)p; //釋放了p,跟[p release];相同
//等同于下面
/** 非ARC */
id p = [[NSObject alloc] init];
NSLog(@"class=%@", [p class]);
[p release];
Objective-C對象與 Core Foundation 對象
Core Foundation對象主要使用在用C語言編寫的Core Foundation框架中,并使用引用計數(shù)的對象。在ARC無效時,CF的CFRetain/CFRelease對應(yīng)retain/release
CF 對象和OC對象沒有區(qū)別,所以在ARC無效時,用簡單的C語言轉(zhuǎn)換也能實現(xiàn)互換。另外這種互換不需要使用額外的CPU資源,因此被稱為免費橋。
1、OC轉(zhuǎn)CF
//可用于toll-free bridge的互換
CFMutableArrayRef cfObject = NULL;
id obj12 = nil;
{
//obj持有對象A的強(qiáng)引用
id obj = [[NSMutableArray alloc] init]; //對象A
//cfObject也持有對象A的強(qiáng)引用
// cfObject = (__bridge_retained CFMutableArrayRef)obj; // 等同于 cfObject = CFBridgingRetain(obj);
//注意: __bridge 不會對引用計數(shù)產(chǎn)生影響
cfObject = (__bridge CFMutableArrayRef)obj;
// obj12 = obj; //obj2也持有對象A的強(qiáng)引用
CFShow(cfObject);
printf("reain count = %ld\n", CFGetRetainCount(cfObject));
/** 打印:reain count = 1 */
}
//下面訪問對象出錯--》 出現(xiàn)懸垂指針
printf("retain count after the scope = %ld\n", CFGetRetainCount(cfObject)); //對象的引用技術(shù) :引用計數(shù)就是對一個對象記錄其被引用的次數(shù),其的引用計數(shù)可加可減
懸垂指針 :指向曾經(jīng)存在的對象,但該對象已經(jīng)不再存在了,此類指針稱為懸垂指針。結(jié)果未定義,往往導(dǎo)致程序錯誤,而且難以檢測。
2、CF轉(zhuǎn)OC
//生成并持有對象
CFMutableArrayRef cfObject = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
printf("retain count = %ld\n", CFGetRetainCount(cfObject));
/** 打?。簉etain count = 1 */
//通過CFBridgingRelease賦值,變量obj持有對象強(qiáng)引用的同時,對象調(diào)用CFRelease釋放,相當(dāng)于調(diào)用了(__bridge_transfer id)cfObject
id obj = CFBridgingRelease(cfObject); //After using a CFBridgingRetain on an NSObject, the caller must take responsibility for calling CFRelease at an appropriate time.
// id obj = (__bridge_transfer id)cfObject; //(__bridge_transfer id)X
//cfObject上面已經(jīng)被釋放,你會奇怪為什么還有,這不是懸垂指針嗎?其實不是,因為0bj繼續(xù)持有對對象的強(qiáng)引用,所以cfObject也指向仍然存在的對象,可以正常使用
printf("retain count after the cast = %ld\n", CFGetRetainCount(cfObject));
/** 打?。簉etain count after the cast = 1 */
屬性
ARC有效時,以下可作為屬性聲明中使用的屬性來用。

書原文中寫道:在聲明類成員變量時,如果同屬性聲明中的屬性不一致則會引起編譯錯誤。比如
@property (nonatomic, weak) id obj1;
需要改成
@property (nonatomic, weak) id __weak obj1;
又或者把屬性聲明改成strong
@property (nonatomic, strong) id obj1;
但經(jīng)筆者試驗,在Xcode V9.2 、macOS 10.12.6 下編譯運行成功,并無報錯
數(shù)組
id __strong *array = nil;
注意:id *類型 默認(rèn)為”id __autoreleasing *“類型,所以需要顯式指定為__strong修飾符。另外,上式雖然保證了附有__strong修飾符的id型變量被初始化為nil,但并不能保證附有__strong修飾符的id指針型變量被初始化為nil。
在動態(tài)數(shù)組中操作附有__strong修飾符的變量與靜態(tài)數(shù)組有很大差異,需要自己釋放所有的元素。如下源碼,在只是簡單地用free函數(shù)廢棄了數(shù)組用內(nèi)存塊的情況下,數(shù)組各元素所賦值的對象不能再次釋放,從而引起內(nèi)存泄漏。
free(array)
這是因為在靜態(tài)數(shù)組中,編譯器能夠根據(jù)變量的作用域自動插入釋放賦值對象的代碼,而在動態(tài)數(shù)組中,編譯器不能確定數(shù)組的生存周期,所以無從處理。所以一定要將對象賦值nil,使元素所賦值對象強(qiáng)引用失效,從而釋放對象,再free函數(shù)廢棄內(nèi)存塊
for (NSUInteger i = 0; i < entries; ++i) { //entries為分配了所需內(nèi)存塊的個數(shù)
array[i] = nil;
free(array);
ARC的實現(xiàn)
1、__strong修飾符的實現(xiàn)
{
id __strong obj = [[NSObject alloc] init];
}
上面代碼如何運行呢?看看匯編和蘋果源碼obj4庫,大概知道程序是如何工作的。下面請看編譯器的模擬源代碼

由圖可知,2次調(diào)用了
obj_msgSeng方法(alloc 和 init 方法),變量作用域結(jié)束時通過 objc_release 釋放對象(編譯器自動插入了release)
_objc_retainAutoreleasedReturnValue函數(shù)主要用于最優(yōu)化程序運行。顧名思義,它是用于自己持有(retain)對象的函數(shù),但它持有的對象應(yīng)為返回注冊在autoreleasepool中對象的方法,或者是函數(shù)的返回值。
_objc_autoreleaseReturnValue與之相對應(yīng),用于NSMutableArray類的array類方法等返回對象的實現(xiàn)上。
注意:
_objc_autoreleaseReturnValue函數(shù)會檢查使用該函數(shù)的方法或函數(shù)調(diào)用方的執(zhí)行命令列表,如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著調(diào)用_objc_retainAutoreleasedReturnValue()函數(shù),那么就不將返回的對象注冊到autoreleasepool中,而是直接傳遞到方法或函數(shù)的的調(diào)用方。_objc_retainAutoreleasedReturnValue()函數(shù)與obj_retain函數(shù)不同,它即便不注冊到autoreleasepool中而返回對象,也能正確的獲取對象。
2、__weak 修飾符的實現(xiàn)
1、若附有
__weak修飾符的變量所引用的對象被廢棄,則將nil賦值給改變量。
2、使用附有__weak修飾符的變量,即是使用注冊到autoreasepool中的對象。
那他們是如何實現(xiàn)的呢?請看下列代碼
{
id __weak obj1 = obj;
}
下面請看編譯器的模擬源代碼

那具體如何實現(xiàn)上圖的操作,請繼續(xù)看源碼


objc_storeWeak 函數(shù)把第二參數(shù)的賦值對象的地址作為建值,將第一參數(shù)的附有__weak修飾符的變量的地址注冊到 weak 表中。如果第二參數(shù)為0,則將變量的地址從weak 表從 weak 表中刪除。
weak 表與引用計數(shù)表相同,作為散列表被實現(xiàn)。如果大量使用附有 __weak 修飾符的變量,則會消耗相應(yīng)的 CPU 資源。良策是只在需要避免循環(huán)引用時才使用 __weak 修飾符
{
id __weak obj = [[NSObject alloc] init];
}
但上面會引起編譯器警告,因為__weak修飾,NSObject 沒有所有者,創(chuàng)建后,馬上就通過 objc_release 函數(shù)被廢棄。
我們看下下列代碼,驗證功能
使用附有__weak修飾符的變量,即是使用注冊到autoreasepool中的對象。
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
源代碼如下:

由此可知,因為附有__weak修飾符變量所引用的對象像這樣被注冊到
autoreleasepool中,所以在@autoreleasepool塊結(jié)束前之前都可以放心使用。但大量使用__weak修飾的變量,
注冊到autoreleasepool的對象也會大量增加,最好先暫時賦值給__strong修飾符的變量后再使用。
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
NSLog(@"1 = %@", obj1);
NSLog(@"2 = %@", obj1);
NSLog(@"3 = %@", obj1);
NSLog(@"4 = %@", obj1);
上面變量obj所賦值的對象也就注冊到
autoreleasepool4次
建議使用:
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
id tmp = obj1;
NSLog(@"1 = %@", tmp);
NSLog(@"2 = %@", tmp);
NSLog(@"3 = %@", tmp);
NSLog(@"4 = %@", tmp);
相關(guān)文獻(xiàn):
http://www.cocoachina.com/ios/20150610/12093.html
免費橋(Toll-free bridge)
How does objc_retainAutoreleasedReturnValue work?







