zoukankan      html  css  js  c++  java
  • 优化iOS程序性能的25个方法


    1. 用ARC管理内存

    ARC(Automatic ReferenceCounting, 自己主动引用计数)和iOS5一起公布。它避免了最常见的也就是常常是因为我们忘记释放内存所造成的内存泄露。它自己主动为你管理retain和release的过程,所以你就不必去手动干预了。忘掉代码段结尾的release简直像记得吃饭一样简单。

    而ARC会自己主动在底层为你做这些工作。除了帮你避免内存泄露。ARC还能够帮你提高性能。它能保证释放掉不再须要的对象的内存。

    如今全部的iOS程序都用ARC了。这条能够忽略。

     

    2. 在正确的地方使用 reuseIdentifier

    一个开发中常见的错误就是没有给UITableViewCells。 UICollectionViewCells,甚至是UITableViewHeaderFooterViews设置正确的reuseIdentifier。

    为了性能最优化,table view用tableView:cellForRowAtIndexPath:为rows分配cells的时候。它的数据应该重用自UITableViewCell。

    一个table view维持一个队列的数据可重用的UITableViewCell对象。

    不使用reuseIdentifier的话,每显示一行table view就不得不设置全新的cell。

    这对性能的影响但是相当大的,尤其会使app的滚动体验大打折扣。

    自iOS6起。除了UICollectionView的cells和补充views,你也应该在header和footer views中使用reuseIdentifiers。

    想要使用reuseIdentifiers的话,在一个table view中加入一个新的cell时在data source object中加入这种方法:

    staticNSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    这种方法把那些已经存在的cell从队列中排除,或者在必要时使用先前注冊的nib或者class创造新的cell。假设没有可重用的cell,你也没有注冊一个class或者nib的话,这种方法返回nil。

     

    3.尽量把views设置为透明

    假设你有透明的Views你应该设置它们的opaque属性为YES。

    原因是这会使系统用一个最优的方式渲染这些views。

    这个简单的属性在IB或者代码里都能够设定。

    Apple的文档对于为图片设置透明属性的描写叙述是:

    (opaque)这个属性给渲染系统提供了一个怎样处理这个view的提示。

    假设设为YES。渲染系统就觉得这个view是全然不透明的,这使得渲染系统优化一些渲染过程和提高性能。假设设置为NO,渲染系统正常地和其他内容组成这个View。默认值是YES。

    在相对照较精巧的画面中,设置这个属性不会有太大影响。

    然而当这个view嵌在scroll view里边,或者是一个复杂动画的一部分,不设置这个属性的话会在非常大程度上影响app的性能。

    你能够在模拟器中用DebugColor Blended Layers选项来发现哪些view没有被设置为opaque。

    目标就是。能设为opaque的就全设为opaque!

    这里有一点须要注意,仅仅要是有中文字符的Label,哪怕你设置成不透明,模拟器中这个Label依旧会变红。这个推測是字符绘制的时候出的问题,这个眼下没找到好的解决方法。

     

    4.避免过于庞大的XIB

    iOS5中增加的Storyboards(分镜)正在高速代替XIB。然而XIB在一些场景中仍然非常实用。

    比方你的app须要适应iOS5之前的设备。或者你有一个自己定义的可重用的view,你就不可避免地要用到他们。

    假设你不得不XIB的话,使他们尽量简单。尝试为每一个Controller配置一个单独的XIB。尽可能把一个View Controller的view层次结构分散到单独的XIB中去。

    须要注意的是,当你载入一个XIB的时候全部内容都被放在了内存里,包含不论什么图片。假设有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。Storyboards就是还有一码事儿了。storyboard仅在须要时实例化一个view controller.

    当家在XIB是,全部图片都被chache,假设你在做OS X开发的话,声音文件也是。

    Apple在相关文档中的记述是:

    当你载入一个引用了图片或者声音资源的nib时。nib载入代码会把图片和声音文件写进内存。在OS X中。图片和声音资源被缓存在named cache中以便将来用到时获取。

    在iOS中,仅图片资源会被存进named caches。取决于你所在的平台,使用NSImage 或UIImage的imageNamed:方法来获取图片资源。

    这个问题我深有体会,用xib写的界面载入速度比直接用代码写的要慢好多。

     

    5.不要堵塞主线程

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

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

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

    你能够使用NSURLConnection异步地做网络操作:

    + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue*)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

    或者使用像AFNetworking这种框架来异步地做这些操作。

    假设你须要做其他类型的须要耗费巨大资源的操作(比方时间敏感的计算或者存储读写)那就用 Grand Central Dispatch。或者NSOperation和 NSOperationQueues.

    以下代码是使用GCD的模板

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // switch to a background thread and perform your expensive operation
        dispatch_async(dispatch_get_main_queue(), ^{
            // switch back to the main thread to update your UI
        });
    });

    发现代码中有一个嵌套的dispatch_async吗?这是由于不论什么UIKit相关的代码须要在主线程上进行。

     

    6. 在Image Views中调整图片大小

    假设要在UIImageView中显示一个来自bundle的图片。你应保证图片的大小和UIImageView的大小同样。在执行中缩放图片是非常耗费资源的,特别是UIImageView嵌套在UIScrollView中的情况下。

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

     

    7. 选择正确的Collection

    学会选择对业务场景最合适的类或者对象是写出能效高的代码的基础。

    当处理collections时这句话尤其正确。

    一些常见collection的总结:

    · Arrays: 有序的一组值。使用index来lookup非常快,使用value lookup非常慢。插入/删除非常慢。

    · Dictionaries: 存储键值对。用键来查找比較快。

    · Sets: 无序的一组值。用值来查找非常快。插入/删除非常快。由于Set用到了哈希,所以插入删除查找速度比Array快非常多

     

    8. 打开gzip压缩

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

    问题是我们的目标是移动设备,因此你就不能指望网络状况有多好。一个用户如今还在edge网络,下一分钟可能就切换到了3G。

    任何场景,你肯定不想让你的用户等太长时间。

    减小文档的一个方式就是在服务端和你的app中打开gzip。

    这对于文字这样的能有更高压缩率的数据来说会有更显著的效用。

    好消息是。iOS已经在NSURLConnection中默认支持了gzip压缩。当然AFNetworking这些基于它的框架亦然。像Google App Engine这些云服务提供者也已经支持了压缩输出。

     

    9. 重用和延迟载入(lazy load) Views

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

    这里我们用到的技巧就是模仿UITableView和UICollectionView的操作:不要一次创建全部的subview,而是当须要时才创建。当它们完毕了使命。把他们放进一个可重用的队列中。

    这种话你就仅仅须要在滚动发生时创建你的views,避免了不划算的内存分配。

    创建views的能效问题也适用于你app的其他方面。

    想象一下一个用户点击一个button的时候须要呈现一个view的场景。

    有两种实现方法:

    1. 创建并隐藏这个view当这个screen载入的时候,当须要时显示它;

    2. 当须要时才创建并展示。

    每一个方案都有其优缺点。用第一种方案的话由于你须要一開始就创建一个view并保持它直到不再使用,这就会更加消耗内存。然而这也会使你的app操作更敏感由于当用户点击button的时候它仅仅须要改变一下这个view的可见性。

    另外一种方案则相反-消耗更少内存,可是会在点击button的时候比第一种稍显卡顿。

     

    10. Cache, Cache, 还是Cache!注意你的缓存

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

    我们能缓存些什么呢?一些选项是。远端server的响应。图片,甚至计算结果,比方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。

    NSCache和NSDictionary类似,不同的是系统回收内存的时候它会自己主动删掉它的内容。

     

    11.权衡渲染方法

    在iOS中能够有非常多方法做出美丽的button。你能够用整幅的图片,可调大小的图片。或者能够用CALayer, CoreGraphics甚至OpenGL来画它们。当然每一个不同的解决方法都有不同的复杂程度和对应的性能。

    简单来说。就是用事先渲染好的图片更快一些,由于如此一来iOS就免去了创建一个图片再画东西上去然后显示在屏幕上的程序。问题是你须要把全部你须要用到的图片放到app的bundle里面,这样就添加了体积–这就是使用可变大小的图片更好的地方了:你能够省去一些不必要的空间,也不须要再为不同的元素(比方button)来做不同的图。

    然而,使用图片也意味着你失去了使用代码调整图片的机动性。你须要一遍又一遍不断地重做他们,这样就非常浪费时间了,并且你假设要做一个动画效果。尽管每幅图仅仅是一些细节的变化你就须要非常多的图片造成bundle大小的不断增大。

    总得来说。你须要权衡一下利弊,究竟是要性能能还是要bundle保持合适的大小。

     

    12.处理内存警告

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

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

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

    · 在app delegate中使用applicationDidReceiveMemoryWarning:的方法

    · 在你的自己定义UIViewController的子类(subclass)中覆盖didReceiveMemoryWarning

    · 注冊并接收 UIApplicationDidReceiveMemoryWarningNotification的通知

    一旦收到这类通知,你就须要释放不论什么不必要的内存使用。

    比如。UIViewController的默认行为是移除一些不可见的view,它的一些子类则能够补充这种方法,删掉一些额外的数据结构。

    一个有图片缓存的app能够移除不在屏幕上显示的图片。

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

    然而,当你一定要确认你所选择的object是能够被重现创建的来释放内存。

    一定要在开发中用模拟器中的内存提醒模拟去測试一下。

    当然,如今iOS设备执行内存越来越大。这一点非常难出现了。

     

    13.重用大开销对象

    一些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须要常常进行日期格式处理的话,你会从这种方法中得到不小的性能提升。

     

    14. 使用Sprite Sheets

    Sprite sheet能够让渲染速度加快,甚至比标准的屏幕渲染方法节省内存。

     

    15.避免重复处理数据

    很多应用须要从server载入功能所需的常为JSON或者XML格式的数据。

    在server端和client使用同样的数据结构非常重要。在内存中操作数据使它们满足你的数据结构是开销非常大的。

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

    类似的。假设须要从特定key中取数据。那么就使用键值对的dictionary。

    这一点在处理大量数据的时候极为重要。用空间换时间的方法或许是极好的。

     

    16.选择正确的数据格式

    从app和网络服务间数据传输有非常多方案,最常见的就是JSON和XML。

    你须要选择对你的app来说最合适的一个。

    解析JSON会比XML更快一些,JSON也通常更小更便于传输。

    从iOS5起有了官方内建的JSON deserialization就更加方便使用了。

    可是XML也有XML的优点,比方使用SAX来解析XML就像解析本地文件一样。你不需像解析json一样等到整个文档下载完毕才開始解析。当你处理非常大的数据的时候就会极大地减低内存消耗和添加性能。

    如今基本上都是JSON了。

     

    17.正确设定背景图片

    在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"]];

     

    18. 降低使用Web特性

    UIWebView非常实用。用它来展示网页内容或者创建UIKit非常难做到的动画效果是非常easy的一件事。

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

    所以想要更高的性能你就要调整下你的HTML了。第一件要做的事就是尽可能移除不必要的javascript,避免使用过大的框架。能仅仅用原生js就更好了。

    另外。尽可能异步载入比如用户行为统计script这样的不影响页面表达的javascript。

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

     

    19. 设定Shadow Path

    怎样在一个View或者一个layer上加一个shadow呢,QuartzCore框架是非常多开发人员的选择:

    UIView *view = [[UIView alloc] init];
    view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
    view.layer.shadowRadius = 5.0f;
    view.layer.shadowOpacity = 0.6;

    看起来非常easy,对吧。但是。坏消息是使用这种方法也有它的问题… Core Animation不得不先在后台得出你的图形并加好阴影然后才渲染。这开销是非常大的。

    使用shadowPath的话就避免了这个问题:

    view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

    使用shadow path的话iOS就不必每次都计算怎样渲染,它使用一个预先计算好的路径。但问题是自己计算path的话可能在某些View中比較困难。且每当view的frame变化的时候你都须要去update shadow path.

    我更喜欢用CALayer自己画一个阴影出来。这样能够设置阴影光栅化,节省大量CPU的运算,坏处就是比較消耗内存。由于假设给view的layer设置光栅化的话整个View都会变得模糊。

     

    20. 优化Table View

    Table view须要有非常好的滚动性能,不然用户会在滚动过程中发现动画的瑕疵。

    为了保证table view平滑滚动。确保你採取了下面的措施:

    · 正确使用reuseIdentifier来重用cells

    · 尽量使全部的view opaque,包含cell自身

    · 避免渐变,图片缩放,后台选人

    · 缓存行高

    · 假设cell内现实的内容来自web。使用异步载入,缓存请求结果

    · 使用shadowPath来画阴影

    · 降低subviews的数量

    · 尽量不使用cellForRowAtIndexPath:,假设你须要用到它,仅仅用一次然后缓存结果

    · 使用正确的数据结构来存储数据

    · 使用rowHeight, sectionFooterHeight和 sectionHeaderHeight来设定固定的高,不要请求delegate

     

    21.选择正确的数据存储选项

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

    你有非常多选择。比方:

    · 使用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了。

     

    23. 使用Autorelease Pool

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

    假如你创建非常多暂时对象,你会发现内存一直在降低直到这些对象被release的时候。这是由于仅仅有当UIKit用光了autorelease pool的时候memory才会被释放。

    好消息是你能够在你自己的@autoreleasepool里创建暂时的对象来避免这个行为:

     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. */ 
         }
        }

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

     

    24. 选择是否缓存图片

    常见的从bundle中载入图片的方式有两种。一个是用imageNamed,二是用imageWithContentsOfFile,第一种比較常见一点。

    既然有两种类似的方法来实现同样的目的。那么他们之间的区别是什么呢?

    imageNamed的长处是当载入时会缓存图片。imageNamed的文档中这么说:这种方法用一个指定的名字在系统缓存中查找并返回一个图片对象假设它存在的话。假设缓存中没有找到对应的图片,这种方法从指定的文档中载入然后缓存并返回这个对象。

    相反的,imageWithContentsOfFile仅载入图片。

    以下的代码说明了这两种方法的使用方法:

    UIImage *img = [UIImage imageNamed:@"myImage"];// caching

    // or

    UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching

    那么我们应该怎样选择呢?

    假设你要载入一个大图片并且是一次性使用,那么就不是必需缓存这个图片。用imageWithContentsOfFile足矣,这样不会浪费内存来缓存它。

    然而,在图片重复重用的情况下imageNamed是一个好得多的选择。

     

    25. 避免日期格式转换

    假设你要用NSDateFormatter来处理非常多日期格式,应该小心以待。就像先前提到的,不论什么时候重用NSDateFormatters都是一个好的实践。

    然而。假设你须要很多其它速度。那么直接用C是一个好的方案。Sam Soffes有一个不错的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)里面有一些能够用来解析ISO-8601日期字符串的代码,简单重写一下就能够拿来用了。

    嗯,直接用C来搞,看起来不错了。可是你相信吗,我们还有更好的方案。

    假设你能够控制你所处理的日期格式,尽量选择Unix时间戳。

    你能够方便地从时间戳转换到NSDate:

    - (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {

    return[NSDate dateWithTimeIntervalSince1970:timestamp];

    }

    这样会比用C来解析日期字符串还快!

    须要注意的是。很多web API会以微秒的形式返回时间戳,由于这样的格式在javascript中更方便使用。

    记住用dateFromUnixTimestamp之前除以1000就好了。

  • 相关阅读:
    iOS-mac下 svn的使用
    iOS-Model-View-ViewModel & ReactiveCocoa
    iOS-通过实现一个TableView来理解iOS UI编程(转)
    iOS-细说 iOS 消息推送(转)
    【原创】O2O,你真的知道怎么玩吗?
    谷歌提升虚拟运营商逼格,传统电信运营商的好日子到头?
    【技术贴】大型发布会现场的WiFi网络应该如何搭建?
    千万别把WIFI玩坏了!关于WIFI的新鲜玩法和商业模式探讨
    【深度分享】千团大战:看今天商业WiFi乱局及其破解之道
    (转)迎接 Entity Framework 7
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/8404433.html
Copyright © 2011-2022 走看看