@(IOS零落的记忆)[runloop, GCD死锁, 多线程]html
reloadData
是一个异步方法,并不会等待UITableView
或者UICollectionView
(后面统称listView
)真正刷新完毕后才执行后续代码,而是当即执行后续代码。咱们执行reloadData
的本意是刷新listView
,随后会进入一系列的DataSource和Delegate回调,有些是和reloadData同步发生的,有些是异步发生的。安全
- 同步:
numberOfSectionsInCollectionView
和numberOfItemsInSection
- 异步:
cellForItemAtIndexPath
- 同步+异步:
sizeForItemAtIndexPath
因为cell复用的缘由,直接在
reloadData
后执行代码是有可能出问题的。好比在reloadData
前保留了一个cell,在reloadData
后,对这个cell(已经不是原来的cell了)进行某些操做,会出现一些异常问题。bash
在
reloadData
前不是保留cell,二是保留当前cell对应的NSIndexPath
,而后在reloadData
完毕(listView
真正刷新完毕)后经过方法cellForItemAtIndexPath:
从新获取cell,而后进行相应的操做。多线程
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
// 刷新完成,执行后续须要执行的代码
if ( self.didPlayIdx ) {
MyCell* cell = (MyCell*)[self.collectionView cellForItemAtIndexPath:self.didPlayIdx];
if (cell) {
[cell playWithPlayer:self.player];
}
}
复制代码
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死锁、Runloopapp
// 发生死锁,永远不会执行任务2和3
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
复制代码
#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(@"完成刷新");
}];
复制代码
由于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);
}
}
复制代码
iOS中只有主线程才能当即刷新UI。在子线程中是不可以更新UI,咱们看到的子线程可以更新UI的缘由是,等到子线程执行完毕,自动进入了主线程去执行子线程中更新UI的代码。因为子线程执行时间很是短暂,让咱们误觉得子线程能够更新UI。若是子线程一直在运行,则没法更新UI,由于没有办法进入主线程。异步
iOS 事件处理机制与图像渲染过程async