├── .gitignore ├── README.md ├── ViewControllerPreRender.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ViewControllerPreRender ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m ├── ViewControllerPreRender.h ├── ViewControllerPreRender.m └── main.m ├── calc.rb └── toBeCaclulate.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ViewControllerPreRender.xcodeproj/project.xcworkspace/xcuserdata/hite.xcuserdatad/UserInterfaceState.xcuserstate 2 | ViewControllerPreRender.xcodeproj/xcuserdata/hite.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist 3 | ViewControllerPreRender.xcodeproj/xcuserdata/hite.xcuserdatad/xcschemes/xcschememanagement.plist 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UIViewController 预加载方案浅谈 2 | 3 | ### 一. 引子 4 | 预加载作为常规性能优化手段,在所有性能敏感的场景都有使用。不同的场景会有不同的方案。举个例子,网易邮箱简约邮里,收件箱列表使用了数据预加载,首页加载完毕后会加载后一页的分页数据,在用户继续翻页时,能极大提升响应速度;在微信公众号列表,不仅预加载了多个分页数据,还加载了某个公众文章的文字部分,所以当列表加载完毕之后,你走到了没有网络的电梯里,依然可以点击某个文字,阅读文字部分,图片是空白。 5 | 6 | 在 iOS 常规的优化方案中,预加载也是极常见的手段,多见于:预加载图片、配置文件、离线包等业务资源。查阅后知, ASDK 有一套很智能的预加载策略; 7 | >在滚动方向(Leading)上 Fetch Data 区域会是非滚动方向(Trailing)的两倍,ASDK 会根据滚动方向的变化实时改变缓冲区的位置;在向下滚动时,下面的 Fetch Data 区域就是上面的两倍,向上滚动时,上面的 Fetch Data 区域就是下面的两倍。 8 | 9 | 系统层面,iOS 10 里`UIKit` 还为开发者新增了`UITableViewDataSourcePrefetching` 10 | ```objective-c 11 | @protocol UITableViewDataSourcePrefetching 12 | @required 13 | 14 | // indexPaths are ordered ascending by geometric distance from the table view 15 | - (void)tableView:(UITableView *)tableView prefetchRowsAtIndexPaths:(NSArray *)indexPaths; 16 | 17 | @optional 18 | 19 | // indexPaths that previously were considered as candidates for pre-fetching, but were not actually used; may be a subset of the previous call to -tableView:prefetchRowsAtIndexPaths: 20 | - (void)tableView:(UITableView *)tableView cancelPrefetchingForRowsAtIndexPaths:(NSArray *)indexPaths; 21 | 22 | @end 23 | ``` 24 | 等新的协议来提供` UITableView\UICollectionView` 预加载 data 的能力。 25 | 26 | 但是对于整个 App 的核心组件 `UIViewController` 却少见预加载的策略。极少数场景是这样的:整个界面包含多个 `UIViewController` 的层级,除了显示第一个 `UIViewController` 外 ,预加载其他的 `UIViewController` 。 27 | ### 二. `UIViewController` 到底能不能预加载? 28 | 在和同事解决严选 App 内“领取津贴”弹窗慢的问题时,我思考了这个问题,所以查阅了 [ Developer Documentation](apple-reference-documentation://hc25s7SuiZ), 大概有以下的收获; 29 | 1. 在同一个 `navigation stack`里不能 push 相同的一个`UIViewController` ,否则会崩溃;而来自不同 `navigation stack` 的 `UIViewController` 是可以被压入 stack 的,这也是预加载的关键。 30 | 2. 当某个 `UIViewController` 执行了 `viewDidLoad()`之后,整个 `UIViewController` 对象已经在内存内。如果我们要使用 VC 时,可以直接从内存里获取,将会获得速度提升 31 | 2. `UIViewController` 作为 `UIWindow` 和 `vc.view`中间层,负责事件分发、响应链, `UIViewController` 子元素容器,子元素根据 `UIViewController` 的尺寸 layout 32 | 2. `UIViewController.view` 是个懒加载属性,由 `loadView()` 初始化,在 viewDidLoad 事件开始时,就已经完成 33 | 3. `UIViewController` 在被添加到 `navigation stack`后是否会被渲染,取决于所在的 window 是不是 hidden = NO,和在不在屏幕上没有关系 34 | 35 | **答案:可以被预加载,除了本文尝试的多个` navigation stack`的方式外, apple 自己在早期推广 storyboard 和 xib 文件模式开发 iOS 应用时,也抱有相同的意图** 36 | 37 | ### 三. `UIViewController` 渲染的流程? 38 | 因为 UIKit 没有开源,我从 Apple Documents 和 `Chameleon` project 的重写源码里试图还原真实的 `UIViewController` 在 UIKit 中的渲染逻辑。以下是我根据自己的理解画的 UIViewController 被添加到 UIWindow 的渲染流程,肯定有错误和遗漏,仅供理解本文使用。 39 | 40 | **图例参考 Safari**,序号后面的图形,表示本阶段 ViewController 的 view 层级,认清这些事件,可以知道哪个阶段做哪些操作是合适的? 41 | ![vc render flow.png](https://upload-images.jianshu.io/upload_images/277783-622238450881ef36.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 42 | *注意:以上为 iOS 12 里的情况,在 iOS 13 里,第 5 序号的 View 比目前 iOS 12 要多两个 View,`UIDropShadowView`,`UITransitionView`*。 43 | 44 | ### 四. ViewControllerPreRender 45 | 在整理出上面的流程结论后,编写了`ViewControllerPreRender`,虽然不到 100 行,前后却花了一周,主要是为了解决下面这个 XCode 警告。 46 | ``` 47 | "Unbalanced calls to begin/end appearance transitions for " 48 | ``` 49 | 幸好通过多次尝试,最终解决掉。 50 | 代码很短,全文摘录,以下以注释的方式详细解读。 51 | ```objective-c 52 | //.h 文件 53 | @interface ViewControllerPreRender : NSObject 54 | 55 | + (instancetype)defaultRender; 56 | 57 | - (void)showRenderedViewController:(Class)viewControllerClass completion:(void (^)(UIViewController *vc))block; 58 | @end 59 | //.m 文件 60 | #import "ViewControllerPreRender.h" 61 | 62 | @interface ViewControllerPreRender () 63 | 64 | @property (nonatomic, strong) UIWindow *windowNO2; 65 | /** 66 | 已经被渲染过后的 ViewController,池子,在必要时候 purge 掉 67 | */ 68 | @property (nonatomic, strong) NSMutableDictionary *renderedViewControllers; 69 | @end 70 | 71 | static ViewControllerPreRender *_myRender = nil; 72 | @implementation ViewControllerPreRender 73 | 74 | + (instancetype)defaultRender{ 75 | static dispatch_once_t onceToken; 76 | dispatch_once(&onceToken, ^{ 77 | _myRender = [ViewControllerPreRender new]; 78 | _myRender.renderedViewControllers = [NSMutableDictionary dictionaryWithCapacity:3]; 79 | // 增加一个监听,当内存紧张时,丢弃这些预加载的对象不会造成功能错误, 80 | // 这样也要求 UIViewController 的 dealloc 都能正确处理资源释放 81 | [[NSNotificationCenter defaultCenter] addObserver:_myRender 82 | selector:@selector(dealMemoryWarnings:) 83 | name:UIApplicationDidReceiveMemoryWarningNotification 84 | object:nil]; 85 | }); 86 | return _myRender; 87 | } 88 | 89 | /** 90 | 内部方法,用来产生可用的 ViewController,如果第一次使用。 91 | 直接返回全新创建的对象,同时也预热一个相同类的对象,供下次使用。 92 | 支持预热多个 ViewController,但是不易过多,容易引起内存紧张 93 | 94 | @param viewControllerClass UIViewController 子类 95 | @return UIViewControllerd 实例 96 | */ 97 | - (UIViewController *)getRendered:(Class)viewControllerClass{ 98 | if (_windowNO2 == nil) { 99 | CGRect full = [UIScreen mainScreen].bounds; 100 | // 对于 no2 的尺寸多少为合适。我自己做了下实验 101 | // 这里设置的尺寸会影响被缓存的 VC 实例的尺寸。但在预热好的 VC 被添加到当前工作的 navigation stack 时,它的 View 的尺寸是正确的和 no2 的尺寸无关。 102 | // 同样的,在被添加到 navigation stack 时,会触发 viewLayoutMarginsDidChange 事件。 103 | // 而且对于内存而言,尺寸越小内存占用越少,理论上 (1,1,1,1) 的 no2 有能达到预热 VC 的效果。 104 | // 但是有些 view 不是被 presented 或者 pushed,而是作为子 ViewController 的子 view 来渲染界面的。这需要 view 有正确的尺寸。 105 | // 所以这里预先设置将来真正展示时的尺寸,减少 resize、和作为子 ViewController 使用时出错,在本 demo 中,默认大部分的尺寸是全屏。 106 | UIWindow *no2 = [[UIWindow alloc] initWithFrame:CGRectOffset(full, CGRectGetWidth(full), 0)]; 107 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[UIViewController new]]; 108 | no2.rootViewController = nav; 109 | no2.hidden = NO;// 必须是显示的 window,才会触发预热 ViewController,隐藏的 window 不可用。但是和是否在屏幕可见没关系 110 | no2.windowLevel = UIWindowLevelStatusBar + 14; 111 | 112 | _windowNO2= no2; 113 | } 114 | 115 | NSString *key = NSStringFromClass(viewControllerClass); 116 | UIViewController *vc = [self.renderedViewControllers objectForKey:key]; 117 | if (vc == nil) { // 下次使用缓存 118 | vc = [viewControllerClass new]; 119 | // 解决 Unbalanced calls to begin/end appearance transitions for 关键点 120 | // 1. 使用 UINavigationController 作为 no2 的 rootViewController 121 | // 2. 如果使用 UIViewController 作为 no2 的 rootViewController,始终有 Unbalanced calls 的错误 122 | // 虽然是编译器警告,实际上 Unbalanced calls 会影响被缓存的 vc, 当它被添加到当前活动的 UINavigation stack 时,它的生命周期是错误的 123 | // 所以这个警告必须解决。 124 | UINavigationController *nav = (UINavigationController *)_windowNO2.rootViewController; 125 | [nav pushViewController:vc animated:NO]; 126 | [self.renderedViewControllers setObject:vc forKey:key]; 127 | // 128 | return [viewControllerClass new]; 129 | } else { // 本次使用缓存,同时储备下次 130 | // 必须是先设置 no2 的新 rootViewController,之后再复用从缓存中拿到的 viewControllerClass。否则会奔溃 131 | UINavigationController *nav = (UINavigationController *)_windowNO2.rootViewController; 132 | [nav popViewControllerAnimated:NO]; 133 | UIViewController *fresh = [viewControllerClass new]; 134 | 135 | [nav pushViewController:fresh animated:NO]; 136 | // 在 setObject to renderedViewControllers 字典时,保证被渲染过 137 | [self.renderedViewControllers setObject:fresh forKey:key]; 138 | 139 | return vc; 140 | } 141 | } 142 | 143 | /** 144 | 主方法。传入一个 UIViewController 的 class 对象,在调用的 block 中同步的返回一个预先被渲染的 ViewController 145 | 146 | @param viewControllerClass 必须是 UIViewController 的 Class 对象 147 | @param block 业务逻辑回调 148 | */ 149 | - (void)showRenderedViewController:(Class)viewControllerClass completion:(void (^)(UIViewController *vc))block{ 150 | // CATransaction 为了避免一个 push 动画和另外一个 push 动画同时进行的问题。 151 | [CATransaction begin]; 152 | UIViewController *vc1 = [self getRendered:viewControllerClass]; 153 | 154 | // 这里包含一个陷阱—— 必须先渲染将要被 cached 的 ViewController,然后再执行真实的 block 155 | // 理想情况,应该是先执行 block,然后执行 cache ViewController,因为 block 更重要些。暂时没想到方法 156 | [CATransaction setCompletionBlock:^{ 157 | block(vc1); 158 | }]; 159 | [CATransaction commit]; 160 | } 161 | 162 | - (void)dealMemoryWarnings:(id)notif 163 | { 164 | NSLog(@"release memory pressure"); 165 | [self.renderedViewControllers removeAllObjects]; 166 | } 167 | @end 168 | ``` 169 | ### 五. 性能提升如何? 170 | 以 native 体验中通常体验最差的 webview 为例, 目标是严选商城的 h5 ,`http://m.you.163.com`,分别以传统的,每次都新创建 `ViewController`的方式;第二次之后使用预热的 `ViewController`加载严选首页两种方式测试,保持 `ViewController`内部逻辑相同,详见 demo 工程里注释。 171 | 172 | 测试方案:*模拟器,每种方式测试时都重启,各测试了 20 次左右*,统计表格如下,navigationStart 作为网络加载时间的开始标志,以 document.onload 作为页面加载完毕的标志; 173 | \> 1. 传统方式 174 | 点击到网络加载时间(ms) | 点击到页面加载完毕时间(ms) 175 | ---|--- 176 | 409.042969 | 2237.258057 177 | 382.000244 | 2294.206055 178 | 421.780762 | 2377.906250 179 | 435.476318 | 2358.933350 180 | 443.190186 | 2261.447998 181 | 379.502930 | 2243.837158 182 | 386.897949 | 2322.465088 183 | 508.499023 | 2385.695068 184 | 490.614014 | 2639.933105 185 | 407.436035 | 2384.422852 186 | 478.447998 | 2305.270264 187 | 426.408691 | 2340.742920 188 | 598.571777 | 2465.007812 189 | 453.924072 | 2424.213135 190 | 441.053955 | 2371.049805 191 | 399.669922 | 2218.141113 192 | 779.028809 | 2659.640625 193 | 68.835938 | 1934.873047 194 | 515.513916 | 2552.829834 195 | 439.666016 | 2268.033936 196 | 440.330811 | 2357.508789 197 | **Avg of 21:** | 198 | 443.14 | 2352.54 199 | \> 2. 使用预加载方式 200 | 201 | 点击到网络加载时间(ms) | 点击到页面加载完毕时间(ms) 202 | ---|--- 203 | 63.797852 | 2538.381836 204 | 63.152832 | 2333.105957 205 | 64.150146 | 2302.843750 206 | 59.484863 | 2155.601074 207 | 57.637207 | 2382.412842 208 | 55.749756 | 2050.655762 209 | 51.270020 | 1895.146729 210 | 54.883789 | 1793.544922 211 | 53.313965 | 1897.723877 212 | 78.262207 | 1777.684814 213 | 48.425049 | 1828.953857 214 | 50.403320 | 2075.978027 215 | 48.640625 | 2168.324951 216 | 58.913818 | 1946.458984 217 | 40.200928 | 1850.614990 218 | 54.635010 | 2198.915039 219 | 51.363770 | 1956.969971 220 | **Avg of 17:** | 221 | 56.13 | 2067.84 222 | 223 | **从测试数据可见**,使用预加载的方式显著的提升了 `navigationStart`的性能,`443 ms` 减少到 `56 ms`,相应的 `document.onload`事件也提前,`2357` 到 `2067`。 224 | 相比之下,预加载方式提前 400ms 发送网络请求(但是完成加载耗时只少 300ms,猜测是 CPU 资源调度问题)。以上数据只作为性能提升参考,对于加载 WebView 的 VC 而言,预初始化 WebView 以及其他元素,可以提高加载 h5 页面的速度。 225 | 226 | ### 六,原因探析 227 | 对 `ViewControllerPrerender`的逻辑分析解释为什么会有提速,在使用`ViewControllerPreRender`时,需要特别留意什么地方,以免掉入误区。 228 | **根据 preRender 的原理**,我大概画了图例来解释。 229 | ![old vs new vc route.png](https://upload-images.jianshu.io/upload_images/277783-e780286788d6f10a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 230 | 231 | 上半部分,所有阶段是线性的;下半部分,可以做到并行,尤其是第三个 VC 的显示,将异步加载数据也放到并行逻辑了,这对有性能瓶颈的界面优化不失为一种方式 232 | 总结:预加载利用了并行这一传统性能优化技术,同时对 ViewController 的生命周期也提出更高的要求,譬如: 233 | 1. 被预热的 ViewController,需要划分职责,在`viewDidLoad`里搭建框架,,而在另一个单独的接口如本 demo 里的`setUrl`用来使用业务数据渲染页面。 234 | 2. 被预加载的 ViewController 的`viewDidLoad` 不宜占用太多主线程资源,避免对当前界面打开产生负面影响。 235 | 236 | ### 七,preRender 适宜的场景 237 | 在 App 性能问题中, native 自己的 ViewController性能表现并不是瓶颈,所以目前业界对 UIViewController 的预加载并没有太多可参考的案例,不过对于某些场景优化还是有指导意义。在本文开始时提到的严选商品详情页里领取津贴是弹窗,常规情况下弹出是比较慢的,经过讨论后,我们决定对津贴弹窗做两个优化 238 | 1. 在弹窗出现时使用缩放动画,h5 加载也使用 loading 239 | 2. 使用预加载弹窗的 ViewController。 240 | 从测试数据来看,从点击到最后加载完毕,大概节省了 300 ms,还需要进一步考虑 h5 的页面优化。 241 | 242 | 题外话,App 作为严选用户体验的重要载体,App 性能是极其重要一环。我们对弹窗的体验做了少许优化。 243 | 244 | 在严选里弹窗有两种,一种是被动弹窗,比方说从后台数据返回中,得知有弹窗需要显示,native 根据全局弹窗排序,决定显示那个——当后台数据返回指定的 url 被加载完毕之后,才弹出遮罩,显示被加载好的 url;如果 url 加载失败,就不会弹出弹窗。 245 | 而对于用户主动弹出的弹窗,如用户在详情页点击 cell,弹出领取津贴,我们分 native 加速(使用预加载)和 h5 加速两部分。 246 | 247 | 另外比较适合 preRender 的地方如, 248 | 1. 我的订单界面,当用户某个订单有商家已发货未收货时,根据行为统计,用户大概率会打开第一条已发货的订单去查看当前物流(物流数据来自第三方,响应速度没有保证),所以在进入我的订单时,可以预先加载一个查看最新未完成订单的物流的 ViewController。 249 | 2. 用户在详情页面,点击了我好评率,那么大概率,用户还会打开用户晒单的视频和图片。这时候可以预加载一个视频播放器和图片浏览器,提供用户的响应速度等。 250 | ![好评跳转到带图评分列表](https://upload-images.jianshu.io/upload_images/277783-27c0e5fd3badb36a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 251 | 252 | 对于大部分功能也能而言, prefetch 并不是必选项,还需要根据自身的业务来决定使用可以 prefetch 的思想解决 App 体验的瓶颈问题,不要随意使用 `ViewControllerPrefetch`,增加额外复杂度。 253 | 254 | ### 八,xib 和 storyboard 带来的启示 255 | 当我接触 iOS 开发时,已经到了 iOS 推销 storyboard 开发方式失败的时候,大部分可需要持续迭代的 App,其实不适合用 xib 和 storyboard 来开发,它的可视化带来的好处相比项目协作迭代里遇到的 diff 困难、复用困难、启动慢等坏处,不值一提。 256 | 时至今日,当我思考预加载方式在 `viewDidiLoad` 里还要多少操作空间时,我发现 xib 和 storyboard 在被苹果推广时没有被提到它预加载的优点,一直没有引起重视。 257 | 相同的 ViewController 使用的 xib 和 storyboard 文件被 init 为 实例之后,后续相同的ViewController 都会来 copy 被初始化好的 storyboard 来构建界面。开发人员创建完 xib 和 storyboard,需要持久化为文件,使用 initWithCoder:方法实现序列化,打开 xib 和 storyboard 时,先从文件反序列化解析得到 xml 文件,然后用 xml 文件绘制 interface builder。它的底层机制决定了它在开发启动、App 启动时会有性能损耗,不过也为我们做了一个例子—— 如何预加载 View 片段乃至 ViewController 本身。以 storyboard 为例,你可以在 storyboard 里做以下操作; 258 | 1. 绘制 ViewController 的 view 层次,特别的,会首先限制 storyboard 里绘制的静态数据 259 | 2. 添加 view 之间的约束 260 | 3. 转场(segue)和按钮动作跳转 261 | 262 | 而最终的用户界面需要等待网络返回真实数据后重新渲染,在此期间,显示静态的等待界面。所以在需要被缓存的 `UIViewController`需要可以安全的编写 UI、事件和转场等逻辑,将动态部分(网络请求)的发起逻辑写在转场结束之后。 263 | 264 | ### 十,补记 265 | 1. [Unbalanced calls to begin/end appearance transitions for ,这个警告必须解决,否则会导致被缓存的 ViewController 被添加到活动 stack 时,生命周期紊乱导致一些依赖生命周期执行的逻辑失效,如电商行业里很看重的曝光统计数据不正确 266 | 2. Demo 工程里已经有 calc.rb 可以直接将从 console 里拿到的数据实现为报表,方便你测试自己的页面性能加载提升对比。 267 | 268 | ### 参考 269 | [1] [预加载与智能预加载(iOS)](https://draveness.me/preload) 270 | [2] [iOS性能优化系列篇之“列表流畅度优化”](https://juejin.im/post/5b72aaf46fb9a009764bbb6a) 271 | [3] [UIWindow 源码 of Chameleon](https://github.com/BigZaphod/Chameleon/blob/master/UIKit/Classes/UIWindow.m)s 272 | [4][https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc](https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc) 273 | [5] [Sharing the Same UIViewController as the rootViewController with Two UINavigationControllers](https://stackoverflow.com/questions/9710676/sharing-the-same-uiviewcontroller-as-the-rootviewcontroller-with-two-uinavigatio) 274 | [6] [Storyboards vs. the old XIB way](https://stackoverflow.com/questions/13834999/storyboards-vs-the-old-xib-way) 275 | [7] [Unbalanced calls to begin/end appearance transitions for ](https://stackoverflow.com/questions/14412890/unbalanced-calls-to-begin-end-appearance-transitions-for-uinavigationcontroller) 276 | [8] [ViewControllerPreRender](https://github.com/hite/ViewControllerPreRender) 277 | 278 | -------------------------------------------------------------------------------- /ViewControllerPreRender.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4883030C229E85FC0091F038 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4883030B229E85FC0091F038 /* AppDelegate.m */; }; 11 | 4883030F229E85FC0091F038 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4883030E229E85FC0091F038 /* ViewController.m */; }; 12 | 48830312229E85FC0091F038 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 48830310229E85FC0091F038 /* Main.storyboard */; }; 13 | 48830314229E85FE0091F038 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48830313229E85FE0091F038 /* Assets.xcassets */; }; 14 | 48830317229E85FE0091F038 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 48830315229E85FE0091F038 /* LaunchScreen.storyboard */; }; 15 | 4883031A229E85FE0091F038 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 48830319229E85FE0091F038 /* main.m */; }; 16 | 48830322229E8E0A0091F038 /* ViewControllerPreRender.m in Sources */ = {isa = PBXBuildFile; fileRef = 48830321229E8E0A0091F038 /* ViewControllerPreRender.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 48830307229E85FC0091F038 /* ViewControllerPreRender.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ViewControllerPreRender.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 4883030A229E85FC0091F038 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 22 | 4883030B229E85FC0091F038 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 23 | 4883030D229E85FC0091F038 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 24 | 4883030E229E85FC0091F038 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 25 | 48830311229E85FC0091F038 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 48830313229E85FE0091F038 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 48830316229E85FE0091F038 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 48830318229E85FE0091F038 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 48830319229E85FE0091F038 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 30 | 48830320229E8E0A0091F038 /* ViewControllerPreRender.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewControllerPreRender.h; sourceTree = ""; }; 31 | 48830321229E8E0A0091F038 /* ViewControllerPreRender.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewControllerPreRender.m; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | 48830304229E85FC0091F038 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 488302FE229E85FC0091F038 = { 46 | isa = PBXGroup; 47 | children = ( 48 | 48830309229E85FC0091F038 /* ViewControllerPreRender */, 49 | 48830308229E85FC0091F038 /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 48830308229E85FC0091F038 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 48830307229E85FC0091F038 /* ViewControllerPreRender.app */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 48830309229E85FC0091F038 /* ViewControllerPreRender */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 4883030A229E85FC0091F038 /* AppDelegate.h */, 65 | 4883030B229E85FC0091F038 /* AppDelegate.m */, 66 | 48830320229E8E0A0091F038 /* ViewControllerPreRender.h */, 67 | 48830321229E8E0A0091F038 /* ViewControllerPreRender.m */, 68 | 4883030D229E85FC0091F038 /* ViewController.h */, 69 | 4883030E229E85FC0091F038 /* ViewController.m */, 70 | 48830310229E85FC0091F038 /* Main.storyboard */, 71 | 48830313229E85FE0091F038 /* Assets.xcassets */, 72 | 48830315229E85FE0091F038 /* LaunchScreen.storyboard */, 73 | 48830318229E85FE0091F038 /* Info.plist */, 74 | 48830319229E85FE0091F038 /* main.m */, 75 | ); 76 | path = ViewControllerPreRender; 77 | sourceTree = ""; 78 | }; 79 | /* End PBXGroup section */ 80 | 81 | /* Begin PBXNativeTarget section */ 82 | 48830306229E85FC0091F038 /* ViewControllerPreRender */ = { 83 | isa = PBXNativeTarget; 84 | buildConfigurationList = 4883031D229E85FE0091F038 /* Build configuration list for PBXNativeTarget "ViewControllerPreRender" */; 85 | buildPhases = ( 86 | 48830303229E85FC0091F038 /* Sources */, 87 | 48830304229E85FC0091F038 /* Frameworks */, 88 | 48830305229E85FC0091F038 /* Resources */, 89 | ); 90 | buildRules = ( 91 | ); 92 | dependencies = ( 93 | ); 94 | name = ViewControllerPreRender; 95 | productName = ViewControllerPreRender; 96 | productReference = 48830307229E85FC0091F038 /* ViewControllerPreRender.app */; 97 | productType = "com.apple.product-type.application"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | 488302FF229E85FC0091F038 /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | LastUpgradeCheck = 1020; 106 | ORGANIZATIONNAME = liang; 107 | TargetAttributes = { 108 | 48830306229E85FC0091F038 = { 109 | CreatedOnToolsVersion = 10.2.1; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = 48830302229E85FC0091F038 /* Build configuration list for PBXProject "ViewControllerPreRender" */; 114 | compatibilityVersion = "Xcode 9.3"; 115 | developmentRegion = en; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = 488302FE229E85FC0091F038; 122 | productRefGroup = 48830308229E85FC0091F038 /* Products */; 123 | projectDirPath = ""; 124 | projectRoot = ""; 125 | targets = ( 126 | 48830306229E85FC0091F038 /* ViewControllerPreRender */, 127 | ); 128 | }; 129 | /* End PBXProject section */ 130 | 131 | /* Begin PBXResourcesBuildPhase section */ 132 | 48830305229E85FC0091F038 /* Resources */ = { 133 | isa = PBXResourcesBuildPhase; 134 | buildActionMask = 2147483647; 135 | files = ( 136 | 48830317229E85FE0091F038 /* LaunchScreen.storyboard in Resources */, 137 | 48830314229E85FE0091F038 /* Assets.xcassets in Resources */, 138 | 48830312229E85FC0091F038 /* Main.storyboard in Resources */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXResourcesBuildPhase section */ 143 | 144 | /* Begin PBXSourcesBuildPhase section */ 145 | 48830303229E85FC0091F038 /* Sources */ = { 146 | isa = PBXSourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 4883030F229E85FC0091F038 /* ViewController.m in Sources */, 150 | 4883031A229E85FE0091F038 /* main.m in Sources */, 151 | 48830322229E8E0A0091F038 /* ViewControllerPreRender.m in Sources */, 152 | 4883030C229E85FC0091F038 /* AppDelegate.m in Sources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXSourcesBuildPhase section */ 157 | 158 | /* Begin PBXVariantGroup section */ 159 | 48830310229E85FC0091F038 /* Main.storyboard */ = { 160 | isa = PBXVariantGroup; 161 | children = ( 162 | 48830311229E85FC0091F038 /* Base */, 163 | ); 164 | name = Main.storyboard; 165 | sourceTree = ""; 166 | }; 167 | 48830315229E85FE0091F038 /* LaunchScreen.storyboard */ = { 168 | isa = PBXVariantGroup; 169 | children = ( 170 | 48830316229E85FE0091F038 /* Base */, 171 | ); 172 | name = LaunchScreen.storyboard; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXVariantGroup section */ 176 | 177 | /* Begin XCBuildConfiguration section */ 178 | 4883031B229E85FE0091F038 /* Debug */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ALWAYS_SEARCH_USER_PATHS = NO; 182 | CLANG_ANALYZER_NONNULL = YES; 183 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_ENABLE_OBJC_WEAK = YES; 189 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_COMMA = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 196 | CLANG_WARN_EMPTY_BODY = YES; 197 | CLANG_WARN_ENUM_CONVERSION = YES; 198 | CLANG_WARN_INFINITE_RECURSION = YES; 199 | CLANG_WARN_INT_CONVERSION = YES; 200 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 201 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 202 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 204 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 205 | CLANG_WARN_STRICT_PROTOTYPES = YES; 206 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 207 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 208 | CLANG_WARN_UNREACHABLE_CODE = YES; 209 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 210 | CODE_SIGN_IDENTITY = "iPhone Developer"; 211 | COPY_PHASE_STRIP = NO; 212 | DEBUG_INFORMATION_FORMAT = dwarf; 213 | ENABLE_STRICT_OBJC_MSGSEND = YES; 214 | ENABLE_TESTABILITY = YES; 215 | GCC_C_LANGUAGE_STANDARD = gnu11; 216 | GCC_DYNAMIC_NO_PIC = NO; 217 | GCC_NO_COMMON_BLOCKS = YES; 218 | GCC_OPTIMIZATION_LEVEL = 0; 219 | GCC_PREPROCESSOR_DEFINITIONS = ( 220 | "DEBUG=1", 221 | "$(inherited)", 222 | ); 223 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 224 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 225 | GCC_WARN_UNDECLARED_SELECTOR = YES; 226 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 227 | GCC_WARN_UNUSED_FUNCTION = YES; 228 | GCC_WARN_UNUSED_VARIABLE = YES; 229 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 230 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 231 | MTL_FAST_MATH = YES; 232 | ONLY_ACTIVE_ARCH = YES; 233 | SDKROOT = iphoneos; 234 | }; 235 | name = Debug; 236 | }; 237 | 4883031C229E85FE0091F038 /* Release */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_ENABLE_OBJC_WEAK = YES; 248 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_COMMA = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INFINITE_RECURSION = YES; 258 | CLANG_WARN_INT_CONVERSION = YES; 259 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 261 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 264 | CLANG_WARN_STRICT_PROTOTYPES = YES; 265 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 266 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | CODE_SIGN_IDENTITY = "iPhone Developer"; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu11; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 283 | MTL_ENABLE_DEBUG_INFO = NO; 284 | MTL_FAST_MATH = YES; 285 | SDKROOT = iphoneos; 286 | VALIDATE_PRODUCT = YES; 287 | }; 288 | name = Release; 289 | }; 290 | 4883031E229E85FE0091F038 /* Debug */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 294 | CODE_SIGN_STYLE = Automatic; 295 | DEVELOPMENT_TEAM = 5F3P75AV37; 296 | INFOPLIST_FILE = ViewControllerPreRender/Info.plist; 297 | LD_RUNPATH_SEARCH_PATHS = ( 298 | "$(inherited)", 299 | "@executable_path/Frameworks", 300 | ); 301 | PRODUCT_BUNDLE_IDENTIFIER = me.hite.app.ViewControllerPreRender; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | }; 305 | name = Debug; 306 | }; 307 | 4883031F229E85FE0091F038 /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | CODE_SIGN_STYLE = Automatic; 312 | DEVELOPMENT_TEAM = 5F3P75AV37; 313 | INFOPLIST_FILE = ViewControllerPreRender/Info.plist; 314 | LD_RUNPATH_SEARCH_PATHS = ( 315 | "$(inherited)", 316 | "@executable_path/Frameworks", 317 | ); 318 | PRODUCT_BUNDLE_IDENTIFIER = me.hite.app.ViewControllerPreRender; 319 | PRODUCT_NAME = "$(TARGET_NAME)"; 320 | TARGETED_DEVICE_FAMILY = "1,2"; 321 | }; 322 | name = Release; 323 | }; 324 | /* End XCBuildConfiguration section */ 325 | 326 | /* Begin XCConfigurationList section */ 327 | 48830302229E85FC0091F038 /* Build configuration list for PBXProject "ViewControllerPreRender" */ = { 328 | isa = XCConfigurationList; 329 | buildConfigurations = ( 330 | 4883031B229E85FE0091F038 /* Debug */, 331 | 4883031C229E85FE0091F038 /* Release */, 332 | ); 333 | defaultConfigurationIsVisible = 0; 334 | defaultConfigurationName = Release; 335 | }; 336 | 4883031D229E85FE0091F038 /* Build configuration list for PBXNativeTarget "ViewControllerPreRender" */ = { 337 | isa = XCConfigurationList; 338 | buildConfigurations = ( 339 | 4883031E229E85FE0091F038 /* Debug */, 340 | 4883031F229E85FE0091F038 /* Release */, 341 | ); 342 | defaultConfigurationIsVisible = 0; 343 | defaultConfigurationName = Release; 344 | }; 345 | /* End XCConfigurationList section */ 346 | }; 347 | rootObject = 488302FF229E85FC0091F038 /* Project object */; 348 | } 349 | -------------------------------------------------------------------------------- /ViewControllerPreRender.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ViewControllerPreRender.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ViewControllerPreRender/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ViewControllerPreRender 4 | // 5 | // Created by liang on 2019/5/29. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | static long long kPushStart = 0; 11 | static long long kDecidePolicyStart = 0; 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /ViewControllerPreRender/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ViewControllerPreRender 4 | // 5 | // Created by liang on 2019/5/29. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /ViewControllerPreRender/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ViewControllerPreRender/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ViewControllerPreRender/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ViewControllerPreRender/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ViewControllerPreRender/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ViewControllerPreRender/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ViewControllerPreRender 4 | // 5 | // Created by liang on 2019/5/29. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @property (nonatomic, strong) NSString *url; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /ViewControllerPreRender/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ViewControllerPreRender 4 | // 5 | // Created by liang on 2019/5/29. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ViewControllerPreRender.h" 11 | #import "AppDelegate.h" 12 | 13 | @import WebKit; 14 | 15 | @interface ViewController () 16 | @property (nonatomic, strong) WKWebView *webView; 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [self printViewsBefore]; 23 | [super viewDidLoad]; 24 | // Do any additional setup after loading the view. 25 | [self.view addSubview:self.webView]; 26 | self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAlways; 27 | 28 | UIBarButtonItem *reuse = [[UIBarButtonItem alloc] initWithTitle:@"reuse" style:UIBarButtonItemStylePlain target:self action:@selector(push2vc:)]; 29 | UIBarButtonItem *no_reuse = [[UIBarButtonItem alloc] initWithTitle:@"no_reuse" style:UIBarButtonItemStylePlain target:self action:@selector(push2vc_v2:)]; 30 | self.navigationItem.rightBarButtonItems = @[reuse, no_reuse]; 31 | 32 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 33 | [self printViews]; 34 | } 35 | 36 | 37 | - (void)printViewsBefore{ 38 | // printf("Before\r\n"); 39 | // [self printViews]; 40 | } 41 | - (void)printViews 42 | { 43 | printf("\r\n"); 44 | UIView *view = self.viewIfLoaded; 45 | if (view == nil) { 46 | printf("\r\n"); 47 | return; 48 | } 49 | int i = 0; 50 | while (view != nil) { 51 | printf("%d = %s\r\n", i++, [[view description] UTF8String]); 52 | view = view.superview; 53 | } 54 | printf("\r\n"); 55 | } 56 | 57 | #define mylog(format, ...) _mylog("[Timing] ", format, ##__VA_ARGS__) 58 | #define _mylog(prefix, format, ...) do{printf(prefix);printf(format, ##__VA_ARGS__);printf("\r\n");}while(0) 59 | 60 | /** 61 | setUrl 是本类的主要业务逻辑入口。需要合适的时候手动触发 62 | 63 | @param url WebView 加载的需求 64 | */ 65 | - (void)setUrl:(NSString *)url 66 | { 67 | if (url.length > 0) { 68 | _url = url; 69 | NSURL *url2 = [NSURL URLWithString:_url?:@"about:blank"]; 70 | mylog("push2setUrl = %f ", [[NSDate date] timeIntervalSince1970] * 1000 - kPushStart); 71 | [self.webView loadRequest:[NSURLRequest requestWithURL:url2 cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1000]]; 72 | 73 | } 74 | [self printViews]; 75 | } 76 | 77 | - (void)push2vc:(id)sender 78 | { 79 | kPushStart = [[NSDate date] timeIntervalSince1970] * 1000; 80 | mylog(""); 81 | 82 | [CATransaction commit]; 83 | [[ViewControllerPreRender defaultRender] showRenderedViewController:ViewController.class completion:^(UIViewController * _Nonnull vc) { 84 | 85 | ViewController *vc3 = (ViewController *)vc; 86 | vc3.url = @"http://m.you.163.com"; 87 | [self.navigationController pushViewController:vc3 animated:YES]; 88 | }]; 89 | } 90 | 91 | - (void)push2vc_v2:(id)sender 92 | { 93 | kPushStart = [[NSDate date] timeIntervalSince1970] * 1000; 94 | mylog(""); 95 | 96 | ViewController *vc1 = [ViewController new]; 97 | vc1.url = @"http://m.you.163.com"; 98 | [self.navigationController pushViewController:vc1 animated:YES]; 99 | } 100 | 101 | #pragma mark - navigation 102 | 103 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation 104 | { 105 | NSLog(@"<%p> %s", self, [NSStringFromSelector(_cmd) UTF8String]); 106 | [webView evaluateJavaScript:@"window.performance.timing.navigationStart" completionHandler:^(id _Nullable r, NSError * _Nullable error) { 107 | long long navigationStart = [r longLongValue]; 108 | // mylog("navigationStart time = %lld, decidePolicy = %lld ", navigationStart, kDecidePolicyStart); 109 | mylog("push2decidePolicy = %lld ", kDecidePolicyStart - kPushStart); 110 | // 根据我以前文章 https://www.jianshu.com/p/d7e79b58979c , 111 | // performance 端的 navigationStart 应该和 decidePolicyForNavigationAction 是一样的。 112 | // 这里是验证下误差 113 | mylog("decidePolicy2navigationStart = %lld ", navigationStart - kDecidePolicyStart); 114 | mylog("push2navigationStart = %lld ", navigationStart - kPushStart); 115 | }]; 116 | mylog("push2finish = %f ", ([[NSDate date] timeIntervalSince1970] * 1000) - kPushStart); 117 | } 118 | 119 | - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error 120 | { 121 | mylog("<%p> %s", self, [NSStringFromSelector(_cmd) UTF8String]); 122 | } 123 | 124 | - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{ 125 | mylog("<%p> %s", self, [NSStringFromSelector(_cmd) UTF8String]); 126 | } 127 | 128 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 129 | { 130 | NSURLRequest *request = navigationAction.request; 131 | NSLog(@"<%p> decidePolicyForNavigationAction. URL = %@,Method = %@, body = %@, allKey = %@", self, request.URL,request.HTTPMethod, request.HTTPBody,[request.allHTTPHeaderFields allKeys]); 132 | decisionHandler(WKNavigationActionPolicyAllow); 133 | kDecidePolicyStart = [[NSDate date] timeIntervalSince1970] * 1000; 134 | } 135 | 136 | #pragma mark - getter 137 | - (WKWebView *)webView 138 | { 139 | if (_webView == nil) { 140 | // 141 | WKWebViewConfiguration *webViewConfig = [WKWebViewConfiguration new]; 142 | 143 | CGRect size = [[UIScreen mainScreen] bounds]; 144 | WKWebView *webview = [[WKWebView alloc] initWithFrame:size configuration:webViewConfig]; 145 | webview.navigationDelegate = self; 146 | _webView = webview; 147 | } 148 | return _webView; 149 | } 150 | 151 | // 以下为测试 view 都干了啥而设置的代理 152 | #pragma mark - test 153 | - (void)loadView{ 154 | [self printViewsBefore]; 155 | [super loadView]; 156 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 157 | [self printViews]; 158 | } 159 | - (void)viewWillAppear:(BOOL)animated 160 | { 161 | [self printViewsBefore]; 162 | [super viewWillAppear:animated]; 163 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 164 | [self printViews]; 165 | } 166 | - (void)viewDidAppear:(BOOL)animated 167 | { 168 | [self printViewsBefore]; 169 | [super viewDidAppear:animated]; 170 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 171 | [self printViews]; 172 | } 173 | - (void)viewWillDisappear:(BOOL)animated 174 | { 175 | [self printViewsBefore]; 176 | [super viewWillDisappear:animated]; 177 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 178 | [self printViews]; 179 | } 180 | - (void)viewDidDisappear:(BOOL)animated 181 | { 182 | [self printViewsBefore]; 183 | [super viewDidDisappear:animated]; 184 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 185 | [self printViews]; 186 | } 187 | 188 | - (void)viewWillLayoutSubviews{ 189 | [self printViewsBefore]; 190 | [super viewWillLayoutSubviews]; 191 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 192 | [self printViews]; 193 | } 194 | 195 | - (void)viewDidLayoutSubviews{ 196 | [self printViewsBefore]; 197 | [super viewDidLayoutSubviews]; 198 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 199 | [self printViews]; 200 | } 201 | 202 | - (void)dealloc 203 | { 204 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 205 | } 206 | 207 | - (void)viewLayoutMarginsDidChange{ 208 | [self printViewsBefore]; 209 | [super viewLayoutMarginsDidChange]; 210 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 211 | [self printViews]; 212 | } 213 | 214 | #pragma contentContainer 215 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator 216 | { 217 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; 218 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 219 | [self printViews]; 220 | } 221 | - (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id)coordinator 222 | { 223 | [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; 224 | NSLog(@"%@ : %@, %p",self.url, NSStringFromSelector(_cmd), self); 225 | [self printViews]; 226 | } 227 | @end 228 | -------------------------------------------------------------------------------- /ViewControllerPreRender/ViewControllerPreRender.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerPreRender.h 3 | // ViewControllerPreRender 4 | // 5 | // Created by liang on 2019/5/29. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface ViewControllerPreRender : NSObject 15 | 16 | + (instancetype)defaultRender; 17 | 18 | - (void)showRenderedViewController:(Class)viewControllerClass completion:(void (^)(UIViewController *vc))block; 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /ViewControllerPreRender/ViewControllerPreRender.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerPreRender.m 3 | // ViewControllerPreRender 4 | // 5 | // Created by liang on 2019/5/29. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import "ViewControllerPreRender.h" 10 | 11 | @interface ViewControllerPreRender () 12 | 13 | @property (nonatomic, strong) UIWindow *windowNO2; 14 | /** 15 | 已经被渲染过后的 ViewController,池子,在必要时候 purge 掉 16 | */ 17 | @property (nonatomic, strong) NSMutableDictionary *renderedViewControllers; 18 | @end 19 | 20 | static ViewControllerPreRender *_myRender = nil; 21 | @implementation ViewControllerPreRender 22 | 23 | + (instancetype)defaultRender{ 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | _myRender = [ViewControllerPreRender new]; 27 | _myRender.renderedViewControllers = [NSMutableDictionary dictionaryWithCapacity:3]; 28 | // 增加一个监听,当内存紧张时,丢弃这些预加载的对象不会造成功能错误, 29 | // 这样也要求 UIViewController 的 dealloc 都能正确处理资源释放 30 | [[NSNotificationCenter defaultCenter] addObserver:_myRender 31 | selector:@selector(dealMemoryWarnings:) 32 | name:UIApplicationDidReceiveMemoryWarningNotification 33 | object:nil]; 34 | }); 35 | return _myRender; 36 | } 37 | 38 | /** 39 | 内部方法,用来产生可用的 ViewController,如果第一次使用。 40 | 直接返回全新创建的对象,同时也预热一个相同类的对象,供下次使用。 41 | 支持预热多个 ViewController,但是不易过多,容易引起内存紧张 42 | 43 | @param viewControllerClass UIViewController 子类 44 | @return UIViewControllerd 实例 45 | */ 46 | - (UIViewController *)getRendered:(Class)viewControllerClass{ 47 | if (_windowNO2 == nil) { 48 | CGRect full = [UIScreen mainScreen].bounds; 49 | // 对于 no2 的尺寸多少为合适。我自己做了下实验 50 | // 这里设置的尺寸会影响被缓存的 VC 实例的尺寸。但在预热好的 VC 被添加到当前工作的 navigation stack 时,它的 View 的尺寸是正确的和 no2 的尺寸。 51 | // 同样的,在被添加到 navigation stack 时,会触发 viewLayoutMarginsDidChange 事件。 52 | // 而且对于内存而言,尺寸越小内存占用越少,理论上 (1,1,1,1) 的 no2 有能达到预热 VC 的效果。 53 | // 但是有些 view 不是被 presented 或者 pushed,而是作为子 ViewController 的子 view 来渲染界面的。这需要 view 有正确的尺寸。 54 | // 所以这里预先设置将来真正展示时的尺寸,减少 resize、和作为子 ViewController 使用时出错,在本 demo 中,默认大部分的尺寸是全屏。 55 | UIWindow *no2 = [[UIWindow alloc] initWithFrame:CGRectOffset(full, CGRectGetWidth(full), 0)]; 56 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[UIViewController new]]; 57 | no2.rootViewController = nav; 58 | no2.hidden = NO;// 必须是显示的 window,才会触发预热 ViewController,隐藏的 window 不可用。但是和是否在屏幕可见没关系 59 | no2.windowLevel = UIWindowLevelStatusBar + 14; 60 | 61 | _windowNO2= no2; 62 | } 63 | 64 | NSString *key = NSStringFromClass(viewControllerClass); 65 | UIViewController *vc = [self.renderedViewControllers objectForKey:key]; 66 | if (vc == nil) { // 下次使用缓存 67 | vc = [viewControllerClass new]; 68 | // 解决 Unbalanced calls to begin/end appearance transitions for 关键点 69 | // 1. 使用 UINavigationController 作为 no2 的 rootViewController 70 | // 2. 如果使用 UIViewController 作为 no2 的 rootViewController,始终有 Unbalanced calls 的错误 71 | // 虽然是编译器警告,实际上 Unbalanced calls 会影响被缓存的 vc, 当它被添加到当前活动的 UINavigation stack 时,它的生命周期是错误的 72 | // 所以这个警告必须解决。 73 | UINavigationController *nav = (UINavigationController *)_windowNO2.rootViewController; 74 | [nav pushViewController:vc animated:NO]; 75 | [self.renderedViewControllers setObject:vc forKey:key]; 76 | // 77 | return [viewControllerClass new]; 78 | } else { // 本次使用缓存,同时储备下次 79 | // 必须是先设置 no2 的新 rootViewController,之后再复用从缓存中拿到的 viewControllerClass。否则会奔溃 80 | UINavigationController *nav = (UINavigationController *)_windowNO2.rootViewController; 81 | [nav popViewControllerAnimated:NO]; 82 | UIViewController *fresh = [viewControllerClass new]; 83 | 84 | [nav pushViewController:fresh animated:NO]; 85 | // 在 setObject to renderedViewControllers 字典时,保证被渲染过 86 | [self.renderedViewControllers setObject:fresh forKey:key]; 87 | 88 | return vc; 89 | } 90 | } 91 | 92 | /** 93 | 主方法。传入一个 UIViewController 的 class 对象,在调用的 block 中同步的返回一个预先被渲染的 ViewController 94 | 95 | @param viewControllerClass 必须是 UIViewController 的 Class 对象 96 | @param block 业务逻辑回调 97 | */ 98 | - (void)showRenderedViewController:(Class)viewControllerClass completion:(void (^)(UIViewController *vc))block{ 99 | // CATransaction 为了避免一个 push 动画和另外一个 push 动画同时进行的问题。 100 | [CATransaction begin]; 101 | UIViewController *vc1 = [self getRendered:viewControllerClass]; 102 | 103 | // 这里包含一个陷阱—— 必须先渲染将要被 cached 的 ViewController,然后再执行真实的 block 104 | // 理想情况,应该是先执行 block,然后执行 cache ViewController,因为 block 更重要些。暂时没想到方法 105 | [CATransaction setCompletionBlock:^{ 106 | block(vc1); 107 | }]; 108 | [CATransaction commit]; 109 | } 110 | 111 | - (void)dealMemoryWarnings:(id)notif 112 | { 113 | NSLog(@"release memory pressure"); 114 | [self.renderedViewControllers removeAllObjects]; 115 | } 116 | @end 117 | -------------------------------------------------------------------------------- /ViewControllerPreRender/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ViewControllerPreRender 4 | // 5 | // Created by liang on 2019/5/29. 6 | // Copyright © 2019 liang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /calc.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # -*- coding: UTF-8 -*- 3 | 4 | categoryType = Hash.new 5 | 6 | File.open("toBeCaclulate.txt", "r") do |file| 7 | file.each_line do |line| 8 | line.delete_prefix!("[Timing] ") 9 | if line.start_with?("//") # comment 10 | next 11 | end 12 | if line.length == 0 13 | puts "Empty line" 14 | else 15 | _arr = line.split("=") 16 | if _arr.count == 2 17 | key = _arr.at(0).strip 18 | val = _arr.at(1).strip 19 | if categoryType[key] == nil; then 20 | categoryType[key] = Array.new 21 | end 22 | categoryType[key].push(val) 23 | else 24 | puts "wrong format, skip it,#{line}" 25 | end 26 | end 27 | 28 | end 29 | end 30 | 31 | # 循环输出 32 | 33 | typeArray = categoryType.keys 34 | markdown_sep = ' | ' 35 | if !typeArray.empty? 36 | sample = categoryType[typeArray.first] 37 | # 先输出 分类 tab 38 | puts typeArray.join(markdown_sep) 39 | # 分隔符,markdown 格式 40 | sepData = Array.new 41 | for i in 0...typeArray.count do 42 | sepData.push('---') 43 | end 44 | puts sepData.join("|") 45 | # 具体数据 46 | for i in 0...sample.count do 47 | tabData = Array.new 48 | typeArray.each{ | key | 49 | dataOfType = categoryType[key] 50 | tabData.push(dataOfType[i].strip) 51 | } 52 | puts tabData.join(markdown_sep) 53 | end 54 | 55 | end 56 | 57 | avgArr = Array.new 58 | categoryType.each do |key, arr| 59 | sum = 0 60 | arr.map{ |item| 61 | sum = sum + item.to_f 62 | } 63 | avg = sum.to_f / arr.length 64 | avgArr.push(avg.round(4)) 65 | end 66 | puts "Avg of #{categoryType.values.first.count}:" 67 | puts avgArr.join(markdown_sep) 68 | -------------------------------------------------------------------------------- /toBeCaclulate.txt: -------------------------------------------------------------------------------- 1 | [Timing] 2 | [Timing] push2setUrl = 64.522949 3 | [Timing] push2finish = 1454.382080 4 | [Timing] push2decidePolicy = 111 5 | [Timing] decidePolicy2navigationStart = 2 6 | [Timing] push2navigationStart = 113 7 | [Timing] 8 | [Timing] push2setUrl = 48.687012 9 | [Timing] push2finish = 1285.288818 10 | [Timing] push2decidePolicy = 77 11 | [Timing] decidePolicy2navigationStart = 2 12 | [Timing] push2navigationStart = 79 13 | [Timing] 14 | [Timing] push2setUrl = 42.260986 15 | [Timing] push2finish = 1300.068115 16 | [Timing] push2decidePolicy = 88 17 | [Timing] decidePolicy2navigationStart = 4 18 | [Timing] push2navigationStart = 92 19 | [Timing] 20 | [Timing] push2setUrl = 41.084961 21 | [Timing] push2finish = 1444.260010 22 | [Timing] push2decidePolicy = 71 23 | [Timing] decidePolicy2navigationStart = 7 24 | [Timing] push2navigationStart = 78 25 | [Timing] 26 | [Timing] push2setUrl = 39.801025 27 | [Timing] push2finish = 1438.300049 28 | [Timing] push2decidePolicy = 68 29 | [Timing] decidePolicy2navigationStart = 5 30 | [Timing] push2navigationStart = 73 31 | [Timing] 32 | [Timing] push2setUrl = 39.239014 33 | [Timing] push2finish = 1481.501221 34 | [Timing] push2decidePolicy = 63 35 | [Timing] decidePolicy2navigationStart = 2 36 | [Timing] push2navigationStart = 65 37 | [Timing] 38 | [Timing] push2setUrl = 39.562988 39 | [Timing] push2finish = 1386.489258 40 | [Timing] push2decidePolicy = 64 41 | [Timing] decidePolicy2navigationStart = 2 42 | [Timing] push2navigationStart = 66 43 | [Timing] 44 | [Timing] push2setUrl = 39.304932 45 | [Timing] push2finish = 1280.473877 46 | [Timing] push2decidePolicy = 63 47 | [Timing] decidePolicy2navigationStart = 9 48 | [Timing] push2navigationStart = 72 49 | [Timing] 50 | [Timing] push2setUrl = 40.439941 51 | [Timing] push2finish = 1522.291016 52 | [Timing] push2decidePolicy = 70 53 | [Timing] decidePolicy2navigationStart = 6 54 | [Timing] push2navigationStart = 76 55 | [Timing] 56 | [Timing] push2setUrl = 32.190918 57 | [Timing] push2finish = 1311.799072 58 | [Timing] push2decidePolicy = 57 59 | [Timing] decidePolicy2navigationStart = 9 60 | [Timing] push2navigationStart = 66 61 | [Timing] 62 | [Timing] push2setUrl = 32.577148 63 | [Timing] push2finish = 1678.435791 64 | [Timing] push2decidePolicy = 57 65 | [Timing] decidePolicy2navigationStart = 3 66 | [Timing] push2navigationStart = 60 67 | [Timing] 68 | [Timing] push2setUrl = 43.409180 69 | [Timing] push2finish = 1449.776855 70 | [Timing] push2decidePolicy = 74 71 | [Timing] decidePolicy2navigationStart = 5 72 | [Timing] push2navigationStart = 79 73 | [Timing] 74 | [Timing] push2setUrl = 40.168945 75 | [Timing] push2finish = 1312.054199 76 | [Timing] push2decidePolicy = 71 77 | [Timing] decidePolicy2navigationStart = 4 78 | [Timing] push2navigationStart = 75 79 | [Timing] 80 | [Timing] push2setUrl = 38.052002 81 | [Timing] push2finish = 1572.002930 82 | [Timing] push2decidePolicy = 70 83 | [Timing] decidePolicy2navigationStart = 7 84 | [Timing] push2navigationStart = 77 85 | [Timing] 86 | [Timing] push2setUrl = 40.468018 87 | [Timing] push2finish = 1387.616943 88 | [Timing] push2decidePolicy = 68 89 | [Timing] decidePolicy2navigationStart = 1 90 | [Timing] push2navigationStart = 69 91 | [Timing] 92 | [Timing] push2setUrl = 40.675049 93 | [Timing] push2finish = 1532.381104 94 | [Timing] push2decidePolicy = 72 95 | [Timing] decidePolicy2navigationStart = 2 96 | [Timing] push2navigationStart = 74 97 | [Timing] 98 | [Timing] push2setUrl = 38.729004 99 | [Timing] push2finish = 1627.913086 100 | [Timing] push2decidePolicy = 62 101 | [Timing] decidePolicy2navigationStart = 2 102 | [Timing] push2navigationStart = 64 103 | [Timing] 104 | [Timing] push2setUrl = 39.722900 105 | [Timing] push2finish = 1456.595947 106 | [Timing] push2decidePolicy = 70 107 | [Timing] decidePolicy2navigationStart = 3 108 | [Timing] push2navigationStart = 73 109 | 110 | --------------------------------------------------------------------------------