Martin
Martin

Reputation: 2905

Allocating NSString repeatedly in a loop while avoiding memory leak

I am playing around with NSOperationQueue in order to run some code in the background and have it update a UILabel. Here's the viewDidLoad.

- (void)viewDidLoad
{
  [super viewDidLoad];

  queue = [[NSOperationQueue alloc] init];

  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(counterTask) object:nil];
  [queue addOperation:operation];
}

And here's the method called as the invocation operation:

- (void)counterTask {
  for (int i=0; i<5000000; i++) {
    if (i % 100 == 0) {
      [self.firstLabel performSelectorOnMainThread:@selector(setText:)
                       withObject:[NSString stringWithFormat:@"%d", i]
                       waitUntilDone:YES];
    }
  }

  [self.firstLabel performSelectorOnMainThread:@selector(setText:) withObject:@"finished." waitUntilDone:NO];
}

As the loop counts up, and more and more @"%d" NSStrings are created, the memory usage naturally goes up. Once the loop finishes however, the memory doesn't seem to deallocate. I expected the memory to fall as the setText: message uses new instances of NSString and releases the old ones.

If I change the loop condition to i<5000000*2, the memory usage is roughly double by the end – so it's definitely something happening on each iteration causing the leak.

Why is memory leaking here?

EDIT: Forgot to mention that I'm using ARC.

Upvotes: 2

Views: 523

Answers (5)

Mihir Das
Mihir Das

Reputation: 478

Try this

  • (void)counterTask {

    __weak NSString *str;

    for (int i=0; i<50000; i++) { if (i % 100 == 0) {

        str = [NSString stringWithFormat:@"%d", i];
        [self.logInTxtField performSelectorOnMainThread:@selector(setText:)
                                          withObject:str
                                       waitUntilDone:YES];
    }
    

    }

    [self.logInTxtField performSelectorOnMainThread:@selector(setText:) withObject:@"finished." waitUntilDone:NO]; }

Upvotes: 0

CouchDeveloper
CouchDeveloper

Reputation: 19116

IMO, the suggested approach of @Wain should fix the issue.

But you may also use this:

- (void)counterTask {
    assert([NSThread currentThread] != [NSThread mainThread]);
    for (int i=0; i<5000000; i++) {
        if (i % 100 == 0) {
            dispatch_sync(dispatch_get_main_queue(), ^{
                @autoreleasepool {
                    self.firstLabel.text = [NSString stringWithFormat:@"%d", i];
                }
            });
        }
    }
    dispatch_async(dispatch_get_main_queue, ^{
        self.firstLabel.text = @"finished";
    });
}

Upvotes: 0

Pandey_Laxman
Pandey_Laxman

Reputation: 3908

What happening in your loop that you are creating NSString and ARC add it to auto release pool.

not releases the memory(NSString*) immediately , will release later.

One more thing is that, actually performSelectorOnMainThread retains both target and objects.

So best way is that you create nsstring instance after sending it to selector set it to nil so ARC will release it.

NSString* strText=[[NSString alloc]initWithFormat:@"%d",i ];

    [self.firstLabel performSelectorOnMainThread:@selector(setText:)
                                      withObject:strText
                                   waitUntilDone:YES];
    strText=nil;

Upvotes: 0

nmh
nmh

Reputation: 2503

Let's try:

- (void)counterTask {
@autoreleasepool {
    for (int i=0; i<5000000; i++) {
        if (i % 100 == 0) {
            [self.firstLabel performSelectorOnMainThread:@selector(setText:)
                                              withObject:[NSString stringWithFormat:@"%d", i]
                                           waitUntilDone:YES];
        }
    }
}
[self.firstLabel performSelectorOnMainThread:@selector(setText:) withObject:@"finished." waitUntilDone:NO];
}

Upvotes: 0

Wain
Wain

Reputation: 119031

ARC doesn't remove retain / release / autorelease, it just controls the calling of these methods. You can add your own autorelease pool into your loop to force cleanup as it goes:

for (int i=0; i<5000000; i++) {
    if (i % 100 == 0) {
        @autoreleasepool {
            [self.firstLabel performSelectorOnMainThread:@selector(setText:)
                                              withObject:[NSString stringWithFormat:@"%d", i]
                                           waitUntilDone:YES];
        }
    }
}

Upvotes: 2

Related Questions