1、尽可能的使用reuseIdentifier
尽可能的使用系统已经实现的重用机制。如UITableViewCell、UICollectionViewCell、UITableViewHeaderFooterView等;
为了性能最优化,table view用 tableView:cellForRowAtIndexPath: 为rows分配cells的时候,它的数据应该重用自UITableViewCell。 一个table view维持一个队列的数据可重用的UITableViewCell对象。不使用reuseIdentifier的话,每显示一行table view就不得不设置全新的cell。这对性能的影响可是相当大的,尤其会使app的滚动体验大打折扣。
2、尽量把view设置为完全不透明
opaque属性尽可能设置为YES。
这个属性给渲染系统提供了一个如何处理这个view的提示。如果设为YES, 渲染系统就认为这个view是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为NO,渲染系统需要结合上层view的色值和透明度,更为复杂的计算当前view的颜色值等,复杂的计算和渲染必然导致性能的下降,尤其是滚动视图的情况。
3、避免过于庞大的XIB
首先,xib的类被初始化,是作为nib文件被加载到内存中的,尤其是当XIB有引用大量图片或音频资源的情况下,大量文件被加载到内存中必然导致性能的下降。所以,一是尽可能避免XIB的使用,二是不可避免的情况下,尽可能减小XIB文件的大小或者说复杂度
4、不要阻塞主线程
UIKit层的所有任务一定在主线程,包括UI渲染,触摸点击等事件响应等,长时间的耗时操作,诸如网络请求、本地数据库操作等如果放在主线程,必然会导致UIKit层的响应变慢或者延迟,会直接影响到用户的感受,所以耗时操作可另外开辟线程执行,如GCD和NSOperationQueue
5、圆角图片
- 直接设置imageV.layer.cornerRadius和imageV.layer.masksToBounds = YES必然会导致离屏渲染,大量使用圆角图片的前提下,这种方法不可取
- 使用CAShapeLayer和UIBezierPath设置圆角,在设置好image的imageView上绘制一层圆形遮罩,此方法类似设置masksToBounds = YES,不可取
UIImageView * imageview = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 150, 150)];
imageview.center = self.view.center;
imageview.image = [UIImage imageNamed:@"meinv"];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageview.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//设置大小
maskLayer.frame = imageview.bounds;
//设置图形样子
maskLayer.path = maskPath.CGPath;
imageview.layer.mask = maskLayer;
[self.view addSubview:imageview];
- 使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角图片,可取,没有离屏渲染
UIImageView * imageview = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 150, 150)];
imageview.center = self.view.center;
imageview.image = [UIImage imageNamed:@"meinv"];
//开始对imageView进行画图
UIGraphicsBeginImageContextWithOptions(imageview.bounds.size, TRUE, 0);
//使用贝塞尔曲线画出一个圆形图
[[UIBezierPath bezierPathWithOvalInRect:imageview.bounds] addClip];
[imageview drawRect:imageview.bounds];
imageview.image = UIGraphicsGetImageFromCurrentImageContext();
//结束画图
UIGraphicsEndImageContext();
[self.view addSubview:imageview];
6、重用和懒加载views
更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了很多view在UIScrollView里边的app更是如此;
这里我们用到的技巧就是模仿UITableView和UICollectionView的操作: 不要一次创建所有的subview,而是当需要时才创建,当它们完成了使命,把他们放进一个可重用的队列中。
7、正确使用缓存
原则:缓存所需要的,也就是那些不大可能改变但是需要经常读取的东西。诸如,远端服务器的响应,图片,甚至计算结果,比如UITableView的行高。
为此,你可以借助工具或者第三方,避免不必要的多次请求和计算,如SDWebImage内部实现了图片的缓存,UITableView-FDTemplateLayoutCell缓存UITableView的行高;
8、权衡渲染方法
对于简单的视图和动画,通常我们使用UIView就可以完成,但是对于一些较为复杂的视图和动画,我们不得不动用Core Graphics层的绘图和动画机制。这个时候,我们就要根据情况,鉴于性能和效果两个方面去考虑,找到更适合的处理方式
9、处理内存警告
一旦系统内存过低,iOS会通知所有运行中app。在官方文档中是这样记述:
如果你的app收到了内存警告,它就需要尽可能释放更多的内存。最佳方式是移除对缓存,图片object和其他一些可以重创建的objects的strong references。
幸运的是,UIKit提供了几种收集低内存警告的方法:
1、在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
2、在你的自定义UIViewController的子类(subclass)中覆盖didReceiveMemoryWarning
3、注册并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
一旦收到这类通知,你就需要释放任何不必要的内存使用。
例如,UIViewController的默认行为是移除一些不可见的view, 它的一些子类则可以补充这个方法,删掉一些额外的数据结构。一个有图片缓存的app可以移除不在屏幕上显示的图片。
这样对内存警报的处理是很必要的,若不重视,你的app就可能被系统杀掉。
然而,当你一定要确认你所选择的object是可以被重现创建的来释放内存。一定要在开发中用模拟器中的内存提醒模拟去测试一下。
10、重用大开销对象和重复使用的对象
对于一些初始化很慢,而且又经常使用的对象,最好设置成类的属性或者封装成单例(整个app只会创建一次),比如NSDateFormatter和NSCalendar,百度地图中常用的定位BMKLocationService对象
下面,使用延迟加载来实现重用
@property (nonatomic, strong)NSDateFormatter *dateFormat;
//在第一次使用的时候才会创建,后来再使用就用第一次创建好的对象,不会多次创建
-(NSDateFormatter *)dateFormat{
if (!_dateFormat) {
_dateFormat = [[NSDateFormatter alloc]init];
_dateFormat.dateFormat = @"YYYY-MM-DD HH:mm:ss";
}
return _dateFormat
}
11、尽量使用WebKit
UIWebView是基于移动版的Safari的,所以它的性能表现十分有限。特别是在对几乎每个Web应用都会使用的JavaScript,表现的尤为糟糕。而iOS8引入的WebKit框架使得开发者可以在原生App中使用Nitro来提高网页的性能和表现,Nitro就是Safari的JavaScript引擎。WKWebView保证在滑动时保持60的帧率,同时具有KVO,内建手势,以及在App和网页之间的原生交流方式。
12、选择正确的数据存储选项
iOS的本地数据存储方式有:
- NSUserDefault
- XML, JSON, 或者 plist
- NSCoding归档
- SQLite
- Core Data
首先,NSUserDefault很好用,但只适用于小数据,类似简易的字段值;
XML和JSON这种结构化归档,还需要把数据读到内存中去解析,NSCoding也需要归档和解档配合解析数据,中间过程过于复杂,特别是大数据的情况就更不适合了。
而SQLite和CoreData这种都能实现数据库式的存储,只要通过特定的语句就能实现数据的读写,唯一不同的是CoreData存储过程中多了一层映射,可以直接操作Object,而SQLite操作的是单个的原始数据,而实际存储后的结果都是DBMS
13、加速启动时间
从点击AppIcon启动应用,到第一个画面出现,应用的启动时间,直接影响用户对一款应用的判断和使用体验。关于启动时间和如何优化
简而言之:t(App总启动时间) = t1(main()之前的加载时间) + t2(main()之后的加载时间)。
对于main()调用之前的耗时我们可以优化的点有:
1、减少不必要的framework,因为动态链接比较耗时
2、check framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查
3、合并或者删减一些OC类,关于清理项目中没用到的类,使用工具AppCode代码检查功能
4、删减一些无用的静态变量
5、删减没有被调用到或者已经废弃的方法。方法见:
http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7 https://developer.Apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html
6、将不必须在+load方法中做的事情延迟到+initialize中
7、尽量不要用C++虚函数(创建虚函数表有开销)main()调用之后的加载时间
在main()被调用之后,App的主要工作就是初始化必要的服务,显示首页内容等。App通常在AppDelegate类中的- (BOOL)Application:(UIApplication )Application didFinishLaunchingWithOptions:(NSDictionary )launchOptions方法中创建首页需要展示的view,然后在当前runloop的末尾,主动调用CA::Transaction::commit完成视图的渲染。而视图的渲染主要涉及三个阶段:
** 准备阶段 这里主要是图片的解码
** 布局阶段 首页所有UIView的- (void)layoutSubViews()运行
** 绘制阶段 首页所有UIView的- (void)drawRect:(CGRect)rect运行
再加上启动之后必要服务的启动、必要数据的创建和读取,这些就是我们可以尝试优化的地方
因此,对于main()函数调用之后我们可以优化的点有:
1、不使用xib,直接视用代码加载首页视图
2、NSUserDefaults实际上是在Library文件夹下会生产一个plist文件,如果文件太大的话一次能读取到内存中可能很耗时,这个影响需要评估,如果耗时很大的话需要拆分(需考虑老版本覆盖安装兼容问题)
3、每次用NSLog方式打印会隐式的创建一个Calendar,因此需要删减启动时各业务方打的log,或者仅仅针对内测版输出log
4、梳理应用启动时发送的所有网络请求,是否可以统一在异步线程请求
14、使用Autorelease Pool
NSAutoreleasePool负责释放block中的autoreleased objects。一般情况下它会自动被UIKit调用。但是有些状况下你也需要手动去创建它。
当在项目中处理数据等需要很多临时变量的时候,你会发现内存持续增长,只有当这些临时变量release的时候,内存才有所缓解;我们知道每创建一个对象,他会被添加到距离最近的Autoreleasepool中,所以,我们可以自己创建AutoreleasePool,使这些临时变量可以尽早被释放。
//每次遍历autorelease pool都会释放里面的变量
NSArray *urls = <# An array of file URLs #>;
for(NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
15、用正确的方式加载图片
常见的从bundle中加载图片的方式有两种,一个是用imageNamed,二是用imageWithContentsOfFile,第一种比较常见一点。
既然有两种类似的方法来实现相同的目的,那么他们之间的差别是什么呢?
imageNamed的优点是当加载时会缓存图片。imageNamed的文档中这么说:
这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。
相反的,imageWithContentsOfFile仅加载图片。
下面的代码说明了这两种方法的用法:
UIImage *img = [UIImage imageNamed:@"myImage"];// caching
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching
那么,对于占用内存大且一次性使用的,就使用不会缓存的imageWithContentsOfFile,常用的小图片或者icon就用会缓存的imageNamed;
16、尽可能避免日期格式转换
NSDateFormatter的创建和设置都是很耗时的操作,用日期字符串转日期的情况也很常出现。提高效率的其中一个方法是重用NSDateFormatter对象,那如果需要不同样式的NSDateFormatter呢?
还有一个方法,尽量使用Unix时间戳,NSDate有直接从时间戳转换为日期的方法
- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
return [NSDate dateWithTimeIntervalSince1970:timestamp];
}
17、tableView的优化系列
1、行高rowHeight的缓存UITableView+FDTemplateLayoutCell
2、有图片的cell复用时,加载图片的方式做优化,例如圆角处理,较大的图片滑动过程中的加载方式 cell图片加载优化
3、Color Blend Layers(图层渲染,避免复合图层和图层透明) + Color Hits Green and Misses Red(光栅化shouldRasterize = YES, 预先渲染成位图并缓存,导致离屏渲染) + Color Misaligned Images(避免图片缩放) + Color OffScreen-Rendered Yellow(离屏渲染,圆角maskToBound = YES导致,合理处理圆角)
4、参考文章 UIKit优化
18、能用CAShapeLayer实现的就不要用drawRect重绘
原因分析: http://blog.csdn.net/sandyloo/article/details/51063799
- 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
- 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
- 不会被图层边界剪裁掉。
- 不会出现像素化。
未完待续。。。。。。