筆者做爲一個剛接觸 iOS 不久的新手小白,被什麼時候須要使用 weak 弱引用折磨了許久,看了許多文章和書之後總結了 iOS 內存管理的一些相關知識。本文講解比較淺顯,不涉及源碼實現,若想深刻了解 ARC 建議閱讀《Objective-C 高級編程》這本書。
首先,咱們從 C 語言開始簡單瞭解程序內存分佈。一個由 C 語言編寫的程序內存主要由如下5個部分組成:程序員
其中代碼段、數據段、BSS 段在編譯時由編譯器分配空間,而堆和棧是在程序運行時系統分配的空間。編程
棧是用於存放本地變量,局部變量,函數參數值的內存區域。程序在運行時,操做系統會經過壓棧和彈棧的方式來自動的分配和釋放,不須要咱們手動干預。堆是用於存放除了棧裏的東西以外全部其餘東西的內存區域,通常由程序員手動分配和釋放,若程序員不釋放,程序結束時可能由操做系統回收。在 iOS 中,全部的值類型是放在棧空間的,內存分配和回收不須要咱們關心,系統會處理。而全部繼承了 NSObject 的對象都存放在堆裏,須要咱們本身負責分配和釋放。安全
Object-C 和 Swift 爲咱們提供了基於引用計數的內存管理方式。從字面上理解,引用計數器表明「對象被引用的次數」,也能夠理解爲有多少人正在使用這個對象。每一個對象都有本身的引用計數器,系統是根據對象的引用計數器來判斷何時須要回收其所佔用的內存空間的。網絡
所以,當咱們須要持有對象時,爲了保證對象的存在,須要使引用計數器+1;當咱們再也不須要持有對象時,須要使引用計數器-1,以便系統能夠回收其內存空間。函數
在 OC 中,NSObject 提供了 alloc 類方法,retain/release/dealloc實例方法用於內存管理,MRC 就是經過手動調用這些方法來實現對象引用計數的增長和減小。手動管理內存須要遵照如下幾個原則:oop
當程序調用方法名以 alloc/new/copy/mutableCopy 開頭的方法來建立對象時,意味着本身生成的對象只有本身持有,該對象的引用計數+1。spa
當程序調用對象的 retain 方法時,意味着程序持有了非本身生成的對象,該對象的引用計數+1。操作系統
本身持有的對象,一旦再也不須要,不管是不是本身生成的,持有者都有義務釋放該對象。程序經過調用對象的 release 方法釋放該對象,引用計數-1。指針
可是有時候咱們不知道到底何時能夠將對象釋放,例如做爲函數返回值返回時。爲了解決這個問題,OC 提供了 autorelease 方法。code
autorelease是一種支持引用計數的內存管理方式,只要給對象發送一條autorelease消息,會將對象放到一個自動釋放池 autorelease pool 中,而對象自己的引用計數並不增長,相似於 C 語言中局部變量的特性。在程序主循環的 RunLoop 或在其餘程序可運行的地方,會對 release pool 對象進行生成、持有和廢棄處理。當自動釋放池被銷燬時,會對池子裏面的全部對象作一次release操做,從而達到管理引用計數的目的。
對於既不是以 alloc/new/copy/mutableCopy 開頭的方法生成並持有的對象也不是用 retain 方法持有的對象,絕對不能使用 release 方法釋放對象,不然就會形成程序崩潰。
管理引用計數的本質部分在 ARC 中其實並無發生改變,ARC 只是會自動地幫咱們處理引用計數。ARC 是編譯器特性,而不是運行時特性,能夠對每一個文件選擇使用或不使用 ARC,若使用 ARC,編譯器在編譯時會幫咱們自動的插入上一節提到的相關代碼,包括retain/release/copy/autorelease/autoreleasepool等等,經過自動生成的代碼去自動釋放或保持對象。
ARC 中,咱們再也不使用 retain/release/autorelease 方法來管理內存,而是爲每一個變量添加全部權修飾符,系統經過變量的全部權修飾符判斷如何處理引用計數。
__strong 修飾符是 id 類型和全部對象類型默認的全部權修飾符,表示對對象的強引用,對應屬性聲明中的 strong/retain/copy 屬性。在 ARC 中,給被 __strong 修飾符修飾的變量賦值便可達成對對象的持有,而該變量在超出其變量做用域後被廢棄,隨着強引用的失效,其引用的對象也會隨之釋放,從而達到管理引用計數的目的。
__weak 修飾符表示對對象的弱引用,對應屬性聲明中的 weak 屬性。在 ARC 中,弱引用不能持有對象實例,因此在超出其變量做用域時,對象即被釋放,能夠用來避免出現循環引用的問題。在持有某對象的弱引用時,若該對象被廢棄,則此弱引用會自動失效,且處於賦值 nil 的狀態。
__unsafe_unretained 修飾符是不安全的全部權修飾符,對應屬性聲明中的 assign/unsafe_unretained 屬性。在 ARC 中,被 __unsafe_unretained 修飾的變量不屬於編譯器的內存管理對象。
ARC 中雖然不能調用 autorelease,可是能夠經過將對象賦值給附加了 __autorelease 修飾符的變量來替代調用 autorelease 方法,將對象註冊到 autorelease pool。
空指針是指沒有存儲任何內存地址的指針,或是被賦值爲 nil 的指針。在 OC 中,經過空指針訪問空對象或是給空指針發消息都是安全的,不會產生異常或引發崩潰。
而野指針是不安全的。一個已經被釋放的對象,叫作殭屍對象,指向殭屍對象的指針被稱爲野指針,野指針的存在是十分危險的。若是咱們經過野指針去訪問殭屍對象,在原對象空間尚未從新分配出去以前,是不會出現問題的,可是若是空間已經被從新分配(有很大的可能),那麼就會引發程序崩潰。所以對於野指針,咱們須要當心處理。
引用計數式內存管理中必然會發生的問題,就是循環引用問題。若是兩個對象 A 和 B 互相強引用,那麼它們永遠不會被釋放,即便外界已經沒有任何指針可以訪問到它們了,它們依然存在,而且互相引用,沒法被釋放。循環引用容易發生內存泄漏,由於應當廢棄的對象在超出其生存週期後繼續存在。
解決循環引用問題主要有兩個方法:
當咱們知道這裏會產生循環引用時,在合理的位置主動斷開環中的一個引用,打破循環,使得對象能夠被回收。例如 GCD 和 YTKNetwork 網絡請求等,因爲持有 block 形成了循環引用,在運行結束後會採起這種方法在 block 執行完成後,主動釋放對於 block 的持有,將其賦值爲 nil,主動打破循環引用,避免內存泄漏。
// YTKNetwork 中執行完成後對於 block 的處理
- (void)clearCompletionBlock {
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
}複製代碼
不過,主動斷開循環引用這種操做依賴於程序員本身手工顯式地控制,至關於回到了之前 「誰申請誰釋放」 的 MRC 內存管理年代,它依賴於程序員本身有能力發現循環引用而且知道在什麼時機斷開循環引用回收內存,因此這種解決方法並不經常使用,更常見的辦法是使用弱引用的辦法。
弱引用並不持有對象,也就不會增長引用計數,這樣就避免了循環引用的產生。在 iOS 開發中,最多見的弱引用就是 delegate。舉個例子來講,ViewController 持有某個 View, 同時 View 的一些點擊事件須要經過 delegate 的方式交給 VC 來處理。這個時候,View 的 delegate 成員變量一般是一個弱引用,以免相互引用對方形成循環應用的問題。
除了 delegate,還有一個弱引用最常出現的地方是 block。因爲 block 會捕獲 block 中使用的變量,稍一不注意就有可能會出現循環引用的問題。例如,ViewController 持有 block,同時 block 中的代碼又用到了 self,那麼這個時候就會形成循環引用。爲了不循環引用就須要使用弱引用 weakself,最多見的寫法是 weak-strong-dance。
__weak typeof(self) weakself = self;
self.completionHandler = ^(void){
__strong typeof(weakself) strongself = weakself;
// do something with strongself
}複製代碼
須要注意的是,weak-strong-dance 有時會致使 block 中的代碼並無執行,由於有可能在須要執行 block 中的代碼時 self 已經被釋放,弱引用 weakself 被置爲 nil。而且,並非全部的 block 中使用 self 都會形成循環引用,像上文提到的,一些類會在執行完成後主動釋放,所以咱們在使用前須要特別注意是否會引發循環引用,再來使用 weak-strong-dance。