背景

最近在开发一个iOS项目,有这么几个页面:登录页、引导页、主页。因为这几个页面之间几乎没有关联,所以就做成了3个不同的viewController,然后通过直接[UIWindow setRootViewController:]的方式来进行页面切换,其中,引导页和主页是单例,登陆页因为某些原因每次使用都会实例化。

出现的问题

正常使用流程是:引导页-> 登录页 -> 主页,流程很顺,但是在退出后就出问题了,在主页退出后,重新展示登录页,然后在登陆页登陆完了之后,调用[UIWindow setRootViewController:]设置主页单例,打了断点发现逻辑已经调用,但是发现无论怎么设置,都没用,屏幕上依然是登录页,主页并没有显示出来,非常疑惑。

问题原因

打开xcode的调试功能:View UI Hierarchy,就能看到类似如下的场景了。(保密需要没有用自己的图)

最下面是引导页,然后中间是登录页,然后最上面是主页,然后因为退出之后又显示了登陆页,而登录页不是单例,所以在主页上又覆盖了一层登录页,而因为主页是单例,所以在第二次setRootViewController之后,并没有生效。而看了一下setRootViewController的作用,主要逻辑就是把要添加的viewController的view添加到window上,UIWindow本身就是一个view。

危害

这个问题其实还是挺有风险的,如果我的主页不是单例,那么我甚至都发现不了这个问题,只会一层一层往上叠加,就会造成内存泄露了。

解决方法

知道了原因,解决起来就轻松了,两种解法:

  1. 不使用setRootViewController的方式,而且采用pushViewController或者presentViewController的之类的方式去显示页面。
  2. 修改setRootViewController,在每次调用之前先删掉UIWindow上所有的view,然后在添加。

其实正常来说,还是推荐使用第一种方式来切换页面,而我因为改动比较大,而且时间比较急,所以采用了第二种方式,代码如下:

1
- (void)setRootViewController:(UIViewController *)rootViewController
{
    //remove old rootViewController's sub views
    for (UIView* subView in self.rootViewController.view.subviews)
    {
        [subView removeFromSuperview];
    }
    
    //remove old rootViewController's view
    [self.rootViewController.view removeFromSuperview];
    
    //remove empty UILayoutContainerView(s) remaining on root window
    for (UIView *subView in self.subviews)
    {
        if (subView.subviews.count == 0)
        {
            [subView removeFromSuperview];
        }
    }
    
    //set new rootViewController
    [self addSubview:rootViewController.view];
    [super setRootViewController:rootViewController];
}
iOS