iOS性能优化技巧(多贴总结)
一 视图相关
1.1 UIView的透明度
避免对UIView使用透明。(UIView默认是非透明,即alpha = 1)。原因是透明对性能要求较高,如果在滚动时页面比较复杂,体验上的差异会相对明显。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor whiteColor];
// 设置了背景颜色
UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 180, 180)];
blueView.backgroundColor = [UIColor blueColor];
blueView.center = self.view.center;
[self.view addSubview:blueView];
// 没有设置背景颜色
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
redView.center = self.view.center;
[self.view addSubview:redView];
NSLog(@"%f",redView.alpha);
NSLog(@"%@",redView.backgroundColor.description);
// 设置了背景颜色
UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
greenView.backgroundColor = [UIColor greenColor];
greenView.center = CGPointMake(100, 100);
[redView addSubview:greenView];
}
image.png
备注:为什么一个红色视图有
frame
,但是还是不显示,因为其backgroundColor
默认为null
,所以不显示。
1.2避免过于庞大的xib
如果不得不使用一个ViewController作为xib,也应该将其其中的子视图拆成小的xib。
需要注意的是,当你加载一个XIB的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。Storyboards就是另一码事儿了,storyboard仅在需要时实例化一个view controller.
1.3 不要阻塞主线程
- 永远不要使主线程承担过多。因为UIKit在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。
- 大部分阻碍主进程的情形是你的app在做一些牵涉到读写外部资源的I/O操作,比如存储或者网络; 对于许多与UI刷新无关的操作,如创建一些与界面无关的对象等,最好也通过次线程来执行,以及对于页面流畅度要求非常高的页面,避免使用storyboard来创建对象,因其比较消耗资源;
- 对于UIView或者CALayer的
frame
,bounds
,transform
等属性的改变,消耗的资源远大于他们其他的属性改变。
1.4 UIImageView使用合适尺寸的图片
使图片符合UIImageView的尺寸。不要在运行的时候再让UIImageView自行压缩,因为这样会降低运行时的性能。(注:手动压缩图片的方法,在context中使用drawInRect)
1.5 选择合适的数据存储对象
-
Array
使用下标查找较快,但插入和删除较慢。 -
Set
进行插入和删除很快。
1.6 对于Button等控件,采用图片作为其image
在非常需要渲染效率的极端情况下,对于Button等控件,应采用图片作为其image,这样可以减少渲染对性能和时间的消耗,因为图片已经存在,但是这样会增加APP boundle的体积。需要权衡。
1.7 设置UIView的背景图片时
- 如果是整幅图,就采用addSubView一个UIImageView;
- 如果是要重复平铺一个小图,就使用colorWithPatternImage,因为这个函数的设计上就是针对小图的,如果用于整幅大图来做背景,反而会消耗更多内存。
1.8 对于结构复杂的 View,使用 drawRect 自绘而不是从 nib 中载入。
1.9 TableView
重用 cell;减少 cell 初始化的工作量,延迟装载;定制复杂 cell 时,使用 drawRect 自绘;Cache 尽可能多的东西,包括 cell 高度;尽可能让 cell 不透明;避免使用图像特性,比如 gradients。
更多详情请参考 UITableView性能优化技巧
1.10 不要在 viewWillAppear 中做费时的操作。
viewWillAppear: 在 view 显示之前被调用,出于效率考虑,在这个方法中不要处理复杂费时的事情;只应该在这个方法设置 view 的显示属性之类的简单事情,比如背景色,字体等。要不然,用户会明显感觉到 view 显示迟钝。
二 网络相关
2.1 对常用的东西进行缓存。
- 如从网上下载的需要经常显示的图片(这个在许多第三方框架如SDWebImage中都已经使用了);(注:NSURLConnection会自动将HTTP请求返回数据缓存在本地)。
- 对于一些下载下来,明显不需要访问网络再获取的图片,可以直接为其制造一个NSURLRequest,并使这个NSURLRequest仅从缓存读取数据。
- 对于另外一些不涉及HTTP请求地址的数据,可以通过NSCache进行缓存。
2.2 网络优化相关
- 合并一些网络请求,避免频繁访问网络;
- 尝试预加载2-5分钟后的数据,数据控制在1-5MB之间;
- 访问之前,先判断网络是否可用;
三 内存相关
3.1 处理低内存警告
在收到内存警告时,清除对cache的强引用,没有当前显示需要的image,以及一些其他可以再创建的对象。
3.2 使用NSAutoreleasepool
在临时创建大量对象时,人工使用NSAutoreleasepool,例如,一个循环用于创建包含多个对象的数组,在循环体内,即可使用@autoreleasepool
包裹创建代码。
四 其他
4.1 重用一些高消耗的对象
- 如
NSDateFormatter
、NSCalender
等。
解决方法:可以将其作为property、甚至是静态变量作为单例在APP中使用。并且,NSDateFormatter的 setDateFormate也是非常消耗资源的一个操作。
4.2 转换后台返回的数据格式
网络传输过来的数据,往往是json或xml字符串。直接将这些字符串转换成我们需要的数据结构(自定义类或者NSDictionary),避免后续使用的时候还要做数据结构转换产生不必要的消耗。
4.3 UITableView提前计算高度
UITableView的高度有时候会根据内容来自动计算,这种情况比较消耗资源。如果明知有哪几种高度的话,就将高度缓存重用;尽量不要在 cellForRowAtIndexPath:方法中做很多事情。不仅仅重用cell,对于section的header和footer也进行重用。
4.4 UIImageView的创建
对于重复使用的图片使用imageNamed:来创建,对于不需重复使用的,使用imageWithContentsOfFile。
详情请参考我的另外一篇简书iOS-imageNamed与imageWithContentsOfFile的区别
4.5 对于排版复杂的文字或者图文混排,使用CoreText技术。(而不是一味地堆UILabel)
4.6 在对渲染的效率要求较高的页面中,避免使用UILabel、UITextView等在主线程中进行排版和绘制的控件。应自定义文本控件,用TextKit或者CoreText进行文本异步绘制。另外,还有facebook的AsyncDisplayKit框架可以采用。
4.7 将绘制图像放在次线程中执行,如在次线程中使用 CGContext进行画图,在主线程中 layer.contents = img。
4.8 图片和视图的大小避免超过4096*4096,因为这是目前iphone5到iphone6p以及ipad仅仅通过GPU就直接处理的纹理尺寸上限,否则就GPU就会提交CPU先处理,这样开销很大。
4.9 减少视图或者layer的层级数量,在有多个层级时,可以将多图合并成一张图,再渲染显示。
4.10 节能相关:GPS在获取用户位置之后,就进行关闭,因为它非常耗电。
4.11 避免浮点运算。
4.12 避免使用NSString的stringWithFormat:,采用asprintf的C函数结合NSString的 stringWithString:来实现。
4.13 关于后台运行
进入后台后,即尽量减少内存占用、释放所有的共享资源(如Calender或address book),因为iOS会kill后台中内存消耗最多的或者进入后台还占用共享资源的进程。
4.14 直接使用IMP
调用方法
如果一个方法在一个循环次数非常多的循环中使用,在进入循环前使用 methodForSelector 获取该方法 IMP,然后在循环体中直接使用该 IMP。
4,15 提高 APP 加载速度
避免使用静态初始化,包括静态c++对象,加载时会运行的代码,如+(void) load{} ,会造成在Main函数之前运行额外的代码。
4.16 利用 cache 空间换时间
cache 是一种常见的空间换时间的提供性能的收到,可以用在相当多的场合。尽量 cache 那些可重复利用的对象,比如 table cell,date/number formatters,正则表达式,sqlite语句等。
4.17 关于数据库
缓存经常用到的 sqlite 语句;优化数据库查询语句,用sqlite3_trace和sqlite3_profile来查找性能差的语句;如果可能的话,缓存查询结果缓。 在使用 sqlite_prepare会将SQL查询编译成字节码,要使用bind,重用那些已经prepared的语句。
五 关于后台任务
系统进入 background 之后,一般只有10分钟的运行时间,因此有很多值得注意的事项:
- 尽量减少内存的使用。当内存不足时,iOS将kill那些消耗内存最多的 App。
- 释放所有的共享资源,比如
Calendar
与Address book
。当应用程序进入后台时,如果它还在使用或没有释放共享资源,iOS会立即kill掉该应用程序。 - 正确处理App生命周期事件。
- 当进入后台时,应该保持应用程序数据,以便回到前台时能够恢复。
- 当进入 inactive 状态时,应该暂停当前的业务流。iOS运行App在后台运行的时间有限,因此后台代码不应该执行非常耗时的任务,可能的话就使用多线程。
- 当进入后台时,iOS会保存当前App的一个快照,以便之后在合适的时候(装载view和数据时)呈现给用户以提高用户体验,因此在进入后台时,应该避免在屏幕上呈现用户信息,以免泄露用户个人资料。
- 不要更新UI或者执行大量消耗CPU或电池的代码。进入后台之后,不应该执行不必要的任务,不要执行 OpenGL ES 调用,应取消 Bonjour 相关的服务,正确处理网络链接失败,避免更新 UI,清除所有的警告或其他弹出对话框。
- 保证后台代码的执行工作正常,注意处理异常。
- 在后台时正确响应系统变化。
- 设备旋转消息UIDeviceOrientationDidChangeNotification
- 重要的时间变化(新的一天开始或时区变化)UIApplicationSignificantTimeChangeNotification
- 电池变化UIDeviceBatteryLevelDidChangeNotification 和 UIDeviceBatteryStateDidChangeNotification,
- 用户默认设置变化NSUserDefaultsDidChangeNotification
- 本地化语言变化NSCurrentLocaleDidChangeNotification 等
本文会持续更新,并且进行完善,配备相应的代码例子,敬请期待。
本文参考(多帖总结) iOS性能优化技巧
- 如有错误,欢迎指正,多多点赞,打赏更佳,您的支持是我写作的动力。
更多优秀文章推荐
1.iOS 保持界面流畅的技巧
2.深入浅出Cocoa-iOS程序性能优化
3.25 iOS App Performance Tips & Tricks