This post was updated 2010 days ago and some of the ideas may be out of date.

一、前言:

APP的性能大家应该都比较关注,那么咱们的自己APP性能如何呢?如何在拿不到源码的情况下去调试第三方应用的性能呢?下面我就简单实践一下。就以比较著名的今日头条和贝壳找房为例吧!大家看看自己的APP和他们还有多少差距。

二、分析启动时和启动后的cpu和内存占用

下面我们对这个指标以2个APP进行性能分析,分别是贝壳找房2.6.1版本、今日头条7.2.2版本。手机我们使用12.2系统的iPhoneX。我们借助于Instruments中的Activity Monitor工具进行分析。首先我们去拿到这几个ipa的脱壳包(这里可以借鉴这两篇文章,传送门一->点击我, 传送门二->点击我;或者可以去越狱市场直接下载脱壳好的包),脱壳后使用development的证书进行重签名(可以参考:传送门->点击我)。然后我们将这两个包安装到手机中去。
如图:

以下我们分别对这2个APP测试APP启动前5秒的CPU峰值以及对应的内存占用和启动后某一平稳时段的CPU占用以及对应的内存。

2.1

先看今日头条的数据:
如图:

今日头条在前5秒的CPU峰值达到了148.1%,此时的内存占用是63.66M
然后我们再看看启动后的平稳数据
如图:

CPU是3.9%,此时的内存占用是102.06M

2.2

我们再看贝壳找房的数据:
如图:

贝壳找房在前5秒的CPU峰值达到了116.4%,此时的内存占用是77.30M
然后我们再看看启动后的平稳数据
如图:

CPU是2.9%,此时的内存占用是77.02M

从以上数据看,两个APP的突发峰值都还好,平峰比较稳定,大家可以对比看下自己的APP是否和他们差不多,我们再看下一个指标,APP启动时间。

三、分析APP启动时常

下面我们使用 MonkeyDev (传送门->点击我) 工具将上面的那两个包运行起来,在Environment Variables中加入DYLD_PRINT_STATISTICS变量,得到Total pre-main time。我们对每个APP分别测量3次,取平均值。
如图:

3.1

我们先看今日头条的3次测量数据
第一次:

第二次:

第三次:

得到最终今日头条的平均值是:1.3s

3.2

然后我们再看贝壳找房的3次数据
第一次:

第二次:

第三次:

得到最终贝壳找房的平均值是:954.66ms

从以上数据分析,各个APP之间的pre-main之间耗费的时间差别并不多,我们看下各个内容的含义。

  • dylib loading time
    载入动态库,这个过程中,会去装载app使用的动态库,而每一个动态库有它自己的依赖关系,所以会消耗时间去查找和读取。对于Apple提供的的系统动态库,做了高度的优化。而对于开发者定义导入的动态库,则需要在花费更多的时间。Apple官方建议尽量少的使用自定义的动态库,或者考虑合并多个动态库,其中一个建议是当大于6个的时候,则需要考虑合并它们

  • rebase/binding time
    重构和绑定,rebase会修正调整处理图像的指针,并且会设置指向绑定(binding)外部的图像指针。所以为了加快rebase/binding,则需要更少的做指针修复。当你的app当中有太多的Objective-C的类,方法选择器,和类别会增加这一部分的启动时间。有一个数据当大于20000个时候,会增加800ms的时间。另一点:当你的app中使用了很少的C++的虚拟函数,使用Swift会更加高效

  • ObjC setup time
    在Objective-C的运行时(runtime),需要对类(class),类别(category)进行注册,以及选择器的分配,所以参照rebase/binding time,尽量减少类的数量,可以达到减少这一部分的时间

  • initializer time
    这一份指代的是执行+initialize方法的时间。如果你执行了+load方法(不建议),尽量使用+initialize代替。

  • slowest intializers
    这个列出的是最慢的几个dylib文件。

从以上数据来看,他们大致的耗时操作都差不多,需要从以上几点进行一下优化。不过我看来优化启动的时间应该从pre-main之后入手,测试main到第一个viewcontroller的viewDidAppear的时间。

四、分析APP第一个控制器的启动时间

下面我以贝壳找房进行详细描述,用FLEXible获取贝壳找房的首页控制器名称
如图:

然后我们注入如下代码:获取首页控制器的viewDidLoad到viewDidAppear的执行时间

CHDeclareClass(LJHomePageViewController)

CHOptimizedMethod1(self, void, LJHomePageViewController, viewDidAppear, BOOL, animated)
{
    CHSuper1(LJHomePageViewController, viewDidAppear, animated);
    NSLog(@"第一个视图完毕");
    // 得到加载时间
    NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:self.testDate];
    NSLog(@"????????????%g", duration);
}

CHOptimizedMethod0(self, void, LJHomePageViewController, viewDidLoad)
{
    CHSuper0(LJHomePageViewController, viewDidLoad);
    YYFPSLabel *fpsLabel = [YYFPSLabel new];
    fpsLabel.frame = CGRectMake(180, 200, 120, 30);
    [fpsLabel sizeToFit];
    [[UIApplication sharedApplication].delegate.window addSubview:fpsLabel];

    NSDate *date = [NSDate date];
    // 保存开始时间
    self.testDate = date;
}

////add new property
CHPropertyRetainNonatomic(LJHomePageViewController, NSDate*, testDate, setTestDate);

CHConstructor{
    CHLoadLateClass(LJHomePageViewController);
    CHClassHook0(LJHomePageViewController, viewDidLoad);
    CHClassHook1(LJHomePageViewController, viewDidAppear);

    CHHook0(LJHomePageViewController, testDate);
    CHHook1(LJHomePageViewController, setTestDate);

}

可以看到他第一个控制器的时间约为0.24s,当然网络请求后的渲染时间此处是没有统计的,有兴趣的大家可以再hook网络那部分。

五、分析APP主要列表的FPS

这里我以今日头条为例子,使用FLEXible获取今日头条的主页面,然后分别对各个主页面的viewDidLoad方法进行Hook,然后在这里加入YYFPSLabel。这里直接对APPDelegate进行Hook也可以。
如图:

以下是hook的代码:

@interface TTFeedCollectionViewController
- (void)viewDidLoad;
@end

CHDeclareClass(TTFeedCollectionViewController)

CHOptimizedMethod0(self, void, TTFeedCollectionViewController, viewDidLoad)
{
    CHSuper0(TTFeedCollectionViewController, viewDidLoad);
    YYFPSLabel *fpsLabel = [YYFPSLabel new];
    fpsLabel.frame = CGRectMake(180, 200, 50, 30);
    [fpsLabel sizeToFit];
    [[UIApplication sharedApplication].delegate.window addSubview:fpsLabel];
}

CHConstructor{
    CHLoadLateClass(TTFeedCollectionViewController);
    CHClassHook0(TTFeedCollectionViewController, viewDidLoad);
}

然后我们就可以看到所有页面的帧率
如图:

此处页面稳定后的帧率是接近60,可以看出今日头条优化的还是可以的。

六、总结:

以上是以今日头条和贝壳找房进行了下实践,大家感兴趣可以多试试,然后对比自己的应用,看看有没有需要提高的。另外可以借鉴逆向的思维,去调试一些东西,挖掘和大厂APP之间的差距。这就是这篇文章的初衷。

参考文献:
优化 App 的启动时间实践 iOS
你的iOS-App启动为什么缓慢