如果你有一个需要耗时很长的操作,你可以创建NSOperation的子类,然后将耗时代码放在main函数中。
@implementation CalculateOperation
- (void)main{
// 耗时任务
}
@end
执行这个任务,你只需要将他添加到NSOperationQueue中:
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
NSOperation *calOperation = [[CalculateOperation alloc] init];
[queue addOperation:calOperation];
如果将多个operation加入到queue中,他们会在后台线程中并发执行,从而不影响主线程中的用户交互。queue会将根据CPU内核的数量智能地调度并行操作的数量,从而有效地利用用户的硬件。
唯一需要注意的是一个operation的生命周期就是main方法。一旦该方法返回,operation就会置为finished,并从queue中移除。如果你需要在main函数中使用具有异步API的类,必须通过一些特殊处理才能达到目的。通常,您必须使用runloop来确保main函数不会过早返回。
你可以通过在NSOperation的子类中重写isConcurrent方法,并返回YES,创建了一个异步operation:
- (BOOL)isConcurrent {
return YES;
}
但是在10.6系统以后,这个方法不再准确。在10.6以后的系统中start方法总是在后台线程中被调用。为了只使用主线程以及依赖于runloop的异步API来正常工作,我们需要将我们的工作分流到主线程。
在任何情况下,并发线程的另一个主要区别是重写start方法,而不是重写main方法。此外,start方法返回后,operation并不是finished状态。这就允许您控制operation的生命周期。
当处理异步API时,可以通过在主线程上执行start方法,在start方法中执行异步调用,保证operation一直处于running状态直到异步调用完成。
不过这样我们需要做一些额外的操作。我们需要跟踪isExecuting
和isFinished
,我们需要通过KVC的形式来修改它们。通常情况下,我们使用实例变量来实现这一点。只有当isFinished
属性更改为YES
时,才认为该操作已完成。
例如,如果我们想使用NSURLConnection
从URL下载数据的操作,它的初始化器将是:
- (id)initWithUrl:(NSURL *)url {
self = [super init];
if (self == nil)
return nil;
_url = [url copy];
_isExecuting = NO;
_isFinished = NO;
return self;
}
- (void)start {
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
NSLog(@"opeartion for <%@> started.", _url);
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
NSURLRequest * request = [NSURLRequest requestWithURL:_url];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
}
其中有3点很重要:
1、我们必须确保任务在主线程上执行。
2、必须修改isExecuting
为YES
。
3、start
方法在NSURLConnection
完成之前返回,但是operation必须保持在执行状态。这就意味着,operation始终处在queue中,而不需要通过runloop。
最后,自定义finish
方法来结束operation:
- (void)finish {
NSLog(@"operation for <%@> finished. "
@"status code: %d, error: %@, data size: %u",
_url, _statusCode, _error, [_data length]);
_connection = nil;
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
这里的关键点是我们改变isExecuting
和isFinished
。只有他们被分别设置为NO
和YES
时,操作才会从队列中删除。队列使用KVO管理这些属性值。
在NSURLConnection
的代理方法中正确的结束operation:
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
_data = [[NSMutableData alloc] init];
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
_statusCode = [httpResponse statusCode];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data {
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self finish];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
_error = [error copy];
[self finish];
}
我们不必把异步API转换成同步API,但是我们仍然能够将这个任务打包为一个操作。虽然使用操作似乎有点违反直觉,但它确实有它的好处。例如,可以使用队列将并行下载的数量限制为两个:
_queue = [[NSOperationQueue alloc] init];
[_queue setMaxConcurrentOperationCount:2];
而且,还可以利用操作的依赖关系来保证任务以正确的顺序执行。
网友评论