Now that I'm living completely in a Swift 6 async/await
world, I've suddenly hit a snag. When writing code of this sort (never mind what it does, just look at the form of the thing):
let result = services.currentPlaylist.list.filter {
services.download.isDownloaded(song: $0)
}
I'm brought up short by the compiler, which says:
Call to actor-isolated instance method
isDownloaded(song:)
in a synchronous main actor-isolated context
Well, the compiler is right; services.download
is, in fact, an actor. So now what? I can't say await
here:
let result = services.currentPlaylist.list.filter {
await services.download.isDownloaded(song: $0)
}
That just nets me a different error:
Cannot pass function of type
(SubsonicSong) async -> Bool
to parameter expecting synchronous function type
What am I supposed to do here? I can't find an async/await
version of filter
, except on an AsyncSequence. But services.currentPlaylist.list
is not an AsyncSequence; it's an array. And even worse, I cannot find any easy way convert an array to an AsyncSequence, or an AsyncSequence to an array.
Of course I could just solve this by dropping the use of filter
altogether and doing this the "stupid" way, i.e. by looping through the original array with for
. At one point I had this:
var songs = services.currentPlaylist.list
for index in songs.indices.reversed() {
if await services.download.isDownloaded(song: songs[index]) {
songs.remove(at: index)
}
}
But that's so ugly...
I never found any built-in solution, so I ended up writing my own conversions:
struct SimpleAsyncSequence<T: Sendable>: AsyncSequence, AsyncIteratorProtocol, Sendable {
private var sequenceIterator: IndexingIterator<[T]>
init(array: [T]) {
self.sequenceIterator = array.makeIterator()
}
mutating func next() async -> T? {
sequenceIterator.next()
}
func makeAsyncIterator() -> SimpleAsyncSequence { self }
}
extension AsyncSequence where Element: Sendable {
func array() async throws -> [Element] {
var result = [Element]()
for try await item in self {
result.append(item)
}
return result
}
}
Or, for that extension, I could write it like this (basically as suggested here):
extension AsyncSequence where Element: Sendable {
func array() async throws -> [Element] {
try await reduce(into: []) { $0.append($1) }
}
}
Now my code can talk like this:
let sequence = SimpleAsyncSequence(array: services.currentPlaylist.list).filter {
await services.download.isDownloaded(song: $0)
}
let result = try await sequence.array()
But I really don't know whether this is the best approach, and I remain surprised that the Swift library doesn't make this a whole lot simpler somehow (and would be happy to hear that it does).
Update The other answers confirmed that, incredibly, no solution is built-in to the library as currently shipping, so I ended up keeping my approach. It's great to know that other solutions are out there, but I don't want my app to use any third-party dependencies.