Как вы редактируете свойство текущего значения внутри BehaviorRelay? - PullRequest
1 голос
/ 24 января 2020

Я очень новичок в RxSwift и RxCocoa, и недавно я интенсивно использовал Variable из-за того, как удобно просто выводить sh мутации в Variable через ее значение. Теперь, когда это устарело, я пытаюсь понять, как лучше использовать BehaviorRelay. Есть Rx-y способ сделать то, что я хочу сделать, но мне тяжело приземлиться на это.

Что я хочу, это поместить экземпляр модели на основе структуры за ViewModel и наблюдать изменения и привязать элементы пользовательского интерфейса таким образом, что я могу изменить эту модель с помощью BehaviorRelay.

Модель проста:

struct Pizza {
    var name: String
    var price: Float
}

Так же, как и модель представления:

final class PizzaViewModel {
    let pizzaRelay =  BehaviorRelay<Pizza>(value: Pizza(name: "Sausage", price: 5.00))

    init(pizza: Pizza) {
        pizzaRelay.accept(pizza)

        // I feel like I'm missing something Rx-like here... 
    }
}

Тогда где-нибудь вы, возможно, захотите связать UITextField с BehaviorRelay следующим образом:

viewModel
    .pizzaRelay
    .asObservable()
    .map { $0.name }
    .bind(to: nameTextField.rx.text)
    .disposed(by: disposeBag)

Возникает вопрос: если вам нужно извлечь sh значения из текстового поля обратно в BehaviorRelay, как это должно работать?

nameTextField
    .rx
    .controlEvent([.editingChanged])
    .asObservable()
    .subscribe(onNext: { [weak self] in
        guard let self = self else { return }
        if let text = self.nameTextField.text {
            self.viewModel.pizzaRelay.value.name = text  // does not compile because value is a let
        }
    }).disposed(by: disposeBag)

Я, вероятно, не использую правильные типы здесь, или я не думаю в правильной Rx-моде в терминах потоков входов / выходов, но мне любопытно, как другие могут подойти к этой проблеме?

Другие вещи, которые я рассмотрел:

  • Просто восстановите новый Pizza в .subscribe, используя текущее значение в BehaviorRelay, изменив имя и затем .accept, вернув его обратно в реле. Однако это не совсем правильно.
  • Создание отдельных BehaviorRelay для каждого свойства, которое я хочу изменить в моих Pizza, затем .accept -ing-значения для каждого свойства и затем использование combineLatest на всех этих реле и возврат Observable<Pizza>. Но это также кажется неуклюжим.

Как это должно работать в идеальном мире? Я думаю об этом неправильно? Помогите! У меня болит голова.

1 Ответ

1 голос
/ 24 января 2020

В идеальном мире вы бы не использовали реле или даже темы для такого кода. Вместо того, чтобы начинать со структуры, вы должны начать с потока. Как данные должны перемещаться по вашей системе?

В качестве примера приведем контроллер представления с моделью представления, которая может конвертировать градусы Фаренгейта в градусы Цельсия и обратно:

struct TempInOut {
    let fahrenheit: Observable<String>
    let celsius: Observable<String>
}

func tempViewModel(input: TempInOut) -> TempInOut {
    let celsius = input.fahrenheit
        .compactMap { Double($0) }
        .map { ($0 - 32) * 5.0/9.0 }
        .map { "\($0)" }

    let fahrenheit = input.celsius
        .compactMap { Double($0) }
        .map { $0 * 5.0/9.0 + 32 }
        .map { "\($0)" }

    return TempInOut(fahrenheit: fahrenheit, celsius: celsius)
}

Главное, что нужно понять, это то, как данные передаются из input.fahrenheit. в output.celsius и как он переходит из input.celsius в output.fahrenheit.

Это другой способ восприятия вашей программы ... Недавно я слышал о понятии "временное проектирование" и думаю, это хороший термин для искусства.

Вот контроллер представления, который будет использовать вышеупомянутую модель представления.

class ViewController: UIViewController {

    @IBOutlet weak var fahrenheitField: UITextField!
    @IBOutlet weak var celsiusField: UITextField!

    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        let input = TempInOut(
            fahrenheit: fahrenheitField.rx.text.orEmpty.asObservable(),
            celsius: celsiusField.rx.text.orEmpty.asObservable()
        )

        let output = tempViewModel(input: input)

        disposeBag.insert(
            output.fahrenheit.bind(to: fahrenheitField.rx.text),
            output.celsius.bind(to: celsiusField.rx.text)
        )
    }
}
...