简体   繁体   中英

ARC, non-ARC, and inheritance

I haven't used ARC yet other than to deal with it when it forces it's way into a project via 3rd party code. I've read all the ARC docs but haven't seen an answer to this question:

If I have a class that's defined in a module compiled with -fobjc-arc , can I derive a new class from this in a module that is NOT ARC-enabled?

In my mind it should work fine as long as the derived class doesn't attempt to touch any ivars in the root class. It seems to me that even having a dealloc method that calls [super dealloc] would be fine in the derived class.

And, what about the other way around? Can I derive a ARC-enabled class from a non-ARC class? Should work fine too, right?

Bonus points: are there any gotcha's when mixing ARC and non-ARC code that I should make myself aware of?

There are no issues that I am aware of. You have to realize that ARC is something like a source code preprocessor, adding the memory management calls for you during the compilation. When you arrive at the linking phase, you can't really tell ARC code from non-ARC code. (This is probably an over-simplification, but one that should work for your purposes.) If your derived class has correct memory management and the super class has correct memory management, the result will work fine.

About the only difference I can think of is handling of weak properties. But I don't know enough about those to say if it's possible to arrive at buggy code using some combination of ARC and MRC code with weak properties.

This was a comment, but having thought about it I want to expand what it said.

Have you tried inheriting an ARC class from a normal subclass? My thoughts (without having tried it either) is that this will not work. Firstly, if the ARC class has public properties or ivars using ARC keywords, like weak I think you will get errors during compilation from the header file. Secondly, I don't know how the dealloc would work. Do you need to call [super dealloc] or not? I don't know.

Anyway, if your superclass is ARC, why would you not use ARC in any subclasses? There's no advantage to doing that at all.

Can I derive a ARC-enabled class from a non-ARC class? Should work fine too, right?

I was going to say that won't work either, but I would have been wrong. Virtually everything has to inherit from NSObject which is manual reference counted.

Yes, you may both implement non-ARC ancestor from ARC parent class, and ARC ancestor from non-ARC parent class.

Actually, ARC is a syntax sugar, or you may say, is just preprocessor which analyzes your source code at compile step and inserts appropriate [release] and [retain] calls to your code. At runtime level nothing is changed (except for the weak properties).

ARC means the compiler takes care of memory management, non-ARC means you take care of it, but in both cases memory management works exactly the same way:

  • If an object must stay alive, its retain counter is increased (that's what retain does)
  • If an object is not needed anymore, its retain counter is decreased before the reference to it is lost (that's what release does)
  • If you are done with an object but it must not die yet, eg as you need to return it as a method result (and you don't want to return a dead object), it must be added to an autorelease pool that will decrease its retain count at a later time (that's what autorelease does, it's like saying "call release on that object at some future time.")
  • Newly created objects have a retain count of 1 .
  • If the retain count reaches zero, the object is freed.

Whether you do all that yourself or the compiler does it for you, it plays no role. After compilation, these methods are being called, also with ARC, but with ARC the compiler has decided for you when which method is called. There is some extra magic, eg ARC doesn't always have to add objects to autorelease pools when returning them as method result, this can often be optimized away, but you don't have to care as this magic is only applied if the caller and the called method both are using ARC; if one of them isn't, then a normal autorelease is used (which still works in ARC exactly as it used to).

The only thing you must take care of is retain cycles. Whether you use ARC or not, reference counting can't deal with retain cycles. No difference here.

Pitfalls? Careful with Toll Free Bridging. A NSString * and a CFStringRef are in fact the same thing but ARC doesn't know about the CF-world, so while ARC takes care of the NSString , you must take care of the CFString . When using ARC, you need to tell ARC how to bridge.

CFStringRef cfstr = ...;
NSString * nsstr = (__bridge_transfer NSString *)cfstr;
// NSString * nsstr = [(NSString *)cfstr autorelease];

Code above means "ARC, please take ownership of that CFString object and take care of releasing it as soon as you are done with it". The code behaves like the code shown in the comment below; so careful, cfstr should have a retain count of at least one and ARC will release it at least once, just not yet. The other way round:

NSString * nsstr = ...;
CFStringRef cfstr = (__bridge_retained CFStringRef)cftr;
// CFStringRef cfstr = (CFStringRef)[nsstr retain];

Code above means "ARC, please give me ownership of that NSString , I'll take care of releasing it once I'm done with it". Of course, you must keep that promise! At some time you will have to call CFRelease(cfstr) otherwise you will leak memory.

Finally there's (__bridge ...) which is just a type cast, no ownership is transferred. This kind of cast is dangerous as it can create dangling pointers if you try to keep the cast result around. Usually you use it when just feeding an ARC object to a function expecting a CF-object as ARC will for sure keep the object alive till the function returns, eg this is always safe:

doSomethingWithString((__bridge CFStringRef)nsstr); 

Even if ARC was allowed to release nsstr at any time as no code below that line ever accesses it anymore, it will certainly not release it before this function has returned and function arguments are by definition only guaranteed to stay alive until the function returns (in case the function wants to keep the string alive, it must retain it and then ARC won't deallocate it after releasing it as the retain count won't become zero).

The thing most people seem to struggle with is passing ARC objects as void * context, as you sometimes have to with older API, yet that is in fact dead simple:

- (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];
}

And I have to real big gotcha for you that are not that obvious at first sight. Please consider this code:

@implementation SomeObject {
    id _someIVAR;
}

- (void)someMethod {
    id someValue = ...;
    _someIVAR = someValue;
}

This code is not the same in ARC and non ARC. In ARC all variables are strong by default, so in ARC this code behaves just like this code would have:

@interface SomeObject
    @property (retain,nonatomic) id someIVAR;
@end

@implementation SomeObject

- (void)someMethod {
    id someValue = ...;
    self.someIVAR = someValue;
}

Assigning someValue will retain it, the object stays alive! In non-ARC the code will behave like this one:

@interface SomeObject
    @property (assign,nonatomic) id someIVAR;
@end

@implementation SomeObject

- (void)someMethod {
    id someValue = ...;
    self.someIVAR = someValue;
}

Note the property is different, as ivar's in non-ARC are neither strong or weak , they are nothing, they are just pointers (in ARC that is called __unsafe_unretained and the keyword here is unsafe ).

So if you have code that uses ivars directly and doesn't use properties with setters/getters to access them, then switching from non-ARC to ARC can cause retain cycles in code that used to have sane memory management. On the other hand, moving from ARC to non-ARC, code like that can cause dangling pointers (pointers to former objects but since the object has already died, these point to nowhere and using them has unpredictable results), as objects that used to be kept alive before may now die unexpectedly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM