iOS 应用性能调优(进阶)

1,cell如果是用xib创建的,一定不能忘记在xib中填写reuseIdentifier,否则不会重用

2,cell的高度如果是根据内容计算的,则可以将计算的高度缓存到indexPath.row对应的字典中,数据源没有变化的时候不需要重新计算。

3,cell中的头像设计喜欢加圆角,如果用系统的方式.layer.maskToBounds和.cornerRadius会有离屏渲染,如果加圆角过多,甚至还会引起屏幕卡顿,所以用裁剪的方式把图片裁剪成圆角显示。

4,cell中图片都使用与imageView大小最匹配的大小,避免加载大图片耗费系统资源渲染。

5,cell中尽量不设置阴影效果,如果要设置,用图片的方式实现,系统的shadow会涉及离屏渲染,降低效率

6,coredata增加表,删除表,增加,删除变量都不需要新建版本,可以用他的自动轻量级迁移来实现版本迁移,但是如果变量类型有变化的话需要新建一个版本,并写map文件进行不同类型之间的迁移。

7,如果需要用到循环创建view的话,重用的时候不要移除原view重新创建,而是要通过设置tag值,通过tag值取view的操作来复用view,

8,所有无关于UI刷新的任务都尽可能放到子线程去执行,当线程需要不断执行任务时可设置runloop执行,避免线程的频繁创建和销毁。

9,内存是app优化中很重要的一部分,需要注意内存泄漏,以及内存缓存的清空,关于内存泄漏的问题可看我另一篇文章《解决iOS开发中app的内存泄漏》

10,当界面过于复杂,比如短租中的房源详情,这时候懒加载的tableViewCell可能会引起第一次滑动时界面卡顿现象,也就是当界面元素过多时懒加载不但不会起到节省资源,用到时才创建的目的,反而会导致UI资源同一时间大规模加载,造成卡顿现象,这对追求极致的开发体验来说是非常有影响的,可以用scrollView在viewDidLoad的时候就全部创建出来,这样才不会卡顿界面。

11,NSTimer默认加到runloop的default模式,在跟随模式下不会执行回调,其设计理念我认为就是为了使滑动时保持流畅,所以除非必要,不要轻易将NSTimer的runllop模式该外common模式。

12,用instrument调试后,如果没有释放的是imageIO时,注意是因为imageNamed会缓存图片到内存,直到内存警告被释放,如果想用完立马释放可以改成imageWithContentsOfFile

13,NSDateFormatter创建和设置timeZone,Locale,dateFormat初始化和设置属性很慢,还有NSCalendar,如果其在for循环中频繁创建,将会产生大量的耗时,可以将其声明为static单例,防止重复创建带来的消耗。
同时NSDateFormatter不是线程安全的,可将其缓存在线程字典中。
[[NSThread currentThread] threadDictionary]

14,dateFromString方法效率很低,执行一万次方法耗时1100ms,用strptime的C方法替代,一万次耗时300ms

time_t t;
struct tm tm;
strptime([iso8601String cStringUsingEncoding:NSUTF8StringEncoding], "%Y-%m-%dT%H:%M:%S%z", &tm);
tm.tm_isdst = -1;
t = mktime(&tm);
[NSDate dateWithTimeIntervalSince1970:t + [[NSTimeZone localTimeZone] secondsFromGMT]];

15,NSFileManager 的attributesOfItemAtPath方法会取文件所有属性信息,有的可能你根本不需要,这时可以用stat代替NSFileManager,直接获取文件属性:

#import 

struct stat statbuf;
const char *cpath = [filePath fileSystemRepresentation];
if (cpath && stat(cpath, &statbuf) == 0) {
    NSNumber *fileSize = [NSNumber numberWithUnsignedLongLong:statbuf.st_size];
    NSDate *modificationDate = [NSDate dateWithTimeIntervalSince1970:statbuf.st_mtime];
    NSDate *creationDate = [NSDate dateWithTimeIntervalSince1970:statbuf.st_ctime];
    // etc
}

经测试,attributesOfItemAtPath执行一万次需要165ms,stat仅需要78ms

16,NSLog写消息到Apple的系统日志,并且系统会在主线程序列化NSLog的内容。即使最新的iOS设备中,NSLog所花费的输出时间也是无法忽略的,所以发布环境中尽可能的少用NSLog
推荐:不用NSLog,用DLog,在DEBUG环境下打印,在Release下不打印或只打印比较严重的错误。

17,stringWithFormat和initWithFormat方法不是特别耗性能,但是如果在for循环中使用的话可以用asprintf的C函数来提高性能。

NSString *firstName = @"Daniel";
NSString *lastName = @"Amitay";
char *buffer;
asprintf(&buffer, "Full name: %s %s", [firstName UTF8String], [lastName UTF8String]);
NSString *fullName = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
free(buffer);

测试发现,stringWithFormat执行一万次代码需要52ms,asprintf仅需19ms

18,相信很多开发时间较长的人都遇到过不知不觉app突然push,pop动画丢失了,这个问题大多跟UI操作在子线程执行有关,用UI/DataSource主线程监测工具可以保证UI和DataSource操作都在主线程执行。该工具还可以解决像UI响应特别慢的操作(由于在子线程操作了UI)

19,Crash监测及修复:iOS中crash有三种,Mach异常,Unix signal信号,NSException,分别可以用Mach API,POSIX API,NSUncaughtExceptionHandler来捕获,
闪退原因:(1)数据库损坏,文件损坏,网络返回数据异常,代码bug,对于数据库,文件可以删除,对于网络数据异常,代码bug要分析crash案例,通过JSPatch来修复。

20,JSPatch原理:通过苹果的JavaScriptCore.framework作为引擎,是苹果官方支持的实现在线更新iOS应用的库,其原理是网络下载js文件,然后调用js文件,通过OC的runtime机制,动态创建类,对象,替换方法等方式实现运行时修改某一个方法执行的代码,达到修复bug或动态更新的目的。为了能第一时间发现程序问题,应用程序需要实现自己的崩溃日志收集服务,SDK有KSCrash,plcrashreporter,CrashKit

21,方法内部执行大量代码用AutoreleasePool给drain一下

22,与短租类app类似的可上传很多张图片的功能要注意,图片先存本地,上传时从本地读二进制流传给服务器,不要把图片放到内存中上传,否则占用大量内存。甚至内存警告,无法释放时crash。

23,如果用xib开发的话要注意,多个view不要写在一个xib文件中,因为即使你不会马上用到,也会立即加载,浪费了宝贵的内存资源。

24,GCD使用的时候要注意,它是苹果提供的自动管理线程的API,但是他的线程数量是不可控的,所以更精细的线程管理用NSOperationQueue来管理吧,设置最大并发数量。

25,UIView的frame应该设置为整数,否则会触发反锯齿降低性能,也有可能引起图形界面的边界模糊。

26,layer的drawsAsynchronously属性会导致layer的CGContext延迟到后台线程绘制。这个属性对于频繁绘制的layer有很大的好处。

27,建议阴影操作用图片的方式来实现,但如有必要用layer的阴影属性时,推荐用shadowPath属性,系统将会缓存阴影减少不必要的重绘。但当改变layer的bounds时,一定要重设shadowPath。

CALayer *layer = view.layer;
layer.shadowOpacity = 0.5f;
layer.shadowRadius = 10.0f;
layer.shadowOffset = CGSizeMake(0.0f, 10.0f);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:layer.bounds];
layer.shadowPath = bezierPath.CGPath;

28,通过Storyboard创建视图对象时,其资源消耗会比直接通过代码创建对象要大非常多,在性能敏感的界面里,storyboard不是一个好的技术选择。

29,CALayer内部并没有属性,当调用属性方法时,背部是通过运行时resolveInstanceMethod为对象临时添加一个方法,把对应属性值保存到内部一个字典中,还会通知delegate,,创建动画等,非常耗资源。UIView的关于显示的属性(frame,bounds,transform)等实际都是ACLayer属性映射来的,所以对UIView的这些属性进行调整时,消耗的资源要远大于一般属性。因此,要减少不必要的属性修改。

30,对象的销毁虽然耗费资源不多,但积累起来也是不容忽视的。通常当容器类持有大量对象时,其销毁时的资源消耗就非常明显。如果对象可以放到后台线程去释放,就放到后台线程。这里有个小Tip:把对象捕获到block中,然后扔到后台队列去随便发个消息,让其在后台线程创建一个自动释放对象,就会在后台线程销毁了。

NSArray *tmp = self.array;
self.array = nil;
dispatch_async(queue, ^{
    [tmp class];
});

31,视图布局的计算是app中最为常见的消耗CPU资源的地方。如果能在后台线程提前计算好视图布局,并对视图布局进行缓存,这个地方基本就不会产生性能问题了。不论通过何种技术对视图进行布局,其最终都会落到对view.frame,bounds,center等属性的调整上。上面也说过,这些属性调整非常好资源,所以尽量提前计算好布局,需要时一次性调整好对应属性,而不要多次,频繁的计算和调整这些属性。

32,AutoLayout是苹果本身提倡的快速布局的技术,大部分情况下能很好的提升开发效率,但是Autolayout对复杂视图来说,会产生严重的性能问题,随着视图数量的增加,AutoLayout带来的CPU消耗会呈指数级上升,具体看文章:http://pilky.me/36/,可以用left,right,top,bottom,width,height快捷属性,或者ComponentKit,AsyncDisplayKit等框架。

33,文本渲染,屏幕上所能看到的所有文本内容控件,包括UIWebView,在底层都是通过CoreText排版、绘制为Bitmap显示的。常见的文本控件(UILabel,UITextView等),其排版和绘制都是在主线程进行的,当显示大量文本时,CPU压力非常大。所以需要自定义文本控件。用TextKit或底层的CoreText对文本异步绘制,尽管实现麻烦,但带来的优势也很大,CoreText对象创建后,能直接获取文本的宽高,避免了多次计算,(调整UILabel的宽度时算一遍,UILabel绘制时再算一遍),CoreText对象占用内存较少,可以缓存下来以备多次渲染使用。

34,图片的解码,当使用UIImage或CGImageSource的几个方法创建图片时,图片数据并不会立刻解码,设置到UIImageView或者CALyer提交到GPU前,CGImage中的数据才会被解码。这一步是发生在主线程,并且不可避免,如果要绕开这个机制,常见的做法是在后台线程把图片绘制到CGBitmapContext中,然后从Bitmap直接创建图片。目前常见的网络图片库都自带这个功能。

35,图像的绘制通常是指用CG开头的方法把图像绘制到画布中,然后从画布创建图片,由于CoreGraphic方法通常都是线程安全的,所以图像绘制可以很容易的放到后台线程进行。
例如:

- (void)display {
    dispatch_async(backgroundQueue, ^{
        CGContextRef ctx = CGBitmapContextCreate(...);
        // draw in context...
        CGImageRef img = CGBitmapContextCreateImage(ctx);
        CFRelease(ctx);
        dispatch_async(mainQueue, ^{
            layer.contents = img;
        });
    });
}

36,所有的Bitmap,包括图片,文本,栅格化的内容,最终都要由内存提交给现存,绑定为GPU Texture。不论是提交到显存的过程,还是GPU调整渲染Texture的过程,都要消耗不少GPU资源,当较短时间显示大量图片时(如TableView存在非常多的图片并且快速滑动时),CPU占用率很低,GPU占用率非常高,界面仍然会掉帧,避免掉帧只能尽量减少短时间内大量图片显示,或将多张图片合成一张显示,以及单张图片像素不能过大。

37,当多个视图(或者说layer)重叠在一起显示时,GPU会首先把他们混合到一起。如果视图结构过于复杂,混合的过程也会消耗很多GPU资源。为了减轻这种情况的GPU消耗,应用应当尽量减少视图数量和层次,并在不透明视图里表明opaque属性以避免无用的alpha通道合成。当然,也可以用上面的方法,把多个视图预先渲染为一张图片来显示。

38,图形的生成:CALayer的border,圆角,阴影,遮罩,CGSharpLayer的矢量图形的显示,通常会触发离屏渲染,而离屏渲染通常发生在GPU中。当一个列表视图中出现大量圆角的layer,并且快速滑动时,可以观察到GPU资源已经占满,而CPU资源消耗很少。这时候界面仍能正常滑动,但是平均帧会降到很低。对于圆角场合,可以用一个圆角图片覆盖到原视图上模拟同样的视觉效果。但最根本的解决办法是把图片在后台线程裁剪成圆角,图片上直接加阴影,图片方式实现遮罩。

你可能感兴趣的:(iOS 应用性能调优(进阶))