iOS - 老生常談內存管理(三):ARC 面世

前言

  ARC全稱Automatic Reference Counting,自動引用計數內存管理,是蘋果在 iOS 五、OS X Lion 引入的新的內存管理技術。ARC經過LLVM編譯器和Runtime協做來進行自動管理內存。LLVM編譯器會在編譯時在合適的地方爲 OC 對象插入retainreleaseautorelease代碼來自動管理對象的內存,省去了在MRC手動引用計數下手動插入這些代碼的工做,減輕了開發者的工做量,讓開發者能夠專一於應用程序的代碼、對象圖以及對象間的關係上。
  下圖是蘋果官方文檔給出的從MRCARC的轉變。html

摘要

ARC的工做原理是在編譯時添加相關代碼,以確保對象可以在必要時存活,但不會一直存活。從概念上講,它經過爲你添加適當的內存管理方法調用來遵循與MRC相同的內存管理規則。算法

爲了讓編譯器生成正確的代碼,ARC限制了一些方法的使用以及你使用橋接(toll-free bridging)的方式,請參閱《Managing Toll-Free Bridging》章節。ARC還爲對象引用和屬性聲明引入了新的生命週期修飾符。express

ARCXcode 4.2 for OS X v10.6 and v10.7 (64-bit applications)以及iOS 4 and iOS 5應用程序中提供支持。但OS X v10.6 and iOS 4不支持weak弱引用。編程

Xcode 提供了一個遷移工具,能夠自動將MRC代碼轉換爲ARC代碼(如刪除retainrelease調用),而不用從新再建立一個項目(選擇 Edit > Convert > To Objective-C ARC)。遷移工具會將項目中的全部文件轉換爲使用ARC的模式。若是對於某些文件使用MRC更方便的話,你能夠選擇僅在部分文件中使用ARC數組

ARC 概述

ARC會分析對象的生存期需求,並在編譯時自動插入適當的內存管理方法調用的代碼,而不須要你記住什麼時候使用retainreleaseautorelease方法。編譯器還會爲你生成合適的dealloc方法。通常來講,若是你使用ARC,那麼只有在須要與使用MRC的代碼進行交互操做時,傳統的 Cocoa 命名約定才顯得重要。安全

Person 類的完整且正確的實現可能以下所示:多線程

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@property NSNumber *yearOfBirth;
@property Person *spouse;
@end
 
@implementation Person
@end
複製代碼

(默認狀況下,對象屬性是strong。關於strong請參閱《全部權修飾符》章節。)app

使用ARC,你能夠這樣實現 contrived 方法,以下所示:框架

- (void)contrived {
    Person *aPerson = [[Person alloc] init];
    [aPerson setFirstName:@"William"];
    [aPerson setLastName:@"Dudney"];
    [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]];
    NSLog(@"aPerson: %@", aPerson);
}
複製代碼

ARC會負責內存管理,所以 Person 和 NSNumber 對象都不會泄露。ide

你還能夠這樣安全地實現 Person 的 takeLastNameFrom: 方法,以下所示:

- (void)takeLastNameFrom:(Person *)person {
    NSString *oldLastname = [self lastName];
    [self setLastName:[person lastName]];
    NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
}
複製代碼

ARC會確保在 NSLog 語句以前不釋放 oldLastName 對象。

ARC 實施新規則

ARC引入了一些在使用其餘編譯器模式時不存在的新規則。這些規則旨在提供徹底可靠的內存管理模型。有時候,它們直接地帶來了最好的實踐體驗,也有時候它們簡化了代碼,甚至在你絲毫沒有關注內存管理問題的時候幫你解決了問題。在ARC下必須遵照如下規則,若是違反這些規則,就會編譯錯誤。

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

不能使用 retain / release / retainCount / autorelease

ARC下,禁止開發者手動調用這些方法,也禁止使用@selector(retain)@selector(release)等,不然編譯不經過。但你仍然能夠對 Core Foundation 對象使用CFRetainCFRelease等相關函數(請參閱《Managing Toll-Free Bridging》章節)。

不能使用 NSAllocateObject / NSDeallocateObject

ARC下,禁止開發者手動調用這些函數,不然編譯不經過。 你可使用alloc建立對象,而Runtime會負責dealloc對象。

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

MRC下,經過 alloc / new / copy / mutableCopy 方法建立對象會直接持有對象,咱們定義一個 「建立並持有對象」 的方法也必須以 alloc / new / copy / mutableCopy 開頭命名,而且必須返回給調用方所應當持有的對象。若是在ARC下須要與使用MRC的代碼進行交互,則也應該遵照這些規則。

爲了容許與MRC代碼進行交互操做,ARC對方法命名施加了約束: 訪問器方法的方法名不能以new開頭。這意味着你不能聲明一個名稱以new開頭的屬性,除非你指定一個不一樣的getterName

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

不能顯式調用 dealloc

不管在MRC仍是ARC下,當對象引用計數爲 0,系統就會自動調用dealloc方法。大多數狀況下,咱們會在dealloc方法中移除通知或觀察者對象等。

MRC下,咱們能夠手動調用dealloc。但在ARC下,這是禁止的,不然編譯不經過。

MRC下,咱們實現dealloc,必須在實現末尾調用[super dealloc]

// MRC
- (void)dealloc
{
    // 其餘處理
    [super dealloc];
}
複製代碼

而在ARC下,ARC會自動對此處理,所以咱們沒必要也禁止寫[super dealloc],不然編譯錯誤。

// ARC
- (void)dealloc
{
    // 其餘處理
    [super dealloc]; // 編譯錯誤:ARC forbids explicit message send of 'dealloc'
}
複製代碼

使用 @autoreleasepool 塊替代 NSAutoreleasePool

ARC下,自動釋放池應使用@autoreleasepool,禁止使用NSAutoreleasePool,不然編譯錯誤。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
複製代碼
error:'NSAutoreleasePool' is unavailable: not available in automatic reference counting mode
複製代碼

關於@autoreleasepool的原理,能夠參閱《iOS - 聊聊 autorelease 和 @autoreleasepool》

不能使用區域(NSZone)

對於如今的運行時系統(編譯器宏 __ OBJC2 __ 被設定的環境),無論是MRC仍是ARC下,區域(NSZone)都已單純地被忽略。

NSZone 《Objective-C 高級編程:iOS 與 OS X 多線程和內存管理》書中對 NSZone 作了以下介紹:

NSZone 是爲防止內存碎片化而引入的結構。對內存分配的區域自己進行多重化管理,根據使用對象的目的、對象的大小分配內存,從而提升了內存管理的效率。 可是,如今的運行時系統已經忽略了區域的概念。運行時系統中的內存管理自己已極具效率,使用區域來管理內存反而會引發內存使用效率低下以及源代碼複雜化等問題。 下圖是使用多重區域防止內存碎片化的例子:

對象型變量不能做爲 C 語言結構體(struct / union)的成員

C 語言的結構體(struct / union)成員中,若是存在 Objective-C 對象型變量,便會引發編譯錯誤。

備註: 我使用的時候編譯經過,難道編譯器優化了嗎?

struct Data {
    NSMutableArray *mArray;
};
複製代碼
error:ARC forbids Objective-C objs in struct or unions NSMutableArray *mArray;
複製代碼

雖然是 LLVM 編譯器 3.0,但不論怎樣,C 語言的規約上沒有方法來管理結構體成員的生存週期。由於ARC把內存管理的工做分配給編譯器,因此編譯器必須可以知道並管理對象的生存週期。例如 C 語言的自動變量(局部變量)可以使用該變量的做用域管理對象。可是對於 C 語言的結構體成員來講,這在標準上就是不可實現的。所以,必需要在結構體釋放以前將結構體中的對象類型的成員釋放掉,可是編譯器並不能可靠地作到這一點,因此對象型變量不能做爲 C 語言結構體的成員。

這個問題有如下三種解決方案:

  • ① 使用 Objective-C 對象替代結構體。這是最好的解決方案。

若是你仍是堅持使用結構體,並把對象型變量加入到結構體成員中,可使用如下兩種方案:

  • ② 將 Objective-C 對象經過Toll-Free Bridging強制轉換爲void *類型,請參閱《Managing Toll-Free Bridging》章節。
  • ③ 對 Objective-C 對象附加__unsafe_unretained修飾符。
struct Data {
    NSMutableArray __unsafe_unretained *mArray;
};
複製代碼

附有__unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象。若是管理時不注意賦值對象的全部者,便有可能遭遇內存泄漏或者程序崩潰。這點在使用時應多加註意。

顯式轉換 「id」 和 「void *」 —— 橋接

MRC下,咱們能夠直接在 idvoid * 變量之間進行強制轉換。

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

但在ARC下,這樣會引發編譯報錯:在Objective-C指針類型idC指針類型void *之間進行轉換須要使用Toll-Free Bridging,請參閱《Managing Toll-Free Bridging》章節。

id obj = [[NSObject alloc] init];
    void *p = obj; // error:Implicit conversion of Objective-C pointer type 'id' to C pointer type 'void *' requires a bridged cast
    id o = p;      // error:Implicit conversion of C pointer type 'void *' to Objective-C pointer type 'id' requires a bridged cast
    [o release];   // error:'release' is unavailable: not available in automatic reference counting mode
複製代碼

全部權修飾符

ARC爲對象和弱引用引入了幾個新的生命週期修飾符(咱們稱爲 「全部權修飾符」)。弱引用weak不會延長它指向的對象的生命週期,而且該對象沒有強引用(即dealloc)時自動置爲nil。 你應該利用這些修飾符來管理程序中的對象圖。特別是,ARC不能防止強引用循環(之前稱爲Retain Cycles,請參閱《從 MRC 提及 —— 使用弱引用來避免 Retain Cycles》章節)。明智地使用弱引用weak將有助於確保你不會建立循環引用。

屬性關鍵字

ARC中引入了新的屬性關鍵字strongweak,以下所示:

// 如下聲明同:@property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
 
// 如下聲明相似於:@property(assign)MyClass *myObject;
// 不一樣的是,若是 MyClass 實例被釋放,屬性值賦值爲 nil,而不像 assign 同樣產生懸垂指針。
@property(weak) MyClass *myObject;
複製代碼

strongweak屬性關鍵字分別對應__strong__weak全部權修飾符。在ARC下,strong是對象類型的屬性的默認關鍵字。

ARC中,對象類型的變量都附有全部權修飾符,總共有如下 4 種。

__strong
__weak
__unsafe_unretained
__autoreleasing
複製代碼
  • __strong是默認修飾符。只要有強指針指向對象,對象就會保持存活。
  • __weak指定一個不使引用對象保持存活的引用。當一個對象沒有強引用時,弱引用weak會自動置爲nil
  • __unsafe_unretained指定一個不使引用對象保持存活的引用,當一個對象沒有強引用時,它不會置爲nil。若是它引用的對象被銷燬,就會產生懸垂指針。
  • __autoreleasing用於表示經過引用(id *)傳入,並在返回時(autorelease)自動釋放的參數。

在對象變量的聲明中使用全部權修飾符時,正確的格式爲:

ClassName * qualifier variableName;
複製代碼

例如:

MyClass * __weak myWeakReference;
    MyClass * __unsafe_unretained myUnsafeReference;
複製代碼

其它格式在技術上是不正確的,但編譯器會 「原諒」。也就是說,以上纔是標準寫法。

__strong

__strong修飾符爲強引用,會持有對象,使其引用計數 +1。該修飾符是對象類型變量的默認修飾符。若是咱們沒有明確指定對象類型變量的全部權修飾符,其默認就爲__strong修飾符。

id obj = [NSObject alloc] init];
    // -> id __strong obj = [NSObject alloc] init];
複製代碼

__weak

若是單單靠__strong完成內存管理,那必然會發生循環引用的狀況形成內存泄漏,這時候__weak就出來解決問題了。 __weak修飾符爲弱引用,不會持有對象,對象的引用計數不會增長。__weak能夠用來防止循環引用。

如下單純地使用__weak修飾符修飾變量,編譯器會給出警告,由於NSObject的實例建立出來沒有強引用,就會當即釋放。

id __weak weakObj = [[NSObject alloc] init]; // ⚠️Assigning retained object to weak variable; object will be released after assignment
    NSLog(@"%@", obj);
    // (null)
複製代碼

如下NSObject的實例已有強引用,再賦值給__weak修飾的變量就不會有警告了。

id __strong strongObj = [[NSObject alloc] init];
    id __weak weakObj = strongObj;
複製代碼

當對象被dealloc時,指向該對象的__weak變量會被賦值爲nil。(具體的執行過程請參閱:《iOS - 老生常談內存管理(四):源碼分析內存管理方法》

備註:__weak僅在ARC中才能使用,在MRC中是使用__unsafe_unretained修飾符來代替。

__unsafe_unretained

__unsafe_unretained修飾符的特色正如其名所示,不安全且不會持有對象。

注意: 儘管ARC內存管理是編譯器的工做,可是附有__unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象。這一點在使用時要注意。

「不會持有對象」 這一特色使它和__weak的做用類似,能夠防止循環引用。
「不安全「 這一特色是它和__weak的區別,那麼它不安全在哪呢?

咱們來看代碼:

id __weak weakObj = nil;
    id __unsafe_unretained uuObj = nil;
    {
        id __strong strongObj = [[NSObject alloc] init];
        weakObj = strongObj;
        unsafeUnretainedObj = strongObj;
        NSLog(@"strongObj:%@", strongObj);
        NSLog(@"weakObj:%@", weakObj);
        NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj);
    }
    NSLog(@"-----obj dealloc-----");
    NSLog(@"weakObj:%@", weakObj);
    NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj); // Crash:EXC_BAD_ACCESS

/* strongObj:<NSObject: 0x6000038f4340> weakObj:<NSObject: 0x6000038f4340> unsafeUnretainedObj:<NSObject: 0x6000038f4340> -----obj dealloc----- weakObj:(null) (lldb) */
複製代碼

以上代碼運行崩潰EXC_BAD_ACCESS。緣由是__unsafe_unretained修飾的對象在被銷燬以後,指針仍然指向原對象地址,咱們稱它爲 「懸垂指針」。這時候若是繼續經過指針訪問原對象的話,就會致使Crash。而__weak修飾的對象在被釋放以後,會將指向該對象的全部__weak指針變量全都置爲nil。這就是__unsafe_unretained不安全的緣由。因此,在使用__unsafe_unretained修飾符修飾的對象時,須要確保它未被銷燬。

Q: 既然 __weak 更安全,那麼爲何已經有了 __weak 還要保留 __unsafe_unretained ?

  • __weak僅在ARC中才能使用,而MRC只能使用__unsafe_unretained
  • __unsafe_unretained主要跟 C 代碼交互;
  • __weak對性能會有必定的消耗,當一個對象dealloc時,須要遍歷對象的weak表,把表裏的全部weak指針變量值置爲nil,指向對象的weak指針越多,性能消耗就越多。因此__unsafe_unretained__weak快。當明確知道對象的生命週期時,選擇__unsafe_unretained會有一些性能提高。

    A 持有 B 對象,當 A 銷燬時 B 也銷燬。這樣當 B 存在,A 就必定會存在。而 B 又要調用 A 的接口時,B 就能夠存儲 A 的__unsafe_unretained指針。 好比,MyViewController 持有 MyView,MyView 須要調用 MyViewController 的接口。MyView 中就能夠存儲__unsafe_unretained MyViewController *_viewController

    雖然這種性能上的提高是很微小的。但當你很清楚這種狀況下,__unsafe_unretained也是安全的,天然能夠快一點就是一點。而當狀況不肯定的時候,應該優先選用__weak

__autoreleasing

自動釋放池

首先講一下自動釋放池,在ARC下已經禁止使用NSAutoreleasePool類建立自動釋放池,而用@autoreleasepool替代。

  • MRC下可使用NSAutoreleasePool或者@autoreleasepool。建議使用@autoreleasepool,蘋果說它比NSAutoreleasePool快大約六倍。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // Code benefitting from a local autorelease pool.
    [pool release]; // [pool drain]
複製代碼

Q: 釋放NSAutoreleasePool對象,使用[pool release][pool drain]的區別?

Objective-C 語言自己是支持 GC 機制的,但有平臺侷限性,僅限於 MacOS 開發中,iOS 開發用的是 RC 機制。在 iOS 的 RC 環境下[pool release][pool drain]效果同樣,但在 GC 環境下drain會觸發 GC 而release不作任何操做。使用[pool drain]更佳,一是它的功能對系統兼容性更強,二是這樣能夠跟普通對象的release區別開。(注意:蘋果在引入ARC時稱,已在 OS X Mountain Lion v10.8 中棄用GC機制,而使用ARC替代)

  • ARC下只能使用@autoreleasepool
@autoreleasepool {
        // Code benefitting from a local autorelease pool.
    }
複製代碼

關於@autoreleasepool的底層原理,能夠參閱《iOS - 聊聊 autorelease 和 @autoreleasepool》

__autoreleasing 使用

MRC中咱們能夠給對象發送autorelease消息來將它註冊到autoreleasepool中。而在ARCautorelease已禁止調用,咱們可使用__autoreleasing修飾符修飾對象將對象註冊到autoreleasepool中。

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

以上代碼在MRC中等價於:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];
    // 或者
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        [obj autorelease];
    }
複製代碼
__autoreleasing 是二級指針類型的默認修飾符

前面咱們說過,對象指針的默認全部權修飾符是__strong。 而二級指針類型(ClassName **id *)的默認全部權修飾符是__autoreleasing。若是咱們沒有明確指定二級指針類型的全部權修飾符,其默認就會附加上__autoreleasing修飾符。

好比,咱們常常會在開發中使用到NSError打印錯誤信息,咱們一般會在方法的參數中傳遞NSError對象的指針。如NSStringstringWithContentsOfFile類方法,其參數NSError **使用的是__autoreleasing修飾符。

NSString *str = [NSString stringWithContentsOfFile:<#(nonnull NSString *)#>
                                              encoding:<#(NSStringEncoding)#> 
                                              error:<#(NSError *__autoreleasing _Nullable * _Nullable)#>];
複製代碼

示例:咱們聲明一個參數爲NSError **的方法,但不指定其全部權修飾符。

- (BOOL)performOperationWithError:(NSError **)error;
複製代碼

接着咱們嘗試調用該方法,發現智能提示中的參數NSError **附有__autoreleasing修飾符。可見,若是咱們沒有明確指定二級指針類型的全部權修飾符,其默認就會附加上__autoreleasing修飾符。

NSError *error = nil;
    BOOL result = [self performOperationWithError:<#(NSError *__autoreleasing *)#>];
複製代碼
注意

須要注意的是,賦值給二級指針類型時,全部權修飾符必須一致,不然會編譯錯誤。

NSError *error = nil;
    NSError **error1 = &error;                 // error:Pointer to non-const type 'NSError *' with no explicit ownersh
    NSError *__autoreleasing *error2 = &error; // error:Initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
    NSError *__weak *error3 = &error;          // error:Initializing 'NSError *__weak *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
    NSError *__strong *error4 = &error;        // 編譯經過
複製代碼
NSError *__weak error = nil;
    NSError *__weak *error1 = &error;          // 編譯經過
複製代碼
NSError *__autoreleasing error = nil;
    NSError *__autoreleasing *error1 = &error; // 編譯經過
複製代碼

咱們前面說過,二級指針類型的默認修飾符是__autoreleasing。那爲何咱們調用方法傳入__strong修飾的參數就能夠編譯經過呢?

NSError *__strong error = nil;
    BOOL result = [self performOperationWithError:<#(NSError *__autoreleasing *)#>];
複製代碼

其實,編譯器自動將咱們的代碼轉化成了如下形式:

NSError *__strong error = nil;
    NSError *__autoreleasing tmp = error;
    BOOL result = [self performOperationWithError:&tmp];
    error = tmp;
複製代碼

可見,當局部變量聲明(__strong)和參數(__autoreleasing)之間不匹配時,會致使編譯器建立臨時變量。你能夠將顯示地指定局部變量全部權修飾符爲__autoreleasing或者不顯式指定(由於其默認就爲__autoreleasing),或者顯示地指定參數全部權修飾符爲__strong,來避免編譯器建立臨時變量。

- (BOOL)performOperationWithError:(NSError *__strong *)error;
複製代碼

可是在MRC引用計數內存管理規則中:使用alloc/new/copy/mutableCopy等方法建立的對象,建立並持有對象;其餘狀況建立對象但並不持有對象。爲了在使用參數得到對象時,遵循此規則,咱們應該指定二級指針類型參數修飾符爲__autoreleasing

《從 MRC 提及 —— 你不持有經過引用返回的對象》章節中也說到,Cocoa 中的一些方法指定經過引用返回對象(即,它們採用ClassName **id *類型的參數),常見的就是使用NSError對象。當你調用這些方法時,你不會建立該NSError對象,所以你不持有該對象,也無需釋放它。而__strong表明持有對象,所以應該使用__autoreleasing

另外,咱們在顯示指定__autoreleasing修飾符時,必須注意對象變量要爲自動變量(包括局部變量、函數以及方法參數),不然編譯不經過。

static NSError __autoreleasing *error = nil; // Global variables cannot have __autoreleasing ownership
複製代碼

使用全部權修飾符來避免循環引用

前面已經說過__weak__unsafe_unretained修飾符能夠用來循環引用,這裏再來囉嗦幾句。

若是兩個對象互相強引用,就產生了循環引用,致使兩個對象都不能被銷燬,內存泄漏。或者多個對象,每一個對象都強引用下一個對象直到回到第一個,產生大環循環引用,這些對象也均不能被銷燬。

ARC中,「循環引用」 是指兩個對象都經過__strong持有對方。

解決 「循環引用」 問題就是採用 「斷環」 的方式,讓其中一方持有另外一方的弱引用。同MRC,父對象對它的子對象持有強引用,而子對象對父對象持有弱引用。

ARC中,「弱引用」 是指__weak__unsafe_unretained

delegate 避免循環引用

delegate避免循環引用,就是在委託方聲明delegate屬性時,使用weak關鍵字。

@property (nonatomic, weak) id<protocolName> delegate;
複製代碼
block 避免循環引用

Q: 爲何 block 會產生循環引用?

  • ① 相互循環引用: 若是當前block對當前對象的某一成員變量進行捕獲的話,可能會對它產生強引用。根據block的變量捕獲機制,若是block被拷貝到堆上,且捕獲的是對象類型的auto變量,則會連同其全部權修飾符一塊兒捕獲,因此若是對象是__strong修飾,則block會對它產生強引用(若是block在棧上就不會強引用)。而當前block可能又因爲當前對象對其有一個強引用,就產生了相互循環引用的問題;
  • ② 大環引用: 咱們若是使用__block的話,在ARC下可能會產生循環引用(MRC則不會)。因爲__block修飾符會將變量包裝成一個對象,若是block被拷貝到堆上,則會直接對__block變量產生強引用,而__block若是修飾的是對象的話,會根據對象的全部權修飾符作出相應的操做,造成強引用或者弱引用。若是對象是__strong修飾(如__block id x),則__block變量對它產生強引用(在MRC下則不會),若是這時候該對象是對block持有強引用的話,就產生了大環引用的問題。在ARC下能夠經過斷環的方式去解除循環引用,能夠在block中將指針置爲nilMRC不會循環引用,則不用解決)。可是有一個弊端,若是該block一直得不到調用,循環引用就一直存在。

ARC 下的解決方式:

  • __weak或者__unsafe_unretained解決:
__weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };
複製代碼
__unsafe_unretained id uuSelf = self;
    self.block = ^{
        NSLog(@"%p",uuSelf);
    };
複製代碼

注意__unsafe_unretained會產生懸垂指針,建議使用weak

 對於 non-trivial cycles,咱們須要這樣作:

__weak typeof(self) weakSelf = self;
    self.block = ^{
        if(!strongSelf) return;
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%p",weakSelf);
    };
複製代碼
  • __block解決(必需要調用block): 缺點:必需要調用block,並且block裏要將指針置爲nil。若是一直不調用block,對象就會一直保存在內存中,形成內存泄漏。
__block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p",blockSelf);
        blockSelf = nil;
    };
    self.block();
複製代碼

MRC 下的解決方式:

  • __unsafe_unretained解決:同ARC
  • __block解決(在MRC下使用__block修飾對象類型,在block內部不會對該對象進行retain操做,因此在MRC環境下能夠經過__block解決循環引用的問題)
__block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p",blockSelf);
    };
複製代碼

更多關於block的內容,能夠參閱《OC - Block 詳解》

屬性

說到屬性,不得不提一下@synthesize@dynamic這兩個指令。

@synthesize 和 @dynamic

  • @property:幫咱們自動生成屬性的settergetter方法的聲明。
  • @synthesize:幫咱們自動生成settergetter方法的實現以及下劃線成員變量。 之前咱們須要手動對每一個@property添加@synthesize,而在 iOS 6 以後 LLVM 編譯器引入了 「property autosynthesis」,即屬性自動合成。換句話說,就是編譯器會自動爲每一個@property添加@synthesize

Q: @synthesize如今有什麼做用呢?

若是咱們同時重寫了settergetter方法,則編譯器就不會爲這個@property添加@synthesize,這時候就不存在下劃線成員變量,因此咱們須要手動添加@synthesize

@synthesize propertyName = _propertyName;
複製代碼

 有時候咱們不但願編譯器爲咱們@synthesize,咱們但願在程序運行過程當中再去決定該屬性存取方法的實現,就可使用@dynamic

  • @dynamic :告訴編譯器不用自動進行@synthesize,等到運行時再添加方法實現,可是它不會影響@property生成的settergetter方法的聲明。@dynamic是 OC 爲動態運行時語言的體現。動態運行時語言與編譯時語言的區別:動態運行時語言將函數決議推遲到運行時,編譯時語言在編譯器進行函數決議。
@dynamic propertyName;
複製代碼

屬性「內存管理」關鍵字與全部權修飾符的對應關係

屬性「內存管理」關鍵字 全部權修飾符
assign __unsafe_unretained
unsafe_unretained __unsafe_unretained
weak __weak
retain __strong
strong __strong
copy __strong

更多關於屬性關鍵字的內容,能夠參閱《OC - 屬性關鍵字和全部權修飾符》

管理 Outlets 的模式在 iOS 和 OS X 平臺下變得一致

ARC下,iOSOS X平臺中聲明outlets的模式變得一致。你應該採用的模式爲:在nib或者storyboard中,除了來自文件全部者的top-level對象的outlets應該使用strong,其它狀況下應該使用weak修飾outlets。(詳情見 Nib Files in Resource Programming Guide

棧變量初始化爲 nil

使用ARCstrongweakautoreleasing的棧變量如今會默認初始化爲nil。例如:

- (void)myMethod {
    NSString *name;
    NSLog(@"name: %@", name);
}
複製代碼

打印name的值爲null,而不是程序Crash

使用編譯器標誌啓用和禁用 ARC

使用-fobjc-arc編譯器標誌啓用ARC。若是對你來講,某些文件使用MRC更方便,那你能夠僅對部分文件使用ARC。對於使用ARC做爲默認方式的項目,可使用-fno-objc-arc編譯器標誌爲指定文件禁用ARC。以下圖所示:

ARC支持 Xcode 4.2 及更高版本、OS X v10.6 及更高版本 (64-bit applications) 、iOS 4 及更高版本。但 OS X v10.6 和 iOS 4 不支持weak弱引用。Xcode 4.1 及更早版本中不支持ARC

Managing Toll-Free Bridging

Toll-Free Bridging

你在項目中可能會使用到Core Foundation樣式的對象,它可能來自Core Foundation框架或者採用Core Foundation約定標準的其它框架如Core Graphics

編譯器不會自動管理Core Foundation對象的生命週期,你必須根據Core Foundation內存管理規則調用CFRetainCFRelease。請參閱《Memory Management Programming Guide for Core Foundation》

MRC下,咱們能夠直接在Objective-C指針類型idC指針類型void *之間進行強制轉換,如Foundation對象和Core Foundation對象進行轉換。因爲都是手動管理內存,無須關心內存管理權的移交問題。

ARC下,進行Foundation對象和Core Foundation對象的類型轉換,須要使用Toll-Free Bridging橋接)告訴編譯器對象的全部權語義。你要選擇使用__bridge__bridge_retained__bridge_transfer這三種橋接方案中的一種來肯定對象的內存管理權移交問題,它們的做用分別以下:

橋接方案 用法 內存管理權 引用計數
__bridge F <=> CF 不改變 不改變
__bridge_retained
(或 CFBridgingRetain)
F => CF ARC 管理 => 手動管理
(你負責調用 CFRelease 或
相關函數來放棄對象全部權)
+1
__bridge_transfer
(或 CFBridgingRelease)
CF => F 手動管理 => ARC 管理
(ARC 負責放棄對象的全部權)
+1 再 -1,不改變
  • __bridge(經常使用):不改變對象的內存管理權全部者。 原本由ARC管理的Foundation對象,轉換成Core Foundation對象後繼續由ARC管理; 原本由開發者手動管理的Core Foundation對象,轉換成Foundation對象後繼續由開發者手動管理。 下面以NSMutableArray對象和CFMutableArrayRef對象爲例:
// 原本由 ARC 管理
    NSMutableArray *mArray = [[NSMutableArray alloc] init];
    // 轉換後繼續由 ARC 管理 
    CFMutableArrayRef cfMArray = (__bridge CFMutableArrayRef)(mArray); 

    // 原本由開發者手動管理
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); 
    // 轉換後繼續由開發者手動管理
    NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray); 
    ...
    // 在不須要該對象時須要手動釋放
    CFRelease(cfMArray); 
複製代碼

__bridge的安全性與賦值給__unsafe_unretained修飾符相近甚至更低,若是使用不當沒有注意對象的釋放,就會因懸垂指針而致使Crash

__bridge轉換後不改變對象的引用計數,好比咱們將id類型轉換爲void *類型,咱們在使用void *以前該對象被銷燬了,那麼咱們再使用void *訪問該對象確定會Crash。因此void *指針建議當即使用,若是咱們要保存這個void *指針留着之後使用,那麼建議使用__bridge_retain

而在使用__bridgevoid *類型轉換爲id類型時,必定要注意此時對象的內存管理仍是由開發者手動管理,記得在不須要對象時進行釋放,不然內存泄漏!

如下給出幾個 「使用__bridgevoid *類型轉換爲id類型」 的示例代碼,要注意轉換後仍是由開發者手動管理內存,因此即便離開做用域,該對象還保存在內存中。

// 使用 __strong
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray);
    
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 2, 由於 mArray 是 __strong, 因此增長引用計數
    NSLog(@"%ld", _objc_rootRetainCount(mArray));  // 1, 可是使用 _objc_rootRetainCount 打印出來是 1 ?
     
    // 在不須要該對象時進行釋放
    CFRelease(cfMArray); 
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 1, 由於 __strong 做用域還沒結束,還有強指針引用着
    NSLog(@"%ld", _objc_rootRetainCount(mArray));  // 1
    
   // 在 __strong 做用域結束前,還能夠訪問該對象
   // 等 __strong 做用域結束,該對象就會銷燬,再訪問就會崩潰
複製代碼
// 使用 __strong
    CFMutableArrayRef cfMArray;
    {
        cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
        NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray));  // 2, 由於 mArray 是 __strong, 因此增長引用計數
    }
    NSLog(@"%ld", CFGetRetainCount(cfMArray));      // 1, __strong 做用域結束
    CFRelease(cfMArray); // 釋放對象,不然內存泄漏
    // 可使用 CFShow 函數打印 CF 對象
    CFShow(cfMArray);    // 再次訪問就會崩潰
複製代碼
// 使用 __weak
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    NSMutableArray __weak *mArray = (__bridge NSMutableArray *)(cfMArray);
    
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 1, 由於 mArray 是 __weak, 因此不增長引用計數
    NSLog(@"%ld", _objc_rootRetainCount(mArray));  // 1
    
    /* * 使用 mArray */
    
    // 在不須要該對象時進行釋放
    CFRelease(cfMArray);
    
    NSLog(@"%@",mArray);  // nil, 這就是使用 __weak 的好處,在指向的對象被銷燬的時候會自動置指針爲 nil,再次訪問也不會崩潰
複製代碼
// 使用 __weak
    NSMutableArray __weak *mArray;
    {
        CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
        mArray = (__bridge NSMutableArray *)(cfMArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray));  // 1, 由於 mArray 是 __weak, 因此不增長引用計數
    }
    CFMutableArrayRef cfMArray =  (__bridge CFMutableArrayRef)(mArray);
    NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 1, 可見即便出了做用域,對象也還沒釋放,由於內存管理權在咱們
    
    CFRelease(cfMArray); // 釋放對象,不然內存泄漏
複製代碼
  • __bridge_retained:用在Foundation對象轉換成Core Foundation對象時,進行ARC內存管理權的剝奪。 原本由ARC管理的Foundation對象,轉換成Core Foundation對象後,ARC再也不繼續管理該對象,須要由開發者本身手動釋放該對象,不然會發生內存泄漏。
// 原本由 ARC 管理
    NSMutableArray *mArray = [[NSMutableArray alloc] init];       
    // 轉換後由開發者手動管理 
    CFMutableArrayRef cfMArray = (__bridge_retained CFMutableArrayRef)(mArray); 
// CFMutableArrayRef cfMArray = (CFMutableArrayRef)CFBridgingRetain(mArray); // 另外一種等效寫法
    ...
    CFRelease(cfMArray);  // 在不須要該對象的時候記得手動釋放
複製代碼

__bridge_retained顧名思義會對對象retain,使轉換賦值的變量也持有該對象,對象的引用計數 +1。因爲轉換後由開發者進行手動管理,因此再不須要該對象的時候記得調用CFRelease釋放對象,不然內存泄漏。

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

以上代碼若是在MRC下至關於:

id obj = [[NSObject alloc] init];

    void *p = obj;
    [(id)p retain];
複製代碼

查看引用計數

ARC下,咱們可使用_objc_rootRetainCount函數查看對象的引用計數(該函數有時候不許確)。對於Core Foundation對象,咱們可使用CFGetRetainCount函數查看引用計數。

uintptr_t _objc_rootRetainCount(id obj);
複製代碼

打印上面代碼的obj對象的引用計數,發現其引用計數確實增長。

id obj = [[NSObject alloc] init];
    NSLog(@"%ld", _objc_rootRetainCount(obj)); // 1
    void *p = (__bridge_retained void *)(obj);
    NSLog(@"%ld", _objc_rootRetainCount(obj)); // 2
    NSLog(@"%ld", CFGetRetainCount(p));        // 2
複製代碼

如下給出幾個示例代碼:

CFMutableArrayRef cfMArray;
    {
        NSMutableArray *mArray = [[NSMutableArray alloc] init];
        cfMArray = (__bridge_retained CFMutableArrayRef)(mArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 2, 由於 mArray 是 __strong, 並且使用了 __bridge_retained
    }
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 1, __strong 做用域結束
    CFRelease(cfMArray); // 在不須要使用的時候釋放,防止內存泄漏
複製代碼

若是將上面的代碼由__bridge_retained改成使用__bridge會怎樣?

CFMutableArrayRef cfMArray;
    {
        NSMutableArray *mArray = [[NSMutableArray alloc] init];
        cfMArray = (__bridge CFMutableArrayRef)(mArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 1, 由於 mArray 是 __strong, 且使用 __bridge 仍是由 ARC 管理,不增長引用計數
    }
    NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 程序崩潰,由於對象已銷燬
複製代碼
  • __bridge_transfer:用在Core Foundation對象轉換成Foundation對象時,進行內存管理權的移交。 原本由開發者手動管理的Core Foundation對象,轉換成Foundation對象後,將內存管理權移交給ARC,開發者不用再關心對象的釋放問題,不用擔憂內存泄漏。
// 原本由開發者手動管理
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    // 轉換後由 ARC 管理
    NSMutableArray *mArray = (__bridge_transfer NSMutableArray *)(cfMArray);
// NSMutableArray *mArray = CFBridgingRelease(cfMArray); // 另外一種等效寫法
複製代碼

__bridge_transfer做用如其名,移交內存管理權。它的實現跟__bridge_retained相反,會release被轉換的變量持有的對象,但同時它在賦值給轉換的變量時會對對象進行retain,因此引用計數不變。也就是說,對於Core Foundation引用計數語義而言,對象是釋放的,可是ARC保留了對它的引用。

id obj = (__bridge_transfer void *)(p);
複製代碼

以上代碼若是在MRC下至關於:

id obj = (id)p;
    [obj retain];
    [(id)p release];
複製代碼

下面也給出一個示例代碼:

CFMutableArrayRef cfMArray;
    {
        cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
        NSMutableArray *mArray = (__bridge_transfer NSMutableArray *)(cfMArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray));    // 1, 由於 cfMArray 指針指向的對象存在,因此仍然能夠經過該指針訪問
        NSLog(@"%ld", _objc_rootRetainCount(mArray)); // 1, mArray 爲 __strong
    }
    // __strong 做用域結束,ARC 對該對象進行了釋放
    NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 再次訪問就會崩潰
複製代碼

若是將上面的代碼由__bridge_transfer改成使用__bridge會怎樣? 其實在__bridge講解中已經給出了示例代碼,若是不釋放就會形成內存泄漏。

以上提到了能夠替代__bridge_retained__bridge_transfer的兩個函數:CFBridgingRetainCFBridgingRelease,下面咱們來看一下函數實現:

/* Foundation - NSObject.h */
#if __has_feature(objc_arc) // ARC

// After using a CFBridgingRetain on an NSObject, the caller must take responsibility for calling CFRelease at an appropriate time.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
    return (__bridge_retained CFTypeRef)X;
}

NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) {
    return (__bridge_transfer id)X;
}

#else // MRC

// This function is intended for use while converting to ARC mode only.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
    return X ? CFRetain((CFTypeRef)X) : NULL;
}

// Casts a CoreFoundation object to an Objective-C object, transferring ownership to ARC (ie. no need to CFRelease to balance a prior +1 CFRetain count). NS_RETURNS_RETAINED is used to indicate that the Objective-C object returned has +1 retain count. So the object is 'released' as far as CoreFoundation reference counting semantics are concerned, but retained (and in need of releasing) in the view of ARC. This function is intended for use while converting to ARC mode only.
NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) NS_RETURNS_RETAINED {
    return [(id)CFMakeCollectable(X) autorelease];
}

#endif
複製代碼

能夠看到在ARC下,這兩個函數就是使用了__bridge_retained__bridge_transfer

小結:ARC下,必須恰當使用Toll-Free Bridging橋接)在Foundation對象和Core Foundation對象之間進行類型轉換,不然可能會致使內存泄漏。

建議:

  • Foundation對象轉爲Core Foundation對象時,若是咱們當即使用該Core Foundation對象,使用__bridge;若是咱們想保存着之後使用,使用__bridge_retained,可是要記得在使用完調用CFRelease釋放對象。
  • Core Foundation對象轉爲Foundation對象時,使用__bridge_transfer

編譯器處理從 Cocoa 方法返回的 CF 對象

編譯器知道返回Core Foundation對象的Objective-C方法遵循歷史 Cocoa 命名約定。例如,編譯器知道,在iOS中,UIColorCGColor方法返回的CGColor並不持有(由於方法名不是以alloc/new/copy/mutableCopy開頭)。因此你仍然必須使用適當的類型轉換,如如下示例所示:

NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
    [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
複製代碼

不然編譯警告:

NSMutableArray *colors = [NSMutableArray arrayWithObject:[[UIColor darkGrayColor] CGColor]];
    // Incompatible pointer types sending 'CGColorRef _Nonnull' (aka 'struct CGColor *') to parameter of type 'id _Nonnull'
    // 不兼容的指針類型,將 CGColorRef(又稱 struct CGColor *)做爲 id 類型參數傳入
複製代碼

使用橋接轉換函數參數

當在函數調用中在Objective-CCore Foundation對象之間進行轉換時,須要告訴編譯器參數的全部權語義。Core Foundation對象的全部權規則請參閱《Memory Management Programming Guide for Core Foundation》Objective-C的全部權規則請參閱《從 MRC 提及 —— 內存管理策略》章節。

以下實例所示,NSArray對象做爲CGGradientCreateWithColors函數的參數傳入,它的全部權不會傳遞給該函數,所以須要使用__bridge進行強制轉換。

NSArray *colors = <#An array of colors#>;
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
複製代碼

如下實例中使用了Core Foundation對象以及Objective-CCore Foundation對象之間進行轉換,同時須要注意Core Foundation對象的內存管理。

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGFloat locations[2] = {0.0, 1.0};
    NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
    [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
    CGColorSpaceRelease(colorSpace);  // Release owned Core Foundation object.
    CGPoint startPoint = CGPointMake(0.0, 0.0);
    CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
    CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint,
                                kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
    CGGradientRelease(gradient);  // Release owned Core Foundation object.
}
複製代碼

ARC 的實現

ARC僅僅依靠LLVM編譯器是沒法完成內存管理工做的,它還須要Runtime的支持。就好比__weak修飾符,若是沒有Runtime,那麼在對象dealloc時就不會將__weak變量置爲nilARC由如下工具、庫來實現:

  • clang(LLVM 編譯器)3.0 以上
  • objc4 Objective-C 運行時庫 493.9 以上

轉換項目時的常見問題

除了以上說明的幾點ARC新規則之外,ARC下還要注意如下幾個問題,也是MRC轉換到ARC項目的常見問題:

  • ARC要求你在init方法中將[super init]的結果分配給self
self = [super init];
    if (self) {
    ...
複製代碼
  • 你沒法實現自定義retainrelease方法。 實現自定義retainrelease方法會破壞weak弱指針。你想要這麼作的緣由可能以下:

    • ① 性能
      請不要再這樣作了,NSObjectretainrelease方法的實現如今要快得多。若是你仍然發現問題,請提交錯誤給蘋果。
    • ② 實現自定義weak弱指針系統
      請改用__weak
    • ③ 實現單例類
      請改用shared instance模式。或者,使用類方法替代實例方法,這樣能夠避免建立對象。
  • 「直接賦值」 的實例變量變成強引用了。

ARC以前,實例變量是弱引用(非持有引用) —— 直接將對象分配給實例變量並不延長對象的生命週期。爲了使屬性變strong,你一般會實現或使用@synthesize合成 「調用適當內存管理方法」 的訪問器方法。相反,有時你爲了維持一個弱引用,你可能會像如下實例這樣實現訪問器方法。

@interface MyClass : Superclass {
    id thing; // Weak reference.
}
// ...
@end

@implementation MyClass
- (id)thing {
    return thing;
}
- (void)setThing:(id)newThing {
    thing = newThing;
}
// ...
@end
複製代碼

對於ARC,實例變量默認是strong強引用 —— 直接將對象分配給實例變量會延長對象的生命週期。遷移工具在將MRC代碼轉換爲ARC代碼時,沒法肯定它該使用strong仍是weak,因此默認使用strong。 若要保持與MRC下一致,必須將實例變量使用__weak修飾,或使用weak關鍵字的屬性。

@interface MyClass : Superclass {
    id __weak thing;
}
// ...
@end

@implementation MyClass
- (id)thing {
    return thing;
}
- (void)setThing:(id)newThing {
    thing = newThing;
}
// ...
@end
複製代碼

或者:

@interface MyClass : Superclass
@property (weak) id thing;
// ...
@end

@implementation MyClass
@synthesize thing;
// ...
@end
複製代碼

ARC 補充

__weak 黑科技

在全部權修飾符中咱們簡單介紹了__weak修飾符。實際上,除了在MRC下沒法使用__weak修飾符之外,還有其餘沒法使用__weak修飾符的狀況。

例如,有一些類是不支持__weak修飾符的,好比NSMachPort。這些類重寫了retain / release並實現該類獨自的引用計數機制。可是賦值以及使用附有__weak修飾符的變量都必須恰當地使用 objc4 運行時庫中的函數,所以獨自實現引用計數機制的類大多不支持__weak修飾符。

NSMachPort __weak *port = [NSMachPort new]; 
    // 編譯錯誤:Class is incompatible with __weak references 類與弱引用不兼容
複製代碼

不支持__weak修飾符的類,其類的聲明中添加了NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE宏,該宏的定義以下。

// Marks classes which cannot participate in the ARC weak reference feature.
#if __has_attribute(objc_arc_weak_reference_unavailable)
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE __attribute__((objc_arc_weak_reference_unavailable))
#else
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE
#endif
複製代碼

若是將不支持__weak的類的對象賦值給__weak修飾符的變量,一旦編譯器檢測出來就會報告編譯錯誤。可是在 Cocoa 框架中,不支持__weak修飾符的類極爲罕見,所以沒有必要太過擔憂。

__weak黑科技來了!!!!!

還有一種狀況也不能使用__weak修飾符。就是當對象的allowsWeakReference/retainWeakReference實例方法返回NO時,這兩個方法的聲明以下:

- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;
複製代碼

這兩個方法的默認實現是返回YES

若是咱們在類中重寫了allowsWeakReference方法並返回NO,那麼若是咱們將該類的實例對象賦值給__weak修飾符的變量,那麼程序就會Crash

例如咱們在HTPerson類中作了此操做,則如下代碼運行就會Crash

HTPerson __weak *p = [[HTPerson alloc] init];
複製代碼
// 沒法對 HTPerson 類的實例持有弱引用。多是此對象被過分釋放,或者正在銷燬。
objc[18094]: Cannot form weak reference to instance (0x600001d7c2a0) of class HTPerson. It is possible that this object was over-released, or is in the process of deallocation.
(lldb) 
複製代碼

因此,對於全部allowsWeakReference方法返回NO的類的實例都絕對不能使用__weak修飾符。

另外,若是實例對象的retainWeakReference方法返回NO,那麼賦值該對象__weak修飾符的變量將爲nil,表明沒法經過__weak變量訪問該對象。

好比如下示例代碼:

HTPerson *p1 = [[HTPerson alloc] init];    
    HTPerson __weak *p2 = p1;
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);

/* 打印以下: <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> */
複製代碼

因爲p1__strong持有對象的強引用,因此在p1做用域結束前,該對象都存在,使用__weak修飾的p2訪問該對象沒問題。

下面在HTPerson類中重寫retainWeakReference方法:

@interface HTPerson ()
{
    NSUInteger count;
}

@implementation HTPerson
- (BOOL)retainWeakReference
{
    if (++count > 3) {
        return NO;
    }
    return [super retainWeakReference];    
}
@end
複製代碼

再次運行以上代碼,發現從第 4 次開始,經過__weak變量就沒法訪問到對象,由於這時候retainWeakReference方法返回值爲NO

HTPerson *p1 = [[HTPerson alloc] init];    
    HTPerson __weak *p2 = p1;
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);

/* 打印以下: <HTPerson: 0x600003e23ba0> <HTPerson: 0x600003e23ba0> <HTPerson: 0x600003e23ba0> (null) (null) */
複製代碼

查看引用計數

ARC下,咱們可使用_objc_rootRetainCount函數查看對象的引用計數。

uintptr_t _objc_rootRetainCount(id obj);
複製代碼

但實際上並不能徹底信任該函數取得的數值。對於已釋放的對象以及不正確的對象地址,有時也返回 「1」。另外,在多線程中使用對象的引用計數數值,由於存在競爭條件的問題,因此取得的數值不必定徹底可信。 雖然在調試中_objc_rootRetainCount函數頗有用,但最好在瞭解其所具備的問題的基礎上來使用。

蘋果對 ARC 一些問題的回答

Q: 我應該如何看待 ARC ?它將 retains/releases 調用的代碼放在哪了?

嘗試不要去思考ARCretains/releases調用的代碼放在哪裏,而是思考應用程序算法,思考對象的strongweak指針、全部權、以及可能產生的循環引用。

Q: 我還須要爲個人對象編寫 dealloc 方法嗎?

有時候須要。 由於ARC不會自動處理malloc/freeCore Foundation對象的生命週期管理、文件描述符等等,因此你仍然能夠經過編寫dealloc方法來釋放這些資源。 你沒必要(實際上不能)釋放實例變量,但可能須要對系統類和其餘未使用ARC編寫的代碼調用[self setDelegate:nil]ARC下的dealloc方法中不須要且不容許調用[super dealloc]Runtime會自動處理。

Q: ARC 中仍然可能存在循環引用嗎?

是的,ARC自動retain/release,也繼承了循環引用問題。幸運的是,遷移到ARC的代碼不多開始泄漏,由於屬性已經聲明是否retain

Q: block 是如何在 ARC 中工做的?

ARC下,編譯器會根據狀況自動將棧上的block複製到堆上,好比block做爲函數返回值時,這樣你就沒必要再調用Block Copy

須要注意的一件事是,在ARC下,NSString * __block myString這樣寫的話,block會對NSString對象強引用,而不是形成懸垂指針問題。若是你要和MRC保持一致,請使用__block NSString * __unsafe_unretained myString或(更好的是)使用__block NSString * __weak myString

Q: 我能夠在 ARC 下建立一個 retained 指針的 C 數組嗎?

能夠,以下示例所示:

// Note calloc() to get zero-filled memory.
__strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(entries, sizeof(SomeClass *));
for (int i = 0; i < entries; i++) {
     dynamicArray[i] = [[SomeClass alloc] init];
}
 
// When you're done, set each entry to nil to tell ARC to release the object.
for (int i = 0; i < entries; i++) {
     dynamicArray[i] = nil;
}
free(dynamicArray);
複製代碼

這裏有一些注意點:

  • 在某些狀況下,你須要編寫__strong SomeClass **,由於默認是__autoreleasing SomeClass **
  • 分配的內存區域必須初始化爲 0(zero-filled)。
  • free數組以前,必須將每一個元素賦值爲nilmemsetbzero將不起做用)。
  • 你應該避免使用memcpyrealloc

Q: ARC 速度上慢嗎?

不。編譯器有效地消除了許多無關的retain/release調用,而且已經投入了大量精力來加速 Objective-C 運行時。特別的是,當方法的調用者是ARC代碼時,常見的 「return a retain/autoreleased object」 模式要快不少,而且實際上並不將對象放入自動釋放池中。

備註:MRC下,經過alloc/new/copy/mutableCopy或以這些命名開頭的方法建立的對象直接持有,而經過其它方法建立的對象會經過調用autorelease加入到自動釋放池中。而ARC下不能調用autorelease方法,那麼ARC怎麼作到這一點呢?

《Objective-C 高級編程:iOS 與 OS X 多線程和內存管理》 書中是說:在ARC下,編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開始,若是不是則自動將返回值的對象註冊到@autoreleasepool

但通過測試,發現並非如此。並且,之前在MRC下經過array類方法建立的NSMutableArray對象會被加入到@autoreleasepool,可是在ARC下並不會。

因此,根據方法名並不能判斷ARC會不會將對象加入到@autoreleasepool。若是咱們須要這麼作,建議使用__autoreleasing修飾符。

須要注意的一個問題是,優化器不是在常見的調試配置中運行的,因此預計在-O0模式下將會比-Os模式下看到更多的retain/release調用。

Q: ARC 在 ObjC++ 模式下工做嗎?

是。你甚至能夠在類和容器中放置strong/weakid對象。ARC編譯器會在複製構造函數和析構函數等中合成retain/release邏輯以使其運行。

Q: 哪些類不支持 weak 弱引用?

你當前沒法建立對如下類的實例的weak弱引用:NSATSTypesetter、NSColorSpace、NSFont、NSMenuView、NSParagraphStyle、NSSimpleHorizontalTypesetter 和 NSTextView。

注意: 此外,在 OS X v10.7 中,你沒法建立對 NSFontManager,NSFontPanel、NSImage、NSTableCellView、NSViewController、NSWindow 和 NSWindowController 實例的weak弱引用。此外,在 OS X v10.7 中,AV Foundation 框架中的任何類都不支持weak弱引用。

此外,你沒法在ARC下建立 NSHashTable、NSMapTable 和 NSPointerArray 類的實例的weak弱引用。

Q: 當我繼承一個使用了 NSCopyObject 的類,如 NSCell 時,我須要作些什麼?

沒什麼特別的。ARC會關注之前必須顯式添加額外retain的狀況。使用ARC,全部的複製方法只須要複製實例變量就能夠了。

Q: 我能夠對指定文件選擇退出ARC而使用MRC嗎?

能夠。當你遷移項目到ARC或建立一個ARC項目時,因此Objective-C源文件的默認編譯器標誌將設置爲-fobjc-arc,你可使用-fno-objc-arc編譯器標誌爲指定的類禁用ARC。操做以下圖所示:

Q: 在 Mac 上是否棄用了 GC (Garbage Collection) 機制?

OS X Mountain Lion v10.8 中不推薦使用GC機制,而且將在 OS X 的將來版本中刪除GC機制。ARC是推薦的替代技術。爲了幫助現有應用程序遷移,Xcode 4.3 及更高版本中的ARC遷移工具支持將使用GC的 OS X 應用程序遷移到ARC

注意: 對於面向 Mac App Store 的應用,Apple 強烈建議你儘快使用ARC替換GC,由於 Mac App Store Guidelines 禁止使用已棄用的技術,不然不會經過審覈,詳情請參閱 Mac App Store Review Guidelines

相關文章
相關標籤/搜索