Objective-C 指针设置为 nil 时的引用计数(无 ARC)
Objective-C reference count when pointer set to nil (without ARC)
我想了解引用计数是如何工作的,所以我禁用了 ARC 并写了一个简单的 class:(Foo.h 未粘贴,因为它未修改)
Foo.m
@implementation Foo
- (instancetype)init
{
NSLog(@"Init object");
return [super init];
}
- (void)dealloc
{
NSLog(@"Dealloc object");
[super dealloc];
}
@end
Main.m
#import <Foundation/Foundation.h>
#import "Foo.h"
int main(int argc, const char * argv[]) {
Foo *obj = [[Foo alloc] init];
obj = nil;
return 0;
}
现在我希望看到 dealloc object
日志,因为对 Foo
对象的唯一引用已经消失,但我得到的唯一消息是 init object
.
为什么我看不到?我赋值obj = nil
的时候对象不是释放了吗?
没有。如果您不使用 ARC,则该对象会在您调用 [obj release];
时释放。 (ARC 为您插入这些调用。)将 obj
设置为 nil
在内存管理方面没有任何作用(尽管它确实创建了一个您无法再访问的对象!)。
基本上,在没有 ARC 的 Cocoa 中:
- 如果您想获得某个对象的所有权,请致电
[obj retain]
。 (alloc
为您完成。)
- 当您想要放弃对象的所有权时,您可以调用
[obj release]
。当对象的保留计数达到 0 时,release
依次调用 dealloc
。
- 当您想要放弃当前范围之外的对象的所有权时,您可以调用
[obj autorelease]
。最常见的情况是,当您 return 来自方法的对象(并且不想保留它的所有权)时会发生这种情况。
一些额外的观察来扩展 mipadi 的出色回答:
在非 ARC 代码中,将变量设置为 nil
将 不会 释放对象。您必须明确地 release
/autorelease
它。
更准确地说,如果所有权已转移给您(即您通过名称以 alloc
、new
、copy
开头的方法获取对象) , 或 mutableCopy
), 那么你有责任显式调用 release
/autorelease
.
最重要的是,简单地将变量设置为 nil
是不够的。
我不敢提这个,怕混淆了问题,但在处理属性时,它有点不同。使用属性,调用 retain
属性 的 setter 将自动为您 retain
对象。如果您稍后将 属性 设置为 nil
,那么 setter 将自动 release
为您设置。
想象一个 class 像这样:
@interface Bar : NSObject
@property (nonatomic, retain) Foo *foo;
@end
当你想设置 foo 属性 时,你可以执行以下操作
Foo *f = [[Foo alloc] init]; // create Foo object with +1 retain count
self.foo = f; // the `retain` property will increase retain count to +2
[f release]; // resolve the local strong reference, reducing retain count back to +1
或者,更简单地说:
self.foo = [[[Foo alloc] init] autorelease];
它们所做的是创建一个具有 +1 保留计数的 Foo
对象。通过设置 foo
属性,您正在创建另一个 strong
引用,因此最终保留计数为 +2。然后当你 release
/autorelease
它时,保留计数回落到 +1.
稍后,当您完成 foo
并想要释放它时,您只需再次调用 setter,这次使用 nil
值:
self.foo = nil; // resolve the `retain` done by the property, reducing the retain count to +0
这解除了 foo
属性 对对象维护的强引用。您不 自己打电话给release
(至少在属性 上)。显然,当你用 nil
值调用 setter 时,不仅释放了先前的对象,而且如果那是最后一个强引用,该对象也会为你释放。
简而言之,原来的alloc
偏移了一个release
/autorelease
,而不是设置变量为nil
。但是,retain
属性 的设置是 通过将 属性 设置为 nil
.
来解决的
一些最后的观察。
我建议如果要编写非 ARC 代码,请仔细阅读 高级内存管理编程指南, 尤其是 Memory Management Policy 章节。
Xcode的静态分析器(shift+command+B ,或 Xcode 的 "Product" 菜单上的 "Analyze")非常擅长识别困扰非 ARC 代码的内存问题。始终确保您绝对没有来自静态分析器的警告。
使用 Instruments 时,分配工具有一个名为 "Record Reference Counts" 的功能(参见 )。如果您发现某个对象没有被释放,此工具可用于诊断对象的完整生命周期及其所有保留计数。
我想了解引用计数是如何工作的,所以我禁用了 ARC 并写了一个简单的 class:(Foo.h 未粘贴,因为它未修改)
Foo.m
@implementation Foo
- (instancetype)init
{
NSLog(@"Init object");
return [super init];
}
- (void)dealloc
{
NSLog(@"Dealloc object");
[super dealloc];
}
@end
Main.m
#import <Foundation/Foundation.h>
#import "Foo.h"
int main(int argc, const char * argv[]) {
Foo *obj = [[Foo alloc] init];
obj = nil;
return 0;
}
现在我希望看到 dealloc object
日志,因为对 Foo
对象的唯一引用已经消失,但我得到的唯一消息是 init object
.
为什么我看不到?我赋值obj = nil
的时候对象不是释放了吗?
没有。如果您不使用 ARC,则该对象会在您调用 [obj release];
时释放。 (ARC 为您插入这些调用。)将 obj
设置为 nil
在内存管理方面没有任何作用(尽管它确实创建了一个您无法再访问的对象!)。
基本上,在没有 ARC 的 Cocoa 中:
- 如果您想获得某个对象的所有权,请致电
[obj retain]
。 (alloc
为您完成。) - 当您想要放弃对象的所有权时,您可以调用
[obj release]
。当对象的保留计数达到 0 时,release
依次调用dealloc
。 - 当您想要放弃当前范围之外的对象的所有权时,您可以调用
[obj autorelease]
。最常见的情况是,当您 return 来自方法的对象(并且不想保留它的所有权)时会发生这种情况。
一些额外的观察来扩展 mipadi 的出色回答:
在非 ARC 代码中,将变量设置为
nil
将 不会 释放对象。您必须明确地release
/autorelease
它。更准确地说,如果所有权已转移给您(即您通过名称以
alloc
、new
、copy
开头的方法获取对象) , 或mutableCopy
), 那么你有责任显式调用release
/autorelease
.最重要的是,简单地将变量设置为
nil
是不够的。我不敢提这个,怕混淆了问题,但在处理属性时,它有点不同。使用属性,调用
retain
属性 的 setter 将自动为您retain
对象。如果您稍后将 属性 设置为nil
,那么 setter 将自动release
为您设置。想象一个 class 像这样:
@interface Bar : NSObject @property (nonatomic, retain) Foo *foo; @end
当你想设置 foo 属性 时,你可以执行以下操作
Foo *f = [[Foo alloc] init]; // create Foo object with +1 retain count self.foo = f; // the `retain` property will increase retain count to +2 [f release]; // resolve the local strong reference, reducing retain count back to +1
或者,更简单地说:
self.foo = [[[Foo alloc] init] autorelease];
它们所做的是创建一个具有 +1 保留计数的
Foo
对象。通过设置foo
属性,您正在创建另一个strong
引用,因此最终保留计数为 +2。然后当你release
/autorelease
它时,保留计数回落到 +1.稍后,当您完成
foo
并想要释放它时,您只需再次调用 setter,这次使用nil
值:self.foo = nil; // resolve the `retain` done by the property, reducing the retain count to +0
这解除了
foo
属性 对对象维护的强引用。您不 自己打电话给release
(至少在属性 上)。显然,当你用nil
值调用 setter 时,不仅释放了先前的对象,而且如果那是最后一个强引用,该对象也会为你释放。
简而言之,原来的alloc
偏移了一个release
/autorelease
,而不是设置变量为nil
。但是,retain
属性 的设置是 通过将 属性 设置为 nil
.
一些最后的观察。
我建议如果要编写非 ARC 代码,请仔细阅读 高级内存管理编程指南, 尤其是 Memory Management Policy 章节。
Xcode的静态分析器(shift+command+B ,或 Xcode 的 "Product" 菜单上的 "Analyze")非常擅长识别困扰非 ARC 代码的内存问题。始终确保您绝对没有来自静态分析器的警告。
使用 Instruments 时,分配工具有一个名为 "Record Reference Counts" 的功能(参见 )。如果您发现某个对象没有被释放,此工具可用于诊断对象的完整生命周期及其所有保留计数。