概述
在iOS中開發(fā)中泌绣,我們或多或少都聽說過內(nèi)存管理楷怒。iOS的內(nèi)存管理一般指的是OC對象的內(nèi)存管理集乔,因?yàn)镺C對象分配在堆內(nèi)存留荔,堆內(nèi)存需要程序員自己去動(dòng)態(tài)分配和回收;基礎(chǔ)數(shù)據(jù)類型(非OC對象)則分配在棧內(nèi)存中爹梁,超過作用域就會(huì)由系統(tǒng)檢測回收腹躁。如果我們在開發(fā)過程中纱皆,對內(nèi)存管理得不到位同眯,就有可能造成內(nèi)存泄露绽昼。
我們通常講的內(nèi)存管理,實(shí)際上從發(fā)展的角度來說须蜗,分為兩個(gè)階段:MRC和ARC硅确。MRC指的是手動(dòng)內(nèi)存管理,在開發(fā)過程中需要開發(fā)者手動(dòng)去編寫內(nèi)存管理的代碼明肮;ARC指的是自動(dòng)內(nèi)存管理菱农,在此內(nèi)存管理模式下由LLVM編譯器和OC運(yùn)行時(shí)庫生成相應(yīng)內(nèi)存管理的代碼。
通篇主要介紹關(guān)于內(nèi)存管理的原理及ARC和MRC環(huán)境下編寫代碼實(shí)現(xiàn)的差異晤愧。
提綱
一 引用計(jì)數(shù)
二 MRC操作對象的方法
- alloc/new/copy/mutableCopy
- retain
- release
- autorelease
- autorelease pool
三 ARC操作對象的修飾符
- __strong
1.1 strong與變量
1.2 strong與屬性
1.3 strong的實(shí)現(xiàn) - __weak
2.1 weak和循環(huán)引用
2.2 weak和變量
2.3 weak的實(shí)現(xiàn)
? 2.3.1 weak和賦值
? 2.3.2 weak和訪問 - __unsafe_unretained
- _autoreleasing
具體介紹
一大莫、引用計(jì)數(shù)
在OC中蛉腌,使用引用計(jì)數(shù)來進(jìn)行內(nèi)存管理官份。每個(gè)對象都有一個(gè)與其相對應(yīng)的引用計(jì)數(shù)器只厘,當(dāng)持有一個(gè)對象,這個(gè)對象的引用計(jì)數(shù)就會(huì)遞增舅巷;當(dāng)這個(gè)對象的某個(gè)持有被釋放羔味,這個(gè)對象的引用計(jì)數(shù)就會(huì)遞減。當(dāng)這個(gè)對象的引用計(jì)數(shù)變?yōu)?钠右,那么這個(gè)對象就會(huì)被系統(tǒng)回收赋元。
當(dāng)一個(gè)對象使用完沒有釋放,此時(shí)其引用計(jì)數(shù)永遠(yuǎn)大于1飒房。該對象就會(huì)一直占用其分配在堆內(nèi)存的空間搁凸,就會(huì)導(dǎo)致內(nèi)存泄露。內(nèi)存泄露到一定程度有可能導(dǎo)致內(nèi)存溢出狠毯,進(jìn)而導(dǎo)致程序崩潰护糖。
二、MRC操作對象的方法
1.alloc/new/copy/mutableCopy
1.1 持有調(diào)用者自己的對象
在蘋果規(guī)定中嚼松,使用alloc/new/copy/mutableCopy創(chuàng)建返回的對象歸調(diào)用者所有嫡良,例如以下MRC代碼:
NSMutableArray *array = [[NSMutableArray alloc] init];/*NSMutableArray類對象A*/
NSLog(@"%p", array);
[array release];//釋放
由于對象A由alloc生成,符合蘋果規(guī)定献酗,指針變量array指向并持有對象A寝受,引用計(jì)數(shù)器會(huì)加1。另外罕偎,array在使用完對象A后需要對其進(jìn)行釋放很澄。當(dāng)調(diào)用release后,釋放了其對對象A的引用颜及,計(jì)數(shù)器減1痴怨。對象A此時(shí)引用計(jì)數(shù)值為零,所以對象A被回收器予。不能訪問已經(jīng)被回收的對象浪藻,會(huì)發(fā)生崩潰。
1.2 持有非調(diào)用者擁有的對象
當(dāng)持有非調(diào)用者自己擁有的對象的時(shí)候乾翔,例如以下代碼:
id obj = [Person person];
[obj retain];
/*do something*/
[obj release];
此時(shí)obj變量獲得但不持有Person類對象爱葵,可以通過retain進(jìn)行持有該對象。當(dāng)我們使用完該對象反浓,應(yīng)該調(diào)用release方法釋放該對象萌丈。
注意:按照蘋果的命名規(guī)則,必須是alloc/new/copy/mutableCopy開頭雷则,并且是符合駝峰命名規(guī)則生成的對象才歸調(diào)用者所有辆雾。例如以下的方法,生成的對象不歸調(diào)用者所有:
- (id)newarray;
- (id)allocwithInfo;
- (id)coPySomething;
- (id)mutablecopyItem;
2.retain
2.1 retain和屬性
我們可以通過屬性來保存對象月劈,如果一個(gè)屬性為強(qiáng)引用度迂,我們就可以通過屬性的實(shí)例變量和存取方法來對某個(gè)對象進(jìn)行操作藤乙,例如某個(gè)屬性的setter方法如下:
- (void)setPerson:(Person *)person {
[person retain];
[_person release];
_person = person;
}
我們通過retain新值,release舊值惭墓,再給實(shí)例變量更新值坛梁。需要注意的一點(diǎn)是:需要先retain新值,再release新值腊凶。因?yàn)槿绻屡f值是同一個(gè)對象的話划咐,先release就有可能導(dǎo)致該對象被系統(tǒng)回收,再去retain就沒有任何意義了钧萍。例如下面這個(gè)例子:
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong)Person *person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//實(shí)例變量持有Person類對象(P對象)褐缠。這樣賦值不會(huì)調(diào)用set方法
_person = [[Person alloc] init];
self.person = _person;//調(diào)用set方法
}
- (void)setPerson:(Person *)person {
//release釋放對P對象的引用,P對象引用計(jì)數(shù)值變?yōu)榱惴缡荩瑒tP對象被系統(tǒng)回收
[_person release];
//由于P對象已經(jīng)被回收送丰,再去retain就容易出問題
[person retain];
_person = person;
}
@end
由于P對象被回收,對應(yīng)其所分配的內(nèi)存被置于“可用內(nèi)存池”中弛秋。如果該內(nèi)存未被覆寫器躏,那么P對象依然有效;如果內(nèi)存被覆寫蟹略,那么實(shí)例變量_person就會(huì)指向一個(gè)被覆寫的未知對象的指針登失,那么實(shí)例變量就變成一個(gè)“懸掛指針”。
2.2 retain和數(shù)組
如果我們把一個(gè)對象加入到一個(gè)數(shù)組中挖炬,那么該數(shù)組的addObject方法會(huì)對該對象調(diào)用retain方法揽浙。例如以下代碼:
//person獲得并持有P對象,P對象引用計(jì)數(shù)為1
Person *person = [[Person alloc] init];//Person類對象生成的P對象
NSMutableArray *array = [NSMutableArray array];
//person被加入到數(shù)組意敛,對象P引用計(jì)數(shù)值為2
[array addObject:person];
此時(shí)馅巷,對象P被person和array兩個(gè)變量同時(shí)持有。
3.release
3.1 自己持有的對象自己釋放
當(dāng)我們持有一個(gè)對象草姻,如果在不需要繼續(xù)使用該對象钓猬,我們需要對其進(jìn)行釋放(release)。例如以下代碼:
//array獲得并持有NSArray類對象
NSArray *array = [[NSArray alloc] init];
/*當(dāng)不再需要使用該對象時(shí)撩独,需要釋放*/
[array release];
//obj獲得但不持有該對象
id obj = [NSArray array];
//持有對象
[obj retain];
/*當(dāng)不在需要使用該對象時(shí)敞曹,需要釋放*/
[obj release];
3.2 非自己持有的對象不要釋放
當(dāng)我們不持有某個(gè)對象,卻對該對象進(jìn)行釋放综膀,應(yīng)用程序就會(huì)崩潰澳迫。
//獲得并持有A對象
Person *p = [[Person alloc] init];//Person類對象A
//p釋放對象A的強(qiáng)引用,對象A所有者不存在
//對象A引用計(jì)數(shù)為零剧劝,所以對象A被回收
[p release];
[p release];//釋放非自己持有的對象
另外橄登,我們也不能訪問某個(gè)已經(jīng)被釋放的對象,該對象所占的堆空間如果被覆寫就會(huì)發(fā)生崩潰的情況。
4.autorelease
autorelease指的是自動(dòng)釋放拢锹,當(dāng)一個(gè)對象收到autorelease的時(shí)候谣妻,該對象就會(huì)被注冊到當(dāng)前處于棧頂?shù)淖詣?dòng)釋放池(autorelease pool)。如果沒有主動(dòng)生成自動(dòng)釋放池面褐,則當(dāng)前自動(dòng)釋放池對應(yīng)的是主運(yùn)行循環(huán)的自動(dòng)釋放池。在當(dāng)前線程的RunLoop進(jìn)入休眠前取胎,就會(huì)對被注冊到該自動(dòng)釋放池的所有對象進(jìn)行一次release操作展哭。
autorelease和release的區(qū)別是:release是馬上釋放對某個(gè)對象的強(qiáng)引用;autorelease是延遲釋放某個(gè)對象的生命周期闻蛀。
autorelease通常運(yùn)用在當(dāng)調(diào)用某個(gè)方法需要返回對象的情況下匪傍,例如以下代碼:
//外部調(diào)用
Person *p = [Person person];
NSLog(@"%p", p);//使用無須retain
//持有則需要retain
[p retain];
_person = p;
[_person release];
//Person類內(nèi)部定義
+ (id)person {
//創(chuàng)建的Person類對象由person獲得并持有
Person *person = [[Person alloc] init];
//[person release];
[person autorelease];
return person;
}
在外部調(diào)用,從方法名person知道觉痛,創(chuàng)建的對象由p指針變量獲得但不持有役衡。在函數(shù)內(nèi)部,person獲得并持有了Person類對象薪棒,所返回的person對象的引用計(jì)數(shù)加1手蝎。換句話說,調(diào)用者需要額外處理這多出來的一個(gè)持有操作俐芯。另外棵介,我們不能在函數(shù)內(nèi)部調(diào)用release,不然對象還沒返回就已經(jīng)被系統(tǒng)回收吧史。這時(shí)候使用autorelease就能很好地解決這個(gè)問題邮辽。
只要把要返回的對象調(diào)用autorelease方法,注冊到自動(dòng)釋放池就能延長person對象的生命周期贸营,使其在autorelease pool銷毀(drain)前依然能夠存活吨述。
另外,person對象在返回時(shí)調(diào)用了autorelease方法钞脂。該對象已經(jīng)在自動(dòng)釋放池中揣云,我們可以直接使用對象p,無須再通過[p retain]訪問;不過冰啃,如果要用實(shí)例變量持有該對象灵再,則需要對變量p進(jìn)行一次retain操作,示例變量使用完該對象需要釋放該對象亿笤。
5. autorelease pool
5.1 autorelease pool和RunLoop(運(yùn)行循環(huán))
每條線程都包含一個(gè)與其對應(yīng)的自動(dòng)釋放池翎迁,當(dāng)某條線程被終止的時(shí)候,對應(yīng)該線程的自動(dòng)釋放池會(huì)被銷毀净薛。同時(shí)汪榔,處于該自動(dòng)釋放池的對象將會(huì)進(jìn)行一次release操作。
當(dāng)應(yīng)用程序啟動(dòng),系統(tǒng)默認(rèn)會(huì)開啟一條線程痴腌,該線程就是“主線程”雌团。主線程也有一個(gè)與之對應(yīng)的自動(dòng)釋放池,例如我們常見的ARC下的main.h文件:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
該自動(dòng)釋放池用來釋放在主線程下注冊到該自動(dòng)釋放池的對象士聪。需要注意的是锦援,當(dāng)我們開啟一條子線程,并且在該線程開啟RunLoop的時(shí)候剥悟,需要為其增加一個(gè)autorelease pool灵寺,這樣有助于保證內(nèi)存的安全。
5.2 autorelease pool和降低內(nèi)存峰值
當(dāng)我們執(zhí)行一些復(fù)雜的操作区岗,特別是如果這些復(fù)雜的操作要被循環(huán)執(zhí)行略板,那么中間會(huì)免不了會(huì)產(chǎn)生一些臨時(shí)變量。當(dāng)被加到主線程自動(dòng)釋放池的對象越來越來多慈缔,卻沒有得到及時(shí)釋放叮称,就會(huì)導(dǎo)致內(nèi)存溢出。這個(gè)時(shí)候藐鹤,我們可以手動(dòng)添加自動(dòng)釋放池來解決這個(gè)問題瓤檐。如以下例子所示:
for (int i = 0; i < largeNumber; i++) {
//創(chuàng)建自動(dòng)釋放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//產(chǎn)生許多被注冊到自動(dòng)釋放池的臨時(shí)對象
id obj = [Person personWithComplexOperation];
//釋放池中對象
[pool drain];
}
如上述例子所示,我們執(zhí)行的循環(huán)次數(shù)是一個(gè)非常大的數(shù)字娱节。并且調(diào)用personWithComplexOperation方法的過程中會(huì)產(chǎn)生許多臨時(shí)對象距帅,所產(chǎn)生的臨時(shí)對象有可能會(huì)被注冊到自動(dòng)釋放池中。我們通過手動(dòng)生成一個(gè)自動(dòng)釋放池括堤,并且在每次循環(huán)結(jié)束前把該自動(dòng)釋放池的對象執(zhí)行release操作釋放掉碌秸,這樣就能有效地降低內(nèi)存的峰值了。
三 ARC操作對象的修飾符
1. __strong
1.1 __strong與變量
在ARC模式下悄窃,id類型和OC對象的所有權(quán)修飾符默認(rèn)是__strong讥电。當(dāng)一個(gè)變量通過__strong修飾符來修飾,當(dāng)該變量超出其所在作用域后轧抗,該變量就會(huì)被廢棄恩敌。同時(shí),賦值給該變量的對象也會(huì)被釋放横媚。例如:
{
//變量p持有Person對象的強(qiáng)引用
Person *p = [Person person];
//__strong修飾符可以省略
//Person __strong *p = [Person person];
}
//變量p超出作用域纠炮,釋放對Person類對象的強(qiáng)引用
//Person類對象持有者不存在,該對象被釋放
上述例子對應(yīng)的MRC代碼如下:
{
Person *p = [Person person];
[p retain];
[p release];
}
可以看出灯蝴,ARC和MRC是對應(yīng)的恢口。只不過在ARC下,對象“持有”和“釋放”的內(nèi)存管理代碼交由系統(tǒng)去調(diào)用罷了穷躁。
1.2 strong與屬性
如果一個(gè)屬性的修飾符是strong,對于其實(shí)例變量所持有的對象耕肩,編譯器會(huì)在該實(shí)例變量所屬類的dealloc方法為其添加釋放對象的方法。在MRC下,我們的dealloc方法有如:
- (void)dealloc {
[p release];//ARC無效
[super dealloc];//ARC無效
}
在ARC下猿诸,我們就無須再去寫這樣的代碼了婚被。另外,在dealloc方法中梳虽,ARC只能幫我們處理OC對象址芯。如果實(shí)例變量持有類似CoreFoundation等非OC對象,則需要我們手動(dòng)回收:
- (void)dealloc {
CFRelease(_cfObject);
}
在ARC下窜觉,dealloc方法一般用來執(zhí)行兩個(gè)任務(wù)谷炸。第一個(gè)就是手動(dòng)釋放非OC對象;第二個(gè)是接觸監(jiān)聽竖螃。另外淑廊,在ARC下我們不能主動(dòng)調(diào)用dealloc方法逗余。因?yàn)橐坏┱{(diào)用dealloc特咆,對象就不再有效。該方法運(yùn)行期系統(tǒng)會(huì)在合適的時(shí)機(jī)自動(dòng)去調(diào)用录粱。
1.3 strong的實(shí)現(xiàn)
在ARC中腻格,除了會(huì)自動(dòng)調(diào)用“保留”和“釋放”方法外,還進(jìn)行了優(yōu)化啥繁。例如某個(gè)對象執(zhí)行了多次“保留”和“釋放”菜职,那么ARC針對特殊情況有可能會(huì)將該對象的“保留”和“釋放”成對地移除。例如:
+ (id)person {
Person *tmp = [[Person alloc] init];//引用計(jì)數(shù)為1
[tmp autorelease];//注冊到自動(dòng)釋放池(ARC無效)
return tmp;
}
{
//ARC
_p = [Person person];//_p是強(qiáng)引用屬性對應(yīng)的實(shí)例變量
//實(shí)現(xiàn)展示
Person *p = [Person person];//Person類對象引用計(jì)數(shù)為1
_p = [p retain];//遞增為2
[_p release];//遞減為1
}
//清空自動(dòng)釋放池旗闽,Person類對象遞減為0酬核,釋放該對象
在上述代碼中,ARC對應(yīng)MRC的具體實(shí)現(xiàn)展示适室。在上面的展示中嫡意,+(id)person方法內(nèi)部會(huì)調(diào)用一次autorelease操作來延遲其返回對象的生命周期,并且稍后在自動(dòng)釋放池進(jìn)行release操作捣辆。_p通過retain來持有該對象蔬螟,使用完就執(zhí)行release操作。從而看出retain和autorelease是多余的汽畴,完全可以簡化成以下代碼:
+ (id)person {
Person *tmp = [[Person alloc] init];//引用計(jì)數(shù)為1
return tmp;
}
{
//ARC
_p = [Person person];//_p是強(qiáng)引用屬性對應(yīng)的實(shí)例變量
//實(shí)現(xiàn)展示
_p = [Person person];//Person類對象引用計(jì)數(shù)為1
[_p release];//遞減為0旧巾,Person類對象被回收
}
那么ARC是如何判斷是否移除這種成對的操作呢?其實(shí)在ARC中忍些,并不是直接執(zhí)行retain和autorelease操作的鲁猩,而是通過以下兩個(gè)方法:
objc_autoreleaseReturnValue(obj);//對應(yīng)autorelease
objc_retainAutoreleasedReturnValue(obj);//對應(yīng)retain
以下為兩個(gè)方法對應(yīng)的偽代碼:
id objc_autoreleaseReturnValue(id obj) {
if ("返回對象obj后面的那段代碼是否執(zhí)行retain") {
//是
set_flag(obj);//設(shè)置標(biāo)志位
return obj;
} else {
return [obj autorelease];
}
}
id objc_retainAutoreleasedReturnValue(obj) {
if (get_flag(obj)) {
//有標(biāo)志位
return obj;
} else {
return [obj retain];
}
}
通過以上兩段偽代碼,我們重新梳理一下代碼:
+ (id)person {
Person *tmp = [[Person alloc] init];//引用計(jì)數(shù)為1
return objc_autoreleaseReturnValue(id tmp);
}
{
//ARC
_p = [Person person];//_p是強(qiáng)引用屬性對應(yīng)的實(shí)例變量
//實(shí)現(xiàn)展示
Person *p = [Person person];//Person類對象引用計(jì)數(shù)為1
_p = objc_retainAutoreleasedReturnValue(p);//遞增為1
[_p release];//遞減為0
}
從上述示例代碼可以看出:
當(dāng)我們用變量p獲取person方法返回的對象前罢坝,person方法內(nèi)部會(huì)執(zhí)行objc_autoreleaseReturnValue方法绳匀。該方法會(huì)檢測返回對象之后即將執(zhí)行的那段代碼,如果那段代碼要向所返回的對象執(zhí)行retain方法,則為該對象設(shè)置一個(gè)全局?jǐn)?shù)據(jù)結(jié)構(gòu)中的標(biāo)志位疾棵,并把對象直接返回戈钢。反之,在返回之前把該對象注冊到自動(dòng)釋放池是尔。
當(dāng)我們對Person類對象執(zhí)行retain操作的時(shí)候殉了,會(huì)執(zhí)行objc_retainAutoreleasedReturnValue方法。該方法會(huì)檢測對應(yīng)的對象是否已經(jīng)設(shè)置過標(biāo)志位拟枚,如果是薪铜,則直接把該對象返回;反之恩溅,會(huì)向該對象執(zhí)行一次retain操作再返回隔箍。
在ARC中,通過設(shè)置和檢測標(biāo)志位可以移除多余的成對(“保留”&“釋放”)操作脚乡,優(yōu)化程序的性能蜒滩。
2. __weak
2.1 weak和循環(huán)引用
__weak與我們上述所提到的__strong相對應(yīng),__strong對某個(gè)對象具有強(qiáng)引用奶稠。那么俯艰,__weak則指的是對某個(gè)對象具有弱引用。一般weak用來解決我們開發(fā)中遇到的循環(huán)引用問題锌订,例如以下代碼:
/*Man類*/
#import <Foundation/Foundation.h>
@class Woman;
@interface Man : NSObject
@property (nonatomic, strong)Woman *person;
@end
/*Woman類*/
#import <Foundation/Foundation.h>
@class Man;
@interface Woman : NSObject
@property (nonatomic, strong)Man *person;
@end
/*調(diào)用*/
- (void)viewDidLoad {
[super viewDidLoad];
Man *man = [[Man alloc] init];
Woman *woman = [[Woman alloc] init];
man.person = woman;
woman.person = man;
}
從上述代碼可以看出竹握,Man類對象和Woman類對象分別有一個(gè)所有權(quán)修飾符為strong的person屬性。兩個(gè)類之間互相通過實(shí)例變量進(jìn)行強(qiáng)引用辆飘,Man類對象如果要釋放啦辐,則需要Woman類對象向其發(fā)送release消息。然而Woman類對象要執(zhí)行dealloc方法向Man類對象發(fā)送release消息的話蜈项,又需要Man類對象向其發(fā)送release消息芹关。雙方實(shí)例變量互相強(qiáng)引用類對象,所以造成循環(huán)引用战得,如下圖所示:
這時(shí)候weak修飾符就派上用場了充边,因?yàn)閣eak只通過弱引用來引用某個(gè)對象,并不會(huì)真正意義上的持有該對象常侦。所以我們只需要把上述兩個(gè)類對象的其中一個(gè)屬性用weak來修飾浇冰,就可以解決循環(huán)引用的問題。例如我們把Woman類的person屬性用weak來修飾聋亡,分析代碼如下:
/*調(diào)用*/
- (void)viewDidLoad {
[super viewDidLoad];
Man *man = [[Man alloc] init];//Man對象引用計(jì)數(shù)為1
Woman *woman = [[Woman alloc] init];//Woman對象引用計(jì)數(shù)為1
man.person = woman;//強(qiáng)引用肘习,Woman對象引用計(jì)數(shù)為2
woman.person = man;//弱引用,Man對象引用計(jì)數(shù)為1
}
//變量man超出作用域坡倔,對Man類對象的強(qiáng)引用失效
//Man類對象持有者不存在漂佩,Man類對象被回收
//Man類對象被回收脖含, woman.person = nil;
//Man調(diào)用dealloc方法,Woman類對象引用計(jì)數(shù)遞減為1
//變量woman超出作用域投蝉,對Woman類對象強(qiáng)引用失效
//Woman類對象持有者不存在养葵,Woman類對象被回收
從上述示例代碼可以看出,我們只需要把其中一個(gè)強(qiáng)引用修改為弱引用就可以打破循環(huán)引用瘩缆。類似的循環(huán)引用常見的有block关拒、NSTimer、delegate等庸娱,感興趣的自行查閱相關(guān)資料着绊,這里不作一一介紹。
另外熟尉,基于運(yùn)行時(shí)庫归露,如果變量或?qū)傩允褂脀eak來修飾,當(dāng)其所指向的對象被回收剧包,那么會(huì)自動(dòng)為該變量或?qū)傩再x值為nil棚放。這一操作可以有效地避免程序出現(xiàn)野指針操作而導(dǎo)致崩潰局骤,不過強(qiáng)烈不建議使用一個(gè)已經(jīng)被回收的對象的弱引用,這本身對于程序設(shè)計(jì)而言就是一個(gè)bug。
2.2 weak和變量
如果一個(gè)變量被__weak修飾,代表該變量對所指向的對象具有弱引用涵卵。例如以下代碼:
Person __weak *weakPerson = nil;
if (1) {
Person *person = [[Person alloc] init];
weakPerson = person;
NSLog(@"%@", weakPerson);//weakPerson弱引用
}
NSLog(@"%@", weakPerson);
輸出結(jié)果如下:
[1198:73648] <Person: 0x600000018120>
[1198:73648] (null)
從上述輸出結(jié)果可以分析贴硫,當(dāng)超出作用域后挖诸,person變量對Person對象的強(qiáng)引用失效搂蜓。Person對象持有者不存在,所以該對象被回收丰涉。同時(shí)投慈,weakPerson變量對Person的弱引用失效猴抹,weakPerson變量被賦值為nil蝙砌。
另外需要注意的是阳堕,如果一個(gè)變量被weak修飾,那么這個(gè)變量不能持有對象示例择克,編譯器會(huì)發(fā)出警告恬总。例如以下代碼:
Person __weak *weakPerson = [[Person alloc] init];
因?yàn)閣eakPerson被__weak修飾,不能持有生成的Person類對象肚邢。所以Person類對象創(chuàng)建完立即被釋放壹堰,所以編譯器會(huì)給出相應(yīng)的警告:
Assigning retained object to weak variable; object will be released after assignment
2.3 weak的實(shí)現(xiàn)
2.3.1 weak和賦值
要解釋weak賦值的實(shí)現(xiàn),我們先看以下示例代碼:
Person *person = [[Person alloc] init];
Person __weak *p = person;
上述對應(yīng)的模擬代碼如下:
Person *person = [[Person alloc] init];
Person *p;
objc_initWeak(&p, person);
objc_destroyWeak(&p, 0);
上述兩個(gè)函數(shù)分別用來對變量p的初始化和釋放骡湖,它們都調(diào)用同一個(gè)函數(shù)贱纠。如下:
id p;
p = 0;
objc_storeWeak(&p, person);//對應(yīng)objc_initWeak
objc_storeWeak(&p, 0);//對應(yīng)objc_destroyWeak
根據(jù)上述代碼我們進(jìn)行分析:
1.初始化變量
當(dāng)我們使用變量弱引用指向一個(gè)對象時(shí),通過傳入變量的地址和賦值對象兩個(gè)參數(shù)來調(diào)用objc_storeWeak方法响蕴。該方法內(nèi)部會(huì)將對象的地址&person作為鍵值谆焊,把變量p的地址&p注冊到weak表中。
2.釋放變量
當(dāng)超出作用域浦夷,Person類對象被回收辖试,此時(shí)調(diào)用objc_storeWeak(&p, 0)把變量的地址從weak表中刪除。 變量地址從weak表刪除前劈狐,利用被回收對象的地址作為鍵值進(jìn)行檢索罐孝,把對應(yīng)的變量地址賦值為nil。
2.3.2 weak和訪問
當(dāng)我們訪問一個(gè)被__weak修飾過的變量所指向的對象時(shí)肥缔,其內(nèi)部是如何實(shí)現(xiàn)的莲兢?我們先看以下示例代碼:
Person *person = [[Person alloc] init];
Person __weak *p = person;
NSLog(@"%@", p);
上述代碼對應(yīng)的模擬代碼如下:
Person *person = [[Person alloc] init];
Person *p;
objc_initWeak(&p, person);
id tmp = objc_loadWeakRetained(&p);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&p);
通過上述代碼可以看出,訪問一個(gè)被__weak修飾的變量辫继,相對于賦值多了兩個(gè)步驟:
- objc_loadWeakRetained怒见,通過該函數(shù)取出對應(yīng)變量所引用的對象并retain;
- 把變量指向的對象注冊到自動(dòng)釋放池俗慈。
這樣一來姑宽,weak變量引用的對象就被加入到自動(dòng)釋放池。在自動(dòng)釋放池結(jié)束前都能安全使用該對象闺阱。需要注意的是炮车,如果大量訪問weak變量,就會(huì)導(dǎo)致weak變量所引用的對象被多次加入到自動(dòng)釋放池酣溃,從而導(dǎo)致影響性能瘦穆。如果需要大量訪問,我們通過__strong修飾的變量來解決赊豌,例如:
Person __weak *p = obj;
Person *tmp = p;//p引用的對象被注冊到自動(dòng)釋放池
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
NSLog(@"%@", tmp);
通過利用__strong中間變量的使用扛或,p引用的對象僅注冊1次到自動(dòng)釋放池中,有效地減少了自動(dòng)釋放池中的對象碘饼。
3. __unsafe_unretained
首先我們需要明確一點(diǎn)熙兔,如果一個(gè)變量被__unsafe_unretained修飾悲伶,那么該變量不屬于編輯器的內(nèi)存管理對象。該修飾符表明不保留值,即對其所指向的對象既不強(qiáng)引用住涉,也不弱引用麸锉。例如以下代碼:
根據(jù)上述代碼分析:當(dāng)超出作用域,變量p對Person類對象的強(qiáng)引用失效舆声。Person類對象的持有者不存在花沉,該對象被回收。當(dāng)我們再次使用該變量的時(shí)候媳握,因?yàn)槠渌硎镜膶ο笠呀?jīng)被回收碱屁,所以就會(huì)發(fā)生野指針崩潰。這一點(diǎn)區(qū)別于__weak蛾找,當(dāng)被__weak修飾變量所指向的對象被回收忽媒,該變量會(huì)賦值為nil。
另外腋粥,被__unsafe_unretained修飾的變量跟__weak一樣晦雨,不能持有對象實(shí)例。因?yàn)開_unsafe_unretained修飾的變量不會(huì)對生成的對象實(shí)例進(jìn)行保留操作隘冲,所以對象創(chuàng)建完立馬被回收闹瞧,編譯器會(huì)給出相應(yīng)的警告。
當(dāng)我們給被__unsafe_unretained修飾的變量賦值時(shí)展辞,必須保證賦值對象確實(shí)存在奥邮,不然程序就會(huì)發(fā)生崩潰。
4. __autoreleasing
在ARC和MRC中罗珍,autorelease作用一致洽腺,只是兩者的表現(xiàn)方式有所不同。
1.如果返回的對象歸對用者所有覆旱,如下:
@autoreleasepool {
Person __autoreleasing *p = [[Person alloc] init];
}
上述代碼模擬代碼如下:
id pool = objc_autoreleasePoolPush();
Person *p = [[Person alloc] init];//持有
objc_autorelease(p);
objc_autoreleasePoolPop(pool);
2.如果返回的對象不歸調(diào)用者所有蘸朋,如下:
@autoreleasepool {
Person __autoreleasing *p = [Person person];
}
上述代碼模擬代碼如下:
id pool = objc_autoreleasePoolPush();
Person *p = [Person person];
objc_retainAutoreleasedReturnValue(p);//持有(優(yōu)化后的retain方法)
objc_autorelease(p);
objc_autoreleasePoolPop(pool);
從上面兩個(gè)例子可以看出,__autoreleasing的實(shí)現(xiàn)都是通過objc_autorelease()函數(shù),只不過是持有方法有所不同而已扣唱。
至此藕坯,內(nèi)存管理就介紹完畢了。由于技術(shù)有限噪沙,難免會(huì)存在紕漏炼彪,歡迎指正。轉(zhuǎn)載請注明出處正歼,萬分感謝辐马。
參考資料:
Objective-C 高級編程 iOS和OS X多線程和內(nèi)存管理
Effective Objective 2.0