前语

过长的发动时刻关于用户体验是非常的欠好的,苹果建议的发动时刻不要超越400ms,大约0.4秒,并且发动时刻超越20s将被体系直接杀死,所以优化发动时刻是极端必要的。

这里有篇写的不错: www.jianshu.com/p/db493e529… 美团: tech.meituan.com/2018/12/06/…

App的两种发动方式:冷/热发动App完整发动流程以及优化思路

但是,一般提到发动优化和监控,基本上都是指的是:冷发动。所以下面的介绍,也是冷发动优化和监控。

一、冷发动热发动

  • 冷发动:App点击发动前,此时App的进程还不在体系里。需求体系新创立一个进程分配给App。
  • 热发动:App在冷发动后用户将App退回后台,此时App的进程还在体系里。用户从头回来App的进程。

首要差异:

称号 差异
冷发动 发动时,App的进程不在体系里,需求敞开新进程。
热发动 发动时,App的进程还在体系里,不需求敞开新进程。

热发动流程:

APP启动过程监控与优化
APP启动过程监控与优化

二、发动耗时核算

用户能感知到的发动慢,其实都发生在主线程上。 而主线程慢的原因有许多,比如在主线程上履行了耗时的IO读写操作、在烘托周期中履行了许多核算等。 咱们一般核算 App 的发动耗时是从点击 App 开始首屏烘托完结的进程所消耗的时刻。 总结来说,App 的发动首要包含三个阶段:

  • main() 函数履行前;即操作体系加载App可履行文件到内存,然后履行一系列的加载&链接等作业,最终履行至App的main()函数
  • main() 函数履行后;即从main()开始,到appDelegatedidFinishLaunchingWithOptions办法履行结束.
  • 首屏烘托完结后;即 didFinishLaunchingWithOptions 办法作用域内履行首屏烘托之后的一切办法履行完结

发动耗时: t(App总发动时刻) = t1(main()之前的加载时刻) + t2(main()之后到didFinishLaunchingWithOptions的加载时刻) + t3首屏烘托完结

注释:pre-main阶段优化是相对比较复杂的,首要优化仍是在main()之后

1,main 函数之前称为 pre_main 阶段,main函数履行前,体系会做的事:

APP启动过程监控与优化

  • App发动后,首要,体系内核(Kernel)创立一个进程。 加载可履行文件。(App里的一切.o文件
  • 加载动态链接库(dyld是苹果的动态链接器),进行rebase指针调整和bind符号绑定。
  • ObjCruntime初始化。包含:ObjC相关Class的注册category注册selector唯一性查看等。
  • 初始化。包含:履行+load()办法、用attribute((constructor))修饰的函数的调用、创立C++静态全局变量等。

此进程pre_main(main 函数之前)咱们能够运用如下办法去查看时长: 翻开需求检测耗时的项目,Xcode -> Product -> Scheme -> Edit Scheme,新增: DYLD_PRINT_STATISTICS 设置值为1

APP启动过程监控与优化

APP启动过程监控与优化

如上图设置完结,运行项目,然后能够看到下面表格数据 :

加载进程 加载时长
Total pre-main time: 460.33 milliseconds (100.0%) 总耗时大概:0.46秒
dylib loading time: 197.97 milliseconds (43.0%)加载动态库 (能够删去不必要的动态库,动态库兼并,或将动态库变成静态库)。
rebase/binding time: 85.82 milliseconds (18.6%) 指针重定位(进行rebase指针调整和bind符号绑定,ASLR随机分配的内存地址+文件偏移地址,用于找到外部办法)(外部办法:可履行文件外的办法)。
ObjC setup time: 49.00 milliseconds (10.6%) ObjC类初始化(包含ObjC相关Class的注册、category注册、selector唯一性查看等,每20000个类大概增加耗时800ms删去僵尸类。)
initializer time: 127.53 milliseconds (27.7%) 其它初始化(调用每个ObjC类与分类的+load办法,创立C++静态全局变量)。
slowest intializers : 比较耗时的动态库,依据上面可对pre-main阶段进行优化,同时对比swift和OC,因为swift是静态言语,相对耗时会更少。
libSystem.B.dylib : 4.10 milliseconds (0.8%) 一个体系的动态库。
libc++.1.dylib : 13.71 milliseconds (2.9%)
libMainThreadChecker.dylib : 31.80 milliseconds (6.9%)

还有一个办法获取更具体的时刻,只需将环境变量 DYLD_PRINT_STATISTICS_DETAILS 设为 1就能够:

  total time: 1.0 seconds (100.0%)
  total images loaded:  243 (0 from dyld shared cache)
  total segments mapped: 721, into 93608 pages with 6173 pages pre-fetched
  total images loading time: 817.51 milliseconds (78.3%)
  total load time in ObjC:  63.02 milliseconds (6.0%)
  total debugger pause time: 683.67 milliseconds (65.5%)
  total dtrace DOF registration time:   0.07 milliseconds (0.0%)
  total rebase fixups:  2,131,938
  total rebase fixups time:  37.54 milliseconds (3.5%)
  total binding fixups: 243,422
  total binding fixups time:  29.60 milliseconds (2.8%)
  total weak binding fixups time:   1.75 milliseconds (0.1%)
  total redo shared cached bindings time:  29.32 milliseconds (2.8%)
  total bindings lazily fixed up: 0 of 0
  total time in initializers and ObjC +load:  93.76 milliseconds (8.9%)
                           libSystem.dylib :   2.58 milliseconds (0.2%)
               libBacktraceRecording.dylib :   3.06 milliseconds (0.2%)
                            CoreFoundation :   1.85 milliseconds (0.1%)
                                Foundation :   2.61 milliseconds (0.2%)
                libMainThreadChecker.dylib :  42.73 milliseconds (4.0%)
                                   ModelIO :   1.93 milliseconds (0.1%)
                              AFNetworking :  18.76 milliseconds (1.7%)
                                    LDXLog :   9.46 milliseconds (0.9%)
                        libswiftCore.dylib :   1.16 milliseconds (0.1%)
                   libswiftCoreImage.dylib :   1.51 milliseconds (0.1%)
                                    Bigger :   3.91 milliseconds (0.3%)
                              Reachability :   1.48 milliseconds (0.1%)
                             ReactiveCocoa :   1.56 milliseconds (0.1%)
                                SDWebImage :   1.41 milliseconds (0.1%)
                             SVProgressHUD :   1.23 milliseconds (0.1%)
total symbol trie searches:    133246
total symbol table binary searches:    0
total images defining weak symbols:  30
total images using weak symbols:  69

那么针对此进程pre_main(main 函数之前)的优化计划:

  • (1)削减运用 +load() 办法里做作业,尽量把这些作业推迟到+initiailize 计划①:如果或许的话,将+load中的内容,放到烘托完结后做。 计划②:运用+initialize()的办法替代+load(),留意把逻辑移动到+initialize()时,要留意防止+initialize()的重复调用问题,能够运用dispatch_once()让逻辑只履行一次。
  • (2)兼并多个动态库 苹果公司建议运用更少的动态库,并且建议在运用动态库的数量较多时,尽量将多个动态库进行兼并。数量上,苹果公司最多能够支持6个非体系动态库兼并为一个。 除了咱们App自身的可行性文件,体系中一切的framework比如UIKit、Foundation等都是以动态链接库的方式集成进App中的。 体系运用动态链接有几点好处代码共用:许多程序都动态链接了这些 lib,但它们在内存和磁盘中只要一份。 易于维护:因为被依靠的 lib 是程序履行时才链接的,所以这些 lib 很容易做更新,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想晋级直接换成libSystem.C.dylib 然后再替换替身就行了。 削减可履行文件体积:相比静态链接,动态链接在编译时不需求打进去,所以可履行文件的体积要小许多。
  • (3)优化类、办法、全局变量 削减ObjC类(class)、办法(selector)、分类(category)的数量;少用C++全局变量;main函数履行后,削减加载发动后不会去运用的类或者办法。
  • (4)优化首屏烘托前的功用初始化 main函数履行后到首屏烘托完结前,只处理首屏烘托相关事务。首屏烘托外的其他功用放到首屏烘托完结后去初始化。
  • (5)优化主线程耗时操作,防止屏幕卡顿 首要查看首屏烘托前,主线程上的耗时操作。将耗时操作滞后或异步处理。通常的耗时操作有:网络加载、编辑、存储图片和文件等资源。针对耗时操作做相对应的优化即可。
  • (6)经过重排 Mach-O中的二进制,削减发动流程中的缺页中断次数(Page Fault)

小知识点:+load()+initialize()两者的差异?

+load()办法会在main()函数调用前就调用,而+initialize()是在类第一次运用时才会调用。 +load办法的调用优先级: 父类 > 子类 > 分类,并且不会被掩盖,均会调用。 +load办法是在main() 函数之前调用,一切的类文件都会加载,包含分类也会加载。+initialize办法的调用优先级:分类 > 子类,父类 > 子类。(父类的分类重写了+initialize办法会掩盖父类的+initialize办法)

2,main函数履行后: main函数履行后的阶段,指的是:从 main 函数履行开始,到 appDelegate didFinishLaunchingWithOptions办法里首屏烘托相关办法履行完结。即: 从main函数履行到设置self.window.rootViewController履行完结的阶段

  • 首屏初始化所需配置文件的读写操作;
  • 首屏列表大数据的读取;
  • 首屏烘托的许多核算;

APP启动过程监控与优化

此进程,时刻核算能够如下: 在main函数文件下:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
CFAbsoluteTime startTime;
int main(int argc, char * argv[]) {
   startTime = CFAbsoluteTimeGetCurrent();
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

AppDelegate.h里面

#import <UIKit/UIKit.h>
extern CFAbsoluteTime startTime;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end

AppDelegate.m里面

#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    double launchTime =  CFAbsoluteTimeGetCurrent() - startTime;
    NSLog(@"main()阶段测量时刻launchTime-----:  %.2f秒",launchTime);
    return YES;
}

最终打印:

main()阶段测量时刻launchTime-----: 0.06

那么针对此进程main() 函数履行后的优化项的优化计划:

  • 削减在主线程中履行IO读写操作.
  • 将各种SDK(二方、三方)初使化作业放到子线程处理.
  • 削减首屏烘托的许多核算.

首屏烘托完结后的的优化计划: 这个阶段用户现已能够看到 App 的主页信息了,所以优化的优先级排在最终。但是,那些会卡住主线程的办法仍是需求最优先处理的,否则仍是会影响到用户后边的交互操作。