内存管理 浅析



下列行为都会添加一个app的内存占用:

    1、创建一个OC对象;

    2、定义一个变量;

    3、调用一个函数或者方法。

    假设app占用内存过大。系统可能会强制关闭app,造成闪退现象,影响用户体验。

怎样让回收那些不再使用的对象呢?本文着重介绍OC中的内存管理。

    所谓内存管理。就是对内存进行管理。涉及的操作有:

    1、分配内存:比方创建一个对象。会添加内存占用;

    2、清除内存:比方销毁一个对象。会降低内存占用。

    内存管理的管理范围:

    1、不论什么继承了NSObject的对象;

    2、对其它非对象类型无效(int、char、float、double、struct、enum等)

    仅仅有OC对象才须要进行内存管理的本质原因:

    1、OC对象存放于堆里。

    2、非OC对象一般放在栈里面(栈内存会被系统自己主动回收)

    系统是怎样推断什么时候须要回收一个对象所占用的内存呢?在这里涉及到对象的“引用计数器”的概念。

    引用计数器:

    每一个OC对象都有自己的引用计数器,它是一个整数。每一个OC对象内部都有4个字节的存储空间来存放引用计数器。

    从字面上看。引用计数器能够理解为“对象被引用的次数”。

    简单来说,能够理解为:引用计数器表示有多少人正在使用这个对象。

    当没有不论什么人使用这个对象时。系统才会回收这个对象;也就是说:

    1、当对象的引用计数器为0时。对象占用的内存就会被系统回收;

    2、假设对象的计数器不为0。那么在整个程序执行过程。它占用的内存就不可能被回收(除非整个程序已经退出)。

    不论什么一个对象。刚生下来的时候,引用计数器都为1。当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1.

    要想管理对象占用的内存。就得学会操作对象的引用计数器。

    引用计数器的常见操作:

    1、给对象发送一条retain消息。能够使引用计数器值+1(retain方法返回对象本身);

    2、给对象发送一条release消息,能够使引用计数器值-1。

    3、给对象发送retainCount消息。能够获得当前的引用计数器值。

    须要注意的是:release并不代表销毁\回收对象。不过计数器值-1。

    当一个对象的引用计数器值为0时:

    1、这个对象即将被销毁,其占用的内存被系统回收。

    2、系统会自己主动给对象发送一条dealloc消息(因此。从dealloc方法有没有被调用。就能够推断出对象是否被销毁)。

    dealloc方法的重写;

    1、通常会重写dealloc方法,在这里释放相关资源,dealloc就是相关的遗言;

    2、一旦重写了dealloc方法。就必须调用[super dealloc],而且放在最后面调用。

    使用注意:

    1、不能直接调用dealloc方法;

    2、一旦对象被回收了。它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)。

    野指针\空指针概念:

    僵尸对象:已经被销毁的对象(不能再使用的对象)。

    野指针:指向僵尸对象的指针;给野指针发消息会报“EXC_BAD_ACCESS”错误。

    空指针:没有指向存储空间的指针(里面存的时nil,也就是0);给空指针发消息是没有不论什么反应的。

    为了避免野指针错误的常见方法是:在对象被销毁后。将指向对象的指针变为空指针。

    多对象内存管理规律:

    单个对象内存管理比較简单,假设对多个对象进行内存管理。而且对象之间是有联系的,那么管理就会变得比較复杂;总的来说,多对象内存管理有几点规律:

    1、仅仅要还有人在用某个对象。那么这个对象就不会被回收。

    2、仅仅要你想用这个对象,就让对象的计数器+1;

    3、当你不再使用这个对象时。就让对象的计数器-1;

    苹果官方规定的内存管理原则:

    1、谁创建谁release:假设你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用调用release或autorrelease。

    2、谁retain谁release:仅仅要你调用了retain。就必须调用一次release。

    总的来说就是:

    1、有加就有减;

    2、以前让对象的计数器+1。就必须在最后让对象计数器-1.

    set方法的内存管理例如以下:


- (             void             )setCar:(Car *)car            
             {            
                          if              (car != _car)            
                          {            
                          // 对当前正在使用的车(旧车)做一次release            
                          [_car release];            
                          
                          // 对新车做一次retain操作            
                          _car = [car retain];            
                          }            
             }



 

    dealloc方法的内存管理


- (             void             )dealloc            
             {            
                          // 当人不在了,代表不用车了            
                          // 对车做一次release操作            
                          [_car release];             
                          [super dealloc];            
             }



 

    以下的代码都会引发内存泄漏:


p.dog = [[Dog alloc] init];             
                          [[Dog alloc] init].weight = 20.8;






内存管理小技巧:






1. 用ARC管理内存

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

除了帮你避免内存泄露,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的性能。

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

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

 

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:`方法来获取图片资源。

 

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: 无序的一组值。

用值来查找非常快,插入/删除非常快。

 

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。你能够用整幅的图片。可调大小的图片。uozhe能够用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是能够被重现创建的来释放内存。

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

 

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一样等到整个文档下载完毕才開始解析。当你处理非常大的数据的时候就会极大地减低内存消耗和添加性能。

 

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框架是非常多开发人员的选择:

#import
 // Somewhere later ...
 UIView *view = [[UIView alloc] init];
 // Setup the shadow ...
 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.

 

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就好了。