本文档版权归NickTang所有,没有本人书面或电子邮件允许,不许转载,摘录,发表。多谢!
一段时间以来,块代码已经成为Ruby,Python,Lisp等脚本语言和编译语言中的一部分(在这些语言中,可能被命名为“closures”或“lambdas”)。从Mac OS X v10.6和iOS 4.0开始,块代码,一个强大的C语言功能点,已经是Cocoa应用开发的一部分了。虽它的语法初看起来有点奇怪,但是你会发现它是很好用的。
下面的讨论都是大概的描述,如果你希望非常详细,定义性的解释,请参看Blocks Programming Topics。
为何使用块代码?
块代码是一个能工作的代码单元,可以在任何时候被执行。它们本质上是轻量级的,匿名的函数,并且可以作为函数的参数,或者返回值。块代码本身可能有一个参数列表,返回类型,或者没有返回值。你可以把块代码赋给一个变量,并在合适的时候调用它,就行调用一个普通函数一样。
插入符(^)被用来做为块代码的开始标记。例如,下面的代码就声明了一个块块代码,它有两个整形的参数,返回类型也是整形。这里在第一个插入符后面列出参数列表,在一对大括号中包含实现代码,并且把这个快代码赋值给变量Multiply
:
int (^Multiply)(int, int) = ^(int num1, int num2) {
|
return num1 * num2;
|
};
|
int result = Multiply(7, 4); // result is 28
|
作为函数或者方法的参数的时候,块代码其实就是一个回调类型的函数,可以使用在局限于函数或方法类型的代理上。作为参数传入后,在调用块代码,可以使得函数和方法可以实现个性化运行。当调用这些函数或方法的时候,它们会在合适的时候,执行这些块代码,去取的附加信息,或者执行特定的行为。
使用块代码作为函数或方法的参数的一个好处就是,你可以在调用函数或方法的地方写回调代码。由于这些代码不需要在额外的函数或方法中,所以这样的实现方式简单易懂。使用通知中类NSNotification
作为一个例子。在过去的模式下,一个对象把自己加入到一个通知的观察者对象中,它需要实现一个额外的函数(在调用addObserver:..
函数的时候使用选择器作为参数传入)去处理这个消息:
- (void)viewDidLoad {
|
[super viewDidLoad];
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
selector:@selector(keyboardWillShow:)
|
name:UIKeyboardWillShowNotification object:nil];
|
}
|
|
- (void)keyboardWillShow:(NSNotification *)notification {
|
// Notification-handling code goes here.
|
}
|
在新的函数addObserverForName:object:queue:usingBlock:
中,你可以替换通知的回调函数为下面的形式:
- (void)viewDidLoad {
|
[super viewDidLoad];
|
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification
|
object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
// Notification-handling code goes here.
|
}];
|
}
|
使用块代码的另一个好处是,块代码可以共享本地作用域内有效的变量。如果你在一个函数中实现了一个块代码,这个块代码可以使用本地变量和函数参数(即栈上的变量),还有在函数中能用包含实例变量等的全局变量。块代码在使用上述变量的时候是只读的,即不能修改这些变量,不过如果你使用__block来修饰变量,那么这个变量对于块代码就是可写的。即使方法或函数已经退出,本地环境已经释放,这些在块代码中使用的变量还存在,只要还有对这个块代码的引用存在。
系统框架API中的块代码
一个显著的使用块代码的动机是在系统类库中的函数越来越多的开始使用这个作为参数,下面是在部分系统函数中使用块代码的情况:
-
结束回调
-
通知处理
-
出错处理
-
枚举
-
视图动画和翻转
-
排序
下面各节就是对上面的讨论。不过在开始讨论之前,我们还需要看一下系统函数中的块代码的声明。考虑下面的这个在类NSSet
中的声明:
- (NSSet *)objectsPassingTest:(BOOL (^)(id obj, BOOL *stop))predicate
|
上面的这个声明标示着传入函数的参数是一个动态对象类型和一个布尔类型,返回一个布尔类型的代码块。(传入参数和返回类型都是用在“Enumeration”中的for循环的类型)。当声明你的块代码的时候,使用一个插入符^开始,并且使用一对括号包起来的参数列表,后面跟着被一对大括号包着的代码:
[mySet objectsPassingTest:^(id obj, BOOL *stop) {
|
// Code goes here; end by returning YES or NO.
|
}];
|
结束回调和错误管理
结束处理是一个回调函数,用在当一个调用端使用系统函数或方法完成一个任务后的处理。很多时候调用端在结束回调中实现释放状态或者更新界面。很多框架方法函数使用块代码作为结束回调。
UIView
类中有很多个实现动画或视图翻转的函数使用块代码作为结束回调参数。(“View
Animation and Transitions”对这些函数做出了描述)代码1-1演示了如何使用animateWithDuration:animations:completion:
函数的例子。这个例子中的动画结束回调,在结束后使得做动画的视图回复原位,并且几秒后把alpha设置为1。
代码1-1一个结束回调块代码
- (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.
sleep(3);
self.imageView.frame = cacheFrame;
self.imageView.alpha = 1.0;
}
}];
}
|
一些框架函数具有出错处理,这个和结束处理是一样的。函数会在默写错误发生的时候不能完成任务的情况下调用出错处理(并且传入一个NSError
对象)。你可以自定义这个出错处理来通知用户有错误发生。
通知管理
NSNotificationCenter类中的方法
addObserverForName:object:queue:usingBlock:让你在设置一个通知的观察者的时候就可以实现一个处理代码块。代码
1-2演示了调用这个函数的情况,为某个通知定义一个通知管理的代码块。作为一个通知管理的函数,一个NSNotification
对象被传入。这个函数还使用了一个NSOperationQueue
实例,你的应用可以使用它来传递执行上下文到代码块中。
Listing 1-2Adding an object as an observer and handling a notification using a block
- (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]);
|
}];
|
}
|
枚举
基础类库中的容器类—NSArray
,NSDictionary
,NSSet
,和NSIndexSet
—定义的可以实现枚举的函数,使用块代码作为参数,以便调用者可以个性化枚举动作。换句话说,这些方法提供了一个快速枚举的环境:
for (id item in collection) {
|
// Code to operate on each item in turn.
|
}
|
有两种类型枚举函数使用块代码。第一种是函数名称以enumerate开头并且没有返回值,这些函数使用块代码对没有被枚举的对象进行处理;第二种是以
passingTest
;结束的函数,这样函数返回一个整形或者NSIndexSet对象,这类函数中的代码块对每一个枚举对象进行测试,如果通过测试返回YES。函数返回的整形或者索引表示通过测试的原始位置或所有通过测试的对象的位置。
代码1-3中对三个中没一个都调用NSArray中的方法。第一个方法(一个“passing test”方法)的块代码在数组中的每一个字符串对象如果含有一个固定的前缀就返回YES。后面的代码使用方法返回的索引创建一个临时的数组。第二个块代码把每一个第一个数组中的前缀去掉,把后面的加入到一个新数组中。
代码1-3使用两个块代码枚举数组
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);
|
上面的每一个枚举函数中的stop参数(这里没有使用)可以用来传入一个YES,在合适的时候停止枚举。你可以使用它在枚举到第一个你需要的项的时候停止。
NSString类,尽管不是一个容器类,也提供两个使用块代码的函数,它们分别是
enumerateSubstringsInRange:options:usingBlock:
和enumerateLinesUsingBlock:。第一个函数枚举一个字符串,使用一个子串进行分割,第二个只是使用换行符进行分割。代码
1-4演示了第一个函数如何使用:
代码1-4使用块代码在一个字符串中查找匹配的子串
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);
|
}
|
}];
|
视图动画和转换
iOS4.0中的UIView
类引入几个使用块代码,实现动画和转换的类方法,这些块代码类型的参数有两种(不是所有的函数都使用两个类型):
-
块代码用来改变视图的属性来形成动画
-
动画完成后的管理
代码1-5演示了使用animateWithDuration:animations:completion:
的使用,这个函数使用了这两种块代码参数。在这个例子中,动画使得视图消失(设置alpha值为0)并在动画完成后把视图移除。
代码1-5视图使用块代码实现简单动画
[UIView animateWithDuration:0.2 animations:^{
|
view.alpha = 0.0;
|
} completion:^(BOOL finished){
|
[view removeFromSuperview];
|
}];
|
UIView中其他的一些类方法实现了两个不同视图的转换,包括翻转和盘旋。下面的例子代码就是使用transitionWithView:duration:options:animations:completion:
做一个从左面开始的翻转(代码中没有实现动画完成的处理):
代码1-6在两个视图间做翻转
[UIView transitionWithView:containerView duration:0.2
|
options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
|
[fromView removeFromSuperview];
|
[containerView addSubview:toView]
|
}
|
completion:NULL];
|
排序
基础类库中声明了NSComparator块代码类型来进行两个条目的比较:
typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
|
NSComparator是一个块代码类型,并且使用两个对象作为参数,返回一个
NSComparisonResult类型的值。它是
NSSortDescriptor
,NSArray
,
和NSDictionary等类的方法的参数,用来进行排序,如下所示:
代码1-7使用NSComparator块代码功能对数组进行排序
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]);
|
上面的代码是从Blocks Programming Topics里面选取的。
块代码和并发
块代码是一个匿名对象,适合于异步调用,由于上面的这些特征,块代码和适合GCD(Grand Central Dispatch (GCD))和NSOperationQueue
,
关于GCD,
NSOperationQueue和
NSOperation的更多信息,请参看
Concurrency
Programming Guide。
分享到:
相关推荐
"Python入门指南"是一本专为初学者设计的教材,它提供了中英文双语对照,帮助学习者更好地理解和掌握Python语言。 该指南可能涵盖以下几个核心知识点: 1. **基础语法**:Python的基础语法包括变量声明、数据类型...
入门指南涵盖了Python的各个方面,包括基本语法、流程控制、数据结构、模块、输入输出、错误和异常处理、类以及标准库的使用。对于初学者来说,可以将Python看作一个高级的计算器来执行简单的数学运算,或者作为编程...
本文档是一份关于Modicon M340 MFB的入门指南,主要针对UnityPro软件用户的使用。本文从单轴应用程序的入门指南开始,逐渐深入到多轴应用程序的介绍,旨在为用户提供全面的指导和帮助。 首先,文档提到了伺服驱动器...
Python 3.4.3 入门指南是一个适合初学者的教程,涵盖了Python语言的基础到进阶知识。本文档旨在引导读者逐步理解Python的核心概念,包括流程控制、数据结构、模块、输入输出、错误和异常处理,以及面向对象编程。 *...
Python编程入门指南是一份专为初学者设计的学习资源,旨在帮助快速掌握Python编程基础。这份教程可能包含了视频讲解、文字教材、习题等多样化的学习材料,使得学习过程既直观又实用。Python作为一门易学且功能强大的...
**Gradle 入门指南(离线版)** Gradle 是一个开源的构建自动化系统,以其灵活性、可扩展性和强大的依赖管理闻名。它支持多种语言,包括Java、Groovy、Kotlin等,广泛应用于Android开发和其他Java生态项目。这份...
### 数据业务工程师DB2入门指南知识点详述 #### 一、DB2数据库系统结构概览 **1.1 概述** DB2是IBM开发的一款高性能的关系型数据库管理系统(RDBMS),广泛应用于企业级应用中。本指南旨在为新入门的数据业务...
《Java 2入门与实例教程》是一本专为初学者设计的编程指南,由孙燕主编,由中国铁道出版社出版。这本书旨在帮助读者快速掌握Java 2编程语言的基础知识,并通过丰富的实例加深理解和应用。书中的源代码是学习过程中的...
《C#源代码集合:深度探索与实践指南》 C#是一种由微软开发的强大、现代且面向对象的编程语言,广泛应用于Windows桌面应用、游戏开发、Web服务以及移动应用等领域。"C#所有源代码集合"是专为C#开发者准备的一份珍贵...
预处理器可以处理头文件,实现代码复用,并能进行简单的文本替换,而宏定义则可以创建可扩展的代码块。 C语言的程序结构通常由主函数和若干辅助函数组成,函数是C语言模块化的基本单位。通过函数的定义和调用,可以...
《C#入门经典第五版》是一本针对初学者的编程指南,旨在帮助读者掌握C#编程语言的基础知识和核心概念。这本书深入浅出地讲解了C#编程的关键要素,包括语法、面向对象编程、类与对象、接口、泛型、异常处理、文件与流...
总之,《VC++实用教程编程指南》是一份全面的入门资料,无论你是对编程感兴趣的新手,还是希望深入理解VC++的开发者,都能从中受益。通过阅读和实践,你将能够熟练掌握VC++编程,为自己的软件开发之路打下坚实基础。
- **Readme.txt**:通常,这个文件会包含关于插件的基本信息、更新日志、许可信息以及开发者联系方式等内容,是了解插件的入门指南。 总的来说,Flex代码格式化插件是一个强大的工具,能够极大地提升Flex开发者的...
- **函数定义**(def)允许创建自定义功能的代码块。 9. **列表(List)**: - Python的列表是一种可变数据类型,可以存储多个项目,用方括号[]包围,如`fruits = ['apple', 'banana', 'cherry']`。 10. **其他...
通过以上总结,《Perl语言入门(第四版)》这本书不仅为初学者提供了详细的入门指南,也为有经验的程序员提供了深入探索Perl世界的宝贵资源。无论是学习基本语法还是掌握高级特性,本书都是一个不可多得的好帮手。
PEP8是Python的官方编码风格指南,包括缩进、命名规则、代码布局等方面,遵循它可以提高代码的可读性和一致性。了解并应用这些规范将使你的代码更易于他人阅读和维护。 4. **函数与模块**: 学习如何定义函数,...
本教程旨在为初学者提供一个详尽易懂的VB入门指南,帮助你快速掌握VB的基本概念、语法和应用。 1. **VB简介** VB是一种基于事件驱动的编程语言,它采用了直观的图形用户界面(GUI),使得程序员可以通过拖放控件和...
"Python快速入门教程CHM"是一本专为初学者设计的指南,旨在帮助读者迅速掌握Python编程的基础概念和技能。 首先,Python的基础知识包括变量、数据类型和操作符。变量在Python中用于存储数据,你可以随时改变它们的...
《C#入门经典(第4版)》是针对初学者编写的一本权威指南,由WROX出版,并在清华大学出版社发行。这本书以其详尽的解释和丰富的实例,为读者提供了全面学习C#编程语言的坚实基础。以下是书中涵盖的一些关键知识点: ...
比如,可能有用于分析性能的工具代码,或者是关于如何调试CUDA程序的指南。 `lib`文件夹可能包含了一些库文件,这些库可能是某些示例所依赖的,用于提供额外的功能,比如特定的数学运算或硬件接口。 `license.txt`...