iOS block 教程

Block是iOS在4.0之后新增的程式語法,嚴格來說block的概念并不算是基礎程式設計的范圍,對初學者來說也不是很容易了解,但是在iOS SDK 4.0之后,block幾乎出現在所有新版的API之中,換句話說,如果不了解block這個概念就無法使用SDK 4.0版本以后的新功能,因此雖然block本身的語法有點難度,但為了使用iOS的新功能我們還是得硬著頭皮去了解這個新的程式概念。

1.初探Block

在這一小節我們先用一些簡單范例來導入block的概念。

1.1宣告和使用Block:

我們使用「^」運算子來宣告一個block變數,而且在block的定義最后面要加上「;」來表示一個完整的述句(也就是將整個block定義視為前面章節所介紹的簡單述句,因為整個定義必須是一個完整的句子,所以必須在最后面加上分號),下面是一個block的范例:

int multiplier = 7;

int (^myBlock)(int) = ^(int num)

{

returnnum * multiplier;

};

我們宣告一個「myBlock」變數,用「^」符號來表示這是一個block。這是block的完整定義,這個定義將會指定給「myBlock」變數。表示「myBlock」是一個回傳值為整數(int)的block。它有一個參數,型態也是整數。這個參數的名字叫做「num」。這是block的內容。

值得注意的地方是block可以使用和本身定義范圍相同的變數,可以想像在上面的例子中multiplier 和myBlock都是某一個函數內定義的兩個變數也就是這個變數都在某個函數兩個大括號「{」和「 }」中間的區塊,因為它們的有效范圍是相同的,因此在block中就可以直接使用 multiplier 這個變數,此外當把block定義成一個變數的時,我們可以直接像使用一般函數般的方式使用它:

int multiplier = 7 ;

int (^myBlock)(int) = ^(int num)

{

returnnum * multiplier;

};

printf("%d", myBlock( 3 ));

//結果會打印出21

1.2直接使用Block:

在很多情況下,我們并不需要將block宣告成變數,反之我們可以直接在需要使用block的地方直接用內嵌的方式將block的內容寫出來,在下面的例子中qsort_b函數,這是一個類似傳統的qsort_t函數,但是直接使用block做為它的參數:

char*myCharacters[3] = {"TomJohn","George","Charles Condomine"};

qsort_b (myCharacters, 3,

sizeof(char*),

^(constvoid*l,constvoid*r)//block部分

{

char*left = *(char**)l;

char*right = *(char**)r;

returnstrncmp(left, right, 1 );

}

);

1.3__block 變量:

一般來說,在block內只能讀取在同一個作用域的變數而且沒有辦法修改在block外定義的任何變數,此時若我們想要這些變數能夠在block中被修改,就必須在前面掛上__block的修飾詞,以上面第一個例子中的 multiplier 來說,這個變數在 block 中是唯讀的,所以 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若我們在 block 中修改 multiplier,在編輯時就會產生錯誤,因此若想要在 block 中修改 multiplier ,就必須在 multiplier 前面加上 __block 的修飾詞,請參考下面的范例:

__blockintmultiplier = 7;

int(^myBlock)(int) = ^(int num)

{

if (num > 5 )

{

multiplier = 7 ;

}

else

{

multiplier = 10 ;

}

returnnum * multiplier;

};

2.Block概要

Block 提供我們一種能夠將函數程式碼內嵌在一般述句中的方法,在其他語言中也有類似的概念稱做「closure」,但是為了配合Objective-C的貫例,我們一律將這種用法稱為「block」

2.1Block 的功能

Block 是一種具有匿名功能的內嵌函數,它的特性如下:如一般的函數般能擁有帶有型態的參數。擁有回傳值。可以擷取被定義的詞法作用域(lexical scope)狀態。可以選擇性地修改詞法作用域的狀態。

注:詞法作用域(lexical scope)可以想像成是某個函數兩個大括號中間的區塊,這個區塊在程式執行時,系統會將這個區塊放入堆疊記憶體中,在這個區塊中的宣告的變數就像是我們常聽到的區域變數,當我們說block可以擷取同一詞法作用域的狀態時可以想像block變數和其他區域變數是同一個層級的區域變數(位于同一層的堆疊里),而block的內容可以讀取到和他同一層級的其他區域變數。

我們可以拷貝一個block,也可以將它丟到其他的執行緒中使用,基本上雖然block在iOS程式開發中可以使用在C/C++開發的程式片段,也可以在Objective-C中使用,不過在系統的定義上,block永遠會被視為是一個Objective-C的物件。

2.2Block 的使用時機

Block 一般是用來表示、簡化一小段的程式碼,它特別適合用來建立一些同步執行的程式片段、封裝一些小型的工作或是用來做為某一個工作完成時的回傳呼叫(callback) 。在新的iOS API中block被大量用來取代傳統的delegate和callback,而新的API會大量使用block主要是基于以下兩個原因:

可以直接在程式碼中撰寫等會要接著執行的程式,直接將程式碼變成函數的參數傳入函數中,這是新API最常使用block的地方。

可以存取區域變數,在傳統的callback實作時,若想要存取區域變數得將變數封裝成結構才能使用,而block則是可以很方便地直接存取區域變數。

3.宣告和建立Block

3.1宣告Block的參考

Block 變數儲存的是一個block的參考,我們使用類似宣告指標的方式來宣告,不同的是這時block變數指到的地方是一個函數,而指標使用的是「*」,block則是使用「^」來宣告,下面是一些合法的block宣告:

/* 回傳void ,參數也是void 的block*/

void(^blockReturningVoidWithVoidArgument)(void);

/* 回傳整數,兩個參數分別是整數和字元型態的block*/

int(^blockReturningIntWithIntAndCharArguments)(int,char);

/*回傳void,含有10個block的陣列,每個block都有一個型態為整數的參數*/

void(^arrayOfTenBlocksReturningVoidWinIntArgument[10])(int);

3.2建立一個Block

我們使用「^」來開始一個block,并在最后使用「;」來表示結束,下面的范例示范了一個block變量,然后再定義一個block把它指定給block變量:

/* 宣告block 變量*/

int(^oneFrom)(int);

/* 定義block 的內容并指定給上面宣告的變量*/

oneFrom = ^(intanInt)

{

returnanInt = - 1 ;

};

3.3全局的Block

可以在檔案中宣告一個全域的block,請參考以下范例:

int GlobalInt = 0;

int(^getGlobalInt)(void) = ^(void){

returnGlobalInt;

};

4.Block和變量

4.1變量的型態

我們可以在block中遇到平常在函數中會遇到的變量類型:

全域(global)變量或是靜態的區域變量(static local)。

全域的函數。

區域變量和由封閉領域(enclosing scope)傳入的參數。

除了上述之外block額外支援了另外兩種變量:在函數內可以使用__block變量,這些變量在block中是可被修改的。匯入常數(const imports)。此外,在方法的實作里,block可以使用Objective-C的實體變量(instance variable)。

下列的規則可以套用到在block中變量的使用:

可以存取全域變量和在同一領域(enclosing lexical scope)中的靜態變量。

可以存取傳入block的參數(使用方式和傳入函數的參數相同)。

在同一領域的區域變數在block中將視為常數(const)。

可以存取在同一領域中以__block 為修飾詞的變數。

在block中宣告的區域變數,使用方式和平常函數使用區域變數的方式相同。

下面的例子介紹了區域變數(上述第三點)的使用方式:

int x = 123;

void(^printXandY)(int) = ^(int y)

{

printf("%d %d", x,y);

}

printXAndY( 456 );//將會印出123 456

就如上面第三點所提到的,在上例中的int x = 123的變量x,在傳入block后將視同常數,因此若我們在block中試著去修改x的值時就會產生錯誤.若在block中想要修改上面的變數x,必須將x宣告加上修飾詞__block,請參考接下來這一小節的介紹。

4.2__block 型態變量

我們可以藉由將一個由外部匯入block的變數放上修飾詞__block來讓這個變數由唯讀變成可以讀和寫,不過有一個限制就是傳入的變數 在記憶體中必須是一個占有固定長度記憶體的變數,__block修飾詞無法使用于像是變動長度的陣列這類不定長度的變數,請參考下面的范例:

//加上__block 修飾詞,所以可以在block 中被修改。

__blockintx = 123;

void(^printXandY)(int) = ^(inty)

{

x = x+y;

printf("%d %d",x,y);

}

printXAndY( 456 );// 將會印出579 456

下面我們使用一個范例來介紹各類型的變數和block之間的互動:

externNSInteger CounterGlobal;

staticNSInteger CounterStatic;

{

NSInteger localCounter = 42 ;

__blockcharlocalCharacter;

void(^aBlock)(void) = ^(void)

{

++ CounterGlobal;//可以存取

++ CounterStatic;//可以存取

CounterGlobal = localCounter;//localCounter在block 建立時就不可變了

localCharacter ='a';//設定外面定義的localCharacter 變數

}

++localCounter;//不會影響的block 中的值

localCharacter ='b';

aBlock();//執行block 的內容

//執行完后,localCharachter 會變成'a'

}

4.3物件和Block變量

Block 支援在Objective-C、C++物件和其他block中當作變數來使用,不過因為在大部分的情況我們都是使用Objective-C的撰寫程式,因 此在這一小節我們僅針對Objective-C的情況進行介紹,至于其他兩種情況就留給有興趣的讀者再自行深入研究了。

4.3.1Objective-C物件

在擁有參考計數(reference-counted)的環境中,若我們在block中參考到Objective-C的物件,在一般的情況下它將會自動增加物件的參考計數,不過若以__block為修飾詞的物件,參考計數則是不受影響。

如果我們在Objective-C的方法中使用block時,以下幾個和記憶體管理的事是需要額外注意的:

若直接存取實體變量(instance variable),self的參考計數將被加1。

若透過變數存取實體變數的值,則只變數的參考計數將被加1。

以下程式碼說明上面兩種情況,在這個假設instanceVariable是實體變量:

dispath_async(queue,^{


//因為直接存取實體變數instanceVariable ,所以self 的retain count會加1

doSomethingWithObject (instanceVariable);

});


id localVaribale = instanceVariable;

dispatch_async(queue,^{

//localVariable是存取值,所以這時只有localVariable 的retain count 加1

//self 的return count并不會增加。

doSomethingWithObject (localVaribale);

});

5. 使用Block

5.1呼叫一個Block

當block宣告成一個變量時,我們可以像使用一般函數的方式來使用它,請參考下面兩個范例:

int(^oneFrom)(int) = ^(int anInt){

return anInt - 1;

};

printf("1 from 10 is %d", oneFrom(10));

//結果會顯示:1 from 10 is 9


float (^distanceTraveled)(float,float,float) = ^(floatstartingSpeed,floatacceleration,floattime)

{

floatdistance = (startingSpeed *time) + ( 0.5 * acceleration *time*time);

returndistance;

};

floathowFar = distanceTraveled( 0.0 , 9.8 , 1.0 );

//howFar會變成4.9

在一般常見的情況中,若是將block當做是參數傳入函數,我們通常會使用「內嵌」的方式來使用block。

5.2將Block當作函數的參數

我們可以像使用一般函數使用參數的方式,將block以函數參數的型式傳入函數中,在這種情況下,大多數我們使用block的方式將不會傾向宣告block而是直接以內嵌的方式來將block傳入,這也是目前新版SDK中主流的做法,我們將補充前面章節的例子來說明:

char*myCharacters[3]={"TomJohn","George","Charles Condomine"};

qsort_b(myCharacters, 3,sizeof(char*),^(constvoid*l,constvoid*r){

char*left = *(char**)l;

char*right = *(char**)r;

returnstrncmp(left, right, 1 );

}//這里是block 的終點。

);

//最后的結果為:{"Charles Condomine", "George", "TomJohn"}

在上面的例子中,block本身就是函數參數的一部分,在下一個例子中dispatch_apply函數中使用block,dispatch_apply的定義如下:

1voiddispatch_apply(size_titerations,dispatch_queue_t queue,void(^block)(size_t));

這個函數將一個block提交到發送佇列(dispatch queue)中來執行多重的呼叫,只有當佇列中的工作都執行完成后才會回傳,這個函數擁有三個變數,而最后一個參數就是block ,請參考下面的范例:

size_tcount = 10;

dispatch_queue_t queue = dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 );

dispatch_apply(count, queue, ^(size_ti))

{

printf("%u\n", i);

}

5.3將Block當作方法的參數

在SDK中提供了許多使用block的方法,我們可以像傳遞一般參數的方式來傳遞block,下面這個范例示范如何在一個陣列的前5筆資料中取出我們想要的資料的索引值:

//所有的資料

NSArray *array = [ NSArray arrayWithObjects : @"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @" Q",nil ];

//我們只要這個集合內的資料

NSSet *filterSet = [ NSSet setWithObjects : @"A", @"B", @"Z", @"Q", nil ];

BOOL(^test)(id obj,NSUInteger idx,BOOL*stop);

test = ^ ( id obj, NSUInteger idx,BOOL*stop)

{

//只對前5筆資料做檢查

if(idx < 5 ) {

if([filterSet containsObject : obj]) {

returnYES ;

}

}

returnNO ;

}

NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test];

NSLog ( @"indexes: %@", indexes);

//結果:indexes: [number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]

//前5筆資料中,有4筆符合條件,它們的索引值分別是0-1, 3-4

5.4該避免的使用方式

在下面的例子中,block是for回圈的區域變數因此在使用上必須避免將區域的block指定給外面宣告的block:

view source

print?

//這是錯誤的范例,請勿在程式中使用這些語法!!

void dontDoThis() {

void(^blockArray[3])(void);// 3 個block 的陣列

for(inti = 0; i < 3; ++i) {

blockArray[i] = ^{printf("hello, %d\n", i); };

// 注意: 這個block 定義僅在for 回圈有效。

}

}

void dontDoThisEither() {

void(^block)(void);

inti = random():

if (i > 1000) {

block = ^{printf("got i at: %d\n", i); };

}

}

《《轉載自 chrislkx?感謝分享!!!》》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Block是iOS在4.0之后新增的語法,在iOS SDK 4.0之后,block幾乎出現在所有新版的API之中,...
    阿窩額咦嗚芋閱讀 480評論 0 0
  • **Block **是iOS在4.0之后新增的語法,在iOS SDK 4.0之后,block幾乎出現在所有新版的A...
    ios_geek閱讀 797評論 0 0
  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,787評論 0 23
  • Block基礎回顧 1.什么是Block? 帶有局部變量的匿名函數(名字不重要,知道怎么用就行),差不多就與C語言...
    Bugfix閱讀 6,802評論 5 61
  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 2,014評論 0 7