一. 耗电优化
耗电的主要来源
CPU处理,Processing
网络,Networking
定位,Location
图像,Graphics
1. CPU、GPU优化
- 尽可能降低CPU、GPU功耗
- 少用定时器
- 优化I/O操作
① 尽量不要频繁写入小数据,最好批量一次性写入。
② 读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API,用dispatch_io系统会优化磁盘访问。
③ 数据量比较大的,建议使用数据库(比如SQLite、CoreData),因为数据库是由优化过的。
2. 网络优化
- 减少、压缩网络数据。
① 比如XML文件体积就比较大,所以现在大部分公司都使用json,json体积比较小,现在有些公司也在使用protocol buffer这种格式,有兴趣的可以去研究下。
② 上传文件的时候可以压缩文件,服务器拿到文件后再解压。 - 如果多次请求的结果是相同的,尽量使用缓存。
- 使用断点续传,否则网络不稳定时可能多次传输相同的内容。
- 网络不可用时,不要尝试执行网络请求。
- 让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间。
- 批量传输。
① 比如下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。
② 如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载。
3. 定位优化
- 如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电。
- 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务。
- 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest。
- 需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新。
- 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion:。
4. 硬件检测优化
用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测,在不需要检测的场合,应该及时关闭这些硬件。
二. 启动优化
APP的启动可以分为两种:
- 冷启动(Cold Launch):从零开始启动APP。
- 热启动(Warm Launch):APP已经在内存中,在后台存活着,再次点击图标启动APP。
APP启动时间的优化,主要是针对冷启动进行优化。
通过添加环境变量可以打印出APP的启动时间分析,点击Edit scheme -> Run -> Arguments,给Environment Variables添加一个DYLD_PRINT_STATISTICS,并且把Value设置为1,如下图:
运行空项目,打印:
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的冷启动可以概括为三大阶段:
- dyld 装载阶段
- runtime阶段
- main阶段
如下图:
① dyld 装载阶段
- dyld(dynamic link editor),它是Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等)。
- 启动APP时,dyld动态链接器会装载APP的可执行文件,同时会递归加载所有依赖的动态库。
什么是可执行文件?
右键点击XX.app选择Show in Finder,找到XX.app,右键点击XX.app选择显示包内容,可以发现里面有个Unix可执行文件,平时我们写的所有代码都在这里面了,这就是一个Mach-O格式的可执行文件。如下图:
注意:项目没编译的时候上面的XX.app是红色的,并且右键Show in Finder也没东西,编译之后才会多一个XX.app的包。
但是我们项目开发中也会依赖一些动态库,比如UIKit、Foundation,这些动态库都不是包含在可执行文件里面的,可执行文件里面只有一些依赖信息,比如这个项目依赖哪些动态库,一个动态库也可能依赖另一个动态库。
在dyld阶段,dyld动态链接器会装载可执行文件,以及检查可执行文件依赖哪些动态库,并加载那些动态库,一个动态库也可能依赖另一个动态库,dyld动态链接器就是这样一个一个检查,递归查找,直到装载完所有的动态库到内存中。
当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理。
② runtime阶段
启动APP时,runtime所做的事情有:
- 调用map_images函数进行可执行文件内容的解析和处理
- 在load_images函数中调用call_load_methods,调用所有Class和Category的+load方法
- 进行各种objc结构的初始化(注册Objc类 、初始化类对象等等)
- 调用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阶段
- APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库,并由runtime负责加载成objc定义的结构。
- 所有初始化工作结束后,dyld就会调用main函数,接下来就是UIApplicationMain函数,然后进入AppDelegate的application:didFinishLaunchingWithOptions:方法。
2. 冷启动优化
按照不同的阶段:
dyld 装载阶段:
减少动态库、合并一些动态库(定期清理不必要的动态库)
减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)
减少C++虚函数数量(因为一旦有虚函数就要多维护一张虚表,有虚表的话冷启动的时候就要多耗费一点时间)
Swift尽量使用struct(因为加载类的时候也耗费时间)runtime阶段:
用+initialize方法和dispatch_once取代所有的__attribute__((constructor))、C++静态构造器、ObjC的+load方法。main阶段:
在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在didFinishLaunchingWithOptions方法中,也就是按需加载。
三. 安装包瘦身
上面我们说了,项目中所有的代码、资源都在XX.app包里面,将来Xcode会将这个XX.app包压缩成一个ipa文件,然后上传到AppStore提供给用户下载,如果项目越来越大,那么这个ipa文件就会越来越大。
为了给安装包(IPA)瘦身,我们就要知道安装包有哪些文件组成,安装包(IPA)主要由可执行文件、资源组成。
安装包瘦身方式:
① 对于资源(图片、音频、视频等)
- 采取无损压缩
- 去除没有用到的资源:https://github.com/tinymind/LSUnusedResources
② 对于可执行文件
- 编译器优化
Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES(现在的项目已经默认为YES了,一些老项目可能还会为NO)。 - 去掉异常支持
Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions。 - 利用AppCode(https://www.jetbrains.com/objc/)检测未使用的代码
菜单栏 -> Code -> Inspect Code。 - 编写LLVM插件检测出重复代码、未被调用的代码(这种方式比较高级也比较难)。
- 生成LinkMap文件,可以查看可执行文件的具体组成。如下图:
如果项目比较大,分析LinkMap文件就会比较麻烦,我们可以借助第三方工具解析LinkMap文件:https://github.com/huanxsd/LinkMap
它其实是个Mac项目,我们下载下来,运行项目 -> 选择文件 -> 点击开始,就能显示每个文件占用多大,我们就能根据文件有目的性的进行优化。如下图: