IOS block 教程
http://pernghh.pixnet.net/blog/trackback/eac87d412e/33563409
本文來自臺灣的某開發(fā)人員的博客,被墻,感覺講的比較易懂,所以引過來。文字簡體化了,原來是繁體,變數(shù)=變量,這個注意一下。
本章學習目標:
1. 了解何謂block。
2. 了解block的使用方法。
Block 是iOS在4.0之后新增的程式語法,嚴格來說block的概念并不算是基礎程式設計的范圍,對初學者來說也不是很容易了解,但是在iOS SDK 4.0之后,block幾乎出現(xiàn)在所有新版的API之中,換句話說,如果不了解block這個概念就無法使用SDK 4.0版本以后的新功能,因此雖然block本身的語法有點難度,但為了使用iOS的新功能我們還是得硬著頭皮去了解這個新的程式概念。
在這一章的目標以了解如何使用block為主而不深入探討block底層的運作方式,至于有些初學者較少遇到的辭匯如「詞法作用域(lexical scope)」等,本章將不再多做解釋,待有興趣的讀者去請教Google大神吧。
X.1 初探Block
在這一小節(jié)我們先用一些簡單范例來導入block的概念。
X.1.1 宣告和使用Block
我們使用「^」運算子來宣告一個block變數(shù),而且在block的定義最后面要加上「;」來表示一個完整的述句(也就是將整個block定義視為前面章節(jié)所介紹的簡單述句,因為整個定義必須是一個完整的句子,所以必須在最后面加上分號),下面是一個block的范例:
1: int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: return num * multiplier;
5: };
我們使用下圖來解釋這個范例(請將文字框的字翻譯如下):
我們宣告一個「myBlock」變數(shù),用「^」符號來表示這是一個block。
這是block的完整定義,這個定義將會指定給「myBlock」變數(shù)。
表示「myBlock」是一個回傳值為整數(shù)(int)的block。
它有一個參數(shù),型態(tài)也是整數(shù)。
這個參數(shù)的名字叫做「num」。
這是block的內(nèi)容。
值得注意的地方是block可以使用和本身定義范圍相同的變數(shù),可以想像在上面的例子中 multiplier 和 myBlock 都是某一個函數(shù)內(nèi)定義的兩個變數(shù)也就是這個變數(shù)都在某個函數(shù)兩個大括號「{」和「 }」中間的區(qū)塊,因為它們的有效范圍是相同的,因此在block中就可以直接使用 multiplier 這個變數(shù),此外當把block定義成一個變數(shù)的時,我們可以直接像使用一般函數(shù)般的方式使用它:
1: int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: return num * multiplier;
5: };
6: printf ( "%d" , myBlock( 3 ));
7: //結果會打印出21
X.1.2 直接使用Block
在很多情況下,我們并不需要將block宣告成變數(shù),反之我們可以直接在需要使用block的地方直接用內(nèi)嵌的方式將block的內(nèi)容寫出來,在下面的例子中qsort_b函數(shù),這是一個類似傳統(tǒng)的qsort_t函數(shù),但是直接使用block做為它的參數(shù):
1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
2: qsort_b (myCharacters, 3 ,
3: sizeof ( char *),
4: ^( const void *l, const void *r)//block部分
5: {
6: char *left = *( char **)l;
7: char *right = *( char **)r;
8: return strncmp (left, right, 1 );
9: } //end
10: );
X.1.3 __block 變量
一般來說,在block內(nèi)只能讀取在同一個作用域的變數(shù)而且沒有辦法修改在block外定義的任何變數(shù),此時若我們想要這些變數(shù)能夠在block中被修改,就必須在前面掛上__block的修飾詞,以上面第一個例子中的 multiplier 來說,這個變數(shù)在 block 中是唯讀的,所以 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若我們在 block 中修改 multiplier ,在編輯時就會產(chǎn)生錯誤,因此若想要在 block 中修改 multiplier ,就必須在 multiplier 前面加上 __block 的修飾詞,請參考下面的范例:
1: __block int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: if (num > 5 )
5: {
6: multiplier = 7 ;
7: }
8: else
9: {
10: multiplier = 10 ;
11: }
12: return num * multiplier;
13: };
X.2 Block 概要
Block 提供我們一種能夠?qū)⒑瘮?shù)程式碼內(nèi)嵌在一般述句中的方法,在其他語言中也有類似的概念稱做「closure」,但是為了配合Objective-C的貫例,我們一律將這種用法稱為「block」
X.2.1 Block 的功能
Block 是一種具有匿名功能的內(nèi)嵌函數(shù),它的特性如下:
如一般的函數(shù)般能擁有帶有型態(tài)的參數(shù)。
擁有回傳值。
可以擷取被定義的詞法作用域(lexical scope)狀態(tài)。
可以選擇性地修改詞法作用域的狀態(tài)。
注:詞法作用域(lexical scope)可以想像成是某個函數(shù)兩個大括號中間的區(qū)塊,這個區(qū)塊在程式執(zhí)行時,系統(tǒng)會將這個區(qū)塊放入堆疊記憶體中,在這個區(qū)塊中的宣告的變數(shù)就像是我們常聽到的區(qū)域變數(shù),當我們說block可以擷取同一詞法作用域的狀態(tài)時可以想像block變數(shù)和其他區(qū)域變數(shù)是同一個層級的區(qū)域變數(shù)(位于同一層的堆疊里),而block的內(nèi)容可以讀取到和他同一層級的其他區(qū)域變數(shù)。
我們可以拷貝一個block,也可以將它丟到其他的執(zhí)行緒中使用,基本上雖然block在iOS程式開發(fā)中可以使用在C/C++開發(fā)的程式片段,也可以在Objective-C中使用,不過在系統(tǒng)的定義上,block永遠會被視為是一個Objective-C的物件。
X.2.2 Block 的使用時機
Block 一般是用來表示、簡化一小段的程式碼,它特別適合用來建立一些同步執(zhí)行的程式片段、封裝一些小型的工作或是用來做為某一個工作完成時的回傳呼叫(callback) 。
在新的iOS API中block被大量用來取代傳統(tǒng)的delegate和callback,而新的API會大量使用block主要是基于以下兩個原因:
可以直接在程式碼中撰寫等會要接著執(zhí)行的程式,直接將程式碼變成函數(shù)的參數(shù)傳入函數(shù)中,這是新API最常使用block的地方。
可以存取區(qū)域變數(shù),在傳統(tǒng)的callback實作時,若想要存取區(qū)域變數(shù)得將變數(shù)封裝成結構才能使用,而block則是可以很方便地直接存取區(qū)域變數(shù)。
X.3 宣告和建立Block
X.3.1 宣告Block的參考(Reference)
Block 變數(shù)儲存的是一個block的參考,我們使用類似宣告指標的方式來宣告,不同的是這時block變數(shù)指到的地方是一個函數(shù),而指標使用的是「*」,block則是使用「^」來宣告,下面是一些合法的block宣告:
1: /* 回傳void ,參數(shù)也是void 的block*/
2: void (^blockReturningVoidWithVoidArgument)( void );
3: /* 回傳整數(shù),兩個參數(shù)分別是整數(shù)和字元型態(tài)的block*/
4: int (^blockReturningIntWithIntAndCharArguments)( int , char );
5: /* 回傳void ,含有10 個block 的陣列,每個block 都有一個型態(tài)為整數(shù)的參數(shù)*/
6: void (^arrayOfTenBlocksReturningVoidWinIntArgument[ 10 ])( int );
7: X.3.2 建立一個Block
8:
9: 我們使用「^」來開始一個block,并在最后使用「;」來表示結束,下面的范例示范了一個block變數(shù),然后再定義一個block把它指定給block變數(shù):
10:
11: int (^oneFrom)( int ); /* 宣告block 變數(shù)*/
12: /* 定義block 的內(nèi)容并指定給上面宣告的變數(shù)*/
13: oneFrom = ^(int anInt)
14: {
15: return anInt = - 1 ;
16: };
X.3.3 全域的Block
我在可以在檔案中宣告一個全域的block,請參考以下范例:
1: int GlobalInt = 0 ;
2: int (^getGlobalInt)( void ) = ^ ( void ) { return GlobalInt ;};
X.4 Block 和變量
接下來的這一小節(jié)我們將會介紹block和變數(shù)之間的互動。
X.4.1 變數(shù)的型態(tài)
我們可以在block中遇到平常在函數(shù)中會遇到的變數(shù)類型:
l 全域(global)變數(shù)或是靜態(tài)的區(qū)域變數(shù)(static local)。
l 全域的函數(shù)。
l 區(qū)域變數(shù)和由封閉領域(enclosing scope)傳入的參數(shù)。
除了上述之外block額外支援了另外兩種變數(shù):
在函數(shù)內(nèi)可以使用__block 變數(shù),這些變數(shù)在block中是可被修改的。
匯入常數(shù)(const imports)。
此外,在方法的實作里,block可以使用Objective-C的實體變數(shù)(instance variable)。
下列的規(guī)則可以套用到在block中變數(shù)的使用:
可以存取全域變數(shù)和在同一領域(enclosing lexical scope)中的靜態(tài)變數(shù)。
可以存取傳入block的參數(shù)(使用方式和傳入函數(shù)的參數(shù)相同)。
在同一領域的區(qū)域變數(shù)在block中將視為常數(shù)(const)。
可以存取在同一領域中以__block 為修飾詞的變數(shù)。
在block中宣告的區(qū)域變數(shù),使用方式和平常函數(shù)使用區(qū)域變數(shù)的方式相同。
下面的例子介紹了區(qū)域變數(shù)(上述第三點)的使用方式:
1: int x = 123 ;
2: void (^printXAndY)( int ) = ^( int y)
3: {
4: printf ( "%d %d\n" , x, y);
5: };
6: // 將會印出123 456
7: printXAndY( 456 );
8: 就如上面第三點所提到的,在上例中的int x = 123的變量x,在傳入block后將視同常數(shù),因此若我們在block中試著去修改x的值時就會產(chǎn)生錯誤,下面的例子將會無法通過編譯:
9:
10: int x = 123 ;
11: void (^printXAndY)( int ) = ^( int y)
12: {
13: // 下面這一行是錯的,因為x 在這是一個常數(shù)不能被修改。
14: x = x + y;
15: printf ( "%d %d\n" , x, y);
16: };
若在block中想要修改上面的變數(shù)x,必須將x宣告加上修飾詞__block,請參考接下來這一小節(jié)的介紹。
X.4.2 __block 型態(tài)變數(shù)
我們可以藉由將一個由外部匯入block的變數(shù)放上修飾詞__block來讓這個變數(shù)由唯讀變成可以讀和寫,不過有一個限制就是傳入的變數(shù)在記憶體中必須是一個占有固定長度記憶體的變數(shù),__block修飾詞無法使用于像是變動長度的陣列這類不定長度的變數(shù),請參考下面的范例:
1: // 加上__block 修飾詞,所以可以在block 中被修改。
2: __block int x = 123 ;
3: void (^printXAndY)( int ) = ^( int y)
4: {
5: x = x + y;
6: printf ( "%d %d\n" , x, y);
7: };
8: // 將會印出579 456
9: printXAndY( 456 );
10: //x 將會變成 579;
11: 下面我們使用一個范例來介紹各類型的變數(shù)和block之間的互動:
12:
13: extern NSInteger CounterGlobal;
14: static NSInteger CounterStatic;
15: {
16: NSInteger localCounter = 42 ;
17: __block char localCharacter;
18: void (^aBlock)( void ) = ^( void )
19: {
20: ++ CounterGlobal ; //可以存取。
21: ++ CounterStatic ; //可以存取?!?/span>
22: CounterGlobal = localCounter; //localCounter在block 建立時就不可變了。
23: localCharacter = 'a' ; //設定外面定義的localCharacter 變數(shù)。
24: };
25: ++localCounter; //不會影響的block 中的值。
26: localCharacter = 'b' ;
27: aBlock(); //執(zhí)行block 的內(nèi)容。
28: //執(zhí)行完后,localCharachter 會變成'a'
29: }
X.4.3 物件和Block變數(shù)
Block 支援在Objective-C、C++物件和其他block中當作變數(shù)來使用,不過因為在大部分的情況我們都是使用Objective-C的撰寫程式,因此在這一小節(jié)我們僅針對Objective-C的情況進行介紹,至于其他兩種情況就留給有興趣的讀者再自行深入研究了。
x.4.3.1 Objective-C 物件
在擁有參考計數(shù)(reference-counted)的環(huán)境中,若我們在block中參考到Objective-C的物件,在一般的情況下它將會自動增加物件的參考計數(shù),不過若以__block為修飾詞的物件,參考計數(shù)則是不受影響。
如果我們在Objective-C的方法中使用block時,以下幾個和記憶體管理的事是需要額外注意的:
l 若直接存取實體變數(shù)(instance variable),self的參考計數(shù)將被加1。
l 若透過變數(shù)存取實體變數(shù)的值,則只變數(shù)的參考計數(shù)將被加1。
以下程式碼說明上面兩種情況,在這個假設instanceVariable是實體變數(shù):
1: dispatch_async (queue, ^{
2: // 因為直接存取實體變數(shù)instanceVariable ,所以self 的retain count 會加1
3: doSomethingWithObject (instanceVariable);
4: });
5: id localVaribale = instanceVariable;
6: dispatch_async (queue, ^{
7: //localVariable 是存取值,所以這時只有l(wèi)ocalVariable 的retain count 加1
8: //self 的 return count 并不會增加。
9: doSomethingWithObject (localVaribale);
10: });
X.5 使用Block
這一小節(jié)我們將會對block的使用方式做一些初步的介紹
X.5.1 呼叫一個Block
當block宣告成一個變數(shù)時,我們可以像使用一般函數(shù)的方式來使用它,請參考下面兩個范例:
1: int (^oneFrom)( int ) = ^( int anInt) {
2: return anInt - 1 ;
3: };
4: printf ( "1 from 10 is %d" , oneFrom( 10 ));
5: //結果會顯示:1 from 10 is 9
6: float (^distanceTraveled)( float , float , float ) = ^( float startingSpeed, float acceleration, float time)
7: {
8: float distance = (startingSpeed * time) + ( 0.5 * acceleration * time * time);
9: return distance;
10: };
11: float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );
12: //howFar會變成4.9
在一般常見的情況中,若是將block當做是參數(shù)傳入函數(shù),我們通常會使用「內(nèi)嵌」的方式來使用block。
X.5.2 將Block當作函數(shù)的參數(shù)
我們可以像使用一般函數(shù)使用參數(shù)的方式,將block以函數(shù)參數(shù)的型式傳入函數(shù)中,在這種情況下,大多數(shù)我們使用block的方式將不會傾向宣告block而是直接以內(nèi)嵌的方式來將block傳入,這也是目前新版SDK中主流的做法,我們將補充前面章節(jié)的例子來說明:
1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
2: qsort_b (myCharacters, 3 , sizeof ( char *),
3: ^( const void *l, const void *r)
4: {
5: char *left = *( char **)l;
6: char *right = *( char **)r;
7: return strncmp (left, right, 1 );
8: } // 這里是block 的終點。
9: );
10: // 最后的結果為:{"Charles Condomine", "George", "TomJohn"}
在上面的例子中,block本身就是函數(shù)參數(shù)的一部分,在下一個例子中dispatch_apply函數(shù)中使用block,dispatch_apply的定義如下:
1: void
2: dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t ));
3: 這個函數(shù)將一個block提交到發(fā)送佇列(dispatch queue)中來執(zhí)行多重的呼叫,只有當佇列中的工作都執(zhí)行完成后才會回傳,這個函數(shù)擁有三個變數(shù),而最后一個參數(shù)就是block ,請參考下面的范例:
4:
5: size_t count = 10 ;
6: dispatch_queue_t queue =
7: dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 );
8: dispatch_apply (count, queue, ^( size_t i) {
9: printf ( "%u\n" , i);
10: });
X.5.3 將Block當作方法的參數(shù)
在SDK中提供了許多使用block的方法,我們可以像傳遞一般參數(shù)的方式來傳遞block,下面這個范例示范如何在一個陣列的前5筆資料中取出我們想要的資料的索引值:
1: // 所有的資料
2: NSArray *array = [ NSArray arrayWithObjects : @"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil ];
3: // 我們只要這個集合內(nèi)的資料
4: NSSet *filterSet = [ NSSet setWithObjects : @"A" , @"B" , @"Z" , @"Q" , nil ];
5: BOOL (^test)( id obj, NSUInteger idx, BOOL *stop);
6: test = ^ ( id obj, NSUInteger idx, BOOL *stop) {
7: // 只對前5 筆資料做檢查
8: if (idx < 5 ) {
9: if ([filterSet containsObject : obj]) {
10: return YES ;
11: }
12: }
13: return NO ;
14: };
15: NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test];
16: NSLog ( @"indexes: %@" , indexes);
17: // 結果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]
18: // 前5筆資料中,有4筆符合條件,它們的索引值分別是0-1, 3-4
X.5.4 該避免的使用方式在下面的例子中,block是for回圈的區(qū)域變數(shù)因此在使用上必須避免將區(qū)域的block指定給外面宣告的block:
1: // 這是錯誤的范例,請勿在程式中使用這些語法!!
2: void dontDoThis() {
3: void (^blockArray[3])(void); // 3 個block 的陣列
4: for (int i = 0; i < 3; ++i) {
5: blockArray[i] = ^{ printf("hello, %d\n", i); };
6: // 注意: 這個block 定義僅在for 回圈有效。
7: }
8: }
9: void dontDoThisEither() {
10: void (^block)(void);
11: int i = random():
12: if (i > 1000) {
13: block = ^{ printf("got i at: %d\n", i); };
14: // 注意: 這個block 定義僅在if 后的兩個大括號中有效。
15: }
16: // ...
17: }
posted on 2012-05-31 13:47 Wangkeke 閱讀(23815) 評論(3) 編輯 收藏 引用 所屬分類: IOS