NSOperation subclass release make Instruments crash - objective-c

I ran instruments on my Mac OS X application in Xcode 4.5. I have two NSOperation dependent subclasses that I forgot to release after adding them in a process queue. So I released them just after adding them in the queue. The application works great. I profiles it on Instruments but it crashes.
processQueue = [[NSOperationQueue alloc] init];
NSUInteger max = [[NSUserDefaults standardUserDefaults] integerForKey:#"jobsKey"];
processQueue.maxConcurrentOperationCount = max;
GeocacheDownloadOperation * downloadOp = [[GeocacheDownloadOperation alloc] initWithGeocache:cache InPath:directoryPath withDelegate:self];
GeocacheJPGConversionOperation * conversionOp = [[GeocacheJPGConversionOperation alloc] initWithCache:cache WithPath:directoryPath WithDelegate:self];
[conversionOp addDependency:downloadOp];
[processQueue addOperation:downloadOp];
[processQueue addOperation:conversionOp];
[downloadOp release];
[conversionOp release]; //This line makes Instruments crash
Instruments crash when I want to release the last operation (see on code) but the application seems to work great.
Did someone have a suggestion ? Is it an Instruments bug or did I code something wrong ?

My guess is that conversionOp releases every dependency (so downloadOp in this case) when deallocated. So you call [conversionOp release] after both operations are finished (and this depends on how the threads are scheduled), so you over-release downloadOp.

I found the error but I can't explain why it was working alone and not in Instruments. I used a released variable in the NSOperation subclasses, that I released the second times in the dealloc function of the NSOperation subclasses. Now, I don't override [super dealloc] anymore in the NSOperationsubclasses and it works.

Related

NSInvocationOperation object release

I've created NSInvocationOperationQueue object then added it into my NSOperationQueue instance.
operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(counterTask) object:nil];
[operationQueue addOperation:operation];
operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(colorRotatorTask) object:nil];
[operationQueue addOperation:operation];
It works but I have questions. This post tells me that every operation should be release, but mine without release still works and it gives me error if I release it. If I do not release, is there any side effect or something will happen? Or is there any steps I missed that caused it cannot be released?
Any help would be appreciated. Thank you.
Notice that the project being created in the link you provided doesn't use Automatic Reference Counting (ARC).
There's no reason not to be using ARC in any new project. With ARC you can avoid all of the attempts to call release or autorelease or retain. ARC will properly release the added operation when it is done.
The code you posted in your question is fine with regard to memory management as long as you are using ARC. And since you are getting errors trying to call release this means you are using ARC.

Potential leak IPhone App

HarrisAnnotation *harrisAnnotation = [[HarrisAnnotation alloc] init];
[self.mapAnnotations insertObject:harrisAnnotation atIndex:0];
[harrisAnnotation release];
Running analyze on project show
Potential leak of an object
for
harrisAnnotation
Never mind, I'm not used to using Xcode. It was the line below.

iOS5 crashes during runMode:beforeDate:

I have a problem with compatibility of my application with an iOS5 b7 and GM versions.
The issue occurs in the next lines of code:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
App crashes with signal EXC_BAD_ACCESS after some iterations.
The number of passed iterations is random (from 2 till 7).
Also everything works quite well on iOS4 and iOS3.
The same issue occurs in Apple's sample: XMLPerformance Sample.
What do you think about this?
October 12th thousands of users of my app will upgrade to iOS5 and I don't want my app to be with such a strange error in the AppStore.
4 hours passed and I've found the problem. I will describe how I've resolved the problem in XMLPerformance sample.
The problem was in NSAutoreleasePool. There is #property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;. When the app starts to download Top300 Paid Apps RSS new thread is created using [NSThread detachNewThreadSelector:#selector(downloadAndParse:) toTarget:self withObject:url];. So in that thread we should keep local autorelease pool. It is done in next way:
- (void)downloadAndParse:(NSURL *)url {
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
}
So in downloadAndParse: everything looks fine. Now let's look in one method that is called when an item from RSS is parsed:
- (void)finishedCurrentSong {
// sending new item to delegate and other ...
countOfParsedSongs++;
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
if (countOfParsedSongs == kAutoreleasePoolPurgeFrequency) {
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
countOfParsedSongs = 0;
}
}
As you see there lines :
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
So exactly that lines causes the exception. If I comment them everything works great.
But I decided not only to comment that lines but also replace NSAutoreleasePool in - (void)downloadAndParse:(NSURL *)url with #autorelease block as it is said that it is more efficient:
- (void)downloadAndParse:(NSURL *)url {
#autoreleasepool {
// initializing internet connection and libxml parser.
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
}
}
Now everything works fine. The only problem that I haven't resolved is:
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
So if anybody has any thoughts about this problem can post another one answer and may be try to explain more correctly the bug fix. I will be glad to accept that answer.
Thanks.
This looks like memory problem, please check Apple Technote QA1367 "Finding EXC_BAD_ACCESS bugs in a Cocoa project"
In your code, try this to crash as soon as possible:
[item release], item = nil;
It doesn't solve the problem, just makes the crash happen earlier and hopefully give you a more meaningful callstack to study.
If you're using multi-threading, well... You could try to print "current" thread id into console to verify that everything really is run in thread where you expect them to be running. Especially verify that all UI stuff is in main thread, even when such code is run as side-effect of other code (error popups, maybe).
#include <pthread.h>
- (void)myFunction
{
NSLog(#"Thread (%d)",
pthread_mach_thread_np(pthread_self()));
}
Run your app with Instruments, make sure to change memory verification to happen every 1 or 2 seconds. Slow, but yet again you want to get notified as close to the actual memory problem as possible.
Looking at your code: where did that "done" variable come from and who changes it's value and when? Now it looks pretty magical.
Also you could check the return value of runMode:beforeDate to make sure it was run. If the return value is NO, runLoop was not run at all. Maybe some other part of your code cannot handle such case?
Just my little contribution.
As I've got the same problem, I've discover that in iOS5, you don't need to have your own NSAutoreleasePool in a thread (used by performSelectorOnMainThread).
Then, in your code (a xml parser- same as me), I think you have to separate code from iOS4 and iOS5.
With iOS4, you need NSAutoreleasePool, but not with iOS5.

Can I use autorelease to fix this memory leak?

I have a memory leak in my iPhone app. I added Google AdMob to my app using sample code downloaded from Google. However, I was having a hard time getting it into testing mode so I added an extra variable as follows:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
I found the memory leak using Instruments. Instruments does not lead me to this line of code, it just dumps me in the main.m file. However, when I comment out the code relating to AdMob the leak goes away and I know enough to see that I haven't taken care of releasing this new variable. I just don't know exactly how I should set about releasing it. The variable r is not addressed in the header file so this is all the code that deals with it.
I tried adding:
- (void)dealloc {
[r release];
....
}
but that caused a build error saying "'r' undeclared". That's weird because it looks to me like I'm declaring r in the first quoted line above but I guess that's wrong. Any help would be much appreciated. I really have tried to educate myself about memory leaks but I still find them very confusing.
Just add [r release]; right below the code:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release];
The variable r is declared only in that section of your code, so that's where it should be released. The point of releasing is to get rid of it as soon as you no longer need it, so the above should work perfectly for you.
If your r is locally declared (as it seems, judging from your snippet), then it cannot be accessed from outside its scope (here: the method it was declared in).
You either need to make it accessible within your class instance by declaring it an ivar.
Declaring it an ivar would look like this:
#interface YourClass : SuperClass {
GADRequest *request;
}
//...
#end
You then change your code to this:
request = [[GADRequest alloc] init];
request.testing = YES;
[bannerView_ loadRequest:request];
Also don't forget to release it in dealloc:
- (void)dealloc {
[request release];
//...
}
However this is not what you want in this situation (I've just included it to clarify why you get the warning about r not being declared).
You (most likely) won't need request any second time after your snippet has run, thus storing it in an ivar will only needlessly occupy RAM and add unwantd complexity to your class. Stuff you only need at the immediate time after its creation should be taken care of (released) accordingly, that is within the very same scope.
What you'll actually want to do is simply (auto)release it, properly taking care of it.
Keep in mind though that your loadRequest: will need to take care of retaining r for as long as it needs it. Apple's implementation does this, of course. But you might want to write a similar method on your own one day, so keep this in mind then.
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release]; //or: [r autorelease];
OP here. Thanks for all the detailed and thoughtful responses. This has definitely helped get a better handle on memory management. I did exactly as recommended, adding [r release]; right below the posted code. However, I still have a leak. I have isolated it to a single line of code. The following leaks:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release];
The following does not leak:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
// [bannerView_ loadRequest:r];
[r release];
I figure I'm changing the retain count on bannerView with the loadRequest but I don't know how to fix it. I tried a [bannerView_ release] immediately after the [r release]; line (i.e. releasing locally) but that didn't work. I didn't expect it to because bannerView_ is declared elsewhere. I tried [bannerView_ release]; in the dealloc method but that didn't work. I also tried [bannerView_ autorelease]; locally. The wise heads at Google put [bannerView_ release]; in the ViewDidUnload method.
It's possible that Instruments is just messing with my head. The leak appears after about 10 seconds but the app performs well and the amount of memory leaked doesn't seem to be spirally upwards as the app continues to run. Is there such a thing as a benign memory leak?
Thanks again for your help,
Dessie.

Objective-C block not being released for background-only applications

I have an application that runs in the background only (by specifying LSBackgroundOnly in the info.plist file).
The problem is, that all blocks that I run on parallel queues are not being released.
The code is executed in a memory-managed environment - no GC is involved.
The (simplified) code looks like below. Blubber is just some dummy class that holds an NSDate for testing. Also, it overwrites retain, release, and dealloc to do some logging:
NSOperationQueue *concurrentQueue = [[NSOperationQueue alloc] init];
[concurrentQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];
Blubber *aBlubber = [[Blubber alloc] init];
aBlubber.aDate = [NSDate date];
[concurrentQueue addOperationWithBlock:^{
NSAutoreleasePool *blockPool = [[NSAutoreleasePool alloc] init];
NSDate *test = [aBlubber aDate];
NSLog(#"Block DONE");
[blockPool release];
}];
[aBlubber release];
[concurrentQueue release];
If I change the application to be a normal (i.e. non-backgound) application, I can observe the blocks being released whenever any input is made via the UI (even changing the focus to another window is sufficient).
Since my backgorund app receives input directly over the HID USB driver and it does not have a window or menu bar this does not happen.
Is there any way to manually force the runloop or whatever is responsible to telling the queues to release the finished blocks?
(All other objects that had been retained by the blocks are also not released, creating huge memory leaks. These leaks cannot be spottet by the Leaks or ObjectAllocations tools but the memory consumption can be observed skyrocketing using top.)
One common "gotcha" for autorelease pools is that if the app is building up memory without receiving events, the outermost pool (the one managed by the event loop) won't be draining.
I don't think that should apply here since you're managing your own pool... but just in case, you could try this:
...
//When no events are coming in (i.e. the user is away from their computer), the runloop doesn't iterate, and we accumulate autoreleased objects
[[NSTimer scheduledTimerWithTimeInterval:60.0f target:self selector:#selector(kickRunLoop:) userInfo:nil repeats:YES] retain];
...
- (void) kickRunLoop:(NSTimer *)dummy
{
// Send a fake event to wake the loop up.
[NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
location:NSMakePoint(0,0)
modifierFlags:0
timestamp:0
windowNumber:0
context:NULL
subtype:0
data1:0
data2:0]
atStart:NO];
}
It looks like you are using a stack based block that is used after the block has gone out of scope. The block needs to be copied. The code should work if it is changed to this:
[concurrentQueue addOperationWithBlock:[[^{
NSAutoreleasePool *blockPool = [[NSAutoreleasePool alloc] init];
NSDate *test = [aBlubber aDate];
NSLog(#"Block DONE");
[blockPool release];
}copy]autorelease]];
Take a look at this post for a full writeup on blocks: http://gkoreman.com/blog/2011/02/27/blocks-in-c-and-objective-c/