1、背景:

2014年4月份第一次接触IOS端开发,为某银行开发一款金融app。
在开发的最后阶段,加入了需要从任意一个页面直接返回主页的功能。
悲催的是,当时没有使用UINavigationController进行导航管理,而是使用了IOS中的模态跳转方式(presentViewController/dismissViewControllerAnimated).

因此需要找的一种方法进行,实现如下方式的导航跳转:

已知: 页面a→页面b→页面c

求解: 页面c直接跳回到页面a,并且要求不能有内存泄露,循环依赖等

2、解题思考:

面对上面的需求,最简单的方式是将所有控制器都改成UINavigationController,并且利用pushViewController / popToViewController/ popToRootViewControllerAnimated 等方法进行完美解题。但是当时项目的页面将近100个,分成三大模块,需要大规模修改设计页面以及调整大量代码,这并不是一个现实的解决方案,不到万不得已,不能采取如此低劣手段!

我们需要一个满足如下条件的解决方案:

1) 对于已经在InterfaceBuilder中完成的页面,不做任何修改
2) 尽量少的修改代码,因为很多代码已经经过测试中心测试过,如果修改,需要全部重新测试,时间来不及。

当时通过两天的研究,深入的了解了IOS中的跳转流程和生命周期后,找到了一个相对完美的解决方案,能够满足上面提到的要求。通过一个Demo,来和大家一起分享。

3、解题流程:

1) 为了减少代码的修改,增加一个基类。
@interface BaseViewController : UIViewController

//防止controller循环引用,使用weak引用方式
@property(nonatomic,weak) BaseViewController* parentController;

//为了能够了解某个页面控制器生命周期相关信息,给该控制器取个名字
@property(nonatomic,copy)   NSString*   ctrlName;

//关键的函数,进行页面c-->跳转到主页
-(void) doDismiss;

@end
2) 为了更好的了解IOS中ViewController的生命周期,我们在基类中输出相关信息来了解生命周期相关信息。
@implementation BaseViewController

-(id) init
{
    self.ctrlName = @"";
    self.parentController = nil;
    return [super init];
}

- (void)viewDidLoad
{
    NSLog(@"%@ invoke viewDidLoad",self.ctrlName);
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

-(void)viewWillAppear:(BOOL)animated
{
    NSLog(@"%@ invoke viewWillAppear",self.ctrlName);
    [super viewWillAppear:animated];
}

-(void)viewDidAppear:(BOOL)animated
{
    NSLog(@"%@ invoke viewDidAppear",self.ctrlName);
    [super viewDidAppear:animated];
}

-(void) viewWillDisappear:(BOOL)animated
{
    NSLog(@"%@ invoke viewWillDisappear",self.ctrlName);
    [super viewWillDisappear:animated];
}

-(void) viewDidDisappear:(BOOL)animated
{
    NSLog(@"%@ invoke viewDidDisappear",self.ctrlName);
    [super viewDidDisappear:animated];
}

- (void)didReceiveMemoryWarning
{
    NSLog(@"%@ invoke didReceiveMemoryWarning",self.ctrlName);
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

-(void)dealloc
{
    NSLog(@"%@ .......dealloc.......",self.ctrlName);
}
3) 关键的递归函数,核心是理解dismissViewControllerAnimated:completion函数中completion回调的时机点,这个是解题的钥匙。
-(void)doDismiss
{
    NSLog(@"%@ dismiss begin",self.ctrlName);

    if([self.parentController isKindOfClass:[ViewController class]])
    {
        [self dismissViewControllerAnimated:YES completion: ^(void)
         {
             NSLog(@"%@ dismiss end",self.ctrlName);

             if(self.parentController)
             {
                 [self.parentController doDismiss];
             }
         }];
    }
    else
    {
        [self dismissViewControllerAnimated:NO completion: ^(void)
         {
             NSLog(@"%@ dismiss end",self.ctrlName);

             if(self.parentController)
             {
                 [self.parentController doDismiss];
             }
         }];

    }
}
4) Demo页面结构如下图: MainViewController-->SecondViewController-->Child1ViewController,然后直接跳回到MainViewController.

5) 所有的ViewController都继承自我们自定义的BaseViewController,具有关键的doDismiss递归方法!

IOS视图控制器导航及生命周期研究Demo_第1张图片

6) 看一下从MainViewController-->SecondViewController-->Child1ViewController-->MainViewController生命周期的相关信息:

a、启动程序,进入MainViewController:
2017-02-11 17:25:16.964 navTest[933:17086] mainViewCtrl invoke viewDidLoad
2017-02-11 17:25:16.966 navTest[933:17086] mainViewCtrl invoke viewWillAppear
2017-02-11 17:25:16.975 navTest[933:17086] mainViewCtrl invoke viewDidAppear

b、从MainViewController→SecondViewController:
2017-02-11 17:28:29.717 navTest[933:17086] SecondViewController invoke viewDidLoad [先newController调用viewDidLoad]
2017-02-11 17:28:29.721 navTest[933:17086] mainViewCtrl invoke viewWillDisappear [然后oldController调用viewWillDisappear]
2017-02-11 17:28:29.722 navTest[933:17086] SecondViewController invoke viewWillAppear [然后newController调用viewWillAppear]
2017-02-11 17:28:30.229 navTest[933:17086] SecondViewController invoke viewDidAppear [然后newController调用viewDidAppear ]
2017-02-11 17:28:30.229 navTest[933:17086] mainViewCtrl invoke viewDidDisappear [然后oldController调用viewDidDisappear ]
2017-02-11 17:28:30.230 navTest[933:17086] mainViewCtrl present end
[最后oldController调用presentViewController完成回调被调用]

下面是presentViewController完成回调信息的输出代码,用来了解completion是在哪个阶段被调用的,很重要的信息哦!!!

- (IBAction)ClickToEnterSecondController:(id)sender {
    SecondViewController* ctrl = [self.storyboard instantiateViewControllerWithIdentifier:@"second"];
    ctrl.parentController = self;
    ctrl.ctrlName = @"SecondViewController";
    [self presentViewController:ctrl animated:YES completion:^(void)
     {
         NSLog(@"%@ present end",self.ctrlName);
     }];
}

c、从Child1ViewController直接返回到MainViewController的流程(跳过SecondViewController)

- (IBAction)returnMainViewController:(id)sender {
    NSLog(@"**********************doDismiss*********************");
    [self doDismiss];
}

d、2017-02-11 17:30:30.634 navTest[933:17086] **doDismiss* [要开始调用doDismiss函数了,表示点击了返回按钮]
2017-02-11 17:30:30.635 navTest[933:17086] Child1ViewController dismiss begin  [输出dismiss begin,表示调用了Child1ViewController的doDismiss递归函数]
2017-02-11 17:30:30.637 navTest[933:17086] Child1ViewController invoke viewWillDisappear [oldController viewWillDisappear]
2017-02-11 17:30:30.638 navTest[933:17086] SecondViewController invoke viewWillAppear [newController viewWillAppear]
2017-02-11 17:30:30.641 navTest[933:17086] SecondViewController invoke viewDidAppear [newController viewDidAppear]
2017-02-11 17:30:30.641 navTest[933:17086] Child1ViewController invoke viewDidDisappear [oldController viewDidDisappear ]
2017-02-11 17:30:30.641 navTest[933:17086] Child1ViewController dismiss end [关键时刻哦,oldController self dismissViewControllerAnimated: completion: 中的completion 回调被触发了,它是在 oldController viewDidDisappear后被触发的哦]
2017-02-11 17:30:30.641 navTest[933:17086] SecondViewController dismiss begin [这时候,从ChildViewController回弹到SecondViewController的流程完成了,接下来递归调用,要完成从SecondViewController回弹到MainViewController的过程,重复上面的流程而已]
2017-02-11 17:30:30.642 navTest[933:17086] Child1ViewController …….dealloc……. [Child1ViewController已经完全消失了,因此内存被析构了,这样确保内存不会泄露哦]

下面是递归部分,和上面流程一样,只是从SecondViewController回跳到MainViewController

2017-02-11 17:30:30.643 navTest[933:17086] SecondViewController invoke viewWillDisappear
2017-02-11 17:30:30.644 navTest[933:17086] mainViewCtrl invoke viewWillAppear
2017-02-11 17:30:31.156 navTest[933:17086] mainViewCtrl invoke viewDidAppear
2017-02-11 17:30:31.156 navTest[933:17086] SecondViewController invoke viewDidDisappear
2017-02-11 17:30:31.156 navTest[933:17086] SecondViewController dismiss end
2017-02-11 17:30:31.157 navTest[933:17086] mainViewCtrl dismiss begin
2017-02-11 17:30:31.157 navTest[933:17086] SecondViewController …….dealloc…….  [SecondViewController 内存也很完美的被析构掉,目前仅剩下MainViewController还活着]

show一下某银行APP以前模拟器中的效果截图,IOS开发还是非常令人非常愉悦的感觉!

IOS视图控制器导航及生命周期研究Demo_第2张图片


×××:

IOS UIViewController 导航及生命周期Demo