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,這樣就能夠保證內外統一了