ARC
全稱Automatic Reference Counting
,自動引用計數內存管理,是蘋果在 iOS 五、OS X Lion 引入的新的內存管理技術。ARC
經過LLVM
編譯器和Runtime
協做來進行自動管理內存。LLVM
編譯器會在編譯時在合適的地方爲 OC 對象插入retain
、release
和autorelease
代碼來自動管理對象的內存,省去了在MRC
手動引用計數下手動插入這些代碼的工做,減輕了開發者的工做量,讓開發者能夠專一於應用程序的代碼、對象圖以及對象間的關係上。
下圖是蘋果官方文檔給出的從MRC
到ARC
的轉變。html
ARC
的工做原理是在編譯時添加相關代碼,以確保對象可以在必要時存活,但不會一直存活。從概念上講,它經過爲你添加適當的內存管理方法調用來遵循與MRC
相同的內存管理規則。算法
爲了讓編譯器生成正確的代碼,ARC
限制了一些方法的使用以及你使用橋接(toll-free bridging
)的方式,請參閱《Managing Toll-Free Bridging》
章節。ARC
還爲對象引用和屬性聲明引入了新的生命週期修飾符。express
ARC
在Xcode 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
代碼(如刪除retain
和release
調用),而不用從新再建立一個項目(選擇 Edit > Convert > To Objective-C ARC)。遷移工具會將項目中的全部文件轉換爲使用ARC
的模式。若是對於某些文件使用MRC
更方便的話,你能夠選擇僅在部分文件中使用ARC
。數組
ARC
會分析對象的生存期需求,並在編譯時自動插入適當的內存管理方法調用的代碼,而不須要你記住什麼時候使用retain
、release
、autorelease
方法。編譯器還會爲你生成合適的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
下,禁止開發者手動調用這些方法,也禁止使用@selector(retain)
,@selector(release)
等,不然編譯不經過。但你仍然能夠對 Core Foundation 對象使用CFRetain
、CFRelease
等相關函數(請參閱《Managing Toll-Free Bridging》
章節)。
在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;
複製代碼
不管在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'
}
複製代碼
在ARC
下,自動釋放池應使用@autoreleasepool
,禁止使用NSAutoreleasePool
,不然編譯錯誤。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
複製代碼
error:'NSAutoreleasePool' is unavailable: not available in automatic reference counting mode
複製代碼
關於
@autoreleasepool
的原理,能夠參閱《iOS - 聊聊 autorelease 和 @autoreleasepool》。
對於如今的運行時系統(編譯器宏 __ OBJC2 __ 被設定的環境),無論是MRC
仍是ARC
下,區域(NSZone)都已單純地被忽略。
NSZone 《Objective-C 高級編程:iOS 與 OS X 多線程和內存管理》書中對 NSZone 作了以下介紹:
NSZone 是爲防止內存碎片化而引入的結構。對內存分配的區域自己進行多重化管理,根據使用對象的目的、對象的大小分配內存,從而提升了內存管理的效率。 可是,如今的運行時系統已經忽略了區域的概念。運行時系統中的內存管理自己已極具效率,使用區域來管理內存反而會引發內存使用效率低下以及源代碼複雜化等問題。 下圖是使用多重區域防止內存碎片化的例子:![]()
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 語言結構體的成員。
這個問題有如下三種解決方案:
若是你仍是堅持使用結構體,並把對象型變量加入到結構體成員中,可使用如下兩種方案:
Toll-Free Bridging
強制轉換爲void *
類型,請參閱《Managing Toll-Free Bridging》
章節。__unsafe_unretained
修飾符。struct Data {
NSMutableArray __unsafe_unretained *mArray;
};
複製代碼
附有__unsafe_unretained
修飾符的變量不屬於編譯器的內存管理對象。若是管理時不注意賦值對象的全部者,便有可能遭遇內存泄漏或者程序崩潰。這點在使用時應多加註意。
在MRC
下,咱們能夠直接在 id
和 void *
變量之間進行強制轉換。
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];
複製代碼
但在ARC
下,這樣會引發編譯報錯:在Objective-C
指針類型id
和C
指針類型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
中引入了新的屬性關鍵字strong
和weak
,以下所示:
// 如下聲明同:@property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
// 如下聲明相似於:@property(assign)MyClass *myObject;
// 不一樣的是,若是 MyClass 實例被釋放,屬性值賦值爲 nil,而不像 assign 同樣產生懸垂指針。
@property(weak) MyClass *myObject;
複製代碼
strong
和weak
屬性關鍵字分別對應__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
修飾符爲強引用,會持有對象,使其引用計數 +1。該修飾符是對象類型變量的默認修飾符。若是咱們沒有明確指定對象類型變量的全部權修飾符,其默認就爲__strong
修飾符。
id obj = [NSObject alloc] init];
// -> id __strong obj = [NSObject alloc] init];
複製代碼
若是單單靠__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
修飾符的特色正如其名所示,不安全且不會持有對象。
注意: 儘管
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
。
首先講一下自動釋放池,在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》。
在MRC
中咱們能夠給對象發送autorelease
消息來將它註冊到autoreleasepool
中。而在ARC
中autorelease
已禁止調用,咱們可使用__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];
}
複製代碼
前面咱們說過,對象指針的默認全部權修飾符是__strong
。 而二級指針類型(ClassName **
或id *
)的默認全部權修飾符是__autoreleasing
。若是咱們沒有明確指定二級指針類型的全部權修飾符,其默認就會附加上__autoreleasing
修飾符。
好比,咱們常常會在開發中使用到NSError
打印錯誤信息,咱們一般會在方法的參數中傳遞NSError
對象的指針。如NSString
的stringWithContentsOfFile
類方法,其參數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
屬性時,使用weak
關鍵字。
@property (nonatomic, weak) id<protocolName> delegate;
複製代碼
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
中將指針置爲nil
(MRC
不會循環引用,則不用解決)。可是有一個弊端,若是該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
這兩個指令。
@property
:幫咱們自動生成屬性的setter
和getter
方法的聲明。@synthesize
:幫咱們自動生成setter
和getter
方法的實現以及下劃線成員變量。 之前咱們須要手動對每一個@property
添加@synthesize
,而在 iOS 6 以後 LLVM 編譯器引入了 「property autosynthesis
」,即屬性自動合成。換句話說,就是編譯器會自動爲每一個@property
添加@synthesize
。Q:
@synthesize
如今有什麼做用呢?
若是咱們同時重寫了setter
和getter
方法,則編譯器就不會爲這個@property
添加@synthesize
,這時候就不存在下劃線成員變量,因此咱們須要手動添加@synthesize
。@synthesize propertyName = _propertyName; 複製代碼
有時候咱們不但願編譯器爲咱們@synthesize
,咱們但願在程序運行過程當中再去決定該屬性存取方法的實現,就可使用@dynamic
。
@dynamic
:告訴編譯器不用自動進行@synthesize
,等到運行時再添加方法實現,可是它不會影響@property
生成的setter
和getter
方法的聲明。@dynamic
是 OC 爲動態運行時語言的體現。動態運行時語言與編譯時語言的區別:動態運行時語言將函數決議推遲到運行時,編譯時語言在編譯器進行函數決議。@dynamic propertyName;
複製代碼
屬性「內存管理」關鍵字 | 全部權修飾符 |
---|---|
assign | __unsafe_unretained |
unsafe_unretained | __unsafe_unretained |
weak | __weak |
retain | __strong |
strong | __strong |
copy | __strong |
更多關於屬性關鍵字的內容,能夠參閱《OC - 屬性關鍵字和全部權修飾符》。
在ARC
下,iOS
和OS X
平臺中聲明outlets
的模式變得一致。你應該採用的模式爲:在nib
或者storyboard
中,除了來自文件全部者的top-level
對象的outlets
應該使用strong
,其它狀況下應該使用weak
修飾outlets
。(詳情見 Nib Files in Resource Programming Guide)
使用ARC
,strong
、weak
和autoreleasing
的棧變量如今會默認初始化爲nil
。例如:
- (void)myMethod {
NSString *name;
NSLog(@"name: %@", name);
}
複製代碼
打印name
的值爲null
,而不是程序Crash
。
使用-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
。
你在項目中可能會使用到Core Foundation
樣式的對象,它可能來自Core Foundation
框架或者採用Core Foundation
約定標準的其它框架如Core Graphics
。
編譯器不會自動管理Core Foundation
對象的生命週期,你必須根據Core Foundation
內存管理規則調用CFRetain
和CFRelease
。請參閱《Memory Management Programming Guide for Core Foundation》。
在MRC
下,咱們能夠直接在Objective-C
指針類型id
和C
指針類型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,不改變 |
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
。
而在使用__bridge
將void *
類型轉換爲id
類型時,必定要注意此時對象的內存管理仍是由開發者手動管理,記得在不須要對象時進行釋放,不然內存泄漏!
如下給出幾個 「使用__bridge
將void *
類型轉換爲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); // 釋放對象,不然內存泄漏
複製代碼
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)); // 程序崩潰,由於對象已銷燬
複製代碼
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
的兩個函數:CFBridgingRetain
和CFBridgingRelease
,下面咱們來看一下函數實現:
/* 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
。
編譯器知道返回Core Foundation
對象的Objective-C
方法遵循歷史 Cocoa 命名約定。例如,編譯器知道,在iOS
中,UIColor
的CGColor
方法返回的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-C
和Core 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-C
和Core 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
僅僅依靠LLVM
編譯器是沒法完成內存管理工做的,它還須要Runtime
的支持。就好比__weak
修飾符,若是沒有Runtime
,那麼在對象dealloc
時就不會將__weak
變量置爲nil
。 ARC
由如下工具、庫來實現:
除了以上說明的幾點ARC
新規則之外,ARC
下還要注意如下幾個問題,也是MRC
轉換到ARC
項目的常見問題:
ARC
要求你在init
方法中將[super init]
的結果分配給self
。self = [super init];
if (self) {
...
複製代碼
你沒法實現自定義retain
或release
方法。 實現自定義retain
或release
方法會破壞weak
弱指針。你想要這麼作的緣由可能以下:
NSObject
的retain
和release
方法的實現如今要快得多。若是你仍然發現問題,請提交錯誤給蘋果。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
複製代碼
在全部權修飾符中咱們簡單介紹了__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
函數頗有用,但最好在瞭解其所具備的問題的基礎上來使用。
Q: 我應該如何看待 ARC ?它將 retains/releases 調用的代碼放在哪了?
嘗試不要去思考ARC
將retains/releases
調用的代碼放在哪裏,而是思考應用程序算法,思考對象的strong
和weak
指針、全部權、以及可能產生的循環引用。
Q: 我還須要爲個人對象編寫 dealloc 方法嗎?
有時候須要。 由於ARC
不會自動處理malloc/free
、Core 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 **
。zero-filled
)。free
數組以前,必須將每一個元素賦值爲nil
(memset
或bzero
將不起做用)。memcpy
或realloc
。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/weak
的id
對象。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。