iOS内存问题

1. 用ARC管理内存

ARC(Automatic ReferenceCounting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露。它自动为你管理retain和release的过程,所以你就不必去手动干预了.除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存。

2.避免过于庞大的XIB

iOS5中加入的Storyboards(分镜)正在快速取代XIB。然而XIB在一些场景中仍然很有用。比如你的app需要适应iOS5之前的设备,或者你有一个自定义的可重用的view,你就不可避免地要用到他们。

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

注意:当你加载一个XIB的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。Storyboards就是另一码事儿了,storyboard仅在需要时实例化一个view controller.

3.常见的加载图片的方式有两种,一个是用`imageNamed`,二是用`imageWithContentsOfFile`,第一种比较常见一点。

差别:`imageNamed`的优点是当加载时会缓存图片。`imageNamed`的文档中这么说:这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。

相反的,`imageWithContentsOfFile`仅加载图片。

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

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

// or

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

如果你要加载一个大图片而且是一次性使用,那么就没必要缓存这个图片,用`imageWithContentsOfFile`足矣,这样不会浪费内存来缓存它。

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

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

当存储大块数据时"

· 使用`NSUerDefaults`:虽然它很nice也很便捷,但是它只适用于小数据,比如一些简单的布尔型的设置选项,再大点你就要考虑其它方式了

· 使用XML, JSON, 或者 plist :你需要读取整个文件到内存里去解析,这样是很不经济的。使用SAX又是一个很麻烦的事情。

· 使用NSCoding存档 :不幸的是,它也需要读写文件,所以也有以上问题。

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

· 使用类似SQLite的本地SQL数据库 

· 使用 Core Data

在性能层面来讲,SQLite和Core Data是很相似的。他们的不同在于具体使用方法。Core Data代表一个对象的graph model,但SQLite就是一个DBMS。Apple在一般情况下建议使用Core Data,但是如果你有理由不使用它,那么就去使用更加底层的SQLite吧。

如果你使用SQLite,你可以用FMDB(https://GitHub.com/ccgus/fmdb)这个库来简化SQLite的操作,这样你就不用花很多经历了解SQLite的 API了。

5. 减少使用Web特性

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

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

尽可能移除不必要的javascript,避免使用过大的框架。能只用原生js就更好了。

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

javascript:通过JS,把一个页面全我们想要的意思进行渲染,执行相应的功能,生成相应的效果,比如,显示当前系统时间,比如,显示实时信息的更新.最后,永远要注意你使用的图片,保证图片的符合你使用的大小。使用Sprite sheet提高加载速度和节约内存。

6.避免反复处理数据

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

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

类似的,如果需要从特定key中取数据,那么就使用键值对的dictionary。

7.IBOutlet 对象需要release

8.特别是UIScrollView上add相同SubView。一定要记得清除之前的SubView,并且在dealloc函数中执行该方法

for (UIView* sbViewin scrvBg.subviews)

{

[sbView removeFromSuperview];

}

这里还有个获得subView的小技巧:

[subView setTag:300];

subView = [self.viewviewWithTag:300]

9.dealloc不一定会被调用,所以可以自己手写一个myRelease方法,当退出该界面的时候手动调用release需要释放的对象,并且将其置为nil。

10.记住:如果你不太明白UIView的drawRect的调用时机,千万不要轻易往drawRect里写代码,特别是没有立即release的对象。很容易在这里因为多次调用了drawRect而没有release该对象导致内存溢出。

iOS的绘图操作是在UIView类的drawRect方法中完成的

iOS平台的内存管理采用引用计数的机制;

当创建一个对象时使用alloc或者allWithZone方法时,引用计数就会+1;当释放对象使用release方法时,引用计数就是-1; 这就意味着每一个对象都会跟踪有多少其他对象引用它,一旦引用计数为0,该对象的内存就会被释放掉;另外,iOS也提供了一种延时释放的机制 AutoRelease,以这种方式申请的内存,开发者无需手动释放,系统会在某一时机释放该内存; 开发者在内存使用上很容易出现内存泄漏或者程序莫名崩溃的情况

2 iOS平台内存使用原则

如果是以alloc,new或者copy,mutableCopy创建的对象,则必须调用release或者autorelease方法释放内存;如果没有释放,则导致内存泄漏!

2.1.2 谁retain,谁释放;

如果对一个对象发送 retain消息,其引用计数会+1,则使用完必须发送release或者autorelease方法释放内存或恢复引用计数;如果没有释放,则导致内存泄漏!

2.1.3 没创建且没retain,别释放;

不要释放那些不是自己alloc或者retain的对象,否则程序会crash;

不要释放autorelease的对象,否则程序会crash;

2.2 对象的深拷贝与浅拷贝

一般来说,复制一个对象包括创建一个新的实例,并以原始对象中的值初始化这个新的实例。 复制非指针型实例变量的值很简单,比如布尔,整数和浮点数。复制指 针型实例变量有两种方法。一种方法称为浅拷贝,即将原始对象的指针值复制到副本中。因此,原始对象和副本共享引用数据。另一种方法称为深拷贝,即复制指针 所引用的数据,并将其赋给副本的实例变量。

2.2.1 深拷贝

深拷贝的流程是 先创建一个新的对象且引用计数为1,并用旧对象的值初始化这个新对象;

ClassA* objA = [[ClassA alloc] init];

ClassA* objB = [objA copy];

objB是一个新对象,引用计数为1,且objB的数据等同objA的数据;

注意: objB需要释放,否则会引起内存泄漏!

2.2.2 浅拷贝

浅拷贝的流程是,无需引入新的对象,把原有对象的引用计数+1即可

ClassA* objA = [[ClassA alloc] init];

ClassA* objB = [objA retain];

注意: objB需要释放,恢复objA的引用计数,否则会引起内存泄漏!

2.3对象的存取方法2.3.1 属性声明和实现

变量声明的常用属性类型包括readonly,assign,retain和copy;且系统会自动为声明了属性的变量生成set和get函数;

readonly属性: 只能读,不能写;

assign属性: 是默认属性,直接赋值,没有任何保留与释放问题;

retain属性: 会增加原有对象的引用计数并且在赋值前会释放原有对象,然后在进行赋值;

copy属性: 会复制原有对象,并在赋值前释放原有对象,然后在进行赋值;

2.3.2 使用属性声明可能带来的隐患

当一个非指针变量使用retain(或者copy)这个属性时,尽量不要显性的release这个变量;直接给这个变量置空即可;否则容易产生过度释放,导致程序crash; 例如:

ClassA类的strName是NSString* 类型的变量且声明的属性为retain;

ClassA.strName = nil; /* 释放原有对象且对此对象赋值为空 */

[ClassA.strName release]; /* strName内存可能已经被释放过了,将导致程序crash */

Assign这个属性一般是非指针变量(布尔类型,整形等)时用这个类型;属于直接赋值型,不需要考虑内存的保留与释放;

如果一个指针类型的变量使用assign类型的属性,有可能引用已经释放的变量;导致程序crash; 例如:

ClassB* obj =[[[ClassB alloc] init] autorelease];

ClassA.strName = obj; /* strName 指向obj的内存地址*/

后续在使用ClassA.strName的时候, 因为obj是autorelease的,可能obj的内存已经被回收;导致引用无效内存,程序crash;

3iOS平台AutoRelease机制

3.1 自动释放池的常见问题

大家在开发iOS程序的时候,是否遇到过在列表滑动的情况内存莫名的增长,频繁访问图片的时候内存莫名的增长,频繁的打开和关闭数据库的时候内存莫名的增长…… 这些都是拜iOS的autorelease机制所赐;具体分析如下:

1: 滑动列表的时候,内存出现莫名的增长,原因可能有如下可能:

1:没有使用UITableView的reuse机制; 导致每显示一个cell都用autorelease的方式重新alloc一次; 导致cell的内存不断的增加;

2:每个cell会显示一个单独的UIView, 在UIView发生内存泄漏,导致cell的内存不断增长;

2: 频繁访问图片的时候,内存莫名的增长;

频繁的访问网络图片,导致iOS内部API,会不断的分配autorelease方式的buffer来处理图片的解码与显示; 利用图片cache可以缓解一下此问题;

3: 频繁打开和关闭SQLite,导致内存不断的增长;

在进行SQLite频繁打开和关闭操作,而且读写的数据buffer较大,那么 SQLite在每次打开与关闭的时候,都会利用autorelease的方式分配51K的内存; 如果访问次数很多,内存马上就会顶到几十兆,甚至上百兆! 所以针对频繁的读写数据库且数据buffer较大的情况, 可以设置SQLite的长连接方式;避免频繁的打开和关闭数据库;

3.2 自动释放池的概念

NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声名为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。

ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此对象加入autorelease pool中

NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数 组,release数组中的每个成员。如果此时数组中成员的retain count为1,那么release之后,retain count为0,对象正式被销毁。如果此时数组中成员的retain count大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。

3.3 自动释放池的作用域与嵌套

AutoreleasePool是可以嵌套使用的!

池是被嵌套的,嵌套的结果是个栈,同一线程只有当前栈顶pool实例是可用的:

当短生命周期内,比如一个循环中,会产生大量的临时内存,可以创建一个临时的autorelease pool,这样可以达到快速回收内存的目的;

3.4 自动施放池的手动创建与自动创建

3.4.1 需要手动创建自动释放池

●如果你正在编写一个不是基于Application Kit的程序,比如命令行工具,则没有对自动释放池的内置支持;你必须自己创建它们。

●如果你生成了一个从属线程,则一旦该线程开始执行,你必须立即创建你自己的自动释放池;否则,你将会泄漏对象。

●如果你编写了一个循环,其中创建了许多临时对象,你可以在循环内部创建一个自动释放池,以便在下次迭代之前销毁这些对象。这可以帮助减少应用程序的最大内存占用量。

3.4.2 系统自动创建自动释放池

Application Kit会在一个事件周期(或事件循环迭代)的开端—比如鼠标按下事件—自动创建一个自动释放池,并且在事件周期的结尾释放它.

4 iOS平台内存使用陷阱

4.1 重复释放

在前文已经提到,不要释放不是自己创建的对象;

释放自己的autorelease对象,app会crash;

释放系统的autorelease对象,app会crash;

4.2 循环引用

循环引用,容易产生野引用,内存无法回收,最终导致内存泄漏!可以通过弱引用的方式来打破循环引用链;所谓的弱引用就是不需要retain,直接赋值的方式,这样的话,可以避免循环引用的问题,但是需要注意的是,避免重复释放的问题;

5 iOS平台内存报警机制

由于iOS平台的内存管理机制,不支持虚拟内存,所以在内存不足的情况,不会去Ram上 创建虚拟内存;所以一旦出现内存不足的情况,iOS平台会通知所有已经运行的app,不论是前台app还是后台挂起的app,都会收到 memory warning的notice;一旦app收到memory warning的notice,就应该回收占用内存较大的变量;

5内存报警处理流程

1: app收到系统发过来的memory warning的notice;

2: app释放占用较大的内存;

3: 系统回收此app所创建的autorelease的对象;

4: app返回到已经打开的页面时,系统重新调用viewdidload方法,view重新加载页面数据;重新显示;

5.2 内存报警测试方法

做iOS开发在模拟器上可以通过 Hardware -> Simulate Memory Warning 模拟内存警告

开发者可以在模拟器上来模拟手机上的低内存报警情况,可以避免由于低内存报警引出的app的莫名crash问题;

6 iOS平台内存检查工具

6.1 编译和分析工具Analyze

iOS的分析工具可以发现编译中的warning,内存泄漏隐患,甚至还可以检查出logic上的问题;所以在自测阶段一定要解决Analyze发现的问题,可以避免出现严重的bug;

内存泄漏隐患提示:

Potential Leak of an object allocated on line ……

数据赋值隐患提示:

The left operand of …… is a garbage value;

对象引用隐患提示:

Reference-Counted object is used after it is released;

以上提示均比较严重,可能会引起严重问题,需要开发者密切关注!

6.2 内存检测工具

内存泄漏检测工具—Leak

Leak工具可以很容易的统计所有内存泄漏的点,而且还可以显示在那个文件,哪行代码有 内存泄漏,这样定位问题比较容易,也比较方面;但是Leak在统计内存泄漏的时候会把autorelease方式的内存也统计进来; 所以我们在查找内存泄漏情况的时候,可以autorelease的情况忽略掉;

Leak工具:通过Leak工具可以很快发现代码中的内存泄漏,通过工具也可以很快找到发生内存泄漏的代码段:

6.2内存猛增检测工具—Allocations

Allocations工具可以很容易的列出所有分配内存的点,这样我们可以按照分配内存大小来进行排序, 这样可以很容易的发现哪些点分配的内存最多,而且是持续分配,这样我们来针对性的分析这些持续分配较大内存的地方;

你可能感兴趣的:(iOS内存问题)