监听reloadData刷新列表完毕的时机

栏目: IOS · 发布时间: 6年前

内容简介:由于cell复用的原因,直接在在方法2、

reloadData 是一个异步方法,并不会等待 UITableView 或者 UICollectionView (后面统称 listView )真正刷新完毕后才执行后续代码,而是立即执行后续代码。我们执行 reloadData 的本意是刷新 listView ,随后会进入一系列的DataSource和Delegate回调,有些是和reloadData同步发生的,有些是异步发生的。

  • 同步: numberOfSectionsInCollectionViewnumberOfItemsInSection
  • 异步: cellForItemAtIndexPath
  • 同步+异步: sizeForItemAtIndexPath

问题:

由于cell复用的原因,直接在 reloadData 后执行代码是有可能出问题的。比如在 reloadData 前保留了一个cell,在 reloadData 后,对这个cell(已经不是原来的cell了)进行某些操作,会出现一些异常问题。

解决办法:

reloadData 前不是保留cell,二是保留当前cell对应的 NSIndexPath ,然后在 reloadData 完毕( listView 真正刷新完毕)后通过方法 cellForItemAtIndexPath: 重新获取cell,然后进行相应的操作。

获取listView真正刷新完毕的时机的几种方法

方法1、通过layoutIfNeeded方法,强制重绘并等待完成。

[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
// 刷新完成,执行后续需要执行的代码
if ( self.didPlayIdx ) {
    MyCell* cell = (MyCell*)[self.collectionView cellForItemAtIndexPath:self.didPlayIdx];
    if (cell) {
	[cell playWithPlayer:self.player];
    }
}
复制代码

方法2、 reloadData 方法会在主线程执行,通过GCD,使后续操作排队在 reloadData 后面执行。一次runloop有两个机会执行GCD dispatch main queue中的任务,分别在休眠前和被唤醒后。设置 listViewlayoutIfNeeded 为YES,在即将进入休眠时执行异步任务,重绘一次界面。

[self.collectionView reloadData];  
dispatch_async(dispatch_get_main_queue(), ^{  
    // 刷新完成,执行后续代码
    if ( self.didPlayIdx ) {
        MyCell* cell = (MyCell*)[self.collectionView cellForItemAtIndexPath:self.didPlayIdx];
        if (cell) {
            [cell playWithPlayer:self.player];
        }
    }
});
复制代码

知识点关联:GCD死锁、Runloop

// 发生死锁,永远不会执行任务2和3
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
   NSLog(@"2");
});
NSLog(@"3");
复制代码

方法3、自定义UICollectionView、UITableView,layoutSubviews之后当作reloadData完成(复杂,但可以更好的理解方法一)

#import "MyTableView.h"

@interface MyTableView()
@property (nonatomic, copy) void (^reloadDataCompletionBlock)();
@end

@implementation MyTableView
- (void)reloadDataWithCompletion:(void (^)())completionBlock {
    self.reloadDataCompletionBlock = completionBlock;
    [super reloadData];
}
- (void)layoutSubviews {
    [super layoutSubviews];
    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}
@end

// 调用的时候
[self.tableView reloadDataWithCompletion:^{
     NSLog(@"完成刷新");
}];
复制代码

引申:更新UI放在主线程的原因

原因一:安全+效率

因为UIKit框架不是线程安全的,当多个线程同时操作UI的时候,抢夺资源,导致崩溃,UI异常等问题。假如在两个线程中设置了同一张背景图片,很有可能就会由于背景图片被释放两次,使得程序崩溃。或者某一个线程中遍历找寻某个subView,然而在另一个线程中删除了该subView,那么就会造成错乱。apple有对大部分的绘图方法和诸如UIColor等类改写成线程安全可用,可还是建议将UI操作保证在主线程中。例如说,我们需要在子线程中读取一个image对象,使用接口 [UIImage imageNamed:] ,但 imageNamed: 实际上在 iOS9 以后才是线程安全的, iOS9 之前都需要在主线程获取。所以,我们需要从子线程切换到主线程获取image,然后再切回子线程拿到这个image,这里我们必须使用sync。

__block UIImage *image;
dispatch_sync_on_main_queue(^{
    image = [UIImage imageNamed:@"Resource/img"];
});
attachment.image = image;

// YYKit中提供了一个同步扔任务到主线程的安全方法:
/**
 Submits a block for execution on a main queue and waits until the block completes.
*/
static inline void dispatch_sync_on_main_queue(void (^block)()) {
    if (pthread_main_np()) {
        block();
    } else {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

反欺骗的艺术

反欺骗的艺术

(美) 米特尼克(Mitnick, K. D.) / 潘爱民 / 清华大学出版社 / 2014-7-1 / 49.80元

凯文•米特尼克(Kevin D. Mitnick)曾经是历史上最令FBI头痛的计算机顽徒之一,现在他已经完成了大量的文章、图书、影片和记录文件。自从2000年从联邦监狱中获释以来,米特尼克改变了他的生活方式,成了全球广受欢迎的计算机安全专家之一。在他的首部将功补过的作品中,这位全世界最著名的黑客为“放下屠刀,立地成佛”这句佛语赋予了新的含义。 在《反欺骗的艺术——世界传奇黑客的经历分享》中,......一起来看看 《反欺骗的艺术》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具