0x01 對象生命週期
對象的生命週期包括誕生(通過alloc或new方法實現)、生存(接收消息並執行操作)、交友(通過複合以及向方法傳遞參數)以及最終死去(被釋放)。
生命週期結束後,它們所佔用的內存將被回收並被新的對象使用。
0x02 基本的內存分配規則
Objective-C的對象都是在堆裏面生成,然後有一個指針指向它,使用完後不會自動銷燬,需要執行dealloc來銷燬,否則就會出現內存泄漏:
ClassA *obj1 = [[ClassA alloc] init]; //alloc
[obj1 dealloc]; //dealloc
0x03 引用計數
Cocoa採用了引用計數(Reference Counting)或稱爲保留計數(Retain Counting)的技術來表示對象的生命。
每個對象都有一個與之相關聯的整數作爲引用計數器。
當某段代碼需要訪問一個對象時,該代碼就將該對象的引用計數器值+1,表示“我需要訪問該對象”;
當某段代碼結束對象訪問時,該代碼就將該對象的引用計數器值-1,表示“不再訪問該對象”;
當對象的引用計數器值爲0時,表示“不再有代碼訪問該對象了”,因此該對象將被銷燬,其佔用的內存資源將被釋放。
獲得引用計數器的值
可以發送retainCount消息來獲得引用計數器的值:
- (NSUInteger) retainCount;
當使用alloc、new方法或者通過copy消息(接收到消息的對象會創建一個自身的副本)創建一個對象時,對象的引用計數器被置爲1。
要增加對象引用計數器的值,可以給對象發送一條retain消息,retain消息返回的是一個id類型的值:
- (id) retain;
要減少對象的引用計數器值,可以給對象發送一條release消息:
- (oneway void) release;
執行release方法釋放的是一塊內存區域的內容,而不是僅僅切斷對象和該內存區域的聯繫!
示例程序:
@interface RetainTracker : NSObject
@end // RetainTracker
@implementation RetainTracker
- (id) init
{
if (self = [super init]) {
[self retainCount]);
}
NSLog (@"init: Retain count of %d.",
return (self);
} // init
- (void) dealloc
{
NSLog (@"dealloc called. Bye Bye.");
[super dealloc];
} // dealloc
@end // RetainTracker
int main (int argc, const char *argv[])
{
RetainTracker *tracker = [RetainTracker new]; // count: 1
[tracker retain]; // count: 2
NSLog (@"%d", [tracker retainCount]);
[tracker retain]; // count: 3
NSLog (@"%d", [tracker retainCount]);
[tracker release]; // count: 2
NSLog (@"%d", [tracker retainCount]);
[tracker release]; // count: 1
NSLog (@"%d", [tracker retainCount]);
[tracker retain]; // count 2
NSLog (@"%d", [tracker retainCount]);
[tracker release]; // count 1
NSLog (@"%d", [tracker retainCount]);
[tracker release]; // count: 0, dealloc it
return (0);
} // main
當一個對象因其保留計數器歸0而即將被銷燬時,Objective-C會自動向對象發送一條dealloc消息。
我們可以自己在對象中重寫dealloc方法,這樣就能釋放掉已經分配的全部相關資源。
但一定不要直接調用dealloc方法,因爲Objective-C會在需要銷燬對象的時候自己調用它!
0x04 對象所有權
如果一個對象內有指向其他對象的實例變量,則稱該對象擁有這些對象。
一般來說,我們說某個實體“擁有一個對象”時,其實也意味着該實體要負責清理其擁有的對象所佔用的資源。
當多個實體擁有某個特定對象時,對象的所有權將變得很複雜,這也是引用計數器的值大於1的原因。
所有權變得複雜起來,就可能出現下列情況:
ClassA *obj1 = [[ClassA alloc] init]; // count = 1
[obj1 release]; // count = 0, 自動dealloc
ClassA *obj1 = [[ClassA alloc] init]; // count = 1
ClassA *obj2 = obj1; // count = 1, 指針賦值不會增加引用計數
[obj1 hello];
[obj1 release]; // count = 0, 自動dealloc
// 因對象已被dealloc, 故以下代碼不會執行, obj2仍然是無效指針:
[obj2 hello];
[obj2 release];
正確的做法是,凡是訪問方法,都先保留新對象,再釋放對象:
ClassA *obj1 = [[ClassA alloc] init]; // count = 1
ClassA *obj2 = obj1; // count = 1
[obj2 retain]; // count = 2,先爲新對象做保留
[obj1 hello];
[obj1 release]; // count = 1
[obj2 hello];
[obj2 release]; // count = 0, 自動dealloc
按照這個規則,我們可以編寫setEngine方法的內存管理版本:
- (void) setEngine: (Engine *) newEngine
{
[newEngine retain]; //保留新對象
[engine release]; //釋放對象
engine = newEngine;
} // setEngine