你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 【Bugly干貨分享】iOS內存管理:從MRC到ARC實踐

【Bugly干貨分享】iOS內存管理:從MRC到ARC實踐

編輯:IOS開發綜合

Bugly 技術干貨系列內容主要涉及移動開發方向,是由 Bugly 邀請騰訊內部各位技術大咖,通過日常工作經驗的總結以及感悟撰寫而成,內容均屬原創。

對於iOS程序員來說,內存管理是入門的必修課。引用計數、自動釋放等概念,都是與C語言完全不同的。搞明白這些,代碼才有可能不 crash。然而就是這麼牛逼的內存管理,著實讓我這個從 C 轉過來的老程序員頭疼了一段時間。

[C++ 程序員的迷惑和憤怒]

iOS 內存管理的核心是引用計數。與眾多五年甚至更多以上開發經驗的程序員一樣,筆者當初是從 C/C++轉到的 OC,接觸到 MRC。當時遇到最頭疼的問題就是:為什麼那麼多 release?到底什麼地方會 release?同樣初始化一個字符串的兩個方法為什麼不同?上邊一個不需要調用 release,後邊一個就需要調用 release?

    NSString * str1 = [NSString stringWithFormat:”qqstock“]; 
   NSString * str2 = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];

再加上一個屬性賦值與成員變量賦值,一個導致計數器加一,一個就不會!真他媽奇葩了!

    self.name = @“qqstock”;
    _name = @“qqstock”;

不知道是不是所有從 C/C++ 轉過來的程序員都遇到過類似的迷惑和憤怒。

[MRC 的初衷和實現方式]

那麼,蘋果為什麼要做這個?

首先,C/C++ 傳統的內存管理方式,所有的內存都需要業務代碼自己處理,程序員自己一定要知道一個內存對象什麼時候不再使用了,一定要知道這個內存對象的終點在哪裡。當代碼越來越復雜,參與開發的程序員越來越多,甚至隨著歲月的流逝更換了新的程序員,這個時候,很難有人說的清了。於是,要麼那個內存對象一直留在那裡,沒人敢釋放,整個程序占用的空間越來越大;要麼,一個膽大的程序員將它釋放掉,某處發生了 crash。盡管大家總結出許多類似“誰創建誰釋放”、“誰持有誰釋放” 的原則,但都導致存儲空間的浪費:為了保留僅僅一個內存對象,卻要將與它關聯的一大堆對象保留住,而其中大部分已經不再使用了。要麼,自己寫許許多多的代碼,頻繁對容器進行主動操作。
騰訊Buglyvc2s0rvE2rTmttTP87XEtdi3vaOsyrnTw9Xf1rvSqrGj1qTX1Ly6IHJldGFpbiDSu7TOo6xyZWxlYXNlINK7tM6jrL7NIE9LIMHLo6y8tLHjsfDIy7u51NrKudPDo6zE49a70qq199PDIHJlbGVhc2UgvavX1Ly6tcTS/dPDtM7K/cflweO+zbrDwcujrLK708O53LHwyMujoTwvcD4NCjxwPtPrIEOjr0Ojq6OrtKvNs7XExNq05rncwO23vcq9z+CxyKOsTVJDIMrHsrvKx8/UtcO3x7Oj1sfE3KO/yseyu8rHuPy807e9seOjv7b4x9KjrNXi0fnX9rXEtPq829Kyt8ezo7XNwa6jrMO/0ru49sTatOa21M/z1Pa809K7uPa8xsr9xve+zSBPSyDBy6Osw7/Su7TOIHJlbGVhc2WjrNa70OjSqrzssunSu7HpvMbK/cb3yse38c6qweOjrMjnufvOqsHjvs3KzbfFo6zI57n7srvOqsHjvs2yu9a00NDV5tX9tcTKzbfFwt+8raGjPC9wPg0KPHA+we3N4qOszqrBy73ivva6r8r9t7W72Na1tcTOyszio6zQ6NKquOPSu7j2IGF1dG9yZWxlYXNlILXEtqvO96Ost/HU8r7Nu+G08sbG1eK49sG8usO1xLP11tSjuiZsZHF1bzvWu7i61PDX1Ly6t7bOp8TatcTKwsfpvs0gT0vBy6OssrvSqrncsfDIy6OhJnJkcXVvOzwvcD4NCjxwPsTHw7SjrM6qyrLDtLK7vavL+dPQxNq05rbUz/O2vM2z0ruzySByZXRhaW7E2KO/ttTT2tK71tax4NLrxvejrMv8xNy5u9PD0ru49ry8yvW94r72y/nT0M7KzOKjrL7NvOG+9rK7u+HTw8G91tayosHQtcS8vMr1tbzWws7KzOK4/Li01NOhozwvcD4NCjxwPk9DINPQ0ru49iBkZWxlZ2F0ZSC1xLarzvejrNXiuPa2q873tcSz9s/W0rLKx9PQxuTP1sq10OjH87XEo6zU2rTLz8jM+Ln9oaPI57n7y/nT0LXYt722vMq508MgcmV0YWluo6xkZWxlZ2F0ZSC1xM7KzOLSu7aou+G1vNbC0a27t9L908OjrLP9wcsgZGVsZWdhdGWjrMa7ufuyu7jSsaPWpMv509DTw7untPrC67XEwt+8rba8ysfK99DOveG5ubXEo6zX7rzytaW1xLHIyOfLtdGtu7fBtLHtoaLLq8/ywbSx7aOss/20y9auzeKjrNK1zvGy47/PtqjSstPQxLPQqbXYt72x2NDr1/azySZsZHF1bzvRrbu30v3TwyZyZHF1bzujrMjnufu2vMrHIHJldGFpbqOsxMfDtKOs1+7W1bSm09rRrbu31tC1xMTatOa21M/zy63SsrK7u+Gxu9fu1tXKzbfFtfSho86qwcu94r721eK49s7KzOKjrMa7ufvSwMi7saPB9MHLIEOjr0Ojq6OrtcTEx9bWyPXS/dPDt73KvaGjJm1kYXNoOyZtZGFzaDvWwcnZuPizzNDy1LHB9Lj2uf22ybXEv9W85KGjPC9wPg0KPGgzIGlkPQ=="mrc-的優點和無奈">[MRC 的優點和無奈]

總結一下:
1. MRC 的計數器機制改善了內存管理的方式,減少了各個模塊的邏輯耦合,釋放了程序員對“何時該釋放”的心理壓力,解決了大部分的問題
2. 為了應對各種復雜的場景,很無奈的留了一個口子;
3. 兩種模式的並存,對 C++程序員轉移到 OC戰場,樹立了一個無形的心理門檻,使得起步階段問題更加復雜,比如:retain、assign、release、autorelease 等。

難道就沒有更好的方式麼?當然有更好的方式,而且一定有許多公司的 C++程序員或者 C 程序員寫了類似引用計數的程序,甚至比引用計數還要高級,只不過大多數公司沒有實力推廣一個編程語言而已。

而且,略微深入思考,一定許多人想到:如果讓系統對所有內存對象在運行時統一管理,問題就能徹底解決了。是的,的確如此,一定有人設計出來了。但是,代價比較高。

系統在運行時統一管理所有內存對象的釋放,會導致增加額外的內存和 CPU 開銷,在硬件設備尚且處於低級階段的時候,當程序員們依然在努力降低內存降低 CPU 消耗的時候,推出這樣的機制,是不合時宜的!

引用計數器的方式,編譯器並沒有增加太多的邏輯,只是在創建的時候增加一個計數器,在釋放的時候編譯器自動幫程序員增加一個邏輯判斷。這個邏輯並沒有增加太多的內存和 CPU 開銷。

再來看 autorelease,這個邏輯增加的成本可就大了去了,系統要一直持有該類型的內存對象,直到本次 runloop 結束。所以,無論蘋果,還是有經驗的程序員,都建議:能不用就盡量不用,能縮短范圍就盡量縮短范圍。
騰訊Bugly

[編程語言和編譯器的發展方向]

由於留了無奈的口子,野指針依然會出現,該 crash 的時候依然 crash。許多人說:這是程序員的問題,如果代碼寫的足夠好,一定不會出現野指針,一定不會出現 crash。是的,如果大家足夠小心,如果大家足夠盡力,這個世界上不會有任何沖突。

然而,編程語言和編譯器的發展,一定向著便利、易用、穩健、職能,甚至傻瓜!如果一個編譯器能夠讓一個對計算機毫無了解的人一天之內搞出自己想要的業務應用,誰又會拒絕呢?

許多程序員都是技術控,自己能做的事情盡量不讓別人做,自己能實現的邏輯盡量不用別人的。比如:C++的各種封裝、引用,我用 C 也能實現,有什麼大不了的!系統提供的各種類庫,我自己用底層的代碼也能實現,而且性能更優,代碼更少!但是,如果你連一個磚頭都要自己燒制,連一堵牆都要自己去砌,其它更重要的事情誰去做?

更何況,人,總有打盹的時候。
騰訊Bugly

[ARC 的適時推出]

隨著硬件的升級,條件已經成熟了,ARC到來了!

ARC 的初衷是為了讓程序員寫代碼的時候更加便利,最好不用再關注任何內存釋放的問題(也不用關注用什麼方式初始化的問題)。當然了,解決野指針的問題也是很重要的!總之,讓編碼更加簡單,程序更加健壯!

之前對 C++程序員頭疼的問題變得異常簡單:

NSString * str1 = [NSString stringWithFormat:”qqstock“];
NSString * str2 = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
self.name = @“qqstock”;    _name = @“qqstock”;

到底何時釋放?總之,你不用管了,用你的就好!
到底有何區別?沒啥區別,只管用就好了!

筆者之前一直很疑惑,因為自己一直想搞明白到底有何區別——技術控本質。現在,了解了ARC的初衷,也就敢於放心大膽的用了——許多刨根究底的程序員從匯編代碼也印證了這個“猜想”。ARC 的目的就是將程序員從 MRC 的各種”不同點“上解脫出來,對於尚未接觸過 MRC 的 C 程序員,是非常容易理解的,而對於已經習慣了 MRC 的程序員,反倒有點”不敢相信“!

如果讓你做,你會如何實現?邏輯其實很簡單。
首先,強引用依然保留 MRC 的方式,因為這樣實現的方式代價很低;
其次,一旦出現弱引用,則將內存對象在系統中建立映射表;一旦內存對象因為所有強引用歸零而釋放,則將所有弱引用指針歸零(指向 nil)——應該有一個鏈表。

其實,將弱引用強制指向 nil,也是一種無奈的方式,按理說,這依然是個隱患,是代碼邏輯的缺陷,只是人家幫你將錯誤的代價降到最低而已。

總之,強引用的邏輯是:如果都不用了,我就釋放掉;弱引用的邏輯是:如果釋放了,我就置 nil!最終,程序員不需要關注內存的持有和釋放問題,更不需要關注別的模塊是否依然在使用同一個內存。做好自己分內的事情,別的事情交給系統和編譯器!
騰訊Bugly

[總結]

其實,筆者之前對 ARC 的了解也僅僅在 coding 層面,最近打算將老的項目從 MRC 轉到 ARC,需要提前讓團隊的所有人了解代碼如何遷移,否則即便依靠一兩個人的力量將代碼遷移了,開發人員的意識和 coding 依然停留在 MRC,那後續的開發任務將會極其危險。但凡做大的動作就應該首先在團隊層面無論是意識還是能力上做好准備,否則就等著填坑吧。

於是突發奇想,想對蘋果問一個為什麼?即:蘋果為什麼要搞一個 ARC?任何一件事情,都不是毫無來由的。一個極客程序員可能會突發奇想搞個牛逼的技術來展現自己的才華,但蘋果這麼大一個公司,做這麼大的改動,一定是有緣由的。果不其然,當自己費盡心思將這個問題搞清楚之後,如何 coding 的問題也得到了大幅提升!

回頭想想,這條路是很牛逼的,如果所有地方都用強引用,或者所有地方都交予系統管理,勢必會導致內存的快速膨脹。某些其它語言的例子就非常明顯,無論程序員如何努力,內存也很難降低下來。

一個心得就是:許多問題,如果我們能夠站在設計者的立場上考慮,就能夠更加清楚自己該如何 coding,設計者的初衷決定了我們 coding 的方式,設計者的 coding 決定了我們的思維方式。

以下是一個簡單的 demo,從代碼運行結果能夠很明顯的驗證 ARC 下 strong、weak、assign、局部變量、類方法初始化以及 autorelease 等使用方法與MRC下的不同。

首先:使用 retain 類型初始化方法給 weak 和 assign 類型變量賦值時,編譯器會報警。
騰訊Bugly

其次:weak 變量當其指向的變量的所有強引用置零後,自己會被置 nil,而 assign 卻不會。
騰訊Bugly

再有:weak 變量被置 nil,不是當其指向變量析構的時候,而是在強引用歸零的時候就已經發生了。
騰訊Bugly
騰訊Bugly

還有,各種類方法初始化的 autorelease 對象,依然是在 runloop 結束的時候析構的,而 retain 類型的對象,卻是在代碼模塊終止的時候析構的。所以,出於內存管理的考慮,依然建議少用 autorelease。
騰訊Bugly
騰訊Bugly

最後,strong 和 weak 對應的 set 方法,簡單了許多哦!
騰訊Bugly

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved