引用计数
引用计数(Reference counting)是一个简单有效管理对象生命周期的方式。
当我们新建一个新对象时候,它的引用计数+1,当一个新指针指向该对象,将引用计数+1。当指针不再指向这个对象时候,引用计数-1,当引用计数为0时,说明该对象不再被任何指针引用,将对象销毁,进而回收内存。
四条规则
- 自己生成的对象自己持有 - alloc/new/copy/mutableCopy等方法
- 非自己生成的对象,自己也能持有 - retain方法
- 不再需要自己持有的对象时释放 - release方法(autorelease可以取得对象存在,但自己不持有对象,超出指定范围时能够自动并正确释放)
- 不要释放非自己持有的对象,释放了会造成崩溃
1.自己生成的对象自己持有 -- alloc/new/copy/mutableCopy等方法
id obj = [[NSObject alloc] init]; // 创建并持有对象,RC = 1
NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
//或 NSLog(@"retainCount = %ld",[obj retainCount]);
// retainCount = 1
id obj = [[NSObject alloc] init]; // 创建并持有对象,RC = 1
/*
* 使用该对象,RC = 1
*/
[obj release]; // 在不需要使用的时候调用 release,RC = 0,对象被销毁
NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
//或 NSLog(@"retainCount = %ld",[obj retainCount]);
// retainCount = 0
2.非自己生成的对象,自己也能持有 -- retain方法
使用上述方法以外的方法创建的对象,我们并不持有,其RC初始值也为 1。但是需要注意的是,如果要使用(持有)该对象,需要先进行retain,否则可能会导致程序Crash。原因是这些方法内部是给对象调用了autorelease方法,所以这些对象会被加入到自动释放池中。
(1).情况一:iOS 程序中不手动指定@autoreleasepool
当RunLoop迭代结束时,会自动给自动释放池中的对象调用release方法。所以如果我们在使用前不进行retain,那么当RunLoop迭代结束,对象就会收到release消息,如果此时该对象RC值降为 0 就会被销毁。而我们这时候再去访问已经被销毁的对象,程序就会Crash。
/* 正确的用法 */
id obj = [NSMutableArray array]; // 创建对象但并不持有,对象加入自动释放池,RC = 1
[obj retain]; // 使用之前进行 retain,对对象进行持有,RC = 2
/*
* 使用该对象,RC = 2
*/
[obj release]; // 在不需要使用的时候调用 release,RC = 1
/*
* RunLoop 可能在某一时刻迭代结束,给自动释放池中的对象调用 release,RC = 0,对象被销毁
* 如果这时候 RunLoop 还未迭代结束,该对象还可以被访问,不过这是非常危险的,容易导致 Crash
*/
(2).手动指定@autoreleasepool
如果@autoreleasepool作用域结束,就会自动给autorelease对象调用release方法。如果这时候我们再访问该对象,程序就会Crash。
/* 错误的用法 */
id obj;
@autoreleasepool {
obj = [NSMutableArray array]; // 创建对象但并不持有,对象加入自动释放池,RC = 1
} // @autoreleasepool 作用域结束,对象 release,RC = 0,对象被销毁
NSLog(@"%@",obj); // EXC_BAD_ACCESS
/* 正确的用法 */
id obj;
@autoreleasepool {
obj = [NSMutableArray array]; // 创建对象但并不持有,对象加入自动释放池,RC = 1
[obj retain]; // RC = 2
} // @autoreleasepool 作用域结束,对象 release,RC = 1
NSLog(@"%@",obj); // 正常访问
/*
* 使用该对象,RC = 1
*/
[obj release]; // 在不需要使用的时候调用 release,RC = 0,对象被销毁
(3).自定义方法 创建但并不持有对象
方法名就不应该以 alloc/new/copy/mutableCopy 开头,且返回对象前应该要先通过autorelease方法将该对象加入自动释放池。
- (id)object
{
id obj = [NSObject alloc] init];
[obj autorelease];
retain obj;
}
这样调用方在使用该方法创建对象的时候,通过方法名他就会知道他不持有该对象,于是他会在使用该对象前进行retain,并在不需要该对象时进行release。
备注:release和autorelease的区别:
- 调用release,对象的RC会立即 -1;
- 调用autorelease,对象的RC不会立即 -1,而是将对象添加进自动释放池,它会在一个恰当的时刻自动给对象调用release,所以autorelease相当于延迟了对象的释放。
3.不再需要自己持有的对象时释放
在不需要使用(持有)对象的时候,需要调用一下release或者autorelease方法进行释放(或者称为 “放弃对象使用权”),使其RC-1,防止内存泄漏。当对象的RC为 0 时,就会调用dealloc方法销毁对象。
4.不要释放非自己持有的对象
如果自己不是持有者,就不能对对象进行release,否则会导致程序Crash。另外,向已回收的对象发送消息也是不安全的.
持有对象的两种方式
- 通过 alloc/new/copy/mutableCopy 等方法创建对象
- 创建但并不持有的对象,通过retain方法持有
id obj = [[NSObject alloc] init]; // 创建并持有对象,RC = 1
[obj release]; // 如果自己是持有者,在不需要使用的时候调用 release,RC = 0
/*
* 此时对象已被销毁,不应该再对其进行访问
*/
[obj release]; // EXC_BAD_ACCESS,这时候自己已经不是持有者,再 release 就会 Crash
/*
* 再次 release 已经销毁的对象(过度释放),或是访问已经销毁的对象都会导致崩溃
*/
id obj = [NSMutableArray array]; // 创建对象,但并不持有对象,RC = 1
[obj release]; // EXC_BAD_ACCESS 虽然对象的 RC = 1,但是这里并不持有对象,所以导致 Crash
执行如下代码,可能会有问题,也可能没有问题。对象所占内存在 “解除分配(deallocated)” 之后,只是放回可用内存池。如果对象所占内存还没有分配给别人,这时候访问没有问题,如果已经分配给了别人,再次访问就会崩溃。
Person *person = [[Person alloc] init]; // 创建并持有对象,RC = 1
[person release]; // 如果自己是持有者,在不需要使用的时候调用 release,RC = 0
[person release]; // 向已回收的对象发送消息是不安全的