How do I await multiple functions with completionHandlers?

Viewed 334

I'm currently working on a bridge for HomeKit for React-Native and I have to read HomeKit values to return for my Javascript code.

To read the latest value of a characteristic we must use readValueWithCompletionHandler where the value isn't ready until it's complete. So I must read 6 of these characteristics and assign each one to my NSMutableDictionary object to return for React-Native to handle.

Rather than nesting each read inside the previous completionHandler with return accObject inside the final one, what's the cleanest approach to waiting for all completionHandlers to have been complete before return.

I've been searching for a while, looking at semaphores, DispatchGroup & a couple other solutions without a great idea about how to solve this problem.

For (rough) example:

NSMutableDictionary *accObject = [[NSMutableDictionary alloc] initWithCapacity: 6];

[charac1 readValueWithCompletionHandler:^(NSError *error) {
        if (error) {
          RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
        }
        RCTLog(@"READ SETPOINT VALUE: %@", charac1.value);
        accObject[@"charac1"] = charac1.value;
      }];
...

[charac6 readValueWithCompletionHandler:^(NSError *error) {
        if (error) {
          RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
        }
        RCTLog(@"READ SETPOINT VALUE: %@", charac6.value);
        accObject[@"charac6"] = charac6.value;
      }];

 return accObject;
4 Answers

In this case, I suggest to use dispatch_group_t to wait for reading values and use a completion block to handle returned value.

- (void)readValuesWithCompletionHandler:(void(^)(NSDictionary* values))completion {
  NSMutableDictionary *accObject = [[NSMutableDictionary alloc] initWithCapacity: 6];

  // Initialize |charac1|, |charac2|, ...

  dispatch_group_t group = dispatch_group_create();

  dispatch_group_enter(group);
  [charac1 readValueWithCompletionHandler:^(NSError *error) {
    if (error) {
      RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
    }
    RCTLog(@"READ SETPOINT VALUE: %@", charac1.value);
    accObject[@"charac1"] = charac1.value;
    dispatch_group_leave(group);
  }];

  // ... Do same thing with another values

  dispatch_group_enter(group);
  [charac6 readValueWithCompletionHandler:^(NSError *error) {
    if (error) {
      RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
    }
    RCTLog(@"READ SETPOINT VALUE: %@", charac6.value);
    accObject[@"charac6"] = charac6.value;
    dispatch_group_leave(group);
  }];

  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    if (completion) {
      completion(accObject);
    }
  });
}

Usage:

[self readValuesWithCompletionHandler:^(NSDictionary *values) {
  // Do whatever you want with |values| after reading
}];

you can use dispatch_semaphore_wait() with wait dispatch_semaphore_t you can use this process this work for me.

     dispatch_semaphore_t sem = dispatch_semaphore_create(0);


    dispatch_async(dispatch_get_main_queue(), ^{
            NSMutableDictionary *accObject = [[NSMutableDictionary alloc] initWithCapacity: 6];

[charac1 readValueWithCompletionHandler:^(NSError *error) {
        if (error) {
          RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
        }
        RCTLog(@"READ SETPOINT VALUE: %@", charac1.value);
        accObject[@"charac1"] = charac1.value;
      }];
...

[charac6 readValueWithCompletionHandler:^(NSError *error) {
        if (error) {
          RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
        }
        RCTLog(@"READ SETPOINT VALUE: %@", charac6.value);
        accObject[@"charac6"] = charac6.value;
      }];


            dispatch_semaphore_signal(sem);
        });

         dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
 return accObject;

Think I've come to a solution based on this article.

NSMutableDictionary *accObject = [[NSMutableDictionary alloc] initWithCapacity: 6];
dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
[charac1 readValueWithCompletionHandler:^(NSError *error) {
        if (error) {
          RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
        }
        RCTLog(@"READ SETPOINT VALUE: %@", charac1.value);
        accObject[@"charac1"] = charac1.value;
        dispatch_group_leave(group);
      }];
...

dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
        // Now call the final completion block
        return accObject;
    });

You can use dispatch group here, using that you cannot return a value but can call a completion block. This completion block can be there as your functions' parameters.

 NSMutableDictionary *accObject = [[NSMutableDictionary alloc] initWithCapacity: 6];
    dispatch_group_t group = dispatch_group_create();

[charac1 readValueWithCompletionHandler:^(NSError *error) {
        dispatch_group_enter(group);
        if (error) {
          RCTLog(@"ERROR READING HOMEKIT VALUE: %@", error);
        }
        RCTLog(@"READ SETPOINT VALUE: %@", charac1.value);
        accObject[@"charac1"] = charac1.value;
        dispatch_group_leave(group);
      }];
...

dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
        // Now here instead of returning a value, you can call a completion handler.
    });
Related