1

Shared Instance

| 12/03/2011
寫一些簡單的東西好了,這邊簡單的介紹一個 Design Pattern:Singleton。
簡單一點的說法,就是「分享實體(Shared Instance)」。

這個 Design Pattern 是用來確保程式中某些物件實體只能有一個、或是數個,不應該被重複性的建立。打個比方來說,程式本身的實體、系統服務(資源)的實體,都是很好的例子。為了要達到這個目的,我們會為該物件建立單一的存取窗口:

@interface MyObj : NSObject
+ (id)sharedInstance;
@end


@implementation MyObj


+ (id)sharedInstance
{
    // 物件實體靜態存在
    static id master = nil;
    // 若實體尚未被建立,在此建立一個
    if (!master) {
         master = [MyObj new];
    }
    return master;
}


@end

如此一來,我們就只能使用 sharedInstance 來存取該實體,並且在該程式之中,任何的介面都會存取到相同的實體。

MyObj *obj1 = [MyObj sharedInstance];
MyObj *obj2 = [MyObj sharedInstance];


// obj1 == obj2

在 Cocoa 中最常用到的實例莫過於:

[NSApplication sharedApplication];
[NSBundle mainBundle];
[NSNotificationCenter defaultCenter];
[NSUserDefault standardUserDefault];

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。但是自動化機制總是會犧牲掉一般程式設計師可以掌握的彈性。我認為理解清楚這個機制,可以有助益於任何記憶體管理方面的進步!


0

安裝 ffmpeg

| 7/17/2011
最近發現電腦裡面太多有的沒有的影片格式,想說一口氣就把這疑大堆亂七八糟的東西解決掉。因為網路上面有太多不同的 codec。就算說是同一種 codec,也有可能因為實作上面的不同,即便你是使用支援該格式的播放器去觀看,恐怕也是會面臨一個頭兩個大的窘境。

這裡我把目標放在 ffmpeg 上面。不用說,大家都知道,這套軟體是集合絕大多格式的編碼工具,許多人都會將這套工具搭配 mencoding 來使用。(這裡因為我不是很喜歡 mencoding,所以在碰到問題之前就乾脆先放棄了)

要在 Mac 下面編譯 ffmpeg 很簡單,就跟一般的 tarball 應用程式安裝的方式相同。至於在 Windows 下面得依靠 MinGW,因為又麻煩,編譯速度又慢,這裡就不說了。

首先得去 ffmpeg.org 取得最新的 source,然後解壓縮:

tar xf ffmpeg-HEAD.tar.gz
cd /path/to/ffmpeg/package/dictionary

接下來是快樂的 config 時間,可以使用 ./configure --help 看看有哪些東西是需要、不需要的,把參數記下來,然後:

./configure --prefix=/path/to/install/dictionary --disable-doc --disable-yasm

因為 Xcode 不是用 yasm 編譯組合語言的,這裡要把它關掉。
然後就是快樂的編譯時間:make !!

經過大約五到十分鐘左右就應該會編譯完成,之後在 make install 就完成了!
後續的就是一些 path 設定的小部分,首先編輯 ~/.bash_profile ,在裡面加上:
export PATH=$PATH:/path/to/install/dictionary/bin

之後就可以開始研究 ffmpeg 去了!
2

Self assign IP address

| 2/08/2011
解了一大堆 Windows 的怪問題,今天換個口味,來解一下 Mac 上面的怪問題。這個問題看起來似乎是從 Leopard 開始的,當然我不知道確定的時間,但是就是有這個問題。
當我們使用 AirPort 直接連結無線基地台的時候,在絕大多數的情況下都是使用 DHCP 來取得 IP Address。取得 IP 這個動作在 AirPort 是有設定一個 Timeout 的時間在的。當 Timeout 過了之後,系統將會自動設定一個 168.* 的 IP 給你,因為被分配到一個不屬於該 AP 的網域之下,所以你的電腦將沒有辦法連上網路。

仔細去分析發生這個問題的癥結:Timeout,可以發現,發生這種事情可能的理由就是該 AP 的網路連線上面有問題,而另外一個最重要的理由就是防火牆!或許大家會覺得不可思議,為什麼防火牆會把 DHCP 給擋在門外?或許沒有人可以給我們一個正確的答案,甚至有的時候你防火牆沒有打開,卻也會發生這種現象。解決的辦法其實也很簡單,只要你在安全性設定裡面把防火牆先「打開」再「關閉」,這個問題便會迎刃而解!