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

.png

前言

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

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

1.自己生成的對(duì)象裹纳,自己所持有

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

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

eg:

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

2.非自己生成的對(duì)象阻星,自己也能持有

retain 方法

3.不在需要自己持有的對(duì)象時(shí)釋放

release 方法 .如果釋放不是自己持有的對(duì)象朋鞍,則程序會(huì)崩潰。

4.非自己持有的對(duì)象無法釋放

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

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

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

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

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

alloc:

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

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

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

retain

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

release

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

值得一提的是:

當(dāng)對(duì)象在執(zhí)行最后一次 release 時(shí),系統(tǒng)知道馬上要回收該對(duì)象了,所以并不會(huì)對(duì)對(duì)象進(jìn)行 retainCount 減1了抱冷,因?yàn)椴还軠p不減1彤蔽,這個(gè)對(duì)象的內(nèi)存都是要被回收的让禀,他所在的內(nèi)存區(qū)域挑社,包括 retainCount 也是沒有意義的了。不將 retainCount 從1減到0巡揍,這樣會(huì)減少一次內(nèi)存操作痛阻,加速對(duì)對(duì)象的回收。

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

dealloc

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

總結(jié)一下:

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

記灼《贰:當(dāng)對(duì)象被釋放掉的時(shí)候,該對(duì)象之前所占有的內(nèi)存已經(jīng)被收回赁咙。但是被收回的內(nèi)存不一定馬上被復(fù)用(暫時(shí)沒有被復(fù)用的對(duì)象钮莲,成為了懸掛指針)。所以有時(shí)候我們向已經(jīng)釋放的對(duì)象發(fā)送消息彼水,會(huì)收到不在預(yù)期中的效果崔拥。如果收回的對(duì)象內(nèi)存被復(fù)用,則程序會(huì)崩潰凤覆。故不應(yīng)該向已經(jīng)釋放的對(duì)象發(fā)送消息链瓦,會(huì)得到無法預(yù)期的結(jié)果。

-(void)testARC
{
    id objAlloc = [[NSObject alloc]init];
    NSLog(@"allco生成并自我持有:%lu",[objAlloc retainCount]);
    [objAlloc release];
    NSLog(@"allco生成并自我持有 release:%lu",[objAlloc retainCount]);//不應(yīng)該向釋放的對(duì)象發(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

簡(jiǎn)述蘋果實(shí)現(xiàn)

retainCount / retain / release

int _CFDoExternRefOperation(uintptr_t op, id obj)  
{  
  CFBasicHashRef table = 取得對(duì)象的散列表(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ù)實(shí)現(xiàn)慈俯。從該函數(shù)中可以看出,蘋果大概采用的是散列表(引用計(jì)數(shù)表)的形式拥峦。CNUstep 將引用計(jì)數(shù)保存在對(duì)象占用內(nèi)存塊的頭部贴膘,而蘋果是保存在引用計(jì)數(shù)表的記錄中。

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

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

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

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

autorelease

顧名思義就是自動(dòng)釋放刑峡,相當(dāng)于 C 語言的局部變量,當(dāng)局部變量超出作用域時(shí)玄柠,自動(dòng)變量就被廢棄突梦,不可再訪問。調(diào)用該方法羽利,將對(duì)象注冊(cè)到 autorealeasePool 中宫患,當(dāng) autorealeasePool 廢棄,會(huì)自動(dòng)調(diào)用 release 辦法这弧。

autorelease 的具體使用辦法:

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

另外撮奏,Cocoa 框架很多類方法用于返回 autorelease 的對(duì)象俏讹。比如 NSMutableArray 類的 arrayWithCapacity 類辦法。

id array = [NSMutableArray arrayWithCapacity:2];

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

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

autorelease 實(shí)現(xiàn)

autorelease 的實(shí)現(xiàn)其實(shí)就是將對(duì)象加入 NSAutoreleasePool 中畜吊。

-(id)autorelease
{
    [NSAutoreleasePool addObject:self];
}//該方法的實(shí)現(xiàn)泽疆,其實(shí)是使用一種特殊的方式來實(shí)現(xiàn),即是使用了函數(shù)指針的調(diào)用方式玲献,因?yàn)樵撧k法的調(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í)際上“引用計(jì)數(shù)式內(nèi)存管理方式”的本質(zhì)部分在 ARC 中并沒有改變的。ARC 只是自動(dòng)地幫我們管理內(nèi)存了捌年,其管理方式就是引用計(jì)數(shù)的方式瓢娜。

所有權(quán)修飾符

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

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

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

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

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

__strong 修飾符之"非自己生成的對(duì)象谷丸,自己也能持有"

-(void)testARC
{
    id __strong obj0 = [[NSObject alloc]init];//對(duì)象A
    /**
        obj0 持有對(duì)象A的強(qiáng)引用
     */
    id __strong obj1 = [[NSObject alloc]init];//對(duì)象B
    /**
     *  obj1 持有對(duì)象B的強(qiáng)引用
     */
    id __strong obj2 = nil;
    /**
     *  obj2 不持有任何對(duì)象
     */
    
    obj0 = obj1;
    /**
     *  obj0 被持有對(duì)象B的 obj1 賦值,所以 obj0 持有對(duì)象B
        因?yàn)?obj0 被賦值应结,所以原來持有對(duì)象A的強(qiáng)引用失效
        對(duì)象A不再被任何對(duì)象持有刨疼,因此廢棄對(duì)象A
     
        此時(shí),持有對(duì)象的B的強(qiáng)引用變量為:obj1 obj0
     */
    
    obj2 = obj0;
    /**
     *  obj2 被持有對(duì)象B的 obj0 賦值鹅龄,所以 obj2 持有對(duì)象B
     
        此時(shí)揩慕,持有對(duì)象的B的強(qiáng)引用變量為:obj1 obj0 obj2
     */
    
    obj1 = nil;
    /**
     *  因?yàn)?obj1 被賦值為 nil,所以持有對(duì)象B的強(qiáng)引用失效
     
        此時(shí)扮休,持有對(duì)象的B的強(qiáng)引用變量為:obj2 obj0
     */
    
    obj0 = nil;
    /**
     *  因?yàn)?obj0 被賦值為 nil漩绵,所以持有對(duì)象B的強(qiáng)引用失效
     
        此時(shí),持有對(duì)象的B的強(qiáng)引用變量為:obj2
     */
    
    obj2 = nil;
    /**
     *  因?yàn)?obj2 被賦值為 nil肛炮,所以持有對(duì)象B的強(qiáng)引用失效
     
        此時(shí)止吐,沒有變量持有對(duì)象的B的強(qiáng)引用,所以對(duì)象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 超出作用域后侨糟,強(qiáng)引用失效碍扔,之后的鏈接也跟著失效。
__weak 修飾符

通過上面對(duì) __strong 修飾符的例子來看秕重,貌似只要有__strong就可以很好地實(shí)現(xiàn)內(nèi)存的管理不同,但是實(shí)際上不是這樣的。__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];//對(duì)象A
    [obj setObj:obj];
}
//obj 超出作用域二拐,對(duì)對(duì)象A強(qiáng)引用失效服鹅,此時(shí)對(duì)象A的強(qiáng)引用變量有:obj的成員變量_obj

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

循環(huán)引用.png

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

__weak 修飾符不持有對(duì)象實(shí)例企软,提供弱引用。
id __weak obj = [[YYTestObject alloc]init];//對(duì)象A

//此源碼編譯器會(huì)發(fā)出警告饭望。因?yàn)檎躺冢瑸榱瞬怀钟凶约荷刹⒊钟械膶?duì)象,生成的對(duì)象會(huì)立即被釋放

id obj = [[YYTestObject alloc]init];//對(duì)象A
id __weak obj2 = obj;

-----------------------------------------------------------
  
 -(void)testARC
{
   id obj = [[YYTestObject alloc]init];//對(duì)象A
   id __weak obj2 = obj;
    [obj setObj:obj2];
}
//因?yàn)?obj 超出作用域铅辞,所以對(duì)對(duì)象A的強(qiáng)引用失效
//對(duì)象A沒有被持有厌漂,因此廢棄對(duì)象A
//obj 無持有者,因此也廢棄對(duì)象 obj
//此時(shí)斟珊,持有該對(duì)象 obj 弱引用的 obj2 變量的弱引用失效苇倡,nil 賦值給 obj2,使 obj2 沒有成為懸掛指針
______unsafe __unretained

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

id __unsafe_unretained obj1 = nil;
    
    {
        id __strong obj0 = [[NSObject alloc]init];//對(duì)象A
        
        obj1 = obj0;
        
        NSLog(@"A: %@",obj1);
    }
    //因?yàn)?obj0 超出作用域辞州,所以對(duì)對(duì)象A的強(qiáng)引用失效
    //對(duì)象A沒有被持有怔锌,因此廢棄對(duì)象A
    //obj0 無持有者,因此也廢棄對(duì)象 obj
    //此時(shí)变过,持有該對(duì)象 obj 弱引用的 obj1 變量的弱引用失效
    NSLog(@"B: %@",obj1);
    //輸出 obj1 變量表示的對(duì)象
    //obj1 變量表示的對(duì)象已經(jīng)被廢棄(懸掛指針)埃元,錯(cuò)誤訪問

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

在 ARC 有效的情況下遗锣,是不能使用 autorelease 方法,也不能使用 NSAutoreleasePool 類嗤形。這樣一來精偿,autorelease 無法直接調(diào)用,但實(shí)際上,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)注冊(cè)到 autoreleasepool 中
    }
}

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

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

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

編譯器會(huì)檢查方法名是否以 alloc / new / copy / mutableCopy 開始勿锅,如果不是則自動(dòng)將返回值的對(duì)象注冊(cè)到 autoreleasepool 中。

@autoreleasepool {
     //取得非自己生成并持有的對(duì)象 --> [NSArray array]
        id __strong *array = [NSArray array];
        //因?yàn)?array 為強(qiáng)引用枣氧,所以持有對(duì)象溢十,
        //并且該對(duì)象有編譯器判斷其方法名后,自動(dòng)注冊(cè)到 autoreleasepool 中
}
//對(duì)象 array 超出作用域达吞,所以強(qiáng)引用失效张弛,同時(shí)自動(dòng)釋放自己所持有的對(duì)象
//同時(shí),隨著 @autoreleasepool 快的結(jié)束酪劫,注冊(cè)到 autoreleasepool 中的所有對(duì)象被自動(dòng)釋放吞鸭。
//因?yàn)閷?duì)象的不存在,所以廢棄對(duì)象

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

(2) 對(duì)象作為返回值

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

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

//由于 return 超出作用域,所以該強(qiáng)引用對(duì)應(yīng)的自己持有的對(duì)象會(huì)被自動(dòng)釋放滩字,但該對(duì)象作為返回值造虏,所以編譯器會(huì)自動(dòng)將該對(duì)象注冊(cè)到 autoreleasepool 中。

(3) __weak 修飾的對(duì)象

______weak 修飾的對(duì)象是對(duì)某對(duì)象持有弱引用麦箍,如果要訪問某對(duì)象的時(shí)候漓藕,該對(duì)象有可能已經(jīng)被廢棄了,這樣會(huì)導(dǎo)致程序崩潰挟裂。因此享钞,______weak 修飾的變量必定要注冊(cè)到 autoreleasepool 中,在 @autoreleasepool 快結(jié)束之前诀蓉,都能確保該對(duì)象的存在栗竖。

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

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

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

對(duì)象的指針,即是獲得對(duì)象的地址渠啤。形式如:NSObject **obj 狐肢。

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

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

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

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

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

//根據(jù)前面所講述的,除了 alloc / new / copy / mutableCopy 辦法返回值取得的是對(duì)象是自己生成并持有的丧没,其他的方法都是非自己生成并持有的鹰椒。所以 performOperationWithError 辦法需要獲取的參數(shù) error 對(duì)象,都會(huì)注冊(cè)到 autoreleasepool呕童,并取得非自己生成并持有的對(duì)象漆际。
值得一提的是:賦值給對(duì)象指針的所有權(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修飾的奸汇,為什么編譯沒有報(bào)錯(cuò)呢?
    其實(shí)實(shí)際上編譯器已經(jīng)將傳遞參數(shù)的那部分源碼轉(zhuǎn)化成如下的形式了:
    NSError * __strong error;
    NSError * __autoreleasing tmp = error;
    BOOL result = [myObject performOperationWithError:&tmp];
    error = tmp;

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

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

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

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

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

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

init 開頭的方法,必定是要返回對(duì)象且警,對(duì)象應(yīng)該是 id 類型或是該方法聲明類的對(duì)象類型粉捻,或者是該類的超類或子類型礁遣。該返回的對(duì)象是不會(huì)注冊(cè)到 autoreleasepool 中的斑芜。基本只是對(duì) alloca 方法返回值的對(duì)象進(jìn)行初始化并返回該對(duì)象的祟霍。

-(id)initWithObject;    (?)

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

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

為了使能夠讓手動(dò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)對(duì)象廢棄的時(shí)候醇王,都會(huì)調(diào)用該辦法的。在該辦法中大多數(shù)只使用刪除已注冊(cè)的代理和觀察者對(duì)象即可崭添。

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

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

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

struct Data
{
    NSMutableArray *array;
};

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

struct Data{

NSMutableArray ______unsafe____unretained *array;

};

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

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

  • __bridge :只做類型轉(zhuǎn)換,不修改相關(guān)對(duì)象的引用計(jì)數(shù)鲁僚。
  • ______bridge____retained:類型轉(zhuǎn)換后扰魂,相關(guān)對(duì)象的引用計(jì)數(shù)+1
  • ______bridge____transfer:類型轉(zhuǎn)換后,該對(duì)象的引用計(jì)數(shù)由 ARC 管理蕴茴,被轉(zhuǎn)換的對(duì)象隨后釋放劝评。
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 變量強(qiáng)轉(zhuǎn)換成 void* 變量是并不會(huì)出問題。

ARC 的實(shí)現(xiàn)

蘋果官方說明中稱倦淀,ARC 是“由編譯器進(jìn)行內(nèi)存管理”的蒋畜,但是實(shí)際上只有編譯器是無法完勝的,需要借助運(yùn)行庫的協(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ù)將對(duì)象注冊(cè)到 autoreleasePool 中姻成,與 autorelease 函數(shù)是有著不同之處的。objc_autoreleasedReturnValue 會(huì)檢查使用該函數(shù)的方法或函數(shù)調(diào)用方的執(zhí)行命令列表愿棋,如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著調(diào)用 objc_retainAutoreleasedReturnValue 函數(shù)科展,那么就不會(huì)將對(duì)象注冊(cè)到 autoreleasePool 中,而是直接傳遞到方法或是函數(shù)調(diào)用方糠雨。objc_retainAutoreleasedReturnValue 與 objc_retain 函數(shù)不同才睹,它即便不注冊(cè)到 autoreleasePool 中,也是能夠正確獲取對(duì)象的甘邀。這樣一來琅攘,就少了對(duì)象注冊(cè)到 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ù)的賦值對(duì)象的地址為鍵值,將第一參數(shù)的附有 __weak 修飾符的變量的地址注冊(cè)到 weak 表中逗抑。如果第二個(gè)參數(shù)為0剧辐,則把變量從 weak 表中刪除寒亥。

問題1:weak 表的作用?

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

問題2:使用 __weak 修飾的變量羞酗,是從 weak 表中還是從 autoreleasePool 中獲取對(duì)象腐宋?

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

注意: ______weak 修飾的變量被每一使用一次卫枝,所賦值的對(duì)象都會(huì)被注冊(cè)到 ______autoreleasePool 中一次,所以為了減少對(duì)內(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);

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

參考資料:

Objective-C 高級(jí)編程

蘋果文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怜奖,一起剝皮案震驚了整個(gè)濱河市浑测,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歪玲,老刑警劉巖迁央,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異滥崩,居然都是意外死亡岖圈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門钙皮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜂科,“玉大人,你說我怎么就攤上這事株灸〕缟悖” “怎么了擎值?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵慌烧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鸠儿,道長(zhǎng)屹蚊,這世上最難降的妖魔是什么厕氨? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮汹粤,結(jié)果婚禮上命斧,老公的妹妹穿的比我還像新娘。我一直安慰自己嘱兼,他們只是感情好国葬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芹壕,像睡著了一般汇四。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上踢涌,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天通孽,我揣著相機(jī)與錄音,去河邊找鬼睁壁。 笑死背苦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的潘明。 我是一名探鬼主播行剂,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼钳降!你這毒婦竟也來了硼讽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤牲阁,失蹤者是張志新(化名)和其女友劉穎固阁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體城菊,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡备燃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凌唬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片并齐。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖客税,靈堂內(nèi)的尸體忽然破棺而出况褪,到底是詐尸還是另有隱情,我是刑警寧澤更耻,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布测垛,位于F島的核電站,受9級(jí)特大地震影響秧均,放射性物質(zhì)發(fā)生泄漏食侮。R本人自食惡果不足惜号涯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锯七。 院中可真熱鬧链快,春花似錦、人聲如沸眉尸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽噪猾。三九已至地消,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畏妖,已是汗流浹背脉执。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戒劫,地道東北人半夷。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像迅细,于是被迫代替她去往敵國(guó)和親巫橄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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