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?
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
.