作者是以前搞Android的辛润,用的是java語言膨处,對象的釋放都是由虛擬機完成,IOS用的是Object C對象需要開發(fā)者自己管理MRC(Mannul Reference Counting),自己創(chuàng)建的對象需要自己釋放真椿,之后因為開發(fā)者太容易遺忘釋放鹃答,導(dǎo)致出錯,所以出現(xiàn)了ARC(Automic Reference Counting)機制突硝,即系統(tǒng)自動管理機制测摔,其實就是在OC編譯的時候插入一些內(nèi)存管理的代碼,比如說retain release之類的
提到MRC和ARC必然要提到引用計數(shù)解恰,即retainCount锋八,如果一個對象的有引用計數(shù)是0,那么該對象就會被系統(tǒng)回收护盈,釋放內(nèi)存空間
廢話不多說直接上代碼
#ViewController.m ARC環(huán)境下編譯
#define RC(obj) CFGetRetainCount((__bridge CFTypeRef)(obj))
__weak NSDictionary *testWeak1 = nil;
__weak NSDictionary *testWeak2 = nil;
__weak NSDictionary *testWeak3 = nil;
__weak NSDictionary *testWeak4 = nil;
__weak NSDictionary *testWeak5 = nil;
@implementation ViewController
- (void)handleGesture
{
[self testReference];
[self testWeak];
}
- (void)testReference{
TestARC *arc = [[TestARC alloc]init];
TestMRC *mrc1 = [[TestMRC alloc]init];
NSDictionary *adExtraDic1 = [mrc1 copyTest];
NSDictionary *adExtraDic2 = [mrc1 testReturnDic];
NSDictionary *adExtraDic3 = [arc testReturnDic];
NSDictionary *adExtraDic4 = [arc copyTestARCDic];
//NSDictionary *adExtraDic5 = [[NSDictionary alloc]init];
NSDictionary *adExtraDic5 = @{@"pull_time": @(1),
@"pull_time_1": @(2)
};
//RC是在ARC模式下挟纱,對象的引用計數(shù)方法
NSLog(@"viewDidLoad 111 adExtraDic1 111 count = %ld", RC(adExtraDic1));
NSLog(@"viewDidLoad 111 adExtraDic2 111 count = %ld", RC(adExtraDic2));
NSLog(@"viewDidLoad 111 adExtraDic3 000 count = %ld", RC(adExtraDic3));
NSLog(@"viewDidLoad 111 adExtraDic4 000 count = %ld", RC(adExtraDic4));
NSLog(@"viewDidLoad 111 adExtraDic5 000 count = %ld", RC(adExtraDic5));
testWeak1 = adExtraDic1;
testWeak2 = adExtraDic2;
testWeak3 = adExtraDic3;
testWeak4 = adExtraDic4;
testWeak5 = adExtraDic5;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"testReference delay testWeak1 = %p,testWeak2=%p,testWeak3=%p,testWeak4=%p,testWeak5=%p", testWeak1,testWeak2,testWeak3,testWeak4,testWeak5);
135. });
136. }
- (void)testWeak{
NSLog(@"testWeak testWeak1 = %p,testWeak2=%p,testWeak3=%p,testWeak4=%p,testWeak5=%p", testWeak1,testWeak2,testWeak3,testWeak4,testWeak5);
}
#TestMRC.m MRC環(huán)境下編譯
-(NSDictionary *)copyTest
{
NSMutableDictionary *adExtraDic = [[NSMutableDictionary alloc] init];
[adExtraDic setValue:@("abcd") forKey:@"c2s_switch"];
return adExtraDic;
}
-(NSDictionary *) testReturnDic
{
NSMutableDictionary *adExtraDic = [[NSMutableDictionary alloc] init];
[adExtraDic setValue:@("abcd") forKey:@"c2s_switch"];
return [adExtraDic autorelease];
}
- (void)dealloc
{
NSLog(@"TestMRC dealloc obj=%p",self);
}
#TestARC.m ARC環(huán)境下編譯
49.-(NSDictionary *) testReturnDic
50.{
51. NSMutableDictionary *adExtraDic = [[NSMutableDictionary alloc] init];
52. return adExtraDic ;
53.}
-(NSDictionary *) copyTestARCDic
{
NSMutableDictionary *adExtraDic = [[NSMutableDictionary alloc] init];
// NSLog(@"TestARC copyTestARCDic adExtraDic=%p",adExtraDic);
return adExtraDic ;
}
- (void)dealloc
{
NSLog(@"TestARC dealloc");
}
打印結(jié)果
2019-09-23 16:31:33.574273+0800 TestMac[46711:3554859] viewDidLoad 111 adExtraDic1 111 count = 1
2019-09-23 16:31:33.574285+0800 TestMac[46711:3554859] viewDidLoad 111 adExtraDic2 111 count = 2
2019-09-23 16:31:33.574293+0800 TestMac[46711:3554859] viewDidLoad 111 adExtraDic3 000 count = 1
2019-09-23 16:31:33.574302+0800 TestMac[46711:3554859] viewDidLoad 111 adExtraDic4 000 count = 1
2019-09-23 16:31:33.574319+0800 TestMac[46711:3554859] viewDidLoad 111 adExtraDic5 000 count = 2
2019-09-23 16:31:33.574382+0800 TestMac[46711:3554859] viewDidLoad 111 adExtraDic7 = __NSDictionaryM adExtraDic6=__NSFrozenDictionaryM
2019-09-23 16:31:33.574414+0800 TestMac[46711:3554859] handleGesture testWeak1 = 0x6000002e3620,testWeak2=0x6000002e35e0,testWeak3=0x6000002e3640,testWeak4=0x6000002e3660,testWeak5=0x6000017b4440
2019-09-23 16:31:33.581273+0800 TestMac[46711:3554859] handleGesture testWeakObj0 = 0x60000000e980
2019-09-23 16:31:33.581358+0800 TestMac[46711:3554859] TestNSObject dealloc obj=0x60000000e980
2019-09-23 16:31:33.581381+0800 TestMac[46711:3554859] TestMRC dealloc
2019-09-23 16:31:33.581393+0800 TestMac[46711:3554859] TestARC dealloc
2019-09-23 16:31:33.581418+0800 TestMac[46711:3554859] TestNSObject dealloc obj=0x60000000e8b0
2019-09-23 16:31:33.581438+0800 TestMac[46711:3554859] testWeak testWeak1 = 0x0,testWeak2=0x6000002e35e0,testWeak3=0x0,testWeak4=0x0,testWeak5=0x6000017b4440
2019-09-23 16:31:35.581492+0800 TestMac[46711:3554859] testReference delay testWeak1 = 0x0,testWeak2=0x0,testWeak3=0x0,testWeak4=0x0,testWeak5=0x0
下面來一個個分析下
PS:匯編代碼和源碼已上傳,可以對照匯編和源碼的行號來理解匯編語言
先看第1行和第2行日志 都是mrc為什么一個是1黄琼,一個是2
先來看第2行日志樊销,直接上匯編語言吧
TesrMRC中testReturnDic的匯編如下
testReturnDic會調(diào)用
[[NSMutableDictionary alloc] init];這個會讓對象的retainCount+1,此時對象的引用計數(shù)是1
再來看看ViewControll中調(diào)用[mrc1 testReturnDic]的匯編語言
在文章的開頭說過脏款,ARC會在編譯的時候插入一些內(nèi)存管理的代碼围苫,這里就可以看到
callq _objc_retainAutoreleasedReturnValue
這句話是什么意思呢,其實就是嘗試retain一個return的value撤师,為什么是嘗試剂府,這里不說,稍后再說
看看_objc_retainAutoreleasedReturnValue的源碼
1.id objc_retainAutoreleasedReturnValue(id obj)
2. {
3. if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
4 . return objc_retain(obj);
5.}
先忽略第3行代碼剃盾,直接跳到第4行代碼腺占,這里做了一次retain,引用計數(shù)加+1痒谴,此時對象的引用計數(shù)是2衰伯,所以說第2行打印的日志是2
在回過頭來看看第一行日志
TesrMRC中copyTest的匯編如下
跟上面的testReturnDic的匯編差不多,這里不多說积蔚,主要看看
ViewControll中調(diào)用[mrc1 copyTest]的匯編語言
看上去很簡單意鲸,并沒有調(diào)用_objc_retainAutoreleasedReturnValue的代碼
為什么會這樣同樣是MRC,幾乎同樣的函數(shù)實現(xiàn)尽爆,就是函數(shù)名不一樣
這是因為根據(jù)蘋果的命名規(guī)定怎顾,調(diào)用以alloc/new/copy/mutableCopy等開頭的方法,表示調(diào)用者自己生成并持有對象漱贱,所以不需要retian槐雾,即在編譯器識別這些方法時,不會自動加上_objc_retainAutoreleasedReturnValue幅狮,所以對應(yīng)的引用計數(shù)是1
再來看看第三行日志募强,即[arc testReturnDic]對應(yīng)的引用計數(shù)
是1株灸,為什么呢?再一次看看arc testReturnDic對應(yīng)的匯編語言
可以看出這個明顯要比[mrc1 testReturnDic]的匯編語言要復(fù)雜一些钻注,這也就說明了ARC機制會在編譯的時候自動插入了一些代碼來自動管理內(nèi)存蚂且,下面來看看主要插入了哪些代碼
312-316行是函數(shù)調(diào)用,在ios中函數(shù)調(diào)用實際就是消息機制幅恋,所以都是_objc_msgSend開頭的
杏死,這里面兩個_objc_msgSend對應(yīng)的就是alloc和init函數(shù)
繼續(xù)往下看321有個retain調(diào)用,這就是ARC自動插入來的代碼增加引用計數(shù)捆交,這里對應(yīng)的代碼
是return adExtraDic ;其實可以理解為
NSMutableDictionary *adExtraDic1 = adExtraDic;
return adExtraDic1;
這樣會更容易理解插入retain的代碼
繼續(xù)看匯編語言的第53行調(diào)用了一個
_objc_storeStrong這行匯編對應(yīng)的是函數(shù)結(jié)束的位置代碼53行淑翼,可以認為是棧幀出棧,需要釋放局部變量品追,objc_storeStrong的實現(xiàn)如下
此時經(jīng)調(diào)試runtime發(fā)現(xiàn)obj為nil玄括,prev就是adExtraDic,第10行肉瓦,調(diào)用了release(prev),在這里objc_storeStrong就是釋放adExtraDic的遭京,即局部變量,此時因為NSMutableDictionary的對象先init了一次泞莉,引用計數(shù)是1
哪雕,然后又retain了一次,引用計數(shù)+1鲫趁,變?yōu)?斯嚎,最后函數(shù)結(jié)束的時候又release了一次,引用計數(shù)-1挨厚,變?yōu)?了堡僻,
最后接著看匯編代碼333行,調(diào)用了
_objc_autoreleaseReturnValue疫剃,先看這個方法的實現(xiàn)
看第三行如果prepareOptimizedReturn為true祭陷,直接返回該對象稽物,否則加入自動釋放池里面鸳君,待下一個runloop到來時釋放睡汹,那么這個prepareOptimizedReturn是什么意思呢
看下prepareOptimizedReturn的實現(xiàn)
上面注釋寫的很清楚,嘗試優(yōu)化蹄溉,否則返回的值必須retain,autorelease您炉,嘗試這個詞是不是很熟悉柒爵,上面提到過,其實這個優(yōu)化就是ARC在運行時的優(yōu)化赚爵,就是調(diào)用_objc_autoreleaseReturnValue是會檢查棉胀,接下來是否會調(diào)用_objc_retainAutoreleasedReturnValue法瑟,如果會,那么就直接返回對象唁奢,直接跳過autorelease和retain霎挟,如果不會,則會autorelease
網(wǎng)上的一個例子時候說的很清楚
當方法全部基于 ARC 實現(xiàn)時麻掸,在方法 return 的時候酥夭,ARC 會調(diào)用 objc_autoreleaseReturnValue() 以替代 MRC 下的 autorelease。在 MRC 下需要 retain 的位置脊奋,ARC 會調(diào)用 objc_retainAutoreleasedReturnValue()熬北。因此下面的 ARC 代碼:
+ (instancetype)createSark {
return [self new];
}
// caller
Sark *sark = [Sark createSark];
實際上會被改寫成類似這樣:
+ (instancetype)createSark {
id tmp = [self new];
return objc_autoreleaseReturnValue(tmp); // 代替我們調(diào)用autorelease
}
// caller
id tmp = objc_retainAutoreleasedReturnValue([Sark createSark]) // 代替我們調(diào)用retain
Sark *sark = tmp;
objc_storeStrong(&sark, nil); // 相當于代替我們調(diào)用了release
有了這個基礎(chǔ),ARC 可以使用一些優(yōu)化技術(shù)诚隙。在調(diào)用 objc_autoreleaseReturnValue() 時讶隐,會在棧上查詢 return address 以確定 return value 是否會被直接傳給 objc_retainAutoreleasedReturnValue()。 如果沒傳久又,說明返回值不能直接從提供方發(fā)送給接收方巫延,這時就會調(diào)用 autorelease。反之地消,如果返回值能順利的從提供方傳送給接收方炉峰,那么就會直接跳過 autorelease 過程,并且修改 return address 以跳過 objc_retainAutoreleasedReturnValue()過程犯建,這樣就跳過了整個 autorelease 和 retain的過程讲冠。
核心思想:當返回值被返回之后,緊接著就需要被 retain 的時候适瓦,沒有必要進行 autorelease + retain竿开,直接什么都不要做就好了。
另外玻熙,當函數(shù)的調(diào)用方是非 ARC 環(huán)境時否彩,ARC 還會進行更多的判斷,在這里不再詳述嗦随,詳見 《黑幕背后的 Autorelease》
對應(yīng)的objc_retainAutoreleasedReturnValue方法也是一樣
1.id objc_retainAutoreleasedReturnValue(id obj)
2. {
3. if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
4 . return objc_retain(obj);
5.}
這里第3行代碼也會判斷是否優(yōu)化列荔,如果優(yōu)化就直接返回對象,沒有必要在retain了枚尼,多余
跟objc_autoreleaseReturnValue對應(yīng)贴浙,objc_autoreleaseReturnValue判斷如果可以優(yōu)化,則把標志位存到Threadlocal里面署恍,objc_retainAutoreleasedReturnValue則調(diào)用acceptOptimizedReturn從ThreadLocal里面取出來
所以到[arc testReturnDic]方法結(jié)束崎溃,NSMutableDictionary對象的引用計數(shù)依然是1
,在繼續(xù)看
再來看看ViewControll中調(diào)用[arc testReturnDic]的匯編語言
可以看出第483行確實調(diào)用了_objc_retainAutoreleasedReturnValue盯质,上面說過了袁串,如果是優(yōu)化就直接返回返回概而,不retain,所以引用計數(shù)依然是1囱修,至此[arc testReturnDic]的分析結(jié)束
再來看看[arc copyTestARCDic]的分析赎瑰,同樣,先看匯編代碼
跟[arc testReturnDic]很像破镰,只是這里缺少_objc_autoreleaseReturnValue的調(diào)用餐曼,那么這里就有疑問了,那缺少_objc_autoreleaseReturnValue說明啤咽,這里不存在優(yōu)化晋辆,那最終在ViewController里面_objc_retainAutoreleasedReturnValue,引用計數(shù)豈不是變?yōu)?了宇整,其實不然瓶佳,如果引用計數(shù)是2的話,函數(shù)結(jié)束只會釋放一次局部變量鳞青,那依然存在1個引用計數(shù)啊霸饲,豈不是內(nèi)存泄漏把,醒醒吧臂拓,IOS怎么可能出現(xiàn)這種低級錯誤厚脉,那么怎么解釋這個問題,那就繼續(xù)看匯編了
ViewControll中調(diào)用[arc copyTestARCDic]的匯編語言
答案揭曉了胶惰,這里面并沒有調(diào)用_objc_retainAutoreleasedReturnValue傻工,為什么呢?其實上面分析MRC的時候提過
這是因為根據(jù)蘋果的命名規(guī)定孵滞,調(diào)用以alloc/new/copy/mutableCopy等開頭的方法中捆,表示調(diào)用者自己生成并持有對象,所以不需要retian坊饶,即在編譯器識別這些方法時泄伪,不會自動加上
到此前4行日志分析完畢,接下來分析第5行日志
NSDictionary *adExtraDic5 = @{@"pull_time": @(1),
@"pull_time_1": @(2)
};
為什么它的引用計數(shù)是2
這個字典的賦值語句匿级,實際上在編譯的時候調(diào)用
[NSDictionary dictionaryWithObjects:forKeys:count:]蟋滴,這個通過debug或者匯編語言都可以知曉,然后接著會[__NSSingleEntryDictionaryI __new:::]一下痘绎,所以此時引用計數(shù)是1津函,
這個為什么不用匯編語言來分析呢?因為NSDictionary是系統(tǒng)類孤页,無法看到匯編的代碼
這是賦值語句的調(diào)用棧
下面再看看ViewControll中調(diào)用 NSDictionary *adExtraDic5 = @{@"pull_time": @(1),@"pull_time_1": @(2) }發(fā)生了什么
首先通過debug發(fā)現(xiàn)它會調(diào)用autorelease
調(diào)用棧如下
然后會調(diào)用objc_retainAutoreleasedReturnValue
調(diào)用棧如下
這樣因為前面沒有調(diào)用_objc_autoreleaseReturnValue尔苦,也就是說沒有優(yōu)化,所以這里的的objc_retainAutoreleasedReturnValue會retain對象,導(dǎo)致引用計數(shù)+1蕉堰,所以此時引用計數(shù)為2
附上ViewControll中該段代碼的匯編
里面也有_objc_autoreleaseReturnValue的調(diào)用,但是沒有autorelease的調(diào)用悲龟,不明白什么原因
至此第5行的日志也分析完畢屋讶,
通過上面的分析基本對ARC和MRC有個大致的了解,所以接下來的日志分析也比較好理解了须教,
先看這個日志
這些個weak引用的是前面分析的那些對象皿渗,當這些對象釋放內(nèi)存時,weak就為nil轻腺,這就可以判斷對象什么時候釋放了乐疆,這行日志的打印的時候,很明顯這些都沒有被釋放贬养,所以都不為nil
再看看這個日志
可以看到除了testWeak2和testWeak5之外挤土,其他都為nil,這說明除了adExtraDic2和adExtraDic5其他對象都被釋放了
分析之前先貼一張testReference函數(shù)結(jié)束時的匯編語言
可以看出里面很多_objc_storeStrong误算,這就是對象釋放局部變量的操作
下面來一個個分析仰美,為什么被釋放,為什么沒被釋放
testWeak1對象的是adExtraDic1儿礼,引用計數(shù)是1咖杂,當testReference結(jié)束時,函數(shù)出棧蚊夫,釋放一次局部變量诉字,導(dǎo)致引用計數(shù)是0,釋放內(nèi)存知纷,
思考個問題壤圃,如果TestMRC copyTest在返回是添加了autorelease即 [adExtraDic autorelease]
會出現(xiàn)什么情況.....,30秒過去了...
這是因為adExtraDic1的引用計數(shù)是1屈扎,它在testReference結(jié)束的時候埃唯,因為釋放局部變量,導(dǎo)致鹰晨,引用計數(shù)變?yōu)?了墨叛,內(nèi)存已經(jīng)釋放,此時因為有autorelease模蜡,它會等到下一次loop的到來時漠趁,嘗試再一次釋放該對象,但是對象在已經(jīng)釋放完畢忍疾,所以會崩潰
再來看看testWeak2對應(yīng)的adExtraDic2為啥在這個testweak方法中沒被釋放
跟adExtraDic1闯传,當testReference結(jié)束時,函數(shù)出棧卤妒,釋放一次局部變量甥绿,導(dǎo)致引用計數(shù)-1字币,但是因為adExtraDic2本身的引用計數(shù)為2,即使-1共缕,剩下1洗出,所以不會釋放
testWeak3 testWeak4 testWeak5的情況都是類似,就不一一分析
這段日志實際上就是模擬下一次runloop的到來图谷,會發(fā)生什么情況翩活,這里的代碼dispatch_after就是模擬下一個runloop
可以看到testWeak2和testWeak5都為nil了,其實就是testWeak2和testWeak5的autorelease的作用便贵,前面也說過autorelease會延遲對象的釋放菠镇,等下次runloop時才會釋放,所以這里又釋放了一次承璃,
至此所有的日志分析結(jié)束
再來看看特殊的case吧
NSDictionary *adExtraDic5 = [[NSDictionary alloc]init];
這個adExtraDic5對象的引用計數(shù)是-1或者無窮大利耍,不管怎么樣都不會被釋放,猜測是因為NSDictionary是不變的字典盔粹,這樣的創(chuàng)建實際上沒有任何意義
NSString *ff = @"dsdsddwdasdsdaddasssa";
這個adExtraDic5對象的引用計數(shù)是-1或者無窮大,這個字符串位于常量區(qū)堂竟,不是堆區(qū),沒有引用計數(shù)玻佩,不存在釋放
NSString *str = [NSString stringWithFormat:@"123456789"];
NSString *longStr = [NSString stringWithFormat:@"1234567890"];
NSLog(@"str %s %p", object_getClassName(str), str);
NSLog(@"longStr %s %p", object_getClassName(longStr), longStr);
str沒有引用計數(shù)出嘹,longStr的引用計數(shù)是1
為什么呢
在網(wǎng)上搜索了一下,一般人給出的答案是:當字符串長度小于10時咬崔,字符串是保存在常量區(qū)税稼,沒有引用計數(shù)。如果長度大于等于10呢垮斯,就會被復(fù)制到堆去郎仆,有引用計數(shù)。
參考鏈接
Tagged Pointer 具體了解一下
最后在擴展一些小知識
關(guān)于ARC和MRC屬性賦值的問題兜蠕,
大家知道ios中屬性的賦值扰肌,實際上是調(diào)用set方法
比如說有個TestARC.h文件
@interface TestARC : NSObject
@property (nonatomic,retain)NSObject * obj;
調(diào)用testArc.obj =[[ NSObject alloc]init]
實際上是調(diào)用[testArc setObj]方法
objc_storeStrong的實現(xiàn)
下面再看看如果是atomic屬性呢》它調(diào)用的是objc_setProperty_atomic->reallySetProperty,看看reallySetProperty的實現(xiàn)
第89行判斷如果是非原子性,直接賦值鳞绕,不加鎖失仁,否則
枷鎖,這樣就解決了的線程安全問題们何,所以上面的例子如果是atomic就沒問題
再來看看
總結(jié):沒啥總結(jié)的解幽,多看匯編贴见,多看源碼
參考鏈接
《黑幕背后的 Autorelease》
NSString 引用計數(shù)
Tagged Pointer 具體了解一下