0

Delegate

| 11/26/2011
Delegation 是物件導向 Design Pattern 的一種。

這種設計原則的作法,是讓 reciver 授權委託給 delegatee 物件來處理所接收到的訊息。如此一來,就可以模糊化 reciver 介面設計。這種設計方法大量被 Cocoa Framework 所使用,原因就在於將所屬職責委託出去,可以減少 reciver 負擔工作的種類,並且可以因為 delegatee 的設計的適應各種不同的狀況,增加程式設計上面的彈性。

在這裡舉一個例子:今天,SDK 裡面設計了表格物件。表格大概是我們最常見到用來分類、統整項目的方法。可是,根據需求不同,表格顯示資料的過濾方式、算法、資料的取得都會有所不同。設計表格物件的設計師不可能有辦法先把所有的狀況建立在表格物件裡面。所以設計便成為,當表格需要顯示資料的時候,它會向表格所授權的 delegatee 要求、取得資料之後,再自行顯示。

下面用一個比較簡單的實作例子,我們要求一個區塊物件,去繪製一個圖型:

@protocol FrameDelegate
@required
- (void)draw;
@end

@interface Frame : NSObject {
    uint32 bound_x;   // 相對整個畫面的座標
    uint32 bound_y;
    uint32 width;    // 框架的長、寬
    uint32 height;

    id delegate;
}
- (void)draw; 
- (void)setDelegate:(id <FrameDelegate>)obj;
@end

我們可以看到,Frame 物件具有一個方法:draw。這個方法可以在框架所決定的區塊裡面繪製東西,可是我們並不知道到底要會製成什麼東西,所以我們就建立兩個物件:

Shape <----- Circle
Shape <----- Square

我們這裡,Shape 可以當做是一個母物件,並且遵守 FrameDelegate 協定,必須要有 draw 方法。如此一來,框架的實作本身就可以很簡單的:

@implementation
... (do something)

- (void)draw {
// some work here
    [delegate draw];
}
... (do something)
@end



在實際應用上面,就成了以下這個樣子:

Frame *f1;
Frame *f2;
Circle *c;
Square *s;

[f1 setDelegate:c];
[f2 setDelegate:s];

[f1 draw];   // 畫出圓形
[f2 draw];   // 畫出方形


Delegate 是 Cocoa Framework 裡面常用的技法,這得要記得喔!
0

Retain Count ~ Auto Release Pool

| 11/23/2011
Retain Count

繼上一篇文章所述,Retain Count 是我們管理 Objective-C 程式記憶體使用最重要的一環。但是我們不難發現,其實如果真的所有的東西都必須經過 alloc、init、retain、release 的動作,會讓整篇程式上上下下充斥著這些程式碼,反而降低了整個程式的閱讀性、也提高了出錯的危險。這裡,Auto Release Pool 這個設計就出現了。從字面上看來,它可以自動的幫我們釋放掉記憶體,但其實它並沒有像是垃圾回收那樣複雜的演算法存在。它其實是一個聰明、卻又單純、簡單的設計,並且解決上面所述造成程式碼撰寫的複雜度、並且同時提高程式在編成的時候的彈性。

我們想像記憶體的空間是個游泳池,而建立的物件實體就像是游泳池的遊客。遊客在游泳池開門之後開始戲水,當游泳池要打烊的時候,遊客就會離開。

這個想法運用在這邊就是,我們先建立一個 NSAutoreleasePool 的物件。之後,我們只要在所有自己建立的物件實體,呼叫方法 -(void)autorelease,便可以讓該物件與 AutoreleasePool 做連結。Pool 也不做什麼複雜計算、搜尋,只是單純的把該物件實體記錄下來。當 Pool 被 Release 掉的時候,Pool 便將所有跟他連結在一起的物件,全部個別的呼叫「一次-(void)release。如此一來,該物件就不需要擔心,什麼時候該釋放它,Pool 會幫我們管好一切。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];


OBJ *o1 = [[[OBJ alloc] init] autorelease];
OBJ *o2 = [[[OBJ alloc] init] autorelease];


// Do Something


[pool release];


這個機制最重要的運用是,由於所有的 Cocoa 應用程式都至少會建立一個 Autorelease Pool。我們可以開始取得一些「暫時物件」的實體,並且讓系統在適當的時候,自己釋放掉那些暫時物件。最好的例子莫過於字串。我們可以用以下的程式碼取得一個字串物件的實體:

NSString *str = @"Some Text Message";
NSString *str2 = [NSString stringWithFormat:@"Number: %d", 50];

上面兩種取得字串的方式,便是包含了 Auto Release 機制在的。

由於系統本身已經預先建立好一個 Autorelease Pool,所以當後者 @"Some Text Message"; 被使用的時候,系統便會使用 alloc->init->autorelease 一連串的方法建立一個物件實體,並且把實體的位置回傳。我們不需要額外的程式碼便可以得到想要的物件。這個原則在物件、實體方法 (methods) 回傳值的時候非常的有用。使用者使用你所設計的方法取得的實體並不需要在多花時間去考慮是否要 release。

但是,這時又會產生一個疑問:相同的都是取得實體,我該怎麼知道哪些是需要 release,哪些不需要?沒錯,這確實是一件很重要的事情。因為當一個物件實體的 Retain Count 降為 0 之後,它就會呼叫 dealloc,並且摧毀。從此之後,如果又有人(不論是你自己,還是 Autorelease Pool)呼叫了實體方法 release,便會因為實體根本不存在而導至崩潰。

要知道究竟是哪一個物件需要手動 release,我們只需要掌握一條原則:所有權在你自己身上的,你自己釋放,其餘的交給 Autorelease Pool。什麼樣的實體,所有權會在自己身上?很簡單。你取得實體的方法,具有幾個關鍵字:init、retain、copy。只要是其中一項,你便具有該物件的所有權,你就得要自己手動 release。

清楚了嗎?
9

Retain Count

|
Retain Counting 是 Cocoa 程式設計不可不學的一個記憶體管理機制。一個好的程式員,在對系統記憶體運用以及管理上面做的要是不佳,就很容易出現效能、漏洞、程式崩潰等問題。

所有的 Objective-C 物件都必須要由指標來建立。一般我們很習慣在 C 語言裡面,使用 malloc 產生一塊可以讓指標所指向的「值」。在物件導向設計裡面,我們簡單的稱這個「值」叫做「實體」(instance)。蘋果在設計 Cocoa 的基礎物件 NSObject 裡面,設計了一個簡單的物件方法 (Object Method): +(id)alloc,可以讓產生一個鄉對應物件所需要的一塊實體的記憶體空間,並且回傳實體的指標。當實體被成功建立之後,我們必須要使用 NSObject 實體方法 (Instance Method) 的 -(id)init 來初始化它。這一條基本規則建立起 Retain Counting 所需具備的一切條件。所有繼承 NSObject 的子孫物件都必須要遵守這一條規則。

OWNObject *val;
// call object method    [object_name method_name]
val = [OWNObject alloc];
// call instance method  [var_name method_name]
val = [val init];

Retain Counting 的機制很簡單,物件建立之後,所有繼承 NSObject 的物件都會有一個 retainCount 的 變數 (ivar) 這個變數會在方法 -(id)init 的時候變成 1,-(void)retain 的時候增加, -(void) release 的時候減少。當這個數值變成 0 的時候,物件會自動呼叫 -(void)dealloc,並且將記憶體釋放出來。


// retain count = 1
val = [val init];
// retain count = 2
[val retain]
// retain count = 1
[val release]
// retain count = 0
[val release] -> call [self dealloc] automatically


!記住,千萬不可以自己去呼叫 -(void)dealloc,程式會崩潰!

講到這裡,會有很多人覺得這個跟 Java 的 Reference Counting 的機制不是一樣嗎?其實是不同的。在這裡,記憶體的分配、釋放都是由物件:也就是應用程式本身來執行,而 Java 的 Reference Counting 則是由 Garbage Collocation Daemon 一段時間去掃描所有在記憶體中的物件,找出 Reference Count = 0 的實體並且釋放。這裡雖然遺留了 C 語言在動態記憶體分配之後,程式設計師必須要自己管控記憶體的工作,也有些現代程式語言的精巧。

雖然說蘋果在最新的 LLVM Compiler 3.0 裡面,新增了 Auto Retain Count 的機制,可以讓編譯器幫助你決定在程式碼的哪一個部位 retain、release。但是自動化機制總是會犧牲掉一般程式設計師可以掌握的彈性。我認為理解清楚這個機制,可以有助益於任何記憶體管理方面的進步!