2011年8月24日 星期三

探討Objective-C Block (part 1)

在ios4推出後,出現了一個新的語言功能block。這個功能其實有接觸多種程式語言的話,會知道其實這個功能不算是Objctive-C特有的
別的語言可能叫做Lambda或是Closure,
這東西主要的特性是他除了是function外,但是更紀錄了此function外部的環境。
也許有點抽象,我先舉個javascript的例子
var i = 2;
    var func = function(){ i = i * i;};
    func();  // than i = 4
    func();  // than i = 16
我們在中間定義一個function並且指派到func這個變數,
而在這邊可以用i這個外面的變數,因為在javascript中這樣的定義的就是一個closure,
他會記住外面有i這個變數,
所以之後我們連續呼叫兩次func,
則可以得到i是16這個結果
這個跟objective-c的block,或是別的語言的lambda,closure都是一樣的東西
相關的資料可以參考Clousre - wikipedia 裡面的介紹。

回到objective-c的block,當然這個好物當然要好好的利用一下,
這個東西最吸引人的地方就是可以帶不定數量的變數到function當中,因為你只要環境中可以取得的都可以在function中使用
並且因為block(或是其他語言的closure)通常使用上都是anonymous function,直接藏身在你的code當中,因此可以讓程式碼更加精簡。
但是我覺得剛開始用block的時候,有時候會有知其然而不知其所以然的情況。
尤其是Objective-c最令人頭疼的記憶體管理的部份在block中更是複雜,
如果你不知道你的物件怎麼在block中去使用那會是一件危險的事情,
所以這個主題算是經驗分享文,來分享我現在對block的認知,
而當然ios有很多官方的API都支援block,最有名的就是Grand Central Dispatch (GCD)
但是這篇不會介紹GCD,我們把內容專注在block這個語言功能。

在講block怎麼用之前,容我先介紹C的function跟function pointer,我認為在學block之前要有這個基本的認識比較好。
一來是block跟funtion有點相似,但是又有點不一樣,知道function再來看block我覺得會比較透徹的瞭解
二來它們兩個定義的部份真的很像,但是實作一個function跟block卻大不相同,仔細想想兩個的差異絕對會對block更有深刻的感覺。
首先function相信大家都會實作function,這再簡單不過了
int plusone(int a)
{
    return a+1;
}


上面這裡定義了一個傳入參數是int回傳是int的function,這應該不用解釋太多了。
那function pointer呢? 可能開始有些人不是那麼熟悉了...
int ooxx()
{
     typedef int (*myfunc_type)(int a);
     myfunc_type myfunc = plusone;
     NSLog(@"return value = %d", myfunc(5));
}

這邊開始就要解釋了
第一行是我定義一個myfunc_type這個function pointer type,而這個function type是傳入是int,回傳是int
而第二行是定義一個myfunc_type的變數,我讓他指到我們剛剛定義的plusone
而第三行我們就是去呼叫這個function pointer,則他就會等同去呼叫plusone,並且帶入5。
如果上面的code你看懂了,恭喜你,block幾乎沒差太多,甚至更好用。
int ooxx()
{
     typedef int (^myblock_type)(int a);
     myblock_type myblock = ^int(int a){
        return a+1;
     };
     NSLog(@"return value = %d", myblock(5));
}

第一行跟function pointer很像,原本function pointer是用*,但是block是用^
第二行也是定義一個myblock_type的變數,但是不一樣的是他指到的是一個block,
block的實作也是^開頭,緊接著是回傳type,接著是傳入的參數,後面接上{....}就是block實作的內容啦。
就像前面所說的,block本身就是anonymous function的方式去定義,
有些時候anonymous function非常好用,例如定義event handler,
我們可以註冊event handler時,直接在後面寫event handler的邏輯,可以很快的可以看到事件發生之後會要做哪些動作
不必像用function pointer或是delegate的方式需要把邏輯寫在另外一個function,
第三行(應該說第三個statement)就跟function pointer很像了,就是呼叫這個block。

當然block強大的地方就是block所包含的code可以使用外面的變數
例如下面的例子,temp是block外面所定義的,
但是我們可以直接拿來用
int ooxx()
{
     int temp = 5;
     typedef int (^myblock_type)(int a);
     myblock_type myblock = ^int(int a){
        return temp+1;
     };
     NSLog(@"return value = %d", myblock(5));
}

但是值得注意的是,上面的code,在block中會把temp當作常數看待,也就是說當定義block的時候,temp的值是5
即便我們在後來把temp改成1
但是得到的結果不會變,
這同樣也說明了一個特性,
那就是我們不能在block中,直接的修改(write)一個外部(區域)變數的值,
所以看下段code
int ooxx()
{
     int temp = 5;
     typedef int (^myblock_type)(int a);
     myblock_type myblock = ^int(int a){
        temp++;
        return temp;
    };
     NSLog(@"return value = %d", myblock(5));
}

這邊compile就會錯了,因為這邊不只是會讀temp的值,還會寫temp的值。
但是如果我們加上__block這個變數修飾..
那此變數就可以在block中修改。
下面就是個例子
int ooxx()
{
     __block int temp = 5;
     typedef int (^myblock_type)(int a);
     myblock_type myblock = ^int(int a){
        temp++;
        return temp;
    };
     NSLog(@"return value = %d", myblock(5));
}

以上就是常用的block的方法
事實上更常用的用法會是把block當參數傳到function,
這邊舉個GCD的例子,
dispatch_async(queue, ^(void) {
        // some task here
    });
這邊就是把一個block丟到queue中等待被背景執行,
而就是丟進一個傳入是void傳回是void的block
由於傳回是void,所以可以把^void(void){...}簡化成^(void){...}
在block的使用上常常會見到這種把block當參數的寫法,之後相信會漸漸的習慣。

探討Objective-C Block (part 1) - block的使用
探討Objective-C Block (part 2) - block變數
探討Objective-C Block (part 3) - block的記憶體管理

2 則留言:

  1. 感謝解釋
    用處我只想到用來傳入要執行的動作block,咦跟用selector有啥分別=w="

    回覆刪除
  2. 你問的很好,selector是跟block很接近的東西,事實上他們的目的也很接近。
    selector就算是objective-c很特殊的東西。他只紀錄了message的signature,例如@selector(onButtonClick:),但是卻沒有存在任何物件跟狀態(state)。

    如果function pointer, selector, block三個要比較
    function pointer是一個變數指到符合函數形態的code位置
    selector是一個變數指到一個signature定義。
    block不只是定義了code,還包含了定義function所在的環境(local and global variable)

    回覆刪除