引用計數(shù)內(nèi)存管理和 ARC 那點事

.png

前言

程序員在開發(fā)程序的時候,對內(nèi)存的管理是很重要的一件事情,比如如何高效使用內(nèi)存,防止內(nèi)存泄露,降低內(nèi)存的峰值等。如何很好去處理這些問題的前提就必須對內(nèi)存的管理機制有一定的掌握了,本文只要簡單介紹引用計數(shù)式內(nèi)存管理方式,和iOS5推出 ARC,引用計數(shù)內(nèi)存管理方式是如何應(yīng)用在 ARC 上的。從 MRC 到 ARC ,讓程序員關(guān)注于更有趣的代碼。

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

1.自己生成的對象,自己所持有

(1)alloc / new / copy / mutableCopy 等方法。

(2)alloc / new / copy / mutableCopy 使用該單詞開頭,命名是以駝峰的方式的辦法,即是該辦法生成并持有對象。

eg:

  • allocMyObject; (?)
  • newThatObject; (?)
  • copyobject; (?)

2.非自己生成的對象,自己也能持有

retain 方法

3.不在需要自己持有的對象時釋放

release 方法 .如果釋放不是自己持有的對象,則程序會崩潰。

4.非自己持有的對象無法釋放

ps:id objArray = [NSArray array]; 類似調(diào)用類辦法時,生成對象,但并不持有,又是怎么實現(xiàn)的呢?

創(chuàng)建對象的代碼如下:

-(instancetype)object
{
    id object = [[NSObject alloc]init];
    [object autorelease];//防止傳遞的時候,object已經(jīng)被釋放了。加了 autorelease ,使對象在超出作用域時能夠自動并正確釋放(調(diào)用 release 辦法)
    NSLog(@"%@",object);
    return object;
}

alloc / retain / release / dealloc 實現(xiàn)

OS X,iOS 中大部分的源碼沒有公開,但是 GNUstep 是 Cocoa 框架的互換框架。也就是說,能從 GNUstep 中窺探蘋果的實現(xiàn)。

alloc:

+ (id)alloc;
+ (id) allocWithZone:(NSZone *)z;
...
//具體代碼就不貼了 大概講述一下實現(xiàn)思路就可以。
 通過分配對象的所需的內(nèi)存大小,所分配的區(qū)域,和通過賦值對對象內(nèi)存頭部的retained +1;

為什么要通過區(qū)域來創(chuàng)建對象呢?

  • 它是為了防止內(nèi)存碎片化而引入的結(jié)構(gòu)。對內(nèi)存分配的區(qū)域本身進行多重化管理,根據(jù)使用對象的目的,對象的大小分配內(nèi)存,從而提高了內(nèi)存管理的效率。

retain

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

release

由對象尋址找到對象內(nèi)存頭部,從而訪問其中的 retained 變量.當(dāng)retained 變量大于0時減1,等于0時調(diào)用 dealloc 實例辦法,廢棄對象。

值得一提的是:

當(dāng)對象在執(zhí)行最后一次 release 時,系統(tǒng)知道馬上要回收該對象了,所以并不會對對象進行 retainCount 減1了,因為不管減不減1,這個對象的內(nèi)存都是要被回收的,他所在的內(nèi)存區(qū)域,包括 retainCount 也是沒有意義的了。不將 retainCount 從1減到0,這樣會減少一次內(nèi)存操作,加速對對象的回收。

具體代碼演示可以見下方總結(jié)一下之記住。

dealloc

找到對象內(nèi)存地址,直接 free()。

總結(jié)一下:

  • 在 Objective-C 的對象中存有引用計數(shù)這一整數(shù)值
  • 調(diào)用 alloc 或是 retain 方法后,引用計數(shù)值加1
  • 調(diào)用 release 方法后,引用計數(shù)值減1
  • 引用計數(shù)值為0時,調(diào)用 dealloc 方法廢棄對象

記?。寒?dāng)對象被釋放掉的時候,該對象之前所占有的內(nèi)存已經(jīng)被收回。但是被收回的內(nèi)存不一定馬上被復(fù)用(暫時沒有被復(fù)用的對象,成為了懸掛指針)。所以有時候我們向已經(jīng)釋放的對象發(fā)送消息,會收到不在預(yù)期中的效果。如果收回的對象內(nèi)存被復(fù)用,則程序會崩潰。故不應(yīng)該向已經(jīng)釋放的對象發(fā)送消息,會得到無法預(yù)期的結(jié)果。

-(void)testARC
{
    id objAlloc = [[NSObject alloc]init];
    NSLog(@"allco生成并自我持有:%lu",[objAlloc retainCount]);
    [objAlloc release];
    NSLog(@"allco生成并自我持有 release:%lu",[objAlloc retainCount]);//不應(yīng)該向釋放的對象發(fā)送消息,已經(jīng)被回收的內(nèi)存無法預(yù)期其是否已經(jīng)被復(fù)用
 }

2016-09-21 09:50:57.796 ARCByCrudherWu[1325:32457] allco生成并自我持有:1
2016-09-21 09:50:57.797 ARCByCrudherWu[1325:32457] allco生成并自我持有 release:1

簡述蘋果實現(xiàn)

retainCount / retain / release

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;  
  }  
} 

蘋果根據(jù)不同操作去調(diào)用不同的函數(shù)實現(xiàn)。從該函數(shù)中可以看出,蘋果大概采用的是散列表(引用計數(shù)表)的形式。CNUstep 將引用計數(shù)保存在對象占用內(nèi)存塊的頭部,而蘋果是保存在引用計數(shù)表的記錄中。

通過內(nèi)存塊頭部管理引用計數(shù)的好處:

  • 少量的代碼就可以實現(xiàn)
  • 能夠一起管理引用計數(shù)的內(nèi)存塊和對象的內(nèi)存塊

通過散列表管理引用計數(shù)的好處:

  • 對象的內(nèi)存塊不用再考慮內(nèi)存塊頭部
  • 引用計數(shù)表記錄著各內(nèi)存塊的地址,可以從各個記錄追溯到各個對象的內(nèi)存塊

autorelease

顧名思義就是自動釋放,相當(dāng)于 C 語言的局部變量,當(dāng)局部變量超出作用域時,自動變量就被廢棄,不可再訪問。調(diào)用該方法,將對象注冊到 autorealeasePool 中,當(dāng) autorealeasePool 廢棄,會自動調(diào)用 release 辦法。

autorelease 的具體使用辦法:

  • 生成并持有 NSAutorealeasePool 對象
  • 調(diào)用已分配對象的 autorelease 方法
  • 廢棄 NSAutorealeasePool 對象(已分配對象會自動調(diào)用 release 方法)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
    
id obj = [[NSObject alloc]init];
    
[obj autorelease];
    
[pool drain];

另外,Cocoa 框架很多類方法用于返回 autorelease 的對象。比如 NSMutableArray 類的 arrayWithCapacity 類辦法。

id array = [NSMutableArray arrayWithCapacity:2];

此源碼相當(dāng)于如下代碼:

id array = [ [NSMutableArray arrayWithCapacity:2] autorelease];

autorelease 實現(xiàn)

autorelease 的實現(xiàn)其實就是將對象加入 NSAutoreleasePool 中。

-(id)autorelease
{
    [NSAutoreleasePool addObject:self];
}//該方法的實現(xiàn),其實是使用一種特殊的方式來實現(xiàn),即是使用了函數(shù)指針的調(diào)用方式,因為該辦法的調(diào)用比較頻繁。(具體可以學(xué)習(xí) Runtime 方法之直接調(diào)用函數(shù)地址)

-(void)addObject :(id)anObject
{
    [array addObject:anObject];
}

-(void)drain
{
    [self dealloc];
}

-(void)dealloc
{
    [self emptyPool];
    [array release];
}

-(void)emptyPool
{
    for (id obj in array) {
        [obj release];
    }
}

ARC 規(guī)則

概要:實際上“引用計數(shù)式內(nèi)存管理方式”的本質(zhì)部分在 ARC 中并沒有改變的。ARC 只是自動地幫我們管理內(nèi)存了,其管理方式就是引用計數(shù)的方式。

所有權(quán)修飾符

OC 中的對象類型和 id 類型的變量,必須要附加所有權(quán)修飾符。類型有如下四中:

  • __strong
  • __weak
  • ______unsafe __unretained
  • __autoreleasing
__strong 修飾符

在 OC 中,對象類型和 id 類型的變量默認(rèn)是附加上了 __strong 修飾符的。

__strong 修飾符之"自己生成并持有的對象"

//ARC 有效
-(void)testARC
{
    /**
     自己生成并持有對象 --> [[NSObject alloc]init]
     */
    id __strong obj = [[NSObject alloc]init];
    
    /**
     *  __strong 修飾符,為強引用,變量 obj 持有對象
     */
}
/**
 *  局部變量 obj 超出了作用域,所有強引用失效
 *  所以自動地釋放自己持有的對象
 *  對象的所有者不存在,因此廢棄該對象
 */

__strong 修飾符之"非自己生成的對象,自己也能持有"

-(void)testARC
{
    id __strong obj0 = [[NSObject alloc]init];//對象A
    /**
        obj0 持有對象A的強引用
     */
    id __strong obj1 = [[NSObject alloc]init];//對象B
    /**
     *  obj1 持有對象B的強引用
     */
    id __strong obj2 = nil;
    /**
     *  obj2 不持有任何對象
     */
    
    obj0 = obj1;
    /**
     *  obj0 被持有對象B的 obj1 賦值,所以 obj0 持有對象B
        因為 obj0 被賦值,所以原來持有對象A的強引用失效
        對象A不再被任何對象持有,因此廢棄對象A
     
        此時,持有對象的B的強引用變量為:obj1 obj0
     */
    
    obj2 = obj0;
    /**
     *  obj2 被持有對象B的 obj0 賦值,所以 obj2 持有對象B
     
        此時,持有對象的B的強引用變量為:obj1 obj0 obj2
     */
    
    obj1 = nil;
    /**
     *  因為 obj1 被賦值為 nil,所以持有對象B的強引用失效
     
        此時,持有對象的B的強引用變量為:obj2 obj0
     */
    
    obj0 = nil;
    /**
     *  因為 obj0 被賦值為 nil,所以持有對象B的強引用失效
     
        此時,持有對象的B的強引用變量為:obj2
     */
    
    obj2 = nil;
    /**
     *  因為 obj2 被賦值為 nil,所以持有對象B的強引用失效
     
        此時,沒有變量持有對象的B的強引用,所以對象B被廢棄
     */
}

__strong 修飾符之"成員變量"

@interface YYTestObject : NSObject
{
    id __strong _obj;
}

-(void)setObj:(id __strong)obj;

@end

-------------------------------------------------------------

#import "YYTestObject.h"

@implementation YYTestObject

-(void)setObj:(id)obj
{
    _obj = obj;
}
@end

-------------------------------------------------------------

-(void)testARC
{
    id __strong test = [[YYTestObject alloc]init];
    
    [test setObj:[[NSObject alloc]init]];
}

/**
 *  self -(1)-> test -(2)-> _obj -(3)-> NSObject
 */
//當(dāng) test 超出作用域后,強引用失效,之后的鏈接也跟著失效。
__weak 修飾符

通過上面對 __strong 修飾符的例子來看,貌似只要有__strong就可以很好地實現(xiàn)內(nèi)存的管理,但是實際上不是這樣的。__strong 修飾符 不很解決很嚴(yán)重的內(nèi)存泄露問題 —循環(huán)引用。

@interface YYTestObject : NSObject
{
    id __strong _obj;
}

-(void)setObj:(id __strong)obj;

@end

-------------------------------------------------------------

#import "YYTestObject.h"

@implementation YYTestObject

-(void)setObj:(id)obj
{
    _obj = obj;
}
@end

-------------------------------------------------------------

-(void)testARC
{
   id obj = [[YYTestObject alloc]init];//對象A
    [obj setObj:obj];
}
//obj 超出作用域,對對象A強引用失效,此時對象A的強引用變量有:obj的成員變量_obj

通過Xcode自帶的 instrument 檢測循環(huán)引用如下:

循環(huán)引用.png

怎樣才能消除循環(huán)引用呢?這時候應(yīng)該引入__weak 修飾符了。

__weak 修飾符不持有對象實例,提供弱引用。
id __weak obj = [[YYTestObject alloc]init];//對象A

//此源碼編譯器會發(fā)出警告。因為,為了不持有自己生成并持有的對象,生成的對象會立即被釋放

id obj = [[YYTestObject alloc]init];//對象A
id __weak obj2 = obj;

-----------------------------------------------------------
  
 -(void)testARC
{
   id obj = [[YYTestObject alloc]init];//對象A
   id __weak obj2 = obj;
    [obj setObj:obj2];
}
//因為 obj 超出作用域,所以對對象A的強引用失效
//對象A沒有被持有,因此廢棄對象A
//obj 無持有者,因此也廢棄對象 obj
//此時,持有該對象 obj 弱引用的 obj2 變量的弱引用失效,nil 賦值給 obj2,使 obj2 沒有成為懸掛指針
______unsafe __unretained

______weak 修飾符只是用于 iOS5 以上及 OS X Lion 以上的版本的程序。因此不在該范圍的程序就使用 ____unsafe __unretained 來修飾。附有該修飾符的變量不屬于編譯器的內(nèi)存管理對象,所以要注意賦值對象的所有者,否則會造成內(nèi)存泄露或是程序崩潰。

id __unsafe_unretained obj1 = nil;
    
    {
        id __strong obj0 = [[NSObject alloc]init];//對象A
        
        obj1 = obj0;
        
        NSLog(@"A: %@",obj1);
    }
    //因為 obj0 超出作用域,所以對對象A的強引用失效
    //對象A沒有被持有,因此廢棄對象A
    //obj0 無持有者,因此也廢棄對象 obj
    //此時,持有該對象 obj 弱引用的 obj1 變量的弱引用失效
    NSLog(@"B: %@",obj1);
    //輸出 obj1 變量表示的對象
    //obj1 變量表示的對象已經(jīng)被廢棄(懸掛指針),錯誤訪問

PS:如果 NSLog(@"B: %@",obj1); 訪問成功的話,那只是碰巧而已,該區(qū)域的內(nèi)存還沒有被復(fù)用。這也說明了__unsafe __unretained 修飾的變量不會被賦值 nil ,以防止懸掛指針,所以訪問者類型的變量,一定要確保對象是否存在,否則會導(dǎo)致程序崩潰。
__autoreleasing

在 ARC 有效的情況下,是不能使用 autorelease 方法,也不能使用 NSAutoreleasePool 類。這樣一來,autorelease 無法直接調(diào)用,但實際上,ARC 有效的情況下,autorelease 方法是起到作用的。

下面來看一下 __autoreleasing 顯示使用情況:

// 非ARC 
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
    
    id __strong obj = [[NSObject alloc]init];
    
    [obj autorelease];
    
    [pool drain];
}

-----------------------------------------------------------------

//ARC
{
  @autoreleasepool {
        id __autoreleasing obj = [[NSObject alloc]init];
        //__autoreleasing --> 調(diào)用 autorelease 方法 --> 意味著 obj 已經(jīng)注冊到 autoreleasepool 中
    }
}

值得注意的是,顯示地添加 ______autoreleasing 修飾符同顯示添加____strong 修飾符一樣少見。所以下面簡述一下其隱式的使用情況:

  • 取得非自己生成并持有的對象,即是除了 alloc / new / copy / mutableCopy 等方法取得對象,一般是類辦法。
  • 對象作為返回值
  • __weak 修飾的對象
  • id 的指針或是對象的指針

(1) 取得非自己生成并持有的對象

編譯器會檢查方法名是否以 alloc / new / copy / mutableCopy 開始,如果不是則自動將返回值的對象注冊到 autoreleasepool 中。

@autoreleasepool {
     //取得非自己生成并持有的對象 --> [NSArray array]
        id __strong *array = [NSArray array];
        //因為 array 為強引用,所以持有對象,
        //并且該對象有編譯器判斷其方法名后,自動注冊到 autoreleasepool 中
}
//對象 array 超出作用域,所以強引用失效,同時自動釋放自己所持有的對象
//同時,隨著 @autoreleasepool 快的結(jié)束,注冊到 autoreleasepool 中的所有對象被自動釋放。
//因為對象的不存在,所以廢棄對象

//持有關(guān)系:autoreleasepool --> array --> [NSArray array]

(2) 對象作為返回值

第(1)條中,不顯式地使用 __autoreleasing 也能使對象注冊到 autoreleasepool 中,下面我們來看下取得非自己生成并持有的對象時被調(diào)用方法的源碼代碼示例:

+(id) array
{
    //方案一
    return [[NSArray alloc]init];
    
    //方案二
    id obj = [[NSArray alloc]init];
    return obj;
}

//由于 return 超出作用域,所以該強引用對應(yīng)的自己持有的對象會被自動釋放,但該對象作為返回值,所以編譯器會自動將該對象注冊到 autoreleasepool 中。

(3) __weak 修飾的對象

______weak 修飾的對象是對某對象持有弱引用,如果要訪問某對象的時候,該對象有可能已經(jīng)被廢棄了,這樣會導(dǎo)致程序崩潰。因此,______weak 修飾的變量必定要注冊到 autoreleasepool 中,在 @autoreleasepool 快結(jié)束之前,都能確保該對象的存在。

id __weak obj1 = obj0;
NSLog(@"obj: %@",obj1);

      相等于
       ||
       ||
        
id __weak obj1 = obj0;
id __autorealeasing tmp = obj1;
NSLog(@"obj: %@",tmp);

(4) id 的指針或是對象的指針

對象的指針,即是獲得對象的地址。形式如:NSObject **obj 。

同前面講述的 id obj 和 id ______strong obj 完全一樣。那么 id 的指針 id *obj 又如何?可以由 id ______strong obj 的例子類推出 id ______strong *obj 嗎?其實推出來的是 id __autorealeasing *obj。

同樣的 NSObject **obj 便成為了 ,NSObject * __autorealeasing *obj。

在開發(fā)中,我們常常需要得到詳細的錯誤信息,經(jīng)常會在方法的參數(shù)中傳遞 NSError 對象的指針,而不是函數(shù)返回值。

NSError *error = nil;
BOOL result = [myObject performOperationWithError:&error];

//方法的聲明
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;

//根據(jù)前面所講述的,除了 alloc / new / copy / mutableCopy 辦法返回值取得的是對象是自己生成并持有的,其他的方法都是非自己生成并持有的。所以 performOperationWithError 辦法需要獲取的參數(shù) error 對象,都會注冊到 autoreleasepool,并取得非自己生成并持有的對象。
值得一提的是:賦值給對象指針的所有權(quán)修飾符必須一致。

(?)NSError *error = nil;
    NSError **pError = &error;
    
(?)NSError *error = nil;
    NSError * __strong *pError = &error;
    
    那么問題來了,上面?zhèn)€的例子中:源碼如下
    NSError *error = nil;
    BOOL result = [myObject performOperationWithError:&error];

    //方法的聲明
    -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
    
    傳遞給 performOperationWithError 辦法的參數(shù) error 的修飾符是 __strong,而辦法接受值是由 __autoreleasing修飾的,為什么編譯沒有報錯呢?
    其實實際上編譯器已經(jīng)將傳遞參數(shù)的那部分源碼轉(zhuǎn)化成如下的形式了:
    NSError * __strong error;
    NSError * __autoreleasing tmp = error;
    BOOL result = [myObject performOperationWithError:&tmp];
    error = tmp;

具體也可以參考蘋果的官方文檔:蘋果文檔

最后,如果想打印 autoreleasePool 中有哪些對象,不管是在 ARC 或是非 ARC 的情況下,都可以調(diào)用非公開的 _objc_autoreleasePoolPrint();

既然簡單地介紹了 ARC 一些簡單用法,下面就看看在 ARC 下,有哪些規(guī)則吧。

  • 不能使用 retain / release / retainCount / autorelease
  • 不能使用NSAllocateObject / NSDeallocateObject
  • 必須遵守內(nèi)存管理的方法命名
  • 不要顯示調(diào)用 dealloc
  • 使用 @autoreleasepool 塊代替 NSAutoreleasepool
  • 不能使用區(qū)域(NSZone)
  • 對象型變量不能作為C語言的結(jié)構(gòu)體(struct / union)的成員
  • 顯示轉(zhuǎn)換 “id” 和 “void *”

上面的一些規(guī)則中,挑幾條值得注意的來講述一下:

(1)必須遵守內(nèi)存管理的方法命名

init 開頭的方法,必定是要返回對象,對象應(yīng)該是 id 類型或是該方法聲明類的對象類型,或者是該類的超類或子類型。該返回的對象是不會注冊到 autoreleasepool 中的?;局皇菍?alloca 方法返回值的對象進行初始化并返回該對象的。

-(id)initWithObject;    (?)

-(void)initWithObject;  (?) --->返回值

-(id)initialize;        (?) --->應(yīng)是駝峰式命名

為了使能夠讓手動管理內(nèi)存和 ARC 管理內(nèi)存兩者之間互相操作,屬性名不能以 new 開頭命名,除非你重新命名該屬性的 getter 辦法。

// Won't work:
@property NSString *newTitle;
 
// Works:
@property (getter=theNewTitle) NSString *newTitle;

(2) 不要顯示調(diào)用 dealloc

當(dāng)對象廢棄的時候,都會調(diào)用該辦法的。在該辦法中大多數(shù)只使用刪除已注冊的代理和觀察者對象即可。

// 非 ARC
-(void)dealloc
{
    //do something
    
    [super dealloc];//一定要注意調(diào)用的順序
}

// ARC
-(void)dealloc
{
    //do something
   
   // [super dealloc];在 ARC 下,無需顯示調(diào)用該方法, ARC 會自動處理的。
}

(3) 對象型變量不能作為C語言的結(jié)構(gòu)體(struct / union)的成員

struct Data
{
    NSMutableArray *array;
};

如果結(jié)構(gòu)體中的成員變量出現(xiàn) Objective - C 對象,便會引起編譯錯誤。這是因為C語言的規(guī)約上沒有方法來管理結(jié)構(gòu)體成員的生存周期。C語言的局部變量可使用該變量的作用域來管理對象,而 ARC 吧內(nèi)存管理的工作分配給編譯器的。不過實在需要,也可以改成如下:

struct Data{

NSMutableArray ______unsafe____unretained *array;

};

(4) 顯示轉(zhuǎn)換 “id” 和 “void *”

在 ARC 下,我們有時候需要將 Core Foundation 對象轉(zhuǎn)換成一個 Objective-C 對象,這時候我們就應(yīng)該告訴編譯器,在轉(zhuǎn)換的過程中引用計數(shù)需要如何調(diào)整,這時候我們就應(yīng)該使用到與 bridge 有關(guān)的關(guān)鍵字了。一下是這些關(guān)鍵字的說明;

  • __bridge :只做類型轉(zhuǎn)換,不修改相關(guān)對象的引用計數(shù)。
  • ______bridge____retained:類型轉(zhuǎn)換后,相關(guān)對象的引用計數(shù)+1
  • ______bridge____transfer:類型轉(zhuǎn)換后,該對象的引用計數(shù)由 ARC 管理,被轉(zhuǎn)換的對象隨后釋放。
CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
id __strong obj = (__bridge id)cfArray;
//CFGetRetainCount(cfArray); --->2

---------------------------------------------------------------
  
id obj = [[NSObject alloc]init];
CFMutableArrayRef cfArray = (__bridge__retained CFMutableArrayRef)obj;
//CFGetRetainCount(cfArray); --->2

---------------------------------------------------------------

CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
id __strong obj = (__bridge__transfer id)cfArray;
//CFGetRetainCount(cfArray); --->1
轉(zhuǎn)換成非 ARC
CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
id obj = (id)cfArray;
[obj retain];  
[(id)cfArray release];
注意:非 ARC id 變量強轉(zhuǎn)換成 void* 變量是并不會出問題。

ARC 的實現(xiàn)

蘋果官方說明中稱,ARC 是“由編譯器進行內(nèi)存管理”的,但是實際上只有編譯器是無法完勝的,需要借助運行庫的協(xié)助。

__strong
{
    id __strong obj = [[NSObject alloc]init];
}
以上的代碼用編譯器的模擬代碼如下:
  id obj = objc_msgSend(NSObject,@selector(alloc));
  objc_msgSend(obj,@selector(init));
  objc_release(obj);
{
  id __strong obj = [NSArray array];
}
以上的代碼用編譯器的模擬代碼如下:
id obj = objc_msgSend(NSArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
+(id)array
{
    return [[NSMutableArray alloc]init];
}
以上的代碼用編譯器的模擬代碼如下:
+(id)array
{
    id obj = objc_msgSend(NSMutableArray,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    return objc_autoreleasedReturnValue(obj);
}

objc_autoreleasedReturnValue 函數(shù)將對象注冊到 autoreleasePool 中,與 autorelease 函數(shù)是有著不同之處的。objc_autoreleasedReturnValue 會檢查使用該函數(shù)的方法或函數(shù)調(diào)用方的執(zhí)行命令列表,如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著調(diào)用 objc_retainAutoreleasedReturnValue 函數(shù),那么就不會將對象注冊到 autoreleasePool 中,而是直接傳遞到方法或是函數(shù)調(diào)用方。objc_retainAutoreleasedReturnValue 與 objc_retain 函數(shù)不同,它即便不注冊到 autoreleasePool 中,也是能夠正確獲取對象的。這樣一來,就少了對象注冊到 autoreleasePool 中而直接傳遞了。

__weak
{
   id __weak obj1 = obj;
   NSLog(@"obj = %@",obj);
}

//以上的代碼用編譯器的模擬代碼如下: 
id obj1;
objc_initWeak(&obj1,obj);// --->轉(zhuǎn)化成:將有 __weak 修飾的變量初始化為0后,即是 id obj1 = 0,objc_storeWeak(&objc1, obj);

id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"obj = %@",tmp);

objc_destroyWeak(&obj1); // --->轉(zhuǎn)化成:objc_storeWeak(&objc1, 0);
    
//objc_storeWeak 函數(shù)把第二參數(shù)的賦值對象的地址為鍵值,將第一參數(shù)的附有 __weak 修飾符的變量的地址注冊到 weak 表中。如果第二個參數(shù)為0,則把變量從 weak 表中刪除。

問題1:weak 表的作用?

  • 當(dāng)對象被廢棄了,在把 __weak 的變量從 weak 表中刪除時,將變量的地址,賦值為 nil;

問題2:使用 __weak 修飾的變量,是從 weak 表中還是從 autoreleasePool 中獲取對象?

  • 在使用 __weak 的變量時,一方我們可以通過 weak 表獲取變量的地址而找到對應(yīng)的對象,一方也可以從注冊在 autoreleasePool 中去獲取,但從模擬編譯器的代碼 NSLog(@"obj = %@",tmp); 可以看出,應(yīng)該是從后者去獲取到對象。

注意: ______weak 修飾的變量被每一使用一次,所賦值的對象都會被注冊到 ______autoreleasePool 中一次,所以為了減少對內(nèi)存的使用,將附有 ______weak 修飾的變量賦值給附有 ______strong 修飾的變量后在使用可以避免此類問題。在 block 中使用也是該這樣使用的。

__autoreleasing
@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_autoreleasePoolPop(pool);
@autoreleasepool {
        id __autoreleasing obj = [NSArray array];
    }
 //以上的代碼用編譯器的模擬代碼如下:   
 id pool = objc_autoreleasePoolPush();
 id obj = objc_msgSend(NSArray,@selector(array));
 objc_retainAutoreleasedReturnValue(obj);
 objc_autorelease(obj);
 objc_autoreleasePoolPop(pool);

尾巴:通過介紹引用計數(shù)式的內(nèi)存管理方式如何使用,到它們是如何實現(xiàn),再到如何利用引用計數(shù)式的內(nèi)存管理方式去實現(xiàn) ARC。

參考資料:

Objective-C 高級編程

蘋果文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容