Reputation: 320
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
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