ReactiveSwift: как подписаться на SignalProducer?

Я пытаюсь узнать ReactiveSwift и ReactiveCocoa. я могу использовать Signal а также Property довольно хорошо, но у меня проблемы с 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()
        }
    }
}

Но как мне ожидать результатов?

Я пробовал что-то вроде этого, но я получаю ошибку, поэтому я должен делать / думать об этом неправильно.

apiLayer.prepareRequest(withInfo: info)
    .startWithValues { (resultModel) in
        // Do Stuff with result ...
}

Вот ошибка, которую я получаю:

Неоднозначная ссылка на член 'startWithValues'

  1. Найден этот кандидат (ReactiveSwift.SignalProducer)
  2. Найден этот кандидат (ReactiveSwift.SignalProducer)

РЕДАКТИРОВАТЬ

Я пытался быть более явным, чтобы помочь компилятору определить правильный метод, вот так. Но ошибка все еще остается.

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

РЕДАКТИРОВАТЬ 2

После получения справки на странице поддержки GitHub и обдумывания предоставленного здесь ответа, вот что я закончил.

Одно из ключевых отличий от моих предыдущих попыток заключается в том, что вызывающий не запускает вручную SignalProducer, Скорее, создавая его внутри / в ответ на другой сигнал, он неявно запускается внутри цепочки.

Ранее я (неправильно) предполагал, что необходимо извлечь и явно подписаться на Signal это SignalProducer "Производство".

Вместо этого я сейчас думаю о SignalProducerЭто просто отложенная работа, которая запускается в ответ на стимулы. Я могу вручную подписаться на SignalProvider или я могу позволить другому Signal вместо этого предоставьте этот стимул. (Последний использовался в моем обновленном образце ниже. Кажется, он довольно чистый и намного более FRP-похожий, чем ручной запуск, который я перенес из своего императивного мышления.)

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 ...
    }

2 ответа

Решение

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

Игнорировать ошибки не очень хорошая идея, но в некоторых случаях их можно рассматривать как nil-значения с таким расширением:

public extension SignalProducer {
    func skipErrors() -> SignalProducer<Value?, NoError> {
        return self
            .flatMap(.latest, { SignalProducer<Value?, NoError>(value: $0) })
            .flatMapError { _ in SignalProducer<Value?, NoError>(value: nil) }
    }
}
Другие вопросы по тегам