iosswiftswift-concurrencydispatch-queueswift6

Swift Actor and GCD Dispatch Queue Executor


This is with reference to AVCam sample code by Apple:

   actor CaptureService {

          // The app's capture session.
         private let captureSession = AVCaptureSession()

        // A serial dispatch queue to use for capture control actions.
         private let sessionQueue = DispatchSerialQueue(label: "com.example.apple-samplecode.AVCam.sessionQueue")

       // Sets the session queue as the actor's executor.
        nonisolated var unownedExecutor: UnownedSerialExecutor {
            sessionQueue.asUnownedSerialExecutor()
        }
     ...
  }

And then later in the Actor code:

    // Set the controls delegate.
    captureSession.setControlsDelegate(controlsDelegate, queue: sessionQueue)

If I understand it correctly, the effect of this code is to make all isolated method calls and mutable state modification to effectively happen to sessionQueue by implicitly calling sessionQueue.sync(). Is my understanding correct or there is more to it?


Solution

  • This defines an actor that is using a custom executor, namely a GCD serial queue:

    actor CaptureService {
        // A serial dispatch queue to use for capture control actions.
        private let sessionQueue = DispatchSerialQueue(label: "com.example.apple-samplecode.AVCam.sessionQueue")
    
        // Sets the session queue as the actor's executor.
        nonisolated var unownedExecutor: UnownedSerialExecutor {
            sessionQueue.asUnownedSerialExecutor()
        }
        …
    } 
    

    But as you note, this CaptureService configures a CaptureControlsDelegate, a AVCaptureSessionControlsDelegate, to also use that same queue:

    captureSession.setControlsDelegate(controlsDelegate, queue: sessionQueue) 
    

    So the delegate methods of CaptureControlsDelegate are called on sessionQueue, and when these are published to CaptureSession, it can be observed by the actor, free of any data races:

    /// Observe when capture control enter and exit a fullscreen appearance.
    private func observeCaptureControlsState() {
        controlsDelegate.$isShowingFullscreenControls
            .assign(to: &$isShowingFullscreenControls)
    }
    

    So, this is not performing a GCD sync call. It is just marrying a preconcurrency, queue-based API with an actor.