0

指標動態產生陣列的兩三事

| 7/04/2012
星星,其實是指標

對大多數程式設計師而言,是能不用就不用的東西。其實先進的程式語言,幾乎都會把指標這種東西排除在外。最主要的原因不外乎就是它難以駕馭的問題;只要稍有疏失,就可能導致整個應用程式不穩定。其實今天這篇文章要談的不是該怎麼用指標的,而是使用指標分配二維陣列記憶體的問題。因為這個地方很容易產生一個嚴重的迷思。

相信大家都知道,用單一指標、再分配某個變數實質佔有空間的乘積之後,變成了如同陣列一般的區塊。

好比如說,我們希望有一個 int [5] 的空間,這裡就會用:

int *a = malloc(sizeof(int) * 5) 來宣告。

當然,不要忘記要釋放:free(a);

那,如果我希望有個 int [5][5] 的空間呢?
喔,不就是雙重指標嗎?
所以就是:

int **a = malloc (sizeof(int) * 5 * 5);

簡單嘛!

錯,而且錯很大,錯到你可能 de 不出 bug 在哪邊。
這也是很多人會犯下的錯誤。

我們把動作拆開來看第一項,當我們想要產生 int [5] 的時候
我們把 int *a 的星號拿掉,成了我們要產生的目標:int,所以我們用 malloc (sizeof(int) * count)。所以換到了雙重指標的時候,int **a 要產生的目標應該是:int *。然後才是 int * 要產生的目標:int。

所以程式碼應該是

int **a = malloc (sizeof(int *) * 5);

for ( int i = 0; i < 5; i++ )
     *(a + i) = malloc ( sizeof (int ) * 5 );

當然,如果你真的要這樣寫,到時候要 free 的時候會瘋掉,但也是保險的作法。為什麼?
因為我們把程式合併起來,整個程式碼是:

int **a = malloc (sizeof (int *) * 5 + sizeof (int) * 5 * 5)
請注意,這裡空間雖然分配了,可是你實際在使用的時候,因為要保證所有 int ** 底下的 int * 所指的位置,所以很容易指錯位置,所以上述的方法是比較保險的。

真複雜?沒錯,超級複雜的。
看完有沒有一堆星星在繞呢?

0

Finder 以及音樂檔案 Artwork 的問題

| 3/13/2012
大獅子,更新之後部分使用者發生 Finder 沒有辦法顯示在 iTunes 設定的音樂圖片的問題。其實這個問題也困擾了我好一陣子。蘋果本身也不斷地推出 iTunes 的更新,卻依然不見這個問題解決。

正當我打算要傳灌之前,我把 iTunes 的資料庫導向另外一個資料夾,讓 iTunes 重建一個新的資料庫,結果,以前本來沒有顯示專輯圖片的檔案圖示突然就這樣正常了!!我再嘗試把轉回原本的資料庫,依然可以正常運作。 (當我這麼想的時候卻不是這樣)

太好了!省下重灌的時間!

How to create new iTunes Library?
Press option key and launch iTunes application than select Create new Library.

修正
結果沒想到卻還是回到 icon 消失的老樣子。不過既然重新產生資料庫可以解決問題,我就先把現有的資料庫輸出,然後移動檔案夾之後,在 Music 下面產生一個同名的 iTunes 資料庫,把檔案移動回去,再匯入資料庫。

看來這次應該是把問題給解決了!
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。

清楚了嗎?