App启动速度的优化与监控

App的启动分为冷启动和热启动两种方式

  • 冷启动: App 点击启动前,它的进程不在系统里,需要系统新创建一个进程分配给他启动的的情况, 这是一次完整的启动过程
  • 热启动: App冷启动后, 将App进入后台. 此时App的进程还在系统中, 用户重新进入App, 这个过程做的事情非常少, 称之为热启动

App启动速度的优化主要是针对冷启动时的优化. 用户感知到启动速度慢主要是因为主线程的卡顿, 在主线程中进行了大量文件读写操作, 或者屏幕渲染周期中进行了大量的计算等等. 然而, 即便处理了首屏之前所有的主线程操作, 有时候App的启动仍然慢于其他竞品App, 这个时候我们就需要对App首屏启动前的流程进行一个分析, 找出可以被优化的方向.

一般而言, App的启动时间是指, 从用户点击App开始, 到用户看到App首屏画面中间的这段时间, 这期间主要包括三个阶段:

  1. main() 函数执行前
  2. main() 函数执行后
  3. 首屏渲染完成后

在main() 函数执行前, 系统会做的几件事包括:

  1. 加载可执行文件(.o)文件(编译时, 先将文件预编译, 替换宏, 删除注释, 展开头文件等生成预编译.i文件, 然后编译生成.s文件, 后将.s文件编译成机器可以识别的.o文件)
  2. 加载动态链接库, rebase指针调整, bind符号绑定
  3. Objc运行时的初始处理, 包括相关类的注册, category的注册, selector的唯一性检查等等

在这个时期, 我们可以做的对启动速度的优化操作包括:

  • 减少不必要的动态库的加载, 尽量将多个动态库进行合并. 数量上, 苹果公司最多支持6个非系统动态库合并为一个
  • 减少在启动后不回去使用的类或者方法
  • +load()方法里的内容可以等到首屏渲染以后再去执行, 或者使用initialize() 方法去替换, 因为在每个+ load()方法中, 进行运行时方法替换都会带来4毫秒的消耗, 积少成多, 大量的+load方法对启动会起到很大的影响
  • 控制C++全局变量的数量

在main()函数执行后(即main()函数开始执行到appDelegate的didFinishlaunchingWithOpitons方法里首屏渲染相关方法执行完成)首页的业务代码都是在这个阶段, 也就是首屏渲染完成前执行的, 主要包括

  • 首屏初始化所需配置的文件的读写操作
  • 首屏列表大数据读取
  • 首屏渲染的大数据计算

在这个时期应该确定, 那些初始化方法需要在这个阶段执行, 那些方法可以再首屏渲染完成后执行, 梳理完后将初始化功能放到合适的阶段

首屏渲染完成后, 此时主要完成的就是:

  • 其他非首屏业务模块的初始化
  • 监听注册, 配置文件的读取

此时用户已经可以看到App首页的信息了, 从代码上看则是以首屏渲染完成后到didFinishLaunchingWithOptions方法作用域结束.

功能级别的启动优化就要从main()函数后的阶段入手.

  1. 通过监听runloop根据时间间隔判断卡顿:

ObserverMonitor.h

//
//  ObserverMonitor.h
//  StackCatch
//
//  Created by 阮皓 on 2019/3/14.
//  Copyright © 2019 阮皓. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface ObserverMonitor : NSObject

+ (instancetype)monitor;

+ (void)start;

@end

ObserverMonitor.m

//
//  ObserverMonitor.m
//  StackCatch
//
//  Created by 阮皓 on 2019/3/14.
//  Copyright © 2019 阮皓. All rights reserved.
//

#import "ObserverMonitor.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>

@interface ObserverMonitor () {
    
    CFRunLoopObserverRef _observer;
    dispatch_semaphore_t _semaphore;
    CFRunLoopActivity _activity;
}



@end

@implementation ObserverMonitor

+ (instancetype)monitor {
    
    static ObserverMonitor *monitor;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        monitor = [[ObserverMonitor alloc] init];
    });
    return monitor;
}

+ (void)start {
    
    [[ObserverMonitor monitor] startObserver];
}

- (void)startObserver {
    
    CFRunLoopObserverContext context = {
        
        0,
        (__bridge void *)(self),
        &CFRetain,
        &CFRelease,
        NULL
    };
    _observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    
    _semaphore = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        while (YES) {
            long result = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
            // Returns zero on success, or non-zero if the timeout occurred.
            if (result != 0) {
                // 说明一个runloop超时
                if (self->_activity == kCFRunLoopBeforeSources || self->_activity == kCFRunLoopAfterWaiting) {
                    // 说明这个超时的loop不是沉睡状态
                    [self logStack];
                }
            }
        }
    });
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    
    ObserverMonitor *monitor = (__bridge ObserverMonitor *)info;
    monitor->_activity = activity;
    dispatch_semaphore_signal(monitor->_semaphore);
}

- (void)logStack {
    
    void *callstack[128];
    int frames = backtrace(callstack, 128);
    char **strs = backtrace_symbols(callstack, frames);
    int i;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (i = 0; i < 4; i++) {
        
        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    NSLog(@"====================堆栈\n %@ \n",backtrace);
}

@end

然而, 这种方式可以检测出runloop的卡顿, 却不能精确地指出到底是哪一个方法导致了runloop的卡顿. 这时候可以使用对objc_msgSend方法进行hook来掌握所有方法的执行耗时.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容

  • 爱情就是死循环,一旦执行就陷进去了;爱上一个人,就是内存泄漏–你永远释放不了;真正爱上一个人的时候,那就是常量限定...
    PetitBread阅读 1,112评论 0 2
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,096评论 1 32
  • 背景 一个项目做的时间长了,启动流程往往容易杂乱,库也用的越来越多,APP的启动时间也会慢慢变长。本次将针对iOS...
    酱油瓶2阅读 3,508评论 0 12
  • 武陵春 春晚 李清照 风住尘香花已尽,日晚倦梳头。物是人非事事休,欲语泪先流。 闻说双溪春尚好,也拟泛轻舟,只恐双...
    胡诹阅读 403评论 0 3
  • 一切都应该尽可能地简单,但不要太简单。——阿尔伯特·爱因斯坦 这句话体现出来的是一种化繁为简的能力,把复杂的系统通...
    刻意练习社区阅读 373评论 1 0