ARC,非ARC和继承

时间:2012-06-13 15:20:54

标签: objective-c ios automatic-ref-counting

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

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

在我看来,只要派生类不试图触及根类中的任何ivars,它应该可以正常工作。在我看来,即使有一个调用[super dealloc]的dealloc方法在派生类中也没问题。

那么,另一种方式呢?我可以从非ARC类派生一个启用ARC的类吗?应该也可以正常工作,对吧?

奖励积分:在混合ARC和非ARC代码时,我应该让自己知道吗?

4 个答案:

答案 0 :(得分:6)

我没有发现任何问题。您必须意识到ARC类似于源代码预处理器,在编译期间为您添加内存管理调用。当您到达链接阶段时,您无法从非ARC代码中真正告知ARC代码。 (这可能是一个过度简化,但应该适用于您的目的。)如果派生类具有正确的内存管理并且超类具有正确的内存管理,则结果将正常工作。

关于我能想到的唯一区别是处理weak属性。但是我不知道那些人是否可以使用具有弱属性的ARC和MRC代码的组合来获得有缺陷的代码。

答案 1 :(得分:0)

这是一个评论,但考虑到了这一点,我想扩大它所说的内容。

您是否尝试从普通子类继承ARC类?我的想法(没有尝试过)是因为这不起作用。首先,如果ARC类具有使用ARC关键字的公共属性或ivars,例如weak,我认为在编译期间您将从头文件中获得错误。其次,我不知道dealloc将如何运作。你需要打电话给[super dealloc]吗?我不知道。

无论如何,如果您的超类是ARC,为什么不在任何子类中使用ARC?这样做完全没有优势。

  

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

我打算说这也行不通,但我错了。实际上,所有内容都必须从NSObject继承,这是手动引用计数。

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

ARC意味着编译器负责内存管理,非ARC意味着你要处理它,但在这两种情况下,内存管理的工作方式完全相同:

  • 如果一个物体必须保持活着,它的保留计数器会增加(这就是retain所做的)
  • 如果不再需要某个对象,它的保留计数器会在对它的引用丢失之前减少(这就是release所做的事情)
  • 如果你已经完成了一个物体,但它一定不会死亡,例如因为你需要将它作为方法结果返回(并且你不想返回死对象),所以必须将它添加到自动释放池中,这将在以后减少其保留计数(这就是autorelease是的,就像在将来某个时候“在该对象上调用release。”)
  • 新创建的对象的保留计数为1
  • 如果保留计数达到零,则释放该对象。

无论你是自己做的还是编译器为你做的,它都不起作用。在编译之后,这些方法也被调用,但是使用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应保留至少一个保留计数,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 / getters的属性来访问它们,那么从非ARC切换到ARC可能导致代码中的保留周期,这些代码曾经有过合理的内存管理。另一方面,从ARC移动到非ARC,像这样的代码可能会导致悬空指针(指向前对象的指针,但由于对象已经死亡,这些指向无处并使用它们具有不可预测的结果),作为以前的对象在可能意外死亡之前保持活力。