1

I have the following snippet of code below that fetches data from Parse using PFQueues in the background and returns data and a status. This structure is based off of waiting for the dispatch_group_t to notify that's it's completed all entered groups. Unfortunately dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ is called before the completion blocks call dispatch_group_leave. By the time the dispatch_group_leave() is called on any of the completion blocks, an EXC_BAD_INSTRUCTION is thrown. I've attached an image below for the instruction error. Does anyone know if I'm doing something wrong or if Parse has some annoyances that prevent me from using this method?

    - (void)downloadAndCacheObjectsWithCompletion:(void (^)(NSError *))callback
{

    __block NSError *downloadError1;
    __block NSError *downloadError2;
    __block NSError *downloadError3;
    __block NSError *downloadError4;

    NSLog(@"%@", NSStringFromSelector(_cmd));

        dispatch_group_t downloadGroup = dispatch_group_create();

        dispatch_group_enter(downloadGroup);
        [self fetchDataWithCompletion:^(NSArray *artwork, NSError *error) {
            downloadError1 = error;
            dispatch_group_leave(downloadGroup);
        }];

        dispatch_group_enter(downloadGroup);
        [self fetchDataWithCompletion:^(NSArray *artworkPhotos, NSError *error) {
            downloadError2 = error;
            dispatch_group_leave(downloadGroup);
        }];

        dispatch_group_enter(downloadGroup);
        [self fetchDataWithCompletion:^(NSArray *artists, NSError *error) {
            downloadError3 = error;
            dispatch_group_leave(downloadGroup);
        }];

        dispatch_group_enter(downloadGroup);
        [self fetchDataWithCompletion:^(NSArray *badges, NSError *error) {
            downloadError4 = error;
            dispatch_group_leave(downloadGroup);
        }];


        dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
            NSError *returnError;
            if (downloadError1 || downloadError2 || downloadError3 || downloadError4) {
                returnError = [[NSError alloc] initWithDomain:@"ParseFactory" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"There was an error retrieving the content"}];
            }
            if (callback) {
              callback(returnError);
            }
        });

}



 - (void)fetchDataWithCompletion:(void(^)(NSArray *data, NSError *error))callback
{
    NSLog(@"Fetching Data");
    if ([self.cachedData objectForKey:kDataClassName]) {
        if (callback) {
            callback([self.cachedData objectForKey:kDataClassName], nil);
        }
        return;
    }
    PFQuery *dataQueue = [PFQuery queryWithClassName:kDataClassName];
    dataQueue.cachePolicy = kPFCachePolicyCacheThenNetwork;
    [dataQueue findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

        if (!error) {
            [self.cachedData setObject:objects forKey:kDataClassName];
        } else {
           NSLog(@"Fetching Data Error: %@", error);
        }
        if (callback) {
            callback(objects, error);
        }
    }];

}

The download process listed above is called from AppDelegate as such

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Register PFObject subclasses
    [Data registerSubclass];

[Parse setApplicationId:@"appkey" clientKey:@"clientkey"];
    [[ParseFactory sharedInstance] downloadAndCacheObjectsWithCompletion:^(NSError *error) {

    }];

    return YES;
}

enter image description here

Stack trace: enter image description here enter image description here

TheCodingArt
  • 3,436
  • 4
  • 30
  • 53
  • 1
    Probably unrelated to the error, but why do you use an outer `dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{...});` ? IMO that makes no sense. You can just remove it, and call the code within the inline block directly. – CouchDeveloper May 10 '14 at 14:20
  • Agree with @couchdeveloper. This looks fine except for wrapping the whole thing in that dispatch_async. – jrturton May 10 '14 at 14:34
  • That was remaining code from the old setup that I haven't removed yet, I'll edit the above to remove that. I know it's a redundant piece. So in this case, why would I be receiving the error I'm getting then? – TheCodingArt May 10 '14 at 14:42
  • Have you changed your code, or just the question? It looks like a variable scope issue, but it's hard to know without knowing what code you're actually running. – jrturton May 10 '14 at 14:48
  • I just removed the dispatch_async wrapper. It really makes no difference. Same error in the end. – TheCodingArt May 10 '14 at 14:58
  • I also updated the code above to reflect the fetch data call – TheCodingArt May 10 '14 at 15:07
  • IMHO, the code should be OK. So, the issue is likely elsewhere. To verify my assertion - please create a simplistic sample with a mock async function and test your code (a Foundation console application is suitable here). – CouchDeveloper May 10 '14 at 15:16
  • Just one note: before calling a block, check if it is not nil ;) – CouchDeveloper May 10 '14 at 15:18
  • Yea I know, the code has not been fully combed yet as core functionality does not work. I've added the class from AppDelegate above. This shows a borderline call from start till the return call. The only thing that now happens, is an empty splash screen ViewController is loaded (with no functionality other than a spinning activity indicator) and the ParseFactory is called to download via the methods provided above. The same issue is happening here leading me to believe I'm doing something wrong. – TheCodingArt May 10 '14 at 15:37

2 Answers2

3

The error you're seeing indicates that your program calls dispatch_group_leave too many times. It's trivial to reproduce. I reproduced it with this program:

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_leave(group);
    }
    return 0;
}

Therefore I deduce that your fetchDataWithCompletion: method calls its completion block more than once. If you can't figure out why, edit your question to include the source code of that method (and any related methods or declarations).

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • I already have editing my question to include the fetchDataWithCompletion method as noted above. I know the error to be associated with that, but, I don't see how this is happening since I don't retain the completion blocks and everything should only be called one. – TheCodingArt May 10 '14 at 16:17
  • I'm not familiar with the Parse API. Are you sure `-[PFQuery findObjectsInBackgroundWithBlock:]` only calls the completion block once? Put an `NSLog` in the completion block and see how many times it's being called. – rob mayoff May 10 '14 at 16:21
  • I've verified that it's only called once via logging. The only time there are multiple callback consists of when downloading a PFFile and getting a download status. The documentation can be found here: https://www.parse.com/docs/ios_guide#top/iOS. Nothing in the documentation or examples indicate otherwise and my existing tests pass for the scenario of one callback. – TheCodingArt May 10 '14 at 16:25
  • I've added the stack trace above. – TheCodingArt May 10 '14 at 16:29
  • I see `fetchArtworkPhotosWithCompletion:` in your stack track but I don't see it in your code. Why is it in the stack trace? – rob mayoff May 10 '14 at 16:33
  • fetchDataWithCompletion is fetchArtworkPhotosWithCompletion. I've change certain variable names for public posting sake. Data is a generic term that is been used to replace anything that my company wouldn't want posted publicly. The re wording does not hide any functionality as the rest is copy/paste. – TheCodingArt May 10 '14 at 16:41
  • “fetchDataWithCompletion is fetchArtworkPhotosWithCompletion”. You have four calls to `fetchDataWithCompletion:` in your posted code. Are they all really calls to `fetchArworkPhotosWithCompletion:`? I suspect they are calls to different methods. When you don't post your real code, you make it difficult to get help. – rob mayoff May 10 '14 at 16:43
  • The 4 calls call 4 different methods that do the EXACT same thing as the fetch method above. The absolute only difference is that they download a different parse class. These methods could be turned into one method while adding a parameter for the classname constant key – TheCodingArt May 10 '14 at 16:45
  • If you want help solving the problem, you'll have to post the real code for all of the methods involved, by copy and paste, without modifying it. If you don't know what's causing the problem, how do you know which details are safe to change? If you think you can safely change the details, then change them in your code (not in your stackoverflow question), run the code again, and make sure it reproduces the problem. Then copy and paste that tested code into your question. – rob mayoff May 10 '14 at 16:48
  • The only reason they are separate methods is for explicit debugging purposes. – TheCodingArt May 10 '14 at 16:51
  • The code provide above is %100 copy paste and useable in the EXACT same manner for the issue I'm having. You are focusing on an invalid assumption that something in the method calls is different and is causing the issue. There is beyond more than enough debugging information above considering the app is ONLY running the code listed above. If you need more information, please don't request it as I will wait for someone else with a better understanding of parse and gdc to assist. I do not appreciate bad assumptions. – TheCodingArt May 10 '14 at 16:56
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/52439/discussion-between-thegamingart-and-rob-mayoff) – TheCodingArt May 10 '14 at 17:06
  • The issue was confirmed to be a typo in the caching policy. I was using cache then network rathe cache else network, causing multiple callbacks. – TheCodingArt May 10 '14 at 17:39
0

I come in late, but it seems clear your issue comes from the kPFCachePolicyCacheThenNetwork. Parse will call the completion block twice, one with cached data (even the 1st time), one with downloaded data... so your dispatch_group_leave will be called twice as much as your dispatch_group_enter.

Sebastien C.
  • 2,099
  • 17
  • 21