您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“ios中如何監聽reloadData刷新列表完畢的時機”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“ios中如何監聽reloadData刷新列表完畢的時機”這篇文章吧。
分析:
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); } }
原因二:用戶體驗
iOS中只有主線程才能立即刷新UI。在子線程中是不能夠更新UI,我們看到的子線程能夠更新UI的原因是,等到子線程執行完畢,自動進入了主線程去執行子線程中更新UI的代碼。由于子線程執行時間非常短暫,讓我們誤以為子線程可以更新UI。如果子線程一直在運行,則無法更新UI,因為沒有辦法進入主線程。
以上是“ios中如何監聽reloadData刷新列表完畢的時機”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。