我还没有使用ARC,除了在通过第三方代码强行进入项目时处理它。我已经阅读了所有ARC文档,但没有看到这个问题的答案:
如果我有一个在用-fobjc-arc
编译的模块中定义的类,我可以在一个未启用ARC的模块中从中派生一个新类吗?
在我看来,只要派生类不试图触及根类中的任何ivars,它应该可以正常工作。在我看来,即使有一个调用[super dealloc]
的dealloc方法在派生类中也没问题。
那么,另一种方式呢?我可以从非ARC类派生一个启用ARC的类吗?应该也可以正常工作,对吧?
奖励积分:在混合ARC和非ARC代码时,我应该让自己知道吗?
答案 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,像这样的代码可能会导致悬空指针(指向前对象的指针,但由于对象已经死亡,这些指向无处并使用它们具有不可预测的结果),作为以前的对象在可能意外死亡之前保持活力。