UICollectionView滑动流畅性优化

UICollectionView滑动流畅性优化

前言

初始的collection view在滑动时都是十分流畅的,然而因为collection view cell 加载更多的内容时因为主线程耗用太多性能而导致主线程出现堵塞,导致原本流畅的滑动出现卡顿的情况。


collection view加载cell时一般会常用复用池,所以滑动时每要显示一个cell都会调用协议中- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;的方法,而在这方法中除了返回复用池中的cell,程序猿还可以趁机在其中调用cell自定义的读取数据的方法(姑且称之为-(void)loadData:),在本文中即将展示如何将- (void)loadData:中的耗时操作放入NSOperationQueue中并发进行从而不影响collection view的滑动,同时在用户快速滑动中,单个cell的NSOperationQueue中会堆积大量operation的情况,也将在本文中提出应对方法。

NSOperationQueue和NSInvocationOperation

NSOperationQueue将线程操作以队列的形式展现,初始化代码如下:

_queue = ({
            NSOperationQueue *q = [[NSOperationQueue alloc]init];
            //设置最大并行操作数为1相当于将queue设置为串行线程
            q.maxConcurrentOperationCount = 1;
            q;
        });

每一个operation都是这个队列中的一个元素,而这个元素在满足条件的情况下可以在不用执行就从队列中移除,我们可以利用这种特性来取消其中一些未执行但是不必执行的operation ,关键代码如下:

if (self.operationQueue.operationCount >= 2) {
    [self.operationQueue cancelAllOperations];
}
NSInvocationOperation *op = [[NSInvocationOperation alloc]
                initWithTarget:self
                      selector:@selector(operationSelector:)
                        object:dic];
[self.operationQueue addOperation:op];

其中-(void)cancelAllOperations可以让队列里的所有待执行的operation移除出队列(详情看官方文档),要注意,如果执行此方法时有operation正在执行,那么那些正在执行的operation不会被强行中止并取消。

Canceling the operations does not automatically remove them from the queue or stop those that are currently executing. For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state.——苹果官方开发文档

NSInvocationOperation是NSOperation的子类,它和NSBlockOperation一样都可以作为元素添加到NSOperationQueue中等待执行,最主要的区别在于前者带的是@selector后者带的是block

异步操作中因视图原因导致程序崩溃时的应对方法

原本高高兴兴把耗时的代码都放到队列中运行时却发现在涉及到视图操作的地方容易出现错乱,甚至是BAD_XXXX之类的崩溃时,就需要检查@selector中是否包含类似addSubview,removeFromSubview之类的傻X操作(每读取一次数据就把view移出来/放进去是什么鬼操作?!View:我来了,我又走了,我来了,我又走了,你打我呀……),遇到这种代码第一想到的是尽量将其移出Operation,最好是将其放到初始化的方法,但是还有些情况是有些视图的属性受到数据的影响,需要等耗时操作结束之后才能确定视图的属性,这个时候就要用到GCD的方法了,如下:

- (void)optimizeOperation:(NSString *)dataStr {
    //模拟十分耗时间的操作,注意,这里不能放在main_queue里面执行,否则时间还是消耗在了主线程里面
    [NSThread sleepForTimeInterval:0.05];
    if ([dataStr isEqualToString:self.dataStr]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.titleLb.text = dataStr;
        });
    }
}

要注意dispatch_async(dispatch_get_main_queue(), ^{})中不要有耗时的方法,我们到目前为止的铺垫都是为了让占用主线程的滑动操作不要被其他操作堵塞,如果把耗时操作放进去的话就前功尽弃了。


很难想象其中每个cell的读取方法中间会有0.05s的耗时方法

附带demo地址,本文所涉及的思路体现在TextCell.m中:https://github.com/MrYu4/OptimizeCollectionView

可能存在的疑问

  • 可是我就是不想用NSInvocationOperation啊,我用NSBlockOperation不行吗?

  • if (self.operationQueue.operationCount >= 2)这个判断语句怎么理解?

队列中如果有两个或两个以上个待出队列的operation,那么第一个可能是正在执行中的,第二个或者之后都是未执行的(忘了说了,queue设置了最多可并发运行一个operation),这个时候正好是有新的operation等待加入,就说明以往的operation没什么用了,这个时候就可以进行剪枝操作,把operation除去节省性能。

  • 接着上个问题,为什么不是>=1呢

[self.operationQueue cancelAllOperations];能立刻去除的是未执行的operation,但正在执行的不会除掉,也就是在cancelAllOperations之前队列内元素个数=1时,就基本上可以确定队列中没有可以除掉的operation了。

求打赏


posted @ 2019-02-23 17:59  MrYu4  阅读(127)  评论(0编辑  收藏  举报