块代码实用入门指南

 

本文档版权归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-2 Adding 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中两个关键函数dispatch_sync (同步派发)和dispatch_async (异步派发)的第二个参数都是一个块代码类型。

  • 一个NSOperationQueue是一个用来调度任务的对象,这些任务可能顺序执行,或者有相互依赖关系。这些任务其实是NSOperation对象,这些对象一般使用块代码来实现任务。

关于GCD, NSOperationQueue和 NSOperation的更多信息,请参看 Concurrency Programming Guide
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Shell脚本是一种通过编写一系列命令来实现自动化任务的脚本语言。它是一种在Unix或类Unix操作系统中常见的解释性编程语言。 Shell脚本是一种非常实用的技术,可以用于各种任务,例如批量处理文件、管理系统配置和自动化任务等。 学习Shell脚本技术的入门步骤如下: 1. 学习Shell语法:Shell脚本使用的是Shell语言的语法,因此需要先掌握Shell语法规则。可以通过在线教程或书籍学习Shell语法,理解脚本的结构、变量、条件判断、循环等基本概念。 2. 选择合适的Shell:常见的Shell有Bash(Bourne Again SHell)、Csh(C SHell)、Ksh(Korn SHell)等。Bash是最常用的Shell,适合大多数任务。选择一个Shell并深入学习它的功能和用法。 3. 编写简单的脚本:从简单的任务开始,例如创建一个Hello World脚本,尝试运行并查看结果。逐步增加复杂性,编写一些实用的脚本。可以使用编辑器,例如Vi或Nano,在终端中编写脚本。 4. 理解常用命令:Shell脚本是通过命令来完成任务的,因此需要熟悉一些常用的命令,例如ls、cd、grep、awk、sed等。可以通过查阅Shell命令文档或在线教程来学习这些常用命令的用法。 5. 学习脚本编程技巧:掌握一些高级的脚本编程技巧,例如重定向输入输出、处理命令行参数、使用函数和循环等。这些技巧可以增强脚本的功能和灵活性。 总之,学习Shell脚本技术需要掌握Shell语法,选择合适的Shell,并通过编写简单的脚本、学习常用命令和掌握脚本编程技巧来逐步提高技能。随着经验的积累,可以处理更加复杂的任务,并更高效地管理和自动化系统。 ### 回答2: shell脚本技术是一种编程语言技术,用于操作和自动化Linux和Unix操作系统下的命令行界面。它可以帮助用户通过一系列的命令组合和逻辑控制实现复杂的任务和操作。 要使用shell脚本技术,首先要了解基本的shell语法和命令。shell脚本是由一系列的命令和控制结构组成的文本文件。常见的shell是Bash,可以在终端中输入"bash"命令来启动。 在脚本中,我们可以使用变量来存储数据和结果。变量可以通过赋值来创建,例如: ``` name="John" ``` 然后可以通过"$"符号进行引用,例如: ``` echo "My name is $name" ``` 除了变量,我们还可以使用条件语句和循环结构来控制程序的执行流程。条件语句可以根据条件的真假执行不同的操作,例如: ``` if [ $age -ge 18 ]; then echo "You are an adult" else echo "You are a minor" fi ``` 循环结构可以用来重复执行一段代码,例如: ``` for i in {1..5}; do echo "Number: $i" done ``` 另外,shell脚本还支持函数的定义和调用。函数是一段可以被重复使用的代码,例如: ``` function greet { echo "Hello, $1!" } greet "Alice" ``` shell脚本技术可以用于实现很多自动化任务,比如文件操作、系统管理、数据处理等。通过编写简单的脚本,我们可以减少重复的工作和简化复杂的操作流程。 总之,掌握shell脚本技术可以帮助我们更好地管理和操作Linux和Unix系统,提高工作效率。希望这个简单的入门介绍能给您一些帮助。 ### 回答3: Shell脚本是一种编程语言,通常用于自动化执行系统任务和命令。它是在操作系统上运行的命令行解释器,可以直接与操作系统交互。下面是Shell脚本技术的入门指南。 首先,了解Shell脚本的基本语法和结构非常重要。Shell脚本使用一系列命令来执行任务,可以通过编写一系列的命令来实现特定目的。可以使用任何文本编辑器编写Shell脚本,并将其保存为以.sh为扩展名的文件。 熟悉Shell脚本的变量和参数也很重要。Shell脚本中可以使用变量来存储和处理数据。变量使用$符号进行引用,例如$变量名。同时,还可以使用特殊变量(如$0表示脚本名称,$1表示第一个参数)来获取脚本的参数。 掌握条件判断和循环结构也是必不可少的。Shell脚本可以使用if语句来进行条件判断,根据条件的结果执行不同的命令。同时,可以使用for循环和while循环进行重复执行一系列的命令。 了解Shell脚本中的输入和输出是非常重要的。可以使用特定命令来获取用户的输入,如read命令。同时,可以使用echo命令将数据输出到屏幕上。还可以使用重定向和管道来重定向命令的输出或将多个命令连接起来。 熟悉Shell脚本中的函数和文件操作也是必要的。可以使用函数来组织和重复使用特定命令序列。同时,可以使用命令来对文件进行读取、写入和操作。 最后,要实践并练习Shell脚本技术。可以编写一些简单的脚本来完成常见的系统任务,如文件处理、文本处理、系统管理等。通过不断地实践和尝试,逐渐提高对Shell脚本的理解和掌握。 总之,通过学习Shell脚本的基本语法、变量和参数、条件判断和循环结构、输入和输出、函数和文件操作,并进行实践和练习,可以逐步掌握Shell脚本技术,从而能够更好地利用它来简化和自动化系统任务与操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值