ARC 意味着编译器负责内存管理,非 ARC 意味着你负责它,但在这两种情况下内存管理的工作方式完全相同:
- 如果一个对象必须保持活动状态,则它的保留计数器会增加(这就是
retain 所做的)
- 如果不再需要某个对象,则其保留计数器会在对它的引用丢失之前减少(这就是
release 所做的)
- 如果你完成了一个对象但它还不能死掉,例如由于您需要将其作为方法结果返回(并且您不想返回死对象),因此必须将其添加到自动释放池中,以便稍后减少其保留计数(这就是
autorelease 所做的,这就像说“在未来某个时间在该对象上调用 release。”)
- 新创建的对象的保留计数为
1。
- 如果保留计数达到零,则释放对象。
无论是您自己完成所有这些,还是编译器为您完成所有这些,它都不起作用。编译后,这些方法也会被调用,ARC 也是如此,但是使用 ARC,编译器会为您决定何时调用哪个方法。还有一些额外的魔法,例如在将对象作为方法结果返回时,ARC 并不总是必须将对象添加到自动释放池中,这通常可以被优化掉,但是您不必关心,因为只有在调用者和被调用方法都使用时才应用这种魔法弧;如果其中一个不是,则使用普通的autorelease(它在 ARC 中仍然像以前一样工作)。
您唯一需要注意的是保留周期。无论您是否使用 ARC,引用计数都无法处理保留周期。这里没有区别。
陷阱?小心使用免费桥接。 NSString * 和 CFStringRef 实际上是一回事,但 ARC 不知道 CF 世界,所以虽然 ARC 负责 NSString,但您必须负责 CFString。使用 ARC 时,需要告诉 ARC 如何桥接。
CFStringRef cfstr = ...;
NSString * nsstr = (__bridge_transfer NSString *)cfstr;
// NSString * nsstr = [(NSString *)cfstr autorelease];
上面的代码表示“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
];
// 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也不是weak,它们什么都不是,它们只是指针(在ARC中称为__unsafe_unretained,这里的关键字是不安全)。
因此,如果您的代码直接使用 ivars 并且不使用带有 setter/getter 的属性来访问它们,那么从非 ARC 切换到 ARC 可能会导致过去具有健全内存管理的代码中的保留周期。另一方面,从 ARC 迁移到非 ARC,这样的代码可能会导致悬空指针(指向以前的对象的指针,但由于对象已经死亡,这些指针指向无处并且使用它们会产生不可预知的结果),因为过去的对象以前还活着,现在可能会意外死去。