Bugly 技術乾貨系列內容主要涉及移動開發方向,是由 Bugly 邀請騰訊內部各位技術大咖,經過平常工做經驗的總結以及感悟撰寫而成,內容均屬原創,轉載請標明出處。
對於iOS程序員來講,內存管理是入門的必修課。引用計數、自動釋放等概念,都是與C語言徹底不一樣的。搞明白這些,代碼纔有可能不 crash。然而就是這麼牛逼的內存管理,着實讓我這個從 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++ 轉過來的程序員都遇到過相似的迷惑和憤怒。函數
那麼,蘋果爲何要作這個?工具
首先,C/C++ 傳統的內存管理方式,全部的內存都須要業務代碼本身處理,程序員本身必定要知道一個內存對象何時再也不使用了,必定要知道這個內存對象的終點在哪裏。當代碼愈來愈複雜,參與開發的程序員愈來愈多,甚至隨着歲月的流逝更換了新的程序員,這個時候,很難有人說的清了。因而,要麼那個內存對象一直留在那裏,沒人敢釋放,整個程序佔用的空間愈來愈大;要麼,一個膽大的程序員將它釋放掉,某處發生了 crash。儘管你們總結出許多相似「誰建立誰釋放」、「誰持有誰釋放」 的原則,但都致使存儲空間的浪費:爲了保留僅僅一個內存對象,卻要將與它關聯的一大堆對象保留住,而其中大部分已經再也不使用了。要麼,本身寫許許多多的代碼,頻繁對容器進行主動操做。
oop
因而,蘋果要解決這個問題。初衷就是:任何一個內存對象由系統本身處理釋放的問題,不管建立者也好,持有者也好,不須要去考慮別人是否還在使用同一個內存對象,作好本身該作的就是了,別人的事情別人負責。蘋果實現此目的的手段就是引用計數。全部使用到同一內存對象的地方,使用者只要保證本身 retain 一次,release 一次,就 OK 了,即使別人還在使用,你只要調用 release 將本身的引用次數清零就行了,不用管別人!性能
與 C/C++傳統的內存管理方式相比,MRC 是否是顯得很是智能?是否是更加方便?並且,這樣作的代價也很是低廉,每個內存對象增長一個計數器就 OK 了,每一次 release,只須要檢查一遍計數器是否爲零,若是爲零就釋放,若是不爲零就不執行真正的釋放邏輯。編碼
另外,爲了解決函數返回值的問題,須要搞一個 autorelease 的東西,不然就會打破這個良好的初衷:「只負責本身範圍內的事情就 OK了,不要管別人!」spa
那麼,爲何不將全部內存對象都統一成 retain呢?對於一種編譯器,它可以用一個技術解決全部問題,就堅定不會用兩種並列的技術致使問題更復雜。操作系統
OC 有一個 delegate 的東西,這個東西的出現也是有其現實需求的,在此先跳過。若是全部地方都使用 retain,delegate 的問題必定會致使循環引用,除了 delegate,蘋果不敢保證全部用戶代碼的邏輯都是樹形結構的,最簡單的好比說循環鏈表、雙向鏈表,除此以外,業務層確定也有某些地方必須作成「循環引用」,若是都是 retain,那麼,最終處於循環中的內存對象誰也不會被最終釋放掉。爲了解決這個問題,蘋果依然保留了 C/C++的那種弱引用方式。——至少給程序員留個過渡的空間。
總結一下:
MRC 的計數器機制改善了內存管理的方式,減小了各個模塊的邏輯耦合,釋放了程序員對「什麼時候該釋放」的心理壓力,解決了大部分的問題
爲了應對各類複雜的場景,很無奈的留了一個口子;
兩種模式的並存,對 C++程序員轉移到 OC戰場,樹立了一個無形的心理門檻,使得起步階段問題更加複雜,好比:retain、assign、release、autorelease 等。
難道就沒有更好的方式麼?固然有更好的方式,並且必定有許多公司的 C++程序員或者 C 程序員寫了相似引用計數的程序,甚至比引用計數還要高級,只不過大多數公司沒有實力推廣一個編程語言而已。
並且,略微深刻思考,必定許多人想到:若是讓系統對全部內存對象在運行時統一管理,問題就能完全解決了。是的,的確如此,必定有人設計出來了。可是,代價比較高。
系統在運行時統一管理全部內存對象的釋放,會致使增長額外的內存和 CPU 開銷,在硬件設備尚且處於低級階段的時候,當程序員們依然在努力下降內存下降 CPU 消耗的時候,推出這樣的機制,是不合時宜的!
引用計數器的方式,編譯器並無增長太多的邏輯,只是在建立的時候增長一個計數器,在釋放的時候編譯器自動幫程序員增長一個邏輯判斷。這個邏輯並無增長太多的內存和 CPU 開銷。
再來看 autorelease,這個邏輯增長的成本可就大了去了,系統要一直持有該類型的內存對象,直到本次 runloop 結束。因此,不管蘋果,仍是有經驗的程序員,都建議:能不用就儘可能不用,能縮短範圍就儘可能縮短範圍。
因爲留了無奈的口子,野指針依然會出現,該 crash 的時候依然 crash。許多人說:這是程序員的問題,若是代碼寫的足夠好,必定不會出現野指針,必定不會出現 crash。是的,若是你們足夠當心,若是你們足夠盡力,這個世界上不會有任何衝突。
然而,編程語言和編譯器的發展,必定向着便利、易用、穩健、職能,甚至傻瓜!若是一個編譯器可以讓一個對計算機毫無瞭解的人一天以內搞出本身想要的業務應用,誰又會拒絕呢?
許多程序員都是技術控,本身能作的事情儘可能不讓別人作,本身能實現的邏輯儘可能不用別人的。好比:C++的各類封裝、引用,我用 C 也能實現,有什麼大不了的!系統提供的各類類庫,我本身用底層的代碼也能實現,並且性能更優,代碼更少!可是,若是你連一個磚頭都要本身燒製,連一堵牆都要本身去砌,其它更重要的事情誰去作?
更況且,人,總有打盹的時候。
隨着硬件的升級,條件已經成熟了,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!最終,程序員不須要關注內存的持有和釋放問題,更不須要關注別的模塊是否依然在使用同一個內存。作好本身份內的事情,別的事情交給系統和編譯器!
其實,筆者以前對 ARC 的瞭解也僅僅在 coding 層面,最近打算將老的項目從 MRC 轉到 ARC,須要提早讓團隊的全部人瞭解代碼如何遷移,不然即使依靠一兩我的的力量將代碼遷移了,開發人員的意識和 coding 依然停留在 MRC,那後續的開發任務將會極其危險。但凡作大的動做就應該首先在團隊層面不管是意識仍是能力上作好準備,不然就等着填坑吧。
因而突發奇想,想對蘋果問一個爲何?即:蘋果爲何要搞一個 ARC?任何一件事情,都不是毫無來由的。一個極客程序員可能會突發奇想搞個牛逼的技術來展示本身的才華,但蘋果這麼大一個公司,作這麼大的改動,必定是有原因的。果不其然,當本身費盡心思將這個問題搞清楚以後,如何 coding 的問題也獲得了大幅提高!
回頭想一想,這條路是很牛逼的,若是全部地方都用強引用,或者全部地方都交予系統管理,勢必會致使內存的快速膨脹。某些其它語言的例子就很是明顯,不管程序員如何努力,內存也很難下降下來。
一個心得就是:許多問題,若是咱們可以站在設計者的立場上考慮,就可以更加清楚本身該如何 coding,設計者的初衷決定了咱們 coding 的方式,設計者的 coding 決定了咱們的思惟方式。
如下是一個簡單的 demo,從代碼運行結果可以很明顯的驗證 ARC 下 strong、weak、assign、局部變量、類方法初始化以及 autorelease 等使用方法與MRC下的不一樣。
首先:使用 retain 類型初始化方法給 weak 和 assign 類型變量賦值時,編譯器會報警。
其次:weak 變量當其指向的變量的全部強引用置零後,本身會被置 nil,而 assign 卻不會。
再有:weak 變量被置 nil,不是當其指向變量析構的時候,而是在強引用歸零的時候就已經發生了。
還有,各類類方法初始化的 autorelease 對象,依然是在 runloop 結束的時候析構的,而 retain 類型的對象,倒是在代碼模塊終止的時候析構的。因此,出於內存管理的考慮,依然建議少用 autorelease。
最後,strong 和 weak 對應的 set 方法,簡單了許多哦!
若是你以爲內容意猶未盡,若是你想了解更多相關信息,請掃描如下二維碼,關注咱們的公衆帳號,能夠獲取更多技術類乾貨,還有精彩活動與你分享~
騰訊 Bugly是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!