Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Element: ListDiffable>: 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
Expand Down Expand Up @@ -79,13 +85,13 @@ open class ASListBindingSectionController<Element: ListDiffable>: 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)
}
}

Expand All @@ -106,77 +112,91 @@ open class ASListBindingSectionController<Element: ListDiffable>: 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<ListDiffable>]>, 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 {
Expand Down