0

I'm plaing with concurrency features of Swift. I've created helper function which returns AsyncStream with values published by NSOBject implementations. Sort of code below.

func asyncStreamFor<Root: NSObject, Value> (_ root: Root, keyPath: KeyPath<Root, Value>) -> AsyncStream<Value> {
    
    AsyncStream { continuation in
        let cancellable = root.publisher(for: keyPath, options: [.initial, .new])
            .sink {
                continuation.yield($0)
            }
        
        continuation.onTermination = { @Sendable _ in
            cancellable.cancel()
        }
    }
}

I'm trying to use it (and previously used it directly using publisher) for such purposes as AVPlayer properties (rate, status) observation. Usage scheme below:

class Player {
    let avPlayer = AVPlayer()
    var cancellable = Set<AnyCancellable>()
    init() {
        avPlayer.publisher(for: \.status)
            .sink {
                print($0)
            }.store(in: &cancellable)
    }
}

Release of Player's instance works properly (no reference cycle problem).

For AsyncStream, I've tried to make it simple and used scheme as below:

class Player {
    let avPlayer = AVPlayer()
    init() {
        Task {
            for await status in asyncStreamFor(avPlayer, keyPath: \.status) {
                print(status)
            }
        }
    }
}

Here, it seems like there's a reference cycle: AsyncStream instance hold reference to cancellable (in onTermination clousure), which in turn hold reference to avPlayer. Player instance is not deinitialised when dropping last reference.

Any idea how to solve the problem without explicitly cancelling Task before dropping reference to Player?

jactmp
  • 36
  • 3

0 Answers0