Как сделать базовые привязки в ReactiveCocoa 3 и 4

Я недавно читал ReactiveCocoa v3, и я изо всех сил пытаюсь создать базовые вещи. Я уже прочитал журнал изменений, тесты, несколько вопросов SO и статьи Колина Эберхардта по этому вопросу. Тем не менее, я все еще не вижу примеров базовых привязок.

Скажем, у меня есть приложение, в котором представлено меню дня. Приложение использует RAC3 и шаблон MVVM.

Модель (меню)

У модели есть один простой метод для извлечения сегодняшнего меню. На данный момент это не делает каких-либо сетевых запросов, это в основном просто создает объект модели. Свойством mainCourse является String.

class func fetchTodaysMenu() -> SignalProducer<Menu, NoError> {
    return SignalProducer {
        sink, dispoable in
            let newMenu = Menu()
            newMenu.mainCourse = "Some meat"
            sendNext(sink, newMenu)
            sendCompleted(sink)
    }
}

ViewModel (MenuViewModel)

Модель просмотра предоставляет различные переменные String, позволяющие диспетчеру просмотра отображать меню. Позвольте просто добавить одно свойство для показа основного курса.

var mainCourse = MutableProperty("")

И добавим привязку для этого свойства:

self.mainCourse <~ Menu.fetchTodaysMenu()
    |> map { menu in
        return menu.mainCourse!
    }

ViewController (MenuViewController)

И последнее, но не менее важное: я хочу представить это основное блюдо в представлении. Я добавлю для этого UILabel.

var headline = UILabel()

И, наконец, я хочу установить свойство text этого UILabel, наблюдая мою модель представления. Что-то вроде:

self.headline.text <~ viewModel.headline.producer

К сожалению, это не работает.

Вопросы

  • Метод fetchTodaysMenu() возвращает SignalProducer<Menu, NoError>, но что, если я хочу, чтобы этот метод возвращал SignalProducer<Menu, NSError>? Это сделало бы мою привязку в моей модели представления неудачной, поскольку метод теперь может вернуть ошибку. Как я могу справиться с этим?
  • Как уже упоминалось, текущая привязка в моем контроллере просмотра не работает. Я играл с созданием MutableProperty, который представляет свойство text для UILabel, но я так и не понял. Я также считаю, что неудобно или многословно создавать дополнительные переменные для каждого свойства, которое я хочу связать. Это не было необходимо в RAC2. Я намеренно также пытался избежать использования DynamicProperty, но, может быть, я не должен? Я просто хочу найти правильный способ сделать RAC(self.headline, text) = RACObserve(self.viewModel, mainCourse);.

Любые другие отзывы/рекомендации о том, как сделать эту базовую установку, высоко оценены.

Ответ 1

Итак, после написания этого вопроса, Колин Эберхардт сделал часть 3 своей серии блога RAC3, которая содержит интересный и очень важный пример использования MVVM и RAC3. Сообщение можно найти здесь и исходный код здесь.

Основываясь на его работе, мне удалось ответить на мои вопросы:

  • Используя несколько иной подход, я могу сделать fetchTodaysMenu() возвратом SignalProducer<Menu, NSError> по мере необходимости. Вот как я тогда мог бы сделать в моей модели:

    MenuService.fetchTodaysMenu()
        |> observeOn(QueueScheduler.mainQueueScheduler)
        |> start(next: { response in
            self.mainCourse.put(response.mainCourse!)
        }, error: {
            println("Error \($0)")
        })
    
  • Кажется, что нет привязок UIKit от RAC3 beta 4. Колин сделал несколько расширений UIKit, чтобы помочь ему выполнить эти привязки, которые я искал. Здесь можно найти здесь. Добавив их в свой проект, я смог сделать то, что хотел:

    self.mainCourse.rac_text <~ self.viewModel.mainCourse
    

Обновление от 25 мая 2015 г.

После многократного использования ReactiveCocoa 3 я хотел бы еще раз ответить 1). Используя catch, можно сделать это более декларативным образом. Я закончил реализацию небольшой вспомогательной функции для этого:

public func ignoreError<T: Any, E: ErrorType>(signalProducer: SignalProducer<T, E>) -> SignalProducer<T, NoError> {
    return signalProducer
        |> catch { _ in
            SignalProducer<T, NoError>.empty
        }
}

Функция преобразует любые NSError в NoError, что позволяет связывать, как я хотел, делая MenuService.fetchTodaysMenu() |> ignoreError.

Я открываю свой проект, так как это может быть хорошей отправной точкой для других, которые смотрят на ReactiveCocoa 3.0: https://github.com/s0mmer/TodaysReactiveMenu

Обновление 5 марта 2016 года

Как указано в комментариях, начиная с Swift 2, функция ignoreError теперь выглядит следующим образом:

public func ignoreError() -> SignalProducer<Value, NoError> {
    return flatMapError { _ in
        SignalProducer<Value, NoError>.empty
    }
}

Кроме того, была добавлена ​​библиотека расширений, называемая Rex, где было добавлено нечто подобное.