admin管理员组文章数量:1516870
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 -> Boolto 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...
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 -> Boolto 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...
Share Improve this question edited Mar 26 at 0:08 matt asked Mar 25 at 21:08 mattmatt 537k93 gold badges934 silver badges1.2k bronze badges 3 |4 Answers
Reset to default 3You can use AsyncSyncSequence from Swift Async Algorithms, like this:
let result = services.currentPlaylist.list.async.filter {
// ^^^^^^
await services.download.isDownloaded(song: $0)
}
This is similar to your answer, but it also handles task cancellation correctly.
To turn the result back to a regular array, you can use reduce instead of a loop:
extension AsyncSequence {
func toArray() async rethrows -> [Element] {
try await reduce(into: []) { $0.append($1) }
}
}
There is also CollectionConcurrencyKit from John Sundell.
It's simple code which gives nice methods.
For instance, asyncMap() is like that:
func asyncMap<T>(
_ transform: (Element) async throws -> T
) async rethrows -> [T] {
var values = [T]()
for element in self {
try await values.append(transform(element))
}
return values
}
Nothing fancy, nothing complicated, just do the job directly on Sequence.
I heard you were looking for reduce(into:_:):
A possible solution (not tested), but by combining the simple logic of CollectionConcurrencyKit and the source code of the method:
func asyncReduce<Result>(
into initialResult: Result,
_ updateAccumulatingResult: (inout Result, Self.Element) async throws -> ()
) async rethrows -> Result {
var accumulator = initialResult
for element in self {
try await updateAccumulatingResult(&accumulator, element)
}
return accumulator
}
Some people suggest using task groups:
func allDownloaded(in data: [String]) async -> [String] {
await withTaskGroup(of: (String?).self) { group in
for item in data {
group.addTask {
await isDownloaded(item) ? item : nil
}
}
return await group.reduce(into: []) { array, result in
if let result {
array.append(result)
}
}
}
}
func isDownloaded(_ item: String) async -> Bool {
return item.count > 3
}
But I think @matt solution is better since it's reusable
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.
本文标签:
版权声明:本文标题:Swift array `filter`, `map`, etc. (higher-order functions) when the closure needs to be `async` - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.betaflare.com/web/1744168397a2593660.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。


extension Sequence { func asyncReduce<Result>( into initialResult: Result, _ updateAccumulatingResult: (inout Result, Self.Element) async throws -> () ) async rethrows -> Result { var accumulator = initialResult for element in self { try await updateAccumulatingResult(&accumulator, element) } return accumulator } }might work by checking also the source code github/swiftlang/swift/blob/… – Larme Commented Mar 26 at 11:36asyncmethods formapandfilteretc., rather than requiring that we turn an array into an async sequence, do the stuff, and turn the async sequence back into an array. This is what I expected Apple to have done for us by now. I realize that in a sense this is "just a link" but still, if you'd give this as an answer (feel free to expand on it), I'll upvote it. – matt Commented Mar 26 at 16:54