iOS的內(nèi)存管理
- 內(nèi)存布局
- 內(nèi)存管理方案
- 數(shù)據(jù)結(jié)構(gòu)
- ARC&MRC
- 引用計(jì)數(shù)
- 弱引用
- 自動(dòng)釋放池
- 循環(huán)引用
內(nèi)存布局

- stack區(qū)
- 方法調(diào)用
- heap區(qū) (堆區(qū))
- alloc分配的一些對(duì)象
- bss :未初始化的全局變量
- data:已初始化的全局變量
- text:程序代碼
內(nèi)存管理方案
- TageedPointer
- NONPOINTER_ISA
- 64位 isa占64位,一般用30~40就夠用了,所以在剩余的位中做了內(nèi)存管理
- 散列表 是復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
- 引用計(jì)數(shù)表
- 弱引用表
內(nèi)存管理的分析全部都是基于開(kāi)源代碼的講解
NONPOINTER_ISA
arm64結(jié)構(gòu)下是64位
- 第一位indexed
- 0代表isa只是一個(gè)純的isa指針,里面內(nèi)容指向當(dāng)前的類(lèi)對(duì)象地址,
- 1代表不單單是isa指針,還有一些關(guān)于內(nèi)存管理的一些數(shù)據(jù)
- 第二位has_assoc
- 0代表 沒(méi)有關(guān)聯(lián)對(duì)象
- 1代表 有關(guān)聯(lián)對(duì)象
- 第三位has_cxx_dtor 當(dāng)前對(duì)象是否用到c++
- 0 沒(méi)有
- 1 有
- shiftcls 4 ~ 36 33位 比特位是類(lèi)對(duì)象的地址
- 當(dāng)前類(lèi)對(duì)象的內(nèi)存地址
- magic
- weakly_referenced
- 是否有弱引用指針
- deallocating
- 是否有dealloc的操作
- has_sidetable_rc
- 如果當(dāng)前的引用計(jì)數(shù)已經(jīng)達(dá)到上限,需要外掛一個(gè)has_sidetable_rc一個(gè)數(shù)據(jù)結(jié)構(gòu)去存儲(chǔ)相關(guān)的引用計(jì)數(shù)內(nèi)容
- extra_rc 44~63 都是 extra_rc 引用計(jì)數(shù)
- 引用計(jì)數(shù)在很小的范圍內(nèi),會(huì)直接存儲(chǔ)到isa這里,而不需要單獨(dú)的引用計(jì)數(shù)表來(lái)單獨(dú)存儲(chǔ)
散列表方式
- sideTables()結(jié)構(gòu)
- SideTable (數(shù)據(jù)結(jié)構(gòu))
- 64個(gè)(非嵌入式系統(tǒng))
SideTable

為什么不是一個(gè)SideTable? 為什么要組成SideTables呢?
- 對(duì)象可能需要在不同線程操作創(chuàng)建,這個(gè)時(shí)候進(jìn)行操作的時(shí)候需要進(jìn)行加鎖處理才能保證安全,這樣存在效率問(wèn)題
- 用戶(hù)的內(nèi)存空間需要4gb,我們可以分配成千上萬(wàn)的引用對(duì)象,如果每個(gè)引用對(duì)象我們都要對(duì)齊進(jìn)行引用計(jì)數(shù)改變,都操作這張表就會(huì)出現(xiàn)效率問(wèn)題。如果一個(gè)對(duì)象操作這張表,會(huì)加鎖下一個(gè)對(duì)象需要在等上一個(gè)對(duì)象的鎖加完以后在處理,這就造成了效率低下的問(wèn)題
- 系統(tǒng)為了解決這個(gè)效率低下,系統(tǒng)給我們提供了一個(gè)分離鎖的概念
- 系統(tǒng)為我們分配了8個(gè)這個(gè)表(64位),這樣就可以并發(fā)的去修改引用解決問(wèn)題。
怎么樣實(shí)現(xiàn)快速分流?
SideTables的本質(zhì)是一張Hash表。
可能有64個(gè)SideTable存儲(chǔ)不同的引用計(jì)數(shù)表、和弱引用表
什么是hash算法
對(duì)象指針可以作為Key通過(guò)hash函數(shù)計(jì)算計(jì)算出hash表中你的value值的下標(biāo)也就是索引。
hash查找的過(guò)程
給定值是對(duì)象內(nèi)存地址,目標(biāo)是數(shù)組下標(biāo)索引。
給了一個(gè)對(duì)象的內(nèi)存地址,通過(guò)哈希函數(shù)的運(yùn)算,得到了一個(gè)集合的下標(biāo)索引值
對(duì)象的內(nèi)存地址指針 % 數(shù)組的count就可以得到這個(gè)數(shù)組中的索引
這樣就查找速度快了
散列表方案設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)
- Spinlock_t
- 自旋鎖 是"忙等"的鎖,如果當(dāng)前鎖已經(jīng)被其他線程獲取,那么當(dāng)前線程會(huì)不斷的探測(cè)這個(gè)鎖是否被其他線程釋放,如果釋放自己第一時(shí)間去獲取這個(gè)鎖
- 特點(diǎn):適合輕量訪問(wèn)(這個(gè)中+1 -1 計(jì)算邏輯不復(fù)雜的情況下稱(chēng)之為輕量)
- 信號(hào)量
- 當(dāng)前線程獲取不到鎖的時(shí)候,會(huì)把自己的線程阻塞休眠,等這個(gè)鎖被釋放的時(shí)候,會(huì)喚醒它獲取這個(gè)鎖
引用計(jì)數(shù)表
- 實(shí)際上是一個(gè)refcountMap(hash表或者是字典),提高查找效率,存儲(chǔ)查找都是hsah,找到
- size_t引用技術(shù)使用64位存儲(chǔ)的
- 第一位是否有weak
- 第二位是否deallocating
弱引用表
- weak_table_t也是一張hash表,
- 通過(guò)對(duì)象指針,hash算法的到我們的weak_entry_t的表
- weak_entry_t里面存儲(chǔ)的就是弱引用指針。
MRC和ARC
MRC
手動(dòng)引用計(jì)數(shù),進(jìn)行內(nèi)存管理
- alloc 分配一個(gè)對(duì)象的內(nèi)存空間
- retain 引用計(jì)數(shù)加1
- release 引用計(jì)數(shù)-1
- retainCount 當(dāng)前引用計(jì)數(shù)的值
- autorelease 會(huì)在autoreleasepool結(jié)束的時(shí)候,向其每一個(gè)對(duì)象發(fā)送release消息
- dealloc 在MRC中需要顯示的調(diào)用super dealloc需要廢棄父類(lèi)的相關(guān)成員變量
ARC
什么事ARC
全名自動(dòng)引用計(jì)數(shù)
- ARC是由LLVM和Runtime協(xié)作完成
- ARC中禁止手動(dòng)調(diào)用retain、release、retainCount、dealloc、
- 可以重寫(xiě)dealloc但是不能調(diào)用super dealloc
- ARC中新增weak、strong屬性關(guān)鍵字
ARC和MRC的區(qū)別
- MRC是手動(dòng)管理,ARC是LLVM和Runtime協(xié)作進(jìn)行自動(dòng)引用計(jì)數(shù)管理
- MRC可以調(diào)用一些引用計(jì)數(shù)相關(guān)的+-1操作方法,而ARC是不能的
- ARC有weak和strong
引用計(jì)數(shù)管理
實(shí)現(xiàn)原理分析
alloc實(shí)現(xiàn)
- 經(jīng)過(guò)一些列調(diào)用,最終調(diào)用了C函數(shù)calloc(初始化完成會(huì)自己置0 而malloc是垃圾數(shù)據(jù))。
- 此時(shí)并沒(méi)有設(shè)置引用計(jì)數(shù)為1
retain實(shí)現(xiàn)
- SideTable &table = SideTables()[this] 先獲取對(duì)應(yīng)的SideTable
- size_t & refcntStorage = table.refcnts[this]; 獲取到SideTable中的引用計(jì)數(shù)值
- refcntstorage += SIDE_TABLE_RC_ONE; 對(duì)其+1
- SIDE_TABLE_RC_ONE不是1,前兩個(gè)位置是存儲(chǔ)弱引用計(jì)數(shù),和是否正在dealloc的,是后面62位,所以需要做一個(gè)偏移量。 所以說(shuō)是4.
release實(shí)現(xiàn)
- SideTable &table = SideTables()[this] 先獲取對(duì)應(yīng)的SideTable
- RefcountMap:iterator it = table.refcnts.find(this); 獲取到SideTable中的引用計(jì)數(shù)值
- it->second -= SIDE_TABLE_RC_ONE; 和retain正好相反
retainCount的實(shí)現(xiàn)
- SideTable &table = SideTables()[this] 先獲取對(duì)應(yīng)的SideTable
- size_t refcnt_result = 1; 聲明一個(gè)局部變量
- RefcountMap::iterator it = table.refcnts.find(this) 獲取到SideTable中的引用計(jì)數(shù)值,剛?cè)lloc的對(duì)象其實(shí)里面是為0的
- refcnt_result += it-> second >> SIDE_TABLE_RC_SHIFT 把查找的結(jié)果做一個(gè)向右偏移的操作,然后在+1
dealloc的實(shí)現(xiàn)
- 調(diào)用_objc_rootDealloc
- rootDealloc
- 是否直接釋放的判斷標(biāo)準(zhǔn)
- nonpointer_isa 是否是非指針型的isa指針
- weakly_referenced 是否有weak指針指向它
- has_assoc 當(dāng)前對(duì)象是否有關(guān)連對(duì)象
- has_cXX_dtor 是否使用了C++相關(guān)的代碼,是否ARC
- has_sidetable_rc 是否是通過(guò)sidtable表來(lái)存儲(chǔ)的。
- 以上都不是 就可以釋放,如果可以就使用c函數(shù)的free函數(shù)釋放
- 如果不可以就使用object_dispose函數(shù)來(lái)進(jìn)行后續(xù)的處理,然后結(jié)束

object_dispose實(shí)現(xiàn)
- objc_destructInstance
- C函數(shù)的free
- 結(jié)束

objc_destructInstance的實(shí)現(xiàn)
- 是否有C++或者是否有ARC
- YES object_cxxDestruct
- NO 調(diào)用AssociatedObjects
- 如果沒(méi)有C++或者ARC 就調(diào)用AssociatedObjects方法
- YES _object_remove_assocations()
- NO clearDealloccating
- _object_remove_assocations,以后還是沒(méi)有關(guān)聯(lián)對(duì)象那么都會(huì)調(diào)用clearDealloccating結(jié)束objc_destructInstance方法。

clearDealloccating的實(shí)現(xiàn)
- sidetable_clearDeallocating
- weak_clear_no_lock
- 將指向該對(duì)象的弱引用指針置為nil
- table.refcnts.erase
- 從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)

弱引用管理
弱引用調(diào)用棧

一個(gè)被聲明為_(kāi)_weak弱引用的指針,經(jīng)過(guò)編譯器編譯以后,會(huì)調(diào)用objc_initWeak方法,經(jīng)過(guò)一系列的函數(shù)調(diào)用棧,會(huì)調(diào)用weak_register_no_lock這個(gè)函數(shù),進(jìn)行弱引用變量的添加。
具體添加的位置,是通過(guò)一個(gè)hash算法來(lái)進(jìn)行進(jìn)行位置查找的,
如果說(shuō)我們查找位置當(dāng)中已經(jīng)有了當(dāng)前對(duì)象所有的弱引用數(shù)組,我們新的弱引用變量添加到引用數(shù)組當(dāng)中,如果沒(méi)有弱引用數(shù)組,那么就創(chuàng)建一個(gè)新的弱引用數(shù)組,然后第0個(gè)位置添加wek指針,后面的都置為0。
當(dāng)清除weak變量 通知設(shè)置指向?yàn)閚il, 它是如何置nil的

完整過(guò)程
- 通過(guò)被廢棄對(duì)象的指針經(jīng)過(guò)hash算法的到,求出弱引用數(shù)組對(duì)應(yīng)的數(shù)組索引位置,然后通過(guò)這個(gè)索引返回給對(duì)象我這個(gè)弱引用數(shù)組
- 如果弱引用數(shù)組為空,不做操作
- 如果弱引用數(shù)組不為空遍歷這個(gè)數(shù)組,如果數(shù)組中的指針就是被廢棄的弱引用指針那么就將其置為nil。
總結(jié)
- 當(dāng)一個(gè)對(duì)象被dealloc之后,dealloc內(nèi)部的實(shí)現(xiàn)會(huì)去調(diào)用弱引用清除這個(gè)函數(shù)
- 然后函數(shù)會(huì)通過(guò),當(dāng)前對(duì)象指針查找弱引用表 ,把一個(gè)對(duì)象的所有弱引用表都拿出來(lái),是一個(gè)數(shù)組
- 編譯所有這個(gè)數(shù)組當(dāng)中的弱引用指針?lè)謩e置為nil
自動(dòng)釋放池

編譯器改寫(xiě)
- objc_autoreleasepoolPush()和objc_autoreleasePoolPop中間是代碼
- push返回值是一個(gè)無(wú)類(lèi)型的指針
-
objc_autoreleasePoolPop需要這個(gè)無(wú)類(lèi)型指針作為標(biāo)記也就是push返回的無(wú)類(lèi)型的指針
autoreleasePool改寫(xiě).png
objc_autoreleasepoolPush和objc_autoreleasePoolPop的函數(shù)調(diào)用棧
- 內(nèi)部會(huì)調(diào)用AutoreleasePoolPage::push方法
- AutoreleasePoolPage::pop方法
- 一次Pop當(dāng)然于一次批量的Pop操作(AutoreleasePool中的對(duì)象都會(huì)放入到自動(dòng)釋放池中當(dāng)Pop之后,AutoreleasePool中的對(duì)象的對(duì)象都會(huì)發(fā)送一次release消息所以是批量操作)
自動(dòng)釋放池的數(shù)據(jù)結(jié)構(gòu)
- 以棧為節(jié)點(diǎn)通過(guò)雙向鏈表的形式組合而成
- 適合線程一一對(duì)應(yīng)的。
AtuoreleasePoolPage的數(shù)據(jù)結(jié)構(gòu)
- id*next
- 當(dāng)前棧的空的位置
- autoreleasePollPage * const parent;
- 鏈表的父指針
- autoreleasePollPage *child;
- 鏈表的孩子指針
- pthread_t const thread;
- 與線程一一對(duì)應(yīng)的
AtuoreleasePoolPage::Push

autorelease

- 一個(gè)對(duì)象調(diào)用了autorelease方法,這個(gè)對(duì)象添加到next指針之后,next移動(dòng)到新的位置,下一個(gè)調(diào)用autorelease方法的對(duì)象就會(huì)添加到這個(gè)位置
AtuoreleasePoolPage::Pop
- 根據(jù)傳入的哨兵對(duì)象找到對(duì)應(yīng)位置
- 給上次Push操作之后添加的對(duì)象依次發(fā)送release消息(next指針到哨兵對(duì)象之間的對(duì)象依次發(fā)送release消息)
- 回退指針搭配正確位置

雙向鏈表的概念

棧結(jié)構(gòu)概念

面試題自動(dòng)釋放池的實(shí)現(xiàn)結(jié)構(gòu)是什么?
- 以棧為節(jié)點(diǎn)通過(guò)雙向鏈表的形式組合而成
自動(dòng)釋放池的總結(jié)
下面代碼什么時(shí)候釋放
viewdidload{
NSMutableArray *array = [NSSmutableArray array];
}
- 在當(dāng)次runloop將要結(jié)束的時(shí)候調(diào)用AutoreleasePoolPage::Pop()
- 每一次runloop循環(huán)將要結(jié)束的時(shí)候都會(huì)對(duì)前一次創(chuàng)建的autoreleasePool進(jìn)行Pop操作,同時(shí)會(huì)Push進(jìn)來(lái)一個(gè)新的autoreleasePool
- viewdidload創(chuàng)建的對(duì)象,是在當(dāng)次runloop將要結(jié)束的時(shí)候,調(diào)用autoreleasePoolpage::Pop方法時(shí)候,把對(duì)應(yīng)的array對(duì)象調(diào)用它的release方法。
多次嵌套就是多次插入哨兵對(duì)象
關(guān)于autoreleasepool為和可以嵌套調(diào)用?
實(shí)際上多次嵌套插入哨兵對(duì)象,每次進(jìn)行autoreleasePool的代碼塊創(chuàng)建的時(shí)候,假如autoreleasePoolpage沒(méi)有滿的情況下,系統(tǒng)就會(huì)在當(dāng)前autoreleasePoolpage棧中進(jìn)行一次哨兵對(duì)象的插入,如果滿了會(huì)創(chuàng)建一個(gè)新的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)引用
- 在合適的時(shí)機(jī)手動(dòng)斷環(huán)
具體解決方案:
- __weak
- __block
- __unsafe_unretained
__weak 破解循環(huán)引用

__block破解* 破解循環(huán)引用
- MRC __block修飾對(duì)象不會(huì)增加其引用計(jì)數(shù),避免了循環(huán)引用
- ARC __block修飾對(duì)象會(huì)被強(qiáng)引用,無(wú)法避免循環(huán)引用,需手動(dòng)解環(huán)
__unsafe_unretained 破解循環(huán)引用
- 修飾對(duì)象不會(huì)增加引用計(jì)數(shù),避免了循環(huán)引用
- 如果被修飾對(duì)象在某一時(shí)機(jī)被釋放,會(huì)產(chǎn)生懸垂指針,懸垂指針又可能會(huì)出現(xiàn)不可預(yù)見(jiàn)的問(wèn)題
循環(huán)引用示例
在開(kāi)發(fā)過(guò)程中,你遇到過(guò)哪些循環(huán)引用問(wèn)題,你又是如何解決的?
Block的使用示例
NSTimer的循環(huán)引用問(wèn)題
有一個(gè)頁(yè)面有一個(gè)banner滾動(dòng)欄,每3秒滾到一次。一般有一個(gè)banner對(duì)象,VC對(duì)它強(qiáng)持有,
banner對(duì)象的需求是每3秒滾動(dòng)一次。需要添加一個(gè)NSTimer,當(dāng)我向banner對(duì)象添加回調(diào)的時(shí)候,NSTimer會(huì)對(duì)banner對(duì)象施加一個(gè)強(qiáng)引用,這個(gè)時(shí)候就產(chǎn)生了相互循環(huán)引用問(wèn)題。
實(shí)際上,NSTimer創(chuàng)建完成以后會(huì)有一個(gè)主線程RunLoop去強(qiáng)引用NSTimer強(qiáng)引用。就算是VC退出釋放掉,那么banner對(duì)象也不會(huì)釋放,因?yàn)橹骶€程runLoop強(qiáng)引用了這個(gè)對(duì)象。
NSTimer有重復(fù),以及非重復(fù)定時(shí)器,假如是非重復(fù)定時(shí)器,那么在NSTimer回調(diào)完成以后設(shè)置無(wú)效并置nil,那么就破解了循環(huán)引用。
假如NSTimer重復(fù)多次回調(diào)
第一種:在NSTimer和Banner之間設(shè)置一個(gè)中間對(duì)象,這個(gè)中間對(duì)象分別弱引用NSTimer和banner對(duì)象。那么當(dāng)VC釋放掉,banner對(duì)象也就釋放掉了,NSTimer的回調(diào)會(huì)去中間對(duì)象,而中間對(duì)象只要判斷banner對(duì)象為不為nil 如果為nil就直接無(wú)效化NSTimer。

考點(diǎn)
- 代理
- Block
- NSTimer
- 大環(huán)引用
面試題總結(jié)
如果我們對(duì)一個(gè)類(lèi)添加了關(guān)聯(lián)對(duì)象,那么在這個(gè)類(lèi)被釋放以后會(huì)清除掉這個(gè)關(guān)聯(lián)對(duì)象嗎?
會(huì)的,因?yàn)橄到y(tǒng)在dealloc中釋放了關(guān)聯(lián)對(duì)象
關(guān)于autoreleasepool為和可以嵌套調(diào)用?
實(shí)際上多次嵌套插入哨兵對(duì)象,每次進(jìn)行autoreleasePool的代碼塊創(chuàng)建的時(shí)候,假如autoreleasePoolpage沒(méi)有滿的情況下,系統(tǒng)就會(huì)在當(dāng)前autoreleasePoolpage棧中進(jìn)行一次哨兵對(duì)象的插入,如果滿了會(huì)創(chuàng)建一個(gè)新的autoreleasePoolpage,然后當(dāng)前的autoreleasePoolpage中的child指針指向新創(chuàng)建的autoreleasePoolpage。
autoreleasePool的應(yīng)用場(chǎng)景
在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場(chǎng)景手動(dòng)插入autoreleasePool。
autoreleasePool的實(shí)現(xiàn)原理是什么?
autoreleasePool實(shí)現(xiàn)原理:
- 以棧為節(jié)點(diǎn),通過(guò)雙向鏈表的形式組合而成的數(shù)據(jù)結(jié)構(gòu)
什么是ARC
- ARC是由LLVM編譯器以及runtime協(xié)作來(lái)為我們實(shí)現(xiàn)自動(dòng)引用計(jì)數(shù)的管理
為什么weak指針指向的對(duì)象在廢棄之后會(huì)被自動(dòng)置為nil
當(dāng)對(duì)象在被廢棄之后,dealloc的內(nèi)部方法實(shí)現(xiàn)當(dāng)中會(huì)調(diào)用清除弱引用的方法,然后在清除弱引用的方法中會(huì)通過(guò)hash算法來(lái)查找被廢棄對(duì)象在弱引用表當(dāng)中的位置來(lái)提取它所對(duì)的人用引用指針的列表數(shù)組,然后進(jìn)行for循環(huán)遍歷,把每一個(gè)弱引用指針都置為nil
蘋(píng)果是如何實(shí)現(xiàn)AutoreleasePool
AutoreleasePool是以棧為節(jié)點(diǎn),雙向鏈表形式來(lái)合成的數(shù)據(jù)結(jié)構(gòu)。
什么是循環(huán)引用?你遇到過(guò)哪些循環(huán)引用,是怎么解決的?
- 自循環(huán)引用
- 雙向循環(huán)引用
- 大環(huán)引用
比如說(shuō)NStimer的循環(huán)引用。
