如何使用 ReactiveSwift 和 Firebase 异步方法调用处理 SignalProducer?
How to handle SignalProducer with ReactiveSwift and Firebase asynchronous method calls?
我正在开发一个 iOS 应用程序 Swift 3 使用 ReactiveSwift 1.1.1,MVVM + Flow Coordinator 模式和 Firebase 作为后端。我最近才开始适应 FRP,我仍在尝试弄清楚如何将新功能集成到我现有的代码库中。
例如,我的模型使用 Firebase 的异步方法从网络下载缩略图,我想提供一个 SignalProducer<Content, NoError>
从我的 ViewModel 类 订阅并观察缩略图是否已被下载下载,然后更新 UI.
// field to be used from the view-models to observe
public let thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
// TODO: send next content via completion below
}
// thumbnail download method
public func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
debugPring("Error id")
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
debugPrint("Error download")
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// TODO: emit signal with content
// How to send the content via the SignalProducer above?
})
}
我也尝试过与 Signal<Content, NoError>
类似的方法,而我使用 Signal<Content, NoError>.pipe()
方法接收 (observer, disposable)
元组,并将观察者保存为私有全局字段以访问它形成 Firebase 回调。
问题:
这是正确的方法还是我遗漏了什么?
如何在完成时发出内容对象?
更新:
经过几个小时的痛苦,我发现了如何设计 SingalProducer 以发出信号并从 ViewModels 订阅。
也许以下代码片段对其他人也有帮助:
// model protocol
import ReactiveSwift
import enum Result.NoError
public protocol ContentService {
func findThumbnail(bucketId: String, contentId: String)
var thumbnailContentProducer: SignalProducer<Content, NoError> { get }
}
// model implementation using firebase
import Firebase
import FirebaseStorage
import ReactiveSwift
public class FirebaseContentService: ContentService {
// other fields, etc.
// ...
private var thumbnailContentObserver: Observer<Content, NoError>?
private var thumbnailContentSignalProducer: SignalProducer<Content, NoError>?
var thumbnailContentProducer: SignalProducer<Content, NoError> {
return thumbnailContentSignalProducer!
}
init() {
thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
self.thumbnailContentObserver = observer
}
}
func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
// TODO handle error
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
// TODO handle error
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// emit signal
self.thumbnailContentObserver?.send(value: content)
})
}
}
// usage from a ViewModel
contentService.thumbnailContentProducer
.startWithValues { content in
self.contents.append(content)
}
也许有人可以验证上面的代码并说这是正确的做法。
当您考虑将 Signal
与 pipe
结合使用时,我认为您走在了正确的道路上。关键是您需要为每个缩略图请求创建一个新的 SignalProducer
,并且您需要一种方法将所有这些请求组合成一个结果信号。我在想这样的事情(注意这是未经测试的代码,但它应该能理解这个想法):
class FirebaseContentService {
// userService and storageThumbnail defined here
}
extension FirebaseContentService: ReactiveExtensionsProvider { }
extension Reactive where Base: FirebaseContentService {
private func getThumbnailContentSignalProducer(bucketId: String, contentId: String) -> SignalProducer<Content, ContentError> {
return SignalProducer<Content, ContentError> { (observer, disposable) in
guard let userId = self.base.userService.getCurrentUserId() else {
observer.send(error: ContentError.invalidUserLogin)
return
}
let ref = self.base.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
observer.send(error: ContentError.contentNotFound)
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
observer.send(value: content)
observer.sendCompleted()
})
}
}
}
class ThumbnailProvider {
public let thumbnailSignal: Signal<Content, NoError>
private let input: Observer<(bucketId: String, contentId: String), NoError>
init(contentService: FirebaseContentService) {
let (signal, observer) = Signal<(bucketId: String, contentId: String), NoError>.pipe()
self.input = observer
self.thumbnailSignal = signal
.flatMap(.merge) { param in
return contentService.reactive.getThumbnailContentSignalProducer(bucketId: param.bucketId, contentId: param.contentId)
.flatMapError { error in
debugPrint("Error download")
return SignalProducer.empty
}
}
}
public func findThumbnail(bucketId: String, contentId: String) {
input.send(value: (bucketId: bucketId, contentId: contentId))
}
}
像这样使用 ReactiveExtensionsProvider
是通过 reactive
属性.
将响应式 API 添加到现有功能的惯用方式
实际的请求代码仅限于 getThumbnailContentSignalProducer
,它为每个请求创建一个 SignalProducer。请注意,错误在这里传递,处理和转换为 NoError
稍后发生。
findThumbnails
只需要一个 bucketId
和 contentId
并通过输入 observable 发送它。
init
中 thumbnailSignal
的构造是魔法发生的地方。每个输入是一个包含 bucketId
和 contentId
的元组,通过 flatMap
转换为请求。请注意,.merge
策略意味着无论请求完成的顺序如何,都会尽快发送缩略图。如果您想确保缩略图按请求的相同顺序返回,您可以使用 .concat
。
flatMapError
是处理潜在错误的地方。在这种情况下,它只是打印 "Error download" 并且什么都不做。
我正在开发一个 iOS 应用程序 Swift 3 使用 ReactiveSwift 1.1.1,MVVM + Flow Coordinator 模式和 Firebase 作为后端。我最近才开始适应 FRP,我仍在尝试弄清楚如何将新功能集成到我现有的代码库中。
例如,我的模型使用 Firebase 的异步方法从网络下载缩略图,我想提供一个 SignalProducer<Content, NoError>
从我的 ViewModel 类 订阅并观察缩略图是否已被下载下载,然后更新 UI.
// field to be used from the view-models to observe
public let thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
// TODO: send next content via completion below
}
// thumbnail download method
public func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
debugPring("Error id")
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
debugPrint("Error download")
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// TODO: emit signal with content
// How to send the content via the SignalProducer above?
})
}
我也尝试过与 Signal<Content, NoError>
类似的方法,而我使用 Signal<Content, NoError>.pipe()
方法接收 (observer, disposable)
元组,并将观察者保存为私有全局字段以访问它形成 Firebase 回调。
问题:
这是正确的方法还是我遗漏了什么?
如何在完成时发出内容对象?
更新:
经过几个小时的痛苦,我发现了如何设计 SingalProducer 以发出信号并从 ViewModels 订阅。
也许以下代码片段对其他人也有帮助:
// model protocol
import ReactiveSwift
import enum Result.NoError
public protocol ContentService {
func findThumbnail(bucketId: String, contentId: String)
var thumbnailContentProducer: SignalProducer<Content, NoError> { get }
}
// model implementation using firebase
import Firebase
import FirebaseStorage
import ReactiveSwift
public class FirebaseContentService: ContentService {
// other fields, etc.
// ...
private var thumbnailContentObserver: Observer<Content, NoError>?
private var thumbnailContentSignalProducer: SignalProducer<Content, NoError>?
var thumbnailContentProducer: SignalProducer<Content, NoError> {
return thumbnailContentSignalProducer!
}
init() {
thumbnailContentSignalProducer = SignalProducer<Content, NoError> { (observer, disposable) in
self.thumbnailContentObserver = observer
}
}
func findThumbnail(bucketId: String, contentId: String) {
guard let userId = userService.getCurrentUserId() else {
// TODO handle error
return
}
let ref = self.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
// TODO handle error
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
// emit signal
self.thumbnailContentObserver?.send(value: content)
})
}
}
// usage from a ViewModel
contentService.thumbnailContentProducer
.startWithValues { content in
self.contents.append(content)
}
也许有人可以验证上面的代码并说这是正确的做法。
当您考虑将 Signal
与 pipe
结合使用时,我认为您走在了正确的道路上。关键是您需要为每个缩略图请求创建一个新的 SignalProducer
,并且您需要一种方法将所有这些请求组合成一个结果信号。我在想这样的事情(注意这是未经测试的代码,但它应该能理解这个想法):
class FirebaseContentService {
// userService and storageThumbnail defined here
}
extension FirebaseContentService: ReactiveExtensionsProvider { }
extension Reactive where Base: FirebaseContentService {
private func getThumbnailContentSignalProducer(bucketId: String, contentId: String) -> SignalProducer<Content, ContentError> {
return SignalProducer<Content, ContentError> { (observer, disposable) in
guard let userId = self.base.userService.getCurrentUserId() else {
observer.send(error: ContentError.invalidUserLogin)
return
}
let ref = self.base.storageThumbnail.reference()
let contentRef = ref
.child(userId)
.child(bucketId)
.child(FirebaseConstants.pathImages)
.child("\(contentId).jpg")
contentRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
guard let data = data else {
observer.send(error: ContentError.contentNotFound)
return
}
let content = Image(data: data)
content.id = contentId
content.userId = userId
content.bucketId = bucketId
observer.send(value: content)
observer.sendCompleted()
})
}
}
}
class ThumbnailProvider {
public let thumbnailSignal: Signal<Content, NoError>
private let input: Observer<(bucketId: String, contentId: String), NoError>
init(contentService: FirebaseContentService) {
let (signal, observer) = Signal<(bucketId: String, contentId: String), NoError>.pipe()
self.input = observer
self.thumbnailSignal = signal
.flatMap(.merge) { param in
return contentService.reactive.getThumbnailContentSignalProducer(bucketId: param.bucketId, contentId: param.contentId)
.flatMapError { error in
debugPrint("Error download")
return SignalProducer.empty
}
}
}
public func findThumbnail(bucketId: String, contentId: String) {
input.send(value: (bucketId: bucketId, contentId: contentId))
}
}
像这样使用 ReactiveExtensionsProvider
是通过 reactive
属性.
实际的请求代码仅限于 getThumbnailContentSignalProducer
,它为每个请求创建一个 SignalProducer。请注意,错误在这里传递,处理和转换为 NoError
稍后发生。
findThumbnails
只需要一个 bucketId
和 contentId
并通过输入 observable 发送它。
init
中 thumbnailSignal
的构造是魔法发生的地方。每个输入是一个包含 bucketId
和 contentId
的元组,通过 flatMap
转换为请求。请注意,.merge
策略意味着无论请求完成的顺序如何,都会尽快发送缩略图。如果您想确保缩略图按请求的相同顺序返回,您可以使用 .concat
。
flatMapError
是处理潜在错误的地方。在这种情况下,它只是打印 "Error download" 并且什么都不做。