NSOperation异步更新单个TableViewCell

1.概论

An operation object is a single-shot object—that is, it executes its task once 
and cannot be used to execute it again. You typically execute operations by 
adding them to an operation queue (an instance of 
the [`NSOperationQueue`](apple-reference-
documentation://hcOi0eohIY) class). An operation queue executes its 
operations either directly, by running them on secondary threads, or 
indirectly using the `libdispatch` library (also known as Grand Central 
Dispatch). For more information about how queues execute operations, 
see [`NSOperationQueue`](apple-reference-
documentation://hcOi0eohIY). 

If you do not want to use an operation queue, you can execute an operation 
yourself by calling its `start` method directly from your code. Executing 
operations manually does put more of a burden on your code, because 
starting an operation that is not in the ready state triggers an exception. 
The `ready` property reports on the operation’s readiness.

NSOperation可以通过start方法直接调用,但是更好的方法是将其放入NSOperationQueue中。
放入NSOperationQueue中的NSOperation会在合适的时机自动执行,如果没有依赖和并发数量限制,则会立刻执行(调用start方法),在NSOperationQueue中的NSOperation默认异步执行。
cancelAllOperations可以将NSOperationQueue中所有的NSOperation发送cancel的消息。

2.异步和同步

一个独立的Operation 默认是同步的,且在当前调用的线程中执行start方法。

When you call the start method of a synchronous operation directly from 
your code, the operation executes immediately in the current thread.

asynchronous是一个控制异步同步的属性,YES可以让NSOperation异步执行,但是当Operation放入OperationQueue中时,asynchronous就失去意义了,Operation默认会变成异步。

3. 非并发的NSOperation

非并发的NSOperation意味着是NSOperation中没有并发的操作,执行完成后这个NSOperation就失效了(状态变为isFinished)。
对于非并发的NSOperation,只需要覆盖main方法,在main中做操作,NSOperation会自动管理状态,比如在main开始之前将状态设置成isReading,在main方法调用完成将状态设置成isFinished。

For non-concurrent operations, you typically override only one method:
Into this method, you place the code needed to perform the given task. Of 
course, you should also define a custom initialization method to make it 
easier to create instances of your custom class. You might also want to 
define getter and setter methods to access the data from the operation. 
However, if you do define custom getter and setter methods, you must make 
sure those methods can be called safely from multiple threads.

4. 并发的NSOperation

对于有并发操作的NSOperation,需要手动管理状态。并且需要覆盖start方法。
NSOperation的状态

isReady

NSOperation准备好执行时候的状态,通常由于NSOperationQueue并发数的限制和NSOperation依赖的影响,NSOperation不总是立刻进入ready状态。

isExecuting

If you replace the start method of your operation object, you must also replace 
the executing property and generate KVO notifications when the execution 
state of your operation changes.

当你自定义并发operation的时候,executing的状态是需要自己通过KVO控制的。

-(void)setExecuting:(BOOL)executing{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

isFinished

isFinished 表明当前的operation完成,这样基于此operation依赖的operation才会执行,如果当operation被添加进queue中,当operation变为finished时,将会被自动移除queue,并且释放掉。
当你自定义并发operation的时候,finished的状态是需要自己通过KVO控制的。

-(void)setFinished:(BOOL)executing{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

isCancelled

isCancelled可以帮助我们在必要的时候枝减掉某些操作,在并发任务中获取isCancelled的状态决定是否继续接下来的操作。

5. 用NSOperation异步更新TableViewCell状态

@interface UpdateOperation()

@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isFinished) BOOL finished;

@end

@implementation UpdateOperation
@synthesize finished = _finished, executing = _executing;

-(void)setFinished:(BOOL)finished{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
    
}
-(void)setExecuting:(BOOL)executing{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)start {
    if (self.isCancelled) {
        self.finished = YES;
        return;
    }
     self.executing = YES;
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
         //请求网络
              dispatch_async(dispatch_get_main_queue(), ^{
                    [self requestCallBack:(id)data];
            });
    });
}
- (void)cancel {
    [super cancel];
    if (self.isExecuting) {
        self.finished = YES;
        self.executing = NO;
    }
}
-(void)requestCallBack:(id)data{
    if (self.isCancelled) {
        //更新UI
        [self.bindCell update:(data)];
        self.finished = YES;
        return;
    }
}

这个地方需要主要的问题是由于tabelViewCell复用的缘故,一定要保证operation和Cell对应关系要正确。
我采用的是在控制器层对operation和cell做一一对应,在cell出现的时候,需要判断下是否cell已经更新或者cell的operation还未结束。

@property (nonatomic, strong) NSMutableDictionary *updateOperations;
@property (nonatomic,strong)  NSMutableDictionary *isUpdateIndexs;

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *code; //标识
if(![self.updateOperations objectForKey:code]){ //operation是否还在列队
            if ([self.isUpdateIndexs objectForKey:code]) { //cell是否已经更新过
                return;
            }
            UpdateOperation *queue = [[UpdateOperation alloc] init];
            queue.bindCell = cell;
            self.updateOperations[code] = queue;
            [self.updateQueue addOperation:queue];
        }
}

在某个cell消失的时候,需要判断下该cell对应的operation是否完成,完成则记录,还未完成,则调用cancel,下次cell在出现时候可以重新创建operation。

-(void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
        NSString *code; //标识
        if ( [self.updateOperations objectForKey:code] )
        {
            UpdateOperation *queue = self.updateOperations[code];
            if ([queue isFinished]) { //queue完成,更新状态
                [self.isUpdateIndexs setValue:queue.selRecord forKey:code];
                [self.updateOperations removeObjectForKey:code];
            }
            else //未完成,取消
            {
                [queue cancel];
                [self.updateOperations removeObjectForKey:code];
            }
        }
}

另一个方式是将标识同时保存在cell和operation的对象中,在operation执行完异步任务刷新cell的时候,检查标识是否匹配,决定是否更新cell。

总结

在自定义并发NSOperation时,手动控制状态一定要仔细,不然可能导致未知的问题。
在合适的地方添加isCancel的判断可以使得代码更加高效。

你可能感兴趣的:(NSOperation异步更新单个TableViewCell)