內(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 釋放。
- objc_release
- 因為引用計數(shù)為 0,所以執(zhí)行 dealloc
- _objc_rootDealloc
- object_dispose
- objc_destructInstance
- objc_clear_deallocating
objc_clear_deallocating 函數(shù)的動作如下
- 從 weak 表中獲取廢棄對象的地址為鍵值的記錄(被 __weak 修飾的所有變量)。
- 將包含在記錄中的所有附有 __weak 修飾符的變量的地址,賦值為 nil。
- 從 weak 表中刪除該記錄。
- 從引用計數(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ù)的動作如下:
- objc_loadWeakRetained 函數(shù)取出 __weak 修飾的變量所引用的對象并 retain。
- 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);