How can I safely go between ARC and MRC methods without unnecessary retain/release calls?

437 views Asked by At

I have an ARC class with the following code:

[object doStuffWithObject:otherObject];

object's -doStuffWithObject: method is compiled with ARC, and is this:

- (void)doStuffWithObject:(id)otherObject
{
    DoStuffHelper(object, otherObject);
}

DoStuffHelper, a C function, is not compiled with ARC (for performance reasons). In DoStuffHelper, do I need to call -retain at the start for object and otherObject and -release for them at the end?

4

There are 4 answers

0
Rob On BEST ANSWER

See Avoid Causing Deallocation of Objects You’re Using in Advance Memory Management: Practical Memory Management, in which they point out that "received objects should typically remain valid throughout the scope of the calling method", i.e. retain and release are not necessary, except with the following caveat:

There are occasional exceptions to this rule, primarily falling into one of two categories.

  1. When an object is removed from one of the fundamental collection classes.

    heisenObject = [array objectAtIndex:n];
    [array removeObjectAtIndex:n];
    // heisenObject could now be invalid.
    

    When an object is removed from one of the fundamental collection classes, it is sent a release (rather than autorelease) message. If the collection was the only owner of the removed object, the removed object (heisenObject in the example) is then immediately deallocated.

  2. When a “parent object” is deallocated.

    id parent = <#create a parent object#>;
     // ...
     heisenObject = [parent child] ;
     [parent release]; // Or, for example: self.parent = nil;
     // heisenObject could now be invalid.
    

    In some situations you retrieve an object from another object, and then directly or indirectly release the parent object. If releasing the parent causes it to be deallocated, and the parent was the only owner of the child, then the child (heisenObject in the example) will be deallocated at the same time (assuming that it is sent a release rather than an autorelease message in the parent’s dealloc method).

To protect against these situations, you retain heisenObject upon receiving it and you release it when you have finished with it. For example:

heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];

I'd be surprised if your helper function fell into one of those categories. You'll probably be fine without the retain and release, but I only mention it for the sake of full disclosure.

Obviously, you might need your own retain and release if your functions need it for other reasons (e.g. if task is asynchronous or if you are otherwise doing something unusual where the object must outlive the scope of the calling method), but I suspect you would have mentioned it if you were doing one of those things.

Also, if your utility function was creating and returning an object, then that would be a different matter (e.g. you'd generally autorelease objects that you were returning).

3
EricS On

In the vast majority of cases you won't find ARC slower than traditional retain/release. Have you used Instruments to verify the problem?

Having said that, you do not need to retain/release the object in DoStuffHelper() because the retainCount will already be >= 1 on entry. If store the objects in a static or global then you will need to retain them.

0
matt On

No. DoStuffHelper is not an object that asserts ownership of object or otherObject. It's just a utility function that operates on them directly. In effect, it's part of your ARC class, which is already performing complete memory management on these objects.

1
bbum On

DoStuffHelper, a C function, is not compiled with ARC (for performance reasons).

Do you have any performance measurements that show ARC to be slower?

In general, ARC'd executables generated with the optimizer turned on will be faster than the same code with ARC disabled (and optimized by the compiler).

This is because the compiler (and the runtime) can reason about the references to the point of being able to avoid calls to retain and release (and, worse, autorelease) wherein said calls would be mandatory in correct MRR code.

If you have a code pattern where that is not the case, I'd like to capture said pattern and file a bug.

(This is, admittedly, a meta-answer. It questions whether the need for such an ARC-MRR interface is even necessary. Having maintained code bases that are partially ARC and partially MRR, such mixes are rife with fragility.)