Object-c 中的block就好像一段C函式般,由函式名,有返回值,有引數,由函式體等
1.簡單的block
1 ^(int A ,int B) 2 { 3 int C=A*B; 4 return C; 5 };
上述程式碼表示block有兩個整形引數A和B.在block體中進行A和B的相乘,將結果作為block的返回值返回出去。
2.將block作為引數的API
在程式開發時,當需要一個NSArray物件的所有元素進行遍歷時,除了for迴圈,開發者可以使用block進行遍歷,程式碼如下:
1 NSArray *arrChar=[@"A/B/C/D/E/F" componentsSeparatedByString:@"/"]; 2 //遍歷元素 3 [arrChar enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 4 NSLog(@"\nindex:[%d], value:[%@]",idx,obj); 5 if(idx == 4) 6 { 7 *stop=YES; 8 } 9 }];
這段程式碼執行後,在控制檯上留下了如下日誌:
index:[0], value:[A] index:[1], value:[B]; index:[2], value:[C]; index:[3], value:[D]; index:[4], value:[E];
這裡,NSArray物件所使用的API主要是一個對自己元素的遍歷功能,enumerateObjectsUsingBlock方法的引數需要是一個block物件,在NSArray的幫助文件中,我們找到了enumerateObjectsUsingBlock的宣告,內容如下:
-(void)enumerateObjectsUsingBlock:(void)(^(id obj,NSInterger idx,BOOL *stop))block
所傳入的3個引數中,前兩個是表明當前元素指標和序號的輸入引數,開發者直接拿來使用即可。最後一個引數講一個布林型別的指標傳遞進來,顯然是屬於一個典型的輸入引數。這個引數讓開發者可以靈活的控制遍歷的繼續執行是否。
3.block的宣告
block在宣告時,需要遵循如下所示的格式結構。
<返回值型別> + (^<block名字>) + (<引數型別1>, <引數型別2>...)
根據enumerateObjectsUsingBlock的結構,我們宣告的block變數物件如下:
void (^arrayEnumerateBlock)(id,NSUInteger,BOOL *);
對於宣告完的block變數,可以直接進行初始化,也可以進行賦值。block的初始化格式如下:
void (^arrayEnumerateBlock)(id,NSUInteger,BOOL *)=^(id anObject, NSUInteger index, BOOL *isStop) { NSLog(@"\nindex:[%d], value:[%@]", index, anObject); //序號4, 則停止遍歷 if(index == 4) { *isStop=YES; } };
上述程式碼中對於變數的初始化,開發者同樣需要加上“^”符號表明這是一個block型別。不過block的返回值和block名不用寫明,block名作為宣告的一部分已經在宣告時寫明,至於返回值,只需要在block體中嚴格遵守返回型別即可。
4.block 的 typedef
在平時開發中,我們習慣於使用typedef定義屬於自己框架的東西,本質上其實是為了型別使用更加統一。
而block由於有著相對複雜的宣告方式,不如可以考慮將block的格式進行typedef,之後所有需要遵循這個block格式的物件宣告再也不需要考慮格式的撰寫,並且由於block有著函式指標般的介面協議特徵,使用typedef定義過的block格式,當作為介面提供外部呼叫時,呼叫者可以明確具體需要給與的引數格式。
同樣是enumerateObjectsUsingBlock所用的那個block格式,typedef定義示例如下
typedef void (^ArrayEnumerateBlockType)(id ,NSUInteger, BOOL *);
雖然typedef後接著的格式和前一小節中block宣告格式相當相似,不過typedef所書寫的內容是型別名字,而block宣告時所書寫的是變數物件的名字。所以在這裡,我們特意以ArrayEnumerateBlockType定義型別名和之前的arrayEnumerateBlock變數型別名加以區分。
一旦有了typedef過得block型別後,到哪裡都能夠簡單的使用這個block了,初始化程式碼如下:
ArrayEnumerateBlockType aEnumerateBlock=^(id aObject, NSUInteger index, BOOL *isStop) { //跟前面一樣 }
5.block體的外部變數使用的奇怪之處
首先,讓我們來看看以下程式碼。
1 NSUInteger result = 0; 2 NSUInteger changeValue=0; 3 //block變數宣告 4 NSUInteger (^testReturnValueBlock)(NSUInteger, NSUInteger) = ^(NSUInteger param1, NSUInteger param2) 5 { 6 return param1+param2+changeValue; 7 }; 8 9 result=testReturnValueBlock(1,2); 10 NSLog(@"1.[%d]",result); 11 12 changeValue++; 13 result=testReturnValueBlock(1,2); 14 NSLog(@"2.[%d]",result);
執行結果卻是
1.[3] 2.[3]
是不是覺得相當奇怪,我明明在第一次日誌列印後對changeValue變數執行了++操作,其實列印的結果並沒有錯,我們需要更深入的瞭解block內部機制才能夠看懂這其中的奧妙。
在block體中,我們不僅能夠訪問到block宣告的傳入引數,也能夠正常訪問到block以外的變數,不過,對於block以外的變數來說,如果把它放在block內進行訪問也罷賦值也罷,一旦進入了block體中,基本型別變數會被block進行一次copy後以一個臨時變數存放在block體中,而指標變數會被block進行一次retain後也以一個臨時變數存放起來,無論是基本資料型別還是指標,被block使用了,就表明他的生命週期除了自己本身所在的作用域,又多了一個block體的作用域,也就是說:
(1)基本資料型別在block中的地址已經發生變化,所以block體外對於此資料型別的值修改對於體內的值毫無影響。
(2)block所copy或者retain的變數,一旦block結束,也就一起跟著被釋放和銷燬了。
(3)所謂的block會進行retain的指標型別,也包含Object-c中的所有物件。
6.克服外部變數的魔咒
(1)可以把外部變數修改成 static NSUInteger changeVaue=0;
static 關鍵字意味著changeValue的地址不再被我們放置於棧中,不過也並不在堆中,而是放在全域性資料區,擁有一個永遠不會改變的地址。
(2)也可以在外部變數前加上 __block 如: __block NSUInteger changeValue=0;
當一個變數被__block所修飾時,block體中就會知道,即使使用到這個外部變數,也堅決不會去進行retain或者copy,這樣就能夠保證內外統一了