1 什麼是block
對于閉包(block),有很多定義,其中閉包就是能夠讀取其它函數内部變量的函數,這個定義即接近本質又較好了解。對于剛接觸Block的同學,會覺得有些繞,因為我們習慣寫這樣的程式main(){ funA();} funA(){funB();} funB(){.....}; 就是函數main調用函數A,函數A調用函數B... 函數們依次順序執行,但現實中不全是這樣的,例如項目經理M,手下有3個程式員A、B、C,當他給程式員A安排實作功能F1時,他并不等着A完成之後,再去安排B去實作F2,而是安排給A功能F1,B功能F2,C功能F3,然後可能去寫技術文檔,而當A遇到問題時,他會來找項目經理M,當B做完時,會通知M,這就是一個異步執行的例子。在這種情形下,Block便可大顯身手,因為在項目經理M,給A安排工作時,同時會告訴A若果遇到困難,如何能找到他報告問題(例如打他手機号),這就是項目經理M給A的一個回調接口,要回掉的操作,比如接到電話,百度查詢後,傳回網頁内容給A,這就是一個Block,在M交待工作時,已經定義好,并且取得了F1的任務号(局部變量),卻是在當A遇到問題時,才調用執行,跨函數在項目經理M查詢百度,獲得結果後回調該block。
2 block 實作原理 Objective-C是對C語言的擴充,block的實作是基于指針和函數指針。
從計算語言的發展,最早的goto,進階語言的指針,到面向對象語言的block,從機器的思維,一步步接近人的思維,以友善開發人員更為高效、直接的描述出現實的邏輯(需求)。
3 block如何聲明
block的結構就用這張圖來說明
int (*CFunc)(int a) 函數調用 int result = CFunc(10);
int (^BFunc)(int a) 函數調用 int result = BFunc(10);
block用法1
void (^myblocks) (void) = NULL;
myblocks = ^(void) {
NSLog(@"in blocks");
};
NSLog(@"before myblocks");
myblocks();
NSLog(@"after myblocks");
int (^myblocks2) (int a, int b) = ^(int a, int b) {
int c = a + b;
return c;
};
NSLog(@"before blocks2");
int ret = myblocks2(10, 20);
NSLog(@"after blocks2 ret %d", ret);
//此處如果不加__block會報錯
__block int sum = 0;
int (^myblocks3) (int a, int b) = ^(int a, int b) {
sum = a + b;
return 3;
};
myblocks3(20, 30);
NSLog(@"sum is %d", sum);
對于這種用法,本人不才,基本沒有在實際項目中用過.因為我的了解這實際意義不大(如果是這種簡單的計算),唯一作用是提高程式的并發性.
回調是block一個重要的特性.
block回調是我在網絡請求中用得最多的.
這裡先用前輩在ASIHTTP中的block為例子.
定義block
#if NS_BLOCKS_AVAILABLE
typedef void (^ASIBasicBlock)(void);
typedef void (^ASIHeadersBlock)(NSDictionary *responseHeaders);
typedef void (^ASISizeBlock)(long long size);
typedef void (^ASIProgressBlock)(unsigned long long size, unsigned long long total);
typedef void (^ASIDataBlock)(NSData *data);
#endif
此定義可以是全局的block,
這裡隻用了上門定義的其中一個block為例,作為ASIHTTPRequest的一個屬性.(意思是block可以作為一個對象的屬性存在)
@interface ASIHTTPRequest : NSOperation {
ASIBasicBlock completionBlock;
}
//向外暴露一個接口 這裡是為回調留一接口方法,其實也可以不用留
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock;
方法實作(這裡用release是為開啟arc)
@implementation ASIHTTPRequest
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock
{
[completionBlock release];
completionBlock = [aCompletionBlock copy];
}
這裡是網絡請求完成後的方法,在這裡實作我們block的調用.
- (void)reportFinished
{
#if NS_BLOCKS_AVAILABLE
if(completionBlock){
completionBlock();
}
#endif
}
@end
如下,然後你可以在你請求網絡的類實作中,實作網絡請求并擷取網絡請求成功後的回調, 這裡可以回傳參數變量(我們這個例子block是回傳的參數為void,--typedef void (^ASIBasicBlock)(void)--).在這裡回傳參數就是,上面說的參數共享,就象周遊數組,這樣也許你更能了解
[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
}];
這裡的obj 和idx 以及stop都可以為你在回調時候所用,就好比代理delegate回傳參數一樣.
ASIHTTPRequest *request = [self requestWithUrl:url];
//請求成功
[request setCompletionBlock:^{
switch (request.responseStatusCode) {
case RequestStatus_OK:
complete(request.responseString);break;
case RequestStatus_ErrorRequest:
failed(@"錯誤的請求");break;
case RequestStatus_NotFound:
failed(@"找不到指定的資源");break;
case RequestStatus_Error:
failed(@"内部伺服器錯誤");break;
default:
failed(@"伺服器出錯");break;
}
}];
//請求失敗
[request setFailedBlock:^{
failed(@"網絡連接配接失敗");
}];
這裡的FailedBlock我們沒有舉例實作.
看了前輩的block實作,兄弟夥寫是否有所了解?接下來看看,我寫的一個block的簡單用法.
第一種,接網絡請求的block自己再次封裝的方法.把block作為方法參數使用.
@interface JHttpRequest : NSObject
//異步Get請求
+ (ASIHTTPRequest *)asyncGetRequest:(NSString *)url complete:(void (^)(NSString *responseStr))complete failed:(void (^)(NSString *errorMsg))failed;
//異步Get請求帶緩存
//異步Post請求
//異步Post上傳圖檔
//異步下載下傳請求
//生成異步請求對象
@end
//發送異步Get請求
+ (ASIHTTPRequest *)asyncGetRequest:(NSString *)url
complete:(void (^)(NSString *responseStr))complete
failed:(void (^)(NSString *errorMsg))failed{
ASIHTTPRequest *request = [self requestWithUrl:url];
[request setUserAgent:UserAgent];
[request setTimeOutSeconds:5.];
//請求成功
[request setCompletionBlock:^{
switch (request.responseStatusCode) {
case RequestStatus_OK:
complete(request.responseString);break;
case RequestStatus_ErrorRequest:
failed(@"錯誤的請求");break;
case RequestStatus_NotFound:
failed(@"找不到指定的資源");break;
case RequestStatus_Error:
failed(@"内部伺服器錯誤");break;
default:
failed(@"伺服器出錯");break;
}
}];
//請求失敗
[request setFailedBlock:^{
failed(@"網絡連接配接失敗");
}];
//開始請求
[request startAsynchronous];
return request;
}
下面是你需要調用網絡接口的viewcontroller種對上面網絡請求block方法的調用
[JHttpRequest asyncGetRequest:url complete:^(NSString *responseStr) {
if (responseStr) {
NSDictionary *getDic = [responseStr JSONValue];
if ([[getDic objectForKey:@"success"] boolValue]) {
//getDic 請求成功取得的json資料
}else{
faild([getDic objectForKey:@"msg"]);
}
}else{
faild(@"擷取資訊失敗");
}
} failed:^(NSString *errorMsg) {
faild(errorMsg);
}];
綜上例子所訴,block可以作為一個方法參數使用,能更友善地去取得回調資料.
第二種,把block作為類的屬性來使用.
//定義一個block作為viewcontroler的一個屬性.
@interface FirstViewController : UIViewController{
//定義一個blocks變量
void (^BarkCallback) (FirstViewController *thisDog, int count);
NSInteger barkCount;
}
@property(nonatomic , copy) void (^BarkCallback) (FirstViewController *thisDog, int count);
@end
其中void (^BarkCallback) (FirstViewController *thisDog, int count);thisDog count是參數.
@implementation FirstViewController
@synthesize BarkCallback;
- (void)viewDidLoad
{
[super viewDidLoad];
barkCount = 0;
//這裡我們啟動一個定時器友善回調看效果
[NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES];
}
-(void) updateTimer:(id) arg
{
barkCount ++;
if (BarkCallback) {
//調用從其他類傳過來的BarkCallback
BarkCallback(self, barkCount);
}
}
@end
實作block
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];
viewController.BarkCallback = ^(FirstViewController *thisDog, int count) {
NSLog(@"count == %d", count);
};
把block作為類的屬性來使用.還有一種表達方法.
typedef void (^BarkCallback) (FirstViewController *thisDog, int count);
@interface FirstViewController : UIViewController{
NSInteger barkCount;
}
@property (nonatomic, copy) BarkCallback completionHandler;
@end
@implementation FirstViewController
@synthesize completionHandler;
- (void)viewDidLoad
{
[super viewDidLoad];
barkCount = 0;
[NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES];
}
-(void) updateTimer:(id) arg
{
barkCount ++;
if (completionHandler) {
completionHandler(self, barkCount);
}
}
@end
實作block
FirstViewController *viewController = [[FirstViewController alloc]initWithNibName:@"FirstViewController" bundle:nil];
BarkCallback myab = ^(FirstViewController *thisDog, int count) {
NSLog(@"count == %d", count);
};
viewController.completionHandler = myab;
源碼下載下傳位址: https://github.com/pjw540566709/BlockText
4 使用block和其它方式的一些比較:
1 使用block和使用delegate完成委托模式有什麼優點? 首先要了解什麼是委托模式,委托模式在iOS中大量應用,其在設計模式中是擴充卡模式中的對象擴充卡,Objective-C中使用id類型指向一切對象,使委托模式更為簡潔。了解委托模式的細節: 可以去看我的另一篇部落格 iOS設計模式 使用block實作委托模式,其優點是回調的block代碼塊定義在委托對象函數内部,使代碼更為緊湊; 适配對象不再需要實作具體某個protocol,代碼更為簡潔。
2 多線程與block GCD與Block 使用 dispatch_async 系列方法,可以以指定的方式執行block GCD程式設計執行個體
dispatch_async的完整定義 void dispatch_async( dispatch_queue_t queue, dispatch_block_t block); 功能:在指定的隊列裡送出一個異步執行的block,不阻塞目前線程
通過queue來控制block執行的線程。主線程執行前文定義的 finishBlock對象 dispatch_async(dispatch_get_main_queue(),^(void){finishBlock();});