《iOS底層原理文章匯總》
上一篇文章iOS-底層原理32-啟動(dòng)優(yōu)化二進(jìn)制重排介紹了啟動(dòng)優(yōu)化二進(jìn)制重排,本文介紹內(nèi)存管理
1.內(nèi)存布局
通過匯編去找指針,匯編通過sp的寄存器去定位,棧中內(nèi)存通過匯編寄存器去定位的漱竖,每一個(gè)內(nèi)存都包含地址的內(nèi)存空間,能直接定位到馍惹。0-0x00400000作為預(yù)留給系統(tǒng)處理万矾,null以及底層發(fā)送的指令
2.內(nèi)存管理
通過nonpointer_isa(非指針型isa)來優(yōu)化整個(gè)64位地址,為什么self.nameStr為cooci的時(shí)候程序不會(huì)發(fā)生多條線程對(duì)同一個(gè)對(duì)象retain release操作薪丁?而self.nameStr為cooci_和諧學(xué)習(xí)不急不躁時(shí)程序在多線程會(huì)發(fā)生retain release同時(shí)對(duì)一個(gè)對(duì)象操作而崩潰严嗜?
查看self.nameStr=@"cooci"時(shí)茄蚯,self.nameStr的isa指向NSTaggedPointerString
查看self.nameStr=@"cooci_和諧學(xué)習(xí)不急不躁"時(shí)第队,self.nameStr的isa指向NSCFString
self.nameStr在進(jìn)行賦值操作執(zhí)行setter或getter方法時(shí)凳谦,會(huì)對(duì)新值newValue進(jìn)行retain尸执,對(duì)舊值oldValue進(jìn)行release,查看源碼,obj的isa為 isTaggedPointer時(shí)如失,setter和getter方法執(zhí)行時(shí)不會(huì)進(jìn)行新值的retain和舊值的release褪贵,故不會(huì)發(fā)生過度釋放而崩潰脆丁。
taggedPointer類型在修飾小對(duì)象時(shí)有了比較特殊的處理槽卫,指針是什么歼培?String有值+指針地址躲庄,通過指針去操作引用計(jì)數(shù)等等其他處理taggerPointer類型修飾的String和對(duì)象String是否是相同的處理呢读跷?
3.taggedPointer
什么是小對(duì)象效览?NSNumber,NSDate丐枉,小于11位的小String,一個(gè)對(duì)象8字節(jié)瘦锹,64位弯院,NSNumber里面存入1听绳,用不了64位椅挣,會(huì)浪費(fèi)空間,為了節(jié)省空間,可以用小對(duì)象去接收
I.在方法查找流程中峡竣,第一次接觸到taggedpointer指針是在readImages->initializeTaggedPointerObfuscator()
通過對(duì)指針地址編碼進(jìn)行異或運(yùn)算混淆,兩次異或就能解碼計(jì)算出原始沒有混淆前的地址
要向獲取原始的真正屬于taggedpointerstring的指針地址呢攻谁,對(duì)現(xiàn)在的地址進(jìn)行一次異或運(yùn)算,得到小對(duì)象地址0xa000000000000621,倒數(shù)兩位62表示98,表示b,@1是NSNumber類型锈嫩,倒數(shù)第二位為1呼寸,說明地址里面存儲(chǔ)了值对雪,不僅是簡單的地址還包含了值瑟捣,double和float類型底層經(jīng)過特殊處理
II.小對(duì)象地址里面的0xa和0xb是哪里來的捐祠?
在判斷是否是小對(duì)象的方法中指針地址與_OBJC_TAG_MASK進(jìn)行與運(yùn)算,相當(dāng)于保留最高位,_OBJC_TAG_MASK的值為#define _OBJC_TAG_MASK (1UL<<63)
最高位是否為1代表是否是taggedpointer踱蛀,2代表NSString率拒,3代表NSNumber猬膨,小對(duì)象放在常量區(qū)寥掐,不需要ARC自動(dòng)管理
TP表示是taggedpointer類型
4.retain
I.判斷是否為nonpointer
II.操作引用計(jì)數(shù)
a.如果不是nonpointer -> 散列表
spinlock_t slock; 開解鎖
RefcountMap refcnts; 引用計(jì)數(shù)表
weak_table_t weak_table; 弱引用表
散列表 在內(nèi)存里面有多張 + 最多能夠多少張 一張 對(duì)象開鎖解鎖會(huì)不安全且效率低
b.是否正在釋放和析構(gòu)
c.extra_rc+1 滿了-散列表
d.carry 滿了標(biāo)記褐隆,extra_rc 滿/2 -> extra_rc 滿/2 -> 散列表(開鎖關(guān)鎖)
5.散列表
為什么有isa衫贬?retain會(huì)操作引用計(jì)數(shù)+1,retainCount在isa的bits中固惯,會(huì)在操作引用計(jì)數(shù)時(shí)用到
A.objc_retain->obj->retain()->rootRetain(),判斷是否為nonpointerisa,若不是nonpointerisa葬毫,無法進(jìn)行存儲(chǔ)贴捡。
B.操作引用計(jì)數(shù):若不是nonpointerisa烂斋,直接操作散列表SideTables不止一張,和關(guān)聯(lián)對(duì)象表原理一樣
為什么散列表在內(nèi)存中有多張,若放在一張表中操作引用計(jì)數(shù)手销,會(huì)暴露所有對(duì)象不安全锋拖,鎖解鎖消耗性能兽埃,真機(jī)下面是8張表适袜,散列表是哈希表苦酱,哈希表結(jié)合鏈表和數(shù)組疫萤,增刪改查都方便扯饶,拉鏈法尾序,通過哈希函數(shù)定位相應(yīng)下標(biāo)每币。不用鏈表和數(shù)組兰怠,鏈表不便于查,數(shù)組不便于增刪。
6.dealloc
對(duì)象要釋放,需要做哪些事情塔次?isa中有個(gè)cxx析構(gòu)代碼励负,關(guān)聯(lián)對(duì)象表清空继榆,弱引用表清空略吨,散列表中引用計(jì)數(shù)表清空翠忠,最后free
7.release:retain的反向操作秽之,將extra_rc中的引用計(jì)數(shù)減為0后考榨,判斷是否有carry董虱,有繼續(xù)減散列表中的引用計(jì)數(shù),extra_rc和散列表中的引用計(jì)數(shù)都減為0后愤诱,開始析構(gòu)dealloc淫半,自動(dòng)觸發(fā)釋放函數(shù)
alloc -> retain -> release -> dealloc構(gòu)成一個(gè)閉環(huán)
8.retainCount
面試題:[NSObject alloc]引用計(jì)數(shù),輸出為1
alloc創(chuàng)建對(duì)象引用計(jì)數(shù)為0,默認(rèn)+1,為1
9.強(qiáng)持有和循環(huán)引用的區(qū)別
self.timer添加到runloop中姻几,控制器pop沒有進(jìn)入dealloc方法進(jìn)行釋放蛇捌?
I.解決辦法:在- (void)didMoveToParentViewController:(UIViewController *)parent
方法中對(duì)timer進(jìn)行銷毀络拌,之后進(jìn)入dealloc方法對(duì)self進(jìn)行銷毀
II.強(qiáng)持有和循環(huán)引用的區(qū)別
以上怎么造成循環(huán)引用混萝,self無法釋放譬圣,無法進(jìn)入dealloc方法中的雄坪?
self -> timer -> self,self很明顯持有timer维哈,timer是怎么持有self的呢阔挠?查看官方文檔购撼,The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated. timer對(duì)self進(jìn)行了強(qiáng)持有
若用
__weak typeof(self) weakSelf = self
weakSelf代替self能解決循環(huán)引用嘛碾盐,結(jié)果是不能毫玖。[NSRunLoop currentRunLoop] 強(qiáng)持有 -> timer -> weakSelf -> self凌盯,runloop對(duì)timer進(jìn)行了強(qiáng)持有阐滩,runloop的生命周期長县忌,runloop對(duì)timer沒有進(jìn)行釋放,weakSelf和self都釋放不掉,和self->block->weakSelf-self的模型完全不一樣weakSelf : 沒有對(duì)內(nèi)存加1
兩個(gè)內(nèi)存地址self和weakSelf指向了同一片內(nèi)存空間,timer根據(jù)官方文檔捕獲的是內(nèi)存地址<LGTimerViewController: 0x7f91a9006110>,Block捕獲的是weakSelf绩郎, 和self沒有關(guān)系翁逞,self -> block -> weakSelf (臨時(shí)變量的指針地址) 地址->內(nèi)存
block捕獲的是地址指針肋杖,timer捕獲的是對(duì)象本身,timer中無法通過weakSelf來打破挖函, NSRunLoop -> timer -> weakSelf (<LGTimerViewController: 0x7fb8c9d11240>),RunLoop包含<LGTimerViewController: 0x7fb8c9d11240>状植,RunLoop不停,LGTimerViewController無法釋放
這就是強(qiáng)持有和循環(huán)引用的區(qū)別
III.解決強(qiáng)持有的方法
解決思路:我們需要打破這一層強(qiáng)持有 - self
A.通過在- (void)didMoveToParentViewController:(UIViewController *)parent
方法中對(duì)timer進(jìn)行銷毀,之后進(jìn)入dealloc方法對(duì)self進(jìn)行銷毀
B.中介者模式:引入中介者LGTimerWapper,添加timer,給VC中的selector發(fā)送消息怨喘,當(dāng)vc pop時(shí)釋放津畸,判斷vc是否為nil,是將timer釋放必怜,從而打破runloop強(qiáng)持有LGTimerWapper肉拓,皆釋放,若timer中的方法過多,要不斷添加到LGTimerWapper中
C.Proxy:虛基類的方式,強(qiáng)持有引用轉(zhuǎn)移到消息轉(zhuǎn)發(fā)梳庆,虛基類無法做事進(jìn)行轉(zhuǎn)發(fā)暖途,不斷的轉(zhuǎn)發(fā)到vc,信息的傳遞,在vc的dealloc方法中對(duì)vc進(jìn)行釋放膏执,proxy進(jìn)而釋放驻售,timer釋放,runloop也釋放了proxy胧后,皆釋放