关于所有权
所有权是iPhone内存管理的核心思想,对象的所有者负责在使用完对象后进行释放。一个对象可以有多个所有者,当它没有所有者时将被设置为取消分配(deallocation)。
创建对象时,所有权通过alloc、new、或者copy的方式建立,之后通过调用retain或者通过Cocoa函数来分配和复制对象的所有权。内存释放有两种方式,一种方法是明确地请求释放对象的所有权,另一种方法则是使用自动释放池(auto-releasepool)。
所有权的背后是一个和引用有关的运算系统,iPhonesDK的大多数对象使用这个系统,彼此之间建立着很强的引用和参照。
当你创建一个对象时,引用值为1,调用一次retain则对象的引用值加1,调用一次release则对象的引用值减1,当引用值为0时,对象的所有权分配将被取消。使用自动释放池意味着对象的所有权将在一段延后的时间内被自动取消。
对象之间也可以建立弱的引用参照,此时意味着,引用值不会被保留,对象的分配需要手动取消。
什么时候使用retain?
什么时候你想阻止对象在使用前就被释放?
每当使用copy、alloc、retain、或者Cocoa函数来创建和复制所有权,你都需要相应的release或者auto-release。
开发者应该从所有权的角度来考虑对象,而不必担心引用值。只要你有相应的retain和release方法,就能够对引用值进行+1和-1操作。
注意:你或许想使用[objectretainCount],但它可能因为SDK的底层代码而发生返回值出错的情况。在内存管理时不推荐这种方式。
copy 和 retain区别:
copy其实是建立了一个相同的对象,而retain不是:
比如一个Nsstring对象,地址为0×1111,内容为@”STR”
copy到另外一个Nsstring之后,地址为0×2222,内容相同,新的对象retain为1,旧有对象没有变化
retain到另外一个Nsstring之后,地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1
也就是说,retain是指针拷贝,copy是内容拷贝。
自动释放
为什么要创建自己的自动释放池?
iPhone上有便利的构造函数,用这种方法创建的对象会设置为自动释放。
例子:
1.
2.
3.
或者用下面的方法:
当指针出界,或者当自动释放池清空时,自动释放对象上的所有权将被取消。
在一个事件循环结束时,自动释放池内的构件通常会被清空。但是当你的循环每次迭代都分配大量内存时,你或许希望这不要发生。这种情况下,你可以在循环内创建自动释放池。自动释放池可以嵌套,所以内部池清空时,其中分配的对象将被释放。在下面的例子中,每次迭代后将释放对象。
1.
2.
3.
4.
5.
6.
7.
注意:在编写的时候iPhone不支持垃圾回收,所以drain和release的功能相同。当你想为程序设置OSX的端口时通常会使用drain,除非后来在iPhone中添加了垃圾回收机制。Drain能够击发垃圾回收器释放内存。
返回一个对象的指针
开发者在遵循所有权规则时需要清楚哪些函数拥有对象的所有权。下面是返回一个对象的指针并释放的例子。
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
在这个例子中,output 的所有者是 Getoutput,让 Test 释放 obj违反了Coccoa内存管理指南中的规则,尽管它不会泄露内存但是这样做不好,因为Test不应该释放并非它所拥有的对象。
正确的方法:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
在第二个例子中,output 被设置为当 Getoutput返回时自动释放。output的引用值减少,Getobject 释放 output 的所有权。Test 函数现在可以自由的 retain和 release 对象,请确保它不会泄露内存。
例子中obj 被设置为自动释放,所以 Test 函数没有它的所有权,但是如果它需要在其他地方存储对象会怎样?
此时对象需要有一个新的所有者来保留。
setter函数必须保留它所存储的对象,也就是声明所有权。如果我们想要创建一个 setter函数,我们需要在分配一个新的指向成员变量的指针之前做两件事情。
在函数里:
1.
首先我们要减少成员变量的引用值:
1.
这将允许当引用值为0时 name 对象被释放,但是它也允许对象的其他所有者继续使用对象。
1.
所以当setName 结束时, newName 不会被取消分配。 newName 现在指向的对象和 name指向的对象不同,两者有不同的引用值。
现在我们设置 name 指向 newName 对象:
1.
但是如果name 和 newName 是同一个对象时怎么办?我们不能在它被释放后保留它,并再次释放。
在释放存储的对象前保留新的对象:
1.
2.
3.
现在两个对象是相同的,先增加它的引用值,然后再减少,从而使得赋值前引用值不变。
另一种做法是使用 objective-c:
声明如下:
1.
1.nonatomic 表示没有对同一时间获取数据的多个线程进行组块儿。Atomic 为一个单一的线程锁定数据,但因为 atomic的方式比较缓慢,所以不是必须的情况一般不使用。
2.retain 表示我们想要保留 newName 对象。
我们可以使用 copy 代替 retain:
这和下面的函数一样:
1.
2.
3.
4.
6.
8.
newName 在这里被复制到 copiedname,现在 copiedname 拥有串的一个副本。name被释放,而 copiedname 被赋给 name。之后 name 保留这个串,从而使得 copiedname 和 name同时拥有它。最后 copiedname 释放这个对象,name 成为这个串的唯一所有者。
如果我们有如下的函数,像这样的 setters 将被输入用来保留成员对象:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
name在调用至 drain 后是未定义的,因为当池被释放时,name 也将被释放。
如果我们用如下的部分替代赋值:
1.
然后name 将被这个类所有,在使用时保留直到调用 release
那么我们何时释放对象?
由于name 是成员变量,释放它的最安全的办法是对它所属的类使用 dealloc 函数。
1.
2.
3.
4.
5.
注意:虽然并不总是调用 dealloc,依靠 dealloc来释放对象可能是危险,可能会触发一些想不到的事情。在出口处,iPhone OS 可能在调用 dealloc前清空全部应用程序的内存。
当用setter 给对象赋值时,请小心下面的语句:
1.
name的设置是正确的但 alloc 没有相应的释放,下面的方式要好一些:
或者使用自动释放:
1.
自动释放池
自动释放池,它是一个存放实体的池,这些实体可能是对象,能够被自动释放。于是,NSObject类提供了一个autorelease的方法,该方法预先设定了一条在某个时间发送的release消息,其返回值是接收到消息的对象。当给一个对象发送autorelease消息的时候实际上是将该对象添加到NSAutoreleasePool中。当自动释放池被销毁时,会向该池中的所有对象发送release消息。
我们在下面的函数中设置一个循环,在循环中将 NSNumber 的一个副本赋给 magicNumber,另外将magicNumber设置为自动释放。在这个例子中,我们希望在每次迭代时清空自动释放池(这样可以在赋值的数量很大时节省循环的内存)
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
这里存在的问题是 clientName在本地的自动释放池中被赋值和释放,所以当外部的池清空时,clientName 已经被释放了,任何对 clientName的进一步使用都是没有定义的。
在这个例子中,我们在赋值后保留 clientName,直到结束时再释放它:
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
我们在调用 retain 函数和 release 函数的期间获得 clientName 的所有权。通过添加一对retain 和 release 的调用,我们就确保 clientName 在明确调用释放前不会被自动释放。
然后我们将它添加进数组,现在它有两个所有者:
1.
我们可以安全的释放这个串,使其仅被数组所有:
1.
当一个集合被释放时,其中的所有对象都将被释放。
1.
2.
3.
4.
在上面的例子中,我们分配了一个数组和一个串,然后将串添加到数组中并释放数组。这使得串仅拥有一个所有者,并且在我们调用[str release] 前它不会被释放。
在这个函数中,我们从串的 input 传递到函数 DoSomething,然后释放input
1.
2.
3.
4.
5.
6.
detatchNewThreadSelector
1.
2.
3.
4.
performSeclectorOnMainTh
自动释放池是特殊的线程,所以如果我们在一个新的线程上创建自动释放的对象,我们需要创建一个自动释放池来释放它们。
1.
1.
2.
3.
4.
5.
6.
7.
8.
对象output 被分配并且在自动释放池中设置了自动释放,它将在函数结束前被释放。
1.
2.
3.
4.
5.
系统会为主线程自动创建一个自动释放池,所以在 FinishProcess中,我们不需要为主线程上运行的函数创建自动释放池。
总结
为了在你的iPhone中避免内存泄露,你必须要清楚每个被分配对象的所有者是谁,要明白什么时候释放所有权,并且还要始终按对设置 retain 和release,这三点非常重要。如果你遵循所有权的规则,你的应用将更加稳定并且因为 bug的减少而节省大量时间。
创建对象
下面是一些小知识点:
- 当你想暂时保留对象时,使用 autolease
- (Money*)showMeTheMoney:(double)amount {
Money *theMoney = [[Money alloc] init:amount];
[theMoney autorelease];
return theMoney;
} - 集合类的 autolease,一种方法是像对象一样调用autolease,另外也可以调用 [NSMutableArray array],最好的方式 return [NSArrayarrayWithObjects:@“Steve”,@“Ankush”,@“Sean”,nil];,其他类似的方法返回的对象都是autolease 的对象。
[NsstringstringWithFormat:@“Meaning of %@ is %d”,@“life”,42];
[NSDictionary dictionaryWithObjectsAnd Keys:ankush,@“TA”,janestudent,@“Student”,nil];
[NSArrayarrayWithContentsOfFile:(Nsstring *)path]; - 集合类对新增的对象拥有 ownership
- @"string" 是 autorelease 的
- Nsstring 一般是 copye 而不是 retain
- 你应该尽快 release 你拥有的对象,越快越好。建议创建完对象后就写好release 的代码
- 当最后一个对象 owner release 后,会自动调用 dealloc函数,在类中需要重载 dealloc,但永远都不要自己去调用 dealloc
- @property 一般直接返回对象变量,我们可以把它理解为返回的是autolease 的对象
- 使用 @synthesize 实现时,@property 可以指定 setter函数使用 retain,copy 或 assign。assign 一般用在属性一定会随着对象的消亡而消亡的,比如 controller的view,view 的 delegate
- Protocols 可以理解为抽象接口,delegat 和 dataSource基本都是用 protocol 定义的
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。