Как использовать BehaviorRelay в качестве альтернативы переменной в RxSwift?

По RxSwift4, Variable перемещается в Deprecated.swift маркировки возможного Deprecation из Variable в будущем. Альтернативой, предлагаемой Variable является BehaviorRelay. Во время публикации этого вопроса, поскольку я не мог найти большую часть учебника по сети, используя BehaviorRelay я размещаю такой фундаментальный вопрос здесь, в SO.

Предположим, что у меня есть вызов webService, и я получаю кусок данных, который является JSONArray, при разборе JSON-объекта один за другим. Я обновляю свойство Variable value

Вот мое объявление переменной

var myFilter = Variable<[MyFilterModel]>([MyFilterModel(data: "{:}")])

при получении нового элемента каждый раз, когда я обновляю переменную как

myFilter.value.append(newModel)

Поскольку переменная была привязана к CollectionView, collectionVie немедленно обновлял свой интерфейс с недавно добавленным объектом.

Проблема с BehaviorRelay

Теперь моя декларация выглядит

var myFilter = BehaviorRelay<[MyFilterModel]>(value: [MyFilterModel(data: "{:}")])

Но самая большая проблема заключается в том, что myFilter.value является readOnly. Так очевидно

myFilter.value.append(newModel) 

не является решением. Я понял, что могу использовать скорее accept.

Но теперь, когда я пытаюсь проанализировать каждый элемент в ответ и обновить значение myFilter

self?.expertsFilter.accept(newModel)

Вышеприведенный оператор дает котировку ошибок

Невозможно преобразовать значение NewModel в ожидаемый тип аргумента [NewModel]

Очевидно, что он ожидает массив, а не отдельный элемент.

Временное решение:

Решение 1:

Таким образом, одно решение накапливает весь ответ во временном массиве и однажды запускает self?.expertsFilter.accept(temporary_array)

Решение 2:

Если мне нужно отправить событие onNext подписчику на разбор каждого элемента, мне нужно скопировать значение self?.ExpertsFilter в новый массив, добавить к нему вновь обработанный элемент и вернуть новый массив.

Решение 3:

Избавьтесь от BehaviorRelay и используйте BehaviorSubject/PublishSubject

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

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

Вопрос:

Поскольку BehaviorRelay предлагается в качестве альтернативы Variable am в дилемме, я использую accept правильно?? Есть ли лучший способ решить эту проблему?

Пожалуйста помоги

Ответ 1

Рассматривали ли вы просто создание нового массива из существующего значения на реле, добавление, а затем вызов accept?

myFilter.accept(myFilter.value + [newModel])

Ответ 2

Я написал это расширение для замены Variable на BehaviorRelay s. Вы можете добавить любой метод, который вам нужен на основе этого шаблона, чтобы легко перенести.

public extension BehaviorRelay where Element: RangeReplaceableCollection {

    public func insert(_ subElement: Element.Element, at index: Element.Index) {
        var newValue = value
        newValue.insert(subElement, at: index)
        accept(newValue)
    }

    public func insert(contentsOf newSubelements: Element, at index: Element.Index) {
        var newValue = value
        newValue.insert(contentsOf: newSubelements, at: index)
        accept(newValue)
    }

    public func remove(at index: Element.Index) {
        var newValue = value
        newValue.remove(at: index)
        accept(newValue)
    }
}

Вместо Variable.value.funcName теперь вы пишете BehaviorRelay.funcName.

Идея использовать, where Element: RangeReplaceableCollection происходит от ответа ретендо

Также обратите внимание, что index имеет тип Element.Index, а не Int или что-либо еще.

Ответ 3

Основываясь на ответе Далтона, здесь есть удобное расширение:

extension BehaviorRelay where Element: RangeReplaceableCollection {
    func acceptAppending(_ element: Element.Element) {
        accept(value + [element])
    }
}

Ответ 4

Я бы сделал что-то подобное -

let requests = PublishSubject<Observable<ServerResponse>>.create()
let responses: Observable<ServerResponse> = requests.switchLatest()

let parsed: Observable<[ParsedItem]> = responses
  .flatMap { Observable.from($0).map { parse($0) }.toArray() }

parsed.bind(to: ui)

// repeated part
let request1: Observable<ServerResponse> = servive.call()
request.onNext(request1)

Ответ 5

Ответ AshKan отличный, но я пришел сюда в поисках недостающего метода из решения. Append:

extension BehaviorRelay where Element: RangeReplaceableCollection {

    func append(_ subElement: Element.Element) {
        var newValue = value
        newValue.append(subElement)
        accept(newValue)
    }

} 

Ответ 6

Я создал это расширение с двумя способами, облегчающими миграцию, если у вас есть переменная массива и вам нужно использовать append.

    extension BehaviorRelay where Element: RangeReplaceableCollection {

        func append(_ subElement: Element.Element) {
            var newValue = value
            newValue.append(subElement)
            accept(newValue)
        }

        func append(contentsOf: [Element.Element]) {
            var newValue = value
            newValue.append(contentsOf: contentsOf)
            accept(newValue)
        }

        public func remove(at index: Element.Index) {
            var newValue = value
            newValue.remove(at: index)
            accept(newValue)
        }

        public func removeAll() {
            var newValue = value
            newValue.removeAll()
            accept(newValue)
        }

    }

и ты называешь это так

    var things = BehaviorRelay<[String]>(value: [])
    things.append("aa")
    let otherThings = ["bb", "cc"]
    things.append(contentsOf: otherThings) 
    things.remove(at: 0)
    things.removeAll()