前言
程序員在開發(fā)程序的時(shí)候评姨,對(duì)內(nèi)存的管理是很重要的一件事情难述,比如如何高效使用內(nèi)存,防止內(nèi)存泄露吐句,降低內(nèi)存的峰值等龄广。如何很好去處理這些問題的前提就必須對(duì)內(nèi)存的管理機(jī)制有一定的掌握了,本文只要簡(jiǎn)單介紹引用計(jì)數(shù)式內(nèi)存管理方式蕴侧,和iOS5推出 ARC择同,引用計(jì)數(shù)內(nèi)存管理方式是如何應(yīng)用在 ARC 上的。從 MRC 到 ARC 净宵,讓程序員關(guān)注于更有趣的代碼敲才。
內(nèi)存管理的思考方式:
1.自己生成的對(duì)象裹纳,自己所持有
(1)alloc / new / copy / mutableCopy 等方法。
(2)alloc / new / copy / mutableCopy 使用該單詞開頭紧武,命名是以駝峰的方式的辦法剃氧,即是該辦法生成并持有對(duì)象。
eg:
- allocMyObject; (?)
- newThatObject; (?)
- copyobject; (?)
2.非自己生成的對(duì)象阻星,自己也能持有
retain 方法
3.不在需要自己持有的對(duì)象時(shí)釋放
release 方法 .如果釋放不是自己持有的對(duì)象朋鞍,則程序會(huì)崩潰。
4.非自己持有的對(duì)象無法釋放
ps:
id objArray = [NSArray array];
類似調(diào)用類辦法時(shí)妥箕,生成對(duì)象滥酥,但并不持有,又是怎么實(shí)現(xiàn)的呢畦幢?創(chuàng)建對(duì)象的代碼如下:
-(instancetype)object
{
id object = [[NSObject alloc]init];
[object autorelease];//防止傳遞的時(shí)候坎吻,object已經(jīng)被釋放了。加了 autorelease 宇葱,使對(duì)象在超出作用域時(shí)能夠自動(dòng)并正確釋放(調(diào)用 release 辦法)
NSLog(@"%@",object);
return object;
}
alloc / retain / release / dealloc 實(shí)現(xiàn)
OS X瘦真,iOS 中大部分的源碼沒有公開,但是 GNUstep 是 Cocoa 框架的互換框架黍瞧。也就是說诸尽,能從 GNUstep 中窺探蘋果的實(shí)現(xiàn)。
alloc:
+ (id)alloc;
+ (id) allocWithZone:(NSZone *)z;
...
//具體代碼就不貼了 大概講述一下實(shí)現(xiàn)思路就可以印颤。
通過分配對(duì)象的所需的內(nèi)存大小弦讽,所分配的區(qū)域,和通過賦值對(duì)對(duì)象內(nèi)存頭部的retained +1;
為什么要通過區(qū)域來創(chuàng)建對(duì)象呢膀哲?
- 它是為了防止內(nèi)存碎片化而引入的結(jié)構(gòu)往产。對(duì)內(nèi)存分配的區(qū)域本身進(jìn)行多重化管理,根據(jù)使用對(duì)象的目的某宪,對(duì)象的大小分配內(nèi)存仿村,從而提高了內(nèi)存管理的效率。
retain
由對(duì)象尋址找到對(duì)象內(nèi)存頭部兴喂,從而訪問其中的 retained 變量 —> +1蔼囊。
release
由對(duì)象尋址找到對(duì)象內(nèi)存頭部,從而訪問其中的 retained 變量.當(dāng)retained 變量大于0時(shí)減1衣迷,等于0時(shí)調(diào)用 dealloc 實(shí)例辦法畏鼓,廢棄對(duì)象。
值得一提的是:
當(dāng)對(duì)象在執(zhí)行最后一次 release 時(shí),系統(tǒng)知道馬上要回收該對(duì)象了,所以并不會(huì)對(duì)對(duì)象進(jìn)行 retainCount 減1了抱冷,因?yàn)椴还軠p不減1彤蔽,這個(gè)對(duì)象的內(nèi)存都是要被回收的让禀,他所在的內(nèi)存區(qū)域挑社,包括 retainCount 也是沒有意義的了。不將 retainCount 從1減到0巡揍,這樣會(huì)減少一次內(nèi)存操作痛阻,加速對(duì)對(duì)象的回收。
具體代碼演示可以見下方總結(jié)一下之記住腮敌。
dealloc
找到對(duì)象內(nèi)存地址阱当,直接 free()。
總結(jié)一下:
- 在 Objective-C 的對(duì)象中存有引用計(jì)數(shù)這一整數(shù)值
- 調(diào)用 alloc 或是 retain 方法后糜工,引用計(jì)數(shù)值加1
- 調(diào)用 release 方法后弊添,引用計(jì)數(shù)值減1
- 引用計(jì)數(shù)值為0時(shí),調(diào)用 dealloc 方法廢棄對(duì)象
記灼《贰:當(dāng)對(duì)象被釋放掉的時(shí)候,該對(duì)象之前所占有的內(nèi)存已經(jīng)被收回赁咙。但是被收回的內(nèi)存不一定馬上被復(fù)用(暫時(shí)沒有被復(fù)用的對(duì)象钮莲,成為了懸掛指針)。所以有時(shí)候我們向已經(jīng)釋放的對(duì)象發(fā)送消息彼水,會(huì)收到不在預(yù)期中的效果崔拥。如果收回的對(duì)象內(nèi)存被復(fù)用,則程序會(huì)崩潰凤覆。故不應(yīng)該向已經(jīng)釋放的對(duì)象發(fā)送消息链瓦,會(huì)得到無法預(yù)期的結(jié)果。
-(void)testARC
{
id objAlloc = [[NSObject alloc]init];
NSLog(@"allco生成并自我持有:%lu",[objAlloc retainCount]);
[objAlloc release];
NSLog(@"allco生成并自我持有 release:%lu",[objAlloc retainCount]);//不應(yīng)該向釋放的對(duì)象發(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
簡(jiǎn)述蘋果實(shí)現(xiàn)
retainCount / retain / release
int _CFDoExternRefOperation(uintptr_t op, id obj)
{
CFBasicHashRef table = 取得對(duì)象的散列表(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ù)實(shí)現(xiàn)慈俯。從該函數(shù)中可以看出,蘋果大概采用的是散列表(引用計(jì)數(shù)表)的形式拥峦。CNUstep 將引用計(jì)數(shù)保存在對(duì)象占用內(nèi)存塊的頭部贴膘,而蘋果是保存在引用計(jì)數(shù)表的記錄中。
通過內(nèi)存塊頭部管理引用計(jì)數(shù)的好處:
- 少量的代碼就可以實(shí)現(xiàn)
- 能夠一起管理引用計(jì)數(shù)的內(nèi)存塊和對(duì)象的內(nèi)存塊
通過散列表管理引用計(jì)數(shù)的好處:
- 對(duì)象的內(nèi)存塊不用再考慮內(nèi)存塊頭部
- 引用計(jì)數(shù)表記錄著各內(nèi)存塊的地址略号,可以從各個(gè)記錄追溯到各個(gè)對(duì)象的內(nèi)存塊
autorelease
顧名思義就是自動(dòng)釋放刑峡,相當(dāng)于 C 語言的局部變量,當(dāng)局部變量超出作用域時(shí)玄柠,自動(dòng)變量就被廢棄突梦,不可再訪問。調(diào)用該方法羽利,將對(duì)象注冊(cè)到 autorealeasePool 中宫患,當(dāng) autorealeasePool 廢棄,會(huì)自動(dòng)調(diào)用 release 辦法这弧。
autorelease 的具體使用辦法:
- 生成并持有 NSAutorealeasePool 對(duì)象
- 調(diào)用已分配對(duì)象的 autorelease 方法
- 廢棄 NSAutorealeasePool 對(duì)象(已分配對(duì)象會(huì)自動(dòng)調(diào)用 release 方法)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
id obj = [[NSObject alloc]init];
[obj autorelease];
[pool drain];
另外撮奏,Cocoa 框架很多類方法用于返回 autorelease 的對(duì)象俏讹。比如 NSMutableArray 類的 arrayWithCapacity 類辦法。
id array = [NSMutableArray arrayWithCapacity:2];
此源碼相當(dāng)于如下代碼:
id array = [ [NSMutableArray arrayWithCapacity:2] autorelease];
autorelease 實(shí)現(xiàn)
autorelease 的實(shí)現(xiàn)其實(shí)就是將對(duì)象加入 NSAutoreleasePool 中畜吊。
-(id)autorelease
{
[NSAutoreleasePool addObject:self];
}//該方法的實(shí)現(xiàn)泽疆,其實(shí)是使用一種特殊的方式來實(shí)現(xiàn),即是使用了函數(shù)指針的調(diào)用方式玲献,因?yàn)樵撧k法的調(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í)際上“引用計(jì)數(shù)式內(nèi)存管理方式”的本質(zhì)部分在 ARC 中并沒有改變的。ARC 只是自動(dòng)地幫我們管理內(nèi)存了捌年,其管理方式就是引用計(jì)數(shù)的方式瓢娜。
所有權(quán)修飾符
OC 中的對(duì)象類型和 id 類型的變量,必須要附加所有權(quán)修飾符礼预。類型有如下四中:
- __strong
- __weak
- ______unsafe __unretained
- __autoreleasing
__strong 修飾符
在 OC 中眠砾,對(duì)象類型和 id 類型的變量默認(rèn)是附加上了 __strong 修飾符的。
__strong 修飾符之"自己生成并持有的對(duì)象"
//ARC 有效
-(void)testARC
{
/**
自己生成并持有對(duì)象 --> [[NSObject alloc]init]
*/
id __strong obj = [[NSObject alloc]init];
/**
* __strong 修飾符托酸,為強(qiáng)引用褒颈,變量 obj 持有對(duì)象
*/
}
/**
* 局部變量 obj 超出了作用域,所有強(qiáng)引用失效
* 所以自動(dòng)地釋放自己持有的對(duì)象
* 對(duì)象的所有者不存在励堡,因此廢棄該對(duì)象
*/
__strong 修飾符之"非自己生成的對(duì)象谷丸,自己也能持有"
-(void)testARC
{
id __strong obj0 = [[NSObject alloc]init];//對(duì)象A
/**
obj0 持有對(duì)象A的強(qiáng)引用
*/
id __strong obj1 = [[NSObject alloc]init];//對(duì)象B
/**
* obj1 持有對(duì)象B的強(qiáng)引用
*/
id __strong obj2 = nil;
/**
* obj2 不持有任何對(duì)象
*/
obj0 = obj1;
/**
* obj0 被持有對(duì)象B的 obj1 賦值,所以 obj0 持有對(duì)象B
因?yàn)?obj0 被賦值应结,所以原來持有對(duì)象A的強(qiáng)引用失效
對(duì)象A不再被任何對(duì)象持有刨疼,因此廢棄對(duì)象A
此時(shí),持有對(duì)象的B的強(qiáng)引用變量為:obj1 obj0
*/
obj2 = obj0;
/**
* obj2 被持有對(duì)象B的 obj0 賦值鹅龄,所以 obj2 持有對(duì)象B
此時(shí)揩慕,持有對(duì)象的B的強(qiáng)引用變量為:obj1 obj0 obj2
*/
obj1 = nil;
/**
* 因?yàn)?obj1 被賦值為 nil,所以持有對(duì)象B的強(qiáng)引用失效
此時(shí)扮休,持有對(duì)象的B的強(qiáng)引用變量為:obj2 obj0
*/
obj0 = nil;
/**
* 因?yàn)?obj0 被賦值為 nil漩绵,所以持有對(duì)象B的強(qiáng)引用失效
此時(shí),持有對(duì)象的B的強(qiáng)引用變量為:obj2
*/
obj2 = nil;
/**
* 因?yàn)?obj2 被賦值為 nil肛炮,所以持有對(duì)象B的強(qiáng)引用失效
此時(shí)止吐,沒有變量持有對(duì)象的B的強(qiáng)引用,所以對(duì)象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 超出作用域后侨糟,強(qiáng)引用失效碍扔,之后的鏈接也跟著失效。
__weak 修飾符
通過上面對(duì) __strong 修飾符的例子來看秕重,貌似只要有__strong就可以很好地實(shí)現(xiàn)內(nèi)存的管理不同,但是實(shí)際上不是這樣的。__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];//對(duì)象A
[obj setObj:obj];
}
//obj 超出作用域二拐,對(duì)對(duì)象A強(qiáng)引用失效服鹅,此時(shí)對(duì)象A的強(qiáng)引用變量有:obj的成員變量_obj
通過Xcode自帶的 instrument 檢測(cè)循環(huán)引用如下:
怎樣才能消除循環(huán)引用呢?這時(shí)候應(yīng)該引入__weak 修飾符了百新。
__weak 修飾符不持有對(duì)象實(shí)例企软,提供弱引用。
id __weak obj = [[YYTestObject alloc]init];//對(duì)象A
//此源碼編譯器會(huì)發(fā)出警告饭望。因?yàn)檎躺冢瑸榱瞬怀钟凶约荷刹⒊钟械膶?duì)象,生成的對(duì)象會(huì)立即被釋放
id obj = [[YYTestObject alloc]init];//對(duì)象A
id __weak obj2 = obj;
-----------------------------------------------------------
-(void)testARC
{
id obj = [[YYTestObject alloc]init];//對(duì)象A
id __weak obj2 = obj;
[obj setObj:obj2];
}
//因?yàn)?obj 超出作用域铅辞,所以對(duì)對(duì)象A的強(qiáng)引用失效
//對(duì)象A沒有被持有厌漂,因此廢棄對(duì)象A
//obj 無持有者,因此也廢棄對(duì)象 obj
//此時(shí)斟珊,持有該對(duì)象 obj 弱引用的 obj2 變量的弱引用失效苇倡,nil 賦值給 obj2,使 obj2 沒有成為懸掛指針
______unsafe __unretained
______weak 修飾符只是用于 iOS5 以上及 OS X Lion 以上的版本的程序囤踩。因此不在該范圍的程序就使用 ____unsafe __unretained 來修飾旨椒。附有該修飾符的變量不屬于編譯器的內(nèi)存管理對(duì)象,所以要注意賦值對(duì)象的所有者高职,否則會(huì)造成內(nèi)存泄露或是程序崩潰钩乍。
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc]init];//對(duì)象A
obj1 = obj0;
NSLog(@"A: %@",obj1);
}
//因?yàn)?obj0 超出作用域辞州,所以對(duì)對(duì)象A的強(qiáng)引用失效
//對(duì)象A沒有被持有怔锌,因此廢棄對(duì)象A
//obj0 無持有者,因此也廢棄對(duì)象 obj
//此時(shí)变过,持有該對(duì)象 obj 弱引用的 obj1 變量的弱引用失效
NSLog(@"B: %@",obj1);
//輸出 obj1 變量表示的對(duì)象
//obj1 變量表示的對(duì)象已經(jīng)被廢棄(懸掛指針)埃元,錯(cuò)誤訪問
PS:如果 NSLog(@"B: %@",obj1); 訪問成功的話,那只是碰巧而已媚狰,該區(qū)域的內(nèi)存還沒有被復(fù)用岛杀。這也說明了__unsafe __unretained 修飾的變量不會(huì)被賦值 nil ,以防止懸掛指針崭孤,所以訪問者類型的變量类嗤,一定要確保對(duì)象是否存在,否則會(huì)導(dǎo)致程序崩潰辨宠。
__autoreleasing
在 ARC 有效的情況下遗锣,是不能使用 autorelease 方法,也不能使用 NSAutoreleasePool 類嗤形。這樣一來精偿,autorelease 無法直接調(diào)用,但實(shí)際上,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)注冊(cè)到 autoreleasepool 中
}
}
值得注意的是,顯示地添加 ______autoreleasing 修飾符同顯示添加____strong 修飾符一樣少見叶组。所以下面簡(jiǎn)述一下其隱式的使用情況:
- 取得非自己生成并持有的對(duì)象拯田,即是除了 alloc / new / copy / mutableCopy 等方法取得對(duì)象,一般是類辦法扶叉。
- 對(duì)象作為返回值
- __weak 修飾的對(duì)象
- id 的指針或是對(duì)象的指針
(1) 取得非自己生成并持有的對(duì)象
編譯器會(huì)檢查方法名是否以 alloc / new / copy / mutableCopy 開始勿锅,如果不是則自動(dòng)將返回值的對(duì)象注冊(cè)到 autoreleasepool 中。
@autoreleasepool {
//取得非自己生成并持有的對(duì)象 --> [NSArray array]
id __strong *array = [NSArray array];
//因?yàn)?array 為強(qiáng)引用枣氧,所以持有對(duì)象溢十,
//并且該對(duì)象有編譯器判斷其方法名后,自動(dòng)注冊(cè)到 autoreleasepool 中
}
//對(duì)象 array 超出作用域达吞,所以強(qiáng)引用失效张弛,同時(shí)自動(dòng)釋放自己所持有的對(duì)象
//同時(shí),隨著 @autoreleasepool 快的結(jié)束酪劫,注冊(cè)到 autoreleasepool 中的所有對(duì)象被自動(dòng)釋放吞鸭。
//因?yàn)閷?duì)象的不存在,所以廢棄對(duì)象
//持有關(guān)系:autoreleasepool --> array --> [NSArray array]
(2) 對(duì)象作為返回值
第(1)條中覆糟,不顯式地使用 __autoreleasing 也能使對(duì)象注冊(cè)到 autoreleasepool 中刻剥,下面我們來看下取得非自己生成并持有的對(duì)象時(shí)被調(diào)用方法的源碼代碼示例:
+(id) array
{
//方案一
return [[NSArray alloc]init];
//方案二
id obj = [[NSArray alloc]init];
return obj;
}
//由于 return 超出作用域,所以該強(qiáng)引用對(duì)應(yīng)的自己持有的對(duì)象會(huì)被自動(dòng)釋放滩字,但該對(duì)象作為返回值造虏,所以編譯器會(huì)自動(dòng)將該對(duì)象注冊(cè)到 autoreleasepool 中。
(3) __weak 修飾的對(duì)象
______weak 修飾的對(duì)象是對(duì)某對(duì)象持有弱引用麦箍,如果要訪問某對(duì)象的時(shí)候漓藕,該對(duì)象有可能已經(jīng)被廢棄了,這樣會(huì)導(dǎo)致程序崩潰挟裂。因此享钞,______weak 修飾的變量必定要注冊(cè)到 autoreleasepool 中,在 @autoreleasepool 快結(jié)束之前诀蓉,都能確保該對(duì)象的存在栗竖。
id __weak obj1 = obj0;
NSLog(@"obj: %@",obj1);
相等于
||
||
id __weak obj1 = obj0;
id __autorealeasing tmp = obj1;
NSLog(@"obj: %@",tmp);
(4) id 的指針或是對(duì)象的指針
對(duì)象的指針,即是獲得對(duì)象的地址渠啤。形式如:NSObject **obj 狐肢。
同前面講述的 id obj 和 id ______strong obj 完全一樣。那么 id 的指針 id *obj 又如何埃篓?可以由 id ______strong obj 的例子類推出 id ______strong *obj 嗎处坪?其實(shí)推出來的是 id __autorealeasing *obj。
同樣的 NSObject **obj 便成為了 ,NSObject * __autorealeasing *obj同窘。
在開發(fā)中玄帕,我們常常需要得到詳細(xì)的錯(cuò)誤信息,經(jīng)常會(huì)在方法的參數(shù)中傳遞 NSError 對(duì)象的指針想邦,而不是函數(shù)返回值裤纹。
NSError *error = nil;
BOOL result = [myObject performOperationWithError:&error];
//方法的聲明
-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
//根據(jù)前面所講述的,除了 alloc / new / copy / mutableCopy 辦法返回值取得的是對(duì)象是自己生成并持有的丧没,其他的方法都是非自己生成并持有的鹰椒。所以 performOperationWithError 辦法需要獲取的參數(shù) error 對(duì)象,都會(huì)注冊(cè)到 autoreleasepool呕童,并取得非自己生成并持有的對(duì)象漆际。
值得一提的是:賦值給對(duì)象指針的所有權(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修飾的奸汇,為什么編譯沒有報(bào)錯(cuò)呢?
其實(shí)實(shí)際上編譯器已經(jīng)將傳遞參數(shù)的那部分源碼轉(zhuǎn)化成如下的形式了:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL result = [myObject performOperationWithError:&tmp];
error = tmp;
具體也可以參考蘋果的官方文檔:蘋果文檔
最后往声,如果想打印 autoreleasePool 中有哪些對(duì)象擂找,不管是在 ARC 或是非 ARC 的情況下,都可以調(diào)用非公開的 _objc_autoreleasePoolPrint()浩销;
既然簡(jiǎn)單地介紹了 ARC 一些簡(jiǎn)單用法贯涎,下面就看看在 ARC 下,有哪些規(guī)則吧慢洋。
- 不能使用 retain / release / retainCount / autorelease
- 不能使用NSAllocateObject / NSDeallocateObject
- 必須遵守內(nèi)存管理的方法命名
- 不要顯示調(diào)用 dealloc
- 使用 @autoreleasepool 塊代替 NSAutoreleasepool
- 不能使用區(qū)域(NSZone)
- 對(duì)象型變量不能作為C語言的結(jié)構(gòu)體(struct / union)的成員
- 顯示轉(zhuǎn)換 “id” 和 “void *”
上面的一些規(guī)則中塘雳,挑幾條值得注意的來講述一下:
(1)必須遵守內(nèi)存管理的方法命名
init 開頭的方法,必定是要返回對(duì)象且警,對(duì)象應(yīng)該是 id 類型或是該方法聲明類的對(duì)象類型粉捻,或者是該類的超類或子類型礁遣。該返回的對(duì)象是不會(huì)注冊(cè)到 autoreleasepool 中的斑芜。基本只是對(duì) alloca 方法返回值的對(duì)象進(jìn)行初始化并返回該對(duì)象的祟霍。
-(id)initWithObject; (?)
-(void)initWithObject; (?) --->返回值
-(id)initialize; (?) --->應(yīng)是駝峰式命名
為了使能夠讓手動(dò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)對(duì)象廢棄的時(shí)候醇王,都會(huì)調(diào)用該辦法的。在該辦法中大多數(shù)只使用刪除已注冊(cè)的代理和觀察者對(duì)象即可崭添。
// 非 ARC
-(void)dealloc
{
//do something
[super dealloc];//一定要注意調(diào)用的順序
}
// ARC
-(void)dealloc
{
//do something
// [super dealloc];在 ARC 下寓娩,無需顯示調(diào)用該方法, ARC 會(huì)自動(dòng)處理的。
}
(3) 對(duì)象型變量不能作為C語言的結(jié)構(gòu)體(struct / union)的成員
struct Data
{
NSMutableArray *array;
};
如果結(jié)構(gòu)體中的成員變量出現(xiàn) Objective - C 對(duì)象棘伴,便會(huì)引起編譯錯(cuò)誤寞埠。這是因?yàn)镃語言的規(guī)約上沒有方法來管理結(jié)構(gòu)體成員的生存周期。C語言的局部變量可使用該變量的作用域來管理對(duì)象焊夸,而 ARC 吧內(nèi)存管理的工作分配給編譯器的仁连。不過實(shí)在需要,也可以改成如下:
struct Data{
NSMutableArray ______unsafe____unretained *array;
};
(4) 顯示轉(zhuǎn)換 “id” 和 “void *”
在 ARC 下阱穗,我們有時(shí)候需要將 Core Foundation 對(duì)象轉(zhuǎn)換成一個(gè) Objective-C 對(duì)象饭冬,這時(shí)候我們就應(yīng)該告訴編譯器,在轉(zhuǎn)換的過程中引用計(jì)數(shù)需要如何調(diào)整揪阶,這時(shí)候我們就應(yīng)該使用到與 bridge 有關(guān)的關(guān)鍵字了昌抠。一下是這些關(guān)鍵字的說明;
- __bridge :只做類型轉(zhuǎn)換,不修改相關(guān)對(duì)象的引用計(jì)數(shù)鲁僚。
- ______bridge____retained:類型轉(zhuǎn)換后扰魂,相關(guān)對(duì)象的引用計(jì)數(shù)+1
- ______bridge____transfer:類型轉(zhuǎn)換后,該對(duì)象的引用計(jì)數(shù)由 ARC 管理蕴茴,被轉(zhuǎn)換的對(duì)象隨后釋放劝评。
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 變量強(qiáng)轉(zhuǎn)換成 void* 變量是并不會(huì)出問題。
ARC 的實(shí)現(xiàn)
蘋果官方說明中稱倦淀,ARC 是“由編譯器進(jìn)行內(nèi)存管理”的蒋畜,但是實(shí)際上只有編譯器是無法完勝的,需要借助運(yùn)行庫的協(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ù)將對(duì)象注冊(cè)到 autoreleasePool 中姻成,與 autorelease 函數(shù)是有著不同之處的。objc_autoreleasedReturnValue 會(huì)檢查使用該函數(shù)的方法或函數(shù)調(diào)用方的執(zhí)行命令列表愿棋,如果方法或函數(shù)的調(diào)用方在調(diào)用了方法或函數(shù)后緊接著調(diào)用 objc_retainAutoreleasedReturnValue 函數(shù)科展,那么就不會(huì)將對(duì)象注冊(cè)到 autoreleasePool 中,而是直接傳遞到方法或是函數(shù)調(diào)用方糠雨。objc_retainAutoreleasedReturnValue 與 objc_retain 函數(shù)不同才睹,它即便不注冊(cè)到 autoreleasePool 中,也是能夠正確獲取對(duì)象的甘邀。這樣一來琅攘,就少了對(duì)象注冊(cè)到 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ù)的賦值對(duì)象的地址為鍵值,將第一參數(shù)的附有 __weak 修飾符的變量的地址注冊(cè)到 weak 表中逗抑。如果第二個(gè)參數(shù)為0剧辐,則把變量從 weak 表中刪除寒亥。
問題1:weak 表的作用?
- 當(dāng)對(duì)象被廢棄了荧关,在把 __weak 的變量從 weak 表中刪除時(shí)护盈,將變量的地址,賦值為 nil;
問題2:使用 __weak 修飾的變量羞酗,是從 weak 表中還是從 autoreleasePool 中獲取對(duì)象腐宋?
- 在使用 __weak 的變量時(shí),一方我們可以通過 weak 表獲取變量的地址而找到對(duì)應(yīng)的對(duì)象檀轨,一方也可以從注冊(cè)在 autoreleasePool 中去獲取胸竞,但從模擬編譯器的代碼 NSLog(@"obj = %@",tmp); 可以看出,應(yīng)該是從后者去獲取到對(duì)象参萄。
注意: ______weak 修飾的變量被每一使用一次卫枝,所賦值的對(duì)象都會(huì)被注冊(cè)到 ______autoreleasePool 中一次,所以為了減少對(duì)內(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);
尾巴:通過介紹引用計(jì)數(shù)式的內(nèi)存管理方式如何使用筒溃,到它們是如何實(shí)現(xiàn)马篮,再到如何利用引用計(jì)數(shù)式的內(nèi)存管理方式去實(shí)現(xiàn) ARC。
參考資料:
Objective-C 高級(jí)編程