天天看點

iOS Block部分

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的結構就用這張圖來說明

iOS 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();});