iOS-性能优化-耗电优化、启动优化、安装包瘦身

一. 耗电优化

耗电的主要来源

耗电来源.png

CPU处理,Processing
网络,Networking
定位,Location
图像,Graphics

1. CPU、GPU优化

  1. 尽可能降低CPU、GPU功耗
  2. 少用定时器
  3. 优化I/O操作
    ① 尽量不要频繁写入小数据,最好批量一次性写入。
    ② 读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API,用dispatch_io系统会优化磁盘访问。
    ③ 数据量比较大的,建议使用数据库(比如SQLite、CoreData),因为数据库是由优化过的。

2. 网络优化

  1. 减少、压缩网络数据。
    ① 比如XML文件体积就比较大,所以现在大部分公司都使用json,json体积比较小,现在有些公司也在使用protocol buffer这种格式,有兴趣的可以去研究下。
    ② 上传文件的时候可以压缩文件,服务器拿到文件后再解压。
  2. 如果多次请求的结果是相同的,尽量使用缓存。
  3. 使用断点续传,否则网络不稳定时可能多次传输相同的内容。
  4. 网络不可用时,不要尝试执行网络请求。
  5. 让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间。
  6. 批量传输。
    ① 比如下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。
    ② 如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载。

3. 定位优化

  1. 如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电。
  2. 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务。
  3. 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest。
  4. 需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新。
  5. 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion:。

4. 硬件检测优化

用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测,在不需要检测的场合,应该及时关闭这些硬件。

二. 启动优化

APP的启动可以分为两种:

  1. 冷启动(Cold Launch):从零开始启动APP。
  2. 热启动(Warm Launch):APP已经在内存中,在后台存活着,再次点击图标启动APP。

APP启动时间的优化,主要是针对冷启动进行优化。

通过添加环境变量可以打印出APP的启动时间分析,点击Edit scheme -> Run -> Arguments,给Environment Variables添加一个DYLD_PRINT_STATISTICS,并且把Value设置为1,如下图:

启动打印.png

运行空项目,打印:

Total pre-main time: 646.22 milliseconds (100.0%) //main函数调用之前总耗时
         dylib loading time: 165.83 milliseconds (25.6%) //动态库加载耗时
        rebase/binding time: 385.45 milliseconds (59.6%) 
            ObjC setup time:  61.10 milliseconds (9.4%) //OC结构体准备耗时
           initializer time:  33.64 milliseconds (5.2%) //初始化耗时
           slowest intializers : //比较慢的加载,分别是下面两个动态库
             libSystem.B.dylib :   8.76 milliseconds (1.3%)
    libMainThreadChecker.dylib :  14.72 milliseconds (2.2%)

如果需要更详细的信息,那就将DYLD_PRINT_STATISTICS改为DYLD_PRINT_STATISTICS_DETAILS并且Value也是设置为1,打印如下:

total time: 741.00 milliseconds (100.0%) //总耗时
total images loaded:  258 (0 from dyld shared cache) //镜像加载耗时
total segments mapped: 767, into 102624 pages with 7415 pages pre-fetched //每个块的映射
total images loading time: 296.70 milliseconds (40.0%)
total load time in ObjC:  69.74 milliseconds (9.4%) //加载objc
total debugger pause time: 244.17 milliseconds (32.9%)
total dtrace DOF registration time:   0.13 milliseconds (0.0%)
total rebase fixups:  2,723,656
total rebase fixups time: 300.98 milliseconds (40.6%)
total binding fixups: 286,603
total binding fixups time:  48.64 milliseconds (6.5%)
total weak binding fixups time:   0.44 milliseconds (0.0%)
total redo shared cached bindings time:  61.81 milliseconds (8.3%)
total bindings lazily fixed up: 0 of 0
total time in initializers and ObjC +load:  24.36 milliseconds (3.2%)
libSystem.B.dylib :   2.38 milliseconds (0.3%)
libBacktraceRecording.dylib :   2.95 milliseconds (0.3%)
CoreFoundation :   1.74 milliseconds (0.2%)
Foundation :   1.73 milliseconds (0.2%)
libMainThreadChecker.dylib :  13.70 milliseconds (1.8%)
total symbol trie searches:    133794
total symbol table binary searches:    0
total images defining weak symbols:  22
total images using weak symbols:  62

一般总时间在400ms以内都是比较正常的,如果大于400ms就可以优化了。

1. 冷启动的三大阶段

APP的冷启动可以概括为三大阶段:

  1. dyld 装载阶段
  2. runtime阶段
  3. main阶段

如下图:

冷启动.png

① dyld 装载阶段

  1. dyld(dynamic link editor),它是Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等)。
  2. 启动APP时,dyld动态链接器会装载APP的可执行文件,同时会递归加载所有依赖的动态库。

什么是可执行文件?
右键点击XX.app选择Show in Finder,找到XX.app,右键点击XX.app选择显示包内容,可以发现里面有个Unix可执行文件,平时我们写的所有代码都在这里面了,这就是一个Mach-O格式的可执行文件。如下图:

包.png

注意:项目没编译的时候上面的XX.app是红色的,并且右键Show in Finder也没东西,编译之后才会多一个XX.app的包。

显示包内容.png
可执行文件.png

但是我们项目开发中也会依赖一些动态库,比如UIKit、Foundation,这些动态库都不是包含在可执行文件里面的,可执行文件里面只有一些依赖信息,比如这个项目依赖哪些动态库,一个动态库也可能依赖另一个动态库。

在dyld阶段,dyld动态链接器会装载可执行文件,以及检查可执行文件依赖哪些动态库,并加载那些动态库,一个动态库也可能依赖另一个动态库,dyld动态链接器就是这样一个一个检查,递归查找,直到装载完所有的动态库到内存中。

当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理。

② runtime阶段

启动APP时,runtime所做的事情有:

  1. 调用map_images函数进行可执行文件内容的解析和处理
  2. 在load_images函数中调用call_load_methods,调用所有Class和Category的+load方法
  3. 进行各种objc结构的初始化(注册Objc类 、初始化类对象等等)
  4. 调用C++静态初始化器和__attribute__((constructor))修饰的函数

到此为止,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP…)都已经按格式成功加载到内存中,被runtime 所管理。

可能你对map_images函数比较熟悉,我们在以前看_objc_init源码的时候接触过这个函数,如下:

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    //map_images:可执行文件内容的解析和处理
    //load_images函数中调用call_load_methods,调用所有Class和Category的+load方法
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    //调用所有Class和Category的+load方法
    call_load_methods();
}

③ main阶段

  1. APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库,并由runtime负责加载成objc定义的结构。
  2. 所有初始化工作结束后,dyld就会调用main函数,接下来就是UIApplicationMain函数,然后进入AppDelegate的application:didFinishLaunchingWithOptions:方法。

2. 冷启动优化

按照不同的阶段:

  1. dyld 装载阶段
    减少动态库、合并一些动态库(定期清理不必要的动态库)
    减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)
    减少C++虚函数数量(因为一旦有虚函数就要多维护一张虚表,有虚表的话冷启动的时候就要多耗费一点时间)
    Swift尽量使用struct(因为加载类的时候也耗费时间)

  2. runtime阶段
    用+initialize方法和dispatch_once取代所有的__attribute__((constructor))、C++静态构造器、ObjC的+load方法。

  3. main阶段
    在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在didFinishLaunchingWithOptions方法中,也就是按需加载。

三. 安装包瘦身

上面我们说了,项目中所有的代码、资源都在XX.app包里面,将来Xcode会将这个XX.app包压缩成一个ipa文件,然后上传到AppStore提供给用户下载,如果项目越来越大,那么这个ipa文件就会越来越大。

为了给安装包(IPA)瘦身,我们就要知道安装包有哪些文件组成,安装包(IPA)主要由可执行文件、资源组成

安装包瘦身方式:

① 对于资源(图片、音频、视频等)

  1. 采取无损压缩
  2. 去除没有用到的资源:https://github.com/tinymind/LSUnusedResources

② 对于可执行文件

  1. 编译器优化
    Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES(现在的项目已经默认为YES了,一些老项目可能还会为NO)。
  2. 去掉异常支持
    Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions。
  3. 利用AppCode(https://www.jetbrains.com/objc/)检测未使用的代码
    菜单栏 -> Code -> Inspect Code。
  4. 编写LLVM插件检测出重复代码、未被调用的代码(这种方式比较高级也比较难)。
  5. 生成LinkMap文件,可以查看可执行文件的具体组成。如下图:
LinkMap.png

如果项目比较大,分析LinkMap文件就会比较麻烦,我们可以借助第三方工具解析LinkMap文件:https://github.com/huanxsd/LinkMap

它其实是个Mac项目,我们下载下来,运行项目 -> 选择文件 -> 点击开始,就能显示每个文件占用多大,我们就能根据文件有目的性的进行优化。如下图:

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