Convert AnySchedulerOf<DispatchQueue> to DispatchQueue?

Hi, I'm working with Apple's Network Framework. In this framework, you have the NWBrowser and the NWListener, which are the client and server respectively, and which are connected through an NWConnection. All three of these classes have a start(on:) method, similar to URLSession.shared.dataTask's resume() method. However, the start(on:) method takes a DispatchQueue on which to deliver all events.

Because it's a DispatchQueue and not a Scheduler, I won't be able to use an AnyScheduler in my environment, meaning that I can't exhaustively control / test this dependency. Is there a way to maybe convert an AnySchedulerOf<DispatchQueue> into a normal DispatchQueue which I can feed into this start(on:) method?

There's no way, in this case at least, to turn a type-erased 'AnyX' type back into the unerased type; it's hidden in a closure that you can't get into (without some pretty dark low-level magic, if at all).

I think perhaps a better way to handle this would be to wrap the functionality you need in a different 'client' entity (eg a NetworkClient struct), which you could then write 'live' and various 'mock' versions of. The live version could take a real live DispatchQueue, and any mock versions you write wouldn't even need to touch the underlying framework that requires that DispatchQueue. Doing that will avoid the problem altogether.

Right, that's what I did, and in the interface's startBrowsing/startListening/startConnection closure I would take a DispatchQueue as a parameter:

public var startConnection: (_ id: AnyHashable, _ queue: DispatchQueue) -> Effect<Event, Never>

And in the live implementation:

startConnection: { id, queue in
    .run { subscriber in
        connectionDependencies[id]?.start(queue: queue)
        // ...
    }
}

(Here's the full live implementation of my "P2PClient".)

So it seems to me like you're saying that I shouldn't even try factoring out the DispatchQueue used by the connection into a closure parameter, and that it should stay as an implementation detail of the live implementation.

So it seems to me like you're saying that I shouldn't even try factoring out the DispatchQueue used by the connection into a closure parameter, and that it should stay as an implementation detail of the live implementation.

Yep, more or less! You could make the live version look maybe something like this:

public extension ConnectionClient {
  static func live(queue: DispatchQueue) -> Self {
    // ...
  }
}

And then interface wouldn't need even know about the existence of DispatchQueues. I find this works pretty well since you already can't really control how that queue is going to be used behind the scenes by the other framework, so you're allowing yourself greater freedom by making it an implementation detail of your wrapper client; you can use the live version that takes a DispatchQueue, or you can do something completely different and not even need to worry about queue/scheduler stuff at all, potentially.

2 Likes