Search code examples
iosobjective-casynchronouscameradelegates

How to wait for a delegate function to finish before returning value from main function


I have a custom function for capturing true depth camera information and the function gets returned before the delegate functions have finished processing the captured photo. I need to somehow wait until the delegates have all completed before I return the correct value.

I tried wrapping the main function call into a synchronized block, but that did not solve the problem.

- (NSDictionary *)capture:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
{
  if (@available(iOS 11.1, *)) {
      // Set photosettings to capture depth data
      AVCapturePhotoSettings *photoSettings = [AVCapturePhotoSettings photoSettingsWithFormat:@{AVVideoCodecKey : AVVideoCodecJPEG}];
      photoSettings.depthDataDeliveryEnabled = true;
      photoSettings.depthDataFiltered = false;
      @synchronized(self) {
          [self.photoOutput capturePhotoWithSettings:photoSettings delegate:self];
      }
  }
  // Somehow need to wait here until the delegate functions finish before returning
  return self.res;
}

The delegate function which gets called too late:

- (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error
{
  Cam *camera = [[Cam alloc] init];
  self.res = [camera extractDepthInfo:photo];
}

Currently nil is returned before the delegate gets ever called and only afterwards does the delegate function assign the desired result to self.res


Solution

  • I believe that what you looking for is dispatch_semaphore_t.

    Semaphores allow you to lock a thread until a secondary action is performed. This way, you can postpone the return of the method until the delegate has returned (if you are operating on a secondary thread).

    The problem with such an approach is that you will be locking the thread! So, if you are operating in the main thread, your app will become unresponsive.

    I would recommend you to consider moving the response to a completion block, similar to:

    -(void)capture:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject completion:(void (^)(NSDicitionary* ))completion {
        self.completion = completion
        ...
    }
    

    And call the completion at the end:

    - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error
    {
      Cam *camera = [[Cam alloc] init];
      self.res = [camera extractDepthInfo:photo];
      self.completion(self.res);
    }
    

    === Edit: Swift Code ===

    The code above would be translated to something like:

    var completion: (([AnyHashable: Any]) -> Void)?
    
    func capture(options: [AnyHashable: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock, completion: @escaping ([AnyHashable: Any]) -> Void) {
        self.completion = completion
        ...
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        let cam = Cam()
        let result = cam.extractDepthInfo(photo)
        self.completion?(result)
    }
    
    

    An important note here is that the completion needs to be marked as @escaping in the capture method, given that the object will be copied.