苹果在Mac OS X10.6 和iOS 4之后引入block语法,之后就大幅改变了OC 的编程方式。Block 是Cocoa和Cocoa框架的匿名函数的实现。所谓匿名函数,就是一段具有对象性质的代码段,一方面这一段代码可以当作函数来执行,另一方面,又可以当作对象来进行传递,所以可以让某代码段变成某个对象的属性,或是当作方法或者是函数的参数传递,也是因为这种特性,我们使用block 来实现回调。
在Block之前,最常见的是使用代理来处理回调(或者使用较具c语言风格的方式,传递回调函数的指针,或者使用target/action pattern)。在iOS 4有了block之后,苹果改写了UIKit的api,把原来使用代理的地方换成了使用block。
Block 语法
一直以来还是有不少人不满block的语法,甚至有人搞了个叫做http://fuckingblocksyntax.com 的网站,“fucking” 呵呵。
将block定义成变量:
定义成property 的语法:
定义成方法的参数的语法是:
在执行某个需要传入block当作参数的方法的时候 ,则是用以下这种方式调用。这也是绝大多数用block当作回调的处理方式:
把一种block定义成typedef:
Block 也可以當成 C 函数的参数或是返回值的类型,但是,在這種狀況下, 我們不能夠直接使用 returnType (^)(parameterTypes) 这种语法,必须要先定义成 typedef 才行。也就是說,这样是不合法的:
(void (^)(void)) test ( (void (^)(void)) block) {
return block;
}
但可以写成这样:
typedef void (^TestBlock)(void);
TestBlock test(TestBlock block) {
return block;
}
虽然 C 语言的函数 的参数不能够使用 returnType (^)(parameterTypes) 语法,但是一 個 block 倒是可以使用这种语法编写输入与返回值的类型,但其实在这种情况下, 还是会比较建议使用 typedef 定义。比方說,我們現在要定义一個 block,這個 block 会返回另外一个类型为 int(^)(void) 的 block,就会写成这样:
int (^(^counter_maker)(void))(void) = ^ {
__block int x = 0;
return ^ {
return ++x;
};
};
但是这样做 ,可读性极差,我们再来试试下面这这种吧:
typedef int (^CounterMakerBlock)(void);
CounterMakerBlock (^counter_maker)(void) = ^ {
__block int x = 0;
return ^ {
return ++x;
};
};
Block 如何代替了 Delegate
要想知道block的使用场景,不妨先看看苹果官方是怎样使用的。
首先是UIView 动画。当我们想改变一个ui 控件的frame,并加上一些动画,让其显得不那么生硬,我们常常会使用到UIView Animation.
栗如,我们想改变某个subView的frame:
self.subview.frame = CGRectMake(10, 10, 100, 100);
在ios4的时代,我们需要使用UIView的+beginAnimations:context: 与 +commitAnimations 两个类方法,把原本的代码 包起來,那么,在这两个类方法之间的代码就会产生动画效果。
[UIView beginAnimations:@"animation" context:nil];
self.subview.frame = CGRectMake(10, 10, 100, 100);
[UIView commitAnimations];
倘若我们想要在这段动画结束的时候去做一件事情,比如执行另一个动画,我们应如何呢?ios 4 之前是遵守UIView代理,实现其中的animationDidStop代理方法。
- (void)moveView {
[UIView beginAnimations:@"animation" context:nil]; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)]; self.subview.frame = CGRectMake(10, 10, 100, 100); [UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{ // do something
}
由此可见 ,使用代理设计模式,最终的结果会导致代码分散。但是block之后,我们可以将“动画该做什么”和”动画完成之后该做什么写道一起了:
- (void)moveView { [UIView animateWithDuration:0.25 animations:^{ self.subview.frame = CGRectMake(10, 10, 100, 100); } completion:^(BOOL finished) { // Do something }]; }
另外像NSArray的排序,以往我们必须使用c函数指针或者是selector的方式:
NSArray*arr = [[NSArrayalloc] initWithObjects:@1, @2, @3,nil];
SELsel =@selector(compare:);
arr = [arr sortedArrayUsingSelector:sel];
也可用block:
NSArray*arr = [[NSArrayalloc] initWithObjects:@1, @2, @3,nil];
NSArray *arr= [array sortedArrayUsingComparator: ^NSComparisonResult(id obj1, id
obj2) {
return [obj1 compare:obj2];
}];
什么时候用 Blocks,什么时候用 Delegate?
即使block可以取代代理处理回调,但是苹果自己的api设计中可以看到,并不是所有的代理都被block的取代,在cocoa与cocoatouch中,仍然大量使用代理。那么我们就要问了:究竟什么时候我们该用代理,什么时候用block呢
区分它们的方法是:如果我们调用一个方法,这个方法只有一个单一的回调,那么就使用block,如果可能有多个不同的回调,那就使用代理。
好处是:如果我们调用一个方法,这个方法只有一个单一的回调,很有可能是有一些回调是非必须实现的。如果是使用代理,那么,在代理需要实现的protocol中,我们可以用@required与@optional区分哪些是需要实现的代理方法;但如果用block就很难做出这样的区分了,尤其是在xcode6.3之前,oc还没有nullable,nonnull等关键字,去让我们知道某个property,或者某个方法的传入参数block可否为nil,我们也搞不清传入nil会发生什么可怕的事情。
举个栗子。在ios 7之后,苹果鼓励开发者使用NSURLSession 处理网络连接,NSURLSession就体现了单一回调使用block,多重回调使用代理这一点。
如果我们要从后台获取数据,我们只需创建一个NSURLSessionDataTask类的对象,一般来说,我们只需要处理「这个链接做完事情的 下一步该做什么」,所以一般我们只要实现这个task 的 completion handler,就是链接完成后要执行的block;一般链接结束后就是成功获取数据或者链接失败两种情况:
NSURL *URL = [NSURL URLWithString:@"http://baidu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask*task = [[[NSURLSession sharedSession]dataTaskWithRequest:request completionHandler:^(NSData* _Nullable data, NSURLResponse * _Nullable response,NSError* _Nullable error)
{
// code after completion of task
}];
[task resume];
但,NSURLSession 本身还有 delegate。。我們在发送链接的時候,除了处理联线结束要做什麼之外,有時候也可能会处理中途发生的各种状况,像 是:HTTP 收到 302 转址、遇到有问题的 SSL验证、server 要求用⼾输入账户密码,這些狀況我們要不要提示使用者?或,如果這是一個传递大文档、很花時間 的链接,我們有沒有必要上传进度?這些狀況还是會传递給给NSURLSession 的 delegate,而如果我們要处理這些狀況,就要实现以下這些 代理方法。
1.当接收到服务器响应的时候调用
session:发送请求的session对象
dataTask:根据NSURLSession创建的task任务
response:服务器响应信息(响应头)
completionHandler:通过该block回调,告诉服务器端是否接收返回的数据
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnullvoid(^)(NSURLSessionResponseDisposition))completionHandler;
2.当接收到服务器返回的数据时调用 该方法可能会被调用多次
-(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data;
3.当请求完成之后调用该方法不论是请求成功还是请求失败都调用该方法,如果请求失败,那么error对象有值,否则那么error对象为空
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
网友评论