內(nèi)存布局
- stack區(qū):方法調(diào)用
- heap區(qū) (堆區(qū)):alloc分配的一些對象
- bss :未初始化的全局變量
- data:已初始化的全局變量
- text:程序代碼
內(nèi)存管理方案
- TageedPointer:小對象绞幌,NSNumber等對象
- NONPOINTER_ISA:64位 isa占64位啊奄,一般用30~40就夠用了菇夸,所以在剩余的位中做了內(nèi)存管理
- 散列表 是復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
① 引用計數(shù)表
② 弱引用表
NONPOINTER_ISA
arm64結(jié)構(gòu)下是64位
- 第一位indexed
① 0代表isa只是一個純的isa指針鞠眉,里面內(nèi)容指向當(dāng)前的類對象地址械蹋,
② 1代表不單單是isa指針哗戈,還有一些關(guān)于內(nèi)存管理的一些數(shù)據(jù) - 第二位has_assoc
① 0代表 沒有關(guān)聯(lián)對象
② 1代表 有關(guān)聯(lián)對象 - 第三位has_cxx_dtor 當(dāng)前對象是否用到c++
① 0 沒有
② 1 有 - shiftcls 4 ~ 36 33位 比特位是類對象的地址
① 當(dāng)前類對象的內(nèi)存地址 - magic
- weakly_referenced:是否有弱引用指針
- deallocating:是否有dealloc的操作
- has_sidetable_rc
如果當(dāng)前的引用計數(shù)已經(jīng)達(dá)到上限唯咬,需要外掛一個has_sidetable_rc一個數(shù)據(jù)結(jié)構(gòu)去存儲相關(guān)的引用計數(shù)內(nèi)容 - extra_rc 44~63 都是 extra_rc 引用計數(shù)
引用計數(shù)在很小的范圍內(nèi)胆胰,會直接存儲到isa這里蜀涨,而不需要單獨的引用計數(shù)表來單獨存儲
散列表方式
sideTables()結(jié)構(gòu)
- SideTable (數(shù)據(jù)結(jié)構(gòu))氧枣,由多個SideTable組成
- 64個(非嵌入式系統(tǒng))
SIDETABLE
為什么不是一個SIDETABLE挑胸? 為什么要組成SIDETABLES呢?
- 對象可能需要在不同線程操作創(chuàng)建移袍,這個時候進行操作的時候需要進行加鎖處理才能保證安全葡盗,這樣存在效率問題
- 用戶的內(nèi)存空間需要4gb觅够,我們可以分配成千上萬的引用對象喘先,如果每個引用對象我們都要對齊進行引用計數(shù)改變窘拯,都操作這張表就會出現(xiàn)效率問題涤姊。如果一個對象操作這張表思喊,會加鎖下一個對象需要在等上一個對象的鎖加完以后在處理恨课,這就造成了效率低下的問題
- 系統(tǒng)為了解決這個效率低下庄呈,系統(tǒng)給我們提供了一個分離鎖的概念
- 系統(tǒng)為我們分配了8個這個表(64位),這樣就可以并發(fā)的去修改引用解決問題。
怎么樣實現(xiàn)快速分流盒刚?
SideTables的本質(zhì)是一張Hash表因块。
可能有64個SideTable存儲不同的引用計數(shù)表涡上、和弱引用表
什么是hash算法
對象指針可以作為Key通過hash函數(shù)計算計算出hash表中你的value值的下標(biāo)也就是索引吩愧。
hash查找的過程
給定值是對象內(nèi)存地址雁佳,目標(biāo)是數(shù)組下標(biāo)索引。
給了一個對象的內(nèi)存地址炸站,通過哈希函數(shù)的運算武契,得到了一個集合的下標(biāo)索引值
對象的內(nèi)存地址指針 % 數(shù)組的count就可以得到這個數(shù)組中的索引
這樣就查找速度快了
散列表方案設(shè)計的數(shù)據(jù)結(jié)構(gòu)
- Spinlock_t
① 自旋鎖 是”忙等”的鎖届垫,如果當(dāng)前鎖已經(jīng)被其他線程獲取装处,那么當(dāng)前線程會不斷的探測這個鎖是否被其他線程釋放妄迁,如果釋放自己第一時間去獲取這個鎖
② 特點:適合輕量訪問(這個中+1 -1 計算邏輯不復(fù)雜的情況下稱之為輕量) - 信號量
當(dāng)前線程獲取不到鎖的時候登淘,會把自己的線程阻塞休眠黔州,等這個鎖被釋放的時候,會喚醒它獲取這個鎖
引用計數(shù)表
實際上是一個refcountMap(hash表或者是字典)牲蜀,提高查找效率涣达,存儲查找都是hsah度苔,找到
- size_t引用技術(shù)使用64位存儲的
- 第一位是否有weak
- 第二位是否deallocating
弱引用表
weak_table_t也是一張hash表林螃,
- 通過對象指針,hash算法的到我們的weak_entry_t的表
- weak_entry_t里面存儲的就是弱引用指針伏钠。
MRC和ARC
MRC
手動引用計數(shù)熟掂,進行內(nèi)存管理
- alloc 分配一個對象的內(nèi)存空間
- retain 引用計數(shù)加1
- release 引用計數(shù)-1
- retainCount 當(dāng)前引用計數(shù)的值
- autorelease 會在autoreleasepool結(jié)束的時候赴肚,向其每一個對象發(fā)送release消息
- dealloc 在MRC中需要顯示的調(diào)用super dealloc需要廢棄父類的相關(guān)成員變量
ARC
自動引用計數(shù)
- ARC是由LLVM和Runtime協(xié)作完成
- ARC中禁止手動調(diào)用retain、release刊愚、retainCount鸥诽、dealloc牡借、
可以重寫dealloc但是不能調(diào)用super dealloc - ARC中新增weak炬藤、strong屬性關(guān)鍵字
ARC和MRC的區(qū)別
- MRC是手動管理,ARC是LLVM和Runtime協(xié)作進行自動引用計數(shù)管理
- MRC可以調(diào)用一些引用計數(shù)相關(guān)的+-1操作方法畅买,而ARC是不能的
- ARC有weak和strong
引用計數(shù)管理
實現(xiàn)原理分析
alloc實現(xiàn)
- 經(jīng)過一些列調(diào)用谷羞,最終調(diào)用了C函數(shù)calloc(初始化完成會自己置0 而malloc是垃圾數(shù)據(jù))湃缎。
- 此時并沒有設(shè)置引用計數(shù)為1
retain實現(xiàn)
- SideTable &table = SideTables()[this] 先獲取對應(yīng)的SideTable
- size_t & refcntStorage = table.refcnts[this]; 獲取到SideTable中的引用計數(shù)值
- refcntstorage += SIDE_TABLE_RC_ONE; 對其+1
SIDE_TABLE_RC_ONE不是1嗓违,前兩個位置是存儲弱引用計數(shù)蹂季,和是否正在dealloc的偿洁,是后面62位涕滋,所以需要做一個偏移量宾肺。 所以說是4.
release實現(xiàn)
- SideTable &table = SideTables()[this] 先獲取對應(yīng)的SideTable
- RefcountMap:iterator it = table.refcnts.find(this); 獲取到SideTable中的引用計數(shù)值
- it->second -= SIDE_TABLE_RC_ONE; 和retain正好相反
retainCount的實現(xiàn)
- SideTable &table = SideTables()[this] 先獲取對應(yīng)的SideTable
- size_t refcnt_result = 1; 聲明一個局部變量
- RefcountMap::iterator it = table.refcnts.find(this) 獲取到SideTable中的引用計數(shù)值黔酥,剛?cè)lloc的對象其實里面是為0的
- refcnt_result += it-> second >> SIDE_TABLE_RC_SHIFT 把查找的結(jié)果做一個向右偏移的操作跪者,然后在+1
dealloc的實現(xiàn)
- 調(diào)用_objc_rootDealloc
- rootDealloc
- 是否直接釋放的判斷標(biāo)準(zhǔn)
① nonpointer_isa 是否是非指針型的isa指針
② weakly_referenced 是否有weak指針指向它
③ has_assoc 當(dāng)前對象是否有關(guān)連對象
④ has_cXX_dtor 是否使用了C++相關(guān)的代碼渣玲,是否ARC
⑤ has_sidetable_rc 是否是通過sidtable表來存儲的忘衍。 - 以上都不是 就可以釋放,如果可以就使用c函數(shù)的free函數(shù)釋放
-
如果不可以就使用object_dispose函數(shù)來進行后續(xù)的處理铅搓,然后結(jié)束
OBJECT_DISPOSE實現(xiàn)
- objc_destructInstance
- C函數(shù)的free
-
結(jié)束
OBJC_DESTRUCTINSTANCE的實現(xiàn)
- 是否有C++或者是否有ARC
YES object_cxxDestruct
NO 調(diào)用AssociatedObjects - 如果沒有C++或者ARC 就調(diào)用AssociatedObjects方法
YES _object_remove_assocations()
NO clearDealloccating -
_object_remove_assocations,以后還是沒有關(guān)聯(lián)對象那么都會調(diào)用clearDealloccating結(jié)束objc_destructInstance方法氢烘。
CLEARDEALLOCCATING的實現(xiàn)
- sidetable_clearDeallocating
- weak_clear_no_lock
將指向該對象的弱引用指針置為nil -
table.refcnts.erase
從引用計數(shù)表中擦除該對象的引用計數(shù)
弱引用管理
弱引用調(diào)用棧
- 一個被聲明為__weak弱引用的指針,經(jīng)過編譯器編譯以后蜀踏,會調(diào)用objc_initWeak方法脓斩,經(jīng)過一系列的函數(shù)調(diào)用棧,會調(diào)用weak_register_no_lock這個函數(shù)吗讶,進行弱引用變量的添加照皆。
- 具體添加的位置膜毁,是通過一個hash算法來進行進行位置查找的瘟滨,
- 如果說我們查找位置當(dāng)中已經(jīng)有了當(dāng)前對象所有的弱引用數(shù)組杂瘸,我們新的弱引用變量添加到引用數(shù)組當(dāng)中败玉,如果沒有弱引用數(shù)組,那么就創(chuàng)建一個新的弱引用數(shù)組返干,然后第0個位置添加wek指針矩欠,后面的都置為0晚顷。
當(dāng)清除weak變量 通知設(shè)置指向為nil该默, 它是如何置nil的
完整過程
- 通過被廢棄對象的指針經(jīng)過hash算法的到栓袖,求出弱引用數(shù)組對應(yīng)的數(shù)組索引位置裹刮,然后通過這個索引返回給對象我這個弱引用數(shù)組
- 如果弱引用數(shù)組為空就調(diào)用捧弃,當(dāng)前對象违霞,沒有弱引用對象返回就成
- 如果弱引用數(shù)組大于4調(diào)用entry->referrers如果小于4就使用entry->inline_referrers
- 遍歷這個數(shù)組买鸽,如果數(shù)組中的指針就是被廢棄的弱引用指針那么就將其置為nil眼五。
總結(jié)
- 當(dāng)一個對象唄dealloc之后看幼,dealloc內(nèi)部的實現(xiàn)會去調(diào)用弱引用清除這個函數(shù)
- 然后函數(shù)會通過桌吃,當(dāng)前對象指針查找弱引用表 茅诱,把一個對象的所有弱引用表都拿出來瑟俭,是一個數(shù)組
- 編譯所有這個數(shù)組當(dāng)中的弱引用指針分別置為nil
自動釋放池
編譯器改寫
- objc_autoreleasepoolPush()和objc_autoreleasePoolPop中間是代碼
- push返回值是一個無類型的指針
-
objc_autoreleasePoolPop需要這個無類型指針作為標(biāo)記也就是push返回的無類型的指針
objc_autoreleasepoolPush和objc_autoreleasePoolPop的函數(shù)調(diào)用棧
- 內(nèi)部會調(diào)用AutoreleasePoolPage::push方法
- AutoreleasePoolPage::pop方法
一次Pop當(dāng)然于一次批量的Pop操作(AutoreleasePool中的對象都會放入到自動釋放池中當(dāng)Pop之后失暴,AutoreleasePool中的對象的對象都會發(fā)送一次release消息所以是批量操作)
自動釋放池的數(shù)據(jù)結(jié)構(gòu)
- 以棧為節(jié)點通過雙向鏈表的形式組合而成
- 是和線程一一對應(yīng)的。
ATUORELEASEPOOLPAGE的數(shù)據(jù)結(jié)構(gòu)
- id*next:當(dāng)前棧的空的位置
- autoreleasePollPage * const parent; :鏈表的父指針
- autoreleasePollPage *child; :鏈表的孩子指針
- pthread_t const thread; : 與線程一一對應(yīng)的
AtuoreleasePoolPage::Push
autorelease
- 一個對象調(diào)用了autorelease方法矩肩,這個對象添加到next指針之后黍檩,next移動到新的位置刽酱,下一個調(diào)用autorelease方法的對象就會添加到這個位置
AtuoreleasePoolPage::Pop
- 根據(jù)傳入的哨兵對象找到對應(yīng)位置
- 給上次Push操作之后添加的對象依次發(fā)送release消息(next指針到哨兵對象之間的對象依次發(fā)送release消息)
-
回退指針搭配正確位置
雙向鏈表的概念
棧結(jié)構(gòu)概念
面試題自動釋放池的實現(xiàn)結(jié)構(gòu)是什么?
以棧為節(jié)點通過雙向鏈表的形式組合而成
自動釋放池的總結(jié)
下面代碼什么時候釋放
viewdidload{
NSMutableArray *array = [NSSmutableArray array];
}
在當(dāng)次runloop將要結(jié)束的時候調(diào)用AutoreleasePoolPage::Pop()
- 每一次runloop循環(huán)將要結(jié)束的時候都會對前一次創(chuàng)建的autoreleasePool進行Pop操作殿怜,同時會Push進來一個新的autoreleasePool
- viewdidload創(chuàng)建的對象,是在當(dāng)次runloop將要結(jié)束的時候麦轰,調(diào)用autoreleasePoolpage::Pop方法時候款侵,把對應(yīng)的array對象調(diào)用它的release方法新锈。
多次嵌套就是多次插入哨兵對象
關(guān)于autoreleasepool為和可以嵌套調(diào)用妹笆?
實際上多次嵌套插入哨兵對象,每次進行autoreleasePool的代碼塊創(chuàng)建的時候墩新,假如autoreleasePoolpage沒有滿的情況下海渊,系統(tǒng)就會在當(dāng)前autoreleasePoolpage棧中進行一次哨兵對象的插入臣疑,如果滿了會創(chuàng)建一個新的autoreleasePoolpage讯沈,然后當(dāng)前的autoreleasePoolpage中的child指針指向新創(chuàng)建的autoreleasePoolpage懒豹。
所以autoreleasepool可以多層嵌套調(diào)用
循環(huán)引用
三種循環(huán)引用:
- 自循環(huán)引用
- 相互循環(huán)引用
- 多循環(huán)引用(大環(huán))
自循環(huán)引用
相互循環(huán)引用
多循環(huán)引用(大環(huán))
如何破除循環(huán)引用?
- 避免產(chǎn)生循環(huán)引用
- 在合適的時機手動斷環(huán)
具體解決方案:
- __weak
- __block
- __unsafe_unretained
__WEAK 破解循環(huán)引用
__BLOCK破解* 破解循環(huán)引用
- MRC __block修飾對象不會增加其引用計數(shù)驮樊,避免了循環(huán)引用
- ARC __block修飾對象會被強引用囚衔,無法避免循環(huán)引用,需手動解環(huán)
__UNSAFE_UNRETAINED 破解循環(huán)引用
- 修飾對象不會增加引用計數(shù)肥哎,避免了循環(huán)引用
- 如果被修飾對象在某一時機被釋放,會產(chǎn)生懸垂指針杈女,懸垂指針又可能會出現(xiàn)不可預(yù)見的問題
循環(huán)引用示例
在開發(fā)過程中翰蠢,你遇到過哪些循環(huán)引用問題,你又是如何解決的趁尼?
- Block的使用示例
- NSTimer的循環(huán)引用問題
有一個頁面有一個banner滾動欄酥泞,每3秒滾到一次。一般有一個banner對象悯姊,VC對它強持有悯许,
banner對象的需求是每3秒滾動一次先壕。需要添加一個NSTimer谆甜,當(dāng)我向banner對象添加回調(diào)的時候,NSTimer會對banner對象施加一個強引用谆棺,這個時候就產(chǎn)生了相互循環(huán)引用問題改淑。
實際上溅固,NSTimer創(chuàng)建完成以后會有一個主線程RunLoop去強引用NSTimer強引用兰珍。就算是VC退出釋放掉掠河,那么banner對象也不會釋放,因為主線程runLoop強引用了這個對象爆捞。
NSTimer有重復(fù)煮甥,以及非重復(fù)定時器成肘,假如是非重復(fù)定時器斧蜕,那么在NSTimer回調(diào)完成以后設(shè)置無效并置nil批销,那么就破解了循環(huán)引用均芽。
假如NSTimer重復(fù)多次回調(diào)
第一種:在NSTimer和Banner之間設(shè)置一個中間對象骡技,這個中間對象分別弱引用NSTimer和banner對象。那么當(dāng)VC釋放掉囤萤,banner對象也就釋放掉了涛舍,NSTimer的回調(diào)會去中間對象唆途,而中間對象只要判斷banner對象為不為nil 如果為nil就直接無效化NSTimer肛搬。
考點
- 代理
- block
- NSTimer
- 大環(huán)引用
MST
如果我們對一個類添加了關(guān)聯(lián)對象温赔,那么在這個類被釋放以后會清除掉這個關(guān)聯(lián)對象嗎?
會的待秃,因為系統(tǒng)在dealloc中釋放了關(guān)聯(lián)對象
關(guān)于autoreleasepool為和可以嵌套調(diào)用痹屹?
實際上多次嵌套插入哨兵對象志衍,每次進行autoreleasePool的代碼塊創(chuàng)建的時候楼肪,假如autoreleasePoolpage沒有滿的情況下淹辞,系統(tǒng)就會在當(dāng)前autoreleasePoolpage棧中進行一次哨兵對象的插入象缀,如果滿了會創(chuàng)建一個新的autoreleasePoolpage,然后當(dāng)前的autoreleasePoolpage中的child指針指向新創(chuàng)建的autoreleasePoolpage霞怀。
autoreleasePool的應(yīng)用場景
在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場景手動插入autoreleasePool毙石。
autoreleasePool實現(xiàn)原理:
以棧為節(jié)點徐矩,通過雙向鏈表的形式組合而成的數(shù)據(jù)結(jié)構(gòu)
什么是ARC
ARC是由LLVM編譯器以及runtime協(xié)作來為我們實現(xiàn)自動引用計數(shù)的管理
為什么weak指針指向的對象在廢棄之后會被自動置為nil
當(dāng)對象在被廢棄之后滤灯,dealloc的內(nèi)部方法實現(xiàn)當(dāng)中會調(diào)用清除弱引用的方法鳞骤,然后在清除弱引用的方法中會通過hash算法來查找被廢棄對象在弱引用表當(dāng)中的位置來提取它所對的人用引用指針的列表數(shù)組,然后進行for循環(huán)遍歷顷帖,把每一個弱引用指針都置為nil
蘋果是如何實現(xiàn)AutoreleasePool
AutoreleasePool是以棧為節(jié)點,雙向鏈表形式來合成的數(shù)據(jù)結(jié)構(gòu)陈症。
什么是循環(huán)引用录肯?你遇到過哪些循環(huán)引用论咏,是怎么解決的颁井?
- 自循環(huán)引用
- 雙向循環(huán)引用
- 大環(huán)引用
比如說NStimer的循環(huán)引用厅贪。