前一篇<<postNotificationName同步调用导致的白屏问题>>里讲到"mediaView里抛通知时在异步线程抛,利用线程切换避免reloadData过程中再reloadData”.
为何这样做可以解决问题呢?
在异步线程(假设是thread10)抛HWCHAT_VIEW_NEED_RELOAD
通知,导致在thread10里同步调用了reloadChatCollectionViewData。因为在reloadChatCollectionViewData里进行了线程切换:yoho_dispatch_execute_in_main_queue,
切回到
主线程执行reloadData。
这样执行reloadData为何避免了reloadData过程中再reloadData呢?
下面涉及到runloop的问题。
看出问题时的log:
2015-03-25 10:44:40:168 demo[2957:807] Debug|[HWChatManager.m 175]: __45-[HWChatManager reloadChatCollectionViewData]_block_invoke 抛通知HWCHAT_VIEW_NEED_RELOAD
2015-03-25 10:44:40:168 demo[2957:807] Debug|[HWChatViewController.m 162]: -[HWChatViewController reloadChatCollectionViewData] 111111*********
2015-03-25 10:44:40:169 demo[2957:807] Debug|[HWChatViewController.m 164]: __52-[HWChatViewController reloadChatCollectionViewData]_block_invoke
22222==========
2015-03-25 10:44:40:206 demo[2957:807] Debug|[HWPhotoMediaItem.m 41]: __29-[HWPhotoMediaItem mediaView]_block_invoke_2
抛通知HWCHAT_VIEW_NEED_RELOAD
2015-03-25 10:52:59:275 demo[2957:807] Debug|[HWChatViewController.m 162]: -[HWChatViewController reloadChatCollectionViewData]
111111*********
2015-03-25 10:52:59:275 demo[2957:807] Debug|[HWChatViewController.m 164]: __52-[HWChatViewController reloadChatCollectionViewData]_block_invoke
22222==========
2015-03-25 10:52:59:277 demo[2957:807] Debug|[HWChatViewController.m 167]: __52-[HWChatViewController reloadChatCollectionViewData]_block_invoke
33333+++++++++++
807是主线程id,在主线程调用了[HWChatViewController reloadChatCollectionViewData],reloadData时导致[HWPhotoMediaItem mediaView]在主线程抛通知HWCHAT_VIEW_NEED_RELOAD,[HWChatViewController
reloadChatCollectionViewData]做为观察者的selector又执行了一次reloadData导致白屏。
这些动作都是在主线程的runloop的一次pass(循环的一次遍历,暂命名passA)里执行的。
主线程中, mediaView里抛通知时改为在异步线程抛。那么,进程需要等到线程切换时,才能切到异步线程thread10。
进程得到时间片进行线程切换,进入线程10.
thread10抛通知HWCHAT_VIEW_NEED_RELOAD, [HWChatViewController reloadChatCollectionViewData]做为观察者的selector在thread10中执行:
-(void)reloadChatCollectionViewData { HWLog(@"111111*********"); yoho_dispatch_execute_in_main_queue(^{ HWLog(@"22222=========="); [_collectionView reloadData]; [_collectionView layoutIfNeeded]; HWLog(@"33333+++++++++++"); [_collectionView scrollToBottomAnimated:YES]; }); }
然后reloadChatCollectionViewData里yoho_dispatch_execute_in_main_queue又切回到主线程。
这些动作是在thread10的runloop的一次pass(假设是passB)里执行的
那么,进程需要等到线程切换时,才能切到主线程执行reloadData。
进程得到时间片进行线程切换,再进入主线程执行passC。此时passA早已经执行完成,即之前的reloadData已完成执行。
那么passC中执行reloadData当然和之前的reloadData没有关系了,避免了"reloadData过程中再reloadData”。
无问题时的log如下:
2015-03-25 11:50:52:145 demo[10982:807] Warn|[HWRecentTableViewController.m 214]: -[HWRecentTableViewController
reloadQLMessage] 重新加载列表
2015-03-25 11:50:52:748 demo[10982:ce17] Debug|[HWPhotoMediaItem.m 41]: __29-[HWPhotoMediaItem mediaView]_block_invoke_2
抛通知HWCHAT_VIEW_NEED_RELOAD
2015-03-25 11:50:52:748 demo[10982:2507] Debug|[HWPhotoMediaItem.m 41]: __29-[HWPhotoMediaItem mediaView]_block_invoke_2
抛通知HWCHAT_VIEW_NEED_RELOAD
2015-03-25 11:50:52:748 demo[10982:ce17] Debug|[HWChatViewController.m 164]: -[HWChatViewController reloadChatCollectionViewData]
111111*********
2015-03-25 11:50:52:748 demo[10982:2507] Debug|[HWChatViewController.m 164]: -[HWChatViewController reloadChatCollectionViewData]
111111*********
2015-03-25 11:50:52:750 demo[10982:807] Debug|[HWChatViewController.m 166]: __52-[HWChatViewController reloadChatCollectionViewData]_block_invoke
22222==========
2015-03-25 11:50:52:777 demo[10982:807] Debug|[HWChatViewController.m 169]: __52-[HWChatViewController reloadChatCollectionViewData]_block_invoke
33333+++++++++++
2015-03-25 11:50:52:777 demo[10982:807] Debug|[HWChatViewController.m 166]: __52-[HWChatViewController reloadChatCollectionViewData]_block_invoke
22222==========
2015-03-25 11:50:52:804 demo[10982:807] Debug|[HWChatViewController.m 169]: __52-[HWChatViewController reloadChatCollectionViewData]_block_invoke
33333+++++++++++
最后再描述一下runloop. 为了好解释,以单cpu情形为例。
程序能不断地处理用户点击或者网络事件, 是因为runloop的驱动,不然,cpu执行完指令不是应该立刻退出程序了吗?
runloop可以理解为你用手臂在水里不断划圈,驱动了水的转动(程序的执行)。你的手臂划一个完整的圆圈,这一个圆圈,就是上面提的一个pass。
理解runloop也可以结合cpu时间片。
进程得到cpu时间片后,在某个线程的runloop的某次pass里执行,执行时如果遇到网络请求或磁盘io或其他原因导致出让cpu,此次pass结束执行。
后面进程再获得cpu时间片得以运行时,就是在下一个pass里执行了。