pearl7721
pearl7721

Reputation: 320

Memory Growth Mystery (Objective-C)

I have a memory growth issue in my app.

Since describing the full code here is intimidating, I narrowed it down to this simple scenario where I switch back and forth between two view controllers to learn basic memory dynamics.

 - (void)viewDidLoad {
    [super viewDidLoad];

for (int i=0; i<100000; i++) { __weak NSString* str = [NSString stringWithFormat:@"abcsdf"]; str = nil; } }

This supposed to show no memory growth, because I allocate 'str' and deallocate 'str' by making 'str' becomes nil, thus losing the owner.

But, the memory keeps growing. Everytime I load this view controller, the memory keeps growing and never coming back.

Can anyone tell me why is that? I am using ARC.

Upvotes: 2

Views: 117

Answers (1)

Kazuki Sakamoto
Kazuki Sakamoto

Reputation: 14009

Your code snippet includes several interesting things about iOS/OS X memory management.

__weak NSString* str = [NSString stringWithFormat:@"abcsdf"];
str = nil;

The code is the same as the following without ARC.

NSString* str = [[[NSString alloc] initWithFormat:@"abcsdf"] autorelease];
str = nil;

Because stringWithFormat: class method does not begin with "alloc", "new", "copy", or "mutableCopy". It's the naming rule. So the NSString object is retained by an Autorelease Pool. The Autorelease Pool might be in the main Runloop. Thus the NSString object was not deallocated immediately. It causes memory growth. @autoreleasepool solves it.

@autoreleasepool {
    __weak NSString* str = [NSString stringWithFormat:@"abcsdf"];
    str = nil;
}

The NSString object is deallocated at the end of @autoreleasepool code block.

By the way, [NSString stringWithFormat:@"abcsdf"] might not allocate any memory every time. The reason is that it's static string. Let's use this class for further explanation.

#import <Foundation/Foundation.h>

@interface Test : NSObject
+ (instancetype)test;
@end

@implementation Test
- (void)dealloc {
    NSLog(@"Test dealloc");
}

+ (instancetype)test
{
    return [[Test alloc] init];
}
@end

Here is test code for __weak.

@autoreleasepool {
    NSLog(@"BEGIN: a = [Test test]\n");
    __weak Test *a = [Test test];
    NSLog(@"END: a = [Test test]\n");
    a = nil;
    NSLog(@"DONE: a = nil\n");
}

The result of the code.

BEGIN: a = [Test test]
END: a = [Test test]
DONE: a = nil
Test dealloc

You said deallocate 'str' by making 'str' becomes nil, thus losing the owner. It is not correct. a weak variable doesn't have ownership of the object. The Autorelease Pool does have the ownership of the object. That's why the object was deallocated at the end of the @autoreleasepool code block. Take a look at the other test code for this case.

NSLog(@"BEGIN: a = [[Test alloc] init]\n");
__weak Test *a = [[Test alloc] init];
NSLog(@"END: a = [[Test alloc] init]\n");
a = nil;
NSLog(@"DONE: a = nil\n");

You can see a compilation warning from the code.

warning: assigning retained object to weak variable; object will be
         released after assignment [-Warc-unsafe-retained-assign]
            __weak Test *a = [[Test alloc] init];
                         ^   ~~~~~~~~~~~~~~~~~~~

[[Test alloc] init] doesn't register the object to Autorelease Pool. Well, no need @autoreleasepool any more. And a is __weak variable, so the object will not be retained from anything. Thus the result is

BEGIN: a = [[Test alloc] init]
Test dealloc
END: a = [[Test alloc] init]
DONE: a = nil

No ownership no life. The object was deallocated immediately after it was allocated. I think you wanted to write the code without __weak as the following.

NSLog(@"BEGIN: a = [[Test alloc] init]\n");
Test *a = [[Test alloc] init];
NSLog(@"END: a = [[Test alloc] init]\n");
a = nil;
NSLog(@"DONE: a = nil\n");

The result is as expected. The object was released by assigning nil to the strong variable a. Then no one has the ownership of the object, the object was deallocated.

BEGIN: a = [[Test alloc] init]
END: a = [[Test alloc] init]
Test dealloc
DONE: a = nil

Upvotes: 1

Related Questions