美文网首页面试题
性能优化:App启动优化

性能优化:App启动优化

作者: 意一ineyee | 来源:发表于2019-11-13 16:36 被阅读0次
    一、App启动流程
    二、App启动优化
    三、定量监测App启动耗时
    四、定位耗时代码
    五、果速送App启动优化

    一、App启动流程


    App启动分两种:冷启动和热启动。冷启动是指启动一个杀死的App,系统需要为App开辟一个新进程,热启动是指启动一个在后台运行的App,App的进程还存活着,系统不需要开辟新进程。而我们通常所说的App启动优化,主要是指冷启动优化——尽可能地缩短启动时间,所以接下来本文中谈到的启动都是指冷启动。

    App启动流程主要分为两大阶段:main函数调用前的阶段和main函数调用至didFinishLaunchingWithOptions方法执行完毕的阶段。

    1、main函数调用前的阶段

    • App启动后,系统会首先加载项目的可执行文件和依赖的动态库到内存中。
    • 然后系统会调用Runtime的函数对项目的可执行文件进行解析和处理,把项目中所有的类和分类加载到内存中,此时会调用项目中所有类和分类的+load方法。

    2、main函数调用至didFinishLaunchingWithOptions方法执行完毕的阶段

    • 系统会调用main函数、UIApplicationMain函数、AppDelegatedidFinishLaunchingWithOptions方法,我们通常会在这个方法里给window设置rootViewController,不过我们要知道只有rootViewController或者rootViewController首屏的viewDidLoad方法和viewWillAppear方法走完了,才算rootViewController设置完毕。
    • rootViewController设置完毕,我们通常还会在这个方法里为App添加一些必要的启动项,didFinishLaunchingWithOptions方法走完了就代表App启动起来了,此时系统才会去调用rootViewController或者rootViewController首屏的viewDidAppear方法,这是后话了。

    二、App启动优化


    1、main函数调用前的阶段

    • 尽量减少动态库的使用,定期清理掉不必要的动态库;
    • 尽量减少类和分类的数量,定期清理掉不必要的类和分类;尽量减少类和分类+load方法的调用,不要在里面做复杂的操作,可以用+initialize方法和dispatch_once替代,因为+initialize方法是初始化类和分类时才调用的。

    2、main函数调用至didFinishLaunchingWithOptions方法执行完毕的阶段

    • rootViewController或者rootViewController首屏的viewDidLoad方法和viewWillAppear方法里尽量只做一些关键数据的初始化和绘制UI操作,不要做别的耗时操作,如果非做不可,就放到子线程里去做。
    • 启动项最好按需配置,不需要在App一启动就配置的启动项可以推迟到使用时再配置,必须配置的启动项如果很耗时,就放到子线程里去做。

    三、定量监测App启动耗时


    1、main函数调用前的阶段

    Xcode --> Target --> Edit Scheme --> Run --> Arguments --> Environment Variables,添加DYLD_PRINT_STATISTICS = YES

    运行项目,控制台就会打印pre-main阶段的启动耗时了。

    我们添加一个ARKit动态库。

    我们再添加很多类和分类。

    我们再调用一下某个类的+load方法,里面休眠2s模拟耗时操作。

    2、main函数调用至didFinishLaunchingWithOptions方法执行完毕的阶段

    Xcode --> Project --> Target --> Build Settings --> dsym --> Debug Information Format --> 都设置为DWARF with dSYM File,否则Time Profiler显示出来的都是函数的内存地址而不是函数名。
    InstrumentsTime Profiler
    • Xcode打开项目,连接真机(一定要用真机调试,因为模拟器用的是电脑的CPU,检测不出手机的启动耗时来)。
    • 打开InstrumentsTime Profiler
    • 选择真机、选择项目、运行、定量地监测App启动耗时,并定位耗时代码。
    // AppDelegate.m
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        [self.window setBackgroundColor:[UIColor whiteColor]];
        [self.window makeKeyAndVisible];
        
        [self.window setRootViewController:[[ViewController alloc] init]];
        
        return YES;
    }
    
    
    // ViewController.m
    - (void)viewDidLoad {
        [super viewDidLoad];
    
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        
    }
    

    我们在rootViewControllerviewDidLoad方法和viewWillAppear方法里添加一些耗时操作。

    // ViewController.m
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self doSomething];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        
        [self doSomething];
    }
    
    
    #pragma mark - 模拟耗时操作
    
    - (void)doSomething {
        
        for (int i = 0; i < 200000; i++) {
            
            NSLog(@"%d", i);
        }
    }
    
    可见Test项目的总启动耗时为2.92s,其中“main函数调用至didFinishLaunchingWithOptions方法执行完毕阶段”耗时2.66s,而导致耗时这么长的原因就是rootViewController的viewDidLoad方法和viewWillAppear方法里的doSomething方法,我们可以对它进行优化

    我们再在rootViewController设置完毕、didFinishLaunchingWithOptions方法执行完毕之前添加一些耗时操作。

    // AppDelegate.m
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        [self.window setBackgroundColor:[UIColor whiteColor]];
        [self.window makeKeyAndVisible];
        
        [self.window setRootViewController:[[ViewController alloc] init]];
        
        [self doSomething];
        
        return YES;
    }
    
    
    #pragma mark - 模拟耗时操作
    
    - (void)doSomething {
        
        for (int i = 0; i < 200000; i++) {
            
            NSLog(@"%d", i);
        }
    }
    

    五、果速送App启动优化


    当然App启动优化的方案有很多,比如尽量减少动态库的使用;尽量减少类和分类的数量,尽量减少类和分类+load方法的调用;在rootViewController首屏的viewDidLoad方法和viewWillAppear方法里尽量只做一些关键数据的初始化和绘制UI操作,不要做别的耗时操作,如果非做不可,就放到子线程里去做;启动项最好按需配置,不需要在App一启动就配置的启动项可以推迟到使用时再配置,必须配置的启动项如果很耗时,就放到子线程里去做。

    但是经过定量检测,我们发现影响我们App启动速度的主要原因有两个:一是有一些不用的动态库没有及时清理掉,二是有一个访问数据库的同步I/O操作的启动项比较耗时。

    所以我们就定了主要的优化方案就是针对这两点,优化之前App的启动时间大概在700ms左右,而当我们清理掉一些不用的动态库,并且把那个同步I/O操作的启动项放在子线程中去做后,App的启动时间大概就在500ms左右了。

    一个额外的优化

    此外我们发现首页里有一个“发起定位 --> 定位成功后,拿定位数据发起网络请求 --> 网络请求成功后,拿网络数据刷新页面”这么个串行流程,这个串行过程是很耗时的,这也是导致用户迟迟不能开始使用App的原因所在。

    所以我们对这个串行流程也做了优化:在发起定位的同时,拿缓存的定位数据发起网络请求。定位成功后,判断最新的定位数据是否命中缓存的定位数据,如果命中则什么都不做,让之前的请求生效就行了;如果未命中,则取消之前的请求,并拿最新的定位数据发起网络请求。

    注意:取消网络请求这个操作

    • 如果请求未发出,取消后则不会发出请求;
    • 如果请求已发出、未完成,取消后则会立即触发请求的failure回调,我们要在这里判断一下是否是取消了请求再做错误弹框处理;
    • 如果请求已完成,取消后则不会有任何效果。

    启动项整理

    杂乱无章的启动项 整理后的启动项

    相关文章

      网友评论

        本文标题:性能优化:App启动优化

        本文链接:https://www.haomeiwen.com/subject/gvnsbctx.html