主线程阻塞导致的事件传递混乱

公司某个ios产品代码里面,在启动过程当中,有个看起来很怪异的逻辑。

先说一下启动的基本过程中,首先window的rootViewController设置为一个活动图FlashViewController:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   ...
   self.window.rootViewController = [[FlashViewController alloc] init];
   ...
}

如果用户点击活动图或者过一两秒,再将rootViewController设置为主视图MainViewController:

- (void)onFlashViewControllerComplete {
    ...
    self.window.userInteractionEnabled = NO;
    self.window.rootViewController = [[MainViewController alloc] init];
    ...
}

在MainViewController里面:

- (void)viewDidAppear {
   ...
   self.view.window.userInteractionEnabled = YES;
   ...
}

读到这里应该已经发现了这个怪异的逻辑,切换rootViewController之前, window被设置成不接受点击事件,在主视图显示之后再恢复。从相关同事哪里了解到,这个逻辑要解决的问题是,如果用户在活动图里面疯狂点击,那么点击事件会传递给了MainViewController,进而造成意外情况。

经过一些测试,我推测原因应该是这样的,ios系统里面用户事件的优先级很高,不管应用在干什么,只要用户点击屏幕,系统会为应用生成事件并放到事件队列里面,如果应用的主线程被阻塞了,那么事件队列里面就可能积压很多的事件,当主线程空出来之后才得到处理。

问题就出在这个时间差上面,在上面的case中,但由于切换rootViewController阻塞了线程,用户感觉点击的目标是FashViewController所代表的界面,实际上部分事件发送给了下一个界面MainViewController。这个问题很容易重现,只要往navigationController里面push一个controller,在后者的loadView里面执行一个很耗时间的操作,比如分配100000个对象,然后点击屏幕就能复现。

解决方法1

产品当前的方法基本上能解决这个问题(当主线程空出来的时候,window不接受事件,于是事件被忽略掉),但是不够彻底,viewDidAppear并不代表渲染已经完成,只要再延迟一两个runloop就好了,在MainViewController里面:

- (void)viewDidAppear {
   ...
   if (!self.view.window.userInteractionEabled) {
	dispatch_async(dispatch_get_main_queue(),^{
	   self.view.window.userInteractionEnable = YES;
	})
   }
   ...
}

这个方法的一个缺陷是将这个逻辑扩散到两个地方,不利于维护

解决方法2

避免切换rootviewcontroller,一启动就将MainViewController设置为rootViewController,把FlashViewcController.view覆盖到window上;这样的实际是启动时将两个controller一起渲染,缺点是MainViewController的viewDidAppear会提前;公司另外有两个产品(当时还不知道有这个问题)用的就是这个方案,所以从来没有这个问题

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    ...
    self.window.rootViewController = [[MainViewController alloc] init];
    FlashViewController *flash = [[FlashViewController alloc] init];
    flash.view.frame = self.window.bounds;
    [self.window addSubview:flash.view];
    ...
}  
原文地址:https://www.cnblogs.com/longhuihu/p/4032638.html