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 [`NSOperation<wbr>Queue`](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 [`NSOperation<wbr>Queue`](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<NSIndexPath * , UpdateOperation *> *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的判断可以使得代码更加高效。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容