首页 > IOS > 区块续--Objective-C编程实践

区块续--Objective-C编程实践

来源:原创 作者:thomas 分类:IOS 阅读:778 日期:2015-04-15

使用类型定义简化区块语法

如果你需要定义不止一个同一类型的区块,你可能需要定义自有类型以简化区块定义。

例如,你可以定义一个不接收任何参数、不返回任何值的简单区块,如下:

typedef void (^XYZSimpleBlock)(void);

这样,你就可以使用自定义类型来声明方法参数或创建区块变量:

XYZSimpleBlock anotherBlock = ^{
    ...
}; 
- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
    ...
    callbackBlock();
} 

自定义区块类型定义在处理需要接收其他区块作为参数或返回的值为区块时的区块定义时特别有用。如下示例:

void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
    ...
    return ^{ 
        ...
    }; 
}; 

complexBlock变量指向一个区块,该区块使用另外一个区块作为参数,并且返回值为另一个区块。

使用类型定义重写上面的代码,以使其变得更加易读:

XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
    ...
    return ^{ 
        ...
    }; 
}; 

对象使用属性来保存区块

定义属性来保存区块的语法类似于定义区块变量:

@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end

注意:你应该对属性指定为copy类型,因为区块需要被复制下来以捕获原作用域的外在状态。在使用自动引用计数时,并不需要为此担心,因为它将自动发生。最佳编程实践是显示属性的结果行为。更多信息,请查看区块编程相关主题。

区块属性的设置和调用类似于区块变量:

self.blockProperty = ^{
    ...
};
self.blockProperty();

也可以在区块属性声明时使用类型定义,如下:

typedef void (^XYZSimpleBlock)(void);
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end

在获取self变量时如何避免强引用循环

如果你需要在区块中获取self变量,例如当定义一个回调区块时,考虑内存管理的影响是非常重要的。

区块对捕获到的任意对象维护一个强引用,包括self变量,这就意味着很容易进入一个强引用循环中。如下例:

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
  - (void)configureBlock {
    self.block = ^{
          [self doSomething];    // 捕获一个强引用的self变量
                                // 创建强引用循环    
    };  
}
... 
@end 

在如上的简单示例中,编译器将发出警告,但在一个复杂示例中,可能在对象之间会调用多个强引用以创建循环,将使其变得更难诊断。

为了避免这个问题,最好的方式是捕获一个弱引用的self变量,如下:

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
        [weakSelf doSomething];   // 捕获软引用以避免引用循环
          }
}  

通过获取指向self变量的弱引用指针,区块不再维护指向XYZBlockKeeper对象的强关系。如果对象在区块调用前被释放,weakSelf指针将被简单地设为nil。

区块可以简化枚举

另外对于常见的复杂处理,许多Cocoa和Cocoa Touch API使用区块来简化常见任务,例如集合迭代。NSArray类就提供了三个基于区块的方法:

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;

该方法需要一个区块参数,数组中的每个元素被迭代时,该区块将被调用,如下:

NSArray *array = ...
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
    NSLog(@"Object at index %lu is %@", idx, obj);
}];

该区块需要三个参数,前两个指向当前对象和它在数组中的索引。第三个参数是一个指向Boolean类型变量的指针,你可以使用它来停止迭代循环,如下:

[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
    if (...) {
        *stop = YES; 
    }
}]; 

也可以通过使用enumerateObjectsWithOptions:usingBlock: 方法来自定义枚举方式。当指定为NSEnumerationReverse选项时,将以倒序的方式来迭代集合。

如果枚举区块中的代码是密集处理型的,并且对并发执行是安全的,你可以指定为NSEnumerationConcurrent选项:

[array enumerateObjectsWithOptions:NSEnumerationConcurrent
                        usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
    ... 
}]; 

该标志表示枚举区块调用将通过多线程来进行分发,当区块代码是处理密集型时,将有很大的性能提升。需要注意的是,当使用该选项时,枚举顺序未定义。

NSDictionary类也提供了基于区块的方法,包括:

NSDictionary *dictionary = ...
[dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) { NSLog(@"key: %@, value: %@", key, obj);
}];

这将使得枚举每个键值对变得更加方便,比使用传统的循环方式。

区块可以简化并发任务

一个区块代表了一个不同的工作单元,包括可执行的代码以及从周边作用域中获取的状态。对于在OS X及iOS平台使用并发选项来执行异步调用来说,区块是比较完美的。和必须指出如何使用如线程一般的低级语言机制相比,通过使用区块,你可以简化任务定义,并让系统以处理器资源的形式去执行这些任务。

OS X和iOS提供很多并发技术,包括两种任务调度机制:操作队列和中央任务调度。这些机制解决了等待被调用的任务队列的调用问题。你可以添加区块到队列中,以便于其可以被调用到,在处理器时间及资源可用时,系统调出队列来执行区块调用。

串行队列的任务仅允许按时间顺序来执行,下一个任务不会出队列,直到前一个任务执行完成。并发队列同时调用多个任务,而不需要等待前一个任务执行完成。

在操作队列中使用区块操作

操作队列是Cocoa和Cocoa Touch中可用的任务调度机制。你可以通过创建NSOperation实例来把任何必要的数据封装进工作单元,然后把队列添加到NSOperationQueue中以便于执行。

尽管你可以创建NSOperation的子类来实现复杂任务处理,但也可以使用NSBlockOperation来使用区块创建操作,如下:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    ...
}]; 

也可以手动来执行操作,一般操作被添加到一个已经存在的队列中,或一个你自己创建的队列中,如下:

// 在主队列上调度任务 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
// 在后台队列上调度任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

如果你使用操作队列,你可以配置优先级或操作间的依赖关系,例如指定某个操作直到其他一组操作完成时才去执行。你也可以通过键值对观察模式监控操作的状态变化,这将使得任务完成前,更加容易更新一个进度条。

如想获取更多关于操作和操作队列的信息,请查看操作队列专题。

使用中央任务调度来基于区块调度分发队列

如果你需要调度任意的区块去执行,你可以直接使用基于GCD的dispatch queues。分发队列使得任务不管是同步还是异步,相对于调用者来说变得更加容易被执行,并以先进先出的顺序来执行。

你也可以创建自己的分发队列,或GCD自动提供的队列。如果你需要调取某个任务以便于并发执行,你可以通过使用dispatch_get_global_queue()函数一个已存在队列的引用,例如:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

为了分发区块到队列中,你可以使用dispatch_async()或dispatch_sync()函数。dispatch_async()函数将立即返回,而不需要等待区块被执行完:

dispatch_async(queue, ^{
    NSLog(@"Block for asynchronous execution");
}); 

dispatch_sync()直到区块执行完才返回。你可能会在一个并发区块需要等待另外一个在主线程上的任务执行完后继续执行当前区块的情景下使用。

 

热门文章 更多>

微信扫一扫,关注技术十日谈