0x00 堆疊
- 堆(heap):由程式猿負責分配和管理,儲存OC物件,比方說繼承自
NSObject
的所有物件,這些物件都是引用型別。 - 棧(stack):由系統負責管理。儲存如
int
,float
等資料型別,這些物件都是值型別。棧的效率會相對較高。
0x01 物件分配和釋放
記憶體管理方式
Apple提供了兩種記憶體管理方式:
- MRR(Manual retain-release), 手動保留釋放。通常在執行時環境中使用。
- ARC(Automatic Reference Counting), 自動引用計數。在編譯時使用。使用ARC的話,相當於將記憶體的管理下放到系統,我們只需關心物件的實現,而無需關心物件的釋放與否的問題。(基本情況下不用care)。
記憶體管理原則
- 你持有自己所擁有的物件:通過使用
alloc
,new
,copy
或者mutableCopy
等方法命名的方法建立的物件,像stringWithFormat:
等方法建立的物件並不能被持有 - 使用
retain
來獲取物件的擁有關係:retain
使用場景有兩個: 1)在訪問器的方法實現或者是init
方法中,獲取想要的物件,並將其作為屬性值 2)避免其他操作對該物件的造成的影響 - 在不使用該物件之後,必須捨棄掉該物件:傳送
release
或者autorelease
的訊息給該物件 - 不能釋放非自己持有的物件!
release
和autorelease
傳送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一個物件會建立該物件的強引用。
迴圈引用的解決
迴圈引用的一種解決方式是使用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
,mutableCopy
和new
打頭的方法 - 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
:
- 比方說像命令列工具等不基於UI framework的程式
- 在自己建立的loop中建立了很多臨時物件
- 生成輔助執行緒:這種情況的話必須線上程執行時建立自己的
autoreleasepool
block, 否則會造成記憶體洩漏
0x07 Other
- 一般情況下,
alloc
和release
配套使用