NSOperationQueue - Getting Completion Call Too Early - objective-c

I am using a NSOperationQueue to queue and call a number of Geocoding location lookups. I want to call a completion method when all asynchronicly running lookups have been finished.
-(void)geocodeAllItems {
NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc]init];
[geoCodeQueue setName:#"Geocode Queue"];
for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
if (item.eventLocationCLLocation){
NSLog(#"-Location Saved already. Skipping-");
continue;
}
[geoCodeQueue addOperationWithBlock:^{
NSLog(#"-Geocode Item-");
CLGeocoder* geocoder = [[CLGeocoder alloc] init];
[self geocodeItem:item withGeocoder:geocoder];
}];
}
[geoCodeQueue addOperationWithBlock:^{
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSLog(#"-End Of Queue Reached!-");
}];
}];
}
- (void)geocodeItem:(EventItem *)item withGeocoder:(CLGeocoder *)thisGeocoder{
NSLog(#"-Called Geocode Item-");
[thisGeocoder geocodeAddressString:item.eventLocationGeoQuery completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) {
NSLog(#"Error: geocoding failed for item %#: %#", item, error);
} else {
if (placemarks.count == 0) {
NSLog(#"Error: geocoding found no placemarks for item %#", item);
} else {
if (placemarks.count > 1) {
NSLog(#"warning: geocoding found %u placemarks for item %#: using the first",placemarks.count,item);
}
NSLog(#"-Found Location. Save it-");
CLPlacemark* placemark = placemarks[0];
item.eventLocationCLLocation = placemark.location;
[[EventItemStore sharedStore] saveItems];
}
}
}];
}
Output
[6880:540b] -Geocode Item-
[6880:110b] -Geocode Item-
[6880:540b] -Called Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:110b] -Geocode Item-
[6880:540b] -Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:540b] -Called Geocode Item-
[6880:110b] -Geocode Item-
[6880:580b] -Geocode Item-
[6880:1603] -Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:1603] -Called Geocode Item-
[6880:580b] -Called Geocode Item-
[6880:907] -End Of Queue Reached!-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
As you can see the End of Queue function is called before the actual end of all geocoding processes + saving events. "End of Queue Reached" should only be displayed at the very end when all queued lookups have been processed. How can I get this into the right order?

Several issues are coming up here. For one, geocodeAddressString: is asynchronous, so it is returning immediately and the block operation is ending, allowing the next one to start right away. Second, you should not be making multiple calls to geocodeAddressString: one right after the other. From Apple's docs for this method:
After initiating a forward-geocoding request, do not attempt to
initiate another forward-or reverse-geocoding request.
Third, you haven't set a max number of concurrent operations on your NSOperationQueue, so multiple blocks may be executing at once anyway.
For all of these reasons, you might want to use some GCD tools to track your calls to geocodeAddressString:. You could do this with a dispatch_semaphore (to make sure one finishes before the other starts) and a dispatch_group (to make sure you know when all of them have finished) -- something like the following. Let's assume you've declared these properties:
#property (nonatomic, strong) NSOperationQueue * geocodeQueue;
#property (nonatomic, strong) dispatch_group_t geocodeDispatchGroup;
#property (nonatomic, strong) dispatch_semaphore_t geocodingLock;
and initialized them like this:
self.geocodeQueue = [[NSOperationQueue alloc] init];
[self.geocodeQueue setMaxConcurrentOperationCount: 1];
self.geocodeDispatchGroup = dispatch_group_create();
self.geocodingLock = dispatch_semaphore_create(1);
You could do your geocoding loop like this (I've altered the code a bit to make the key parts more obvious):
-(void) geocodeAllItems: (id) sender
{
for (NSString * addr in #[ #"XXX Address 1 XXX", #"XXX Address 2 XXX", #"XXX Address 3 XXXX"]) {
dispatch_group_enter(self.geocodeDispatchGroup);
[self.geocodeQueue addOperationWithBlock:^{
NSLog(#"-Geocode Item-");
dispatch_semaphore_wait(self.geocodingLock, DISPATCH_TIME_FOREVER);
[self geocodeItem: addr withGeocoder: self.geocoder];
}];
}
dispatch_group_notify(self.geocodeDispatchGroup, dispatch_get_main_queue(), ^{
NSLog(#"- Geocoding done --");
});
}
- (void)geocodeItem:(NSString *) address withGeocoder:(CLGeocoder *)thisGeocoder{
NSLog(#"-Called Geocode Item-");
[thisGeocoder geocodeAddressString: address completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) {
NSLog(#"Error: geocoding failed for item %#: %#", address, error);
} else {
if (placemarks.count == 0) {
NSLog(#"Error: geocoding found no placemarks for item %#", address);
} else {
if (placemarks.count > 1) {
NSLog(#"warning: geocoding found %u placemarks for item %#: using the first",placemarks.count, address);
}
NSLog(#"-Found Location. Save it:");
}
}
dispatch_group_leave(self.geocodeDispatchGroup);
dispatch_semaphore_signal(self.geocodingLock);
}];
}

A good solution is to add all geocoding operations as dependencies of the final cleanup operation:
- (void)geocodeAllItems {
NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];
NSOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{
// ...
}];
for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
// ...
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// ...
}];
[finishOperation addDependency:operation]
[geoCodeQueue addOperation:operation];
}
[geoCodeQueue addOperation:finishOperation];
}
Another solution would be to make the operation queue serial. The operations are still performed on a background thread, but only one at a time and in the order in which they are added to the queue:
NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];
[geoCodeQueue setMaxConcurrentOperationCount:1];

CompletionBlocks are built in to NSOperation and NSBlockOperation can handle multiple blocks so it's really easy to just add all the work that you need to run async and set your completion block to be called when it is all finished.
- (void)geocodeAllItems {
NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [[[NSBlockOperation alloc] init] autorelease]
for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
// ...
// NSBlockOperation can handle multiple execution blocks
operation addExecutionBlock:^{
// ... item ...
}];
}
operation addCompletionBlock:^{
// completion code goes here
// make sure it notifies the main thread if need be.
}];
// drop the whole NSBlockOperation you just created onto your queue
[geoCodeQueue addOperation:operation];
}
Note: You can't assume that the operations will be performed in your geoCodeQueue. They will be run concurrently. NSBlockOperation manages this concurrency.

NSOperationQueue doesn't work in the way you think, there is no direct dependence between the execution order and adding order. You can call the function in which you subtract till the number equals to zero and you can call the "callback" function.

NSOperationQueues run multiple operations concurrently by default. In practice of course, that means that operations added to the queue will not necessarily be started or finished in the same order you added them.
You can make the queue run all operations serially by setting the queue's maxConcurrentOperationCount value to 1 after you create it:
NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc]init];
[geoCodeQueue setName:#"Geocode Queue"];
[geoCodeQueue setMaxConcurrentOperationCount:1];
If you do indeed want operations to run concurrently, but still want to be notified when they've all finished, observe the queue's operations property and wait until it reaches zero, as explained in answer linked to by Srikanth in his comment.
EDIT: Nikolai Ruhe's answer is great too.

Related

What happens with unmatched dispatch_group_enter and dispatch_group_leave?

I have an async NSOperation to download data for multiple ImageFile objects. Since this is all happening asynchronously I am using a dispatch group to track the requests and then dispatch_group_notify to complete the operation when they are all done.
The question I have is what happen when the operation is ended prematurely, either by cancelation or by some other error. The dispatch group will be left with unmatched dispatch_group_enter and dispatch_group_leave so dispatch_group_notify will never be called. Is it the block retained somewhere by the system forever waiting, or will it get released when the NSOperation gets released?
Or is my approach not ideal, how else should I do this?
- (void)main
{
if (self.cancelled) {
[self completeOperation];
return;
}
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = self.persistentStoreCoordinator;
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
[context performBlock:^{
NSFetchRequest *request = [ImageFile fetchRequest];
request.predicate = ....
request.sortDescriptors = ...
NSError *error;
NSArray *imageFiles = [context executeFetchRequest:request error:&error];
if (!imageFiles) {
// Error handling...
[self completeOperation];
return;
}
dispatch_group_t group = dispatch_group_create();
for (ImageFile *imageFile in imageFiles) {
dispatch_group_enter(group);
#autoreleasepool {
[self.webService requestImageWithId:imageFile.id completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (self.cancelled) {
[self completeOperation];
return;
}
[context performBlock:^{
if (data && !error) {
imageFile.data = data;
NSError *error;
if (![context save:&error]) {
// Error handling...
[self completeOperation];
return;
}
}
dispatch_group_leave(group);
}];
}];
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self completeOperation];
});
}];
}
From the docs for dispatch_group_enter():
A call to this function must be balanced with a call to dispatch_group_leave.
From the docs for dispatch_group_t:
The dispatch group keeps track of how many blocks are outstanding, and GCD retains the group until all its associated blocks complete execution.
It talks about outstanding blocks, but what it really means is unmatched calls to dispatch_group_enter().
So, the answer to your question about what happens is that the dispatch group object effectively leaks. The block object passed to dispatch_group_notify() and any objects it has strong references to also leak. In your case, that includes self.
The answer to your question of whether your approach is "ideal" is: no, it's not ideal. It's not even valid by the design contract of GCD. You must balance all calls to dispatch_group_enter() with calls to dispatch_group_leave().
If you want to somehow distinguish between success and failure or normal completion and cancellation, you should set some state that's available to the notify block and then code the notify block to consult that state to decide what to do.
In your case, though, the code paths where you fail to call dispatch_group_leave() just do the same thing the notify block would do. So I'm not even sure why you're not just calling dispatch_group_leave() rather than calling [self completeOperation] in those cases.

MKNetworkKit and GCD dispatch_group_t

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.

Error trying to assigning __block ALAsset from inside assetForURL:resultBlock:

I am trying to create a method that will return me a ALAsset for a given asset url. (I need upload the asset later and want to do it outside the result block with the result.)
+ (ALAsset*) assetForPhoto:(Photo*)photo
{
ALAssetsLibrary* library = [[[ALAssetsLibrary alloc] init] autorelease];
__block ALAsset* assetToReturn = nil;
NSURL* url = [NSURL URLWithString:photo.assetUrl];
NSLog(#"assetForPhoto: %#[", url);
[library assetForURL:url resultBlock:^(ALAsset *asset)
{
NSLog(#"asset: %#", asset);
assetToReturn = asset;
NSLog(#"asset: %# %d", assetToReturn, [assetToReturn retainCount]);
} failureBlock:^(NSError *error)
{
assetToReturn = nil;
}];
NSLog(#"assetForPhoto: %#]", url);
NSLog(#"assetToReturn: %#", assetToReturn); // Invalid access exception coming here.
return assetToReturn;
}
The problem is assetToReturn gives an EXC_BAD_ACCESS.
Is there some problem if I try to assign pointers from inside the block? I saw some examples of blocks but they are always with simple types like integers etc.
A few things:
You must keep the ALAssetsLibrary instance around that created the ALAsset for as long as you use the asset.
You must register an observer for the ALAssetsLibraryChangedNotification, when that is received any ALAssets you have and any other AssetsLibrary objects will need to be refetched as they will no longer be valid. This can happen at any time.
You shouldn't expect the -assetForURL:resultBlock:failureBlock:, or any of the AssetsLibrary methods with a failureBlock: to be synchronous. They may need to prompt the user for access to the library and will not always have their blocks executed immediately. It's better to put actions that need to happen on success in the success block itself.
Only if you absolutely must make this method synchronous in your app (which I'd advise you to not do), you'll need to wait on a semaphore after calling assetForURL:resultBlock:failureBlock: and optionally spin the runloop if you end up blocking the main thread.
The following implementation should satisfy as a synchronous call under all situations, but really, you should try very hard to make your code asynchronous instead.
- (ALAsset *)assetForURL:(NSURL *)url {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self assetsLibrary] assetForURL:url resultBlock:^(ALAsset *asset) {
result = [asset retain];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = [error retain];
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
[assetError release];
return [result autorelease];
}
You should retain and autorelease the asset:
// ...
assetToReturn = [asset retain];
// ...
return [assetToReturn autorelease];

How do I track NSError objects across threads?

I have a set of asynchronous calls being spawned using NSInvocationOperation:
- (void)listRequestQueue:(StoreDataListRequest *)request {
[openListRequests addObject:request];
NSInvocationOperation *requestOp = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(listRequestStart:)
object:request];
[opQueue addOperation:requestOp];
[requestOp release];
}
- (void)listRequestStart:(StoreDataListRequest *)request {
if(self.resourceData == nil) {
//TODO fail appropriately...
return;
}
StoreDataListResponse *response = [self newListResponseForProductID:request.productID];
[self performSelectorOnMainThread:#selector(listRequestFinish:)
withObject:response waitUntilDone:NO];
[response release];
[self performSelectorOnMainThread:#selector(cleanUpListRequest:)
withObject:request waitUntilDone:NO];
}
- (void)listRequestFinish:(StoreDataListResponse *)response {
[self.delegate storeData:self didReceiveListResponse:response];
}
- (StoreDataListResponse *)newListResponseForProductID:(NSString *)productID {
CollectionData *data = [self.resourceData dataForProduct:productID];
if(data == nil) {
//TODO do something
}
StoreDataListResponse *response = [[StoreDataListResponse alloc] init];
response.productID = productID;
if(productID != data.productID) {
//TODO fail; remove product from list
}
response.name = NSLocalizedString(#"Loading...", #"Loading message");
response.blurb = NSLocalizedString(#"Waiting for response from server", #"Waiting for website to respond");
return response;
}
For each of the TODOs in the above code, I should resolve the issue and let any handlers know that things have failed and why. Looking at the NSError class and documentation, it appears to be the appropriate answer but I am having trouble figuring out how to get to work with NSInvocationOperation and performSelectorOnMainThread:withObject:waitUntilDone:. I can certainly get the NSErrors out of the newListResponseForProductID: method by changing it to something like this:
- (StoreDataListResponse *)newListResponseForProductID:(NSString *)productID error:(NSError **)error;
How do I get the error generated back into the main thread so I can deal with the failed request?
The easiest way to run any code on the main thread is to use GCD and blocks on iOS 4 and above. Like this:
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Your code to run on the main thread here,
// any variables in scope like NSError is captured.
});
Or use dispatch_sync() if you know you are on a background thread and want the block to complete on the main thread before you continue.

When making unit tests for objective C, how do you test blocks?

I have a function (internally uses ASIHTTPRequest) which calls a block with the result:
[Http get:#"http://api.geonames.org/postalCodeLookupJSON"
params:params cacheMins:0 complete:^(NSDictionary *response, BOOL success) {
STAssertTrue(success, #"JSON retrieved OK");
STFail(#"blah");
}];
I want to test the above, but it seems the test doesn't get called.
How can i ensure that the test waits till the block is called?
-edit-
Of course i don't recommend to do this in the main app in the gui thread, in this particular situation it is only for a unit test.
Found a solution:
Wait for code to finish execution
Eg:
__block int done=0;
[Http get:#"http://api.geonames.org/postalCodeLookupJSON"
params:params cacheMins:0 complete:^(NSDictionary *response, BOOL success) {
STAssertTrue(success, #"JSON retrieved OK");
NSArray *postalcodes = [response objectForKey:#"postalcodes"];
NSDictionary *first = [postalcodes objectAtIndex:0];
NSString *adminName1 = [first objectForKey:#"adminName1"];
STAssertTrue([adminName1 isEqualToString:#"New South Wales"], #"NSW");
done=1;
}];
// https://stackoverflow.com/questions/3615939/wait-for-code-to-finish-execution
while (!done) {
// This executes another run loop.
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
// Sleep 1/100th sec
usleep(10000);
}
Not sure where I found this, but there's a better way that doesn't use sleeps:
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !placeMarkUpdated){};
here it is in context, testing a reverse geocoding request:
__block BOOL placeMarkUpdated = NO;
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
if (placeMarkUpdated == NO) {
placeMarkUpdated = YES;
CLPlacemark *placemark = [placemarks objectAtIndex:0];
address = [Address addressFromPlacemark:placemark];
}
}];
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !placeMarkUpdated){};
Sleeps suck cause they slow down the build (I know 5s doesn't sound bad, but consider the old story: guy goes to doctor cause his knees hurt from running, Doctor says 'get up on the table' and taps his knee and says 'does that hurt?' guy says 'no,' doctor: 'it would if I did it 10K times...'