美文网首页
Concurrent Operations

Concurrent Operations

作者: woshishui1243 | 来源:发表于2018-05-22 15:34 被阅读5次

如果你有一个需要耗时很长的操作,你可以创建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状态直到异步调用完成。

不过这样我们需要做一些额外的操作。我们需要跟踪isExecutingisFinished,我们需要通过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、必须修改isExecutingYES
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"];
}

这里的关键点是我们改变isExecutingisFinished。只有他们被分别设置为NOYES时,操作才会从队列中删除。队列使用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];

而且,还可以利用操作的依赖关系来保证任务以正确的顺序执行。

相关文章

网友评论

      本文标题:Concurrent Operations

      本文链接:https://www.haomeiwen.com/subject/ssghjftx.html