iOS 项目优化
前言
- 不要提前过度优化
- 要找到性能瓶颈
- 要在不同性能指标间权衡
- 要理解优化任务的底层运行机制
- 要有技术保障体系
一、启动速度优化
1.1 学习文章
- WWDC 启动速度优化视频 Session 406 Optimizing App Startup Time
- iOS性能(二) 启动时间优化
1.2 操作步骤
查看启动时间
配置 Xcode 环境变量在日志中打印启动时间:
- 打开工程 -> Edit Scheme -> Run -> Environment Variables
根据需要添加
DYLD_PRINT_STATISTICS
和DYLD_PRINT_STATISTICS_DETAILS
环境变量。value 设置为 1(表示 YES),开启这个功能。Total pre-main time: 617.58 milliseconds (100.0%)
dylib loading time: 472.75 milliseconds (76.5%)
rebase/binding time: 27.01 milliseconds (4.3%)
ObjC setup time: 28.90 milliseconds (4.6%)
initializer time: 88.76 milliseconds (14.3%)
slowest intializers :
libSystem.B.dylib : 8.81 milliseconds (1.4%)
libMainThreadChecker.dylib : 14.42 milliseconds (2.3%)
AFNetworking : 18.43 milliseconds (2.9%)
Realm : 20.98 milliseconds (3.3%)
CYKJBasic : 12.96 milliseconds (2.0%)
包括执行以下步骤的所有时间:
- 解析镜像
- 映射镜像
- Rebase 镜像
- Bind 镜像
- 镜像初始化
- 调用 main()方法
- 调用 UIApplicationMain() 方法
- 调用 applicationWillFinishLaunching 回调
优化
main()
函数之后的执行时间- 使用代码绘制 UI,减少或者不用 xib 和 storyboard
- 延迟初始化和加载不必要的 UIViewController 和 View。
- 使用后台线程处理耗时的任务
- 能延迟初始化的尽量延迟初始化
优化
main()
函数之前的执行时间Session 所要传达的内容:
- 使用 DYLD_PRINT_STATISTICS 测试启动加载时间
- 减少自定义的动态库集成
- 精简原有的 Objective-C 类和代码
- 移除静态的初始化操作
- 使用更多的 Swift 代码
优化:
loading dylib
减少动态库的数量。尽量保证将 App 现有的非系统级的动态库个数保证在 6 个以内。采取的方式:1、合并动态库;2、使用静态库。
rebase/binding
Rebase 和 Binding 都是为了解决指针引用的问题。对于 Objective-C 开发来说,主要的时间消耗在 Class/Method 的符号加载上,所以常见的优化方案是:
- 减少 __DATA 段中的指针数量。
- 合并 Category 和功能类似的类,减少唯一 Selector 的个数。
- 删除无用的方法和类。
- 多用 Swift Structs,因为 Swfit Structs 是静态分发的。
- 减少 c++ 虚函数
ObjC setup time
尽量减少类的数量,可以达到减少这一部分的时间。
Initializers
- 用 initialize 替代 load。(注意:要根据实际情况替换)
- 减少使用 c/c++ 的 __atribute__((constructor))。推荐使用 dispatch_once()、pthread_once()、std:once()等方法;
- 推荐使用 swift
- 不要在初始化中调用 dlopen() 方法,因为加载过程是单线程,无锁,如果调用 dlopen 则会变成多线程,会开启锁的消耗,同时有可能死锁
- 不要在初始化中创建线程
1.3 实际处理
实际处理时因为要考虑团队的原因,所以只采用了一下三个步骤:
- 删除不需要的三方库,因为工程中用 use_frameworks! pod 进来的库是动态库。
- 将大部分 +load 方法改为 +initialize 方法,保留部分必要的 +load 方法;
- 动态库 -> 静态库,技术上实现了,讨论后最终弃用。
- 删除无用的方法和类,减少 rebase/binding 时间。。
1.4 处理效果
慈云 app 启动时间有 700 ~ 850ms,降为 600 ~ 700ms。
如果采用静态方式 pod,可以降为 450 ~ 550ms。
二、接口请求优化
主要集中处理一级 tab 的切换体验,频繁的调用接口,频繁的刷新界面显然是影响用户体验的。优化的思路有以下几点:
使用 loading 框 + 默认灰色矩形视图;
使用本地缓存
每隔 15s(或者 10s) 以上才请求一次,防止频繁触发请求
@property (nonatomic, assign) CFTimeInterval lastTi;
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CFTimeInterval nowTi = CACurrentMediaTime();
// 10 秒内不请求
if (nowTi - self.lastTi > 10) {
self.reqData.pageNo = 1;
[self.service requestPatientConsultList:self.reqData];
self.lastTi = nowTi;
}
}
- (void)makeRequestAction
{
// 接口请求
}
CACurrentMediaTime() 在退到后台、手动修改设备时间后没有影响。
对数据进行判断,数据没有更新不需要刷新界面。
@property (nonatomic, copy) NSString * primaryCompareMD5;
// 初始值 @""
self.primaryCompareMD5 = @"";
SELF_WEEK;
// 异步执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
// 这里的 data 是要判断的接口数据
NSString * string = [data componentsJoinedByString:@""];
if (!string) {
string = @"";
}
NSString * md5 = [CYDXUtil doMd5:string];
SELF_STRONG;
// 数据未发生改变,直接返回
if ([strongSelf.primaryCompareMD5 isEqualToString:md5]) {
return;
}
strongSelf.primaryCompareMD5 = md5;
// 主线程刷新界面
dispatch_sync(dispatch_get_main_queue(), ^{
[strongSelf.tableView reloadData];
});
}
});
这里的策略还是存在点问题:
- 不能处理好未读标识。看实际情况,处理从子界面返回时的逻辑。
三、界面滑动优化
3.1 监测界面卡顿工具
- 使用系统的 Instrument - CoreAnimation
- 使用系统的 Instrument - Time Profiler
- YYFPSLabel。
在未滑动时,Instrument - CoreAnimation 显示的是 0 FPS。
YYFPSLabel 显示的是 60FPS。
卡顿问题修改后,Instrument - CoreAnimation 需要在设备上运行一遍,然后重新用工具检测;YYFPSLabel 可以直接加入工程,可视化,更加的友好,方便。
Time Profiler 可以查看哪些方法占用时间多,优化那些可以优化的。
3.2 优化处理
图层混合
Xcode 顶部菜单栏 -> Debug -> View Debugging -> Rendering -> Color Blended Layers
正常的为绿色;出现图层混合时为红色。
- 确保控件的 opaque 属性设置为 true,确保 backgroundColor 和父视图颜色一致且不透明;
- 如无特殊需要,不要设置低于 1 的 alpha 值;
- 确保 UIImage 没有 alpha 通道;
图片缩放
Xcode 顶部菜单栏 -> Debug -> View Debugging -> Rendering -> Color Misaligned Images
如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。
这个看情况是否优化,是否需要 UI 配合。
- 确保图片大小和frame一致,不要在滑动时缩放图片;
- 确保图片颜色格式被 GPU 支持,避免劳烦 CPU 转换;
离屏渲染
Xcode 顶部菜单栏 -> Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow
触发离屏渲染的地方标记为黄色。
下面的情况或操作会引发离屏渲染:
- 为图层设置遮罩(layer.mask)
- 同时设置 layer.masksToBounds 和 corneRadius 属性设置为 true
- 将图层的 layer.allowsGroupOpacity 属性设置为 YES 和 layer.opacity < 1.0
- 为图层设置阴影(layer.shadow*)
- 为图层设置 layer.shouldRasterize = true
- 文本(任何种类,包括 UILabel、CATextLayer、CoreText 等)
- 使用 CGContext 在 drawRect: 方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现
解决:
- 使用 CoreGraphics 绘制圆角,不使用 mask 或 masksToBounds + corneRadius;
- 设置阴影是使用 shadowPath;
- UILabel 如果不是透明的,设置 opaque = YES,Clip To bounds = YES,backgroundColor;
- 设置图片圆角可以让 UI 切图,也可以使用 CoreGraphics 绘制图片的方式。在 iOS 9.0 以上,图片设置圆角不会触发离屏渲染。
UITableViewCell 优化
- Cell 复用;
- 提前计算并缓存 Cell 的高度;
- 减少 subviews 的个数和层级;
- 少用 subviews 的透明图层;
- 如果不是透明视图,背景色不要使用 clearColor;
- 注意离屏渲染问题;
- 图片提前在子线程异步解码,SDWebImage 已经实现;
- 异步绘制(自定义 Cell 绘制)VVeboTableViewDemo、YYAsyncLayer、AsyncDisplayKit
- 滑动时,按需加载。注意:这个会导致滑动时出现大量空白,不友好。
- 尽量显示“大小刚好合适的”图片资源
- 避免同步的从网络、文件获取数据
四、内存占用
主要检查工程中的内存泄露、循环引用的问题。
- instrument - Allocations 动态分析
- Analyze 静态分析
- 腾讯 MLeaksFinder
- 脸书 FBMemoryProfiler
说明:
- MLeaksFinder 效果比较好,能查找工程中出现循环引用的的场景,在慈云找到十几处循环引用。
- 官方的 instrument 工具效果一般,在慈云 app 只检测出了 3 处循环引用,2 处内存泄露;
- Analyze 静态分析可检测出“创建了但未被使用的”变量,需要结合代码逻辑,小心处理。
- 脸书的 FBMemoryProfiler 工具不大好用,主要是记录内存的开辟与销毁。
如果要更多的处理内存占用问题,需要分析工程中的代码逻辑,通过使用不同的存储方式、容器类、加载数据方式等,达到减少内存使用的目的。
五、缩小 ipa 包大小
安装包大小的优化,主要包含两大块:资源大小的优化和二进制大小的优化。
5.1 资源大小的优化
资源大小的优化主要包括以下几个方面:
- 资源压缩
- 未使用、重复资源的删除
- 资源上云
具体步骤:
资源压缩
Xcode 的编译选项中,提供了
Compress PNG Files
及Remove Text MetaData From PNG Files
但是由于 PNG 是无损压缩,经过 Xcode 压缩后的图片资源,依然很大。
使用 pngquant 对大多数的 32 位图进行了处理,将其转为 8 位图,并且使用 Zopfli 进行了压缩,这样整体的 PNG 图片资源大概被压缩了 70% 左右。这里要注意,由于一些渐变背景的颜色覆盖范围较大,转为 8 位图颜色丢失较大,表现效果会差很多,所以这些图片要谨慎处理。
未使用、重复资源的删除
资源上云
资源上云可以有效减少包内资源,唯一要注意的是这些资源由于是 lazy load,所以比较适合层级较深的页面使用。
5.2 其他处理
配置编译选项 Generate Debug Symbols 设置为 NO;
舍弃架构,如:armv7,根据实际选择。
编译的版本必须是 release 版本,
查找内部使用到的第三方库,一方面可以进行删减代码,用不到的类,直接删除,还有第三方库中的图片资源统统删除掉,如果能够自己手写实现的,那费功夫自己写吧
六、引用库升级及替换
持续更新的第三方库也会对性能做出优化,在替换前,需先确定不会给项目引入问题。
七、更多优化
iOS 性能调试
iOS视图成像理论及性能优化
iOS性能优化系列篇之“列表流畅度优化”
iOS 项目优化的更多相关文章
- ios项目里扒出来的json文件
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000 } p.p2 { margin: 0.0px 0. ...
- iOS项目的本地化处理(多国语言)
项目的本地化就是:iOS系统在不同语言环境下自动切换语言,从而实现一个app发布到全世界各个国家的AppStore上. 我们不仅仅需要在iOS项目中做本地化处理,在上架iOS APP的时候,也需要做对 ...
- 【腾讯Bugly干货分享】微信读书iOS性能优化
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578c93ca9644bd524bfcabe8 “8小时内拼工作,8小时外拼成长 ...
- iOS性能优化:Instruments使用实战
iOS性能优化:Instruments使用实战 最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instrument ...
- IOS 性能优化的建议和技巧
IOS 性能优化的建议和技巧 本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelo ...
- IOS 项目问题总结
把自己项目中遇到的问题总结一下,供大家参考,希望大家多多提出意见!! 在Xcode 6.2中遇到Your build settings specify a provisioning profile w ...
- 深入浅出聊Unity3D项目优化:从Draw Calls到GC
前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...
- Unity3D项目优化(转)
前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...
- 检查iOS项目中是否使用了IDFA
(1)什么是IDFA 关于IDFA,在提交应用到App Store时,iTunes Connect有如下说明: 这里说到检查项目中是否包含IDFA,那如何来对iOS项目(包括第三方SDK)检查是否 ...
随机推荐
- DotNet Core 使用 StackExchange.Redis 简单封装和实现分布式锁
前言 公司的项目以前一直使用 CSRedis 这个类库来操作 Redis,最近增加了一些新功能,会存储一些比较大的数据,内测的时候发现其中有两台服务器会莫名的报错 Unexpected respons ...
- 一行python代码搞定文件分享
给同事分享文件,如你所知通过聊天工具,网盘或linux命令各种方法,还有一个也可以尝试下:使用一行python代码快速搭建一个http服务器在局域网内进行下载. python3使用: python3 ...
- 一文看懂js中元素的滚动大小(scrollWidth,scrollHeight,scrollTop,scrollLeft)
滚动大小(scroll dimension) 滚动大小指的是包含滚动内容元素的大小. 以下是与元素滚动内容大小相关的属性: 1. scrollWidth:在没有滚动条的情况下,元素内容的总宽度. 2. ...
- css中grid属性的使用
grid布局 加在父元素上的属性 grid-template-columns/grid-template-rows 定义元素的行或列的宽高 如果父元素被等分成了9等分,则,不管有多少个子元素,都显示9 ...
- 一份简明的 Base64 原理解析
书接上回,在 记一个 Base64 有关的 Bug 一文里,我们说到了 Base64 的编解码器有不同实现,交叉使用它们可能引发的问题等等. 这一回,我们来对 Base64 这一常用编解码技术的原理一 ...
- day05基本运算符,格式化输出,垃圾回收机制
内容大纲:1.垃圾回收机制详解(了解) 引用计数 标记清除 分代回收 2.与用户交互 接收用户输入 # python3中 input # python2.7(了解) input raw_input 格 ...
- ES6的原始类型数据——Symbol
javascript中原始值,即基本数据类型,像Number,String,Boolean,undefined,Null都是基本类型值,保存在栈中,但是有个疑问: Symbol打印出来明明是个函数,具 ...
- selenium 操作 获取动态页面数据
# selenium from selenium import webdriver import time driver_path = r"G:\Crawler and Data\chrom ...
- Python——项目-小游戏
开始我们的项目 飞机大战 1 项目的初体验 以及前期准备 游戏初体验画面 验证一下本地第三方包有没有导入 python3 -m pygame.examples.aliens 如果没有出现游戏画面请先安 ...
- 问题描述:判断一个整数 n 是否为 2 的幂次方
一.2的幂次方的基本定义 什么样的数为2的幂次方?例如2^0=1,2^1=2,2^2=4……,符合公式2^n(n>=0)的数称为2的幂次方. 如何判断一个数是否为2的幂次方呢?基本思路:把一个数 ...