订阅移动开发RSS CSDN首页> 移动开发

【书摘】iPad应用开发实战:内存管理

发表于2011-06-07 16:32| 次阅读| 来源CSDN| 0 条评论| 作者李晨

摘要:导读:本文节选自《iPad应用开发实战》的其中一章,主要对iOS的内存管理进行了细致的讲解。本书由资深iOS开发专家(现就任于美国Apple公司)撰写。 1 对象所有权、引用计数与释放 当一个所
导读:本文节选自《iPad应用开发实战》的其中一章,主要对iOS的内存管理进行了细致的讲解。本书由资深iOS开发专家李晨(现就任于美国Apple公司)撰写。

1   对象所有权、引用计数与释放

当一个所有者(owner,其本身可以是任何一个Objective-C对象)做了以下某个动作时,它就拥有了对一个对象的所有权(ownership):

1)创建一个对象。包括使用任何名称中包含“alloc”、“new”或者“copy”的方法。

2)保留(retain)一个对象。

一个对象可以有多个所有者,一个所有者也可以拥有多个对象。

相应地,引用计数(reference count)增减的基本规则是:

1)当所有者创建一个对象时,该对象的引用计数为1。

2)当所有者保留它时,该对象的引用计数加1。

3)当所有者释放(release)它时,该对象的引用计数减1。

与此相关,当一个所有者对于一个对象的引用计数的增减总计为0时,它就放弃了对这个对象的所有权。 现在可以从两个不同的角度来看Objective-C的内存管理问题。从对象所有权的角度来看,当一个对象有着至少一个所有者的时候,它依然存在;当它没有任何所有者的时候,它会被释放掉。从引用计数的角度看,一个对象存在时,其引用计数大于零;当一个对象的引用计数为零时,它会调用dealloc方法并被释放掉。这两个角度的关系是:在所有权的背后起作用的机制是引用计数机制,通过引用计数的增减来理解所有权的概念。只能使用所有权的概念来管理内存,如果试图直接获取对象的引用计数,那么得到的数将会令人意外,因为系统的一些框架会“偷偷”增减对象的引用计数。

回顾一下,内存管理的目标是:当一个对象的某个所有者依然需要使用它时,要保证这个对象的存在;当一个对象的所有所有者都不再需要它时,要保证这个对象被销毁。因此只要任何一个所有者在使用完一个对象之后释放掉它,那么以上内存管理的目标就可以实现。由此可以得出任何一个所有者(记住,所有者本身也只是一个对象)所应当遵守的基本步骤:

拥有一个对象→使用一个对象→放弃对象的所有权(释放对象)

从引用计数的角度来看就是:

还需要这个对象时,保持对其增减为正;不再需要这个对象时,不增减其引用计数值。

如下图所示,所有者1和所有者2单独地执行了拥有对象、使用对象、放弃对象所有权的步骤;当所有者1不再需要该对象时及时放弃了所有权,但此时所有者2依然拥有该对象,因此该对象依然存在,所有者2可以继续使用它;当所有者2也不再需要该对象时,也放弃所有权,这时对象不再有任何所有者(相应的引用计数变为0),因此立刻被销毁掉。

两个所有者对同一个对象的内存管理

需要注意的是,所有者2只是复制了该对象的指针,并没有使用copy方法,因此复制指针这个操作本身并不增加对象的引用计数。而正因为所有者2希望能使用该对象,因此通过retain方法成为它的所有者,也保证了在所有者1放弃该对象时,该对象不被销毁。

2   自动释放与便捷方法

有时候,一个所有者创建一个对象后,会立刻将该对象的指针传递给其他所有者。这时,这个创建者不希望再拥有这个对象,但如果立刻给它发送一个release(释放)消息,则这个对象被立刻释放掉,这样会导致其他所有者还没有来得及保留该对象。解决这个两难问题的方法是,给对象发送一个autorelease(自动释放)消息,这样创建者不再拥有该对象的所有权;该对象成为自动释放的对象,但是不会立刻被释放掉,其他所有者可以有时间保留或复制该对象,并成为其唯一所有者。

来看一个自动释放的例子。一个所有者先用alloc方法创建一个对象,此时该所有者拥有这个对象,对象的引用计数为1。紧接着,所有者自动释放该对象,即放弃了所有权,但对象的引用计数在一段时间内依然为1。我们可以看出自动释放的另一个好处,就是不会因为在后面忘记给对象发送release消息而造成内存泄露。相关代码如下所示:

-(Object*)returnAutoreleaseObject {     Object* obj = [[Object alloc] init];     return [obj autorelease]; }

与自动释放相关的有一大类构造方法,由它们构造的对象直接就是自动释放的对象,这一类构造方法叫做便捷方法。比如下面这个字符串就是一个自动释放的对象,stringWithFormat:就是一个便捷方法。

NSString* string = [NSString stringWithFormat:@"autoreleaseString"];

再举几个便捷方法的例子,方便读者以后的开发。

q NSArray的arrayWithObjects:和arrayWithArray。

q UIImage的imageNamed。

q NSNumber的numberWithBool。

如上所述,autorelease方法会在一段时间以后释放掉一个对象,在这段时间内可以安全地使用该对象。那么这段时间究竟是多久呢?我们需要先更多地了解自动释放的机制,再来回答这个问题。

先来看看自动释放池。自动释放池是NSAutoreleasePool的实例,其中包含了收到autorelease消息的对象。当一个自动释放池自身被销毁(dealloc)时,它会给池中每一个对象发送一个release消息(如果给一个对象多次发送autorelease消息,那么当自动释放池销毁时,这个对象也会收到同样数目的release消息)。可以看出,一个自动释放的对象,它至少能够存活到自动释放池销毁的时候。

那么自动释放池何时被创建,又何时被销毁呢?在每一个事件周期的开始,系统会自动创建一个自动释放池,在每一个事件周期的结尾,系统会自动销毁这个自动释放池。一般情况下,可以理解为:当用户的代码在持续运行时,自动释放池是不会被销毁的,这段时间内用户可以安全地使用自动释放的对象。当用户的代码运行告一段落,开始等待用户输入(或者其他事件)时,自动释放池就会被释放掉,池中的对象都会收到一个release消息,有的可能会因此被销毁。

到此为止,已经对自动释放的机制有了一个大体的了解。自动释放而非直接释放,可以节省一些代码量,提高开发速度。但是它有一个明显的缺点:它延缓了对象的释放,在有大量自动释放的对象时,会占用大量内存资源。因此,需要避免将大量对象自动释放。并且,在以下两种情况下,需要手动建立并手动销毁掉自动释放池。

q 当在主线程外开启其他线程时,系统只会在主线程中自动生成并销毁自动释放池。

q 在短时间内制造了大量自动释放对象时,及时销毁有助于有效利用iPad上有限的内存资源。

3   访问器方法与属性

当需要访问一个对象的成员变量时,不是直接访问这个成员变量,而是借助于访问器方法(accessor method),这样做的好处是帮助实现了代码的封装。可是,在内存管理相对复杂的环境中,要为一个变量实现访问器方法是一件非常烦琐的事情,如果手动写代码来实现所有的访问器方法,将会大大降低效率,并容易发生失误。如果使用一些通用的访问器方法在访问成员变量时帮助管理内存,那么将大大提高开发的效率并可防止内存泄漏的发生。

在Objective-C中,可以很方便地为对象作出属性声明(property declaration),然后让编译器去合成(synthesize)对象的访问器方法。比如属性声明如下:

@property int value;

就等价于如下两个访问器方法的声明:

-(int)value;

-(void)setValue:(int)newValue;

而以下指令将告诉编译器去合成这两个访问器方法:

@synthesize value;

注意,编译器是按照@property后面跟随的属性来确定如何合成访问器方法的,因此为变量声明属性至关重要。下面介绍几组属性。

q 可写性(Writability)

readwrite:指定这个属性会让编译器合成getter和setter;如果不指定readonly,那么readwrite将为默认属性。

readonly:指定这个属性会让编译器只合成getter,而不合成setter。在这种情况下,如果使用点语法(dot syntax)来给变量赋值,编译器会报错,提醒这个变量具有只读属性。

在定义读写属性时应当注意:对于外界只读取而不修改的变量,尽量使用readonly属性;外界毫不关心的变量,则不要为其设置任何属性。这样封装能增加代码的独立性和安全性。

q 存入器属性(Setter Semantics)

assign:表示在setter中只使用简单的赋值,而不会发送retain消息给对象。这是默认属性。当变量类型为scalar时(比如int、CGRect),应当指定这个属性。

retain:表示在setter中将向旧值发送release消息,向新值发送retain消息。

copy:表示在setter中将向旧值发送release消息,向新值发送copy消息。

4   改变引用计数的特殊情况

在iOS开发中,当使用外部接口(IBOutlet)时,应当使用如下属性声明:

@property (nonatomic,retain) IBOutlet IBObject* object;

以上模式只适用于iOS开发。针对MacOS开发,应当采用assign属性。

在nib文件(也就是xib文件)中的对象被创建时的引用计数为1,然后它们会被自动释放。随后,UIKit重建对象之间的结构,会用到可用的setter(如果没有setter的话,会自行retain对象),这样外部接口对象的引用计数依然为1,所以即使没有写代码来初始化它们或者保留它们,仍然需要在dealloc方法中释放它们(事实上,在前两章的例子中已经这样做了)。

在3.1和3.2节中提到,当一个对象被创建时引用计数为1,被retain时引用计数加1,被release时引用计数减1,被autorelease后引用计数减1。但这些不是会引起引用计数发生变化的全部情形。比如,Nib对象的使用会“悄悄”改变引用计数。事实上在其他一些特殊情况下,引用计数也会被改变。

q NSArray:当把一个对象添加到NSArray时,对象的引用计数会加1;反之当把对象从NSArray移除时,对象的引用计数会减1。同样的规则也适用于其子类NSMutableArray。

q UIView:当一个视图对象使用addGestureRecognizer:添加手势识别器时,手势识别器的引用计数会加1;反之使用removeGestureRecognizer:时,手势识别器的引用计数会减1。类似地,当视图对象使用addSubview:和removeFromSuperView时,对象的引用计数也会发生变化。

总结一下,容器(container)使用名字中带有“add”的方法常常会增加对象的引用计数,使用名字中带有“remove”的方法常常会减少引用计数。容器指的是数组(NSArray)、字典(NSDictionary)、集合(NSSet)等,它们用于集中管理大量对象。

5   内存管理总结

下面来总结一下内存管理规则:

1)一个对象的引用计数为正时,它依然存在;当它的引用计数为0时,就被销毁。

2)一个对象通过alloc、create、copy和retain等方法成为另一个对象的所有者。

3)当一个所有者使用完一个对象之后,应当放弃对这个对象的所有权。放弃所有权的方法是发送release给这个对象,使得所有者对这个对象的引用计数的增减为0。

4)一个对象可能有多个所有者。这意味着,当一个所有者放弃所有权后,对象可能依然存在。只有当全部所有者放弃所有权时,对象才被销毁。

5)给对象发送autorelease消息,意味着这个对象的引用计数将在未来减1。

6)使用快捷方法创建对象后,不用给对象发送release消息。

7)使用属性声明,并且让编译器来合成访问器方法。

8)为外部接口作出nonatomic和retain的属性声明,并记得release它们。

9)一些容器(如NSArray和UIView的实例)会通过add方法增加对象的引用计数,通过remove方法减少对象的引用计数。

10)当自己开辟线程,或者建立大量临时的自动释放对象时,需要手动建立自动释放池并销毁它。

掌握了这些规则,再通过之后的实战加深对iOS内存管理的理解,就能够开发出高效率的iPad应用了!

0
0
【书摘】iPad应用开发实战:内存管理
  • CSDN官方微信
  • 扫描二维码,向CSDN吐槽
  • 微信号:CSDNnews
程序员移动端订阅下载

微博关注

相关热门文章