Files
CoreStore/Sources/Internals.DiffableDataUIDispatcher.swift
2019-10-13 11:45:30 +09:00

230 lines
6.9 KiB
Swift

//
// Internals.DiffableDataUIDispatcher.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import CoreData
#if canImport(QuartzCore)
import QuartzCore
#endif
// MARK: - Internals
extension Internals {
// MARK: Internal
// Implementation based on https://github.com/ra1028/DiffableDataSources
@usableFromInline
internal final class DiffableDataUIDispatcher<O: DynamicObject> {
// MARK: Internal
typealias ObjectType = O
init(dataStack: DataStack) {
self.dataStack = dataStack
}
func apply<View: AnyObject>(_ snapshot: DiffableDataSourceSnapshot, view: View?, animatingDifferences: Bool, performUpdates: @escaping (View, StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void) -> Void) {
self.dispatcher.dispatch { [weak self] in
guard let self = self else {
return
}
self.currentSnapshot = snapshot
let newSections = snapshot.sections
guard let view = view else {
return self.sections = newSections
}
let performDiffingUpdates: () -> Void = {
let changeset = StagedChangeset(source: self.sections, target: newSections)
performUpdates(view, changeset) { sections in
self.sections = sections
}
}
#if canImport(QuartzCore)
if !animatingDifferences {
CATransaction.begin()
CATransaction.setDisableActions(true)
performDiffingUpdates()
CATransaction.commit()
return
}
#endif
performDiffingUpdates()
}
}
func snapshot() -> DiffableDataSourceSnapshot {
var snapshot: DiffableDataSourceSnapshot = .init()
snapshot.sections = self.currentSnapshot.sections
return snapshot
}
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
guard (0 ..< self.sections.endIndex) ~= indexPath.section else {
return nil
}
let items = self.sections[indexPath.section].elements
guard (0 ..< items.endIndex) ~= indexPath.item else {
return nil
}
return items[indexPath.item].differenceIdentifier
}
func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
let indexPathMap: [O.ObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
for (itemIndex, item) in section.element.elements.enumerated() {
result[item.differenceIdentifier] = IndexPath(
item: itemIndex,
section: section.offset
)
}
}
return indexPathMap[itemIdentifier]
}
func numberOfSections() -> Int {
return self.sections.count
}
func numberOfItems(inSection section: Int) -> Int {
return self.sections[section].elements.count
}
func sectionIdentifier(inSection section: Int) -> String {
return self.sections[section].differenceIdentifier
}
// MARK: Private
private let dispatcher: MainThreadSerialDispatcher = .init()
private let dataStack: DataStack
private var currentSnapshot: Internals.DiffableDataSourceSnapshot = .init()
private var sections: [Internals.DiffableDataSourceSnapshot.Section] = []
// MARK: - ElementPath
@usableFromInline
internal struct ElementPath: Hashable {
@usableFromInline
var element: Int
@usableFromInline
var section: Int
@inlinable
init(element: Int, section: Int) {
self.element = element
self.section = section
}
}
// MARK: - MainThreadSerialDispatcher
fileprivate final class MainThreadSerialDispatcher {
// MARK: FilePrivate
fileprivate init() {
self.executingCount.initialize(to: 0)
}
deinit {
self.executingCount.deinitialize(count: 1)
self.executingCount.deallocate()
}
fileprivate func dispatch(_ action: @escaping () -> Void) {
let count = OSAtomicIncrement32(self.executingCount)
if Thread.isMainThread && count == 1 {
action()
OSAtomicDecrement32(executingCount)
}
else {
DispatchQueue.main.async { [weak self] in
guard let self = self else {
return
}
action()
OSAtomicDecrement32(self.executingCount)
}
}
}
// MARK: Private
private let executingCount: UnsafeMutablePointer<Int32> = .allocate(capacity: 1)
}
}
}
#endif