GCD Prevents Call-back Blocks Being Called? - objective-c

I am very new to GCD, but I am trying to only call certain code after other actions have completed. Anyway, this means I am using code you see below:
dispatch_group_async(group, queue, ^{
[self getTitlesArrayForChannel:channelID completionHandler:^(NSMutableArray *results) {
//Nothing in this block called, when method inside dispatch_group
[resultsDict setObject:results forKey:kFeedElementTitle];
NSLog(#"Received title result");
}];
});
So I am calling a method with a call-back block giving me the results of that method. When I put it inside the dispatch_group_asyncblock the call-back block doesn't ever get called. Why might this be?
Something else worth noting would be, I am getting a console message when running this:
Storing duplicate dispatch for GTLQueryYouTube selector setPart:
I have no idea what it really means and can't find any relatable examples online. Possible it has something to do with it?
Basically, I am trying to call two different methods with call-back blocks, giving me results, then once I have results from both, I want to call a final block giving me a dictionary of each of the results. But I bumped into this problem.

This has more to do with Google API, the error is not caused by GCD. You probably called this method:
+ (void)setStoredDispatchForClass:(Class<GTLRuntimeCommon>)dispatchClass
selector:(SEL)sel
returnClass:(Class)returnClass
containedClass:(Class)containedClass
jsonKey:(NSString *)jsonKey;
This method stores the dispatch details for a class and selector. If you call two times this method passing the same class and selector, this code will be executed:
NSDictionary *selDict = (NSDictionary *)CFDictionaryGetValue(classDict, sel);
if (selDict == nil) {
selDict = [NSDictionary dictionaryWithObjectsAndKeys:
jsonKey, kJSONKey,
returnClass, kReturnClassKey, // can be nil (primitive types)
containedClass, kContainedClassKey, // may be nil
nil];
CFDictionarySetValue(classDict, sel, selDict);
} else {
// we already have a dictionary for this selector on this class, which is
// surprising
GTL_DEBUG_LOG(#"Storing duplicate dispatch for %# selector %#",
dispatchClass, NSStringFromSelector(sel));
}
}
In the else the error gets printed. You may see the code here:
http://google-api-objectivec-client.googlecode.com/svn/trunk/Source/Objects/GTLRuntimeCommon.m

The Google API does not appear to be thread safe. I was having similar problems, then I adjusted my code to ensure all API method calls occur on the main thread. Problem disappeared.

Related

dispatch async with blocks exc_bad_access non ARC project

i have a non arc project. i'm trying to use dispatch_async to get data from server and save it in sqlite. the dispatch_async happens inside a method with callback. on calling the method the app crashes with exc bad access. here is how i've implemented the code.
- (void) HandleData:(const char*) receivedData WithSuccess:(void(^)(BOOL finishing))completed
{
dispatch_queue_t fetchQ = dispatch_queue_create("Refreshing", NULL);
dispatch_async(fetchQ, ^{
[self write_data_in_sqlite]// **<--crash happens here in the method which is called here**
}
dispatch_sync(dispatch_get_main_queue(), ^{
completed(YES);
});
});
dispatch_release(fetchQ);
}
and i call the method as follow:
HandleResponse *handleResponse = [[[HandleResponse alloc] init] autorelease];
[handleResponse HandleData:aData WithSuccess:^(BOOL finishing) {
if(finishing)
{
//update the UI here
}
}];
if i remove the dispatch_async then it doesnt crash, but my UI gets blocked while writing to the sqlite.
what am i doing wrong?
edit:
removing the block and using dipatch_async produces the same exc_bad_access crash.
edit 2:
i tried example answer given below, it still crashes.
i thought to copy it then autorelease it. it crashes still but nit that often. i'm gonna check for memory leak. i'll report.
HandleResponse *handleResponse = [[[HandleResponse alloc] init] autorelease];
[handleResponse HandleData:aData WithSuccess: [[^(BOOL finishing) {
if(finishing)
{
//update the UI here
}
} copy] autorelease];
edit 3:
the crash happens in strlen even the xml content is in xmlResopnse. but why this happen with dispatch and not without it
xmlDocPtr xml= xmlParseMemory(xmlResopnse, strlen(xmlResponse);
edit 4:
as in answer below suggested not to use c objects in dispatch async. so i converted xmlResponse from const char* to nsstring and it doesnt crash.
Everything you've shown seems to be okay in terms of blocks and memory management. It must be something else.
I notice that you're passing in a C string (the char pointer receivedData) that you're not using. If you're not showing us the real code, and you are actually using the receivedData variable in the block, then that could be a problem, because the block simply captures the char pointer, but does not manage the memory of the string behind the pointer (it is not an Objective-C object). Therefore, it is possible that the C string is only valid in the calling scope (before the asynchronous operation), and no longer valid when the asynchronous operation runs. Your statement that something is crashing at strlen supports the idea that there is something wrong with some C string. You should try using NSString objects instead, since as objects they are properly memory-managed by blocks.

Cancel a completion handler block from running

I'm using a library that does some work in the background and then calls a completion handler. All really standard.
[[LastFm sharedInstance] getInfoForArtist:#"Pink Floyd" successHandler:^(NSDictionary *result) {
// Do stuff...
} failureHandler:nil];
I'm actually using this inside a tableview: in every cell (subclass) I get information about an artist and show it. This is also the problem: when the cell is moved off screen and reused for another artist, the successHandler for the previous artist can still be executed, resulting in labels and images that change multiple times in rapid succession.
My thought was to create a NSOperationQueue, add the getInfoForArtist call inside of it, and make sure it can be cancelled:
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOperation = operation;
[operation addExecutionBlock:^{
[[LastFm sharedInstance] getInfoForArtist:mediaItem.artist successHandler:^(NSDictionary *result) {
if (weakOperation.isCancelled) {
return;
}
// Do stuff...
} failureHandler:nil];
}];
[self.queue addOperation:operation];
The problem is that weakOperation is always null inside the successHandler. If I change it to be __block instead of __weak, weakOperation is the correct instance, but's its isCancelled state is always NO.
I am calling [self.queue cancelAllOperations]; at the correct time, when the cell is moved off screen.
So my question is, how can I prevent the successHandler from running after the cell was reused for another artist?
The problem is that you're calling an asynchronous API. The lifetime of your operation is the call to getInfoForArtist:successHandler:, which probably returns immediately. By the time the asynchronous callback is executed, the operation has been disposed of, which is why the reference is nil inside the callback block.
By the time the successHandler is executed, there's no point in canceling the operation--you're not going to save network resources, and you may as well save the results of the lookup locally. The UI problem is that you shouldn't reference the cell (or its subviews, if you reuse them) directly. You might consider storing the results in a local NSDictionary keyed on either the NSIndexPath of the row, or the artist ID. Then, in your cellForRowAtIndexPath:, first check that dictionary before making the LastFM call. In the successHandler callback, you could iterate through the tableView's visibleCells and try to load the data from your dictionary.

App stops inconsistently at a call to dispatch_sync() [duplicate]

Im using XMPPFramework and in it's code there's a method like this:
- (NSDictionary *)occupants
{
if (dispatch_get_current_queue() == moduleQueue)
{
return occupants;
}
else
{
__block NSDictionary *result;
dispatch_sync(moduleQueue, ^{//IT BLOCKS HERE, WITHOUT MESSAGE
result = [occupants copy];
});
return [result autorelease];
}
}
[EDIT]
It blocks inconsistently, not always, since the app is not doing anything I pause it and I see the thread has stopped there, and it never continues to execute.
What is wrong? Any ideas?
Thanks
The behavior you explain perfectly matches with the one that appears when you try to send perform an operation on main thread via GCD while being on the main thread. So you should check if moduleQueue is the main queue, then this is it. Try checking if it is the main queue if it is, skip the dispatch_sync block.
Blocks sometimes need to retain variables to ensure they are available when they execute. If you use a local variable inside a block, you should initialise it to zero where you declare it outside the block.

App blocks while dipatching a queue

Im using XMPPFramework and in it's code there's a method like this:
- (NSDictionary *)occupants
{
if (dispatch_get_current_queue() == moduleQueue)
{
return occupants;
}
else
{
__block NSDictionary *result;
dispatch_sync(moduleQueue, ^{//IT BLOCKS HERE, WITHOUT MESSAGE
result = [occupants copy];
});
return [result autorelease];
}
}
[EDIT]
It blocks inconsistently, not always, since the app is not doing anything I pause it and I see the thread has stopped there, and it never continues to execute.
What is wrong? Any ideas?
Thanks
The behavior you explain perfectly matches with the one that appears when you try to send perform an operation on main thread via GCD while being on the main thread. So you should check if moduleQueue is the main queue, then this is it. Try checking if it is the main queue if it is, skip the dispatch_sync block.
Blocks sometimes need to retain variables to ensure they are available when they execute. If you use a local variable inside a block, you should initialise it to zero where you declare it outside the block.

ARC: Getting EXC_BAD_ACCESS from inside block used in delegate method

I must be doing something wrong, but the Automatic Reference Counting docs don't give me a hint on what it might be. What I'm doing is calling a method with a block callback from inside a delegate method. Accessing that same delegate from inside the block results in a bad access. The problem is the object I'm passing - loginController which is sending the message to its delegate - is clearly not released, when I don't access it inside the block I can call the method multiple times without an issue. Here's my code:
- (void)loginViewDidSubmit:(MyLoginViewController *)loginController
{
NSString *user = loginController.usernameLabel.text;
NSString *pass = loginController.passwordLabel.text;
__block MyLoginViewController *theController = loginController;
[self loginUser:user withPassword:pass callback:^(NSString *errorMessage) {
DLog(#"error: %#", errorMessage);
DLog(#"View Controller: %#", theController); // omit this: all good
theController = nil;
}];
}
NSZombieEnabled does not log anything and there is no usable stack trace from gdb. What am I doing wrong here? Thanks for any pointers!
Edit:
I figured the problem has a bigger scope - the callback above is called from an NSURLConnectionDelegate method (the block itself is a strong property for that delegate so ARC should call Block_copy()). Do I need to take special measurements in this scenario?
Flow (the loginController stays visible all the time):
loginController
[delegate loginViewDidSubmit:self];
View Delegate
(method shown above calls the loginUser: method, which does something like:)
httpDelegate.currentCallback = callback;
httpDelegate.currentConnection = // linebreak for readability
[[NSURLConnection alloc] initWithRequest:req
delegate:httpDelegate
startImmediately:YES];
NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)aConnection
didFailWithError:(NSError *)error
{
if (NULL != currentCallback) {
currentCallback([error localizedDescription]);
self.currentCallback = NULL;
}
}
And this is where I get the bad access, but ONLY if I access that loginController variable...
Set copy attribute to the property, or just call 'copy' method for the block.
- (void)loginUser:(NSString *)user withPassword:(NSString *)pass callback:(void (^callback)(NSString *))
{
callback = [callback copy];
The actual solution was that I had the block as a strong property, but it should have been a copy property! D'oh!
First "Solution":
I just found a way to prevent the bad access. As shown in my Edit above, the View Delegate forwards the block to the httpDelegate (an instance of another class), which in turn keeps a strong reference to the block. Assigning the block to a temporary variable and forwarding the temporary block variable solves the problem, for whatever reason. So:
This crashes on block execution, as described
httpDelegate.currentCallback = callback;
This works
MyCallbackType aCallback = callback;
httpDelegate.currentCallback = aCallback;
I'll accept this as the answer, if anybody has more insights I'm happy to revise my decision. :)
I figure what is happening there is that the loginController is dead right after calling its delegate. Therefore a crash occurs. Without more information I can think of possible scenarios only:
The block do not retains the loginController object (__block type modifier). If the block is executed asynchronously, the loginController might no longer be available if it was killed elsewere. Therefore, no matter what you want to do with it, you wont be able to access it inside the block and the app will crash. This could happen if the controller is killed after sending loginViewDidSubmit.
I think most likely this could be your situation: The loginController calls its delegate object. The delegate method ends up synchronously invoking the callback block that kills the controller. The controller is expected to be alive after invoking the delegate method. Killing it inside the delegate method, most likely will cause crashes to happen. To make sure this is the problem, simply nil the loginController in the delegate method and put an NSLog statement in the controller after calling the delegate, never mind the block, you will get a crash there.
Perhaps if you paste some code we could help more.
My best.