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, NSArray和NSDictionary方法的一个参数,并被这些类的实例用于排序。列表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/