翻译:Blocks 简明实用指南(A Short Practical Guide to Blocks)

Block是C语言一个非常强大的特性,是Cocoa应用开发的一部分。他们类似Ruby、Python和Lisp等脚本和编程语言中的“闭包”和“lambdas”。虽然乍一看觉得block的语法和存储很神秘,其实你会发现在项目中使用block很容易。

下面将讨论block的高级特性并举例说明block典型使用方法。Block明确描述请参考block编程主题。

目录:

  • 为什么使用block?

  • block系统框架API

  • Block和并发性

为什么使用block?

Block是对象封装单元,或者从抽象的角度来看,是一段可以在任何时间执行的一段代码。从本质上来讲,block是便携的匿名函数,可以作为方法和函数传递的参数或返回值。Block本身有一个类型参数列表,可以推断或声明返回类型。也可以将block分配给一个变量,然后像调用函数一样调用block。

Block的语法标记是插入符号(^) 。例如,下面代码声明了一个block变量,有两个integer参数并返回一个integer值。在第二个插入符号后是参数列表,在大括号中是实现代码,并将这些分配给Multiply 变量。

<pre><code>
int (^Multiply)(int, int) = ^(int num1, int num2) {

return num1 * num2;

};

int result = Multiply(7, 4); // Result is 28.

</pre></code>

作为方法或函数的参数,block是一种回调,可以视为仅限于方法或函数的委托。通过block调用代码,可以定制一个方法或函数的行为。当block被调用,在适当的时间,方法或函数执行代码并通过block返回到调用代码,用于请求更多信息或获取特定应用程序行为。

Block作为方法或函数的参数有个优势是:在调用点提供回调代码。因为这段代码不需要在一个单独的方法或函数里实现,实现代码可以更简单和更容易理解。以通知的NSNotification为例,在“传统”的方法中,一个对象将本身作为通知的观察者,然后实现一个单独的方法(在 addObserver:.. 方法中标识为selector)来处理程序通知。

<pre><code>
-(void)viewDidLoad {

[super viewDidLoad];

[[NSNotificationCenter defaultCenter] addObserver:self

    selector:@selector(keyboardWillShow:)

    name:UIKeyboardWillShowNotification object:nil];

}

  • (void)keyboardWillShow:(NSNotification *)notification {

    // Notification-handling code goes here.

}
</pre></code>

使用addObserverForName:object:queue:usingBlock: 方法,可以以通知处理程序代码巩固调用方法。

<pre><code>
-(void)viewDidLoad {

[super viewDidLoad];

[[NSNotificationCenter defaultCenter] 

addObserverForName:UIKeyboardWillShowNotification

     object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) 

{

         // Notification-handling code goes here. 

}];

}
</pre></code>

相对于其他形式的回调,block具有更有价值的优势,block可以在局部语法范围内共享数据。如果实现的方法中定义一个block,这个block可以访问方法的局部变量和参数(包括堆栈变量),也可以访问函数和全局变量包括实例变量。这个访问默认是只读,但是声明一个__block修饰符变量,可以在block中修改该变量的值。即使在包括block的方法或函数已经返回并且局部作用域被销毁,只要有block的引用,局部变量作为block对象的一部分也会一直存在。

Block系统框架API

使用block显而易见的动机是,越来越多的系统框架采用block作为参数的方法和函数。在框架方法中可以找到很多block的用例。

  • 完成处理程序

  • 通知处理程序

  • 错误处理程序

  • 枚举

  • 视图动画和过渡

  • 排序

接下来的章节描述了各种情况。在那之前,这里有一个关于在框架方法中声明block的概述。考虑以下NSSet类的方法:
<pre><code>
-(NSSet *)objectsPassingTest:(BOOL (^)(id obj, BOOL *stop))predicate
</pre></code>

Block声明表明,方法传递一个动态类型对象和一个引用boolean值到block(每个枚举项),block返回一个boolean值。(这些参数和返回值实际上包含在枚举中。)当指定block,以插入符号开始,加上括号的参数列表,以及由大括号封闭的block代码本身。

<pre><code>
[mySet objectsPassingTest:^(id obj, BOOL *stop) {

// Code goes here: Return YES if obj passes the test and NO if obj does not pass the 

test.

}];

</pre></code>

完成和错误处理程序

完成处理程序是一种回调机制,当框架方法或函数完成时,允许客户端完成某些行为。通常客户端使用完成处理程序来释放状态或更新用户界面。一些框架方法用block来实现完成处理程序(或者说代理方法或通知处理程序)。

UIView类有多个动画和视图切换的类方法,这些类方法使用完成处理程序block作为参数。(视图动画和过渡中有这些方法的概述。)在列表1-1中的示例中显示了 animateWithDuration:animations:completion:方法的实现。本例中的完成处理程序在动画结束几秒后重置动画视图到原始位置和透明度值(alpha)。

列表1-1 完成处理程序

<pre><code>
-(IBAction)animateView:(id)sender {

CGRect cacheFrame = self.imageView.frame;

[UIView animateWithDuration:1.5 animations:^{

    CGRect newFrame = self.imageView.frame;

    newFrame.origin.y = newFrame.origin.y + 150.0;

    self.imageView.frame = newFrame;

    self.imageView.alpha = 0.2;

}

                 completion:^ (BOOL finished) {

                     if (finished) {

                         // Revert image view to original.

                         self.imageView.frame = cacheFrame;

                         self.imageView.alpha = 1.0;

                     }

}];

}

</pre></code>

一些框架方法有错误处理程序,该程序是类似完成处理程序的block参数。当因为一些错误条件而不能完成任务时,该方法调用他们(并传递一个NSError对象)。通常通过错误处理程序来通知用户错误信息。

通知处理程序

当设置观察者时,NSNotificationCenter对象的addObserverForName:object:queue:usingBlock: 方法实现通知处理程序。列表1-2说明了如何调用这个方法,即为通知定义一个block处理程序。正如通知处理方法一样,传入一个NSNotification对象。该方法也创建一个NSOperationQueue实例,这样应用程序可以在指定上下文处运行block处理程序。

列表1-2 添加一个对象作为观察者,并使用block处理通知

<pre><code>
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {

opQ = [[NSOperationQueue alloc] init];

[[NSNotificationCenter defaultCenter] addObserverForName:@"CustomOperationCompleted"

         object:nil queue:opQ

    usingBlock:^(NSNotification *notif) {

    NSNumber *theNum = [notif.userInfo objectForKey:@"NumberOfItemsProcessed"];

    NSLog(@"Number of items processed: %i", [theNum intValue]);

}];

}

</pre></code>

枚举

基础框架的集合类——NSArray、NSDictionary、NSSet和NSIndexSet,声明的方法执行集合特定类型的枚举值和为客户端指定block提供代码处理或测试每个枚举项。换句话说,该方法执行fast-enumeration构造方法。

<pre><code>
for (id item in collection) {

// Code to operate on each item in turn.

}
</pre></code>

一般有两种带block的枚举方法。第一种方法是命名以枚举开头并且没有返回值。这些方法的block在每个枚举项上执行一些工作。第二种方法是以passingTest开头,这种方法返回一个整数或一个NSIndexSet对象。这些方法的block针对每个枚举项进行测试,如果通过测试则返回YEStrue。用整数或索引标识通过测试的源集合中的对象或对象集。

列表1-3中的代码针对每种类型调用一个NSArray方法。第一个方法的block(“通过测试”方法)如果数组中每个string有某一前缀则返回YEStrue。随后的代码创建一个临时数组使用方法返回的索引。第二个方法的block除去每个string的前缀,并将其添加到一个新的数组。

列表1-3中的代码调用每种类型的NSArray方法。

使用两个block处理枚举数组

<pre><code>
NSString *area = @"Europe";

NSArray *timeZoneNames = [NSTimeZone knownTimeZoneNames];

NSMutableArray *areaArray = [NSMutableArray arrayWithCapacity:1];

NSIndexSet *areaIndexes = [timeZoneNames

indexesOfObjectsWithOptions:NSEnumerationConcurrent

                            passingTest:^(id obj, NSUInteger idx, BOOL *stop) {

NSString  *tmpStr = (NSString *)obj;

return [tmpStr hasPrefix:area];

}];

NSArray *tmpArray = [timeZoneNames objectsAtIndexes:areaIndexes];

[tmpArray enumerateObjectsWithOptions:NSEnumerationConcurrent|NSEnumerationReverse

                       usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

                           [areaArray addObject:[obj substringFromIndex:[area length]

+1]];

}];

NSLog(@"Cities in %@ time zone:%@", area, areaArray);

</pre></code>

在这些枚举方法中的停止参数(这个例子中没有使用)允许block传递YEStrue回方法,告知方法退出枚举。当仅仅想在集合中寻找符合条件的第一项。

NSString类虽然不代表一个集合,但有两个带block参数的方法,方法名字以enumerate: enumerateSubstringsInRange:options:usingBlock:enumerateLinesUsingBlock:开头。第一种方法列举了一个指定粒度的文本单元中的字符串(行,段落,单词,句子等等)。第二种方法仅列举了行。列表1-4演示了如何使用第一种方法。

列表1-4使用block查找匹配的子字符串

<pre><code>
NSString *musician = @"Beatles";

NSString *musicDates = [NSString stringWithContentsOfFile:

@"/usr/share/calendar/calendar.music"

encoding:NSASCIIStringEncoding error:NULL];

[musicDates enumerateSubstringsInRange:NSMakeRange(0, [musicDates length]-1)

options:NSStringEnumerationByLines

usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL 

*stop) {

       NSRange found = [substring rangeOfString:musician];

       if (found.location != NSNotFound) {

            NSLog(@"%@", substring);

       }

  }];

</pre></code>

视图动画和过渡

在iOS4.0中的UIView类介绍了几个带block的类方法,这些方法用于动画和视图过渡。Block参数有两种(不是所有方法都带这两种参数):

  • Block改变视图属性呈现动画

  • 完成处理程序

列表1-5中显示了animateWithDuration:animations:completion:的调用。改方法有两种block参数。在这个例子中,在动画中(通过指定alpha为0)使视图消失,在完成处理程序中将它从子视图中删除。

列表1-5使用block视图的简单动画

<pre><code>
[UIView animateWithDuration:0.2 animations:^{

    view.alpha = 0.0;

} completion:^(BOOL finished){

    [view removeFromSuperview];

}];

</pre></code>

其他UIView类方法在两个视图过渡中执行,包括翻转和旋转。在列表1-6例子中调用transitionWithView:duration:options:animations:completion: ,用向左翻转动画来实现子视图的替换。(并没有实现完成处理程序。)

列表1-6实现两个视图过渡的翻转效果

<pre><code>
[UIView transitionWithView:containerView duration:0.2

               options:UIViewAnimationOptionTransitionFlipFromLeft

            animations:^{

                [fromView removeFromSuperview];

                [containerView addSubview:toView]

            }

            completion:NULL];

</pre></code>

排序

基础框架声明了NSComparator类型用于比较两个项目。
<pre><code>typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);</pre></code>

NSComparator是有两个对象并返回NSComparisonResult值的block类型。它是NSSortDescriptor, NSArrayNSDictionary方法的一个参数,并被这些类的实例用于排序。列表1-7给出了使用例子。

列表1-7使用NSComparator block对数组进行排序

<pre><code>
NSArray *stringsArray = [NSArray arrayWithObjects:

                             @"string 1",

                             @"String 21",

                             @"string 12",

                             @"String 11",

                             @"String 02", nil];

static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch |
NSNumericSearch |

    NSWidthInsensitiveSearch | NSForcedOrderingSearch;

NSLocale *currentLocale = [NSLocale currentLocale];

NSComparator finderSort = ^(id string1, id string2) {

NSRange string1Range = NSMakeRange(0, [string1 length]);

return [string1 compare:string2 options:comparisonOptions range:string1Range 

locale:currentLocale];

};

NSLog(@"finderSort: %@", [stringsArray sortedArrayUsingComparator:finderSort]);

</pre></code>

这个例子来自block编程主题。

Block和并发性

Block是封装在可异步执行的工作单元中的匿名可移植对象。因为这个基本事实,block是GCD和NSOperationQueue类的中心要素,推荐这两种技术进行并发处理。

GCD的两个核心功能,dispatch_sync(3) OS X 发工具手册页面(同步调用)或dispatch_async(3) OS X 发工具手册页面(异步调用)作为block的第二参数。

NSOperationQueue是一个对象,用于安排任务并发执行或按照依赖关系定义的顺序执行。NSOperation对象经常使用block来执行任务。

更多关于CCD,可参阅NSOperationQueue和NSOperation。

官方原文地址:

https://developer.apple.com/library/ios/featuredarticles/Short_Practical_Guide_Blocks/

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,138评论 30 470
  • 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C C...
    GrayLand阅读 1,613评论 1 10
  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 3,852评论 1 22
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,937评论 6 13