《Objective-C高級編程》溫故知新之"自動引用計數(shù)"

本文"鳥瞰圖"

前言

很久前看了《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程序中,其實就是下圖


引用計數(shù)的內(nèi)存管理

內(nèi)存管理的思考方式

  • 自己生成的對象,自己所持有。
  • 非自己生成的對象,自己也能持有。
  • 不再需要自己持有的對象時釋放。
  • 非自己持有的對象無法釋放。

上面出現(xiàn)的“生成” “持有” “釋放” 再加上個 “廢棄” 對應(yīng)的OC方法如下


1、使用allocnew、copy、mutableCopy的意味著自己生成的對象只有自己持有

eg:id obj = [NSObject alloc] init]; //自己生成并持有對象
其中自己可理解為“對象的使用環(huán)境”或者理解改變世界的程序員本身

2、用alloc、newcopy、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、newcopy、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)一下


retainCount源代碼

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


通過對象訪問對象內(nèi)存頭部

因為分配時全部置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 修飾符了

循環(huán)引用

我們修改下上面例子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?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容