当app通过一段儿时间的迭代,每每会出现一些性能问题,这时可以协助开发解决这些性能问题也成为咱们测试的重要工做。凑巧最近一段时间就一直在协助开发去进行app内存优化。这里整理了一份关于内存优化的心得分享给你们。javascript
首先咱们先要明确咱们的目的,在保证程序运行流畅的前提下尽量的优化使用内存。因此千万不要掉进为了优化而优化的陷阱。程序要先保证能运行,而后再谈良好运转。因此解决问题比较宽泛,必要的时候在交互或者运起色制上小动刀子来保证既能完成任务,程序又能正常运转,没必要去对代码进行彻底重构。毕竟在优化效率达到上限的时候,只能用时间换空间了。java
1. 用ARC管理内存web
ARC(Automatic ReferenceCounting, 自动引用计数),它避免了最多见的因为咱们忘记释放内存所形成的内存泄露。它自动为你管理retain和release的过程,因此你就没必要去手动管理了。编写代码的时候很容易忘掉结尾的release。而ARC会自动在底层为你作这些工做。除了帮你避免内存泄露,ARC还能够帮你提升性能,它能保证释放掉再也不须要的对象的内存。面试
2. 在正确的地方使用 reuseIdentifier编程
一个开发中常见的错误就是没有给UITableViewCells, UICollectionViewCells设置正确的reuseIdentifier。json
为了性能最优化,table view用tableView:cellForRowAtIndexPath:
为rows分配cells的时候,它的数据应该重用自UITableViewCell。一个table view维持一个队列的数据可重用的UITableViewCell对象。缓存
不使用reuseIdentifier的话,每显示一行table view就不得不设置全新的cell。这对性能的影响但是至关大的。服务器
自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的性能。
4.避免过于庞大的XIB
iOS5中加入的Storyboards正在快速取代XIB。然而XIB在一些场景中仍然颇有用。好比你的app须要适应iOS5以前的设备,或者你有一个自定义的可重用的view,你就不可避免地要用到他们。
若是你不得不XIB的话,使他们尽可能简单。尝试为每一个Controller配置一个单独的XIB,尽量把一个View Controller的view层次结构分散到单独的XIB中去。
须要注意的是,当你加载一个XIB的时候全部内容都被放在了内存里,包括任何图片。若是有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。然而storyboard仅在须要时实例化一个view controller.
在使用XIB,全部图片都被chache,若是你在作OS X开发的话,声音文件也是。
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中使用缩放后的图片。
这是个人iOS开发交流群:519832104无论你是小白仍是大牛欢迎入驻,能够一块儿分享经验,讨论技术,共同窗习成长!
另附上一份各好友收集的大厂面试题,须要iOS开发学习资料、面试真题,进群便可自行下载!
点击此处,当即与iOS大牛交流学习
7. 选择正确的使用Collection
学会选择对业务场景最合适的类或者对象是写出能效高的代码的基础。当处理collections时这句话尤为正确。
一些常见collection的总结:
· Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢,插入/删除很慢。
· Dictionaries: 存储键值对。用键来查找比较快。
· Sets: 无序的一组值。用值来查找很快,插入/删除很快。
8. 重用和延迟加载(lazy load) Views
更多的view意味着更多的渲染,也就是更多的CPU和内存消耗,对于那种嵌套了不少view在UIScrollView里边的app更是如此。
这里咱们用到的技巧就是模仿UITableView
和UICollectionView
的操做:不要一次建立全部的subview,而是当须要时才建立,当它们完成了使命,把他们放进一个可重用的队列中。
这样的话你就只须要在滚动发生时建立你的views,避免了没必要要的内存分配。
建立views的能效问题也适用于你app的其它方面。想象一下,一个用户点击一个按钮的时候须要呈现一个view的场景。有两种实现方法:
1. 建立并隐藏这个view当这个screen加载的时候,当须要时显示它;
2. 当须要时才建立并展现。
每一个方案都有其优缺点。用第一种方案的话由于你须要一开始就建立一个view并保持它直到再也不使用,这就会更加消耗内存。然而这也会使你的app操做更敏感由于当用户点击按钮的时候它只须要改变一下这个view的可见性。
第二种方案则相反-消耗更少内存,可是会在点击按钮的时候比第一种稍显卡顿。
9. 避免日期格式转换
若是你要用NSDateFormatter
来处理不少日期格式。就像先前提到的,任什么时候候重用NSDateFormatters
都是一个好的实践。
若是你能够控制你所处理的日期格式,尽可能选择Unix时间戳。你能够方便地从时间戳转换到NSDate:
* (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp { return[NSDate dateWithTimeIntervalSince1970:timestamp]; }
10.渲染方法
在iOS中能够有不少方法作出漂亮的按钮。固然每一个不一样的解决方法都有不一样的复杂程度和相应的性能。简单来讲,就是用事先渲染好的图片更快一些,由于如此一来iOS就免去了建立一个图片再画东西上去而后显示在屏幕上的程序。问题是你须要把全部你须要用到的图片放到app的bundle里面,这样就增长了体积–这就是使用可变大小的图片更好的地方了:你能够省去一些没必要要的空间,也不须要再为不一样的元素(好比按钮)来作不一样的图。
然而,使用图片也意味着你失去了使用代码调整图片的机动性,你须要一遍又一遍不断地重作他们,这样就很浪费时间了,并且你若是要作一个动画效果,虽然每幅图只是一些细节的变化你就须要不少的图片形成bundle大小的不断增大。
总得来讲,你须要权衡一下利弊,究竟是要性能能仍是要bundle保持合适的大小。
11.处理内存警告
一旦系统内存太低,iOS会通知全部运行中app。在官方文档中是这样记述:
若是你的app收到了内存警告,它就须要尽量释放更多的内存。最佳方式是移除对缓存,图片object和其余一些能够重建立的objects的strong references.
UIKit提供了几种收集低内存警告的方法:
· 在app delegate中使用applicationDidReceiveMemoryWarning:
的方法
· 在你的自定义UIViewController的子类(subclass)中覆盖didReceiveMemoryWarning
· 注册并接收 UIApplicationDidReceiveMemoryWarningNotification的通知
一旦收到这类通知,你就须要释听任何没必要要的内存使用。
例如,UIViewController的默认行为是移除一些不可见的view,它的一些子类则能够补充这个方法,删掉一些额外的数据结构。一个有图片缓存的app能够移除不在屏幕上显示的图片。
这样对内存警报的处理是很必要的,若不重视,你的app就可能被系统杀掉。
然而,当你必定要确认你所选择的object是能够被重现建立的来释放内存。必定要在开发中用模拟器中的内存提醒模拟去测试一下。
12.避免反复处理数据
许多应用须要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要。在内存中操做数据使它们知足你的数据结构是开销很大的。
好比你须要数据来展现一个table view,最好直接从服务器取array结构的数据以免额外的中间数据结构改变。
相似的,若是须要从特定key中取数据,那么就使用键值对的dictionary。
13.选择正确的数据格式
从app和网络服务间传输数据有不少方案,最多见的就是JSON和XML。你须要选择对你的app来讲最合适的一个。
解析JSON会比XML更快一些,JSON也一般更小更便于传输。从iOS5起有了官方内建的JSON deserialization就更加方便使用了。
可是XML也有XML的好处,好比使用SAX来解析XML就像解析本地文件同样,你不需像解析json同样等到整个文档下载完成才开始解析。当你处理很大的数据的时候就会极大地减低内存消耗和增长性能。
14.正确设定背景图片
在View里放背景图片就像不少其它iOS编程同样有不少方法:
使用UIColor的 colorWithPatternImage来设置背景色;
在view中添加一个UIImageView做为一个子View。
若是你使用全画幅的背景图,你就必须使用UIImageView由于UIColor的colorWithPatternImage是用来建立小的重复的图片做为背景的。这种情形下使用UIImageView能够节约很多的内存:
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]]; [self.view addSubview:backgroundView];
若是你用小图平铺来建立背景,你就须要用UIColor的colorWithPatternImage来作了,它会更快地渲染也不会花费不少内存:
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
15. 减小使用Web特性
UIWebView颇有用,用它来展现网页内容或者建立UIKit很难作到的动画效果是很简单的一件事。
可是你可能有注意到UIWebView并不像驱动Safari的那么快。这是因为以JIT compilation为特点的Webkit的Nitro Engine的限制。
因此想要更高的性能你就要调整下你的HTML了。第一件要作的事就是尽量移除没必要要的javascript,避免使用过大的框架。能只用原生js就更好了。
另外,尽量异步加载例如用户行为统计script这种不影响页面表达的javascript。
最后,永远要注意你使用的图片,保证图片的符合你使用的大小。使用Sprite sheet提升加载速度和节约内存。
16. 优化Table View
Table view须要有很好的滚动性能,否则用户会在滚动过程当中发现动画的瑕疵。
为了保证table view平滑滚动,确保你采起了如下的措施:
· 正确使用reuseIdentifier
来重用cells
· 尽可能使全部的view opaque,包括cell自身
· 避免渐变,图片缩放
· 缓存行高
· 若是cell内现实的内容来自web,使用异步加载,缓存请求结果
· 使用shadowPath
来画阴影
· 减小subviews的数量
· 尽可能不适用cellForRowAtIndexPath:
,若是你须要用到它,只用一次而后缓存结果
· 使用正确的数据结构来存储数据
· 使用rowHeight
, sectionFooterHeight
和 sectionHeaderHeight
来设定固定的高,不要请求delegate
17. 使用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对象
18. 选择是否缓存图片
常见的从bundle中加载图片的方式有两种,一个是用imageNamed
,二是用imageWithContentsOfFile
。
既然有两种相似的方法来实现相同的目的,那么他们之间的差异是什么呢?
imageNamed
的优势是当加载时会缓存图片。imageNamed
的文档中这么说:这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象若是它存在的话。若是缓存中没有找到相应的图片,这个方法从指定的文档中加载而后缓存并返回这个对象。
相反的,imageWithContentsOfFile
仅加载图片。
下面的代码说明了这两种方法的用法:
UIImage *img = [UIImage imageNamed:@"myImage"];// caching // or UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching
那么咱们应该如何选择呢?
若是你要加载一个大图片并且是一次性使用,那么就不必缓存这个图片,用imageWithContentsOfFile
,这样不会浪费内存来缓存它。
然而,在图片反复重用的状况下imageNamed
是一个好得多的选择。
这是个人iOS开发交流群:519832104无论你是小白仍是大牛欢迎入驻,能够一块儿分享经验,讨论技术,共同窗习成长!
另附上一份各好友收集的大厂面试题,须要iOS开发学习资料、面试真题,进群便可自行下载!
点击此处,当即与iOS大牛交流学习