ARC 意味着编译器处理内存管理,非 ARC 则意味着你自己处理内存管理,但在两种情况下,内存管理的工作方式完全相同:
- 如果一个对象必须保持活动状态,则增加其保留计数器(这就是
retain
的作用)
- 如果一个对象不再需要,则在引用丢失之前减少其保留计数器(这就是
release
的作用)
- 如果你完成了一个对象的使用但它不能立即释放,例如你需要将其作为方法结果返回(而你不想返回一个已经死亡的对象),那么它必须添加到自动释放池中,在稍后的时间内减少其保留计数(这就是
autorelease
的作用,就像是“在将来的某个时刻调用 release
”)
- 新创建的对象的保留计数为
1
。
- 如果保留计数达到零,则该对象将被释放。
无论是你自己做还是编译器为你做,它都没有作用。在编译后,这些方法被调用,即使使用ARC,但是使用ARC时编译器已经为你决定了何时调用哪个方法。有一些额外的魔法,例如ARC不总是需要将对象添加到自动释放池中,当将它们作为方法结果返回时,这通常可以被优化掉,但是你不必担心,因为只有在调用者和被调用的方法都使用ARC时才会应用此魔法;如果其中一个不是,则使用普通的autorelease
(在ARC中仍然像以前一样工作)。
唯一需要注意的是保留循环。无论您使用ARC还是不使用ARC,引用计数都无法处理保留循环。这里没有区别。
陷阱?小心使用Toll Free Bridging。 NSString *
和CFStringRef
实际上是相同的东西,但是ARC不知道CF世界,因此虽然ARC负责NSString
,但您必须负责CFString
。使用ARC时,您需要告诉ARC如何桥接。
CFStringRef cfstr = ...;
NSString * nsstr = (__bridge_transfer NSString *)cfstr;
上面的代码意味着“ARC,请接管那个CFString
对象,并在使用完毕后负责释放它”。该代码的行为类似于下面的注释中显示的代码;因此要小心,cfstr
的保留计数至少应为1,而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
];
}
- (void)iAmDone:(void *)context {
NSDictionary * contextDict = (__bridge_transfer NSDictionary *)context;
}
我有一个对于你来说可能不太明显的重要提示。请考虑以下代码:
@implementation SomeObject {
id _someIVAR;
}
- (void)someMethod {
id someValue = ...;
_someIVAR = someValue;
}
这段代码在 ARC 和非 ARC 中不同。在 ARC 中,所有变量默认都是 strong 的,因此在 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也不是weak,它们只是指针(在ARC中称为__unsafe_unretained,这里的关键字是unsafe)。
因此,如果您的代码直接使用ivar而不使用具有setter/getter的属性来访问它们,则从非ARC切换到ARC可能会导致以前具有合理内存管理的代码中出现保留循环。另一方面,从ARC转换到非ARC,像那样的代码可能会导致悬空指针(指向以前的对象,但由于对象已经死亡,这些指针指向无处,使用它们会产生不可预测的结果),因为以前保持活动状态的对象现在可能会意外死亡。