MRC引用计数规则
内存管理的范围:任何继承了NSObject的对象,对基本数据类型无效(系统会自动回收)
相关名词:
- 内存泄漏:程序未能释放已经不在使用的内存
- 僵尸对象:对象被销毁,不能再使用
- 野指针(iOS):指针指向的对象已经被销毁,指向僵尸对象的指针,准确的说iOS中的野指针应该叫悬垂指针,对于野指针,要及时将其赋值为nil,成为空指针
- 空指针:没有指向任何对象的指针
内存管理的思考方式:
- 自己生成的对象,自己持有
- 非自己生成的对象,也能持有
- 不再需要自己持有的对象时,释放
- 非自己持有的对象无法释放
操作 | 方法 | 思考方式 |
---|---|---|
生成对象 | alloc/new/copy/mutableCopy | 自己生成,自己持有 |
持有对象 | retain | 非自己生成,也能持有 |
释放对象 | release | 不再需要时,释放 |
废弃对象 | dealloc |
相关方法
- alloc/new/copy/mutableCopy:生成对象,引用计数初值为1
- retain:引用计数+1,并返回+1后的当前实例对象
- release:引用计数-1,并没有释放内存
- dealloc:实例对象销毁前,自动调用;引用计数为0,自动调用
release与dealloc的区别:release释放的是对该对象的所有权,dealloc释放的是该对象占用的内存
自己生成,自己持有(alloc/new/copy/mutableCopy)
//生成并持有对象
id obj1 = [[NSObject alloc] init];
id obj2 = [NSObject new];
NSString *str = @"123";
NSString *str2 = [str copy]; //生成并持有str的副本
NSMutableArray *array1 = [NSMutableArray alloc]init];
NSMutableArray *array2 = [array1 mutableCopy]; //生成并持有array1的副本
copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。
mutableCopy
方法利用基于NSMutableCopying:方法约定,由各类实现的mutableCopyWithZone:
生成并持有对象的副本。
区别:copy生成不可变更的对象,mutableCopy生成可变更的对象
注:以alloc/new/copy/mutableCopy开头,使用驼峰命名法的方法,也是自己生成并持有对象
非自己生成,也能持有
通过retain方法,让不是自己生成的对象与使用alloc/new/copy/mutableCopy方法生成的对象一样都能被持有。
//取得,但不持有对象
NSArray *array = [NSArray array];
//持有对象
[array retain]
不再需要的对象,释放
alloc/new/copy/mutableCopy生成并持有的对象,或者retain方法持有的对象,一旦不需要,无比要使用release进行释放。
{
//取得,但不持有对象
id obj = [NSMutableArray array];
//持有对象
[obj retain];
//释放对象
[obj release];
}
注:
内存中的常量对象(类对象、常量字符串对象等)的内存分配空间与其他对象不同,没有引用计数机制,永远不能释放这些对象,获取的retainCount结果返回为NSUIntegerMax(最大的无符号整数)
非自己持有的对象无法释放
释放后,再次释放
id obj = [NSObject alloc]init];
[obj release];
[obj release];
取得对象存在,但不持有时释放
id obj = [NSObject object];
[obj release];
这些都会导致程序的崩溃
MRC下autorelease的使用
autorelease的具体使用方法:
- 生成并持有NSAutoreleasePool对象
- 调用已分配对象的autorelease实例方法
- 废弃NSAutoreleasePool对象
while(...) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/*
在此进行一系列操作,调用临时对象的autorelease方法
*/
[pool drain]; // 废弃pool
}
注:
- 需要长时间运行的代码段或大量使用临时对象的代码可以通过autoreleasepoool提高内存利用率
- 可以给实例多次发送retain,相应的也可以给实例多次发送autorelease,只要autorelease和retain要成对发送
release与autorelease的区别
release:调用后,立即释放对象
autorelease:对象在超出指定的生存范围是能够自动并正确的释放(调用release方法)
过程:[obj autorelease]
不立即释放变量obj所指的对象,而是注册到autoreleasepool中,pool结束时自动调用release
- 以alloc为头,使用驼峰命名法的方法,如何实现自己生成,自己持有的?
- (id)allocObject
{
//obj生成并持有对象
id obj = [NSObject alloc]init];
return obj;
}
id obj = [NSObject allocObject];
- 如何实现[NSArray array]这种取得对象存在,但并不持有的?
- (id)object
{
//obj生成并持有对象
id obj = [[NSObject alloc]init];
//obj注册到autoreleasepool中,这里的autoreleasepool一般都是main函数最开始生成的自动释放池
[obj autorelease];
return obj;
}
//取得对象,并不持有
id obj = [NSObject object];
[obj retain]; //持有对象
MRC中要重写的方法
- dealloc:彻底释放一个对象,还需要释放该对象所持有的所有的对象的所有权
- (void)dealloc
{
//所有持有的对象调用release方法,释放所有权
[super dealloc]; //一定要调用父类的dealloc,内存的释放才会从子类一直上升到NSObject,对象才会彻底被释放
}
- setter方法
基本类型:
- (void)setObj:(NSUInteger)obj
{
_obj = obj;
}
对象类型:
//以下写法参数obj与_obj是同一对象时,会出问题
- (void)setObj:(id)obj {
//obj与_obj都只指向同一对象,但obj不持有对象,这时release后对象
//引用计数为0,对象被销毁,然后调用retain会出问题
[_obj release];
_obj = [obj retain];
}
//两种更安全的写法
- (void)setObj:(id)obj
{
//新对象所有权+1
[obj retain];
//旧对象所有权释放
[_obj release];
//将新对象赋给_obj
_obj = obj;
}
- (void)setObj:(id)obj
{
if(_obj != obj)
{
//释放旧对象所有权
[_obj release];
//赋给_obj所有权+1后的新对象
_obj = [obj retain];
}
}
重写过后的setter方法与实例变量直接赋值的比较:
id obj = [[NSObject alloc]init];
self.obj = obj; //重写过setter方法后,所有权会+1
_obj = obj; //实例变量直接赋值,所有权不变
3、getter方法
getter方法不属于alloc/new/copy/mutableCopy的自己生成自己持有,与[NSArray array]
一样,都是取得非自己生成对象但不持有
- (id)obj
{
id tmp = [obj retain];
return [tmp autorelease]; //注册到autoreleasepool中,实现取得非自己生成对象但不持有,这里的autoreleasepool通常都是main函数里最开始生成的那个
}
int main(void) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Object *obj = [Object obj];
[pool drain];
}
总结:
- 自己生成对象自己持有,只适用于以alloc/new/copy/mutableCopy开头的方法。
- MRC的引用计数规则下,持有方法与释放方法一定要成对使用,确保对象被正确释放
- 其他方式获得的对象,都属于取得非自己生成的对象,所以需要调用autorelease方法放入到autoreleasepool中。
参考资料:
- Objective-C编程全解
- Objective-C高级编程iOS与OS X多线程和内存管理