解决方案
1. 正确使用reuseIdentifier
2. 不要阻塞主线程
3. 重用和延迟加载Views
4. 缓存不容易修改且利用率高的资源
5. 重用开销大的对象
6. 选择正确的方式加载本地图片
7. 使用WKWebView替代UIWebView
8. UITableView 性能优化
9. 处理内存警告
10. 用户体验提升
1. 正确使用reuseIdentifier
- UITableView中UITableViewCell重用
//1. 注册(Cell 为纯代码) [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell_Reuse"]; //2. 注册(Cell 包含Nib 文件) [tableView registerNib:[UINib nibWithNibName:@"TalkCell" bundle:nil] forCellReuseIdentifier:@"TalkCell_Reuse"]; // 根据reuseIdentifier获取 [tableView dequeueReusableCellWithIdentifier:@"TalkCell_Reuse" forIndexPath:indexPath];
- UITableView中UITableViewHeaderFooterViews的重用,
UITableViewHeaderFooterViews为Section的Header或者Footer。// 注册 [tableView registerClass:[UITableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:@"UITableViewHeaderFooterView_Reuse"]; // 根据reuseIdentifier获取 [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"UITableViewHeaderFooterView_Reuse"];
- UICollectionView中UICollectionViewCell重用,同UITableViewCell重用类似
- UICollectionView中Supplementary Views重用(追加视图)类似于UITableView中Section的Header或者Footer.
2. 不要阻塞主线程
主线程负责UI更新,渲染,用户交互处理等事件,为了不阻塞主线程,除此之外的一些复杂和耗时的操作必须放在子线程中去执行。常见的如获取网络资源。
//阻塞主线程
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.baiu.com"]];
//解决方案
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.baiu.com"]];
});
3. 重用和延迟加载(lazy load)Views
更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了很多view在UIScrollView里边的app更是如此。
- 常见的广告图浏览,或者图片放大查看器可仿照UITableView或者UICollectionView进行重用,只创建有效数量的View,减少CPU和内存消耗。
- 不要一次创建所有的subview,而是当需要时才创建(lazy load/懒加载)。如一个筛选器(当点击筛选按钮才会弹出筛选界面)在效率和内存方面都得到提升。
4. 缓存不容易修改且利用率高的资源
一个极好的原则就是,缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。
我们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果。
- 网络图片的缓存,如SDWebImage
- UITableView Cell 的行高,或者Header/Footer行高等
- 数据处理结果。
5. 重用开销大的对象
一些对象的初始化很慢,比如NSDateFormatter和NSCalendar。然而,你又不可避免地需要使用它们。那么可以把它作为一个属性或者是单例来使用。但作为单例会一直占用内存。
6. 选择正确的方式加载本地图片
- 加载经常使用的小图片
特点:这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。[UIImage imageNamed:@"hello.png"];
优点:因为有缓存,第二次加载同一张图片,则速度更快。
缺点:有缓存,更多的内存消耗。 - 不经常使用,或者大图片:
特点:每次都是找到对应路径的图片之后再加载图片,无缓存。//图片路径 NSString *path = [[NSBundle mainBundle]pathForAuxiliaryExecutable:@"hello.png"]; //根据路径加载图片 [UIImage imageWithContentsOfFile:path];
7. 使用WKWebView替代UIWebView
iOS8以后,苹果推出了新框架Wekkit,提供了替换UIWebView的组件WKWebView。各种UIWebView的问题没有了,速度更快了,占用内存少了,一句话,WKWebView是App内部加载网页的最佳选择!
8. UITableView 性能优化
- 正确使用reuseIdentifier来重用cells及Section 的Header 和Footer
- 如果行高是变化的,则缓存行高,如cell,Section的Header和Footer的行高。
- 如果行高是固定的,使用使用tableView,rowHeight,
sectionFooterHeight 和 sectionHeaderHeight来设定固定的高,不用实现代理方法。 - 如果cell内现实的内容来自web,使用异步加载,缓存请求结果,如图片等
- Cell数据资源的缓存,避免重复处理数据。如富文本的多样化显示(NSString->NSAttributeString),时间的多样化现实(2016-01-08 20:40:40 -> 9个月前)
9. 处理内存警告
一旦系统内存过低,iOS会通知所有运行中app。在官方文档中是这样记述:
如果你的app收到了内存警告,它就需要尽可能释放更多的内存。最佳方式是移除对缓存,以及一切可再生资源.
幸运的是,UIKit提供了几种收集低内存警告的方法:
- 在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
- 在你的自定义UIViewController的子类(subclass)中覆盖
didReceiveMemoryWarning - 注册并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
一旦收到这类通知,你就需要释放任何不必要的内存使用。
例如,UIViewController的默认行为是移除一些不可见的view, 它的一些子类则可以补充这个方法,删掉一些额外的数据结构。一个有图片缓存的app可以移除不在屏幕上显示的图片。
这样对内存警报的处理是很必要的,若不重视,你的app就可能被系统杀掉。
一定要在开发中用模拟器中的模拟内存警告。
10. 用户体验提升
本地缓存一些用户经常浏览的数据,实现离线浏览功能,如微信的朋友圈,微博的首页等。
- 使用NSUerDefaults
- Plist文件
- 使用NSCoding存档
- 使用类似SQLite的本地SQL数据库
- 使用 Core Data
NSUserDefaults只适用于小数据,比如一些简单的布尔型的设置选项。
Plist文件只支持一些简单的系统类,如NSDictionary,NSArray,NSString等。其他类就不能用此方法。
NSCoding它需要读写文件,当储存大块数据或者要支持查询部分数据,它就没有任何优势了。
当存储大块数据时,以上的方法都不适用. 在这种应用场景下,使用SQLite 或者 Core Data比较好。使用这些技术你用特定的查询语句就能只加载你需要的对象。
SQLite是iOS内嵌的数据库,使用C语言的,用SQL语句进行数据操作。
CoreData基于SQLite封装,OC语言,底层存储数据的依然是SQLite。