現在搞iphone開發,一直不是很懂object-c的內存管理機制,看到apple的官方文檔寫的不錯而又沒有找到翻譯的文章。於是自己在學習它的過程中就順便把它翻譯了,自己的英語不是太好,文字組織能力那就更菜了,讀的蹩腳之處還望大家指出,我好在以後的翻譯過程中好好改正。第一次翻譯,歡迎拍磚,可不要把我拍死了呀!!!
文章中帶有LPSTUDY的字樣表明是我個人的理解,可能會有不對的地方,敬請指教。
實用內存管理
雖然在內存管理策略一節中的很多基本的概念都很明確了,但是你仍然可以采用一些很實用的步驟來更容易的管理內存,而且可以使你的程序更可靠和健壯,同時還可以減少它的資源需求。
使用存取方法使內存管理更容易
假設你的類有一個對象屬性,你就必須確保當你在使用它的時候,它不能被釋放掉。你也必須在它被賦值的時候聲明所有權,同時確保釋放了所有權。
很多時候這是一件枯燥無味的事情,但是如果你一致的使用存取方法,那麼你遇到內存管理的問題的可能性就會大大降低了。如果你在代碼中對實例變量使用了retain和release方法,那麼幾乎可以確定,你在做一件錯誤的事情。
現在你想設置一個Counter對象的count屬性,代碼如下:
[cpp]
@interface Counter : NSObject {
NSNumber *_count;
}
@property (nonatomic, retain) NSNumber *count;
@end;
property屬性聲明了兩個存取方法。通常情況下,你應該要求編譯器去合成(synthesize)此方法,但是,通過看看它們的實現代碼會對你有所幫助。在“get”方法中,你僅僅需要返回實例變量,因此沒有必要retain或者release
[cpp]
- (NSNumber *)count {
return _count;
}
在“set”方法中,如果其他的人也遵循相同的規則,那麼 新的count變量可能會在某一個時刻被釋放掉。為了保留此對象,獲取它的所有權,你就必須調用retain方法。同時你也必須向它發送release消息,釋放它擁有的舊的對象的所有權(object-c中允許對nil發送消息)。你必須先調用[newCount retain] 以防你是在進行自己對自己賦值。如果先釋放的話,retain操作就沒有意義了。
[cpp]
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
使用存取方法來設定屬性值
例如你向實現一個重置counter對象的方法。你有很多選擇,第一種實現方式是用alloc創建NSNumber實例,然後釋放它。
[cpp]
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[self setCount:zero];
[zero release];
}
第二種方法是使用一種更方便的構造方法來創建NSNumber對象。於是就沒有必要retain或者release消息了。
[cpp]
- (void)reset {
NSNumber *zero = [NSNumber numberWithInteger:0];
[self setCount:zero];
}
請注意上面的兩種方式都使用了“set”方法
下面的例子對於簡單的情況也可以正常工作。但是正如它的目的是去避免存取方法一樣,這樣做在一些情況下總會導致錯誤(例如,忘記了retain或者release,或者如果實例變量的內存管理語義改變了)
[cpp]
- (void)reset {
NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
[_count release];
_count = zero;
}
Notealso that if you are using key-value observing, then changing the variable inthis way is not KVO compliant.(我現在還不懂kvo,不知道什麼叫kvo)
不要在init和dealloc方法中使用存取方法
唯一你不能通過使用存取方法來設定變量的地方是在inializer方法和dealloc方法。為了初始一個數字對象為0,你可以在init方法中這樣實現:
[cpp]
- init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
為了能夠用count去初始化一個counter對象,你可以實現一個initWithCount方法:
[cpp]
- initWithCount:(NSNumber *)startingCount {
self = [super init];
if (self) {
_count = [startingCount copy];
}
return self;
}
LPSTUDY: 這是初始化方法,因此不用先release。我自己也不是很懂它為什麼要調用copy方法,而不是調用retain方法。Copy相當於自己再申請一塊擁有相同數據的內存,而retain相當於獲取了原來數據的所有權。如果你知道,請留一下言,謝謝。
使用弱引用來避免retain循環
retain 一個對象會對這個對象創建一個強引用。直到一個對象所有的強引用被release了,此對象所占有的內存才會被釋放。於是有一種問題,被稱作“retaincycle”產生了。
LPSTUDY:
這個很容易理解,就特別像操作系統中的死鎖,a引用b,b引用c,c再引用a,然後形成了一個環。或者咱們再簡單一點,a中有b,b中有a。現在我想釋放a,那麼我必須先釋放b,而我要釋放b,我就必須先釋放a,這樣就死鎖勒,誰也釋放不了,不知道這樣說是不是會容易理解一點。如果想仔細弄清楚引用循環的話,這個估計需要大家去仔細搜索一下相關的技術文檔了。
在圖標1中展示了一個潛在的引用循環。Document類對於它的每一個Page,都有一個Page對象。每一個Page類中也有一個屬性來指明其所在的Document。如果Document對象擁有指向Page的強引用而且Page類也擁有一個指向Document類的強引用,那樣的話就沒有對象可以被釋放了。Document的引用計數直到所有的Page對象釋放完了才可以為0,自己才能釋放。對於Page類,同樣的,需要所有的Document對象釋放後太才可以釋放,於是就死鎖了。
圖1:循環引用的示例圖
retain cycle的解決辦法是使用弱引用,弱引用指的是是一個非擁有關系,也就是說擁有一個對象的引用並沒有擁有retain這個對象。
為了保持對象圖之間的聯系,強引用是必要的。因此,Cocoa采用的一種約定:“parent”對象應該擁有對其“children”的強引用,“children”應該擁有對其“parents”的弱引用。
因此,對於圖標1來說,Document類擁有對其Page的強引用,Page擁有對Document類的弱引用。
Cocoa中弱引用的例子存在於但是並不局限於像table的數據源,大綱視圖條目,通知類觀察者,以及多種多樣的目標和委托中。
當你想發送消息到一個弱引用的對象時,你必須非常非常小心。如果此對象已經被釋放了,程序會崩潰。因此,你必須清楚的知道這個對象什麼時候是有效的。在大多數情況下,被弱引用的對象能夠感知所有對其進行弱引用的對象,當此對象被釋放的時候,它需要負責向引用它的對象發送通知。例如,當你向通知中心注冊了一個對象,通知中心存儲了對這個對象的弱引用,而且在合適的通知發出的時候向這個對象發送消息。如果這個對象被釋放,你需要向通知中心取消注冊來阻止向這個對象發送消息。同樣的,如果一個委托對象被釋放了,你需要通過向其他的對象發送setDelegate:nil來刪除所有的委托鏈接。這樣的方法通常是在委托對象的dealloc方法中做。
LPSTUDY:
個人理解(我還沒有自己做過委托類,oc剛學,感覺這個流程最符合事實上的邏輯,就這樣寫了。如果哪個地方不對,希望大家看的時候能夠指出來,我再改正一下,希望不會因為我的錯而誤認子弟):a想委托b去干活,在a中可以調用setDelegate:b這樣的方法來設置b為a的委托,那麼這樣a就可以委托b了,a把自己要干的活都扔給b,由b去完成。a要想讓b干活,b必須首先是活著的,因為a並不知道b什麼時候死掉了,於是b必須在它死掉之前告訴a自己死掉了,不要再委托我了。b告訴自己掛掉的消息是b在自己的dealloc中調用a的setDelegate:nil方法來實現的。
不要釋放你正在使用的對象
cocoa的對象擁有策略能夠確保你在調用的方法中的整個生存期中接收到的對象都是有效的。同樣的,你也可以返回這個接收到的對象(不是很通順,不知道received objects具體該怎麼翻譯)。這種情況對於你是返回實例變量還是運算結果並不重要。重要的是當你在使用它的時候,對象必須是有效的。
有一些例外的場合,下面是其中的兩個。
1. 當一個對象從基礎收集類collection classes中刪除掉的時候
[cpp]
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.
當一個對象從基礎收集類中remove的時候,它是發送了一個release方法,而不是autorelease方法。如果此收集類是這個對象僅有的擁有者,那麼被remove的對象(像本例中heisenObject)就會立刻被釋放掉。
2. 當父對象被釋放了
[cpp]
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.
為了阻止這樣的情況發生,你需要retain一下這個對象。當你結束使用它的時候,release它。例如:一些場合中你從另一個對象中獲取了一個對象,然後release了父對象。如果在release父對象觸發了父對象的dealloc方法,而且此父對象是孩子的唯一擁有者,那麼這個孩子(本例中如heisenObject)會被釋放掉(父對象的dealloc方法中是向子類發送release消息)
[cpp]
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];