diff --git a/Comier/Classes/AS+IGListKit/COListBindableSectionController+DifferenceKit.swift b/Comier/Classes/AS+IGListKit/COListBindableSectionController+DifferenceKit.swift index 036edf1..c3d380f 100644 --- a/Comier/Classes/AS+IGListKit/COListBindableSectionController+DifferenceKit.swift +++ b/Comier/Classes/AS+IGListKit/COListBindableSectionController+DifferenceKit.swift @@ -34,15 +34,21 @@ enum SectionState { case applied } +struct WaitItem { + let object: Any? + let animated: Bool + let shouldUpdateCell: Bool + let completion: ((Bool) -> Void)? +} + open class ASListBindingSectionController: COSectionController { - public typealias SectionModel = Element - + private var lockCount: Int = 0 public var viewModels: [ListDiffable] = [] public var object: SectionModel? = nil var state: SectionState = .idle - var lastWaitForUpdate: (animated: Bool, shouldUpdateCell: Bool, completion: ((Bool) -> Void)?)? = nil + var lastWaitForUpdate: WaitItem? = nil public weak var dataSource: ASListBindingDataSource? = nil public weak var delegate: ASListBindingDelegate? = nil @@ -79,13 +85,13 @@ open class ASListBindingSectionController: COSectionContr open override func didUpdate(to object: Any) { let firstUpdate = self.object == nil + let copyObj = object as? Element self.object = object as? Element - if firstUpdate { - let viewModels = self.dataSource?.viewModels(for: object) + let viewModels = self.dataSource?.viewModels(for: copyObj ?? object) self.viewModels = objectsWithDuplicateIdentifiersRemoved(viewModels) ?? [] } else { - self.updateAnimated(animated: willUpdateWithAnimation) + self.updateAnimated(object: copyObj, animated: willUpdateWithAnimation) } } @@ -106,77 +112,91 @@ open class ASListBindingSectionController: COSectionContr return ASIGListSectionControllerMethods.cellForItem(at: index, sectionController: self) } - public func updateAnimated(animated: Bool, shouldUpdateCell: Bool = true, completion: ((Bool) -> Void)? = nil) { - guard self.object != nil else {return} + public func updateAnimated(object: Any?, animated: Bool, shouldUpdateCell: Bool = true, completion: ((Bool) -> Void)? = nil) { + guard let object else {return} if self.state != .idle { - self.lastWaitForUpdate = (animated, shouldUpdateCell, completion) + self.lastWaitForUpdate = .init(object: object, animated: animated, shouldUpdateCell: shouldUpdateCell, completion: completion) return } self.state = .queued - self.collectionContext?.performBatch(animated: animated, updates: { [weak self] (batchContext) in - guard let self = self, self.state == .queued else {return} - let object = self.object - let copyViewModels = self.viewModels.map({$0}) - let oldViewModels = copyViewModels - let newViewModels = self.dataSource?.viewModels(for: object) + DispatchQueue.global().async { [weak self] in + guard let self else {return} + let newViewModels = self.dataSource?.viewModels(for: object as Any) let filterVM = objectsWithDuplicateIdentifiersRemoved(newViewModels) ?? [] - let result = ListDiff(oldArray: oldViewModels, newArray: filterVM, option: .equality) - guard result.hasChanges else {return} - self.viewModels = filterVM - - if !result.updates.isEmpty { - var indexReloads: [Int] = [] - for oldIndex in result.updates { - guard oldIndex < oldViewModels.count else {break} - if shouldUpdateCell { - let id = oldViewModels[oldIndex].diffIdentifier() - let indexAfterUpdate = result.newIndex(forIdentifier: id) - if indexAfterUpdate != NSNotFound { - if let cell = self.context.nodeForItem(at: oldIndex, section: self) { + let boxs = filterVM.map({DiffBox(value: $0)}) + let oldViewModels = viewModels.map({item in + let copy = item + return DiffBox(value: copy) + }) + let stageChanged = StagedChangeset(source: oldViewModels, target: boxs) + DispatchQueue.main.async { [weak self] in + guard let self else {return} + performUpdate(stageChanged: stageChanged, animated: animated, shouldUpdateCell: shouldUpdateCell, completion: completion) + } + } + } + + private func performUpdate(stageChanged: StagedChangeset<[DiffBox]>, animated: Bool, shouldUpdateCell: Bool, completion: ((Bool) -> Void)? = nil) { + self.lockCount = stageChanged.count + for stage in stageChanged { + self.collectionContext?.performBatch(animated: animated, updates: { [weak self] (batchContext) in + guard let self = self, self.state != .idle else {return} + guard !stageChanged.isEmpty else {return} + let changeData = stage.data.map({$0.value}) + self.viewModels = changeData + + if let ex = self.collectionContext?.experiments, !stage.elementUpdated.isEmpty, ListExperimentEnabled(mask: ex, option: IGListExperiment.invalidateLayoutForUpdates) { + batchContext.invalidateLayout(in: self, at: IndexSet(stage.elementUpdated.map({$0.element}))) + } + + if !stage.elementUpdated.isEmpty { + var indexReloads: [Int] = [] + for indexPath in stage.elementUpdated { + let index = indexPath.element + if shouldUpdateCell { + let value = changeData[indexPath.element] + if let cell = self.context.nodeForItem(at: index, section: self) { let node = cell as? ListBindable - node?.bindViewModel(filterVM[indexAfterUpdate]) + node?.bindViewModel(value) } else { - indexReloads.append(oldIndex) + indexReloads.append(indexPath.element) } + } else { + indexReloads.append(indexPath.element) } - } else { - indexReloads.append(oldIndex) + } + if !indexReloads.isEmpty { + batchContext.reload(in: self, at: IndexSet(indexReloads)) } } - if !indexReloads.isEmpty { - batchContext.reload(in: self, at: IndexSet(indexReloads)) + + if !stage.elementDeleted.isEmpty { + batchContext.delete(in: self, at: IndexSet(stage.elementDeleted.map({$0.element}))) } - } - - if let ex = self.collectionContext?.experiments, !result.updates.isEmpty, ListExperimentEnabled(mask: ex, option: IGListExperiment.invalidateLayoutForUpdates) { - batchContext.invalidateLayout(in: self, at: result.updates) - } - - if !result.deletes.isEmpty { - batchContext.delete(in: self, at: result.deletes) - } - - if !result.inserts.isEmpty { - batchContext.insert(in: self, at: result.inserts) - } - - if !result.moves.isEmpty { - for move in result.moves { - batchContext.move(in: self, from: move.from, to: move.to) + + if !stage.elementInserted.isEmpty { + batchContext.insert(in: self, at: IndexSet(stage.elementInserted.map({$0.element}))) } - } - - self.state = .applied - }, completion: { [weak self] (finished) in - completion?(finished) - if finished, let self = self { - self.state = .idle - if let wait = self.lastWaitForUpdate { - self.lastWaitForUpdate = nil - self.updateAnimated(animated: wait.animated, shouldUpdateCell: wait.shouldUpdateCell, completion: wait.completion) + + if !stage.elementMoved.isEmpty { + for move in stage.elementMoved { + batchContext.move(in: self, from: move.source.element, to: move.target.element) + } } - } - }) + self.state = .applied + }, completion: { [weak self] (finished) in + self?.lockCount -= 1 + if self?.lockCount ?? 0 <= 0 { + self?.lockCount = 0 + self?.state = .idle + completion?(finished) + if let wait = self?.lastWaitForUpdate { + self?.lastWaitForUpdate = nil + self?.updateAnimated(object: wait.object ,animated: wait.animated, shouldUpdateCell: wait.shouldUpdateCell, completion: wait.completion) + } + } + }) + } } deinit {