I have some code which is similar to the following code:
dispatch_queue_t queue = dispatch_queue_create("", 0);
dispatch_queue_t inner_queue = dispatch_queue_create("", 0);
dispatch_async(queue,
^{
NSAutoreleasePool* autoreleasePool = [[NSAutoreleasePool alloc] init];
NSArray* objects = [self.otherObject getObjectsFromSlowQuery];
[objects enumerateObjectsWithUsingBlock:^(id anObject, NSUInteger idx, BOOL *stop)
{
[anObject prepare];
dispatch_async(inner_queue,
^{
InnerObject* innerObject = anObject.innerObject;
[self.helper doSomethingExpensiveWithObject:innerObject];
});
dispatch_sync(self.syncQueue,
^{
[self insertIntoCollection:anObject];
});
}];
dispatch_release(inner_queue);
[autoreleasePool drain];
});
dispatch_release(queue);
Where [anObject.innerObject] is a nonatomic property.
I got a crash report from a user which shows an EXC_BAD_ACCESS in objc_msgSend() when trying to access a property of innerObject within the doSomethingExpensiveWithObject: call.
At first I was thinking that perhaps the autoreleasePool was drained so the innerObject instance was released before returning from doSomethingExpensiveWithObject: but as far as I know anObject should be retained by the inner dispatch_async call which should also keep the innerObject alive.
What am I missing?
Instruments will make quick work of this - run with zombies and review the reference counts when it stops.
Related
I have the code below to load a group of images and notify me via completionHandler when they are all done loading. However, I find that certain dispatch_group_leave won't be called at times and my guess is imageLoader is deallocated before the block gets to run. If I put a reference of imageLoader within the loadImageWithURL:completionHandler block, then everything works as intended.
Is my guess of the cause correct? What's the correct fix for this issue? I know ARC does block copy in most cases automatically, should I do a block copy in this case?
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler {
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
dispatch_async(queue, ^{
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
});
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
});
}
Here's my guess. It's only a guess because you haven't posted the code of anything to do with ImageLoader.
If -loadImageWithURL:completionHandler: operates asynchronously i.e. it uses an async dispatch queue itself then you could be right that it is being deallocated before the load finishes. This is because its lifetime is just the scope of the for block it is declared in.
In fact, there is no reason why that method needs to do stuff asynchronously, because you have already got it in an asynchronous block. Just have a synchronous method and call dispatch_group_leave() after the method finishes.
EDIT
Given that you have no control over ImageLoader and -loadImageWithURL:completionHandler: operates asynchronously without any help from you, you should remove the dispatch_async wrapper around the call. You'll still have the problem of the ImageLoader being deallocated, but that can be avoided by putting each ImageLoader in a array when you create it.
The code would look something like this:
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler
{
NSMutableArray* loaders = [[NSMutableArray alloc] init];
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
[loaders addObject: imageLoader];
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
[loaders removeAllObjects];
});
}
The following is my code using GCD to fetch data from the network and then pass it to a response block on the main queue.
+ (void)allData:(void(^)(NSArray*))responseBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block NSArray *data = [[self all] retain]; // get data from network
dispatch_async(dispatch_get_main_queue(), ^{
responseBlock(data); // 2
});
});
}
The [[self all] retain] is to prevent the object from being released . But now the responseBlock has to release it . Is this the correct way ?
you can do like this
+ (void)allData:(void(^)(NSArray*))responseBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block NSArray *data = [[self all] retain]; // get data from network
dispatch_async(dispatch_get_main_queue(), ^{
responseBlock(data); // 2
[data release]; // you can release here
data = nil;
});
});
}
or simply remove __block, the inner block (the one you pass to main queue) will retain/release data automatically and because data does not retaining anything else so I can't see any possible retain cycle here (since self here is a Class).
I am trying to use MKNetworkKit to fetch an array of links from a web service, then parse each response on a background thread, and use the dispatch_group_t of GCD to wait until all threads are finished processing. Where I'm stuck is I can't figure out why my dispatch_group_notify is not waiting for all threads in the group to complete. Running this code will print:
results count: 0
added into results, count: 1
added into results, count: 2
The dispatch group is not waiting on its threads. I have also tried dispatch_group_wait but that gave me a crash. I don't know if MKNetworkKit's use of NSOperation is conflicting with this issue. Thanks for any help!
- (MKNetworkOperation *)getABunchOfMovies:(NSArray *)movies onCompletion:(CastResponseBlock)completionBlock onError:(MKNKErrorBlock)errorBlock
{
MKNetworkOperation *operation;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
block NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:[movies count]];
for (NSString *movieTitle in movies) {
operation = [self operationWithPath:REQUEST_URL(API_KEY, [movieTitle urlEncodedString])];
[operation onCompletion:^(MKNetworkOperation *completedOperation) {
dispatch_group_async(group, queue, ^{
NSDictionary *response = [completedOperation responseJSON];
id result = [self processResponse:response withMovieTitle:movieTitle];
#synchronized (results) {
[results addObject:result];
NSLog(#"added into results, count: %d", [results count]);
}
});
}
onError:^(NSError *error) {
errorBlock(error);
}];
[self enqueueOperation:operation];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"results count: %d", [results count]);
// Return array here
completionBlock(results);
});
dispatch_release(group);
return operation;
}
Edit:
I still can't figure out why, but if I change it to use dispatch_group_enter(group); and match it with a dispatch_group_leave(group); at the end of the completion block, it works. Does anyone have any idea why this is happening?
At the moment MKNetworkKit doesn't support queue completion handlers.
You should consider adding operation dependency instead of this hack.
[lastOperation addDependency:op1];
[lastOperation addDependency:op2];
and assume that when "lastOperation" completes, the queue has indeed completed.
Another way is to KVO the "operationCount" keypath on the engine and check if it reaches zero.
MKNetworkEngine has a code block that does this to show and hide the network activity indicator.
Hey experts, I'm having a little trouble with NSThread. Xcode keeps on giving me "* __NSAutoreleaseNoPool(): Object 0x5694dc0 of class NSCFString autoreleased with no pool in place - just leaking" errors.
I'm correctly declaring the pool with the line
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
then at the end of my loop I use:
[pool release];
Is it because I'm using a delegate method as the performSelectorInBackground?
Thanks stackoverflow.
- (void)preFetch { //process filenames to be downloaded and assign types to each one
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *regions = [NSArray arrayWithObjects: #"dr_national", #"ds_ir", #"conus_FL360", #"FL360_conus", #"dr_nw", #"dr_nc", #"dr_ne", #"dr_sw", #"dr_sc", #"dr_se", #"ds_ir_nw", #"ds_ir_nc", #"ds_ir_ne", #"ds_ir_sw", #"ds_ir_sc", #"ds_ir_se", nil];
NSError* error;
for (NSString *regionDir in regions) {
NSLog(#"region now: %#", regionDir); foo = 0;
NSString *regUrl = [NSString stringWithFormat:#"http://someUrl/%#/index.lst", regionDir ];
NSString* text1 = [NSString stringWithContentsOfURL:[NSURL URLWithString:regUrl ] encoding:NSASCIIStringEncoding error:&error];
NSArray *listItems = [text1 componentsSeparatedByString:#"\n"];
for (int k=0; k<[listItems count]; k++) {
if ([[listItems objectAtIndex:k] length] != 0){
NSString *newpath = [NSString stringWithFormat:#"http://someUrl/%#", [listItems objectAtIndex:k]];
NSLog(#"newpath: %#",newpath);
[self performSelectorInBackground:#selector(moveProgressBar) withObject:nil];
[self fetchImages:newpath:type]; //pass multiple arguments to fetchImages, newpath and type
}
}
}
[pool release];
}
- (void)moveProgressBar{
[delegate increaseAmount];
}
You should just set up an autorelease pool in your method, since that's being called on a different thread.
- (void)moveProgressBar
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[delegate increaseAmount];
[pool drain];
}
Edit
Having said that, looking at the code itself, it seems that you might be trying to update the UI from a background thread? Any code that does that should be executed on the main thread.
If you have a long running process that you want to run which doesn't lock the UI, and keeps the user updated on progress, the typical pattern would be to do the processing itself on a background thread, and periodically update the UI using performSelectorOnMainThread:.
I would like to send an object back to the main thread from worker thread. However do auto-release pools work between threads? Is there anything wrong with the following code:
-(void)mainThreadReceiveResult:(id)response
{
[response retain];
/* Do some stuff with response */
[response release];
}
-(void)workerThreadDoWork
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
response * response = [[[response alloc] init] autorelease];
response->someData = [self getSomeData];
[delegate performSelectorOnMainThread:#selector(receiveResult:) withObject:response waitUntilDone:NO];
[pool release];
}
Seems to work fine. However is it possible that the worker thread could reach [pool release] before the main thread is able to retain it?
Your code shouldn't crash: performSelectorOnMainThread: retains its arguments until after the selector finishes, so your retain/release pair is superfluous.
See the documentation:
This method retains the receiver and the arg parameter until after the selector is performed.
Also: you should probably [pool drain] instead of [pool release].
This may answer your question.
Here's what he did to solve the problem. The explanation is given in that link.
- (void)runSomethingThatWillFail:(NSError **)error {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:#"/BOGUS" error:error];
[*error retain];
[pool release];
[*error autorelease];
}