11

I was reading a little about ARC and I saw this:

@interface Address : NSObject {
    @public
    NSString *city;
}
@end

@implementation Address
- (Address*) init: (NSString*) c {
    city = c;

    return self;
}

- (void) dealloc {
    NSLog(@"Destroying address: %@", city);
}
@end

@interface Customer : NSObject {
    NSString *name;
    Address *addr;
}
@end

@implementation Customer
- (Customer*) init: (NSString*) n withAddress: (Address*) a {
    //Note 1: Automatic retain on assignment
    name = n;
    addr = a;

    return self;
}

- (void) dealloc {
    NSLog(@"Destroying: %@", name);
    //Note 2: Automatic release of member variables
}

@end

Customer* objectReturnTest() {
    NSString * n = [[NSString alloc] initWithString: @"Billy Bob"];
    Address * a = [[Address alloc] init: @"New York City"];      

    Customer *c = [[Customer alloc] init: n withAddress: a];

    //Note 3: ARC will put the returned object in autorelease pool.
    return c;
}

A couple of basic things to note here. As "Note 1"  says, when an object is assigned to a variable, a call to retain is made automatically. This increments the reference count. As "Note 2" says, when an object is destroyed, all member variable objects are released for you. You no longer have to do that from the dealloc method.

Finally, when a method returns a newly created object, ARC will put the returned object in an autorelease pool. This is stated in "Note 3".

Now, let’s use the code.

int main (int argc, const char * argv[])
{
    NSString * n = [[NSString alloc] initWithString: @"Johnny Walker"];
    Address * a = [[Address alloc] init: @"Miami"];
    Customer *c = [[Customer alloc] init: n withAddress: a];

    NSLog(@"Before force release");
    c = nil; //Force a release
    NSLog(@"After force release");

    @autoreleasepool {

        Customer *c2 = objectReturnTest();

    }
    NSLog(@"After autorelease pool block.");

    return 0;
}

The log output from this code will be:

Before force release

Destroying: Johnny Walker

After force release

Destroying: Billy Bob

Destroying address: New York City

After autorelease pool block.

Destroying address: Miami

A couple of things to note here. See how force release works. We set a variable to nil. ARC immediately releases the reference count. This causes the Customer object "Johnny Walker" to get destroyed. But, the member Address object "Miami" doesn’t get destroyed. This object gets destroyed at the very end of the main method. This is an extremely odd and non-intuitive behavior. Technically, this is not a memory leak, but, in reality member variables can pile up and take up a lot of memory. This is just as bad as memory leak.

The object return test works as expected. Customer "Billy Bob" is put in auto release pool. At the end of the @autoreleasepool block, the pool is drained and the object is released.

Looking at this part;

int main (int argc, const char * argv[])
{
    NSString * n = [[NSString alloc] initWithString: @"Johnny Walker"];
    Address * a = [[Address alloc] init: @"Miami"];
    Customer *c = [[Customer alloc] init: n withAddress: a];

    NSLog(@"Before force release");
    c = nil; //Force a release
    NSLog(@"After force release");

    @autoreleasepool {

        Customer *c2 = objectReturnTest();

    }
    NSLog(@"After autorelease pool block.");

    return 0;
}

When he does c = nil; shouldn't c a and n all be destroyed? Yet it says the output is only that n is destroyed.. Can someone explain why?

And he says the outcome is as bad as a memory leak, then how do you fix it?

And one last question, when should you use @autoreleaasepool?

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
user1021085
  • 729
  • 3
  • 10
  • 28

2 Answers2

15

by line

c = nil; //Forces a release

a Customer instance is deallocated because no one is retaining it, so as a result the output is

Destroying: Johnny Walker

but n and a have not been deallocated because they still remain in the scope and nil has not been assigned to them.

and I don't think this is any kind of memory leak


you normally do not need to use @autorelasepool unless you are doing something like this

- (void)myMethod {
    for (int i = 0; i < 1000000; i++) {
        NSString *string = [NSString stringWithFormat:@"%d", i];
        // do something with string
    }
}

Than 1000000 NSString will be allocated during loop. They will be deallocated after the method returned (actually after this runloop) but already consume too much memory. Therefore should replace to

- (void)myMethod {
    for (int i = 0; i < 1000000; i++) {
        @autoreleasepool {
            NSString *string = [NSString stringWithFormat:@"%d", i];
            // do something with string
        }
    }
}

you should read this to learn more about memory management https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-CJBFBEDI

mfaani
  • 33,269
  • 19
  • 164
  • 293
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • Shouldn't you add autorelease when you declare the string? – user1021085 Feb 12 '12 at 12:49
  • 1
    And just one more thing, what if you want a function to return something, like a string? Do you just do NSString *a = @"hey"; return a;? Wouldn't a be deallocated and you wont be able to save the value ? – user1021085 Feb 12 '12 at 12:56
  • @user1021085 `[NSString stringWithFormat:]` it same as `[[[NSString alloc] initWithFormat:] autorelease]` so it is an autoreleased object. ARC can handle everything (unless you are dealing with __unsafe_unretained pointer or __bridge cast) so it will retain and autorelease the string and it will not be deallocated until next runloop – Bryan Chen Feb 13 '12 at 05:28
  • What I don't understand is that in the second run of the loop, string is pointing to a new string and the previous is ready to be dealloced. Why does it wait until the end of the method to do so? What is the use of all those pointerless strings in memory. It seems a bit weird that we have to force this behavior by adding a @autoreleasepool. – Yvo Mar 15 '13 at 16:43
  • 1
    @Zyphrax because it is autoreleased object (`[[[NSString alloc] init] autorelease]`) where the autorelease pool take the responsibility to free it. without `@autoreleasepool` only you code will be executed in the loop and the autorelease will not have chance to free the memory. – Bryan Chen Mar 15 '13 at 23:55
  • @xlc thanks, that cleared it up for me (together with reading the memory management article a few more times). It's hard to get out of the Garbage collection mindset of C# :) – Yvo Mar 16 '13 at 06:13
1

The obvious differense between name and address is that you create an Address Object for address and NSString for name. In the address object it is @public. This meeas the NSString is out of scope when customer is released, but not the address object, it will still remember the address given to the @public NSString *city when you release customer.

So when you call this public value for address it is still there, but not the NSString for name. To fix this you either remove the interface of Address Object, which releases both values or you create one interface for name instead of using NSString.

gnat
  • 6,213
  • 108
  • 53
  • 73