iOS記憶體管理相關

kim_jin發表於2017-12-26

0x00 堆疊

  • 堆(heap):由程式猿負責分配和管理,儲存OC物件,比方說繼承自NSObject的所有物件,這些物件都是引用型別。
  • 棧(stack):由系統負責管理。儲存如int, float等資料型別,這些物件都是值型別。棧的效率會相對較高。

0x01 物件分配和釋放

記憶體管理方式

圖片來源https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html

Apple提供了兩種記憶體管理方式:

  1. MRR(Manual retain-release), 手動保留釋放。通常在執行時環境中使用。
  2. ARC(Automatic Reference Counting), 自動引用計數。在編譯時使用。使用ARC的話,相當於將記憶體的管理下放到系統,我們只需關心物件的實現,而無需關心物件的釋放與否的問題。(基本情況下不用care)。

記憶體管理原則

  1. 你持有自己所擁有的物件:通過使用alloc, new, copy或者mutableCopy等方法命名的方法建立的物件,像stringWithFormat:等方法建立的物件並不能被持有
  2. 使用retain來獲取物件的擁有關係:retain使用場景有兩個: 1)在訪問器的方法實現或者是init方法中,獲取想要的物件,並將其作為屬性值 2)避免其他操作對該物件的造成的影響
  3. 在不使用該物件之後,必須捨棄掉該物件:傳送release或者autorelease的訊息給該物件
  4. 不能釋放非自己持有的物件!

releaseautorelease

傳送release訊息的話,會馬上將物件釋放掉。如果想要延遲釋放物件的話,可以使用autorelease.

通過引用返回的物件並不能被持有。

使用dealloc來捨棄物件的持有

這裡需要注意的一點是不能直接呼叫其他物件的dealloc方法

不要再init或者dealloc方法中使用訪問器方法

蘋果在官方文件中提到,不要在初始化或者是釋放方法中使用Accessor Methods(這可能是在MRC階段的,蘋果的記憶體管理文件是比較久之前,不過這是個人理解,有待驗證)。所以在初始化的方法中,應該這樣:

- (instancetype)init {
  self = [super init];
  if (self) {
    _count = [[NSNumber alloc] initWithInteger: 0];
  }
  return self;
}
複製程式碼

Core Foundation

對於一般的OC物件,只需要使用ARC來進行計數實現記憶體管理即可。對於底層的Core Foundation來說,則需要呼叫CFRetain()CFRelease()這兩個方法來增減引用計數。

OC物件和Core Foundation物件的轉換通過bridge關鍵字進行轉換。

  • __bridge: 只進行型別轉換,不修改引用計數
  • __bridge_retained: 型別轉換後引用計數加1
  • __bridge_transfer: 型別轉換後將物件的引用計數交給ARC管理

weak

weak引用是一種non-owning的關係,所以並不會retain物件。

宣告為weak的變數可以防止迴圈引用造成的記憶體問題,因為如果物件沒有了強引用的話,就會被置為nil。這一點需要跟unsafe_unretained區分開來。unsafe_unretained的物件如果沒有了強引用,並不會被置為nil。如果物件被回收或者銷燬的話,該指標就會變為野指標。

retain一個物件會建立該物件的強引用。

迴圈引用的解決

圖片來源:Advanced Memory Management Programming Guide

迴圈引用的一種解決方式是使用weak引用。如圖中所示,Cocoa有一個約定,就是父物件對其子物件是強引用,子物件則是對父物件弱引用。用上面的例子來說的話,就是Page物件強引用了Paragraph, Paragraph弱引用了Page.

傳送訊息給已經釋放的物件的話,程式會crash,這點需要跟傳送訊息給nil進行區分。OC是允許傳送訊息給nil的。

集合型別retain其包含的物件

如Array, Dictionary和Set等集合物件,retain其object,在移除物件或者集合物件被釋放的時候才會將該object捨棄。

0x02 野指標錯誤

野指標導致的錯誤通常在Xcode中表現為:Thread 1: EXC_BAD_ACCESS(code=EXC_I386_GPFIT)錯誤。原因是訪問了一塊已經釋放或者不屬於你的記憶體。

0x03 Retained/Unretained return value

  • Retained return value: 表示呼叫者擁有這個返回值,需要負責釋放。比方說alloc, copy, init, mutableCopynew打頭的方法
  • Unretained return value: 呼叫者不擁有這個返回值。如[NSString stringWithFormat:]這樣的方法

0x04 記憶體洩露

記憶體洩露有兩種型別:

  • Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).
  • Abandoned memory: Memory still referenced by your application that has no useful purpose.

其中第一種可以使用Leak工具來檢測,第二種使用Allocations來檢測

0x05 Block的分配與釋放

Block一開始是釋放在棧中,但是在執行時會呼叫Block_copy方法將block物件複製到堆中,並且將引用計數加1,返回新的block指標(如果block物件已經存在於堆中的話,則引用計數加1)。而Block_release方法則是將引用計數減一,或者在計數為0時將物件釋放摧毀。

#0x08 Autorelease Pool Block

@autoreleasepool {
  // Code
}
複製程式碼

一般在下面三種情況下需要使用autoreleasepool

  1. 比方說像命令列工具等不基於UI framework的程式
  2. 在自己建立的loop中建立了很多臨時物件
  3. 生成輔助執行緒:這種情況的話必須線上程執行時建立自己的autoreleasepool block, 否則會造成記憶體洩漏

0x07 Other

  1. 一般情況下,allocrelease配套使用

參考文章傳送門

  1. Objective-C 中的記憶體分配

相關文章