我有一个 A 对象数组。我想将其转换为包含 B 类型对象的数组。但棘手的部分是同时下载图像并使用 RxSwift 或 ReactiveSwift 完成所有操作。你有什么建议我该怎么做吗?
struct A {
let name: String
let imageURL: URL
let thumbnailURL: URL
}
struct B {
let name: String
let image: UIImage?
let thumbnail: UIImage?
}
因此,尽管我对上下文的评论可能很重要,但以下是我如何使用 ReactiveSwift 异步将[A]
转换为[B]
。请注意,我没有机会测试此代码,但它应该涵盖基本思想:
// This function takes an `NSURL` and creates an asynchronous `SignalProducer` to
// download an image from that URL or yields `nil` if there is an error.
func downloadImage(url: URL) -> SignalProducer<UIImage?, NoError> {
let request = URLRequest(url: url)
return URLSession.shared.reactive.data(with: request)
.map { (data, response) -> UIImage? in UIImage(data: data) }
.flatMapError { _ in SignalProducer<UIImage?, NoError>(value: nil) }
}
func convertAToB(_ a: A) -> SignalProducer<B, NoError> {
let imgDownload = downloadImage(url: a.imageURL)
let thumbDownload = downloadImage(url: a.thumbnailURL)
return SignalProducer<UIImage?, NoError>.combineLatest(imgDownload, thumbDownload)
.map { images in
return B(name: a.name, image: images.0, thumbnail: images.1)
}
}
func convertAllAsToBs(_ inputs: [A]) -> SignalProducer<[B], NoError> {
return SignalProducer<A, NoError>(values: inputs)
.flatMap(.concat, convertAToB)
.collect()
}
let inputs: [A] = ...
convertAllAsToBs(inputs).startWithValues { outputs in
// `outputs` is [B]
}
编辑:
为了使这个答案与@PhilippeC的 RxSwift 答案相提并论,以下是每个 ReactiveSwift 运算符正在做的事情的摘要:
SignalProducer.init(values:)
相当于 RxSwift 的Observable.from
。它创建一个生产者,将序列的每个值作为单独的事件发送。collect
相当于RxSwift的toArray
。它从源生产者收集每个值,并在源生产者完成后将它们发送到单个数组中。flatMap
为每个传入的A
启动convertAToB
生产者,并根据指定的FlattenStrategy
合并结果。在这种情况下,我使用了.concat
,这使得它等效于 RxSwift 的concatMap
连接每个结果并保留@PhilippeC在他的答案中描述的顺序。
使用 RxSwift,这可以使用几个运算符来完成:
- 将数组转换为事件序列的
Observable.from
和toArray()
运算符,以及来回。 - concatMap 运算符,它转换每个单独的
A
项,然后将每个单独的结果连接成一个可观察的序列,并遵循原始排序。
下面是一个 Swift 代码:
// choose the implementation you prefer for this function
// N.B. : if using RxCocoa, prefer Single<UIImage?> as return type
func downloadImage(url: URL) -> Observable<UIImage?> {
return URLSession.shared.rx
.data(URL)
.map { data in UIImage(data: data) }
.catchErrorJustReturn(nil)
}
let arrayOfA: [A] = []; // your input array goes here.
let arrayOfB: Observable<[B]> =
Observable
// Convert each array element to an item
.from(arrayOfA)
// concatMap preserves the order
.concatMap { a in
Observable.zip(downloadImage(a.imageURL), downloadImage(a.thumbnailURL))
.map { image, thumbnail in
B(name: a.name, image: image, thumbnail: thumbnail)
}
}
.toArray()
// do some stuff with the result: arrayOfB
最后,将生成的数组作为单个事件所需的Observable<[B]>
。若要使用它,只需订阅此可观察量,或将其直接绑定到您的 UI 或数据源。
注意:如果是长数组,我还建议在后台队列上运行下载的:多亏了 Rx,只需在.toArray()
之后添加类似.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
的东西即可轻松完成此操作。