ARC 意味着编译器负责内存管理,非 ARC 意味着你负责它,但在这两种情况下内存管理的工作方式完全相同:
- 如果一个对象必须保持活动状态,它的保留计数器会增加(这就是这样
retain
做的)
- 如果不再需要一个对象,它的保留计数器会在对它的引用丢失之前减少(这就是这样
release
做的)
- 如果你完成了一个对象但它还不能死,例如你需要将它作为方法结果返回(并且你不想返回一个死对象),它必须被添加到一个自动释放池中,这将减少它在以后的时间保留计数(就是
autorelease
这样,就像说“release
在未来某个时间调用该对象。”)
- 新创建的对象的保留计数为
1
.
- 如果保留计数达到零,则释放对象。
无论您是自己做所有这些,还是编译器为您做这一切,它都不起作用。编译后,这些方法会被调用,ARC 也是如此,但使用 ARC 时,编译器会为您决定何时调用哪个方法。还有一些额外的魔力,例如 ARC 在将对象作为方法结果返回时并不总是必须将对象添加到自动释放池中,这通常可以被优化掉,但你不必关心,因为这种魔力仅在调用者时应用并且被调用的方法都使用ARC;如果其中一个不是,则使用法线autorelease
(它仍然像以前一样在 ARC 中工作)。
您唯一需要注意的是保留周期。无论您是否使用 ARC,引用计数都无法处理保留周期。这里没有区别。
陷阱?小心使用免费桥接。ANSString *
和 aCFStringRef
实际上是一回事,但 ARC 不知道 CF 世界,所以当 ARC 处理 时NSString
,您必须处理CFString
. 使用 ARC 时,需要告诉 ARC 如何桥接。
CFStringRef cfstr = ...;
NSString * nsstr = (__bridge_transfer NSString *)cfstr;
// NSString * nsstr = [(NSString *)cfstr autorelease];
上面的代码意味着“ARC,请取得该CFString
对象的所有权,并在完成后立即释放它”。该代码的行为类似于下面评论中显示的代码;非常小心,cfstr
应该至少保留一个计数,ARC 将至少释放一次,只是还没有。反过来:
NSString * nsstr = ...;
CFStringRef cfstr = (__bridge_retained CFStringRef)cftr;
// CFStringRef cfstr = (CFStringRef)[nsstr retain];
上面的代码意味着“ARC,请给我它的所有权,NSString
一旦我完成它,我会负责发布它”。当然,你必须信守诺言!在某些时候你将不得不打电话,CFRelease(cfstr)
否则你会泄漏内存。
最后,(__bridge ...)
这只是一个类型转换,没有所有权转移。这种类型的转换是危险的,因为如果你试图保留转换结果,它会产生悬空指针。通常,当您将 ARC 对象提供给期望 CF 对象的函数时,通常会使用它,因为 ARC 肯定会在函数返回之前使对象保持活动状态,例如,这始终是安全的:
doSomethingWithString((__bridge CFStringRef)nsstr);
即使 ARC 被允许nsstr
在任何时候释放,因为该行下面的代码不再访问它,它肯定不会在此函数返回之前释放它,并且根据定义,函数参数仅保证在函数返回之前保持活动状态(以防万一该函数想要保持字符串处于活动状态,它必须保留它,然后 ARC 在释放它后不会释放它,因为保留计数不会变为零)。
大多数人似乎难以解决的问题是将 ARC 对象作为void *
上下文传递,就像您有时必须使用旧 API 一样,但这实际上非常简单:
- (void)doIt {
NSDictionary myCallbackContext = ...;
[obj doSomethingWithCallbackSelector:@selector(iAmDone:)
context:(__bridge_retained void *)myCallbackContext
];
// Bridge cast above makes sure that ARC won't kill
// myCallbackContext prior to returning from this method.
// Think of:
// [obj doSomethingWithCallbackSelector:@selector(iAmDone:)
// context:(void *)[myCallbackContext retain]
// ];
}
// ...
- (void)iAmDone:(void *)context {
NSDictionary * contextDict = (__bridge_transfer NSDictionary *)context;
// Use contextDict as you you like, ARC will release it
// prior to returning from this method. Think of:
// NSDictionary * contextDict = [(NSDictionary *)context autorelease];
}
而且我必须对你来说真正的大问题,乍一看并不那么明显。请考虑以下代码:
@implementation SomeObject {
id _someIVAR;
}
- (void)someMethod {
id someValue = ...;
_someIVAR = someValue;
}
此代码在 ARC 和非 ARC 中是不一样的。在 ARC 中,默认情况下所有变量都是强变量,因此在 ARC 中,这段代码的行为就像这段代码一样:
@interface SomeObject
@property (retain,nonatomic) id someIVAR;
@end
@implementation SomeObject
- (void)someMethod {
id someValue = ...;
self.someIVAR = someValue;
}
分配someValue
将保留它,对象保持活动状态!在非 ARC 中,代码的行为如下:
@interface SomeObject
@property (assign,nonatomic) id someIVAR;
@end
@implementation SomeObject
- (void)someMethod {
id someValue = ...;
self.someIVAR = someValue;
}
请注意属性是不同的,因为非 ARC 中的 ivar 既不是strong
or weak
,它们什么也不是,它们只是指针(在被调用的 ARC 中,__unsafe_unretained
这里的关键字是unsafe)。
因此,如果您的代码直接使用 ivars 并且不使用带有 setter/getter 的属性来访问它们,那么从非 ARC 切换到 ARC 可能会导致过去具有健全内存管理的代码中的保留周期。另一方面,从 ARC 迁移到非 ARC,这样的代码可能会导致悬空指针(指向以前的对象的指针,但由于对象已经死亡,这些指针指向无处并且使用它们会产生不可预知的结果),因为过去的对象以前还活着,现在可能意外死去。