It appears that I can no longer use FileManager's directory enumerator in an async function? This method is on an Actor with a custom executor that uses a serial dispatch queue:
///
/// A custom executor for an Actor that uses a serial dispatch queue, which allows us to adapt the Actor pattern to older APIs that offer
/// a dispatchQueue for running their operations/callbacks.
///
final class SerialDispatchQueueExecutor: SerialExecutor
{
private let queue: DispatchQueue
init(queue: DispatchQueue) {
self.queue = queue
}
@available(macOS 14.0, iOS 17.0, *)
func enqueue(_ job: consuming ExecutorJob)
{
let unownedJob = UnownedJob(job)
let unownedExecutor = asUnownedSerialExecutor()
queue.async {
unownedJob.runSynchronously(on: unownedExecutor)
}
}
}
The method has always worked fine, but I gather there's some edge case with makeIterator in Swift Concurrency. So, is there a new Concurrency-blessed approach that I should be using here?
Unfortunately it looks like the underlying Obj-C NSEnumerator's Sequence conformance has been updated to be explicitly incompatible with Swift concurrency.
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
extension NSEnumerator : Sequence {
/// Return an *iterator* over the *enumerator*.
///
/// - Complexity: O(1).
@available(*, noasync)
public func makeIterator() -> NSFastEnumerationIterator
/// A type representing the sequence's elements.
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.10, *)
public typealias Element = Any
/// A type that provides the sequence's iteration interface and
/// encapsulates its iteration state.
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.10, *)
public typealias Iterator = NSFastEnumerationIterator
}
@available(*, unavailable)
extension NSEnumerator : @unchecked Sendable {
}
noasync seems extremely aggressive. I'm not sure there's a workaround that doesn't require materializing the entire enumerator, which rather defeats the point. Perhaps you can create your own Sequence-conforming type to safely wrap NSEnumerator?
@Jon_Shier Yep. I figure whatever the edge cases are, they don’t apply to me because this whole thing is running on one serial queue—there is no “concurrency” for the enumerator itself. I’m away from my Mac, but I wonder if I can use the assumeIsolated trick from the standard library here to get past the compiler?
It’s very frustrating. This API is now suddenly unavailable in Swift Concurrency, but there is (as far as I can see) no modern replacement that does work with Concurrency. I mean, one of the most natural things to do on a background actor is walk a giant file tree.
No, noasync prevents running the code in any async context. I don't know how smart the diagnostic is and whether wrapping it behind a sync function would do anything, or whether you'd need a whole other wrapper.
Maybe, that's why I said it depends on how smart the diagnostic is. But before C I'd try wrapping the DirectoryEnumerator yourself, as NSEnumerator can be enumerated manually.