Skip to content

Commit 32227f2

Browse files
committed
Fix Swift 6.1 strict concurrency errors in timer/callback bridging
Task closures that captured self from non-isolated callbacks (Timer.scheduledTimer, VTCompressionSession output, SCStream output) were failing on Swift 6.1 with "reference to captured var 'self' in concurrently-executing code". Explicitly recapture with [weak self] at the Task boundary. FrameOutputStream also needed an UncheckedSendableBox wrapper to pass CMSampleBuffer (non-Sendable) from the compression callback into a MainActor Task without a sending warning. Verified with swift build locally; Swift 6.3 was lenient about the original patterns but Swift 6.1 on the CI runner was not.
1 parent dd16a30 commit 32227f2

3 files changed

Lines changed: 13 additions & 6 deletions

File tree

Sources/VirtualDisplayKit/Core/DisplayRecorder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public final class DisplayRecorder: ObservableObject {
288288

289289
// Start duration timer
290290
durationTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
291-
Task { @MainActor in
291+
Task { @MainActor [weak self] in
292292
self?.updateDuration()
293293
}
294294
}

Sources/VirtualDisplayKit/Core/DisplayStreamRenderer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public final class DisplayStreamRenderer: NSView {
205205

206206
// Create output handler
207207
let output = SCStreamOutputHandler { [weak self] surface, pixelBuffer in
208-
Task { @MainActor in
208+
Task { @MainActor [weak self] in
209209
self?.handleFrame(surface: surface, pixelBuffer: pixelBuffer)
210210
}
211211
}

Sources/VirtualDisplayKit/Core/FrameOutputStream.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ import CoreMedia
1212
import CoreVideo
1313
import VideoToolbox
1414

15+
// Wrapper to cross Sendable boundaries for Core Media types that are not
16+
// themselves Sendable but are safe to hand off to MainActor work.
17+
private struct UncheckedSendableBox<Value>: @unchecked Sendable {
18+
let value: Value
19+
init(_ value: Value) { self.value = value }
20+
}
21+
1522
/// Delegate for receiving streaming frames
1623
@MainActor
1724
public protocol FrameOutputStreamDelegate: AnyObject {
@@ -365,10 +372,10 @@ public final class FrameOutputStream: ObservableObject {
365372
frameProperties: nil,
366373
infoFlagsOut: &flags
367374
) { [weak self] status, infoFlags, sampleBuffer in
368-
guard let self = self, status == noErr, let buffer = sampleBuffer else { return }
369-
370-
Task { @MainActor in
371-
self.handleEncodedFrame(buffer)
375+
guard status == noErr, let buffer = sampleBuffer else { return }
376+
let sendableBuffer = UncheckedSendableBox(buffer)
377+
Task { @MainActor [weak self] in
378+
self?.handleEncodedFrame(sendableBuffer.value)
372379
}
373380
}
374381

0 commit comments

Comments
 (0)