2

Edit: Problem defined below actually occurred with this code:

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        XYZPerson *myPerson = [XYZPerson person];
        myPerson = nil;
        NSLog(@"The end.");
    }
}

The method 'person' is a factory method.


I have the following code:

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        XYZPerson *myPerson = [[XYZPerson alloc] init];
        myPerson = nil;
        NSLog(@"The end.");
    }
}

XYZPerson overrides dealloc method so that it prints out something with NSLog. I expect the code above to output something like:

Dealloc!
The end.

But it is not as I expected:

The end.
Dealloc!

Am I doing something wrong or did I misunderstand the concept of ARC?

mostruash
  • 4,169
  • 1
  • 23
  • 40
  • 3
    Everything is fine here. Leave ARC a chance to get active, check for objects that are not referenced any more and delete them. – Hermann Klecker Feb 03 '13 at 14:32
  • You could try Nslogin "the End" after the @autoreleasepool block. But I am not too positive that that will provide the desired result. – Hermann Klecker Feb 03 '13 at 14:33
  • 1
    @HermannKlecker It does, but I guess that is not the point of ARC. ARC should dealloc immediately when there is no more strong references that points to the instance; at least that is what I understood from Mac Dev docs. Basically you're putting some code out of ARC scope, I guess. – mostruash Feb 03 '13 at 14:36
  • 3
    @mostruash: I have tested your code with a `XYZPerson` class that is a direct subclass of `NSObject`. "Dealloc!" is printed first, before "The end.", as you expected. So you must have something special in your class, perhaps in the `init` method? – Martin R Feb 03 '13 at 15:51
  • @MartinR In fact, you are right. I was using a factory method for allocation/initialization. I thought there would be no difference, but this post proves me wrong, sorry it's my bad: http://stackoverflow.com/questions/14484923/using-a-factory-method-to-set-a-weak-variable-seems-to-keep-the-object-alive-t – mostruash Feb 03 '13 at 16:39
  • @mostruash: OK. You might want to either delete the question or provide an answer to clear things up. – Martin R Feb 03 '13 at 16:48
  • 6
    @HermannKlecker ARC is **not** a garbage collector. http://stackoverflow.com/questions/6385212/how-does-the-new-automatic-reference-counting-mechanism-work – Fabian Kreiser Feb 03 '13 at 17:15
  • @FabianKreiser If it were a garbage collector, then I wouldn't expect it to deallocate the object instantaneously after I set the pointer to nil as that would be an overkill for a GC. Moreover the tutorial I'm following from the Apple Developer Center claims that it should deallocate at the point where I expect it to do it. – mostruash Feb 05 '13 at 19:07
  • @mostruash My comment has been directed to HermannKlecker ;) – Fabian Kreiser Feb 05 '13 at 19:14
  • @FabianKreiser oops :) – mostruash Feb 05 '13 at 19:17
  • @mostruash Actually, the garbage collector on Mac OS X had thread local collection that would collect an object that did not escape a thread's scope pretty much immediately upon the object's last remaining reference being destroyed. Several other collectors feature this kind of immediate collection behavior, too. – bbum Apr 13 '13 at 17:29

2 Answers2

4

ARC guarantees that objects will be automatically reference counted at compile time. It goes further and places the requirement that the code be algorithmically fully coherent (which manifests as errors when trying to convert between, say, void* and id via casting -- under ARC, you have to qualify the memory management policy across such casts).

ARC is not a garbage collector; there is no scanning, no threading, and no stop-the-world behavior. This means more predictable behavior at the cost of things like automatic cycle detection.

While ARC guarantees that an object's lifespan will be automatically managed, ARC does not guarantee that lifespan beyond "the object will live for at least as long, maybe longer, than it is used in the code".

In fact, you might see lifespan changes depending on both the optimization level of the code and whether or not the factory method you invoked is compiled in an ARC vs. manual-retain-release [MRR] source file. And the lifespan may change across releases of the compiler and/or runtime.

For example, ARC code calling into a factory method can sometimes short-circuit the autorelease entirely.

Sounds scary, but it isn't because of the algorithmic coherence requirement. Since there cannot be ambiguous behavior (as there can in plain old MRR), that the lifespan might change over releases should not impact your code.

Of course, this means that you should not have order dependencies between dealloc methods. This should not be an onerous requirement as having order dependencies between dealloc methods under MRR was always a nasty thing.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • 1
    While your answer is not entirely related to my question, accepting it for the part that concerns factory methods. – mostruash Feb 05 '13 at 10:01
  • @bbum How would you suggest to solve this when there's some data processing model class that needs to stop its code execution ASAP when model receives nil (for example user closed the window). Do I need to have a special "alreadyDeallocated" property in my model, set to YES in controller and add conditional checking in data processing methods in my model? (because I can't check retainCount) – Centurion Apr 13 '13 at 08:29
  • @Centurion `retainCount` wouldn't have worked in the MRR case. Set a flag that is periodically checked in the computation thread/queue. Or use a zeroing weak reference for some object critical to the computation that goes away when the window is closed. Personally, I would go the explicit flag route as it makes the logic quite clear (`if (stopFlag) { ... stop calculating ... }`. – bbum Apr 13 '13 at 16:20
0

This is because ARC still respects the Cocoa memory-management naming conventions. You can add attribute to Your factory method person like this: + (instancetype)person __attribute__((objc_method_family(new))); so ARC assumes that the object it returns comes with an incremented retain count that will need to be balanced with a corresponding release. Then immediately after setting variable to nil the dealloc will occur.

Marcin Kapusta
  • 5,076
  • 3
  • 38
  • 55