@property (nonatomic, retain) NSObject *obj;
_obj = [[NSObject alloc] init];
self.obj = _obj;
[_obj release];
在 MRC(Manual Reference Counting)环境下,这段代码是安全的,但它代表了一种极度不规范且容易埋坑的写法。
虽然最终内存引用计数是平衡的,但它违反了 Objective-C 的“所有权语义”一致性,在更复杂的场景下会导致崩溃。
1. 内存走线分析
我们来拆解这三行代码执行后,引用计数(Reference Count, RC)的变化:
-
_obj = [[NSObject alloc] init];alloc使对象 RC = 1。- 根据规则,你(当前作用域)拥有这个对象的所有权。
-
self.obj = _obj;- 调用了
setter方法。由于属性标记为retain,setter内部会执行[_obj retain]。 - 此时对象 RC = 2。
- 现在有两个持有者:你的局部变量
_obj和类的成员变量_obj。
- 调用了
-
[_obj release];- 局部变量放弃所有权,对象 RC = 1。
- 此时对象依然存活,被
self.obj妥善持有。
结论: 内存没有泄露,也没有野指针,逻辑上是安全的。
2. 为什么这种写法“不专业”?
在工程实践中,这种“先 alloc 给成员变量,再通过 self 赋值,最后手动 release”的操作非常繁琐且冗余。它存在以下两个问题:
A. 语义重复(Double Ownership)
你通过 alloc 拿到了一次所有权,又通过 setter 拿到了第二次所有权,最后再放掉第一次。这像是在搬家时,你自己抱起一个箱子(alloc),把它交给搬家公司(setter),然后你再把手松开(release)。
B. 潜在的 Setter 陷阱
如果在 init 方法里这样做,会有风险。在 init 或 dealloc 中,苹果官方建议直接访问成员变量(即 _obj = ...),而不是通过属性(self.obj = ...)。
- 原因:
setter可能被子类重写。如果子类在重写的方法里访问了尚未初始化完成的其他属性,程序会直接崩溃。
3. 更正宗(Idiomatic)的两种写法
写法一:直接赋值(最常用)
如果你是在 init 方法里:
Objective-C
// 只有一次 alloc,RC = 1
_obj = [[NSObject alloc] init];
直接操作底层变量,不触发 setter,既高效又安全。
写法二:利用 Autorelease(工厂方法风格)
如果你非要用 self.obj 赋值:
Objective-C
// 使用类方法,返回的对象是 autorelease 的,你不拥有它
self.obj = [NSObject new]; // 注意:在 MRC 中 new 依然需要 release,通常改用:
self.obj = [[[NSObject alloc] init] autorelease];
alloc产生 RC=1,autorelease标记延迟释放。self.obj执行retain使 RC=2。- 当前周期结束,池子发
release使 RC=1。 - 优点:代码只有一行,逻辑清晰。
4. 延伸:Setter 内部发生了什么?
为了确保安全,属性声明为 retain 时,编译器(或你手写的)setter 必须处理“自赋值”情况:
Objective-C
- (void)setObj:(NSObject *)newObj {
if (_obj != newObj) {
[_obj release]; // 释放旧的
_obj = [newObj retain]; // 持有新的
}
}
总结
你提供的代码在内存引用平衡上是闭环的,但它属于**“绕路走”**。在 MRC 时代,这种写法增加了代码行数,也就增加了手抖写错 release 的概率。