Objective-C 內存管理之ARC規則

基本概念

ARC爲自動引用計數,引用計數式內存管理的本質並無改變,ARC只是自動處理「引用計數」的相關部分。安全

在編譯上,能夠設置ARC有效或無效。默認工程中ARC有效,若設置無效,則指定編譯器屬性爲-fno-objc-arcbash


內存管理思考方式

同引用計數相同框架

  • 本身生成的對象,本身持有
  • 非本身生成的對象,本身也能持有
  • 本身持有的對象不須要時釋放
  • 非本身持有的對象沒法釋放

全部權修飾符

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__stong修飾符

__strong修飾符是id類型和對象類型默認的全部權修飾符。 也就是說,如下代碼中的id變量實際上都被家了全部權修飾符
函數

id __strong obj = [[NSObject alloc] init];ui

在ARC無效時,寫法雖然同ARC有效時一致,但spa

{
    id __strong obj = [[NSObject alloc] init];
}
複製代碼

以上代碼明確指定了變量的做用域。代理

ARC無效時,該源碼可記述以下:指針

/** ARC無效 */
{
    id obj =[[NSObject alloc] init];
    [obj release];
}
複製代碼

爲了釋放生成並持有的對象,增長調用了release方法的代碼。該源代碼進行的動做跟ARC有效時的動做徹底同樣。code

如上所示,附有__strong修飾符的變量obj在超出其變量做用域是,即在該變量被廢棄是,會釋放其被賦予的對象。對象

__strong修飾符表示對對象的「強引用」。持有強你用的變量在超出其做用域時被廢棄,雖則會強引用的失效,引用的對象會隨之釋放。

/**本身生成並持有對象*/
{
    id __strong obj = [[NSObject alloc] init];
    /**由於變量obj爲強引用,因此本身持有對象*/
}
/*
 *由於變量obj超出其做用域,強引用失效,因此自動釋放本身持有的對象。
 *對象的全部者不存在,由於廢棄該對象。
 */
複製代碼

取得非本身生成並持有的對象時,代碼以下:

/**取得非本身生成並持有的對象*/
{
    id __strong obj = [NSMutableArray array];
    /**由於變量obj爲強引用,因此本身持有對象*/
}
/**由於變量obj超出其做用範圍,強引用失效,因此自動地釋放本身持有的對象*/
複製代碼

變量的賦值以下:

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持有由obj1賦值的對象B的強引用
 *由於obj0被賦值,因此原先持有對象A的強引用失效。
 *對象A的全部者不存在,由於廢棄對象A.
 *此時,持有對象B的強引用的變量爲obj0和obj1
 */
 
obj2 = obj0;
/*
 *obj2持有由obj0賦值的對象B的強引用
 *此時,持有對象B的強引用的變量爲obj0,obj1和obj2;
 */
 
obj1 = nil;
/*
 *由於nil被賦予了obj1,因此對對象B的強引用失效。
 *此時,持有對象B的強引用的變量爲obj0和obj2;
 */
 
obj0 = nil;
/*
 *由於nil被賦予了obj1,因此對對象B的強引用失效。
 *此時,持有對象B的強引用的變量爲obj0和obj2;
 */
 
obj2 = nil;
/*
 *由於nil被賦予了obj2,因此對對象B的強引用失效。
 *由於對象B的全部者不存在,因此廢棄對象B.
 */

複製代碼

__strong修飾符的變量,不只只在變量做用域中,在賦值上也可以正確得管理其對象的全部者。一樣,在方法的參數上,也可使用附有__strong修飾符的變量。

@interface Test : NSObjecgt
{
    id __strong obj_;
}
- (void) setObject:(id __strong)obj;
@end

@implementation Test
- (id) init
{
    self = [super init];
    return self;
}

- (void) setObject:(id __strong)obj
{
    obj_ = obj;
}
@end
複製代碼

使用該類以下:

{
    id __strong test = [[Test alloc] init];
    /*
     *test 持有Test對象的強引用
     */
    [test setObject:[[NSObject alloc] init];
    /*
     *Test對象的obj_成員,
     *持有NSObject對象的引用;
     */
}
/*
 *由於test變量超出其做用域,強引用失效,因此自動釋放Test對象。
 *Test對象的全部者不存在,所以廢棄該對象。
 *廢棄Test對象的同時,Test的成員變量obj_也被廢棄,
 *NSObject對象的強引用失效,自動釋放NSObject對象。
 *NSObject對象的全部者不存在,所以廢棄該對象。
 */

    
複製代碼

另外,__strong修飾符同__weak __autoreleasing,能夠保證將附有這些修飾符的自動變量初始化爲nil。

經過__strong修飾符,沒必要再次retain或者release,就能夠知足引用計數內存管理的思考方式。

  • 本身生成的對象本身持有
  • 非本身生成的對象,本身也能夠持有
  • 再也不須要本身持有的對象時釋放
  • 非本身持有的對象沒法釋放

__weak修飾符

僅僅經過__strong修飾符不可以解決引用計數內存管理中的「循環引用」問題。

例如:

@interface Test : NSObjecgt
{
    id __strong obj_;
}
- (void) setObject:(id __strong)obj;
@end

@implementation Test
- (id) init
{
    self = [super init];
    return self;
}

- (void) setObject:(id __strong)obj
{
    obj_ = obj;
}
@end
複製代碼

如下爲循環引用:

{
    id test0 = [[Test alloc] init];/* 對象A */
    /*
     * test0 持有Test對象A的強引用
     */
     
    id test1 = [[Test alloc] init];/* 對象B */
    /*
     * test1 持有Test對象B的強引用
     */
     
    [test0 setObject:test1];
    /*
     * Test對象A的obj_成員變量持有Test對象B的強引用
     * 此時,持有Test對象B的強引用的變量爲Test對象A的obj_和test1
     */
     
    [test1 setObject:test0];
    /*
     * Test對象B的obj_成員變量持有Test對象A的強引用
     * 此時,持有Test對象A的強引用的變量爲Test對象B的obj_和test0
     */
     
}
/*
 * 由於test0變量超出其做用域,強引用失效,因此自動釋放Test對象A
 * 由於test1變量超出其做用域,強引用失效,因此自動釋放Test對象B
 * 此時持有Test對象A的強引用的變量爲Test對象B的obj_
 * 此時持有Test對象B的強引用的變量爲Test對象A的obj_
 * 發生內存泄漏
 */

複製代碼

循環引用容易發生內存泄漏。內存泄漏就是應當廢棄的對象在超出其生存週期後繼續存在。

如下代碼也會引發內存泄漏(對自身的強引用)

id test = [[Test alloc] init];
[test setObject:test];

複製代碼

使用__weak修飾符能夠避免循環引用。

__weak修飾符與__strong修飾符相反,提供弱引用。弱引用不能持有對象實例。

id __weak obj = [[NSObject alloc] init];
複製代碼

若是運行以上代碼,編譯器會發出警告。

Assigning retained object to weak variable; object will be released after assignment
複製代碼

以上代碼將本身生成並持有的對象賦值給附有__weak修飾符的變量obj。即變量obj持有對持有對象的弱引用。所以,爲了避免以本身持有的狀態來保存本身生成並持有的對象,生成的對象會被當即釋放。若是將對象賦值給附有__strong修飾符的變量以後再賦值給附有__weak修飾符的變量,就不會發生警告了。

{
    /**本身生成並持有對象*/
    id __strong obj0 = [[NSObject alloc] init];
    /** 由於obj0變量爲強引用,因此本身持有對象 */
    
    id __weak obj1 = obj0;
    /** obj1變量持有生成對象的弱引用 */
}
/*
 *由於obj0變量超出其做用域,強引用失效,因此自動釋放本身持有的對象。
 *由於對象的全部者不存在,因此廢棄該對象。
 */
複製代碼

由於帶__weak修飾符的變量(弱引用)不持有對象,因此在超出其變量做用域時,對象即被釋放。以下代碼便可避免循環引用。

@interface Test : NSObject
{
    id __weak obj_;
}
- (void) setObject:(id __strong) obj;
@end
複製代碼

__weak修飾符還有另外一優勢:在持有某對象的弱引用時,若該對象被廢棄,則此弱引用將自動失效且處於nil被賦值的狀態(空弱引用)。以下所示:

id __weak obj1 = nil;
{
    /**本身生成並持有對象*/
    
    id __strong obj0 = [[NSObject alloc] init];
    
    /**由於obj0變量爲強引用,因此本身持有對象*/
    
    obj1 = obj0;
    
    /**obj1持有對象的弱引用*/
    
    NSLog(@"A: %@",obj1);
    /** 輸出obj1變量持有的弱引用的對象*/
}
/*
 *由於obj0變量超出其做用域,強引用失效,因此自動釋放本身持有的對象。
 *由於對象無持有者,因此廢棄該對象。
 *廢棄對象的同時,持有該對象弱引用的obj1變量的弱引用失效,nil賦值給obj1.
 */

NSLog(@"B: %@",obj1);
/** 輸出賦值給obj1變量中的nil */

複製代碼

該代碼的運行結果爲:

2017-12-05 20:13:28.458858+0800 ImageOrientation[6316:1604800] A: <NSObject: 0x604000207710>
2017-12-05 20:13:30.112086+0800 ImageOrientation[6316:1604800] B: (null)
複製代碼

以上,使用__weak修飾符能夠避免循環引用。經過檢查__weak修飾符的變量是否爲nil,能夠判斷被賦值的對象是否以廢棄。

__unsafe_unretained 修飾符

__unsafe_unretained修飾符的變量部署與編譯器的內存管理對象。(ARC式的內存管理式編譯器的工做)

id __unsafe_unretained obj = [[NSObject alloc] init];

若是運行以上代碼,編譯器會發出警告。雖然使用了unsafe的變量,可是編譯器並不會忽略。

Assigning retained object to unsafe_unretained variable; object will be released after assignment

附有__unsafe_unretained修飾符的變量同附有__weak修飾符的變量同樣,由於本身生成並持有的對象不能繼續爲本身全部,因此生成的對象會當即釋放。

id __unsafe_unretained obj1 = nil;
{
    /**本身生成並持有對象*/
    
    id __strong obj0 = [[NSObject alloc] init];
    
    /**由於obj0變量爲強引用,因此本身持有對象*/
    
    obj1 = obj0;
    
    /**雖然obj0變量賦值給obj1,但obj1變量既不持有隊形的強引用,也不持有對象的弱引用*/
    
    NSLog(@"A: %@",obj1);
    /** 輸出obj1變量表示的對象*/
}
/*
 *由於obj0變量超出其做用域,強引用失效,因此自動釋放本身持有的對象。
 *由於對象無持有者,因此廢棄該對象。
 */

NSLog(@"B: %@",obj1);
/*
 * 輸出obj1變量表示的對象
 *
 * obj1變量表示的對象已經被廢棄(懸垂指針)
 * 指向曾經存在的對象,但該對象已經再也不存在了,此類指針稱爲懸垂指針。結果未定義,每每致使程序錯誤,並且難以檢測。
 * 錯誤訪問
 */

複製代碼

該代碼的運行結果爲:

2017-12-06 08:45:54.005966+0800 ImageOrientation[6859:1736666] A: <NSObject: 0x604000011f00>

運行到NSLog(@"B: %@",obj1)時crash:Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

複製代碼

__autoreleasing 修飾符

ARC有效時,不能使用autorelease方法,也不能使用NSAutoreleasePool類。雖然autorelease沒法直接使用,可是autorelease功能是起做用的。

ARC無效時代碼以下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autoreleae];
[pool drain];
複製代碼

ARC有效時,代碼能夠寫成下面這樣:

@autoreleaepool{
    id __autoreleasing obj = [[NSObject alloc] init];
}
複製代碼

指定「@autoreleasepool塊」來代替「NSAutoreleasePool類對象生成,持有以及廢棄」這一範圍。

另外,ARC有效時,要經過將對象賦值給附加了 __autoreleaing修飾符的變量來代替調用autorelease方法。對象賦值給附有__autoreleasing修飾符的變量等價於在ARC無效時調用對象的autorelease方法,即對象被註冊到autoreleasepool。

也就是說能夠理解爲,在ARC有效時,用@autoreleasepool塊替代NSAutoreleasePool類,用附有__autoreleasing修飾符的變量替代autorelease方法。

通常來講,不會顯示地附加__autoreleasing修飾符。在取得非本身生成並持有的對象時,索然可使用alloc/new/copy/mutableCopy之外的方法來取得對象,但該對象已經被註冊到了autoreleasepool。這是因爲編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開始,若是不是則自動將返回值的對象註冊到autoreleasepool。(init方法返回值的對象不註冊到autoreleasepool)

@autoreleasepool{
    /** 取得非本身生成並持有的對象*/
    id __strong obj = [MutableArray array];
    /*
     * 由於變量obj爲強引用,因此本身持有對象。
     * 而且該對象由編譯器判斷其方法名後自動註冊到autoreleasepool
     */
}
/*
 * 由於變量obj超出其做用域,強引用失效,因此自動釋放本身持有的對象。
 * 同時,隨着@autoreleasepool塊的結束,註冊到autoreleasepool中的全部對象被自動釋放。
 * 由於對象的全部者不存在,因此廢棄對象。
 */
複製代碼

訪問附有__weak修飾符的變量時,必須訪問註冊到autoreleasepool的對象。

id __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
複製代碼

等同於

id __weak obj1 = obj0;
id __autoreleasing temp = obj1;
NSLog(@"class = %@",[temp class]);
複製代碼

由於__weak修飾符只持有對象的弱引用,而在訪問引用對象的過程當中,該對象有可能被廢棄。若是要把訪問的對象註冊到autoreleasepool中,那麼在@autoreleasepool塊結束以前都能確保該對象存愛。所以,在使用附有__weak修飾符的變量時就一定要shying註冊到autoreleasepool中的對象。

一樣地,id的指針或者對象的指針在沒有顯示指定時會被附加上__autoreleasing修飾符。


ARC 規則

在ARC有效的狀況下編譯源代碼,必須遵照如下規則:

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必須遵照內存管理的方法命名規則
  • 不要顯示調用dealloc
  • 使用@autoreleasepool塊代替NSAutoreleasePool
  • 不能使用區域(NSZone)
  • 對象型變量不能做爲C語言結構體(struct/union)的成員
  • 顯示轉換"id"和"void"

不能使用retain/release/retainCount/autorelease

內存管理是編譯器的工做,由於沒有必要使用內存管理的方法(retain/release/retainCount/autorelease)。只能在ARC無效且手動進行內存管理時才能使用。

不能使用NSAllocateObject/NSDeallocateObject

通常經過調用NSObject類的alloc類方法來生成並持有OC對象。alloc類方法其實是經過直接調用NSAllocateObject函數來生成並持有對象的。

須遵照內存管理的方法命名規則

在ARC無效時,用於對象生成/持有的方法必須遵循如下命名規則:以alloc/new/copy/mutablCopy開始的方法在返回對象時,必須返回給調用方所應當持有的對象。

在ARC有效時,除了上述規則外,增長init規則。

以init開始的方法的規則要比alloc/new/copy/mutableCopy更嚴格。該方法必須是實例方法,而且必需要返回對象。返回的對象應該爲id類型或者該方法聲明類的對象類型。

id obj = [[NSObject alloc] init];

如上所示,init方法會初始化alloc方法返回的對象,而後原封不動地返還給調用方。

注:initialize不包含在上述命名規則裏。

不要顯式調用dealloc

不管ARC是否有效,只要對象的全部者都不持有該對象,該對象就被廢棄。對象被廢棄時,不管ARC是否有效,都會調用對象的dealloc方法。在ARC無效時,必須調用[super dealloc]。ARC有效時會遵循沒法顯式調用dealloc這一規則,ARC對此會自動進行處理,dealloc中只需技術廢棄對象時所必須的處理,好比刪除已註冊的代理或者觀察者對象。

使用@autoreleasepool塊代替NSAutoreleasePoll

不能使用區域(NSZone)

對象型變量不能做爲C語言結構體的成員

struct Data{
    NSMutableArray * array;
}
複製代碼

編譯後

error:ARC forbids Objective-C objects in struct

由於C語言的規約上沒有方法來管理結構體成員的生存週期。

要把對象型變量假如到結構體成員中,可強制轉換爲void*或者是附加__unsafe_unretained修飾符。(附有__unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象)

顯式轉換id和void*

ARC無效時,id變量和void*變量相互賦值沒有問題,可是ARC有效時則會引發編譯錯誤。

在ARC有效時,id型或對象型變量賦值給void*或者逆向賦值時都須要進行特定的轉換。若是想單純地賦值,可使用__bridige轉換。

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
複製代碼

可是轉換爲void*的__bridge轉換,其安全性與賦值給__unsafe_unretained修飾符相近,甚至會更低。若是管理時不注意賦值對象的全部者,就會因懸垂指針而致使程序崩潰。

__bridge轉換還有其餘兩種:__bridge_retained轉換和__bridge_transfer轉換

__bridge_retained轉換可以使要轉換賦值的變量也持有所賦值的對象。__bridge_retained轉換與retain相相似。

void *p = 0;
{
    id obj = [[NSObject alloc] init];
    p = (__bridge_retained void *)obj;
}
NSLog(@"class = %@",[(__bridge id)p class]);
複製代碼

運行結果爲: 2017-12-06 16:39:06.148058+0800 ImageOrientation[7685:2312365] class = NSObject

變量做用域結束時,雖然隨着持有強引用的變量obj失效,對象隨之釋放,但因爲__bridge_retained轉換使變量p看上去持有該對象的狀態,所以該對象不會被廢棄。

ARC無效時實現以上邏輯的代碼爲:

void *p = 0;
{
    id obj = [[NSObject alloc] init];
    /** [obj retainCount] ->1 */
    p = [obj retain];
    /** [obj retainCount] ->2 */
    [obj release];
    /** [obj retainCount] ->1 */
}
/*
 * [(id)p retainCount] ->1
 * 即[obj retainCount] ->1
 * 對象仍存在
 */
複製代碼

__bridge_transfer轉換,被轉換的變量所持有的對象在該變量被賦值給轉換目標變量後隨之釋放。__bridge_transfer轉換與release相相似。

id obj = (__bridge_transfer id)p;至關於ARC無效時代碼以下:

/**ARC無效*/
id obj = (id)p;
[obj retain];
[p release];
複製代碼

__bridge轉換,__bridge_retained轉換,__bridge_transfer轉換經常使用於OC對象和CoreFoundation對象之間的相互變換中。

  1. __bridge:CF和OC對象轉化時只涉及對象類型不涉及對象全部權的轉化;
  2. __bridge_transfer:經常使用在講CF對象轉換成OC對象時,將CF對象的全部權交給OC對象,此時ARC就能自動管理該內存;(做用同CFBridgingRelease())
  3. __bridge_retained:(與__bridge_transfer相反)經常使用在將OC對象轉換成CF對象時,將OC對象的全部權交給CF對象來管理;(做用同CFBridgingRetain())

如下函數可用於OC對象和CoreFoundation對象之間的相互變換,成爲Toll-Free Bridge "免費橋"轉換。

CFTypeRef CFBridgingRetain(id X){
    return (__bridge_retained CFTypeRef)X;
}

id CFBridgingRelease(CFTypeRef X){
    return (__bridge_transfer id)X;
}
複製代碼

下例爲將生成並持有的NSMutableArray對象做爲CoreFoundation對象來處理。

CFMutableArrayRef cfObject = NULL;
{
    id obj = [[NSMutableArray alloc] init
    /**變量obj持有對生成並持有對象的強引用 */
    
    cfObject = CFBridgingRetain(obj);
    /** 經過CFBridgingRetain將對象CFRetain賦值給變量cfObject */
    
    CFShow(cfObject);
    printf("retain count =%d\n",CFGetRetainCount(cfObject));
    /** 經過變量obj的強引用和經過CFBridgingRetain,引用計數爲2
}
/** 由於變量obj超出做用範圍,因此其強引用失效,引用計數爲1 */
printf("retain count after the scope =%d\n",CFGetRetainCount(cfObject));

CFRelease(cfObject);
/** 由於將對象CFRelease,因此其引用計數爲0,古該對象被廢棄。*/


複製代碼

運行結果爲:

(
)
retain count =2
retain count after the scope =1
複製代碼

由此可知,Foundation框架的API生成並持有的OC對象可以做爲CF對象來使用,也能夠經過CFRelease來釋放。以上代碼也能夠用__bridge_retained轉換來代替。

CFMutableArrayRef cfObject = (__bridge_retained CFMutableArrayRef) obj;

若是使用__bridge轉換,第一句打印的結果是1,由於__bridge轉換不改變對象的持有情況,obj持有NSMutableArray對象的強引用,因此爲1.後一句打印crash,由於obj超出其做用域,因此強引用失效,對象釋放,無持有者的對象被廢棄,因此出現懸垂指針,致使崩潰。

那麼,將CoreFoundation的API生成並持有對象,將該對象做爲NSMutableArray對象來處理,代碼以下:

{
    CFMutableArrayRef cfObject = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    printf("retain count =%d\n",CFGetRetainCount(cfObject));
    /*
     * CoreFoundation框架的API生成並持有對象
     * 以後的對象引用計數爲1
     */
    id obj = CFBridgingRelease(cfObject);
    /*
     * 經過CFBridgingRelease賦值,變量obj持有對象強引用的同時,對象經過CFRelease釋放
     */
    
    printf("retain count after the cast =%d\n",CFGetRetainCount(cfObject));
    /*
     * 由於只有變量obj持有對生成並持有對象的強引用,因此引用計數爲1
     * 另外,經由CFBridgingRelease轉換後,賦值給變量cfObject中的指針也指向仍在存在的對象,因此能夠正常使用。
     */
    NSLog(@"class =%@",obj);
    
}
/*
 * 由於變量obj超出做用域,因此其強引用失效,對象獲得釋放,無全部者的對象隨之被廢棄。
 */
複製代碼

運行結果以下:

retain count =1
retain count after the cast =1
2017-12-06 21:30:02.473804+0800 ImageOrientation[8249:2573155] class =(
)
複製代碼

也可使用__bridge_transfer轉換代替CFBridgingRelease。

id obj = (__bridge_transfer id)cfObject;
複製代碼

若是使用__bridge來替代__bridge_transfer或CFBridgingRelease轉換,則第一句打印爲1,中間打印爲2,由於obj和cfObject同時持有對象的強引用,因此爲2。超出範圍後obj強引用失效,對象的引用計數爲1。


ARC屬性

ARC有效時,Objective-C類的屬性也會發生變化。具體以下因此:

屬性聲明的屬性 全部權修飾符
assign __unsafe_unretained修飾符
copy __strong修飾符(可是賦值的是被複制的對象)
ratai __strong修飾符
unsafe_unretained __unsafe_unretained修飾符
weak __weak修飾符

以上各屬性複製給指定的屬性中就想彈鼓賦值給附加各屬性對應的全部權修飾符的變量中。只有copy屬性不是簡單的賦值,他賦值的是經過NSCopying接口的copyWithZone:方法複製複製源所生成的對象。

相關文章
相關標籤/搜索