谈谈Block(一)

  苹果在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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容