目前,我试图在一个基本示例中使用Diffing
和RxSwift
。我正在使用Differ库。
这是我的关联的Interactor
+ViewModel
s:
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/