Reputation: 2905
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"
NSString
s 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
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
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
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
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
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