内容简介:由于cell复用的原因,直接在在方法2、
reloadData
是一个异步方法,并不会等待 UITableView
或者 UICollectionView
(后面统称 listView
)真正刷新完毕后才执行后续代码,而是立即执行后续代码。我们执行 reloadData
的本意是刷新 listView
,随后会进入一系列的DataSource和Delegate回调,有些是和reloadData同步发生的,有些是异步发生的。
-
同步:
numberOfSectionsInCollectionView
和numberOfItemsInSection
-
异步:
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中的任务,分别在休眠前和被唤醒后。设置 listView
的 layoutIfNeeded
为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); } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 封装中修改旧代码的时机
- 国内存储芯片迎来最好的发展时机
- 虚拟机类加载机制:类加载时机
- 什么时候才是代码重构的最佳时机?
- Java异常系列之finally的真正运行时机
- JVM类生命周期概述:加载时机与加载过程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。