苹果App性能优化:启动优化

启动优化

热启动:当用户按下home键的时候,iOS的App并不会马上被kill掉,还会继续存活若干时间。理想情况下,用户点击App的图标再次回来的时候,App几乎不需要做什么,就可以还原到退出前的状态,继续为用户服务。这种持续存活的情况下启动App,我们称为热启动,

冷启动:从用户点击App图标开始到appDelegate didFinishLaunching方法执行完成为止,相对而言冷启动就是App被kill掉以后一切从头开始启动的过程。

冷启动比热启动重要,首先App冷启动的情况

App启动过程

  • 解析Info.plist
    • 加载相关信息,例如如闪屏
    • 沙箱建立、权限检查
  • Mach-O加载
    • 如果是胖二进制文件,寻找合适当前CPU类别的部分
    • 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
    • 定位内部、外部指针引用,例如字符串、函数等
    • 执行声明为__attribute__((constructor))的C函数
    • 加载类扩展(Category)中的方法
    • C++静态对象加载、调用ObjC的 +load 函数
  • 程序执行
    • 调用main()
    • 调用UIApplicationMain()
    • 调用applicationWillFinishLaunching

T1:main()函数之前,即操作系统加载App可执行文件到内存,然后执行一系列的加载&链接等工作,最后执行至App的main()函数。

T2:main()函数之后,即从main()开始,到appDelegate的didFinishLaunchingWithOptions方法执行完毕
3.T3: 首页请求和渲染

苹果App性能优化:启动优化

 

T1阶段

main()函数之前

在不越狱的情况下,以往很难精确的测量在main()函数之前的启动耗时,因而我们也往往容易忽略掉这部分数据。小型App确实不需要太过关注这部分。但如果是大型App(自定义的动态库超过50个、或编译结果二进制文件超过30MB),这部分耗时将会变得突出。所幸,苹果已经在Xcode中加入这部分的支持。

苹果提供的方法

  • 在Xcode的菜单中选择Product→Scheme→Edit Scheme...,然后找到 Run → Environment Variables →+,添加name为DYLD_PRINT_STATISTICS值为YSE的环境变量。

苹果App性能优化:启动优化在Xcode运行App时,会在console中得到一个报告。例如,我在项目中加入以上设置之后,会得到这样一个报告:

苹果App性能优化:启动优化

如何解读

  1. main()函数之前总共使用了2.8s
  2. 在2.8s中,加载动态库用了437.31ms,指针重定位使用了1.6s,ObjC类初始化使用了258.48ms,各种初始化使用了491.03ms。
  3. 在初始化耗费的491.03ms中,用时最多的三个初始化是libSystem.B.dylib、libMainThreadChecker.dylib、MediaServices。

多次启动一般越来速度越快,模拟器。。。。时间就像我这个一样,,,,,,,,,,,,,很长

 

优化部分:

main()函数之前耗时的影响因素

  • 动态库加载越多,启动越慢。
  • ObjC类越多,启动越慢
  • C的constructor函数越多,启动越慢
  • C++静态对象越多,启动越慢
  • ObjC的+load越多,启动越慢

 

优化建议:

  • 合并功能类似的类和扩展(Category)
  • 减少不必要的framework,因为动态链接比较耗时
  • check framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查
  • 压缩资源图片
  • 移除不需要用到的动态库,尽量使用静态库,减少动态库的使用,动态链接比较耗时,如果要用动态库,尽量将多个dylib动态库合并成一个
  • 尽量避免对系统库使用optional linking,如果App用到的系统库在你所有支持的系统版本上都有,就设置为required,因为optional会有些额外的检查
  • 减少Objective-C Class、Selector、Category的数量,可以合并或者删减一些OC类,关于清理项目中没用到的类,使用工具AppCode代码检查功能
  • 删减一些无用的静态变量,删减没有被调用到或者已经废弃的方法
  • 将不必须在+load中做的事情尽量挪到+initialize中,+initialize是在第一次初始化这个类之前被调用,+load在加载类的时候就被调用。尽量将+load里的代码延后调用。在系统第一次使用到这个类的使用,才会使用到他的+(void)initlalize方法
  • 尽量不要用C++虚函数,创建虚函数表有开销
  • 不要使用__attribute__((constructor))将方法显式标记为初始化器,而是让初始化方法调用时才执行。比如使用dispatch_once(),pthread_once()或 std::once()
  • 在初始化方法中不调用dlopen(),dlopen()有性能和死锁的可能性
  • 在初始化方法中不创建线程

号外:关于每个objc文件的+load函数的执行循序

  • 每个objc文件都有+load方法,此处的文件顺序,决定了+load的执行顺序

苹果App性能优化:启动优化

 

T2阶段

main()函数之后

从main()函数开始至applicationWillFinishLaunching结束,我们统一称为main()函数之后的部分。

这部分可以这么测试在main函数中我们加入如下代码:

CFAbsoluteTime  startTime;

int main(int argc, char * argv[]) {

    @autoreleasepool {

        

        startTime = CFAbsoluteTimeGetCurrent();

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

    }

}

在appdelegate中我们在didfinish中加入

extern CFAbsoluteTime  startTime;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    

    NSLog(@"starttime %f",CFAbsoluteTimeGetCurrent() - startTime);

    return YES;

}

这样就会得到这段的时间。

苹果App性能优化:启动优化

main()函数之后耗时的影响因素

  • 执行main()函数的耗时
  • 执行applicationWillFinishLaunching的耗时
  • rootViewController及其childViewController的加载、view及其subviews的加载applicationWillFinishLaunching的耗时

优化建议:

  • 尽量不要使用xib/storyboard,而是用纯代码作为首页UI,如果要用xib/storyboard,不要在xib/storyboard中存放太多的视图
  • 对application:didFinishLaunchingWithOptions:里的任务尽量延迟加载或懒加载,减少操作,在启动页出现后再进行相关操作.(例如放到子线程中去执行)
  • 不要在NSUserDefaults中存放太多的数据,NSUserDefaults是一个plist文件,plist文件会被反序列化一次
  • 避免在启动时打印过多的log,少用NSLog,因为每一次NSLog的调用都会创建一个新的NSCalendar实例
  • 为了防止使用GCD创建过多的线程,解决方法是创建串行队列,或者使用带有最大并发数限制的NSOperationQueue
  • 不要在主线程执行磁盘、网络、Lock或者dispatch_sync、发送消息给其他线程等操作
  • 优化rootViewController加载
  • 比如日志 / 统计、Crash监控等需要第一时间启动的, 仍然放在 didFinishLaunchingWithOptions 中.
  • 比如用户数据需要在广告显示完成以后使用, 所以需要伴随广告页启动
  • 不是第一类事件,但是需要APP加载第一个主体页面前就需要启动的,例如一些提供用户信息的SDK、定位功能的初始化、网络初始化等。比如直播和分享等业务, 肯定是用户能看到真正的主界面以后才需要启动, 所以推迟到主界面加载完成以后启动(viewDidAppear:)

 

T3阶段 优化建议

  1. 合理利用闪屏页
    现在许多App在启动时不直接进入首页,而是向用户展示闪屏页。我们可以利用闪屏页作为App的RootViewController的这段时间来构建首页UI,一举两得。
  2. 缓存定位&首页预请求
    一般APP冷启动过程中一个重要的串行流程就是:首页定位-->首页请求-->首页渲染过程

苹果App性能优化:启动优化



优化后的设计是 

  • 发起定位
  • 使用上次缓存的定位,进行首页数据的预请求
    这2个请求并行进行。当真实定位成功后,比较真实定位是否等同缓存定位,如果相同,则刚才的预请求数据有效,这样可以节省大概40%的时间首页加载时间,效果非常明显;如果未命中,则弃用预请求数据,重新请求。

苹果App性能优化:启动优化

 

  1. 如果viewDidLoad 方法里有很多耗时的操作,用户还是会感觉到卡顿。
    所以可以这么优化
    先展示一个空壳的 UI 给用户,然后在 viewDidAppear 方法里进行数据加载解析、渲染等一系列操作,这样一来,用户已经看到界面了,就不会觉得是启动慢,这个时候的等待就变成等待数据请求了,这样就把这部分时间转嫁出去了

 

 

 

 

 

热启动

1.按下home键的时候,iOS APP还存存在一段时间,这时点击APP马上就能恢复到原状态,这种启动我们称为热启动。

2.相对而言冷启动就是App被kill掉以后一切从头开始启动的过程。

热启动优化方案:

1.数据优化,将耗时操作做异步处理。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(globalQueue, ^{

    //子线程异步执行IO任务,防止主线程卡顿

        dispatch_queue_t mainQueue = dispatch_get_main_queue();

         //异步返回主线程,根据获取的数据,更新UI

        dispatch_async(mainQueue, ^{

            NSLog(@"根据更新UI界面");

        });

});

 

2.检查NSUserDefaults的存储,NSUserDefaults实际上是在Library文件夹下会生产一个plist文件,加载的时候是整个plist配置文件全部load到内存中。所以非常频繁的存取大量数据也是有可能导致APP启动卡顿的。