1、iOS上的页面展示和逻辑
在介绍iOS的页面展示之前,我们需要先知道iOS应用的运行逻辑和设计模式。MVC即Model,View,Controller(模型,视图,控制器)。
用户在View层中所进行的创建或修改数据的操作,通过Controller对象传达出去,最终会创建或更新Model。Model更改时(例如通过网络连接接收到新数据),它会通知Controller对象,Controller对象更新相应的View对象。
2、Controller 的页面管理
上文提到,iOS中通过Controller(控制器)来管理View的绘制逻辑,那么具体是如何实现的呢?
在iOS中,有两类ViewController:
其中,容器类ViewController是这篇文章关注的重点,因为他们管理着View的显示逻辑。
容器类ViewController都是通过持有一个ViewController的数组来管理,一般来讲UINavigationController是通过先进后出(First In Last Out)的方式来管理,而UITabBarController则不局限于此。
不管是哪类ViewController,都继承自UIViewController。View作为一个ViewController的属性(property)存在,其生命周期在ViewController的生命周期内。
3、多页面栈的管理方法
有了上文的铺垫,接下来可以更加细致地介绍iOS中多页面栈的管理方法。正如之前提到的,多页面即多个View。iOS中采用容器类的ViewController来管理多个ViewController,而每个ViewController又对应着自己的View,从而实现统一管理。
以UINavigationController为例,作为官方推荐的容器类控制器,继承于UIViewController。UINavigationController通过栈的方式管理控制器的切换,控制入栈和出栈来展示各个视图控制器。
同时UINavigationController还持有屏幕上方的交互栏(navigationBar)和屏幕下方的工具栏(toolBar),并控制他们是否可见。
根据官方的文档,UINavigationController每次只会展示一个ViewController的View,每次进入到一个View的时候会push这个ViewController到navigation stack的最上层,覆盖并隐藏起其他的页面。而点击应用上方的NavigationBar返回按钮(如果没有隐藏起来的话)就会pop当前的ViewController,也就是返回上一层。
UINavigationController通过一个ordered array来管理,叫做navigation stack。
应用默认的UINavigationController的第一个view controller是根视图控制器,即 root view controller,放在stack的最底层,最新的在最高层。
UINavigationController* nav = [[UINavigationController alloc] init];// 新建两个ViewController,并设置他们的View的背景颜色UIViewController* vc1 = [[UINavigationController alloc] init];vc1.view.backgroundColor = [UIColor redColor];UIViewController* vc2 = [[UINavigationController alloc] init];vc2.view.backgroundColor = [UIColor blueColor]; [nav pushViewController:vc1 animated:NO]; //把vc1推到nav的stack中[nav pushViewController:vc2 animated:NO]; //把vc2推到nav的stack中
UIViewController* top = nav.topViewController; //这时top其实就是vc2
[nav popViewControllerAnimated:NO]; //这时nav的顶层VC被pop出,top变成vc1
通过解析源码的方法,发现UINavigationController不仅有简单的pushViewController和popViewController,还有popToViewController以及popToRootViewController这种指定的页面跳转,同时也可以添加动画效果,可操作范围还是比较大的。
//承接上面的代码..[nav popToViewController:vc1 animated:NO];[nav popToRootViewControllerAnimated:NO];
跟所有UIViewController一样,UINavigationController通过自己的代理(delegate)来实现方法,可以重写他的push和pop来实现自己的动画效果,但需要遵循UINavigationControllerDelegate这个协议。
一张图概括NavigationController的结构
容器类的ViewController通过一个特定的结构来实现多层级管理,但并不是所有页面都能确保是在同一个容器中,这个情况下ViewController基类提供了以自己为起点的页面跳转:
以ViewController自己为基础,可以得到自己的父控制器(parentViewController);自己展示的控制器(presentedViewController);展示自己的控制器(presentingViewController)。
得到了控制器,切换的方法也由ViewController类直接提供:
UIViewController* vc3 = [[UIViewController alloc] init];vc2 = vc3.parentViewController; //父控制器vc2 = vc3.presentedViewController; //vc3展示的控制器vc2 = vc3.presentingViewController; //展示vc3的控制器[vc3 presentViewController:vc2 animated:NO completion:nil];[vc3 dismissViewControllerAnimated:vc2 completion:nil];
上述的ViewController的转场方式是比较通用的方式,也许你会注意到,他还会收取一个animated的参数。这个参数就是询问你是否需要在页面切换的时候加入动画。默认的动画就是从右往左推出一个新的页面。但UIViewController还有一个方法可以自定义这个转场动画:
[vc3 transitionFromViewController:vc2 toViewController:vc1 duration:0.5 options:UIViewAnimationOptionTransitionFlipFromRight animations:^{} completion:nil];
这个方法就可以自定义转场方式了,可以看见我们在这里设置动画的时间是0.5秒,效果是从右边翻页入场。
从源码中可以发现ViewController实现了一个叫做UIStateRestoring的协议,所以持有一个叫做restorationIdentifier的属性,给他赋予一个NSString的值,会让ViewController在App被放到后台之前完成编码(保存)。保存的时候ViewController会把他内部的所有带有同样restorationIdentifier的子视图控制器(child view controller)的状态也一起保存下来。但ViewController自己不会自动保存其他的状态。
如果自己实现一个容器类的ViewController,就需要自己去给子视图控制器编码,保证每一个都必须是有独一无二的 restorationIdentifier。
每个UIViewController的类都会有自动的内存管理,通过didReceiveMemoryWarning这个方法来释放不需要的内存,进而管理low-memory condition。
如果多层级页面过多,清理过之后物理内存还是不满足,就会发生out of memory crash。
统计iOS设备的内存上限:以 iPhone XS Max 为例,总共的可用内存是 3735 MB(比硬件大小小一些,因为系统本身也会消耗一部分内存),而单个 app 可用内存达到 2039 MB,达到了 55%。当 app 使用的内存超过这个临界值,就会发生 OOM 崩溃。
多层级页面的管理由ViewController运营,同时包含有各种的view controller 分类结构来完成复杂的页面跳转或是实现不同的页面功能,如展示类ViewController和容器ViewController。
容器类控制器会根据添加的member value来决定是否需要留存之前的状态,会保存带有restorationIdentifier的所有ViewController的状态。但是内存有限制,如果进程根据didReceiveMemoryWarning的警告清理了内存却还是不够,应用就会崩溃。
至此,我们了解到了iOS端是如何去实现路由管理的,那么,就请期待我们下一篇文章《大前端开发中的路由管理之五:Flutter篇》吧,下篇文章将为大家揭秘Flutter是如何去做路由管理的。
[1] View Programming Guide for iOS
https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009503-CH1-SW2
[2] iOS MVC、MVVM、MVP详解
https://www.jianshu.com/p/d39a5eee48d7
[3] window and view 架构
https://blog.csdn.net/qq_30185503/article/details/102758732
[4] 深入理解ViewController以及view的加载
https://www.jianshu.com/p/52072a0d49c3
[5] UINavigationController 的详解(基于 API )
https://juejin.cn/post/6844903543262937096
[6] class UINavigationController
https://developer.apple.com/documentation/uikit/uinavigationcontroller?language=objc
QQ音乐招聘 Android / iOS 客户端开发,点击左下方“查看原文”投递简历~
也可将简历发送至邮箱:tmezp@tencent.com
文末为大家推荐一个技术号《腾讯音乐天琴实验室》,TME天琴实验室致力于对业内前沿科技如AI等方向进行相关研发,持续推出新技术提升TME旗下QQ音乐等平台的音乐视听体验,对音视频相关AI研发感兴趣的同仁们一起交流学习起来吧!!!
↓ ↓ ↓