什么样的对象会被加入到自动释放池?

2,348 阅读4分钟

问题描述

之前被别人问道,什么样的对象会在自动释放池结束的时候释放?我当时想到的只有两种情况:

  1. arc下,在一个函数里,初始化一个NSobject的局部对象,这个对象在函数结尾的地方就释放了,这个应该不是。
  2. stringwithformat生成的字符串,在viewwillappear还存在,到didappear才消失,所以这个应该是延迟释放了。

但是其他的是什么样呢,其实我之前是看了sunny的这篇博客。照着博客的代码试了一下,发现不是这么回事,然后又看到这里。大概意思是说: NSString 是个类簇,几个初始化方法返回的string类型有下边三种:

类型 初始化后的retaincount
__NSCFConstantString 1
__NSCFString 1
__NSTaggedPointerString -1
  • 对于__NSCFConstantString,系统进行维护,retain和release不起作用(程序中内容相同的常量字符串只有一个),直观理解就是一个单例。它存在于字符串常量区。
  • __NSCFString与其他oc对象一样维护retainCount,需要注意,copy方法是把引用计数加1,然后返回原地址,mutablecopy是一个深拷贝,新对象引用计数加一,返回一个新的地址。
  • __NSTaggedPointerString,retain/release不起作用,其实就是内存地址里存了要存的数据,算是一个伪对象。

具体可以参考这里,sunny那段代码运行环境应该是mrc,我自己试了一下,如果string是__NSCFString类型的话,打印是正常的。

引用计数规则

之后本着严谨的态度,我查了苹果文档关于引用计数的部分: 这里讲述了引用计数的几个原则:

  • 你创建你持有:你用alloc, init,copy,new等创建的话,在你不用的时候你要release。
  • You can take ownership of an object using retain,以下是使用retain的两种情况: 1.设置属性或者初始化一个对象时, 2.为了保证使用这个对象期间,对象不被释放。
  • When you no longer need it, you must relinquish ownership of an object you own。不用的时候,记得释放,可以用release或者是autorelase。
  • You must not relinquish ownership of an object you do not own 你不能释放你不持有的对象。

以下是例子:

{
    Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];
}

 - (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
 self.firstName, self.lastName] autorelease];
    return string;
}

调用这个方法的对象从名称看不持有它(没有alloc,init关键字),所以我们应该自己管理它,用autorelase。 以下是autolease的官方文档注释: Decrements the receiver’s retain count at the end of the current autorelease pool block.

它会在自动释放池出栈的时候给对象发送release,从而使对象延迟释放。

- (NSString *)fullName {
    NSString *string = [NSString stringWithFormat:@"%@ %@",
     self.firstName, self.lastName];
    return string;
}

文档里的原话:Following the basic rules, you don’t own the string returned by stringWithFormat:, so you can safely return the string from the method.

和上面的代码对比,这里的写法是错误的,因为从方法名来看,调用者不持有string,所以不会relase,然后就导致内存泄漏。

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@"%@ %@",
                                         self.firstName, self.lastName];
    return string;
}

retaincount

以下是官方文档关于自动引用计数的描述:

 Important: There should be no reason to explicitly ask an object
 what its retain count is (see retainCount). The result is often
 misleading, as you may be unaware of what framework objects have
 retained an object in which you are interested. In debugging
 memory management issues, you should be concerned only with
 ensuring that your code adheres to the ownership rules.
 

我们可以看出reatincount有时候不太靠谱,我们只要保证按照规则来对对象进行操作就可以了。

自动释放池

具体可以参考这三篇博客 第一篇第二篇第三篇再补充一篇

这里有一个疑问:文中说每次push一个page,就会新插入一个哨兵对象,那假如一个释放池包含好几个page,最后pop的时候,如何保证能释放到最初的那个哨兵对象呢?所以应该是自动释放池新建的时候,才会插入哨兵对象

我的结论

最后我总结出这个问题的答案是这样的:

1.主动调用autorelase方法的 用alloc, init,copy等方法创建的对象,这些我们自己持有的,我们想让他延迟释放,就调用autorelase方法,这样在自动释放池出栈的时候,对象就会释放掉。

2.而对于那种stringwithformt这种从名字来看,没有被调用者持有的情况,要么是自动加到自动释放池里的,要么是常量字符串,不用引用计数来管理。

以上是我的理解,大家可以自己结合代码验证一下。