iOS 性能调试

性能调优的方式:

 一、经过专门的性能调优工具javascript

 二、经过代码优化html

1. 性能调优工具:

下面针对iOS的性能调优工具进行一个介绍:java

1.1 静态分析工具–Analyze

相信iOS开发者在App进行Build或Archive时,会产生不少编译警告,这些警告是编译时产生的,静态分析的过程也相似,在XCode Product菜单下,点击Analyze对App进行静态分析。git

Analyze主要分析如下四种问题:程序员

   一、逻辑错误:访问空指针或未初始化的变量等;github

  二、内存管理错误:如内存泄漏;web

  三、声明错误:从未使用的变量;数据库

  四、API调用错误:未包含使用的库和框架。编程

1.2 内存泄漏分析工具–Leaks

点击XCode的Product菜单Profile启动Instruments,使用Leaks开始动态分析。
选择Leaks,会自动启动Leaks工具和IOS模拟器,Leaks启动后会开始录制,随着对模拟器运行的App的操做,能够在Leaks中查看内存占用的状况。浏览器

  注:若是项目使用了ARC,随着操做,不断地开启或关闭视图,内存可能持续上升,但这不必定表示存在内存泄漏,ARC释放的时机是不固定的。

Leaks顶部分为两栏:All Heap & Anonymous VM和Leaks Checks,All Heap & Anonymous VM中的曲线表明内存分配和内存泄漏曲线。

点击第二栏Leaks Checks展现内存泄漏,进行内存泄漏分析,将光标放置在上图的小红叉上可看到leak数量,右下方是leaks调试的选项:

 建议把Snapshot Interval间隔时间设置为10秒,勾选Automatic Snapshotting,Leaks会自动进行内存捕捉分析。

 在你怀疑有内存泄漏的操做前和操做后,能够点击Snapshot Now进行手动捕捉。

Leaked Object的表格中显示了内存泄漏的类型、数量及内存空间等。

点击具体的某个内存泄漏对象,在右侧Detail窗口中会出现致使泄漏可能的位置,其中黑色头像表明了最可能的位置。

Leaks已成功找出了友盟相关的函数:

 内存泄漏动态分析技巧

  熟练使用Leaks后会对内存泄漏判断更准确,在可能致使泄漏的操做里,多使用Snapshot Now手动捕捉。

  开始时若是设备性能较好,能够把自动捕捉间隔设置为5秒钟。

  使用ARC的项目,通常内存泄漏都是malloc、自定义结构、资源引发的,多注意这些地方进行分析。

开启ARC后,内存泄漏的缘由

  开启了ARC并非就不会存在内存问题,苹果有句名言:ARC is only for NSObject。

  在IOS 中使用malloc分配的内存,ARC是不会处理的,须要本身进行处理。

  例子中的 CGImageRef 也是一个Image的指针,ARC也不会进行处理。

1.3 不合理内存分析工具–Allocation

关于内存的问题,除了内存泄漏之外,还可能存在内存不合理使用的状况,也会致使IOS内存警告。

内存的不合理使用每每比内存泄漏更难发现,内存泄漏能够更多借助于工具的判断,而内存的不合理运用更多须要开发者结合代码、架构来进行分析。

明确说明一下二者的区别:

  内存泄漏:指内存被分配了,可是程序中已经没有志向该内存的指针,致使该内存没法被释放,一直占用着内存,产生内存泄漏。

   内存不合理运用:苹果官方称这种状况为abandoned memory,也就是存在已分配内存的引用,但实际上程序中并不会使用,好比图片等对象进行了缓存,可是缓存中的对象一直没有被使用。

XCode提供的Instruments中的Allocation工具能够用来帮你了解内存的分配状况,当你的App收到内存警告时,首先应该用Allocation进行内存分析,了解哪些对象占用了太多内存。

1.4 干掉僵尸对象–Zombies

僵尸对象,也就是咱们会遇到的EXC_BAD_ACCESS错误,因为内存已经被释放,而这个对象仍旧保留这那个坏地址而致使的。
在MRC的开发中,这个错误比较常见,ARC下面在使用到C++的代码也会遇到。
不过这个工具比较简单,遇到这类错误,打开这个位于Instruments下的工具,直接就能帮你定位到,这里就不赘述了。

1.5 性能提高工具–Time Profile

这篇文章的重中之重!

既然是性能调优,那么怎么提高代码的运行效率其实才是咱们程序员最直接的诉求,而这个工具能够辅助咱们办到这件事。

Time Profiler分析原理: 它按照固定的时间间隔来跟踪每个线程的堆栈信息,经过统计比较时间间隔之间的堆栈状态,来推算某个方法执行了多久,并得到一个近似值。它将各个方法消耗的时间统计起来,造成了咱们直接定位须要进行优化的代码的好帮手。

选择Time Profiler工具开始测试,这时会自动启动模拟器和Time Profiler录制。

先进行一些App的操做,让Time Profiler收集足够的数据,尤为是你以为那些有性能瓶颈的地方。

  2是扩展面板,用来跟踪显示堆栈;

  3里面有设置和详情,能够从这里对录制作些配置,detail下查看到cpu运行的时间都消耗在哪里;

 

经过对应用的操做,能够在详细面板中看到那些最耗时的操做是哪些,并能够逐行展开查看:

图标为黑色头像的就是Time Profiler给咱们的提示,有可能存在性能瓶颈的地方,能够逐渐向下展开,找到产生的根本缘由。

Time Profiler参数设置

这里边几个选项的含义以下:

  Separate by Thread: 每一个线程应该分开考虑。只有这样你才能揪出那些大量占用CPU的”重”线程

  Invert Call Tree: 从上倒下跟踪堆栈,这意味着你看到的表中的方法,将已从第0帧开始取样,这一般你是想要的,只有这样你才能看到CPU中话费时间最深的方法.也就是说FuncA{FunB{FunC}} 勾选此项后堆栈以C->B-A 把调用层级最深的C显示在最外面

  Hide System Libraries: 勾选此项你会显示你app的代码,这是很是有用的. 由于一般你只关心cpu花在本身代码上的时间不是系统上的

  Flatten Recursion: 递归函数, 每一个堆栈跟踪一个条目

  Top Functions: 一个函数花费的时间直接在该函数中的总和,以及在函数调用该函数所花费的时间的总时间。所以,若是函数A调用B,那么A的时间报告在A花费的时间加上B花费的时间,这很是真有用,由于它可让你每次下到调用堆栈时挑最大的时间数字,归零在你最耗时的方法。

上面的参数在实践中合理设置,也没有什么太多技巧,就是经过数据的隐藏、显示让咱们更关注于想找到的数据。

2. 性能调优的代码优化:

下面介绍一下在开发中能够直接进行的代码优化的方面。

2.1 views设置为不透明(opaque=yes)

(opaque)这个属性给渲染系统提供了一个如何处理这个view的提示。若是设为YES, 渲染系统就认为这个view是彻底不透明的,这使得渲染系统优化一些渲染过程和提升性能。

若是设置为NO,渲染系统正常地和其它内容组成这个View。默认值是YES。

给大家看个图,大家就知道了,若是这个属性为NO,那么:

这里写图片描述

知道了吧,GPU会利用图层颜色合成公式去合成真正的色值,这在ScrollView或者动画中是很消耗性能的。

2.2 不要阻塞主线程

永远不要使主线程承担过多。由于UIKit在主线程上作全部工做,渲染,管理触摸反应,回应输入等都须要在它上面完成。

一直使用主线程的风险就是若是你的代码真的block了主线程,你的app会失去反应。

大部分阻碍主进程的情形是你的app在作一些牵涉到读写外部资源的I/O操做,好比存储或者网络。

一般建议这些操做都使用GCD的方式直接异步执行,并将UI相关操做在主线程进行回调,像这样:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 切换到全局队列,异步执行耗时操做
    dispatch_async(dispatch_get_main_queue(), ^{
        // 切换到主线程,更新你的UI。
    });
});

2.3 提早调整ImageView中的图片大小(同图片和动画的渲染)

若是要在UIImageView中显示一个图片,你应保证图片的大小和UIImageView的大小相同。
由于在运行中缩放图片是很耗费资源的,特别是UIImageView嵌套在UIScrollView中的状况下。

若是图片是从远端服务加载的你不能控制图片大小,好比在下载前调整到合适大小的话,你能够在下载完成后,最好是用background thread,缩放一次,而后在UIImageView中使用缩放后的图片。

这个类比到图片和动画的渲染中,是通用的。

具体方法参考上面的GCD操做。

2.4 正确使用容器的特性

Arrays: 有序的一组值。使用index来查找很快,使用value 查找很慢, 插入/删除很慢。 Dictionaries: 存储键值对。 用键来查找比较快。 Sets: 无序的一组值。用值来查找很快,插入/删除很快。

2.5 大文件传输使用gzip

大量app依赖于远端资源和第三方API,你可能会开发一个须要从远端下载XML, JSON, HTML或者其它格式的app。

问题是咱们的目标是移动设备,所以你就不能期望网络情况有多好。一个用户如今还在edge网络,下一分钟可能就切换到了3G。不论什么场景,你确定不想让你的用户等太长时间。

减少文档的一个方式就是在服务端和你的app中打开gzip。这对于文字这种能有更高压缩率的数据来讲会有更显著的效用。

固然,如今苹果已经自动支持了,你只须要告诉大家服务端的同窗,传输大文件的时候记得用gzip就完了。

2.6 View的重用和懒加载

更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了不少view在UIScrollView里边的app更是如此。

重用就是模仿UITableViewUICollectionView的操做: 不要一次建立全部的subview,而是当须要时才建立,当它们完成了使命,把他们放进一个可重用的队列中。
当须要使用View的时候,去可重用队列里面找一找有没有能够被复用的View。
这里个人一份框架中曾经使用过相似的方法去建立一个图片浏览器,你们能够稍作参考。View的重用

懒加载就是在程序启动时并不进行加载,只有当用到这个对象的时候,才进行加载。
这个不只在属性中能够进行这样的使用,在View上面也是同样,不过实现稍有不一样。
懒加载会消耗更少内存,可是在View的显示上会稍有滞后。

2.7 Cache

一个极好的原则就是,缓存所须要的,也就是那些不大可能改变可是须要常常读取的东西。

咱们能缓存些什么呢?一些选项是,远端服务器的响应,图片,甚至计算结果,好比UITableView的行高。

NSURLConnection默认会缓存资源在内存或者存储中根据它所加载的HTTP Headers。你甚至能够手动建立一个NSURLRequest而后使它只加载缓存的值。

下面是一个可用的代码段,你能够能够用它去为一个基本不会改变的图片建立一个NSURLRequest并缓存它:

+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
 
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image
    request.HTTPShouldHandleCookies = NO;
    request.HTTPShouldUsePipelining = YES;
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
 
    return request;
}

注意你能够经过 NSURLConnection 获取一个URL request, AFNetworking也同样的。这样你就没必要为采用这条tip而改变全部的networking代码了。

若是你须要缓存其它不是HTTP Request的东西,你能够用NSCache。

2.8 记得处理内存警告

一旦系统内存太低,iOS会通知全部运行中app。在官方文档中是这样记述:

  若是你的app收到了内存警告,它就须要尽量释放更多的内存。最佳方式是移除对缓存,图片object和其余一些能够重建立的objects的strong references.

幸运的是,UIKit提供了几种收集低内存警告的方法:

  在app delegate中使用<code>applicationDidReceiveMemoryWarning:</code> 的方法 在你的自定义UIViewController的子类(subclass)中覆盖<code>didReceiveMemoryWarning</code> 注册并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知

一旦收到这类通知,你就须要释听任何没必要要的内存使用。

  例如,UIViewController的默认行为是移除一些不可见的view, 它的一些子类则能够补充这个方法,删掉一些额外的数据结构。一个有图片缓存的app能够移除不在屏幕上显示的图片。

这样对内存警报的处理是很必要的,若不重视,你的app就可能被系统杀掉。

然而,当你必定要确认你所选择的object是能够被重现建立的来释放内存。必定要在开发中用模拟器中的内存提醒模拟去测试一下。

2.9 重用大的开销对象

这里的大开销是指一些初始化很慢的objects,如:NSDateFormatter和NSCalendar。可是,你又不可避免地须要使用它们,好比从JSON或者XML中解析数据。

想要避免使用这个对象的瓶颈你就须要重用他们,能够经过添加属性到你的class里或者建立静态变量来实现。

注意若是你要选择第二种方法,对象会在你的app运行时一直存在于内存中,和单例(singleton)很类似。

下面的代码说明了使用一个属性来延迟加载一个date formatter. 第一次调用时它会建立一个新的实例,之后的调用则将返回已经建立的实例:

// in your .h or inside a class extension

@property (nonatomic, strong) NSDateFormatter *formatter;
 
// inside the implementation (.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
    if (! _formatter) {
        _formatter = [[NSDateFormatter alloc] init];
        _formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
    }
    return _formatter;
}

还须要注意的是,其实设置一个NSDateFormatter的速度差很少是和建立新的同样慢的!因此若是你的app须要常常进行日期格式处理的话,你会从这个方法中获得不小的性能提高。

2.10 避免反复的处理数据

许多应用须要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要。在内存中操做数据使它们知足你的数据结构是开销很大的。

好比你须要数据来展现一个table view,最好直接从服务器取array结构的数据以免额外的中间数据结构改变。

相似的,若是须要从特定key中取数据,那么就使用键值对的dictionary。

2.11 正确设定背景图片

在View里放背景图片就像不少其它iOS编程同样有不少方法:

  使用UIColor的 colorWithPatternImage来设置背景色; 在view中添加一个UIImageView做为一个子View。

若是你使用全画幅的背景图,你就必须使用UIImageView由于UIColor的colorWithPatternImage是用来建立小的重复的图片做为背景的。这种情形下使用UIImageView能够节约很多的内存:

// You could also achieve the same result in Interface Builder
UIImageView *backgroundView = [ [UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];

若是你用小图平铺来建立背景,你就须要用UIColor的colorWithPatternImage来作了,它会更快地渲染也不会花费不少内存:

self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];

2.12 试试苹果最新的WKWebView来处理web

UIWebView颇有用,用它来展现网页内容或者建立UIKit很难作到的动画效果是很简单的一件事。

可是你可能有注意到UIWebView并不像驱动Safari的那么快。这是因为以JIT compilation 为特点的Webkit的Nitro Engine的限制。

因此想要更高的性能你就要调整下你的HTML了。第一件要作的事就是尽量移除没必要要的javascript,避免使用过大的框架。能只用原生js就更好了。

另外,尽量异步加载例如用户行为统计script这种不影响页面表达的javascript。

最后,永远要注意你使用的图片,保证图片的符合你使用的大小。使用Sprite sheet提升加载速度和节约内存。

固然,上面是针对你在使用UIWebView的状况下,须要尽可能减小使用web的特性,而苹果最近刚推出的Safari的底层框架WKWebView也许能帮咱们规避掉不少这样的性能问题。

2.13 优化你的TableView

Table view须要有很好的滚动性能,否则用户会在滚动过程当中发现动画的瑕疵。

为了保证table view平滑滚动,确保你采起了如下的措施:

  正确使用reuseIdentifier来重用cells 尽可能使全部的view opaque,包括cell自身 避免渐变,图片缩放,后台选人 缓存行高 若是cell内现实的内容来自web,使用异步加载,缓存请求结果 使用shadowPath来画阴影 减小subviews的数量 尽可能不适用cellForRowAtIndexPath:,若是你须要用到它,只用一次而后缓存结果 使用正确的数据结构来存储数据 使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight来设定固定的高,不要请求delegate

2.14 选择正确的数据存储方式

当存储大块数据时你会怎么作?

你有不少选择,好比:

  使用NSUerDefaults 使用XML, JSON, 或者 plist 使用NSCoding存档 使用相似SQLite的本地SQL数据库 使用 Core Data

NSUserDefaults的问题是什么?虽然它很nice也很便捷,可是它只适用于小数据,好比一些简单的布尔型的设置选项,再大点你就要考虑其它方式了

XML这种结构化档案呢?整体来讲,你须要读取整个文件到内存里去解析,这样是很不经济的。使用SAX又是一个很麻烦的事情。

NSCoding?不幸的是,它也须要读写文件,因此也有以上问题。

在这种应用场景下,使用SQLite 或者 Core Data比较好。使用这些技术你用特定的查询语句就能只加载你须要的对象。

在性能层面来说,SQLite和Core Data是很类似的。他们的不一样在于具体使用方法。Core Data表明一个对象的graph model,但SQLite就是一个DBMS。Apple在通常状况下建议使用Core Data,可是若是你有理由不使用它,那么就去使用更加底层的SQLite吧。

若是你使用SQLite,你能够用FMDB(https://github.com/ccgus/fmdb)这个库来简化SQLite的操做,这样你就不用花不少经历了解SQLite的C API了。

2.15 把Xib换成Storyboard吧

当你加载一个XIB的时候全部内容都被放在了内存里,包括任何图片。若是有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。

Storyboards就是另外一码事儿了,storyboard仅在须要时实例化一个view controller.

当加载XIB时,全部图片都被缓存,若是你在作OS X开发的话,声音文件也是。Apple在相关文档中的记述是:

  当你加载一个引用了图片或者声音资源的nib时,nib加载代码会把图片和声音文件写进内存。在OS X中,图片和声音资源被缓存在named cache中以便未来用到时获取。在iOS中,仅图片资源会被存进named caches。取决于你所在的平台,使用NSImage 或UIImage 的`imageNamed:`方法来获取图片资源。

很明显,一样的事情也发生在storyboards中,但我并无找到任何支持这个结论的文档。

另外,快速打开app是很重要的,特别是用户第一次打开它时,对app来说,第一印象太太过重要了。

你能作的就是使它尽量作更多的异步任务,好比加载远端或者数据库数据,解析数据。

仍是那句话,避免过于庞大的XIB,由于他们是在主线程上加载的。因此尽可能使用没有这个问题的Storyboards吧!

  注意,用Xcode debug时watchdog并不运行,必定要把设备从Xcode断开来测试启动速度

2.16 学会手动建立Autorelease Pool

NSAutoreleasePool负责释放block中的autoreleased objects。通常状况下它会自动被UIKit调用。可是有些情况下你也须要手动去建立它。

假如你建立不少临时对象,你会发现内存一直在减小直到这些对象被release的时候。这是由于只有当UIKit用光了autorelease pool的时候memory才会被释放。

好消息是你能够在你本身的@autoreleasepool里建立临时的对象来避免这个行为:

NSArray *urls = [@"url1",@"url2"];
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. */
    }
}

这段代码在每次遍历后释放全部autorelease对象。

连接

相关文章
相关标签/搜索