iOS为开发者提供了丰富的多线程编程机制,从最直接最简单的调用NSObject
的performSelector
系列API,到NSThread
、Operation Queues
以及Dispatch Queues
,当然iOS也支持更原始的pthread
。本文主要讨论使用这些多线程编程机制时候的一些注意事项,具体如何使用这些机制,可以移步到Raywenderlich的教程以及官方文档:
- Grand Central Dispatch In-Depth: Part 1/2
- Grand Central Dispatch In-Depth: Part 2/2
- How To Use NSOperations and NSOperationQueues
- Concurrency Programming Guide
前两篇教程通过逐步改善一个图片集加载的例子,来说明GCD(Grant Central Dispatch)在数据读写同步、数据加载以及线程同步上的应用;类似的,第三篇教程通过一个加载table内容的例子,说明了NSOperation以及NSOperationQueue的应用。
NSOperation和NSOperationQueue
相比其他iOS多线程机制,NSOperation
给开发者提供了更多独特的特性,这些特性包括:
NSOperation
将任务封装在对象中,完全支持OOP开发方式,有利于保持代码结构的一致性。NSOperation
对象特性使得可以给任务线程设置相应的属性,比如NSOperation
为开发者定义了三个属性isExecuting
,NSOperation
,isCanceled
,当然开发者在子类化NSOperation
时,可以根据需求定义额外的属性;之后开发者可以对这些属性进行KVO。调用
NSOperation
的cancel
方法可以重置任务线程的isCanceled
属性,任务根据这个状态作出相应的操作,比如退出或者暂停等;调用NSOperationQueue
的cancelAllOperations
方法可以重置该队列上所有任务的isCanceled
属性;任务队列支持暂停操作,调用
NSOperationQueue
的setSuspended:(BOOL)
可以暂停或者重启队列任务之间支持互相依赖,通过调用
NSOperation
的addDependency:(NSOperation *)
方法为自身任务添加依赖任务线程,调用removeDependency:(NSOperation *)
可以将依赖队列移除可以给任务队列中的任务设置运行优先级,从而控制任务队列中任务执行顺序
可以给任务设置一个
completionBlock
,该block在任务运行结束后执行。
下面总结几点在使用队列和任务时的几个要点:
1)NSOperation
是一个抽象类,开发者需要继承该类并至少要重载main
方法,
@interface CustomizedOperation : NSOperation
@property(strong) id myData;
-(id)initWithData:(id)data;
@end
@implementation CustomizedOperation
- (id)initWithData:(id)data {
if (self = [super init]){
_myData = data;
}
return self;
}
- (void)main {
@autoreleasepool {
while (true) {
// TODOs:
}
}
}
@end
2) 前面提到,可以调用cancel
或者cancelAllOperations
方法,但是这两个方法除了重置任务的isCanceled
属性之外,不会做其他操作,要想达到真正取消任务的目的,需要开发者自己不断检查给属性的状态,并作出相应操作,如:
@interface CustomOperation: NSOperation
@end
@implementation CustomOperation
- (void)main {
// 耗时操作
@autoreleasepool {
for (int i = 0 ; i < 1000000 ; i++) {
// 检查任务状态
if (self.isCancelled)
break;
// step 1
[self doSomething];
// 检查任务状态
if (self.isCancelled)
break;
// step 2
[self doSomethingelse];
}
}
}
@end
3) 任务默认都是同步执行的,如果想要任务异步执行,开发者需要自己维护任务的状态,需要重写start
, isExecuting
, isFinished
, isConcurrent
方法。其中重载start
方法时不需要调用父类方法,isConcurrent
方法只需要直接返回YES
即可。
@interface MyOperation : NSOperation {
BOOL _executing;
BOOL _finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
- (id)init {
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}
- (void)start {
// 启动任务前检查任务是否被取消
if ([self isCancelled])
{
// 如果任务被取消,则重置其他属性状态.
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
// 这里执行主要任务
// 检查任务状态
if (self.isCancelled)
break;
// step 1
[self doSomething];
// 检查任务状态
if (self.isCancelled)
break;
// step 2
[self doSomethingelse];
[self completeOperation];
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return _executing;
}
- (BOOL)isFinished {
return _finished;
}
GCD(Grant Central Dispatch)
1)dispatch queue的创建时候需要传递一个串行或者并行标志,GCD库提供了这两个标志,但是很多开发者却不使用,下面是正确的姿势:
dispatch_queue_t serialQueue = dispatch_queue_create("com.johnkui.serial", **DISPATCH_QUEUE_SERIAL**);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.johnkui.concurrent", **DISPATCH_QUEUE_CONCURRENT**);
2)我们知道使用dispatch_group_async
或者dispatch_group_enter/dispatch_group_leave
配合dispatch_group_notify
或者dispatch_group_wait
可以实现同步操作,但是如果使用这些API不当的话,结果却会出人意料。下面是使用dispatch_group_async
的正确姿势:
dispatch_queue_t queueA = dispatch_queue_create("com.johnkui.concurrentA", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueB = dispatch_queue_create("com.johnkui.concurrentB", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
// 给任务组添加任务
dispatch_group_async(group, queueA, ^{
// Some asynchronous work
});
// 给任务组添加其他任务
dispatch_group_async(group, queueB, ^{
// Some other asynchronous work
});
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_async
配合dispatch_group_wait
使用时,要保证dispatch_group_async
调用的block必须运行相应的队列中,在上例中分为为queueA和queueB中,如果block里的任务实际运行在其他queue中,则dispatch_group_wait
将立即返回而监测不到两个任务的完成状态,因为group只监控和该group关联的queue上的任务的运行状态。如果block里面的任务确实想运行在其他queue上,则可以使用dispatch_group_enter/dispatch_group_leave
,下面是正确的姿势:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queueA = dispatch_queue_create("com.johnkui.concurrentA", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueB = dispatch_queue_create("com.johnkui.concurrentB", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
dispatch_async(queueA, ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时任务
dispatch_group_leave(group);
});
});
dispatch_group_enter(group);
dispatch_async(queueB, ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Hello"
message:@"任务完成了!"
delegate:nil
cancelButtonTitle:@"Ok"
otherButtonTitles: nil];
[alertView show];
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"group is finished...");
});
当block里的任务运行完需要更新UI时,一般都会这么操作:
dispatch_async(dispatch_get_main_queue(), ^{
//更新UI
});
这个时候一定不要使用同步的dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
,否则整个主线程将出现死锁,而是使用异步的dispatch_group_notify
;