@(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