文章目录
-
- 持有的定义
- 持有的方式
-
- 通过创建持有 (alloc, new, copy, mutablecopy)
- [通过retain 主动持有](#通过retain 主动持有)
- 不持有:通过非创建方法拿到的对象
- [通过 autorelease 转移所有权](#通过 autorelease 转移所有权)
- 属性关键字与引用计数的关系
- [dealloc 完整写法](#dealloc 完整写法)
- 自动释放池
对象的生命周期由 **引用计数(retain count)**决定
当对象的引用计数大于 0 的时候, 该对象可以存活, 当引用计数等于 0 的时候, 对象就需要被销毁
影响引用计数的操作
| 操作 | 结果 |
|---|---|
| alloc/new/copy | +1 |
| retain | +1 |
| release | -1 |
| autorelease | 延迟 -1 |
| = 0 | 调用 dealloc,释放内存 |
retainCount: 查看计数
当对象的引用计数为 0 的时候, 程序会自动调用该对象的 dealloc 方法来销毁它
核心法则:
- 谁alloc/new/copy/mutableCopy -> 谁 release
- 谁 retain -> 谁 release
- 不是你创建的, 不是你 retain 的 -> 不要 release
准确来说, OC 内存管理的真正核心是一套所有权(Ownership)契约, 谁持有对象, 谁就有责任释放它, 引用计数只是这套契约的实现手段
持有的定义
代码让对象的引用计数 + 1, 并且有义务在不需要的时候让它 - 1, 不是所有拿到对象的指针的地方都持有它

一个对象可以被多个持有者同时持有, 只有所有持有者都放弃后才能真正销毁
持有的方式
通过创建持有 (alloc, new, copy, mutablecopy)
通过这四种方式创建的对象,返回的对象由你持有, 必须负责 release
objc
NSObject* a = [[NSobject alloc] init];
NSObject* b = [NSobject new];
NSString* c = [somestr copy];
NSMutableString* d = [someStr mutableCopy];
用完必须 release
objective-c
[a release];
[b release];
[c release];
[d release];
通过retain 主动持有
拿到了别人的对象, 想让它活的久一点 ,就需要 retain
objc
- (void) setChild: (Person* )child {
[_child release]; // 先舍弃旧的对象
_chile = [child retain]; // 持有新对象
}
不持有:通过非创建方法拿到的对象
方法名不以 alloc/new/copy/mutableCopy 开头,返回的是 autorelease 对象,你不持有它,不能 release:
objc
objc
// 以下对象你不持有,不要 release!
NSString *s = [NSString stringWithFormat:@"hello %d", 42];
NSArray *a = [NSArray arrayWithObjects:@"x", @"y", nil];
NSDate *d = [NSDate date];
// 这里直接用,没有问题
NSLog(@"%@ %@ %@", s, a, d);
// 不要这样做:
// [s release]; // 错误!没有持有它,多余的 release 导致崩溃
这些对象被放进了 autorelease pool,会在 pool drain 时自动释放。
通过 autorelease 转移所有权
autorelease 的语义是:"我现在放弃所有权,但不立刻 -1,等 pool drain 时再 -1"。常用于从方法返回对象时:
objc
objc
// 工厂方法的标准写法
+ (instancetype)personWithName:(NSString *)name {
Person *p = [[Person alloc] initWithName:name];
return [p autorelease];
// alloc 让 count=1,autorelease 把释放权交给 pool
// 调用者收到的是一个"不归你管"的对象
}
属性关键字与引用计数的关系
strong
赋值时引用计数 +1
持有者销毁或属性被重新赋值时 -1
对应 MRC 下的 retain
weak
赋值时引用计数 不变
不持有对象,当对象被销毁时,weak 指针自动置 nil
常用于解决循环引用(delegate、block 中的 self)
assign
- 引用计数 不变
- 纯粹的指针赋值,对象销毁后不会自动置 nil(野指针风险)
- 一般只用于基本数据类型(int、float、BOOL 等)
assign 只用于基本数据类型
当一个属性被 assign 修饰时,它仅仅是简单地记录了内存地址。
- 释放时不置空 :当该对象因为引用计数归零被销毁时,
assign修饰的指针依然指向那块已经被回收的内存区域 。造成野指针
objc
// delegate 对象销毁后:
_delegate = someObj; // assign,直接指针赋值,不 retain
// someObj 在外部被 release,销毁
[_delegate doSomething]; // 崩溃!_delegate 是野指针
dealloc 完整写法
objc
- (void)dealloc {
// retain/copy 属性:release 并置 nil
[_name release], _name = nil;
[_title release], _title = nil;
[_children release], _children = nil;
// assign 属性(对象):只置 nil,不 release(你没持有)
_delegate = nil;
// assign 属性(基本类型):不需要任何操作
// _count、_flag 等不用管
[super dealloc]; // 必须最后调用,释放父类持有的成员
}
copy
赋值时对原对象调用 copy,创建一个新对象 ,新对象引用计数为 1
原对象引用计数不变
常用于 NSString、Block,防止外部修改影响内部状态
objc
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name copy]; // copy 出一个新对象,引用计数=1
}
}
retain(MRC)
- 赋值时引用计数 +1
- 即 ARC 下
strong的前身
retain ---- setter持有新值, 释放旧值
objc
- (void)setName:(NSString *)name {
if (_name != name) { // 必须判断自赋值
[_name release]; // 如果不判断,self.name == _name 时:
_name = [name retain]; // 先 release 让 count=0,对象被销毁
} // 再 retain 访问已销毁对象 → 崩溃
}
在上面的 setter 方法中,先判断 _item 与被传入的 FKItem 对象是否相等也是很有必要的。如果不进行这个判断,当程序多次将同一个 FKItem 对象传给该 setter 方法时,将会导致每执行一次 setter 方法,被传入的 FKItem 对象的引用计数都加 1,这显然不是希望看到的结果。
自动释放池
自动释放池就是一个存放对象的容器, 自动释放池会保证延迟释放池中所有的对象. 、
- (id)autorelease: 该方法不会改变对象的引用计数, 只是将对象添加到自动释放池中
自动释放池预设了在将来某一个时间将要调用relese方法, 将对象的引用计数减一, 当自动释放池释放时 ,自动释放池中所有的对象都会执行 release 方法