This commit is contained in:
John Estropia
2019-10-08 21:09:34 +09:00
parent b073b7e795
commit d5114fc4bc
13 changed files with 1393 additions and 225 deletions

View File

@@ -0,0 +1,107 @@
//
// DiffableDataSource.TableView.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)
import UIKit
import CoreData
//// MARK: - DiffableDataSource
//
//extension DiffableDataSource {
//
// // MARK: - TableView
//
// public open class TableView<D: DynamicObject>: NSObject, UITableViewDataSource {
//
// // MARK: Public
//
// public typealias ObjectType = D
//
// public var defaultRowAnimation: UITableView.RowAnimation = .automatic
//
// public init(tableView: UITableView, cellProvider: @escaping (UITableView, IndexPath, ObjectType) -> UITableViewCell?) {
//
// self.tableView = tableView
// self.cellProvider = cellProvider
//
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
//
// self.rawDataSource = UITableViewDiffableDataSource<String, D.ObjectID>(
// tableView: tableView,
// cellProvider: { (tableView, indexPath, managedObjectID) -> UITableViewCell? in
//
// cellProvider(
// }
// )
// }
// else {
//
// self.rawDataSource = nil
// }
//
// super.init()
//
// tableView.dataSource = self
// }
//
// public func apply(_ snapshot: ListSnapshot<ObjectType>, animatingDifferences: Bool = true) {
//
// let dataSource = UITableViewDiffableDataSource<String, D>.
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
//
// self.rawDataSource! as! UITableViewDiffableDataSource<String, D>
//
// }
// else {
//
// }
// core.apply(
// snapshot,
// view: tableView,
// animatingDifferences: animatingDifferences,
// performUpdates: { tableView, changeset, setSections in
// tableView.reload(using: changeset, with: self.defaultRowAnimation, setData: setSections)
// })
// }
//
//
// // MARK: Private
//
// private weak var tableView: UITableView?
// private let cellProvider: (UITableView, IndexPath, ObjectType) -> UITableViewCell?
// private let rawDataSource: Any?
//
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
// private var diffableDataSource: UITableViewDiffableDataSource<String, D.ObjectID> {
//
// return self.rawDataSource! as! UITableViewDiffableDataSource<String, D.ObjectID>
// }
// }
//}
#endif

View File

@@ -0,0 +1,29 @@
//
// File.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.
//
// MARK: - DiffableDataSource
public enum DiffableDataSource {}

View File

@@ -33,7 +33,12 @@ import CoreData
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
*/
public protocol DynamicObject: AnyObject {
/**
The object ID for this instance
*/
typealias ObjectID = NSManagedObjectID
/**
Used internally by CoreStore. Do not call directly.
*/
@@ -52,7 +57,7 @@ public protocol DynamicObject: AnyObject {
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_id() -> NSManagedObjectID
func cs_id() -> ObjectID
/**
Used internally by CoreStore. Do not call directly.
@@ -103,7 +108,7 @@ extension NSManagedObject: DynamicObject {
return object.isKind(of: self)
}
public func cs_id() -> NSManagedObjectID {
public func cs_id() -> ObjectID {
return self.objectID
}
@@ -166,7 +171,7 @@ extension CoreStoreObject {
return (self as AnyClass).isSubclass(of: type as AnyClass)
}
public func cs_id() -> NSManagedObjectID {
public func cs_id() -> ObjectID {
return self.rawObject!.objectID
}

View File

@@ -29,9 +29,9 @@ public struct DataStackEnvironmentKey: EnvironmentKey {
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension EnvironmentValues {
// MARK: Public
public var dataStack: DataStack {
get {
return self[DataStackEnvironmentKey.self]

View File

@@ -82,6 +82,11 @@ extension Internals {
try self.reapplyAffectedStores(self.typedFetchRequest, self.managedObjectContext)
try self.performFetch()
if case let delegate as FetchedDiffableDataSourceSnapshotDelegate = self.delegate {
delegate.initialFetch()
}
}
@nonobjc

View File

@@ -0,0 +1,606 @@
//
// Internals.DiffableDataSourceSnapshot.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(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// MARK: - Internals
extension Internals {
// MARK: Internal
internal typealias DiffableDataSourceSnapshot = _Internal_DiffableDataSourceSnapshot
// MARK: - FallbackDiffableDataSourceSnapshot
// Implementation based on https://github.com/ra1028/DiffableDataSources
internal struct FallbackDiffableDataSourceSnapshot: DiffableDataSourceSnapshot {
// MARK: Internal
init(sections: [NSFetchedResultsSectionInfo]) {
self.structure = .init(sections: sections)
}
// MARK: DiffableDataSourceSnapshot
init() {
self.structure = .init()
}
var numberOfItems: Int {
return self.itemIdentifiers.count
}
var numberOfSections: Int {
return self.sectionIdentifiers.count
}
var sectionIdentifiers: [NSString] {
return self.structure.allSectionIDs
}
var itemIdentifiers: [NSManagedObjectID] {
self.structure.allItemIDs
}
func numberOfItems(inSection identifier: NSString) -> Int {
return self.itemIdentifiers(inSection: identifier).count
}
func itemIdentifiers(inSection identifier: NSString) -> [NSManagedObjectID] {
return self.structure.items(in: identifier)
}
func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> NSString? {
return self.structure.section(containing: identifier)
}
func indexOfItem(_ identifier: NSManagedObjectID) -> Int? {
return self.itemIdentifiers.firstIndex(of: identifier)
}
func indexOfSection(_ identifier: NSString) -> Int? {
return self.sectionIdentifiers.firstIndex(of: identifier)
}
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: NSString?) {
self.structure.append(itemIDs: identifiers, to: sectionIdentifier)
}
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID) {
self.structure.insert(itemIDs: identifiers, before: beforeIdentifier)
}
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID) {
self.structure.insert(itemIDs: identifiers, after: afterIdentifier)
}
mutating func deleteItems(_ identifiers: [NSManagedObjectID]) {
self.structure.remove(itemIDs: identifiers)
}
mutating func deleteAllItems() {
self.structure.removeAllItems()
}
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID) {
self.structure.move(itemID: identifier, before: toIdentifier)
}
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID) {
self.structure.move(itemID: identifier, after: toIdentifier)
}
mutating func reloadItems(_ identifiers: [NSManagedObjectID]) {
self.structure.update(itemIDs: identifiers)
}
mutating func appendSections(_ identifiers: [NSString]) {
self.structure.append(sectionIDs: identifiers)
}
mutating func insertSections(_ identifiers: [NSString], beforeSection toIdentifier: NSString) {
self.structure.insert(sectionIDs: identifiers, before: toIdentifier)
}
mutating func insertSections(_ identifiers: [NSString], afterSection toIdentifier: NSString) {
self.structure.insert(sectionIDs: identifiers, after: toIdentifier)
}
mutating func deleteSections(_ identifiers: [NSString]) {
self.structure.remove(sectionIDs: identifiers)
}
mutating func moveSection(_ identifier: NSString, beforeSection toIdentifier: NSString) {
self.structure.move(sectionID: identifier, before: toIdentifier)
}
mutating func moveSection(_ identifier: NSString, afterSection toIdentifier: NSString) {
self.structure.move(sectionID: identifier, after: toIdentifier)
}
mutating func reloadSections(_ identifiers: [NSString]) {
self.structure.update(sectionIDs: identifiers)
}
// MARK: Private
private var structure: BackingStructure
// MARK: - BackingStructure
internal struct BackingStructure {
// MARK: Internal
var sections: [Section]
init() {
self.sections = []
}
init(sections: [NSFetchedResultsSectionInfo]) {
self.sections = sections.map {
Section(
id: $0.name as NSString,
items: $0.objects?
.compactMap({ ($0 as? NSManagedObject)?.objectID })
.map(Item.init(id:)) ?? [],
isReloaded: false
)
}
}
var allSectionIDs: [NSString] {
return self.sections.map({ $0.id })
}
var allItemIDs: [NSManagedObjectID] {
return self.sections.lazy.flatMap({ $0.elements }).map({ $0.id })
}
func items(in sectionID: NSString) -> [NSManagedObjectID] {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
return self.sections[sectionIndex].elements.map({ $0.id })
}
func section(containing itemID: NSManagedObjectID) -> NSString? {
return self.itemPositionMap()[itemID]?.section.id
}
mutating func append(itemIDs: [NSManagedObjectID], to sectionID: NSString?) {
let index: Array<Section>.Index
if let sectionID = sectionID {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
index = sectionIndex
}
else {
let section = self.sections
guard !section.isEmpty else {
Internals.abort("No sections exist")
}
index = section.index(before: section.endIndex)
}
let items = itemIDs.lazy.map(Item.init)
self.sections[index].elements.append(contentsOf: items)
}
mutating func insert(itemIDs: [NSManagedObjectID], before beforeItemID: NSManagedObjectID) {
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
Internals.abort("Item \(beforeItemID) does not exist")
}
let items = itemIDs.lazy.map(Item.init)
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
}
mutating func insert(itemIDs: [NSManagedObjectID], after afterItemID: NSManagedObjectID) {
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
Internals.abort("Item \(afterItemID) does not exist")
}
let itemIndex = self.sections[itemPosition.sectionIndex].elements
.index(after: itemPosition.itemRelativeIndex)
let items = itemIDs.lazy.map(Item.init)
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemIndex)
}
mutating func remove(itemIDs: [NSManagedObjectID]) {
let itemPositionMap = self.itemPositionMap()
var removeIndexSetMap: [Int: IndexSet] = [:]
for itemID in itemIDs {
guard let itemPosition = itemPositionMap[itemID] else {
continue
}
removeIndexSetMap[itemPosition.sectionIndex, default: []]
.insert(itemPosition.itemRelativeIndex)
}
for (sectionIndex, removeIndexSet) in removeIndexSetMap {
for range in removeIndexSet.rangeView.reversed() {
self.sections[sectionIndex].elements.removeSubrange(range)
}
}
}
mutating func removeAllItems() {
for sectionIndex in self.sections.indices {
self.sections[sectionIndex].elements.removeAll()
}
}
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
guard let removed = self.remove(itemID: itemID) else {
Internals.abort("Item \(itemID) does not exist")
}
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
Internals.abort("Item \(beforeItemID) does not exist")
}
self.sections[itemPosition.sectionIndex].elements
.insert(removed, at: itemPosition.itemRelativeIndex)
}
mutating func move(itemID: NSManagedObjectID, after afterItemID: NSManagedObjectID) {
guard let removed = self.remove(itemID: itemID) else {
Internals.abort("Item \(itemID) does not exist")
}
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
Internals.abort("Item \(afterItemID) does not exist")
}
let itemIndex = self.sections[itemPosition.sectionIndex].elements
.index(after: itemPosition.itemRelativeIndex)
self.sections[itemPosition.sectionIndex].elements
.insert(removed, at: itemIndex)
}
mutating func update(itemIDs: [NSManagedObjectID]) {
let itemPositionMap = self.itemPositionMap()
for itemID in itemIDs {
guard let itemPosition = itemPositionMap[itemID] else {
Internals.abort("Item \(itemID) does not exist")
}
self.sections[itemPosition.sectionIndex].elements[itemPosition.itemRelativeIndex].isReloaded = true
}
}
mutating func append(sectionIDs: [NSString]) {
let newSections = sectionIDs.lazy.map(Section.init)
self.sections.append(contentsOf: newSections)
}
mutating func insert(sectionIDs: [NSString], before beforeSectionID: NSString) {
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
}
let newSections = sectionIDs.lazy.map(Section.init)
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func insert(sectionIDs: [NSString], after afterSectionID: NSString) {
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
Internals.abort("Section \"\(afterSectionID)\" does not exist")
}
let sectionIndex = self.sections.index(after: beforeIndex)
let newSections = sectionIDs.lazy.map(Section.init)
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func remove(sectionIDs: [NSString]) {
for sectionID in sectionIDs {
self.remove(sectionID: sectionID)
}
}
mutating func move(sectionID: NSString, before beforeSectionID: NSString) {
guard let removed = self.remove(sectionID: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
}
self.sections.insert(removed, at: sectionIndex)
}
mutating func move(sectionID: NSString, after afterSectionID: NSString) {
guard let removed = self.remove(sectionID: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
Internals.abort("Section \"\(afterSectionID)\" does not exist")
}
let sectionIndex = self.sections.index(after: beforeIndex)
self.sections.insert(removed, at: sectionIndex)
}
mutating func update(sectionIDs: [NSString]) {
for sectionID in sectionIDs {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
continue
}
self.sections[sectionIndex].isReloaded = true
}
}
// MARK: Private
private func sectionIndex(of sectionID: NSString) -> Array<Section>.Index? {
return self.sections.firstIndex(where: { $0.id == sectionID })
}
@discardableResult
private mutating func remove(itemID: NSManagedObjectID) -> Item? {
guard let itemPosition = self.itemPositionMap()[itemID] else {
return nil
}
return self.sections[itemPosition.sectionIndex].elements
.remove(at: itemPosition.itemRelativeIndex)
}
@discardableResult
private mutating func remove(sectionID: NSString) -> Section? {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
return nil
}
return self.sections.remove(at: sectionIndex)
}
private func itemPositionMap() -> [NSManagedObjectID: ItemPosition] {
return self.sections.enumerated().reduce(into: [:]) { result, section in
for (itemRelativeIndex, item) in section.element.elements.enumerated() {
result[item.id] = ItemPosition(
item: item,
itemRelativeIndex: itemRelativeIndex,
section: section.element,
sectionIndex: section.offset
)
}
}
}
// MARK: - Item
internal struct Item: Identifiable, Equatable {
var isReloaded: Bool
init(id: NSManagedObjectID, isReloaded: Bool) {
self.id = id
self.isReloaded = isReloaded
}
init(id: NSManagedObjectID) {
self.init(id: id, isReloaded: false)
}
func isContentEqual(to source: Item) -> Bool {
return !self.isReloaded && self.id == source.id
}
// MARK: Identifiable
var id: NSManagedObjectID
}
// MARK: - Section
internal struct Section: Identifiable, Equatable {
var elements: [Item] = []
var isReloaded: Bool
init(id: NSString, items: [Item], isReloaded: Bool) {
self.id = id
self.elements = items
self.isReloaded = isReloaded
}
init(id: NSString) {
self.init(id: id, items: [], isReloaded: false)
}
init<S: Sequence>(source: Section, elements: S) where S.Element == Item {
self.init(id: source.id, items: Array(elements), isReloaded: source.isReloaded)
}
func isContentEqual(to source: Section) -> Bool {
return !self.isReloaded && self.id == source.id
}
// MARK: Identifiable
var id: NSString
}
// MARK: - ItemPosition
fileprivate struct ItemPosition {
var item: Item
var itemRelativeIndex: Int
var section: Section
var sectionIndex: Int
}
}
}
}
// MARK: - NSDiffableDataSourceSnapshot: Internals.DiffableDataSourceSnapshot
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension NSDiffableDataSourceSnapshot: Internals.DiffableDataSourceSnapshot where SectionIdentifierType == NSString, ItemIdentifierType == NSManagedObjectID {}
// MARK: - Internals.DiffableDataSourceSnapshot
internal protocol _Internal_DiffableDataSourceSnapshot {
init()
var numberOfItems: Int { get }
var numberOfSections: Int { get }
var sectionIdentifiers: [NSString] { get }
var itemIdentifiers: [NSManagedObjectID] { get }
func numberOfItems(inSection identifier: NSString) -> Int
func itemIdentifiers(inSection identifier: NSString) -> [NSManagedObjectID]
func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> NSString?
func indexOfItem(_ identifier: NSManagedObjectID) -> Int?
func indexOfSection(_ identifier: NSString) -> Int?
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: NSString?)
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID)
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID)
mutating func deleteItems(_ identifiers: [NSManagedObjectID])
mutating func deleteAllItems()
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID)
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID)
mutating func reloadItems(_ identifiers: [NSManagedObjectID])
mutating func appendSections(_ identifiers: [NSString])
mutating func insertSections(_ identifiers: [NSString], beforeSection toIdentifier: NSString)
mutating func insertSections(_ identifiers: [NSString], afterSection toIdentifier: NSString)
mutating func deleteSections(_ identifiers: [NSString])
mutating func moveSection(_ identifier: NSString, beforeSection toIdentifier: NSString)
mutating func moveSection(_ identifier: NSString, afterSection toIdentifier: NSString)
mutating func reloadSections(_ identifiers: [NSString])
}
#endif

View File

@@ -0,0 +1,157 @@
//
// Internals.FallbackDiffableDataUIDispatcher.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
// internal final class FallbackDiffableDataUIDispatcher {
//
// // MARK: Internal
//
// internal func apply() {
//
// }
//
// func snapshot() -> DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> {
// var snapshot = DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>()
// snapshot.structure.sections = currentSnapshot.structure.sections
// return snapshot
// }
//
// func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? {
// guard 0..<sections.endIndex ~= indexPath.section else {
// return nil
// }
//
// let items = sections[indexPath.section].elements
//
// guard 0..<items.endIndex ~= indexPath.item else {
// return nil
// }
//
// return items[indexPath.item].differenceIdentifier
// }
//
// func unsafeItemIdentifier(for indexPath: IndexPath, file: StaticString = #file, line: UInt = #line) -> ItemIdentifierType {
// guard let itemIdentifier = itemIdentifier(for: indexPath) else {
// universalError("Item not found at the specified index path(\(indexPath)).")
// }
//
// return itemIdentifier
// }
//
// func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? {
// let indexPathMap: [ItemIdentifierType: IndexPath] = 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 sections.count
// }
//
// func numberOfItems(inSection section: Int) -> Int {
// return sections[section].elements.count
// }
//
//
//
// // MARK: Private
//
// private let dispatcher: MainThreadSerialDispatcher = .init()
//
// private var currentSnapshot: Internals.DiffableDataSourceSnapshot = FallbackDiffableDataSourceSnapshot()
// private var sections: [FallbackDiffableDataSourceSnapshot.BackingStructure.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

View File

@@ -23,8 +23,6 @@
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
import CoreData
@@ -41,10 +39,9 @@ import AppKit
// MARK: - FetchedDiffableDataSourceSnapshot
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: NSDiffableDataSourceSnapshotReference)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot)
}
@@ -54,7 +51,6 @@ extension Internals {
// MARK: - FetchedDiffableDataSourceSnapshotDelegate
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
internal final class FetchedDiffableDataSourceSnapshotDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Internal
@@ -77,18 +73,71 @@ extension Internals {
self.fetchedResultsController?.delegate = nil
}
internal func initialFetch() {
#if canImport(UIKit) || canImport(AppKit)
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
return
}
#endif
guard let fetchedResultsController = self.fetchedResultsController else {
return
}
self.controllerDidChangeContent(fetchedResultsController.dynamicCast())
}
// MARK: NSFetchedResultsControllerDelegate
#if canImport(UIKit) || canImport(AppKit)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
self.handler?.controller(
controller,
didChangContentWith: snapshot
didChangContentWith: snapshot as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
)
}
#endif
@objc
dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.reloadedIDs = []
}
@objc
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.handler?.controller(
controller,
didChangContentWith: FallbackDiffableDataSourceSnapshot(
sections: controller.sections ?? []
)
)
self.reloadedIDs = []
}
// @objc
// dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
//
//
//// [1].difference(from: <#T##BidirectionalCollection#>)
// let managedObject = anObject as! NSManagedObject
// self.reloadedIDs.insert(managedObject.objectID)
// }
// MARK: Private
private var reloadedIDs: Set<NSManagedObjectID> = []
}
}
#endif

View File

@@ -23,8 +23,6 @@
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import CoreData
#if canImport(UIKit)
@@ -38,21 +36,170 @@ import AppKit
// MARK: - LiveList
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollection, Hashable {
// MARK: Public
public subscript<S: Sequence>(indices: S) -> [ObjectType] where S.Element == Index {
public typealias SectionID = String
public typealias ItemID = D.ObjectID
public subscript<S: Sequence>(indices indices: S) -> [ObjectType] where S.Element == Index {
let context = self.context!
let objectIDs = self.snapshotStruct.itemIdentifiers
let objectIDs = self.diffableSnapshot.itemIdentifiers
return indices.map { position in
let objectID = objectIDs[position]
return context.fetchExisting(objectID)!
}
}
public subscript(section sectionID: SectionID) -> [ObjectType] {
let context = self.context!
let objectIDs = self.itemIdentifiers(inSection: sectionID)
return objectIDs.map {
return context.fetchExisting($0)!
}
}
public subscript<S: Sequence>(section sectionID: SectionID, itemIndices itemIndices: S) -> [ObjectType] where S.Element == Int {
let context = self.context!
let objectIDs = self.itemIdentifiers(inSection: sectionID)
return itemIndices.map { position in
let objectID = objectIDs[position]
return context.fetchExisting(objectID)!
}
}
public var numberOfItems: Int {
return self.diffableSnapshot.numberOfItems
}
public var numberOfSections: Int {
return self.diffableSnapshot.numberOfSections
}
public var sectionIdentifiers: [String] {
return self.diffableSnapshot.sectionIdentifiers as [String]
}
public var itemIdentifiers: [ItemID] {
return self.diffableSnapshot.itemIdentifiers as [ItemID]
}
public func numberOfItems(inSection identifier: SectionID) -> Int {
return self.diffableSnapshot.numberOfItems(inSection: identifier as NSString)
}
public func itemIdentifiers(inSection identifier: SectionID) -> [ItemID] {
return self.diffableSnapshot.itemIdentifiers(inSection: identifier as NSString)
}
public func itemIdentifiers(inSection identifier: SectionID, atIndices indices: IndexSet) -> [ItemID] {
let itemIDs = self.itemIdentifiers(inSection: identifier)
return indices.map({ itemIDs[$0] })
}
public func sectionIdentifier(containingItem identifier: ItemID) -> SectionID? {
return self.diffableSnapshot.sectionIdentifier(containingItem: identifier) as SectionID?
}
public func indexOfItem(_ identifier: ItemID) -> Index? {
return self.diffableSnapshot.indexOfItem(identifier)
}
public func indexOfSection(_ identifier: SectionID) -> Int? {
return self.diffableSnapshot.indexOfSection(identifier as NSString)
}
public mutating func appendItems(_ identifiers: [ItemID], toSection sectionIdentifier: SectionID? = nil) {
self.diffableSnapshot.appendItems(identifiers, toSection: sectionIdentifier as NSString?)
}
public mutating func insertItems(_ identifiers: [ItemID], beforeItem beforeIdentifier: ItemID) {
self.diffableSnapshot.insertItems(identifiers, beforeItem: beforeIdentifier)
}
public mutating func insertItems(_ identifiers: [ItemID], afterItem afterIdentifier: ItemID) {
self.diffableSnapshot.insertItems(identifiers, afterItem: afterIdentifier)
}
public mutating func deleteItems(_ identifiers: [ItemID]) {
self.diffableSnapshot.deleteItems(identifiers)
}
public mutating func deleteAllItems() {
self.diffableSnapshot.deleteAllItems()
}
public mutating func moveItem(_ identifier: ItemID, beforeItem toIdentifier: ItemID) {
self.diffableSnapshot.moveItem(identifier, beforeItem: toIdentifier)
}
public mutating func moveItem(_ identifier: ItemID, afterItem toIdentifier: ItemID) {
self.diffableSnapshot.moveItem(identifier, afterItem: toIdentifier)
}
public mutating func reloadItems(_ identifiers: [ItemID]) {
self.diffableSnapshot.reloadItems(identifiers)
}
public mutating func appendSections(_ identifiers: [SectionID]) {
self.diffableSnapshot.appendSections(identifiers as [NSString])
}
public mutating func insertSections(_ identifiers: [SectionID], beforeSection toIdentifier: SectionID) {
self.diffableSnapshot.insertSections(identifiers as [NSString], beforeSection: toIdentifier as NSString)
}
public mutating func insertSections(_ identifiers: [SectionID], afterSection toIdentifier: SectionID) {
self.diffableSnapshot.insertSections(identifiers as [NSString], afterSection: toIdentifier as NSString)
}
public mutating func deleteSections(_ identifiers: [SectionID]) {
self.diffableSnapshot.deleteSections(identifiers as [NSString])
}
public mutating func moveSection(_ identifier: SectionID, beforeSection toIdentifier: SectionID) {
self.diffableSnapshot.moveSection(identifier as NSString, beforeSection: toIdentifier as NSString)
}
public mutating func moveSection(_ identifier: SectionID, afterSection toIdentifier: SectionID) {
self.diffableSnapshot.moveSection(identifier as NSString, afterSection: toIdentifier as NSString)
}
public mutating func reloadSections(_ identifiers: [SectionID]) {
self.diffableSnapshot.reloadSections(identifiers as [NSString])
}
// MARK: SnapshotResult
@@ -64,18 +211,18 @@ public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollec
public var startIndex: Index {
return 0
return self.diffableSnapshot.itemIdentifiers.startIndex
}
public var endIndex: Index {
return self.snapshotStruct.numberOfItems
return self.diffableSnapshot.itemIdentifiers.endIndex
}
public subscript(position: Index) -> ObjectType {
let context = self.context!
let objectID = self.snapshotStruct.itemIdentifiers[position]
let objectID = self.diffableSnapshot.itemIdentifiers[position]
return context.fetchExisting(objectID)!
}
@@ -106,16 +253,14 @@ public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollec
// MARK: Internal
internal init() {
self.snapshotReference = .init()
self.snapshotStruct = self.snapshotReference as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
self.diffableSnapshot = Internals.FallbackDiffableDataSourceSnapshot()
self.context = nil
}
internal init(snapshotReference: NSDiffableDataSourceSnapshotReference, context: NSManagedObjectContext) {
self.snapshotReference = snapshotReference
self.snapshotStruct = snapshotReference as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
internal init(diffableSnapshot: Internals.DiffableDataSourceSnapshot, context: NSManagedObjectContext) {
self.diffableSnapshot = diffableSnapshot
self.context = context
}
@@ -123,9 +268,6 @@ public struct ListSnapshot<D: DynamicObject>: SnapshotResult, RandomAccessCollec
// MARK: Private
private let id: UUID = .init()
private let snapshotReference: NSDiffableDataSourceSnapshotReference
private let snapshotStruct: NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
private let context: NSManagedObjectContext?
private var diffableSnapshot: Internals.DiffableDataSourceSnapshot
}
#endif

View File

@@ -23,18 +23,8 @@
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import CoreData
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
#if canImport(Combine)
import Combine
@@ -48,7 +38,6 @@ import SwiftUI
// MARK: - LiveList
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
public final class LiveList<D: DynamicObject> {
// MARK: Public
@@ -57,23 +46,22 @@ public final class LiveList<D: DynamicObject> {
willSet {
#if canImport(Combine)
let oldValue = self.snapshot
guard newValue != oldValue else {
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) else {
return
}
#if canImport(Combine)
#if canImport(SwiftUI)
withAnimation {
self.objectWillChange.send()
}
#else
self.objectWillChange.send()
#endif
#endif
}
}
@@ -182,13 +170,20 @@ public final class LiveList<D: DynamicObject> {
applyFetchClauses: applyFetchClauses
)
#if canImport(Combine)
self.rawObjectWillChange = ObservableObjectPublisher()
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) {
#else
self.rawObjectWillChange = nil
#if canImport(Combine)
self.rawObjectWillChange = ObservableObjectPublisher()
#endif
#else
self.rawObjectWillChange = nil
#endif
}
else {
self.rawObjectWillChange = nil
}
// if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
@@ -278,14 +273,16 @@ public final class LiveList<D: DynamicObject> {
// MARK: - LiveList: FetchedDiffableDataSourceSnapshotHandler
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension LiveList: FetchedDiffableDataSourceSnapshotHandler {
// MARK: FetchedDiffableDataSourceSnapshotHandler
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
self.snapshot = .init(snapshotReference: snapshot, context: controller.managedObjectContext)
self.snapshot = .init(
diffableSnapshot: snapshot,
context: controller.managedObjectContext
)
}
}
@@ -307,5 +304,3 @@ extension LiveList: LiveResult {
}
#endif
#endif

View File

@@ -23,143 +23,151 @@
// SOFTWARE.
//
#if canImport(SwiftUI) && canImport(Combine)
import CoreData
import Combine
import SwiftUI
#warning("TODO: autoupdating doesn't work yet")
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
@propertyWrapper
public struct LiveQuery<Result: LiveResult>: DynamicProperty {
// MARK: Public
@Environment(\.dataStack)
public var dataStack: DataStack
public typealias ObjectType = Result.ObjectType
// MARK: @propertyWrapper
public fileprivate(set) var wrappedValue: Result {
get {
return self.nonMutatingWrappedValue.wrappedValue
}
set {
self.nonMutatingWrappedValue = LazyNonmutating { newValue }
}
}
public var projectedValue: Result {
return self.wrappedValue
}
// MARK: DynamicProperty
public mutating func update() {
SwiftUI.withAnimation {
let dataStack = self.dataStack
if self.set(dataStack: dataStack) {
return
}
self.wrappedValue = self.newWrappedValue(dataStack)
}
}
// MARK: FilePrivate
fileprivate let newWrappedValue: (DataStack) -> Result
fileprivate init(newWrappedValue: @escaping (DataStack) -> Result) {
self.newWrappedValue = newWrappedValue
}
// MARK: Private
private var nonMutatingWrappedValue: LazyNonmutating<Result> = .init { fatalError() }
private var currentDataStack: DataStack?
private mutating func set(dataStack: DataStack) -> Bool {
guard self.currentDataStack != dataStack else {
return false
}
self.currentDataStack = dataStack
let newWrappedValue = self.newWrappedValue
self.nonMutatingWrappedValue = LazyNonmutating<Result> {
newWrappedValue(dataStack)
}
return true
}
// MARK: - LazyNonmutating
fileprivate final class LazyNonmutating<Value> {
// MARK: FilePrivate
lazy var wrappedValue: Value = self.initializer()
init(_ initializer: @escaping () -> Value) {
self.initializer = initializer
}
// MARK: Private
private var initializer: () -> Value
}
}
#if canImport(UIKit) || canImport(AppKit)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension LiveQuery {
public init<D: DynamicObject>(liveList: LiveList<D>) where Result == LiveList<D> {
self.init(
newWrappedValue: { _ in liveList }
)
}
public init<D: DynamicObject>(_ clauseChain: FetchChainBuilder<D>) where Result == LiveList<D> {
self.init(
newWrappedValue: { $0.liveList(clauseChain) }
)
}
public init<D: DynamicObject>(_ clauseChain: SectionMonitorChainBuilder<D>) where Result == LiveList<D> {
self.init(
newWrappedValue: { $0.liveList(clauseChain) }
)
}
}
#endif
#endif
//#if canImport(SwiftUI) && canImport(Combine)
//
//import CoreData
//import Combine
//import SwiftUI
//
//
//#warning("TODO: autoupdating doesn't work yet")
//@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
//@propertyWrapper
//public struct LiveQuery<Result: LiveResult>: DynamicProperty {
//
// // MARK: Public
//
// @Environment(\.dataStack)
// public var dataStack: DataStack
//
// public typealias ObjectType = Result.ObjectType
//
//
// // MARK: @propertyWrapper
//
// public var wrappedValue: Result {
//
// get {
//
// return self.backingWrapper!.wrappedValue
// }
// set {
//
// self.backingWrapper!.wrappedValue = newValue
// }
// }
//
// public var projectedValue: ObservedObject<Result>.Wrapper {
//
// return self.backingWrapper!.projectedValue
// }
//
//
// // MARK: DynamicProperty
//
// public mutating func update() {
//
// SwiftUI.withAnimation {
//
// let dataStack = self.dataStack
//
// if self.backingWrapper == nil {
//
// self.backingWrapper = ObservedObject(wrappedValue: self.newLiveResult(dataStack))
// }
// else {
//
// self.backingWrapper!.wrappedValue = self.newLiveResult(dataStack)
// }
// self.backingWrapper!.update()
// }
// }
//
//
// // MARK: FilePrivate
//
// fileprivate let newLiveResult: (DataStack) -> Result
//
// fileprivate init(newLiveResult: @escaping (DataStack) -> Result) {
//
// self.newLiveResult = newLiveResult
// }
//
//
// // MARK: Private
//
//// private var nonMutatingWrappedValue: LazyNonmutating<Result> = .init { fatalError() }
//
// private var currentDataStack: DataStack?
// private var backingWrapper: ObservedObject<Result>?
//
// private mutating func set(dataStack: DataStack) -> Bool {
//
// guard self.currentDataStack != dataStack else {
//
// return false
// }
// self.currentDataStack = dataStack
// if self.backingWrapper == nil {
//
// self.backingWrapper = ObservedObject(wrappedValue: self.newLiveResult(dataStack))
// }
// else {
//
// self.backingWrapper?.wrappedValue = self.newLiveResult(dataStack)
// }
// return true
// }
//
//
// // MARK: - LazyNonmutating
//
// fileprivate final class LazyNonmutating<Value> {
//
// // MARK: FilePrivate
//
// lazy var wrappedValue: Value = self.initializer()
//
// init(_ initializer: @escaping () -> Value) {
//
// self.initializer = initializer
// }
//
//
// // MARK: Private
//
// private var initializer: () -> Value
// }
//}
//
//
//#if canImport(UIKit) || canImport(AppKit)
//
//@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
//extension LiveQuery {
//
// public init<D: DynamicObject>(liveList: LiveList<D>) where Result == LiveList<D> {
//
// self.init(
// newLiveResult: { _ in liveList }
// )
// }
//
// public init<D: DynamicObject>(_ clauseChain: FetchChainBuilder<D>) where Result == LiveList<D> {
//
// self.init(
// newLiveResult: { $0.liveList(clauseChain) }
// )
// }
//
// public init<D: DynamicObject>(_ clauseChain: SectionMonitorChainBuilder<D>) where Result == LiveList<D> {
//
// self.init(
// newLiveResult: { $0.liveList(clauseChain) }
// )
// }
//}
//
//#endif
//
//#endif