ARC、非ARC和继承

5

除了在第三方代码中强制使用 ARC 处理时处理它,我还没有使用过 ARC。我已经阅读了所有的 ARC 文档,但没有看到这个问题的答案:

如果我有一个类是在使用 -fobjc-arc 编译的模块中定义的,我可以从未启用 ARC 的模块中派生一个新类吗?

在我看来,只要派生类不尝试触摸根类中的任何 ivar,那么它应该可以正常工作。对我来说,即使具有调用 [super dealloc] 的 dealloc 方法也适用于派生类。

那么反过来呢?我可以从非 ARC 类派生出 ARC 启用的类吗?这样也应该工作正常,对吧?

额外的提示:我是否需要了解在混合使用 ARC 和非 ARC 代码时需要注意的事项?

4个回答

6
我不知道有什么问题。你必须意识到ARC就像一个源代码预处理器,在编译期间为你添加内存管理调用。当你到达链接阶段时,你无法真正区分ARC代码和非ARC代码。(这可能是一种过度简化,但对你的目的应该有效。)如果你的派生类具有正确的内存管理且超级类具有正确的内存管理,则结果将正常工作。
我唯一能想到的区别就是处理弱引用属性。但是我不了解这些内容,无法确定是否可能使用某些组合的ARC和MRC代码与弱引用属性导致出现错误的代码。

这似乎是共识。感谢您的帮助。 - TomSwift

0
这是一条评论,但考虑后我想扩展它的内容。
你尝试过从普通的子类继承 ARC 类吗?我的想法(没有实际尝试)是这样做不会起作用。首先,如果 ARC 类具有使用 ARC 关键字的公共属性或 ivar,如 `weak`,我认为在编译时从头文件中会出现错误。其次,我不知道 `dealloc` 会如何工作。需要调用 `[super dealloc]` 吗?我不知道。
总之,如果您的超类是 ARC,则为什么不在任何子类中使用 ARC?这根本没有任何优势。

我可以从非 ARC 类派生一个 ARC 已启用的类吗?也应该可以正常工作,对吧?

我本来要说那也不行,但我错了。几乎所有东西都必须继承自手动引用计数的 `NSObject`。

在 MRC 代码中从 ARC 启用的类派生是有意义的,例如当您链接到使用 ARC 的库并且无法重写主项目时。(我这样做了,它似乎可以工作。)在 ARC 下,您不能调用 [super dealloc],我认为编译器会自动插入该调用。 - zoul
@zoul - 是的,这也是我们所面临的情况;第三方模块需要使用ARC,但我们不准备在自己的模块中使用ARC。 - TomSwift
在ARC类的情况下,拥有一个非ARC子类也是有意义的。具体而言,如果子类需要大量使用CF API,则有时从MRR代码而不是ARC代码进行操作会更容易。 - bbum
@JeremyP - 你为什么建议我们会出现编译器错误?除了继承之外,MRC代码总是可以导入一个ARC类的头文件并使用它,而该头文件中可能有ARC关键字。是吗? - TomSwift

0

是的,您可以从ARC父类实现非ARC祖先,也可以从非ARC父类实现ARC祖先。

实际上,ARC只是一种语法糖,或者您可以说它只是预处理器,在编译步骤中分析您的源代码并插入适当的[release]和[retain]调用到您的代码中。在运行时级别上,除了weak属性之外,没有任何变化。


0

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;
// 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 中,所有变量默认都是 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,像那样的代码可能会导致悬空指针(指向以前的对象,但由于对象已经死亡,这些指针指向无处,使用它们会产生不可预测的结果),因为以前保持活动状态的对象现在可能会意外死亡。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接