天天看点

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