引言:
咱們都知道Objective-C經過「引用計數」來管理對象釋放。基本原理就是管理對象的持有者個數(引用計數),引用計數爲0時釋放對象。如今有ARC(自動引用計數),則無需咱們本身顯式持有(retain)和釋放(release)對象,ARC經過對對像加上全部權修飾符(__strong等),編譯器經過對象的全部權修飾符將會自動鍵入引用計數管理(根據全部權修飾符自動鍵入retain、release、autorelease)javascript
本文主要敘述引用計數的實現原理,ARC和MRC在使用上的區別,以及編譯器在ARC中爲咱們作了什麼。java
使用棧(後進先出)來管理NSAutoreleasePool對象,所以能夠隨時拿到最近(hotPage)的NSAutoreleasePool,調用對象的autorelease方法,會在hotPage中的內部數組將對象加入進去。當NSAutoreleasePool出棧時,調用內部數組中元素的release方法便可。objective-c
引用計數表:使用散列表實現引用計數表,key爲對象的地址的散列值,value爲引用計數和內存塊地址。
retain:經過對象的地址在引用計數表中找到引用計數,若是retainCount超過最大值,則拋異常,不然retainCount加1。
release:經過對象的地址在引用計數表中找到引用計數,若是retainCount值爲0,則拋異常。不然retainCount減1,若是retainCount減1後爲0,則從引用計數表衝移除,並調用對象的dealloc方法。express
Objective-C對象的ARC是經過全部權修飾符來管理對象的持有和釋放。全部權修飾符一共有4種:數組
取得本身生成而且持有對象:
使用ARC:安全
{
id obj1 = [NSObject new];
//至關於
//id __strong obj1 = [NSObject new];
}複製代碼
不使用ARC:框架
{
id obj1 = [NSObject new];
//在變量做用域結束時插入release
[obj1 release];
}複製代碼
取得非本身生成並持有對象:
使用ARC:函數
- (void)create {
test = [self object];
NSLog(@"ARC------------------ARC");
NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}
- (id)object {
id obj = [[MyObject alloc] init];
return obj;
}
- (void)printRetainCount {
NSLog(@"ARC------------------ARC");
NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}複製代碼
不使用ARC:優化
- (void)create {
if (test) {
NSLog(@"MRC------------------MRC");
NSLog(@"release");
[test release];
}
test = [self object];
[test retain];
NSLog(@"MRC------------------MRC");
NSLog(@"after create count = %ld", [test retainCount]);
}
- (id)object {
id obj = [[MyObject alloc] init];
[obj autorelease];
return obj;
}
- (void)printRetainCount {
NSLog(@"MRC------------------MRC");
NSLog(@"retain count = %ld", [test retainCount]);
}複製代碼
打印結果:spa
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:51.212 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.213 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] release**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:36:03.540 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:36:03.543 ARCLearn[53676:16388070] retain count = 1**複製代碼
能夠看到使用ARC的對象持有,在返回對象時,並無把對象註冊到autoreleasepool中,下面爲ARC的autoreleasepool打印:
**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 13 releases pending.**
**objc[54013]: [0x7fa3c9811000] ................ PAGE (hot) (cold)**
**objc[54013]: [0x7fa3c9811038] ################ POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040] 0x60800002e3c0 __NSCFString**
**objc[54013]: [0x7fa3c9811048] ################ POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811058] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811060] 0x6000002712c0 __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068] 0x7fa3c8600bf0 UIWindow**
**objc[54013]: [0x7fa3c9811070] 0x60000008e6f0 __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078] 0x6000000567d0 __NSSetM**
**objc[54013]: [0x7fa3c9811080] 0x600000052de0 __NSSetM**
**objc[54013]: [0x7fa3c9811088] 0x600000053b30 __NSSetM**
**objc[54013]: [0x7fa3c9811090] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c9811098] 0x600000271640 __NSCFString**
**objc[54013]: ##############**複製代碼
下面爲MRC的autoreleasepool打印:
**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 14 releases pending.**
**objc[54013]: [0x7fa3c9811000] ................ PAGE (hot) (cold)**
**objc[54013]: [0x7fa3c9811038] ################ POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040] 0x60800002e3c0 __NSCFString**
**objc[54013]: [0x7fa3c9811048] ################ POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811058] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811060] 0x6000002712c0 __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068] 0x7fa3c8600bf0 UIWindow**
**objc[54013]: [0x7fa3c9811070] 0x60000008e6f0 __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078] 0x6000000567d0 __NSSetM**
**objc[54013]: [0x7fa3c9811080] 0x600000052de0 __NSSetM**
**objc[54013]: [0x7fa3c9811088] 0x600000053b30 __NSSetM**
**objc[54013]: [0x7fa3c9811090] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c9811098] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c98110a0] 0x608000017200 MyObject**
**objc[54013]: ##############**複製代碼
在autoreleasepool裏有MyObject對象。
爲何ARC沒有把對象放到autoreleasepool裏了?它是怎樣持有非本身生成的對象的?
代碼:
{
id __strong obj = [NSMutableArray array];
}複製代碼
轉換爲編譯器模擬代碼:
{
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
}複製代碼
objc_retainAutoreleasedReturnValue函數,顧名思義:讓obj持有(retain)池中(autoreleased)返回的值(retuenValue);
代碼:
+ (id)array {
return [[NSMutableArray alloc] init];
}複製代碼
轉換爲編譯器模擬代碼:
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}複製代碼
objc_autoreleaseReturnValue函數,顧名思義:把obj註冊在池中(調用obj的autorelease方法),並返回。
所以按照上面的理解objc_autoreleaseReturnValue
將返回對象註冊到池子中,objc_retainAutoreleasedReturnValue`持有池中的對象。
可是事實不是那麼簡單:
可是objc_autoreleaseReturnValue遠遠不是autorelease那麼簡單。objc_autoreleaseReturnValue函數會檢查使用該函數的方法或函數調用方的執行命令列表,若是方法或函數的調用方在調用了方法或函數後緊接着調用了objc_retainAutoreleasedReturnValue,那就不會將返回的對象註冊到autoreleasepool中,而是直接傳遞到方法或函數的調用方。
所以objc_retainAutoreleasedReturnValue也不是retain那麼簡單,即便放回對象不註冊到autoreleasepool中,也能正確的獲取對象。
經過objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue協做,能夠不將對象註冊到autoreleasepool中而直接傳遞,這一過程達到了最優化。
如今咱們添加一個weakTest變量,使用weak全部權修飾符
在ARC中實現:
@interface ARC() {
id test;
id __weak weakTest;
}
@end
@implementation ARC
- (void)create {
test = [self object];
weakTest = test;
_objc_autoreleasePoolPrint();
NSLog(@"ARC------------------ARC");
NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}
- (id)object {
id obj = [[MyObject alloc] init];
return obj;
}
- (void)printRetainCount {
NSLog(@"ARC------------------ARC");
NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}
@end複製代碼
打印結果:
**2016-12-13 16:24:30.934 ARCLearn[55038:16489628] ARC------------------ARC**
**2016-12-13 16:24:30.935 ARCLearn[55038:16489628] after create count = 1**複製代碼
由此能夠看出__weak並無增長引用個數。
添加empty函數,使test爲nil:
- (void)empty {
if (weakTest) {
NSLog(@"weak is %p", weakTest);
}
test = nil;
if (!weakTest) {
NSLog(@"weak change to nil");
}
}複製代碼
打印結果:
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] weak is 0x608000011530**
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] myobject dealloc**
**2016-12-13 16:33:06.302 ARCLearn[55349:16509066] weak change to nil**複製代碼
由此能夠看出若附有__weak修飾符的變量所引用的對象被廢棄,則將nil賦值給該變量。
是如何實現將nil值賦值給引用爲0的__weak對象?
{
id __weak obj1 = obj;
}複製代碼
轉換爲編譯器模擬代碼:
{
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(obj1);
}複製代碼
其中objc_initWeak(&obj1,obj)的實現:
obj1 = 0;
objc_storyWeak(&obj1, obj);複製代碼
objc_destroyWeak的實現:
objc_storyWeak(&obj1, 0);複製代碼
因此合在一塊兒是:
{
id obj1;
obj1 = 0;
objc_storyWeak(&obj1, obj);
objc_storyWeak(&obj1, 0);
}複製代碼
其中objc_storyWeak就是針對weak表(散列表)操做:
廢棄引用爲0的對象的流程:
將對象賦值給附有__autoreleasing修飾符的變量等同於ARC無效時調用對象的autorelease方法。
ARC有效:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
id __strong obj1 = [[NSObject alloc] init];
id __autoreleasing obj2 = obj1;
id __strong obj3 = [NSMutableArray array];
id __autoreleasing obj4 = obj3;
}複製代碼
ARC無效:
@autoreleasepool {
id obj = [[NSObject alloc] init];
[obj autorelease];
id obj1 = [[NSObject alloc] init];
id obj2 = obj1;
[obj2 retain];
[obj2 autorelease];
id obj3 = [NSMutableArray array];
[obj3 retain];
id obj4 = obj3;
[obj4 retain];
[obj4 autorelease];
[obj3 release];
}複製代碼
注意:顯式的附加autoreleasing修飾符同顯式地添加strong修飾符同樣罕見。咱們常常非顯式的使用__autoreleasing修飾符。
隱式使用__autoreleasing:
如:
NSError *error = nil;
BOOL result = [obj performOperationWithError: &error];複製代碼
performOperationWithError聲明爲:
- (BOOL)performOperationWithError:(NSError **)error;複製代碼
自動添加__autoreleasing的代碼:
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
/* 錯誤發生 */
*error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}複製代碼
非ARC的實現:
- (BOOL)performOperationWithError:(NSError **)error {
/* 錯誤發生 */
*error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
[*error autorelease];
return NO;
}複製代碼
由於聲明爲NSError __autoreleasing 類型的error做爲*error被賦值,因此可以返回註冊到autoreleasepool中的對象。
注意:賦值給對象指針時,全部權修飾符必須一致。
編譯器會自動加上全部權修飾符:NSError * __strong error = nil; NSError * __autoreleasing *pError = &error;複製代碼
全部權修飾符不一致會產生編譯錯誤:
Initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
unsafe_unretained修飾符正如其名unsafe所示,是不安全的全部權修飾符。儘管ARC式的內存管理是編譯器的工做,但附有unsafe_unretained修飾符的變量不屬於編譯器的內存管理對象。
ARC實現:
id obj = [[NSObject alloc] init];
id __unsafe_unretained obj1 = obj;複製代碼
非ARC實現:
id obj = [[NSObject alloc] init];
id obj1 = obj;複製代碼
因此就是...編譯器啥都不作。這樣當對象的引用計數爲0,被廢棄後,obj1就是不安全的了(懸垂指針),由於不會被置爲nil。
注意:使用時確保對象確實存在。
何時使用__unsafe_unretained?
Core Foundation對象主要使用在用C語言編寫的Core Foundation框架中,並使用引用計數的對象。
ARC無效時:
id obj = [[NSObject alloc] init];
void *p = obj; //void *至關與oc的id
id o = p;
[o release];複製代碼
ARC有效時,會引發編譯錯誤,要使用「__bridge」轉換。
ARC有效時的代碼:
id obj = [[NSObject alloc] init];
void *p = (__bridge void*)obj;
id o = (__bridge id)p;
[o release];複製代碼
注意:bridge的安全性與賦值給unsafe_unretained修飾符相近,甚至會更低,若是管理時不注意賦值對象的全部者,就會因懸垂指針而致使程序崩潰。
__bridge轉換中還有另兩中轉換,分別爲:
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void*)obj;複製代碼
ARC無效:id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];複製代碼
void *p = 0;
id obj = (__bridge_transfer id)p;複製代碼
ARC無效void *p = 0;
id obj = (id)p;
[obj retain];
[(id)p release];複製代碼
簡書我的主頁:www.jianshu.com/users/b92ab…