《Objective-C 高級編程》引用計數(shù) 筆記摘要

內(nèi)存管理 / 引用計數(shù)

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

  • 自己生成的對象,自己持有。
  • 非自己生成的對象,自己也能持有。
  • 不再需要自己持有的對象時釋放。
  • 非自己持有的對象無法釋放。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 發(fā)生異常
[pool autorelease];
/*
通常在使用 OC,也就是 Foundation 框架時,無論調(diào)用哪一個對象的autorelease實例方法,實現(xiàn)上調(diào)用的都是 NSObject 類的 autorelease 實例方法。但是對于NSAutoreleasePool 類,autorelease方法已被該類重載,因此運行時就會出錯。  
*/

ARC規(guī)則

__weak 修飾的變量需要注冊到 autoreleasepool 對象中

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

// 同上面等價
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);

/*
Q: 為什么在訪問附有 __weak 修飾符的變量時必須訪問注冊到 autoreleasepool 的對象呢?
A: 因為 __weak 只持有對象的弱引用,而在訪問引用對象的過程中,該對象有可能被廢棄。注冊到 autoreleasepool 中能確保對象在 pool 塊結(jié)束之前存在。
*/

對象的指針和 id 指針:NSObject **obj,id *obj

對象 id obj 等價于 id __strong obj。
id 的指針 id *obj 等價于 id __autoreleasing *obj。
對象的指針 NSObject **obj 等價于 NSObject *__autoreleasing *obj。 

上面這些是默認行為,可以手動修改。如 id *obj 默認是 id __autoreleasing *obj,可以改成 id __strong *obj。
  
像這樣,id 的指針或?qū)ο蟮闹羔樤跊]有顯式指定時會被附加上 __autoreleasing 修飾符。
  
**賦值給對象指針時,所有權(quán)修飾符必須一致。**

// 默認就是 __strong
NSerror *error = nil;
// 下面代碼會產(chǎn)生編譯錯誤
// NSError **pError = &error;
// 正確寫法
NSError * __strong *pError = &error;

// __weak
NSError __weak *error = nil;
NSError * __weak *pError = &error;
  
// __unsafe_unretained
NSError * __unsafe_unretained *unsafeError = nil;
NSError * __unsafe_unretained *pUnsafeError = &unsafeError;

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

// 為什么上面沒有編譯報錯呢?因為編譯器自動將源碼轉(zhuǎn)成了下面形式。
NSError * __strong *error = nil;
NSError * __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;

- (BOOL)performOperationWithError:(NSError* __autoreleasing *)error;

對象型變量不能作為 C 語言結(jié)構(gòu)體的成員。要把對象型變量加入到結(jié)構(gòu)體成員中,需要強制轉(zhuǎn)換為 void * 或者 __unsafe_unretained 修飾符。

struct Data {
  NSMutableArray __unsafe_unretained *array;
}

顯示轉(zhuǎn)換 id 和 void *。( __bridge_retained / __bridge_transfer )

/* ARC 無效時,id 和 void * 互轉(zhuǎn)沒問題 */
// id 轉(zhuǎn) void *
id obj = [[NSObject alloc] init];
void * p = obj;
// 更進一步
id o = p;
[o release];

/* 在 ARC 有效時,上述互轉(zhuǎn)會編譯錯誤 */
/* 如果想單純地賦值,可以使用 __bridge,它不改變對象的持有狀況。 */
// ARC 有效
id obj = [[NSObject alloc] init];
// 轉(zhuǎn)換為 void * 的__bridge 安全性低,類似 __unsafe_unretained
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

/* __bridge_retained:使賦值的對象也持有被賦值的對象 */
/* ARC 有效 */ 
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
/* ARC 無效 */
// __bridge_retained 轉(zhuǎn)換變?yōu)榱?retain。變量 obj 和變量 p 同時持有對象。
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

/* __bridge_transfer:與 __bridge_retained 相反,被轉(zhuǎn)換的變量所持有的對象在被賦值給目標變量后隨之釋放。 */
/* ARC 有效時 */
id obj = (__bridge_transfer id)p;
/* ARC 無效時 */
id obj = (id)p;
[obj retain];
[(id)p release];

// (__bridge_retained void *)obj, (__bridge_transfer id)p.
/* __bridge_retained 與 __bridge_transfer 在 Foundation 與 CoreFoundation 中用得比較多 */
CFMutableArrayRef cfObj = (__bridge_retained CFMutableArrayRef)obj;

CFMutableArrayRef cfObject = NULL;
{
  id obj = [[NSMutableArray alloc] init];
  // cfObject 強引用 obj 持有的對象。CFBridgingRetain 與 __bridge_retained 等價
  // 如果是 __bridge,則不會改變持有狀況,這里就是 cfObject 不會強引用。
  cfObject = CFBridgingRetain(obj);
  CFShow(cfObject);
  // retain count = 2
  printf("retain count = %d/n", CFGetRetainCount(cfObject));
}
// retain count = 1
printf("retain count after the scope = %d/n", CFGetRetainCount(cfObject));
CFRelease(cfObejct);

/*
    需要時刻注意在使用 CFBridgingRetain / CFBridgingRelease 或者 __bridge_retained / __bridge_transfer 轉(zhuǎn)換時的內(nèi)存管理。
*/

屬性

屬性聲明的屬性 所有權(quán)修飾符
assign __unsafe_unretained 修飾符
copy __strong 修飾符(但是被賦值的是被復制的對象)
retain __strong 修飾符
strong __strong 修飾符
unsafe_unretained __unsafe_unretained 修飾符
weak __weak 修飾符

ARC 的實現(xiàn)(參考:objc4 源碼)

__strong 修飾符

id __strong obj = [NSMutableArray array]; 
/* 編譯器的模擬代碼 */
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(objc);
objc_release(obj);

/*
objc_retainAutoreleasedReturnValue 函數(shù)與 objc_autoreleaseReturnValue 函數(shù)是成對的。
在調(diào)用 array 類方法后,由編譯器插入該函數(shù)。
*/

+ (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 函數(shù)返回的對象原本應(yīng)該注冊到 autoreleasepool 中,再從 pool 中獲取對象。
但是通過 objc_retainAutoreleasedReturnValue 函數(shù)與 objc_autoreleaseReturnValue 函數(shù)實現(xiàn)了最優(yōu)化,不需要注冊到 pool 中,直接返回了對象。
*/

__weak 修飾符

// 假設(shè)變量 obj 附加 __strong 修飾符,且對象被賦值。
id __weak obj1 = obj;

/* 編譯器的模擬代碼 */
id obj1;
// 初始化由 __weak 修飾的 obj1 變量。
objc_initWeak(&obj1, obj);
// 在作用域結(jié)束時釋放變量。
objc_destroyWeak(&obj1);

/* 上面的代碼又等價于下面的代碼 */
id obj1;
// 1. objc_initWeak(&obj1, obj);
obj1 = 0;
// 把 obj 作為 key,將 obj1 的地址注冊到 weak 表中。當有多個 __weak 變量時,一個鍵值可注冊多個變量的地址。
objc_storeWeak(&obj1, obj);
// 2. objc_destroyWeak(&obj1);
// 當 key 是 0 的時候,把變量的地址從 weak 表中移除。
objc_storeWeak(&obj1, 0);

程序是如何廢棄誰都不持有的對象的呢

對象通過 objc_release 釋放。

  1. objc_release
  2. 因為引用計數(shù)為 0,所以執(zhí)行 dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstance
  6. objc_clear_deallocating

objc_clear_deallocating 函數(shù)的動作如下

  1. 從 weak 表中獲取廢棄對象的地址為鍵值的記錄(被 __weak 修飾的所有變量)。
  2. 將包含在記錄中的所有附有 __weak 修飾符的變量的地址,賦值為 nil。
  3. 從 weak 表中刪除該記錄。
  4. 從引用計數(shù)表中刪除廢棄對象的地址為鍵值的記錄。

從以上步驟可知,如果大量使用附有 __weak 修飾符的變量,則會消耗相應(yīng)的 CPU 資源。一般只在避免循環(huán)引用時使用 __weak 修飾符。

id __weak obj = [[NSObject alloc] init]; 內(nèi)部發(fā)生了什么

// 會被立即釋放,所以 obj 是 nil。
id __weak obj = [[NSObject alloc] init];

/* 編譯器的模擬代碼 */
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&obj);

/*
雖然自己生成并持有的對象通過 init_Weak 函數(shù)被賦值給附有 __weak 修飾符的變量中,但編譯器判斷其沒有持有者,所以該對象通過 objc_release 函數(shù)被釋放和廢棄。
這樣一來,nil 就會被賦值給 obj 變量。
*/

id __unsafe_unretained obj = [[NSObject alloc] init]; 與 id weak obj =[[NSObject alloc] init]; 類似。對象會被立即釋放,但是不會置為 nil,會產(chǎn)生懸垂指針賦值給 obj。

使用 __weak 修飾的變量,即是使用注冊到 autoreleasepool 中的對象

{
  id __weak obj1 = obj;
  NSLog(@"%@", obj1);
}

/* 編譯器的模擬代碼 */
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);

與被賦值時相比,在使用附有 __weak 修飾符變量的情形下,增加了 objc_loadWeakRetained 函數(shù)和 objc_destroyWeak 函數(shù)的調(diào)用。這些函數(shù)的動作如下:

  1. objc_loadWeakRetained 函數(shù)取出 __weak 修飾的變量所引用的對象并 retain。
  2. objc_autorelease 函數(shù)將該對象注冊到 autoreleasepool 中。

如果大量使用 __weak 修飾的變量,注冊到 autoreleasepool 中的對象也會大量增加,因此使用 __weak 修飾的變量時,最好先暫時賦值給 __strong 修飾的變量再使用。

// 變量 obj1 所賦值的對象在 autoreleasepool 中注冊了 5 次。
{
  id __weak obj1 = obj;
  NSLog(@"1 %@", obj1);
  NSLog(@"2 %@", obj1);
  NSLog(@"3 %@", obj1);
  NSLog(@"4 %@", obj1);
  NSLog(@"5 %@", obj1);
}

// 只在 autoreleasepool 中注冊了 1 次。
{
  id __weak obj1 = obj;
  id tmp = obj1;
  NSLog(@"1 %@", tmp);
  NSLog(@"2 %@", tmp);
  NSLog(@"3 %@", tmp);
  NSLog(@"4 %@", tmp);
  NSLog(@"5 %@", tmp);
}

不能使用 __weak 修飾符的類

NSMachPort 類就是不支持 __weak 修飾符的類。這些類重寫了 retain / release 并實現(xiàn)該類獨自的引用計數(shù)機制。

/*
不支持 __weak 修飾符的類,其類聲明中附加了 "__attribute__((objc_arc_weak_reference_unavailable))" 這一屬性,同時定義了 NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE。
*/

NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE 
@interface NSMachPort : NSPort {
    @private
    id _delegate;
    NSUInteger _flags;
    uint32_t _machPort;
    NSUInteger _reserved;
}
...

allowsWeakReference / retainWeakReference 方法

@interface MyObject : NSObject

{
    NSUInteger count;
}

@end

#import "MyObject.h"

@implementation MyObject

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

// 如果返回 NO,那么將對象賦值給 __weak修飾的變量時運行就會直接報錯。
- (BOOL)allowsWeakReference {
    return NO;
}

// 當返回 NO,那么將對象賦值給 __weak修飾的變量時,返回 nil。
- (BOOL)retainWeakReference {
    if (++count > 3) {
        return NO;
    }
    return [super retainWeakReference];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id __strong obj = [[MyObject alloc] init];
      // 如果 - (BOOL)allowsWeakReference 返回NO,則運行時報錯。
        id __weak o = obj;
      /* 1~3 有值 */
        NSLog(@"1 %@", o);
        NSLog(@"2 %@", o);
        NSLog(@"3 %@", o);
      // 4 (null)
        NSLog(@"4 %@", o);
      // 5 (null)
        NSLog(@"5 %@", o);        
    }
}

@end

__autoreleasing 修飾符

/* id __autoreleasing obj = [[NSObject alloc] init]; */
@autoreleasepool {
  id __autoreleasing obj = [[NSObject alloc] init];  
}

/* 編譯器的模擬代碼 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

/* id __autoreleasing obj = [NSMutableArray array]; */
@autoreleasepool {
  id __autoreleasing obj = [NSMutableArray array];  
}

/* 編譯器的模擬代碼 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容