0

我正在使用 ReactiveSwift 1.1.1、MVVM + Flow Coordinator 模式和 Firebase 作为后端开发一个带有 Swift 3 的 iOS 应用程序。我最近才开始适应 FRP,我仍在试图弄清楚如何将新功能集成到我现有的代码库中。

例如,我的模型使用 Firebase 的异步方法从 Web 下载缩略图,我想提供一个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 来发出信号并从 ViewModel 订阅。

也许以下代码片段对其他人也有帮助:

// 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)
    }

也许有人可以验证上面的代码并说这是正确的方法。

4

1 回答 1

1

我认为当您考虑使用Signalwith时,您走在正确的道路上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将反应式 API 添加到现有功能的惯用方式。reactive

实际的请求代码仅限于getThumbnailContentSignalProducer为每个请求创建一个 SignalProducer。请注意,错误会在此处传递,处理和转换将在NoError稍后进行。

findThumbnails只需要一个bucketIdandcontentId并通过输入 observable 发送它。

thumbnailSignalin的构造init是魔法发生的地方。每个输入,它是一个包含 abucketId和的元组contentId,通过 转换为一个请求flatMap。请注意,该.merge策略意味着缩略图会以请求完成的任何顺序尽快发送。.concat如果您想确保以与请求相同的顺序返回缩略图,则可以使用。

flatMapError是处理潜在错误的地方。在这种情况下,它只是打印“错误下载”而不做其他任何事情。

于 2017-05-17T19:24:22.833 回答