ReactiveSwift:如何订阅SignalProducer?

ABe*_*d89 3 swift reactive-cocoa-5 reactive-swift

我正在尝试学习ReactiveSwift和ReactiveCocoa.我可以使用SignalProperty相当不错,但我遇到的麻烦SignalProducer.

据我所知,它SignalProducer非常适合网络请求.我设置了我的API层来创建并返回一个信号提供者,调用者可以启动它.

class ApiLayer {
    func prepareRequest(withInfo info: RequestInfo) -> SignalProducer<ModelType, ErrorType> {
        return SignalProducer<ModelType, ErrorType> { (observer, lifetime) in

            // Send API Request ...

            // In Completion Handler:
            let result = parseJson(json)
            observer.send(value: result)
            observer.sendCompleted()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但我该如何倾听结果呢?

我尝试过这样的事情,但是我得到了一个错误,所以我一定在做/想到这个错误.

apiLayer.prepareRequest(withInfo: info)
    .startWithValues { (resultModel) in
        // Do Stuff with result ...
}
Run Code Online (Sandbox Code Playgroud)

这是我得到的错误:

对成员'startWithValues'的模糊引用

  1. 找到这个候选人(ReactiveSwift.SignalProducer <Value,NoError>)
  2. 找到这个候选人(ReactiveSwift.SignalProducer <Never,NoError>)

编辑

我试图更明确地帮助编译器识别正确的方法,就像这样.但错误仍然存​​在.

apiLayer.prepareRequest(withInfo: info)
    .startWithValues { (resultModel: ModelType) in // Tried adding type. Error remained.
        // Do Stuff with result ...
}
Run Code Online (Sandbox Code Playgroud)

编辑2

在GitHub支持页面获得帮助并在此处思考提供的答案之后,这就是我最终的结果.

与我之前尝试的一个关键区别是调用者不会手动启动返回的SignalProducer.相反,通过在内部/响应另一个信号创建它,它隐含地在链内启动.

我以前(错误地)认为有必要提取并明确订阅Signal,一个SignalProducer"生产".

相反,我现在认为SignalProducers仅仅是为了响应刺激而启动的延期工作.我可以手动订阅,SignalProvider或者我可以让另一个Signal提供刺激代替.(后者在我下面的更新样本中使用.它看起来相当干净,而且比手动启动它更多FRP-esque,这是我从命令式思维模式中延续下来的.)

enum ErrorType: Error {
    case network
    case parse
}
class ApiLayer {
    func prepareRequest(withInfo info: RequestInfo) -> SignalProducer<ModelType, ErrorType> {
        let producer = SignalProducer<ResultType, NoError> { (observer, lifetime) in

            sendRequest(withInfo: info) { result in
                observer.send(value: result)
                observer.sendCompleted()
            }

        }

        return producer
            .attemptMap { result throws -> ResultType in
                let networkError: Bool = checkResult(result)
                if (networkError) {
                    throw ErrorType.network
                }
            }
            .retry(upTo: 2)
            .attemptMap { result throws -> ModelType in
                // Convert result
                guard let model: ModelType = convertResult(result) else {
                    throw ErrorType.parse
                }
                return model
            }
            // Swift infers AnyError as some kind of error wrapper.
            // I don't fully understand this part yet, but to match the method's type signature, I needed to map it.
            .mapError { $0.error as! ErrorType}
    }
}

// In other class/method
// let apiLayer = ApiLayer(with: ...)
// let infoSignal: Signal<RequestInfo, NoError> = ...
infoSignal
    .flatMap(.latest) { (info) in
        apiLayer.prepareRequest(withInfo: info)
    }
    .flatMapError { error -> SignalProducer<ModelType, NoError> in
        // Handle error
        // As suggested by the ReactiveSwift documentation,
        // return empty SignalProducer to map/remove the error type
        return SignalProducer<ModelType, NoError>.empty
    }
    .observeValues { model in
        // Do stuff with result ...
    }
Run Code Online (Sandbox Code Playgroud)

jjo*_*son 6

ReactiveSwift的理念是,用户忽视错误应该不容易.因此startWithValues仅在生产者的错误类型为时才可用NoError,这确保不会发送任何错误.如果你的制作人可以发送错误,你需要使用一个类似的函数startWithResult来处理它:

apiLayer.prepareRequest(withInfo: info).startWithResult { result in
    switch result {
    case let .success(model):
        // Do stuff with model
    case let .failure(error):
        // Handle error
    }
}
Run Code Online (Sandbox Code Playgroud)