前言
程序員在開發(fā)程序的時候,對內(nèi)存的管理是很重要的一件事情,比如如何高效使用內(nèi)存,防止內(nèi)存泄露,降低內(nèi)存的峰值等。如何很好去處理這些問題的前提就必須對內(nèi)存的管理機制有一定的掌握了,本文只要簡單介紹引用計數(shù)式內(nèi)存管理方式,和iOS5推出 ARC,引用計數(shù)內(nèi)存管理方式是如何應(yīng)用在 ARC 上的。從 MRC 到 ARC ,讓程序員關(guān)注于更有趣的代碼。
內(nèi)存管理的思考方式:
1.自己生成的對象,自己所持有
(1)alloc / new / copy / mutableCopy 等方法。
(2)alloc / new / copy / mutableCopy 使用該單詞開頭,命名是以駝峰的方式的辦法,即是該辦法生成并持有對象。
eg:
- allocMyObject; (?)
- newThatObject; (?)
- copyobject; (?)
2.非自己生成的對象,自己也能持有
retain 方法
3.不在需要自己持有的對象時釋放
release 方法 .如果釋放不是自己持有的對象,則程序會崩潰。
4.非自己持有的對象無法釋放
ps:
id objArray = [NSArray array];
類似調(diào)用類辦法時,生成對象,但并不持有,又是怎么實現(xiàn)的呢?創(chuàng)建對象的代碼如下:
-(instancetype)object
{
id object = [[NSObject alloc]init];
[object autorelease];//防止傳遞的時候,object已經(jīng)被釋放了。加了 autorelease ,使對象在超出作用域時能夠自動并正確釋放(調(diào)用 release 辦法)
NSLog(@"%@",object);
return object;
}
alloc / retain / release / dealloc 實現(xiàn)
OS X,iOS 中大部分的源碼沒有公開,但是 GNUstep 是 Cocoa 框架的互換框架。也就是說,能從 GNUstep 中窺探蘋果的實現(xiàn)。
alloc:
+ (id)alloc;
+ (id) allocWithZone:(NSZone *)z;
...
//具體代碼就不貼了 大概講述一下實現(xiàn)思路就可以。
通過分配對象的所需的內(nèi)存大小,所分配的區(qū)域,和通過賦值對對象內(nèi)存頭部的retained +1;
為什么要通過區(qū)域來創(chuàng)建對象呢?
- 它是為了防止內(nèi)存碎片化而引入的結(jié)構(gòu)。對內(nèi)存分配的區(qū)域本身進行多重化管理,根據(jù)使用對象的目的,對象的大小分配內(nèi)存,從而提高了內(nèi)存管理的效率。
retain
由對象尋址找到對象內(nèi)存頭部,從而訪問其中的 retained 變量 —> +1。
release
由對象尋址找到對象內(nèi)存頭部,從而訪問其中的 retained 變量.當(dāng)retained 變量大于0時減1,等于0時調(diào)用 dealloc 實例辦法,廢棄對象。
值得一提的是:
當(dāng)對象在執(zhí)行最后一次 release 時,系統(tǒng)知道馬上要回收該對象了,所以并不會對對象進行 retainCount 減1了,因為不管減不減1,這個對象的內(nèi)存都是要被回收的,他所在的內(nèi)存區(qū)域,包括 retainCount 也是沒有意義的了。不將 retainCount 從1減到0,這樣會減少一次內(nèi)存操作,加速對對象的回收。
具體代碼演示可以見下方總結(jié)一下之記住。
dealloc
找到對象內(nèi)存地址,直接 free()。
總結(jié)一下:
- 在 Objective-C 的對象中存有引用計數(shù)這一整數(shù)值
- 調(diào)用 alloc 或是 retain 方法后,引用計數(shù)值加1
- 調(diào)用 release 方法后,引用計數(shù)值減1
- 引用計數(shù)值為0時,調(diào)用 dealloc 方法廢棄對象
記?。寒?dāng)對象被釋放掉的時候,該對象之前所占有的內(nèi)存已經(jīng)被收回。但是被收回的內(nèi)存不一定馬上被復(fù)用(暫時沒有被復(fù)用的對象,成為了懸掛指針)。所以有時候我們向已經(jīng)釋放的對象發(fā)送消息,會收到不在預(yù)期中的效果。如果收回的對象內(nèi)存被復(fù)用,則程序會崩潰。故不應(yīng)該向已經(jīng)釋放的對象發(fā)送消息,會得到無法預(yù)期的結(jié)果。
-(void)testARC
{
id objAlloc = [[NSObject alloc]init];
NSLog(@"allco生成并自我持有:%lu",[objAlloc retainCount]);
[objAlloc release];
NSLog(@"allco生成并自我持有 release:%lu",[objAlloc retainCount]);//不應(yīng)該向釋放的對象發(fā)送消息,已經(jīng)被回收的內(nèi)存無法預(yù)期其是否已經(jīng)被復(fù)用
}
2016-09-21 09:50:57.796 ARCByCrudherWu[1325:32457] allco生成并自我持有:1
2016-09-21 09:50:57.797 ARCByCrudherWu[1325:32457] allco生成并自我持有 release:1
簡述蘋果實現(xiàn)
retainCount / retain / release
int _CFDoExternRefOperation(uintptr_t op, id obj)
{
CFBasicHashRef table = 取得對象的散列表(obj);
int count;
switch(op) {
case OPERATION_retainCount;
count = CFBasicHashGetCountOfKey(table, obj);
return count;
case OPERATION_retain:
CFBasicHashAddValue(table, obj);
return obj;
case OPERATION_release:
count = CFBasicHashRemoveValue(table, obj);
return 0 == count;
}
}
蘋果根據(jù)不同操作去調(diào)用不同的函數(shù)實現(xiàn)。從該函數(shù)中可以看出,蘋果大概采用的是散列表(引用計數(shù)表)的形式。CNUstep 將引用計數(shù)保存在對象占用內(nèi)存塊的頭部,而蘋果是保存在引用計數(shù)表的記錄中。
通過內(nèi)存塊頭部管理引用計數(shù)的好處:
- 少量的代碼就可以實現(xiàn)
- 能夠一起管理引用計數(shù)的內(nèi)存塊和對象的內(nèi)存塊
通過散列表管理引用計數(shù)的好處:
- 對象的內(nèi)存塊不用再考慮內(nèi)存塊頭部
- 引用計數(shù)表記錄著各內(nèi)存塊的地址,可以從各個記錄追溯到各個對象的內(nèi)存塊
autorelease
顧名思義就是自動釋放,相當(dāng)于 C 語言的局部變量,當(dāng)局部變量超出作用域時,自動變量就被廢棄,不可再訪問。調(diào)用該方法,將對象注冊到 autorealeasePool 中,當(dāng) autorealeasePool 廢棄,會自動調(diào)用 release 辦法。
autorelease 的具體使用辦法:
- 生成并持有 NSAutorealeasePool 對象
- 調(diào)用已分配對象的 autorelease 方法
- 廢棄 NSAutorealeasePool 對象(已分配對象會自動調(diào)用 release 方法)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease];
[pool drain];
另外,Cocoa 框架很多類方法用于返回 autorelease 的對象。比如 NSMutableArray 類的 arrayWithCapacity 類辦法。
id array = [NSMutableArray arrayWithCapacity:2];
此源碼相當(dāng)于如下代碼:
id array = [ [NSMutableArray arrayWithCapacity:2] autorelease];
autorelease 實現(xiàn)
autorelease 的實現(xiàn)其實就是將對象加入 NSAutoreleasePool 中。
-(id)autorelease
{
[NSAutoreleasePool addObject:self];
}//該方法的實現(xiàn),其實是使用一種特殊的方式來實現(xiàn),即是使用了函數(shù)指針的調(diào)用方式,因為該辦法的調(diào)用比較頻繁。(具體可以學(xué)習(xí) Runtime 方法之直接調(diào)用函數(shù)地址)
-(void)addObject :(id)anObject
{
[array addObject:anObject];
}
-(void)drain
{
[self dealloc];
}
-(void)dealloc
{
[self emptyPool];
[array release];
}
-(void)emptyPool
{
for (id obj in array) {
[obj release];
}
}
ARC 規(guī)則
概要:實際上“引用計數(shù)式內(nèi)存管理方式”的本質(zhì)部分在 ARC 中并沒有改變的。ARC 只是自動地幫我們管理內(nèi)存了,其管理方式就是引用計數(shù)的方式。
所有權(quán)修飾符
OC 中的對象類型和 id 類型的變量,必須要附加所有權(quán)修飾符。類型有如下四中:
- __strong
- __weak
- ______unsafe __unretained
- __autoreleasing
__strong 修飾符
在 OC 中,對象類型和 id 類型的變量默認(rèn)是附加上了 __strong 修飾符的。
__strong 修飾符之"自己生成并持有的對象"
//ARC 有效
-(void)testARC
{
/**
自己生成并持有對象 --> [[NSObject alloc]init]
*/
id __strong obj = [[NSObject alloc]init];
/**
* __strong 修飾符,為強引用,變量 obj 持有對象
*/
}
/**
* 局部變量 obj 超出了作用域,所有強引用失效
* 所以自動地釋放自己持有的對象
* 對象的所有者不存在,因此廢棄該對象
*/
__strong 修飾符之"非自己生成的對象,自己也能持有"
-(void)testARC
{
id __strong obj0 = [[NSObject alloc]init];//對象A
/**
obj0 持有對象A的強引用
*/
id __strong obj1 = [[NSObject alloc]init];//對象B
/**
* obj1 持有對象B的強引用
*/
id __strong obj2 = nil;
/**
* obj2 不持有任何對象
*/
obj0 = obj1;
/**
* obj0 被持有對象B的 obj1 賦值,所以 obj0 持有對象B
因為 obj0 被賦值,所以原來持有對象A的強引用失效
對象A不再被任何對象持有,因此廢棄對象A
此時,持有對象的B的強引用變量為:obj1 obj0
*/
obj2 = obj0;
/**
* obj2 被持有對象B的 obj0 賦值,所以 obj2 持有對象B
此時,持有對象的B的強引用變量為:obj1 obj0 obj2
*/
obj1 = nil;
/**
* 因為 obj1 被賦值為 nil,所以持有對象B的強引用失效
此時,持有對象的B的強引用變量為:obj2 obj0
*/
obj0 = nil;
/**
* 因為 obj0 被賦值為 nil,所以持有對象B的強引用失效
此時,持有對象的B的強引用變量為:obj2
*/
obj2 = nil;
/**
* 因為 obj2 被賦值為 nil,所以持有對象B的強引用失效
此時,沒有變量持有對象的B的強引用,所以對象B被廢棄
*/
}
__strong 修飾符之"成員變量"
@interface YYTestObject : NSObject
{
id __strong _obj;
}
-(void)setObj:(id __strong)obj;
@end
-------------------------------------------------------------
#import "YYTestObject.h"
@implementation YYTestObject
-(void)setObj:(id)obj
{
_obj = obj;
}
@end
-------------------------------------------------------------
-(void)testARC
{
id __strong test = [[YYTestObject alloc]init];
[test setObj:[[NSObject alloc]init]];
}
/**
* self -(1)-> test -(2)-> _obj -(3)-> NSObject
*/
//當(dāng) test 超出作用域后,強引用失效,之后的鏈接也跟著失效。
__weak 修飾符
通過上面對 __strong 修飾符的例子來看,貌似只要有__strong就可以很好地實現(xiàn)內(nèi)存的管理,但是實際上不是這樣的。__strong 修飾符 不很解決很嚴(yán)重的內(nèi)存泄露問題 —循環(huán)引用。
@interface YYTestObject : NSObject
{
id __strong _obj;
}
-(void)setObj:(id __strong)obj;
@end
-------------------------------------------------------------
#import "YYTestObject.h"
@implementation YYTestObject
-(void)setObj:(id)obj
{
_obj = obj;
}
@end
-------------------------------------------------------------
-(void)testARC
{
id obj = [[YYTestObject alloc]init];//對象A
[obj setObj:obj];
}
//obj 超出作用域,對對象A強引用失效,此時對象A的強引用變量有:obj的成員變量_obj
通過Xcode自帶的 instrument 檢測循環(huán)引用如下:
怎樣才能消除循環(huán)引用呢?這時候應(yīng)該引入__weak 修飾符了。
__weak 修飾符不持有對象實例,提供弱引用。
id __weak obj = [[YYTestObject alloc]init];//對象A
//此源碼編譯器會發(fā)出警告。因為,為了不持有自己生成并持有的對象,生成的對象會立即被釋放
id obj = [[YYTestObject alloc]init];//對象A
id __weak obj2 = obj;
-----------------------------------------------------------
-(void)testARC
{
id obj = [[YYTestObject alloc]init];//對象A
id __weak obj2 = obj;
[obj setObj:obj2];
}
//因為 obj 超出作用域,所以對對象A的強引用失效
//對象A沒有被持有,因此廢棄對象A
//obj 無持有者,因此也廢棄對象 obj
//此時,持有該對象 obj 弱引用的 obj2 變量的弱引用失效,nil 賦值給 obj2,使 obj2 沒有成為懸掛指針
______unsafe __unretained
______weak 修飾符只是用于 iOS5 以上及 OS X Lion 以上的版本的程序。因此不在該范圍的程序就使用 ____unsafe __unretained 來修飾。附有該修飾符的變量不屬于編譯器的內(nèi)存管理對象,所以要注意賦值對象的所有者,否則會造成內(nèi)存泄露或是程序崩潰。
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc]init];//對象A
obj1 = obj0;
NSLog(@"A: %@",obj1);
}
//因為 obj0 超出作用域,所以對對象A的強引用失效
//對象A沒有被持有,因此廢棄對象A
//obj0 無持有者,因此也廢棄對象 obj
//此時,持有該對象 obj 弱引用的 obj1 變量的弱引用失效
NSLog(@"B: %@",obj1);
//輸出 obj1 變量表示的對象
//obj1 變量表示的對象已經(jīng)被廢棄(懸掛指針),錯誤訪問
PS:如果 NSLog(@"B: %@",obj1); 訪問成功的話,那只是碰巧而已,該區(qū)域的內(nèi)存還沒有被復(fù)用。這也說明了__unsafe __unretained 修飾的變量不會被賦值 nil ,以防止懸掛指針,所以訪問者類型的變量,一定要確保對象是否存在,否則會導(dǎo)致程序崩潰。
__autoreleasing
在 ARC 有效的情況下,是不能使用 autorelease 方法,也不能使用 NSAutoreleasePool 類。這樣一來,autorelease 無法直接調(diào)用,但實際上,ARC 有效的情況下,autorelease 方法是起到作用的。
下面來看一下 __autoreleasing 顯示使用情況:
// 非ARC
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
id __strong obj = [[NSObject alloc]init];
[obj autorelease];
[pool drain];
}
-----------------------------------------------------------------
//ARC
{
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc]init];
//__autoreleasing --> 調(diào)用 autorelease 方法 --> 意味著 obj 已經(jīng)注冊到 autoreleasepool 中
}
}
值得注意的是,顯示地添加 ______autoreleasing 修飾符同顯示添加____strong 修飾符一樣少見。所以下面簡述一下其隱式的使用情況:
- 取得非自己生成并持有的對象,即是除了 alloc / new / copy / mutableCopy 等方法取得對象,一般是類辦法。
- 對象作為返回值
- __weak 修飾的對象
- id 的指針或是對象的指針
(1) 取得非自己生成并持有的對象
編譯器會檢查方法名是否以 alloc / new / copy / mutableCopy 開始,如果不是則自動將返回值的對象注冊到 autoreleasepool 中。
@autoreleasepool {
//取得非自己生成并持有的對象 --> [NSArray array]
id __strong *array = [NSArray array];
//因為 array 為強引用,所以持有對象,
//并且該對象有編譯器判斷其方法名后,自動注冊到 autoreleasepool 中
}
//對象 array 超出作用域,所以強引用失效,同時自動釋放自己所持有的對象
//同時,隨著 @autoreleasepool 快的結(jié)束,注冊到 autoreleasepool 中的所有對象被自動釋放。
//因為對象的不存在,所以廢棄對象
//持有關(guān)系:autoreleasepool --> array --> [NSArray array]
(2) 對象作為返回值
第(1)條中,不顯式地使用 __autoreleasing 也能使對象注冊到 autoreleasepool 中,下面我們來看下取得非自己生成并持有的對象時被調(diào)用方法的源碼代碼示例:
+(id) array
{
//方案一
return [[NSArray alloc]init];
//方案二
id obj = [[NSArray alloc]init];
return obj;
}
//由于 return 超出作用域,所以該強引用對應(yīng)的自己持有的對象會被自動釋放,但該對象作為返回值,所以編譯器會自動將該對象注冊到 autoreleasepool 中。
(3) __weak 修飾的對象
______weak 修飾的對象是對某對象持有弱引用,如果要訪問某對象的時候,該對象有可能已經(jīng)被廢棄了,這樣會導(dǎo)致程序崩潰。因此,______weak 修飾的變量必定要注冊到 autoreleasepool 中,在 @autoreleasepool 快結(jié)束之前,都能確保該對象的存在。
id __weak obj1 = obj0;
NSLog(@"obj: %@",obj1);
相等于
||
||
id __weak obj1 = obj0;
id __autorealeasing tmp = obj1;
NSLog(@"obj: %@",tmp);
(4) id 的指針或是對象的指針
對象的指針,即是獲得對象的地址。形式如:NSObject **obj 。
同前面講述的 id obj 和 id ______strong obj 完全一樣。那么 id 的指針 id *obj 又如何?可以由 id ______strong obj 的例子類推出 id ______strong *obj 嗎?其實推出來的是 id __autorealeasing *obj。
同樣的 NSObject **obj 便成為了 ,NSObject * __autorealeasing *obj。
在開發(fā)中,我們常常需要得到詳細的錯誤信息,經(jīng)常會在方法的參數(shù)中傳遞 NSError 對象的指針,而不是函數(shù)返回值。
NSError *error = nil;
BOOL result = [myObject performOperationWithError:&error];
//方法的聲明
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
//根據(jù)前面所講述的,除了 alloc / new / copy / mutableCopy 辦法返回值取得的是對象是自己生成并持有的,其他的方法都是非自己生成并持有的。所以 performOperationWithError 辦法需要獲取的參數(shù) error 對象,都會注冊到 autoreleasepool,并取得非自己生成并持有的對象。
值得一提的是:賦值給對象指針的所有權(quán)修飾符必須一致。
(?)NSError *error = nil;
NSError **pError = &error;
(?)NSError *error = nil;
NSError * __strong *pError = &error;
那么問題來了,上面?zhèn)€的例子中:源碼如下
NSError *error = nil;
BOOL result = [myObject performOperationWithError:&error];
//方法的聲明
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
傳遞給 performOperationWithError 辦法的參數(shù) error 的修飾符是 __strong,而辦法接受值是由 __autoreleasing修飾的,為什么編譯沒有報錯呢?
其實實際上編譯器已經(jīng)將傳遞參數(shù)的那部分源碼轉(zhuǎn)化成如下的形式了:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL result = [myObject performOperationWithError:&tmp];
error = tmp;
具體也可以參考蘋果的官方文檔:蘋果文檔
最后,如果想打印 autoreleasePool 中有哪些對象,不管是在 ARC 或是非 ARC 的情況下,都可以調(diào)用非公開的 _objc_autoreleasePoolPrint();
既然簡單地介紹了 ARC 一些簡單用法,下面就看看在 ARC 下,有哪些規(guī)則吧。
- 不能使用 retain / release / retainCount / autorelease
- 不能使用NSAllocateObject / NSDeallocateObject
- 必須遵守內(nèi)存管理的方法命名
- 不要顯示調(diào)用 dealloc
- 使用 @autoreleasepool 塊代替 NSAutoreleasepool
- 不能使用區(qū)域(NSZone)
- 對象型變量不能作為C語言的結(jié)構(gòu)體(struct / union)的成員
- 顯示轉(zhuǎn)換 “id” 和 “void *”
上面的一些規(guī)則中,挑幾條值得注意的來講述一下:
(1)必須遵守內(nèi)存管理的方法命名
init 開頭的方法,必定是要返回對象,對象應(yīng)該是 id 類型或是該方法聲明類的對象類型,或者是該類的超類或子類型。該返回的對象是不會注冊到 autoreleasepool 中的?;局皇菍?alloca 方法返回值的對象進行初始化并返回該對象的。
-(id)initWithObject; (?)
-(void)initWithObject; (?) --->返回值
-(id)initialize; (?) --->應(yīng)是駝峰式命名
為了使能夠讓手動管理內(nèi)存和 ARC 管理內(nèi)存兩者之間互相操作,屬性名不能以 new 開頭命名,除非你重新命名該屬性的 getter 辦法。
// Won't work:
@property NSString *newTitle;
// Works:
@property (getter=theNewTitle) NSString *newTitle;
(2) 不要顯示調(diào)用 dealloc
當(dāng)對象廢棄的時候,都會調(diào)用該辦法的。在該辦法中大多數(shù)只使用刪除已注冊的代理和觀察者對象即可。
// 非 ARC
-(void)dealloc
{
//do something
[super dealloc];//一定要注意調(diào)用的順序
}
// ARC
-(void)dealloc
{
//do something
// [super dealloc];在 ARC 下,無需顯示調(diào)用該方法, ARC 會自動處理的。
}
(3) 對象型變量不能作為C語言的結(jié)構(gòu)體(struct / union)的成員
struct Data
{
NSMutableArray *array;
};
如果結(jié)構(gòu)體中的成員變量出現(xiàn) Objective - C 對象,便會引起編譯錯誤。這是因為C語言的規(guī)約上沒有方法來管理結(jié)構(gòu)體成員的生存周期。C語言的局部變量可使用該變量的作用域來管理對象,而 ARC 吧內(nèi)存管理的工作分配給編譯器的。不過實在需要,也可以改成如下:
struct Data{
NSMutableArray ______unsafe____unretained *array;
};
(4) 顯示轉(zhuǎn)換 “id” 和 “void *”
在 ARC 下,我們有時候需要將 Core Foundation 對象轉(zhuǎn)換成一個 Objective-C 對象,這時候我們就應(yīng)該告訴編譯器,在轉(zhuǎn)換的過程中引用計數(shù)需要如何調(diào)整,這時候我們就應(yīng)該使用到與 bridge 有關(guān)的關(guān)鍵字了。一下是這些關(guān)鍵字的說明;
- __bridge :只做類型轉(zhuǎn)換,不修改相關(guān)對象的引用計數(shù)。
- ______bridge____retained:類型轉(zhuǎn)換后,相關(guān)對象的引用計數(shù)+1
- ______bridge____transfer:類型轉(zhuǎn)換后,該對象的引用計數(shù)由 ARC 管理,被轉(zhuǎn)換的對象隨后釋放。
CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
id __strong obj = (__bridge id)cfArray;
//CFGetRetainCount(cfArray); --->2
---------------------------------------------------------------
id obj = [[NSObject alloc]init];
CFMutableArrayRef cfArray = (__bridge__retained CFMutableArrayRef)obj;
//CFGetRetainCount(cfArray); --->2
---------------------------------------------------------------
CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
id __strong obj = (__bridge__transfer id)cfArray;
//CFGetRetainCount(cfArray); --->1
轉(zhuǎn)換成非 ARC
CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
id obj = (id)cfArray;
[obj retain];
[(id)cfArray release];
注意:非 ARC id 變量強轉(zhuǎn)換成 void* 變量是并不會出問題。
ARC 的實現(xiàn)
蘋果官方說明中稱,ARC 是“由編譯器進行內(nèi)存管理”的,但是實際上只有編譯器是無法完勝的,需要借助運行庫的協(xié)助。
__strong
{
id __strong obj = [[NSObject alloc]init];
}
以上的代碼用編譯器的模擬代碼如下:
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);
{
id __strong obj = [NSArray array];
}
以上的代碼用編譯器的模擬代碼如下:
id obj = objc_msgSend(NSArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
+(id)array
{
return [[NSMutableArray alloc]init];
}
以上的代碼用編譯器的模擬代碼如下:
+(id)array
{
id obj = objc_msgSend(NSMutableArray,@selector(alloc));
objc_msgSend(obj,@selector(init));
return objc_autoreleasedReturnValue(obj);
}
objc_autoreleasedReturnValue 函數(shù)將對象注冊到 autoreleasePool 中,與 autorelease 函數(shù)是有著不同之處的。objc_autoreleasedReturnValue 會檢查使用該函數(shù)的方法或函數(shù)調(diào)用方的執(zhí)行命令列表,如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著調(diào)用 objc_retainAutoreleasedReturnValue 函數(shù),那么就不會將對象注冊到 autoreleasePool 中,而是直接傳遞到方法或是函數(shù)調(diào)用方。objc_retainAutoreleasedReturnValue 與 objc_retain 函數(shù)不同,它即便不注冊到 autoreleasePool 中,也是能夠正確獲取對象的。這樣一來,就少了對象注冊到 autoreleasePool 中而直接傳遞了。
__weak
{
id __weak obj1 = obj;
NSLog(@"obj = %@",obj);
}
//以上的代碼用編譯器的模擬代碼如下:
id obj1;
objc_initWeak(&obj1,obj);// --->轉(zhuǎn)化成:將有 __weak 修飾的變量初始化為0后,即是 id obj1 = 0,objc_storeWeak(&objc1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"obj = %@",tmp);
objc_destroyWeak(&obj1); // --->轉(zhuǎn)化成:objc_storeWeak(&objc1, 0);
//objc_storeWeak 函數(shù)把第二參數(shù)的賦值對象的地址為鍵值,將第一參數(shù)的附有 __weak 修飾符的變量的地址注冊到 weak 表中。如果第二個參數(shù)為0,則把變量從 weak 表中刪除。
問題1:weak 表的作用?
- 當(dāng)對象被廢棄了,在把 __weak 的變量從 weak 表中刪除時,將變量的地址,賦值為 nil;
問題2:使用 __weak 修飾的變量,是從 weak 表中還是從 autoreleasePool 中獲取對象?
- 在使用 __weak 的變量時,一方我們可以通過 weak 表獲取變量的地址而找到對應(yīng)的對象,一方也可以從注冊在 autoreleasePool 中去獲取,但從模擬編譯器的代碼 NSLog(@"obj = %@",tmp); 可以看出,應(yīng)該是從后者去獲取到對象。
注意: ______weak 修飾的變量被每一使用一次,所賦值的對象都會被注冊到 ______autoreleasePool 中一次,所以為了減少對內(nèi)存的使用,將附有 ______weak 修飾的變量賦值給附有 ______strong 修飾的變量后在使用可以避免此類問題。在 block 中使用也是該這樣使用的。
__autoreleasing
@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);
@autoreleasepool {
id __autoreleasing obj = [NSArray array];
}
//以上的代碼用編譯器的模擬代碼如下:
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
尾巴:通過介紹引用計數(shù)式的內(nèi)存管理方式如何使用,到它們是如何實現(xiàn),再到如何利用引用計數(shù)式的內(nèi)存管理方式去實現(xiàn) ARC。
參考資料:
Objective-C 高級編程