Objective-C的Block(閉包),遞迴與泛型

來源:互聯網
上載者:User

Apple在C,Objective-C和C++中擴充了Block這種文法的,並且在GCC4.2中進行了支援。現在我們可以在Mac 10.6和iOS 4中使用。如果是Mac 10.6 或 iOS 4.0 之前的平台,據說可以用http://code.google.com/p/plblocks/這個項目來支援Block文法。

Apple在 Snow Leopard中所用到的Grand Central Dispatch(GCD)就是基於Blocks實現的。Grand Central Dispatch是蘋果開發的工具,目的是協助開發人員更容易的利用多核處理器的平行處理功能。關於Blocks以及GCD在蘋果官方的介紹,請見:Introducing Blocks and Grand Central Dispatch。

你可以把它理解為函數指標,匿名函數,閉包,lambda運算式,這裡暫且用塊對象來表述,因為它們之間還是有些許不同的。

塊對象

塊對象是C層級的文法,同時也是一種運行時特徵,即它允許您把函數運算式組合在一起,組合結果可作為參數傳遞也可儲存,還可供多個線程使用。塊對象的函數運算式可引用或持有局部變數。在其他的語言環境中,塊對象有時也被稱為closure或者lambda。如果您需建立可如數值般傳遞的工作單元(即程式碼片段),則可使用塊對象,它可為您提供更多的編程靈活性和更強大的功能。如需編寫回呼函數或對某個群體的所有項執行某種操作,也可使用塊對象。

l  聲明一個塊

如果以內聯方式使用塊對象,則無需聲明。塊對象聲明文法與函數指標聲明文法相似,但是塊對象應使用脫字元(^)而非星號指標 (*)。下面的代碼聲明一個aBlock變數,它標識一個需傳入三個參數並具有float傳回值的塊。

float (^aBlock)(const int*, int, float);

l  建立一個塊

塊使用脫字元(^)作為起始標誌,使用分號作為結束標誌。下面的例子聲明一個簡單塊,並且將其賦給之前聲明的block變數(oneFrom)。

int (^oneFrom)(int);

oneFrom = ^(int anInt) {

    return anInt - 1;

};

結尾處的分號是標準C的行結束標誌。如果未顯式聲明塊運算式的傳回值,則編譯器會根據塊內容自動進行推導。

l  塊可變變數

如果某個局部變數使用__block儲存修飾符,則表示塊應使用此變數的引用,並可更改它的值。對變數的任何改變都只在塊的文法範圍內部,以及該範圍中定義的其它塊中起作用。

塊對象的特性

Blocks比C++0x中的lambda運算式更強的一點是,它可以是個數群組類型:

int main(void)

{

    void (^p[2])(void) = { ^(void){ puts("Hello, world!"); }, ^(void){ puts("Goodbye!"); } };

    p[0](), p[1]();

}

這裡p的類型為void(^[2])(void),表示含有2個void(^)(void)區塊引述元素的變數。下面談談函數塊對其外部臨時變數的可訪問情況。

static int global = 100;

int main(void)

{

    int local = 200;

    void (^p)(void) = ^(void){ printf("The answer is: %dn", global + local); };

    p();

}

對於FP比較熟悉的朋友可能會想到,如果一個外部變數能夠隨隨便便被一個函數塊修改的話,那麼對於其本身的副作用仍然無法進行方便地多核並行編程。那麼我們不妨試試看吧:

static int global = 100;

int main(void)

{

    int local = 200;

    void (^p)(void) = ^(void){

        printf("The answer is: %dn", global + local);

        global++;

        local--;    // compiling error:error: decrement of read-only variable \'local\'

    };

    p();

printf("After modified, global is: %d and local is %dn", global, local);

}

對於全域變數可以進行修改,但是對於main函數中的局部變數則不行。如果對local修改則無法通過編譯。很顯然,Blocks對此已經有了相應的機制。 那麼我們如何能夠對local進行修改呢?

static int global = 100;

int main(void)

{

    __block int local = 200;

    static int s = 10;

   

    void (^p)(void) = ^(void){

        printf("The answer is: %dn", global + local);

        global++;

        s++;

        local--;

    };

    p();

    printf("After modified, global is: %d and local is %d and s is: %dn", global, local, s);

}

這裡引入了一個新的關鍵字 ——__block,用此聲明一個局部變數可以被函數塊修改。

Block是否可以遞迴

如果要使Blocks能夠遞迴,那麼在函數塊中必須能夠引用函數塊的入口地址。我做了一些嘗試,當函數區塊引述是全域的或static的,即函數塊內所引用的函數 區塊引述變數的值在初始時就已經確定的,那麼可以使用遞迴。

int main(void)

{

    void (^p)(int) = 0;

    static void (^ const blocks)(int) = ^(int i){ if(i > 0){ puts("Hello, world!"); blocks(i - 1); } };

    p = blocks;

    p(2);

}

如果在上述代碼中將blocks前的static去掉,那麼在運行時就會出錯,因為blocks在被函數區塊引述時是未初始化值,所以調用它的話就訪問了無效地址,或者所要執行的指令是未定義的。

Blocks結合泛型(mm才支援範型)

由於泛型能夠使我們更高效、合理地管理好自己的代碼,同時也為組件化提供了許多便利之處。那麼Blocks與泛型結合會產生什麼新元素呢?

我們先舉一個簡單例子:

#import <Foundation/Foundation.h>

 

template <void pBlock(void)>

void BlockTest(void)

{

    pBlock();

}

 

void Hi(void)

{

    NSLog(@"Hi, there!");

}

 

int main(int argc, const char* argv[])

{

    BlockTest<Hi>();

}

上述代碼中尚未出現Blocks,但是我們可以看到,一般的外部函數能夠作為模板參數。那麼Blocks是否可以這麼做呢?我們不妨嘗試一下:

#import <Foundation/Foundation.h>

 

template <void (^pBlock)(void)>

void BlockTest(void)

{

    pBlock();

}

 

int main(int argc, const char* argv[])

{

    BlockTest<^(void) { NSLog(@"Hi, there!"); }>();

}

編譯時間會在第11行出現error: no matching function for call to \'BlockTest()\'。C++標準中明確指出,模板參數必須為常量運算式,如果是函數的話必須是帶有外部串連(即external-linkage)的函數指標。而Blocks運算式首先 就不是一個常量運算式,然後它也沒有外部串連。我們下面看第二個例子:

#import <Foundation/Foundation.h>

 

template <typename T>

void BlockTest(void (&pBlock)(T))

{

    pBlock(T());

}

 

static void Hi(int a)

{

    NSLog(@"The value is: %dn", a);

}

 

int main(int argc, const char* argv[])

{

    BlockTest(Hi);

}

上述代碼中使用了函數引用作為函數參數,然後由實參類型演繹出模板類型。這段代碼將能正常地通過編譯、串連並正常運行。那麼我們下面再看一看 Blocks是否具有這個泛型特性:

#import <Foundation/Foundation.h>

 

template <typename T>

void BlockTest(void (^pBlock)(T))

{

    pBlock(T());

}

 

int main(int argc, const char* argv[])

{

    BlockTest(^(int a) { NSLog(@"The value is: %dn", a); });

}

編譯後出現 error: no matching function for call to \'BlockTest(void (^)(int))\'。即使顯式地將<int>模板實參加上也沒用。也就是說Blocks的參數類型包括傳回型別不能是一個泛型。我們再看第三個例子:

#import <Foundation/Foundation.h>

#include <iostream>

#include <typeinfo>

using namespace std;

 

template <typename T>

void BlockTest(T pBlock)

{

    pBlock();

    cout << "The type is: " << typeid(T).name() << endl;

}

 

static void Hi(void)

{

    NSLog(@"Hi, there!");

}

 

int main(int argc, const char* argv[])

{

  BlockTest(Hi);

}

這段代碼展示了整個函數指標類型演繹出模板實參。對於目前已被很多編譯器所實現的Lambda運算式,這是與泛型掛鈎的唯一橋樑,那麼Blocks是否具備這個特性呢?

#import <Foundation/Foundation.h>

#include <iostream>

#include <typeinfo>

using namespace std;

 

template <typename T>

void BlockTest(T pBlock)

{

    pBlock();

    cout << "The type is: " << typeid(T).name() << endl;

}

 

int main(int argc, const char* argv[])

{

    BlockTest(^(void) { NSLog(@"Hi, there!"); });

}

恭喜,我們成功了。這段代碼能夠正常編譯和運行。各位可以自己看看輸出結果。其中,類型資訊是被壓縮過的:F表示函數,P表示指標,v表示void類型。Blocks 與C++0x中的lambda運算式一樣,必須作為一個完整的類型。對其類型做拆分進行泛型化是非法的。由於C++0x的Lambda運算式的具體類型不對程式員開放,因此它即不能作為模板形參亦無法作為模板函數的形參,但是它可以在模板函數內使用泛型。

#include <iostream>

using namespace std;

 

template <typename T>

void LambdaTest(T)

{

    T a = 100;

    auto ref = [a](const T& b) -> T { return a + b; };

    cout << "The value is: " << ref(200) << endl;

}

int main(void)

{

    LambdaTest((short)0);

}

上述代碼可以在VS2010以及Intel C++ Compiler11.0通過編譯並正常運行。然而比較奇怪的是Blocks在模板函數內的表現就非常不好——

template <typename T>

void BlockTest(void)

{

    void (^pBlocks)(void) = ^{};

}

int main(int argc, const char* argv[])

{

    BlockTest<void>();

}

上面這段代碼中,模板函數BlockTest中pBlocks根本就沒用泛型,也無法通過編譯,而且報的錯誤是internal error: segmentation fault。而只有下面這種情況才能通過編譯,但實際上是沒有任何意義的:

#import <Foundation/Foundation.h>

template <typename T>

void BlockTest(void)

{

    void (^pBlock)(void) = nil;

}

 

int main(int argc, const char* argv[])

{

    BlockTest<void>();

}

可見,Blocks作為Lambda運算式而言已經是非常棒的,但與lambda函數功能相比就稍遜一些,尤其是與泛型結合時,表現得很一般。其實這也是與 Blocks的實現有關的。Blocks仍然像Objective-C的很多特性那樣,主要是靠動態實現的,在編譯時間所花的精力較少。能夠在C以及Objective-C中用上Lambda特性也很不錯。

小結

Blocks在範圍,記憶體管理等方面具有自己獨特的特性和需要留意的地方,在許多情境也很多實用的功能。雖然有時理解起來有點費勁,但是用好了,相信用好了可以大大提高代碼可讀性和效率等。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.