NSThread with _NSAutoreleaseNoPool error - cocoa

I have an method which save files to the internet, it works but just slow. Then I'd like to make the user interface more smooth, so I create an NSThread to handle the slow task.
I am seeing a list of errors like:
_NSAutoreleaseNoPool(): Object 0x18a140 of class NSCFString autoreleased with no pool in place - just leaking
Without NSThread, I call the method like:
[self save:self.savedImg];
And I used the following to use NSThread to call the method:
NSThread* thread1 = [[NSThread alloc] initWithTarget:self
selector:#selector(save:)
object:self.savedImg];
[thread1 start];
Thanks.

Well first of all, you are both creating a new thread for your saving code and then using NSUrlConnection asynchronously. NSUrlConnection in its own implementation would also spin-off another thread and call you back on your newly created thread, which mostly is not something you are trying to do. I assume you are just trying to make sure that your UI does not block while you are saving...
NSUrlConnection also has synchronous version which will block on your thread and it would be better to use that if you want to launch your own thread for doing things. The signature is
+ sendSynchronousRequest:returningResponse:error:
Then when you get the response back, you can call back into your UI thread. Something like below should work:
- (void) beginSaving {
// This is your UI thread. Call this API from your UI.
// Below spins of another thread for the selector "save"
[NSThread detachNewThreadSelector:#selector(save:) toTarget:self withObject:nil];
}
- (void) save {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// ... calculate your post request...
// Initialize your NSUrlResponse and NSError
NSUrlConnection *conn = [NSUrlConnection sendSyncronousRequest:postRequest:&response error:&error];
// Above statement blocks until you get the response, but you are in another thread so you
// are not blocking UI.
// I am assuming you have a delegate with selector saveCommitted to be called back on the
// UI thread.
if ( [delegate_ respondsToSelector:#selector(saveCommitted)] ) {
// Make sure you are calling back your UI on the UI thread as below:
[delegate_ performSelectorOnMainThread:#selector(saveCommitted) withObject:nil waitUntilDone:NO];
}
[pool release];
}

You need to mainly create an autorelease pool for the thread. Try changing your save method to be like this:
- (void) save:(id)arg {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Existing code
[pool drain];
}
You will not you that the above does not call release on the NSAutoreleasePool. This is a special case. For NSAutoreleasePool drain is equivalent to release when running without GC, and converts to a hint to collector that it might be good point to run a collection.

You may need to create a run loop. I will add to Louis's solution:
BOOL done = NO;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSRunLoop currentRunLoop];
// Start the HTTP connection here. When it's completed,
// you could stop the run loop and then the thread will end.
do {
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, YES);
if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished)) {
done = YES;
}
} while (!done);
[pool release];

Within the thread, you need to create a new autorelease pool before you do anything else, otherwise the network operations will have issues as you saw.

I don't see any reason for you to use threads for this. Simply doing it asynchronously on the run loop should work without blocking the UI.
Trust in the run loop. It's always easier than threading, and is designed to provide the same result (a never-blocked UI).

Related

Cocoa/OSX - Strange behavior in NSSavePanel that not shows sub-itens

I've a NSSavePanel instance with a strange behavior: whenever I open it and click on a directory's arrow (the little expand button) it shows an indeterminate loading icon on the left-bottom corner that never ends, and not shows the directory/file tree. An image can see as follow:
In that example, I've clicked in "workspace" directory. And the panel not shows the sub-itens. Even strange is that after I click it again (redrawing the directory) and then click again (re-open the directory), it properly shows all files.
My code is as follows:
// here, I'm creating a web service client, and then calling a method to download a report, and passing the same class as delegate
- (IBAction) generateReport:(id)sender {
// SOME STUFF HERE...
WSClient *client = [[[WSClient alloc] init] initWithDelegate:self];
[client GenerateReport:#"REPORT" withParams:params];
}
- (void) GenerateReport:(NSString *)from withParams:(NSDictionary *)parameters {
// SOME STUFF HERE...
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
NSLog(#"GenerateReport: [success]");
[self.delegate successHandlerCallback:data from: from];
});
});
}
}];
// this is the callback
- (void) successHandlerCallback:(NSData *) data from: (NSString *) from {
NSString savePath = [savePath stringByReplacingOccurrencesOfString:#"file://" withString:#""];
NSString *filePath = [NSString stringWithFormat:#"%#", savePath];
[data writeToFile:filePath atomically:YES];
}
// and this is to build a panel to let user chose the directory to save the file
- (NSURL *) getDirectoryPath {
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setNameFieldStringValue:[self getDefaultFileName]];
[panel setDirectoryURL:[NSURL fileURLWithPath:[[NSString alloc] initWithFormat:#"%#%#%#", #"/Users/", NSUserName(), #"/Downloads"]]];
if ([panel runModal] != NSFileHandlingPanelOKButton) return nil;
return [panel URL];
}
Can someone give a hint on where I'm missing?
UPDATE: To me, it seens to be something related with dispatch_async!
Thanks in advance!
Actually, this is a bad interaction between NSSavePanel and the Grand Central Dispatch main queue and/or +[NSOperationQueue mainQueue]. You can reproduce it with just this code:
dispatch_async(dispatch_get_main_queue(), ^{
[[NSSavePanel savePanel] runModal];
});
First, any time you perform GUI operations, you should do so on the main thread. (There are rare exceptions, but you should ignore them for now.) So, you were right to submit the work to the main queue if it was going to do something like open a file dialog.
Unfortunately, the main queue is a serial queue, meaning that it can only run one task at a time, and NSSavePanel submits some of its own work to the main queue. So, if you submit a task to the main queue and that task runs the save panel in a modal fashion, then it monopolizes the main queue until the save panel completes. But the save panel is relying on the ability to submit its own tasks to the main queue and have them run.
As far as I'm concerned, this is a bug in Cocoa. You should submit a bug report to Apple.
The correct solution is for NSSavePanel to submit any tasks it has to the main thread using a re-entrant mechanism like a run-loop source, -performSelectorOnMainThread:..., or CFRunLoopPerformBlock(). It needs to avoid using the main queue of GCD or NSOperationQueue.
Since you can't wait for Apple to fix this, the workaround is for you to do the same. Use one of the above mechanisms to submit your task that may run the save panel to the main queue.
I just found the way: I was making all the calls within the dispatch_async on the main thread queue. By the fact that the download is still running at the callback moment, it conflicted with the thread that opens the panel. I fixed all issues by just placing the correct lines, changing from:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
NSLog(#"GenerateReport: [success]");
[self.delegate successHandlerCallback:data from: from];
});
});
to:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSLog(#"GenerateReport: [success]");
[self.delegate successHandlerCallback:data from: from];
});
and in the callback, just updating the fields. In the end, I found that all of that were a main/background thread misunderstand by me.

How do you safely lock a variable using GCD?

I have an NSMutableArray I need to add objects to from multiple blocks I have dispatched. Is this an acceptable way to make sure that the array is safely being changed? These are already being dispatched from inside and NSOperation and running in the background. I was loading the data from within that thread serially but it was getting very slow to load a list of locations at once.
NSMutableArray *weatherObjects = [[NSMutableArray alloc] init];
ForecastDownloader *forecastDownloader = [[ForecastDownloader alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t serialQueue;
serialQueue = dispatch_queue_create("us.mattshepherd.ForecasterSerialQueue", NULL);
for (NSDictionary *theLocation in self.weatherLocations) {
// Add a task to the group
dispatch_group_async(group, queue, ^{
NSLog(#"dispatching...");
int i = 0;
WeatherObject *weatherObject = [forecastDownloader getForecast:[theLocation objectForKey:#"lat"] lng:[theLocation objectForKey:#"lng"] weatherID:[[theLocation objectForKey:#"id"] intValue]];
}
if(!weatherObject){
//need to implement delegate method to show problem updating weather
NSLog(#"problem updating weather data");
}else{
NSLog(#"got weather for location...");
dispatch_sync(serialQueue, ^{
[weatherObjects addObject:weatherObject];
});
}
});
}
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(#"finished getting weather for all locations...");
//we will now do something with the weatherObjects
That's not going to work because you're making a new lock each time, rather than using a single lock for the variable (analogy: imagine a locked door to a room. If everyone gets their own door with a lock, it hardly matters that they lock it, since everyone else will come in their own door).
You can either use a single NSLock for all iterations, or (basically equivalently) a single serial dispatch queue.

how to update other contexts with changes from alternate threads

I have a mainContext that is used on the main thread. When the mainContext is created, I add an observer to the NotificationCenter for NSManagedObjectContextDidSaveNotification notifications.
If a new thread gets created and needs an NSManagedObjectContext, I create the context on the new thread and store some info for it. I save changes to the context on the new thread.
My notification handler gets called and merges changes for all contexts on their threads. I have a merge policy for each context in affect and I am merging changes on the appropriate threads.
I still randomly get "optimistic locking failure". Is there something I am missing?
- (void)contextChanged:(NSNotification *)notif
{
//gets called from the thread(where the context was) that made the changes
//iterate over all contexts and mergeChanges on their thread
NSLog(#"NotifContext %# %p", [notif object], [NSThread currentThread]);
//always check the main
if([notif object] != [self mainContext]){
NSLog(#"merge with main %#", [self mainContext]);
[[self mainContext] performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notif waitUntilDone:NO];
}
//check alternate cotexts and merge changes on their threads
NSDictionary *altContexts = [self.altContexts copy];
for(NSString *threadAddress in altContexts){
NSDictionary *info = [altContexts objectForKey:threadAddress];
NSManagedObjectContext *context = [info objectForKey:#"context"];
if(context != NULL && [notif object] != context){
NSLog(#"merge with %#", context);
NSThread *thread = [info objectForKey:#"thread"];
[context performSelector:#selector(mergeChangesFromContextDidSaveNotification:) onThread:thread withObject:notif waitUntilDone:NO];
}else{
NSLog(#"not with %#", context);
}
}
[altContexts release];
}
waitUntilDone:NO //should have been YES
I overlooked this. I meant to wait until it was done. Otherwise, the save happens (on thread 2), the notification gets dispatched, the contextChanged: handler gets triggered, the other contexts are told to merge changes on their thread (say thread 1), and thread 2 continues before the context on thread 1 actually gets to save.

High memory usage during CoreData import

I'm attempting to perform a fairly large CoreData import (around 25,000 rows) while still maintaining a fairly low memory footprint. I've read the documentation surrounding efficient importing of data and have endeavoured to implement everything suggested there (including setting things like my MOC's undoManager to nil).
Unfortunately, my applications memory usage still climbs to around 180MB when running the below code. Upon completion the application will sit at around the 180MB mark, regardless of the final NSAutoreleasePool drain call.
Running the application through Allocations shows that 95% of the memory usage is attributable to my [self.moc save:&error] call. What am I doing wrong here?
- (void)generateCache
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSUInteger count = 0, batchSize = 1000;
// SNIP SNIP
// Iterate over our directory structure
for(NSString *item in directoryStructure)
{
NSDictionary *info = [fm attributesOfItemAtPath:item error:nil];
FileRecord *record = (FileRecord *)[NSEntityDescription insertNewObjectForEntityForName:#"FileRecord" inManagedObjectContext:self.moc];
record.size = [NSNumber numberWithUnsignedLongLong:[info fileSize]];
record.path = item;
count ++;
if(count == batchSize)
{
NSError *error = nil;
if([self.moc save:&error])
{
NSLog(#"MOC saved down and reset");
[self.moc reset];
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
count = 0;
}
}
}
// Perform any necessary last minute MOC saves
if (count != 0) {
[self.moc save:nil];
[self.moc reset];
}
// Drain our NSAutoreleasePool
[pool drain];
// Tell our main thread that we're done
if ([self respondsToSelector:#selector(completedCache)])
{
[self performSelectorOnMainThread:#selector(completedCache) withObject:nil waitUntilDone:NO];
}
}
Instead of dealing with auto-release pools, why not explicitly manage the life-cycle of your managed objects by creating them with NSManagedObject's initWithEntity:insertIntoManagedObjectContext:? You can safely release them after modifying the object's properties since a managed object context retains a newly inserted object -- till its saved to the persistent store.
Also, I should mention a couple of problems that I see with your code:
As someone mentioned above, you are not logging errors from the save: operation. You really should -- that may highlight some (possibly unrelated) problem.
If save: is successful, you really should not need to call reset. See this section in the core data guide.

NSAutorelease memory leak

I am getting this error message in the console:
*** _NSAutoreleaseNoPool(): Object 0x10d2e0 of class NSPathStore2
autoreleased with no pool in place - just leaking
I can't figure out what is the error?
Thanks.
This is a classic memory management issue, you are autoreleasing some objects without having an autorelease pool in place. Autoreleasing is not a magic. There is an object of type NSAutoreleasePool that keeps track of all objects you autorelease and ‘from time to time’ releases them:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// An autoreleased object referenced by our pool.
id object = [NSNumber numberWithInt:1];
[pool drain];
// Our object no longer valid.
Each thread has to have its own autorelease pool. That’s quite logical, because threads run ‘at the same time’ and if they shared a common autoreleased pool, it could release an object while you are still working with it.
Now the point. There is a default autorelease pool in the main thread of every application, which means you don’t have to think about all of this and autoreleased objects are collected just fine. But if you create another thread, you are usually forced to also create an autorelease pool for this thread. Otherwise there is nobody to claim the autoreleased objects and they just leak. Which is exactly why you are getting the warning.
Leaking thread without an autorelease pool can look like this:
- (void) doSomethingInBackground
{
id object = [NSNumber numberWithInt:1];
}
- (void) someOtherMethod
{
[self performSelectorInBackground:#selector(doSomethingInBackground);
}
The fix is simple:
- (void) doSomethingInBackground
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id object = [NSNumber numberWithInt:1];
[pool drain];
}
Now you only have to figure out where you are running code in another thread.
It sounds like you've spawned a method onto a new thread (possibly using + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument;)
Any method that runs on its own thread will need to have an autorelease pool setup up to catch any autoreleased objects:
- (void)myLovelyThreadedMethod
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
... // your code here
[pool release];
}
Try using the Clang Static Analyzer

Resources