目前,我试图在一个基本示例中使用DiffingRxSwift。我正在使用Differ库。
这是我的关联的Interactor+ViewModels:

import Foundation
import RxSwift
import RxCocoa

class Interactor {

    var items = [
            [1,5,6,7,4,6,7,1,5],
            [1,5,2,1,0,6,7],
    ]

    let viewModel: BehaviorRelay<ViewModel>

    var currentObjects: Int = 0 {
        didSet {
            viewModel.accept(.init(with: .loaded(items[currentObjects])))
        }
    }

    init() {
        viewModel = BehaviorRelay(value: .init(with: .initialized))
    }

    func fetchValue() {
        currentObjects = currentObjects == 0 ? 1 : 0
    }


}

struct ViewModel {

    enum ViewModelType: Equatable {
        case cell(CellViewModel)
    }

    enum State {
        case initialized
        case loaded([Int])
    }

    let state: State
    let viewModels: [ViewModelType]

    init(with state: State) {
        self.state = state
        switch state {
        case .initialized: viewModels = []
        case .loaded(let values):
            viewModels = CellViewModel.from(values).map(ViewModelType.cell)
        }
    }
}

extension ViewModel: Equatable {

    static func ==(left: ViewModel, right: ViewModel) -> Bool {
        return left.state == left.state
    }
}

extension ViewModel.State: Equatable {

    static func ==(left: ViewModel.State, right: ViewModel.State) -> Bool {
        switch (left, right) {
        case (.initialized, .initialized): return true
        case let (.loaded(l), .loaded(r)): return l == r
        default: return false
        }
    }
}

struct CellViewModel {
    let description: String
}

extension CellViewModel {

    static func from(_ values: [Int]) -> [CellViewModel] {
        return values.map { CellViewModel(description: String($0)) }
    }
}

extension CellViewModel: Equatable {

    static func ==(left: CellViewModel, right: CellViewModel) -> Bool {
        return left.description == right.description
    }
}

现在对于视图,我使用一个简单的“UITableView”
import UIKit
import Differ
import RxSwift

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        ...

        interactor
            .viewModel
            .asObservable()
            .scan([], accumulator: { (previous, current) in
              Array(previous + [current]).suffix(2)
            })
            .map({ (arr) -> (previous: ViewModel?, current: ViewModel) in
              (arr.count > 1 ? arr.first : nil, arr.last!)
            }).subscribe(onNext: { [weak self] (previous, current) in
                if let prev = previous {
                    print("Previous => State: \(prev.state) | ViewModelType.count: \(prev.viewModels.count)")
                } else {
                    print("Previous => State: nil | ViewModelType.count: nil")
                }
                print("Current => State: \(current.state) | ViewModelType.count: \(current.viewModels.count)")
                guard let strongSelf = self else { return }
                DispatchQueue.main.async {
                    strongSelf.tableView.animateRowChanges(oldData: previous?.viewModels ?? [], newData: current.viewModels)
                }
            }).disposed(by: disposeBag)

        interactor.fetchValue()
    }

    @objc
    func onRefresh() {
        interactor.fetchValue()
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return interactor.viewModel.value.viewModels.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellViewModel = interactor.viewModel.value.viewModels[indexPath.row]
        switch cellViewModel {
        case .cell(let viewModel):
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = viewModel.description
            return cell
        }
    }
}

所有的事情都是一致的,我以为这项工作会完成,但我得到了一个NSInternalInconsistencyException异常。
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (7) must be equal to the number of rows contained in that section before the update (7), plus or minus the number of rows inserted or deleted from that section (7 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
我在坠机秀前放在Rx上的指纹:
Previous => State: nil | ViewModelType.count: nil
Current => State: initialized | ViewModelType.count: 0
Previous => State: initialized | ViewModelType.count: 0
Current => State: loaded([1, 5, 2, 1, 0, 6, 7]) | ViewModelType.count: 7

从逻辑的角度看,这个流程对我来说很好。我遗漏了什么吗?
编辑2019/10/29
我做了另一个版本,但没有使用RxSwift来知道问题是否是du toRxSwift
protocol InteractorDelegate: class {
    func viewModelDidChange(_ old: ViewModel?, _ new: ViewModel)
}

class Interactor {

    weak var delegate: InteractorDelegate?

    var items = [
            [1,5,6,7,4,6,7,1,5],
            [1,5,2,1,0,6,7],
    ]

    var viewModel: ViewModel? {
        didSet {
            delegate?.viewModelDidChange(oldValue, viewModel!)
        }
    }

    var currentObjects: Int = 0 {
        didSet {
            viewModel = .init(with: .loaded(items[currentObjects]))
        }
    }

    init() {
        viewModel = .init(with: .initialized)
    }

    func fetchValue() {
        currentObjects = currentObjects == 0 ? 1 : 0
    }
}

对于ViewController
extension ViewController: InteractorDelegate {

    func viewModelDidChange(_ old: ViewModel?, _ new: ViewModel) {

        if let prev = old {
            print("Previous => State: \(prev.state) | ViewModelType.count: \(prev.viewModels.count)")
        } else {
            print("Previous => State: nil | ViewModelType.count: nil")
        }
        print("Current => State: \(new.state) | ViewModelType.count: \(new.viewModels.count)")
        DispatchQueue.main.async {
            self.tableView.animateRowChanges(oldData: old?.viewModels ?? [], newData: new.viewModels)
        }
    }
}

似乎在没有RxSwift的情况下,问题仍然是一样的:
Previous => State: initialized | ViewModelType.count: 0
Current => State: loaded([1, 5, 2, 1, 0, 6, 7]) | ViewModelType.count: 7
2019-10-29 13:45:56.636678+0900 TestDiffer[93631:21379549] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (7) must be equal to the number of rows contained in that section before the update (7), plus or minus the number of rows inserted or deleted from that section (7 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

我的Equatable一致性有问题吗?
编辑2019/10/29#2
在新的测试之后,我发现只有在前一个值为空时才会发生崩溃。
通过这样更改代码,一切正常:
extension ViewController: InteractorDelegate {

    func viewModelDidChange(_ old: ViewModel?, _ new: ViewModel) {

        DispatchQueue.main.async {
            guard old != nil && !old!.viewModels.isEmpty else {
                self.tableView.reloadData()
                return
            }
            self.tableView.animateRowChanges(oldData: old!.viewModels, newData: new.viewModels)
        }
    }
}

当放回RxSwift而不是委托时同样成功。
即使它现在按预期工作。我仍然在质疑为什么当数组为空时扩散不起作用。如果前一个值为空,并且新值包含2个元素,则应将其分析为2个inserts no?这是怎么回事?

最佳答案

如果实现RxTableViewDataSourceType并将其传递给表视图的items运算符,则效果会更好。这样做的示例项目位于:https://github.com/danielt1263/RxMultiCounter
我在示例代码中使用了DifferenceKit,因此您必须对tableView(_:observedEvent:)方法进行一些更改,但这将有助于您找到正确的方向。

class RxSimpleAnimatableDataSource<E, Cell>: NSObject,
    RxTableViewDataSourceType,
    UITableViewDataSource
    where E: Differentiable, Cell: UITableViewCell
{
    typealias Element = [E]

    init(identifier: String, with animation: UITableView.RowAnimation = .automatic, configure: @escaping (Int, E, Cell) -> Void) {
        self.identifier = identifier
        self.animation = animation
        self.configure = configure
    }

    func tableView(_ tableView: UITableView, observedEvent: Event<Element>) {
        let source = values
        let target = observedEvent.element ?? []
        let changeset = StagedChangeset(source: source, target: target)
        tableView.reload(using: changeset, with: animation) { data in
            self.values = data
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return values.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! Cell
        let row = indexPath.row
        configure(row, values[row], cell)
        return cell
    }

    let identifier: String
    let animation: UITableView.RowAnimation
    let configure: (Int, E, Cell) -> Void
    var values: Element = []
}

关于ios - RxSwift Differ库崩溃`NSInternalInconsistencyException`,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58587566/

10-14 19:56