Objective-C 高級(jí)編程讀書(shū)筆記之內(nèi)存管理

前言


項(xiàng)目中經(jīng)常會(huì)有些crash說(shuō)是使用了野指針或者是內(nèi)存泄露,深覺(jué)自己的姿勢(shì)水平不夠,又翻了翻iOS界的經(jīng)典書(shū)籍《Objective-C 高級(jí)編程》。將重讀過(guò)程中的一些想法體會(huì)記錄下來(lái),供自己和大家加深理解。
盡管現(xiàn)在ARC已經(jīng)做的很出色了,我們的代碼幾乎不用寫(xiě)任何和內(nèi)存管理相關(guān)的代碼,app可以很好的運(yùn)行不會(huì)出現(xiàn)內(nèi)存泄露。但是有沒(méi)有想過(guò)為什么可以做到這樣?畢竟c程序猿在malloc(c++里是new)完之后還要手動(dòng)free(c++ 是delete),java碼農(nóng)還會(huì)時(shí)不時(shí)提一個(gè)詞GC(garbage collection,簡(jiǎn)單說(shuō)下java里的內(nèi)存管理,java虛擬機(jī)會(huì)時(shí)刻監(jiān)控對(duì)象所處的狀態(tài),根據(jù)使用情況將對(duì)象在不同年代的區(qū)域來(lái)回倒,當(dāng)處于可回收的對(duì)象達(dá)到一定程度后,就來(lái)一次集中處理,這個(gè)時(shí)候整個(gè)程序會(huì)暫停(很短暫))呢,我們iOSer有沒(méi)有想過(guò)這個(gè)在OC里內(nèi)存管理是如何做的,我們知道OC是基于引用計(jì)數(shù)來(lái)做對(duì)內(nèi)存管理的,那這個(gè)具體又是怎么做的呢?即使現(xiàn)在熟悉MRC這個(gè)詞的人越來(lái)越少,但是面試官一般還是會(huì)和你聊OC內(nèi)存管理或者ARC背后的實(shí)現(xiàn)原理等,其實(shí)還是有必要一些基本概念的。鑒于本人的姿勢(shì)水平有些內(nèi)容可能理解有誤歡迎指出并討論。


慣例先說(shuō)一些基礎(chǔ)姿勢(shì)

  1. 變量作用域
    學(xué)過(guò)C語(yǔ)言都知道,變量作用域明確了變量的有效空間,如果出了變量的作用域就表示該變量無(wú)效了
  2. 通過(guò)引用計(jì)數(shù)來(lái)管理對(duì)象生命周期
    iOS中是通過(guò)CocoaTouch框架和運(yùn)行時(shí)來(lái)提供具體的實(shí)現(xiàn)和支持。在蘋(píng)果的實(shí)現(xiàn)方案中通過(guò)集中管理的所有對(duì)象的引用計(jì)數(shù),使用一個(gè)全局hash表存儲(chǔ)了對(duì)象和它引用計(jì)數(shù)的映射關(guān)系。而本書(shū)中是通過(guò)在每個(gè)對(duì)象頭部加入一個(gè)引用計(jì)數(shù)的字段來(lái)表示存儲(chǔ)對(duì)象引用計(jì)數(shù)的值。
    3.弱引用表來(lái)實(shí)現(xiàn)弱引用管理
    蘋(píng)果運(yùn)行時(shí)也是通過(guò)一個(gè)全局的hash表存儲(chǔ)了對(duì)象以及所有指向它的弱引用的指針的映射關(guān)系。
    變量(可以理解為OC對(duì)象指針)與對(duì)象的關(guān)系可以其實(shí)就只有三種:強(qiáng)引用,弱引用以及無(wú)關(guān)系。強(qiáng)引用理解為持有對(duì)象會(huì)影響對(duì)象的生命周期,弱引用理解為指向?qū)ο蟮粫?huì)影響對(duì)象的生命周期,無(wú)關(guān)系就是變量與對(duì)象沒(méi)有任何聯(lián)系比如3歲的小明和沙灘上的一粒沙子。在OC里對(duì)應(yīng)的用于表達(dá)強(qiáng)引用和弱引用就是用__strong 和 __weak修飾符來(lái)表達(dá)。強(qiáng)引用和弱引用貌似已經(jīng)可以完整表達(dá)指針變量和對(duì)象的關(guān)系了,但實(shí)際使用過(guò)程中發(fā)現(xiàn)還是不夠用,出現(xiàn)了一些其他變量與對(duì)象關(guān)系修飾符,比如 __autoreleasing,__unsafe_unretain。下面就具體說(shuō)下這幾個(gè)修飾符的作用以及與引用計(jì)數(shù)的關(guān)系
    __strong:指針變量的默認(rèn)修飾符,如下代碼
id obj = [[NSObject alloc] init];

與下面的代碼等價(jià):

id __strong obj = [[NSObject alloc] init];

__strong修飾的變量指向(賦值)對(duì)象時(shí)會(huì)將對(duì)象的引用計(jì)數(shù)加一,類(lèi)似MRC下調(diào)用有一次[obj retain],在指針變量出了它作用域后會(huì)通過(guò)調(diào)用對(duì)象的release方法將對(duì)象引用計(jì)數(shù)減一,如果對(duì)象的引用計(jì)數(shù)為0了,就會(huì)調(diào)用對(duì)象的dealloc方法(我們項(xiàng)目有個(gè)很簡(jiǎn)單的檢查VC是否有內(nèi)存泄露的方法,就是判斷VC的dealloc方法在pop后的2秒之內(nèi)有沒(méi)有調(diào)用,如果沒(méi)有就彈框提示,不知道各位有沒(méi)有其他更高級(jí)的方法)。在MRC下,在對(duì)象在出了它作用域的時(shí)候需要主動(dòng)調(diào)用下 [obj release],在ARC就不需要鍵入這行代碼(手寫(xiě)也通不過(guò)編譯),因?yàn)榫幾g器在ARC下會(huì)自動(dòng)在合適的地方插入這句[obj release]??偨Y(jié)下:__strong 修飾的變量保證訪問(wèn)期間對(duì)象 一直存在

__weak修飾的變量在指向?qū)ο蟮臅r(shí)候,對(duì)象的引用計(jì)數(shù)不會(huì)發(fā)生變化,它存在的主要目的是解決循環(huán)引用的問(wèn)題。如下代碼(ARC下,如無(wú)特殊說(shuō)明,本文貼出的代碼均指ARC環(huán)境下):

@interface TestA : NSObject
@property (nonatomic, strong) id obj;//生成的成員變量使用__strong修飾
@end
@interface TestB : NSObject
@property (nonatomic, strong) id obj;//生成的成員變量使用__strong修飾
@end
int main() {
TestA *a = [[TestA alloc] init];    //a指向?qū)ο蟮囊糜?jì)數(shù)為1
TestB *b = [[TestB alloc] init];    //b指向?qū)ο蟮囊糜?jì)數(shù)為1
a.obj = b;    // 由于a的obj是強(qiáng)引用,根據(jù)上面說(shuō)的規(guī)則b指向?qū)ο蟮囊糜?jì)數(shù)為2
b.obj = a;    // 由于b的obj是強(qiáng)引用,根據(jù)上面說(shuō)的規(guī)則a指向?qū)ο蟮囊糜?jì)數(shù)為2,出現(xiàn)了循環(huán)引用
}

出了main函數(shù)的作用域后,a和b變量均失效,這個(gè)時(shí)候會(huì)調(diào)用[a release],[b release];方法,這個(gè)時(shí)候a和b指向的對(duì)象引用計(jì)數(shù)均減1,完了后都為1。但是這個(gè)時(shí)候a和b指向的對(duì)象均無(wú)法再次調(diào)用release方法進(jìn)行釋放了,因?yàn)橥饷鏇](méi)機(jī)會(huì)接觸到他們了,他們的引用計(jì)數(shù)又不為零,所謂的內(nèi)存泄露就來(lái)了。。。值得一提的是在對(duì)象釋放后,weak變量自動(dòng)被賦值為nil。大致流程如下:

  1. 在weak表中找到鍵值為對(duì)象地址的記錄(目測(cè)應(yīng)該是個(gè)鏈表)
  2. 然后逐個(gè)將這些weak變量賦值位置為nil
  3. 完了后將改鍵值從weak表中刪除
  4. 從引用計(jì)數(shù)表中刪除該對(duì)象鍵值對(duì)應(yīng)的記錄
    需要注意的是雖然書(shū)上說(shuō)每使用一次weak變量會(huì)把對(duì)象在自動(dòng)釋放池里就注冊(cè)一次,而且在往自動(dòng)釋放池中注冊(cè)的時(shí)候?qū)ο蟮囊糜?jì)數(shù)會(huì)也加一,但是實(shí)際上這兩條都沒(méi)有出現(xiàn)(Xcode8.3.1,sdk 10.3),具體見(jiàn)下圖:
weak并沒(méi)有將對(duì)象注冊(cè)到自動(dòng)釋放池中代碼

輸出結(jié)果

總結(jié)下:__weak修飾符主要用于打破循環(huán)引用

__autoreleaseing表示將對(duì)象放入自動(dòng)釋放池中,等價(jià)于MRC下的[obj autorelease]方法,在自動(dòng)釋放池里清理對(duì)象的時(shí)候?qū)?duì)象的引用計(jì)數(shù)減一,如果對(duì)象計(jì)數(shù)為零則廢棄并調(diào)用對(duì)象的dealloc方法??偨Y(jié):__autoreleaseing最終效果是延長(zhǎng)了對(duì)象的生命周期,將對(duì)象的釋放(對(duì)自動(dòng)釋放池里的對(duì)象調(diào)用[obj release])時(shí)機(jī)改為runloop結(jié)束時(shí)。主要用于函數(shù)返回對(duì)象和引用參數(shù)的傳遞。函數(shù)返回對(duì)象如下:

\+ (id)myObject {
NSObject *obj = [[NSObject alloc] init];//1
return obj;//2
}

步驟1里生成一個(gè)自己持有的對(duì)象obj,其引用計(jì)數(shù)為1,步驟2強(qiáng)這個(gè)obj放回,那個(gè)obj會(huì)被放入自動(dòng)釋放池中,也就是會(huì)調(diào)用一次[obj autorelease];那么obj的引用計(jì)數(shù)為2,由于obj要出自己的作用域了(函數(shù)體),其會(huì)調(diào)用一次[obj release],這個(gè)時(shí)候obj的引用計(jì)數(shù)為1。如下代碼:

id temp = [MyObject myObject];

將對(duì)象obj作為函數(shù)的返回值賦給temp(蘋(píng)果做了很多優(yōu)化,可以先不用管),可以簡(jiǎn)單認(rèn)為就是將對(duì)象傳遞給了調(diào)用方,temp所指向的對(duì)象引用計(jì)數(shù)為1(這里面還有很多細(xì)節(jié),大家可以繼續(xù)去那本書(shū)里面挖,或者一起套討論)。
對(duì)于__autoreleaseing還要注意一點(diǎn)是: 對(duì)象加入自動(dòng)釋放池一次,對(duì)象的引用計(jì)數(shù)就+1. 如下代碼可以有驗(yàn)證:

autorelease改變對(duì)象的引用計(jì)數(shù)

最后說(shuō)下__unsafe_unretain。這個(gè)可以理解為對(duì)象廢棄后不會(huì)置nil的weak就可以了,現(xiàn)在基本用不到了,可以暫時(shí)不用管。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容