Question

I am currently looping through a range of dates and caching the EKEvents and EKReminders associated with each date. The issue that I am having is that when I fetch the EKReminder, the API runs asynchronously. The issue is that I am needing to access the contents of that thread, from my current dispatch thread queue. I created a local variable and __block'd it so that I can assign the contents of the API async to it, and then I wrapped the whole fetch invocation in a dispatch_sync call, hoping that it would hold up my current queue until the async was completed but that does not happen. How can I stop my current thread until the async API call is completed?

I provided two comments below in the code to show you the locations where I need to stop the code execution and wait.

- (void)setupCalendarCache {
    // Dispatch the queueing of previous days to a background thread
    dispatch_queue_t previousDaysCacheQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    dispatch_async(previousDaysCacheQueue, ^{
        NSLog(@"Preparing to cache previous calendar days.");

        int reverseYearsToCache = (HZ_NUMBER_OF_TOTAL_YEARS_TO_CACHE * 365) / 2;
        DEDateUtility *dateUtility = [[DEDateUtility alloc] init];
        NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

        dispatch_apply(reverseYearsToCache, previousDaysCacheQueue, ^(size_t i) {
            @synchronized (self.datesOnCalendar) {
                NSLog(@"synchronizing calendar dates.");

                // Setup start and end time for event & reminder fetching.
                NSDate *date = [dateUtility adjustDate:self.currentDate byNumberOfDays:i-1];
                NSLog(@"Working date %@", [date description]);

                [self.datesOnCalendar insertObject:date atIndex:0];

                // Start of the day
                NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
                dateComponents.hour = 0;
                dateComponents.minute = 0;
                dateComponents.second = 0;
                NSDate *startDate = [calendar dateByAddingComponents:dateComponents toDate:date options:0];

                // End of the day
                dateComponents.hour = 23;
                dateComponents.minute = 59;
                dateComponents.second = 59;
                NSDate *endDate = [calendar dateByAddingComponents:dateComponents toDate:date options:0];

                // Collections for the reminders and events fetched.
                NSArray *events = [[NSArray alloc] init];
                // Must be blocked so that the Block thread can access it even after this method is completed.
                __block __strong NSArray *remindersList = [[NSArray alloc] init];

                NSLog(@"Building predicates.");
                // Predicates for fetching the reminders and events.
                NSPredicate *fetchPredicateForEvents = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:[self.eventStore calendarsForEntityType:EKEntityTypeEvent]];
                NSPredicate *fetchPredicateForReminders = [self.eventStore predicateForIncompleteRemindersWithDueDateStarting:startDate ending:endDate calendars:[self.eventStore calendarsForEntityType:EKEntityTypeReminder]];

                // Fetch the events matching the aforementioned NSPredicate.
                events = [self.eventStore eventsMatchingPredicate:fetchPredicateForEvents];
                // Don't store a nil array in the dictionary.
                if (!events) {
                 events = [[NSArray alloc] init];
                    NSLog(@"No events found for this day.");
                } else {
                    NSLog(@"Found %d events.", [events count]);
                }

                // ISSUE: Need to block execution until the following dispatch_sync is completed. How can I do this?
                NSLog(@"Running reminders block.");
                dispatch_sync(previousDaysCacheQueue, ^ {
                    // Fetch the reminders matching the aforementioned NSPredicate.
                    [self.eventStore fetchRemindersMatchingPredicate:fetchPredicateForReminders completion:^(NSArray *reminders) {
                        if (reminders ) {
                            remindersList = reminders;
                            NSLog(@"Found %d reminders (%d)", [remindersList count], [reminders count]);
                            return;
                        }
                    }];
                });

                NSLog(@"Building dictionaries for events and reminders.");
                // CANT DO THIS. API async invocation is still taking place.
                //Place the events and reminders in the dictionary
                NSDictionary *eventsAndRemindersForDay = @{@"events": events, @"reminders" : remindersList};
                [previousEventsAndReminders setObject:eventsAndRemindersForDay forKey:[date description]];

                for (int index = 0; index < [remindersList count]; index++) {
                    EKReminder *reminder = remindersList[index];
                    NSLog(@"reminder: %@", reminder.title);
                }
            }
        });
        self.cachingStage += 1;
    });

}

Was it helpful?

Solution

Move the code that comes after the dispatch_sync() Block into the completion Block for fetchRemindersMatchingPredicate:. Waiting to do something until an asynchronous task is done is exactly what a completion Block is for.

Also, don't make dispatch_sync() calls to the current queue. It is guaranteed to deadlock a serial queue, and has a high chance even when the queue is concurrent.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top