iOS App 启动优化

简介: 作为程序猿来说,“性能优化”是我们都很熟悉的词,也是我们需要不断努⼒以及持续进⾏的事情;其实优化是⼀个很⼤的课题,因为细分来说的话有⼤⼤⼩⼩⼗⼏种优化⽅向 ,但是切忌在实际开发过程中不能盲⽬的 为了优化⽽优化,这样有时可能会造成适得其反的负效果,需要我们根据实际场景以及业务需求进⾏合理优 化。接下来进⼊正题,本⽂将会以iOS App的启动优化为展开点进⾏探讨。

前言

作为程序猿来说,性能优化是我们都很熟悉的词,也是我们需要不断努⼒以及持续进⾏的事情;其实优化是⼀个很⼤的课题,因为细分来说的话有⼤⼤⼩⼩⼗⼏种优化⽅向,但是切忌在实际开发过程中不能盲⽬的为了优化⽽优化,这样有时可能会造成适得其反的负效果,需要我们根据实际场景以及业务需求进⾏合理优 化。接下来进⼊正题,本⽂将会以iOSApp的启动优化为展开点进⾏探讨。


启动流程:

iOSApp的启动我们都知道分为  pre-main  main()两个阶段,并且在这两个阶段中,系统会进 ⾏⼀系列的加载操作,过程如下:

1、pre-main阶段

1.  加载应⽤的可执⾏⽂件

2.  加载dyld动态连接器

3.  dyld递归加载应⽤所有依赖的动态链接库dylib

2、main()阶段

1.  dyld调⽤  main()

2.  调⽤UIApplicationMain()

3.  调⽤applicationWillFinishLaunching

4.  调⽤didFinishLaunchingWithOptions

阶段优化项

1、pre-main阶段

针对  pre-main阶段做优化时,我们需要先详细了解其加载过程,这个可以在2016WWDC  OptimizingAppStartupTime中详细了解到,   相关材料

1.1 Load dylibs

1.jpg

这⼀阶段dyld会分析应⽤依赖的  dylibxcode7以后.dylib已改为名.tbd),找到其  mach-o⽂件,打开和读取这些⽂件并验证其有效性,接着会找到代码签名注册到内核,最后对  dylib的每⼀个  segmentmmap()。不过这⾥的  dylib ⼤部分都是系统库,不需要我们去做额外的优化。

优化结论:

1、尽量不使⽤内嵌的dylib,从⽽避免增加`Loaddylibs`开销

2、合并已有的dylib和使⽤静态库(staticarchives),减少dylib的使⽤个数

3、懒加载dylib,但是要注意dlopen()可能造成⼀些问题,且实际上懒加载做的⼯作会更多

1.2 Rebase/Bind

2.jpg

dylib的加载过程中,系统为了安全考虑,引⼊了ASLR Address Space Layout Randomization)技术和    代码签名。由于ASLR的存在,镜像(Image,包括可执⾏⽂件、  dylibbundle)会在随机的地址上加载,和 之前指针指向的地址(preferred_address)会有⼀个偏差(slide),dyld需要修正这个偏差,来指向正确的 地址。   Rebase在前,   Bind在后,  Rebase做的是将镜像读⼊内存,修正镜像内部的指针,性能消耗主要在     IOBind做的是查询符号表,设置指向镜像外部的指针,性能消耗主要在CPU计算。


优化结论:

在此过程中,我们需要注意的是尽量减少指针数量,⽐如:

1.减少ObjC类(class)、⽅法(selector)、分类(category)的数量

2.减少C++虚函数的的数量(创建虚函数表有开销)

3. 使⽤ Swift struct (内部做了优化,符号数量更少)

1.3 Objc setup

3.jpg

⼤部分ObjC初始化⼯作已经在Rebase/Bind阶段做完了,这⼀步dyld会注册所有声明过的ObjC类,将分类插⼊到类的⽅法列表⾥,再检查每个selector的唯⼀性。

在这⼀步倒没什么优化可做的,  Rebase/Bind阶段优化好了,这⼀步的耗时也会减少。

1.4 Initializers

4.jpg

在这⼀阶段,   dyld开始运⾏程序的初始化函数,调⽤每个Objc类和分类的+load⽅法,调⽤C/C++中的构造器 函数(⽤attribute((constructor))修饰的函数),和创建⾮基本类型的C++静态全局变量。  Initializers阶段执⾏  完后,  dyld开始调⽤main()函数。

优化结论:

1.少在类的+load⽅法⾥做事情,尽量把这些事情推迟到+initiailize

2.减少构造器函数个数,在构造器函数⾥少做些事情

3.减少构造器函数个数,在构造器函数⾥少做些事情

2、main()阶段

在这⼀阶段⾥,主要优化重点放在SDK初始化、业务⼯具注册、整体

didFinishLaunchingWithOptions⽅法中,因为我们的⼀些第三⽅    app ⻛格配置、启动引导⻚显示状态逻辑、版本更新逻辑等等基本⽅都会在这⾥进⾏,如果这部分逻辑没有做好优化梳理,随着业务不断拓展,臃肿的业务逻辑会直接导致启动时间加⻓。

在满⾜业务需求的前提下,尽量减少  didFinishLaunchingWithOptions⽅法在主线程中的事件处理逻辑,⽐如:

1.根据实际业务状况,梳理各个⼆⽅/三⽅库,找到可以延迟加载的库,做延迟加载处理,⽐如放到⾸⻚控制器viewDidAppear⽅法⾥。

2.梳理业务逻辑,把可以延迟执⾏的逻辑,做延迟执⾏处理。⽐如检查新版本、注册推送通知等逻辑

3.避免进⾏⼀些复杂/多余的计算逻辑,这类逻辑尽量进⾏异步延迟处理

4.避免在⾸⻚控制器的viewDidLoadviewWillAppear做太多容易阻塞主线程的事情,这2个⽅法执⾏完,⾸⻚控制器才能显示

场景补充:

另外,在我们实际开发过程中,很多项⽬的⾸⻚控制器都会有⼀些后台可配、较为丰富的结构或者推荐数据  进⾏展示,⽽且我们的⾸⻚展示速度通常也会被纳⼊启动优化的⼀部分,其实对于这种类型的优化,如果我  们还只是⽤传统的  api->data->UI⽅式进⾏的话,就很难有明显的改善空间,因为⽤户的⽹络状态并不是可控项,如果不做其他处理的话,那在很多场景下对⽤户来说,即使我们放上⼀些占位图,展示的样式也是很不友好的,毕竟⾸⻚控制器对⽤户的第⼀视觉冲击影响还是⽐较⼤的。

对于这种场景下的优化来说,⼀般我们可以采取  Local+Network+Update的⽅式在⼀定程度上优化⾸⻚加载速度:    即:

1app更新过程中,⾸先进⾏本地内嵌处理逻辑,内嵌⾸⻚数据结构(localDataBase)、内嵌⾸⻚样式所需资源(localStorage

2、在安装启动之后,对本地与线上数据更新记录进⾏对⽐,检测是否需要更新本地内嵌数据结构

3、检测到有需要更新的数据时,才会对指定结构进⾏静默更新,并且同步更新本地数据结构


这样做的好处是:

1、⾸⻚数据直接从本地加载,减少⽹络数据等待时间

2、仅检测数据key值变化,⼩数据量对⽐定向更新结构,减少api数据交互频次及数据包体积

3、能够保证⾸⻚对于⽤户来说会⼀直处于⼀个友好的展示状态

当然这种也并不是唯⼀的应对⽅式,⽽且也并⾮对所有场景都适⽤,只是提供⼀种思路⽽已,还是需要根据项⽬的实际场景选择适合的优化⽅案。

统计时⻓

另外如果在开发过程中,我们想直观的查看  app启动期间,各阶段的耗时情况,也可以在Xcode,的  edit scheme 设置添加   DYLD_PRINT_STATISTICS 1 ,打印启动时⻓,例如

5.jpg

优化前启动时⻓:

6.jpg

优化后启动时⻓:

7.jpg

当然,这些log我们仅仅只能在开发调试阶段查看打印,那么在实际项⽬中,我们需要对线上项⽬的启动数据进⾏监控,以便及时的定位和优化那些影响  app 启动时⻓的环节,这时我们应该怎样更好的处理呢?

当然我们可以通过服务器埋点上报的⽅式⾃⾏统计分析,不过这样⼀来会发现我们的统计成本就会⼤⼤增      加,⽽且结果分析也会变得不那么灵活。所以这⾥推荐⼀种简单的监控⽅式,那就是友盟的  U-APM应能性能监控SDK,只需要我们进⾏简单的pod集成之后,便可根据我们的实际需要进⾏⼿动或者⾃动监控启动数  据,详情可以参考U-APM 并且为了⽅便我们对数据进⾏分析,友盟后台已经根据这些数据帮我们绘制出 了对应的分布图,我们可以⼀⽬了然的得出启动耗时分布、启动类型占⽐等等,如图:

8.jpg

哦.jpg

9.jpg

哦1.jpg

除此之外,我们还可以通过SDK进⾏崩溃分析、  ANR分析、监控告警、卡顿分析、内存分析等等诸多功能,有了 U-APM这个监控平台,其实在实际开发过程中很⼤程度的提升了我们对线上 app的优化分析效率。

当然本⽂的介绍也只是⽐较浅显的优化项,仅供参考以及思路引导,优化之路任重⽽道远,还需要我们不断的去探索、发现、提⾼。不过最后还是要提醒⼀句:在实际项⽬开发过程中,不要为了优化⽽优化,要根据项⽬情况有针对性的进⾏优化。

参考:

探秘 Mach-O ⽂件

iOS底层 - 从头梳理 dyld 加载流程

iOSapp启动 - dyld加载App流程

wwdc2016optimizingappstartuptime.pdf



作者:武玉宝

相关文章
|
4月前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
207 3
|
1月前
|
编解码 iOS开发
IOS上架APP Store时预览图尺寸
IOS上架APP Store时预览图尺寸
54 3
|
1月前
|
iOS开发
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
89 0
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
|
1月前
|
开发工具 iOS开发
解决Flutter运行报错Could not run build/ios/iphoneos/Runner.app
解决Flutter运行报错Could not run build/ios/iphoneos/Runner.app
88 2
|
1月前
|
Web App开发 移动开发 前端开发
如何优化运行在webkit上的web app
如何优化运行在webkit上的web app
|
20天前
|
Android开发 iOS开发 C#
Xamarin:用C#打造跨平台移动应用的终极利器——从零开始构建你的第一个iOS与Android通用App,体验前所未有的高效与便捷开发之旅
【8月更文挑战第31天】Xamarin 是一个强大的框架,允许开发者使用单一的 C# 代码库构建高性能的原生移动应用,支持 iOS、Android 和 Windows 平台。作为微软的一部分,Xamarin 充分利用了 .NET 框架的强大功能,提供了丰富的 API 和工具集,简化了跨平台移动应用开发。本文通过一个简单的示例应用介绍了如何使用 Xamarin.Forms 快速创建跨平台应用,包括设置开发环境、定义用户界面和实现按钮点击事件处理逻辑。这个示例展示了 Xamarin.Forms 的基本功能,帮助开发者提高开发效率并实现一致的用户体验。
42 0
|
1月前
|
iOS开发
解决IOS上架App Store后显示语言为英文的问题
解决IOS上架App Store后显示语言为英文的问题
39 0
|
3月前
|
ARouter IDE 开发工具
Android面试题之App的启动流程和启动速度优化
App启动流程概括: 当用户点击App图标,Launcher通过Binder IPC请求system_server启动Activity。system_server指示Zygote fork新进程,接着App进程向system_server申请启动Activity。经过Binder通信,Activity创建并回调生命周期方法。启动状态分为冷启动、温启动和热启动,其中冷启动耗时最长。优化技巧包括异步初始化、避免主线程I/O、类加载优化和简化布局。
58 3
Android面试题之App的启动流程和启动速度优化
|
3月前
|
缓存 JSON 网络协议
Android面试题:App性能优化之电量优化和网络优化
这篇文章讨论了Android应用的电量和网络优化。电量优化涉及Doze和Standby模式,其中应用可能需要通过用户白名单或电池广播来适应限制。Battery Historian和Android Studio的Energy Profile是电量分析工具。建议减少不必要的操作,延迟非关键任务,合并网络请求。网络优化包括HTTPDNS减少DNS解析延迟,Keep-Alive复用连接,HTTP/2实现多路复用,以及使用protobuf和gzip压缩数据。其他策略如使用WebP图像格式,按网络质量提供不同分辨率的图片,以及启用HTTP缓存也是有效手段。
67 9
|
3月前
|
XML 监控 安全
Android App性能优化之卡顿监控和卡顿优化
本文探讨了Android应用的卡顿优化,重点在于布局优化。建议包括将耗时操作移到后台、使用ViewPager2实现懒加载、减少布局嵌套并利用merge标签、使用ViewStub减少资源消耗,以及通过Layout Inspector和GPU过度绘制检测来优化。推荐使用AsyncLayoutInflater异步加载布局,但需注意线程安全和不支持特性。卡顿监控方面,提到了通过Looper、ChoreographerHelper、adb命令及第三方工具如systrace和BlockCanary。总结了Choreographer基于掉帧计算和BlockCanary基于Looper监控的原理。
63 3