Search code examples
iosobjective-ccompletion-block

IOS/Objective-C: Retrieve NSArray from completion block


I am trying to retrieve an array of EKReminders from a completion block of a method. The method returns the array I desire. However, I can't find a good way to obtain it back on the main thread at known time such that that I know the block has completed.

To retrieve EKReminders, Apple requires a completion block approach (in contrast to EKEvents.) So my fetch code contains the following:

-(void) fetchReminders{
[self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
        self.reminders = reminders;
 }];
}

The above code successfully runs and places reminders in the ivar once finished..

The problem is I don't know when the block completes so if I merely try to access self.reminders in viewwillappear after callng the above method, it returns empty presumably because the block has not yet completed I would like some way to set the ivar when I know that the block has completed to eliminate the race condition.

A number of answers on SO for how to retrieve a value form a completion block recommend passing in a completion block to the method as follows:

//change above method to 
-(void) fetchReminders withCompletion:(void (^)(NSArray *reminders))completion; {

and call it in viewwillappear with:
  [self fetchReminders withCompletion:^(NSArray* reminders) {
        self.reminders =remindersArray;
        NSLog(@"remindersArray in viewwillappear%@",remindersArray);
    }];

When I try this, the completion block in viewwillappear never fires so something is going wrong.

Would appreciate any suggestions on how to successfully retrieve at a known point in time an array retrieved within a completion block.


Solution

  • There are various different ways to achieve that. You can either set up a key value observer or a NSNotification or do your necessary actions in your setter method of your reminders object or even just call your necessary actions in that completionHandler block.

    Calling out from the handler block

    -(void) fetchReminders{
       [self.eventStore fetchRemindersMatchingPredicate:predicate 
        completion:^(NSArray *reminders) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.reminders = reminders;
                [self doWhatYaGottaDoMethod];
            });
        }];
     }
    

    Key Value Observer Method
    When your view loads you can add a key value observer like this:

    [self addObserver:self
           forKeyPath:@"self.reminders"
              options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
              context:nil];
    

    And implement this method:

    -(void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context{
    
         if([keyPath isEqualToString:@"self.reminders"]){
             //You got your reminders, do what you gotta do
          }
    }
    

    Make sure to remove observers when your viewController is dealloacted or you may get a crash