mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-13 04:40:32 +01:00
Compare commits
13 Commits
8.0.1
...
transactio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf10f4668c | ||
|
|
a1a04aaf8a | ||
|
|
4ddfa95140 | ||
|
|
45215c7a18 | ||
|
|
d2f1656fdd | ||
|
|
5fbb1ab09d | ||
|
|
d0cc01877e | ||
|
|
a434628249 | ||
|
|
9cc616f720 | ||
|
|
4534bc06f5 | ||
|
|
798d30bbd9 | ||
|
|
7938aa2447 | ||
|
|
6a2394052c |
@@ -197,7 +197,7 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
modelVersion: "V1",
|
||||
entities: [
|
||||
Entity<Animal>("Animal"),
|
||||
Entity<Dog>("Dog"),
|
||||
Entity<Dog>("Dog", indexes: [[\Dog.$nickname, \Dog.$age]]),
|
||||
Entity<Person>("Person")
|
||||
],
|
||||
versionLock: [
|
||||
|
||||
@@ -102,6 +102,7 @@ extension Modern.ColorsDemo {
|
||||
|
||||
try transaction.deleteAll(From<Modern.ColorsDemo.Palette>())
|
||||
},
|
||||
sourceIdentifier: TransactionSource.clear,
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
@@ -113,6 +114,7 @@ extension Modern.ColorsDemo {
|
||||
|
||||
_ = transaction.create(Into<Modern.ColorsDemo.Palette>())
|
||||
},
|
||||
sourceIdentifier: TransactionSource.add,
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
@@ -127,6 +129,7 @@ extension Modern.ColorsDemo {
|
||||
palette.setRandomHue()
|
||||
}
|
||||
},
|
||||
sourceIdentifier: TransactionSource.shuffle,
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -65,9 +65,22 @@ extension Modern {
|
||||
sectionIndexTransformer: { $0?.first?.uppercased() }
|
||||
)
|
||||
.where(self.filter.whereClause())
|
||||
.orderBy(.ascending(\.$hue))
|
||||
.orderBy(.ascending(\.$hue)),
|
||||
sourceIdentifier: TransactionSource.refetch
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - TransactionSource
|
||||
|
||||
enum TransactionSource {
|
||||
|
||||
case add
|
||||
case delete
|
||||
case shuffle
|
||||
case clear
|
||||
case refetch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ extension Modern.ColorsDemo.UIKit {
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<Modern.ColorsDemo.Palette>,
|
||||
didUpdateObject object: Modern.ColorsDemo.Palette,
|
||||
changedPersistentKeys: Set<KeyPathString>
|
||||
changedPersistentKeys: Set<KeyPathString>,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
||||
|
||||
@@ -32,17 +32,26 @@ extension Modern.ColorsDemo.UIKit {
|
||||
)
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `dataSource.apply()` once.
|
||||
⭐️ Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `dataSource.apply()` once. This example inspects the optional `transactionSource` to determine the source of the update which is helpful for debugging or for fine-tuning animations.
|
||||
*/
|
||||
private func startObservingList() {
|
||||
|
||||
let dataSource = self.dataSource
|
||||
self.listPublisher.addObserver(self) { (listPublisher) in
|
||||
self.listPublisher.addObserver(self, notifyInitial: true) { (listPublisher, transactionSource) in
|
||||
|
||||
dataSource.apply(listPublisher.snapshot, animatingDifferences: true)
|
||||
switch transactionSource as? Modern.ColorsDemo.TransactionSource {
|
||||
|
||||
case .add,
|
||||
.delete,
|
||||
.shuffle,
|
||||
.clear:
|
||||
dataSource.apply(listPublisher.snapshot, animatingDifferences: true)
|
||||
|
||||
case nil,
|
||||
.refetch:
|
||||
dataSource.apply(listPublisher.snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
|
||||
dataSource.apply(self.listPublisher.snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,6 +83,7 @@ extension Modern.ColorsDemo.UIKit {
|
||||
|
||||
transaction.delete(objectIDs: [itemID])
|
||||
},
|
||||
sourceIdentifier: Modern.ColorsDemo.TransactionSource.delete,
|
||||
completion: { _ in }
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ let package = Package(
|
||||
.macOS(.v10_13), .iOS(.v11), .tvOS(.v11), .watchOS(.v4)
|
||||
],
|
||||
products: [
|
||||
.library(name: "CoreStore", type: .static, targets: ["CoreStore"])
|
||||
.library(name: "CoreStore", targets: ["CoreStore"])
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
|
||||
@@ -158,9 +158,19 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) {
|
||||
internal init(
|
||||
mainContext: NSManagedObjectContext,
|
||||
queue: DispatchQueue,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
|
||||
super.init(
|
||||
mainContext: mainContext,
|
||||
queue: queue,
|
||||
supportsUndo: false,
|
||||
bypassesQueueing: false,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
internal func autoCommit(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void) {
|
||||
@@ -168,12 +178,15 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
self.isCommitted = true
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
self.context.saveAsynchronouslyWithCompletion { (hasChanges, error) -> Void in
|
||||
|
||||
completion(hasChanges, error)
|
||||
self.result = (hasChanges, error)
|
||||
group.leave()
|
||||
}
|
||||
self.context.saveAsynchronously(
|
||||
sourceIdentifier: self.sourceIdentifier,
|
||||
completion: { (hasChanges, error) -> Void in
|
||||
|
||||
completion(hasChanges, error)
|
||||
self.result = (hasChanges, error)
|
||||
group.leave()
|
||||
}
|
||||
)
|
||||
group.wait()
|
||||
self.context.reset()
|
||||
}
|
||||
|
||||
@@ -412,6 +412,11 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
|
||||
// MARK: 3rd Party Utilities
|
||||
|
||||
/**
|
||||
An arbitrary value that identifies the source of this transaction. Callers of the transaction can provide this value through the `DataStack.perform(...)` methods.
|
||||
*/
|
||||
public let sourceIdentifier: Any?
|
||||
|
||||
/**
|
||||
Allow external libraries to store custom data in the transaction. App code should rarely have a need for this.
|
||||
```
|
||||
@@ -435,7 +440,13 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
internal var isCommitted = false
|
||||
internal var result: (hasChanges: Bool, error: CoreStoreError?)?
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, supportsUndo: Bool, bypassesQueueing: Bool) {
|
||||
internal init(
|
||||
mainContext: NSManagedObjectContext,
|
||||
queue: DispatchQueue,
|
||||
supportsUndo: Bool,
|
||||
bypassesQueueing: Bool,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
|
||||
queue == .main
|
||||
@@ -446,6 +457,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
self.context = context
|
||||
self.supportsUndo = supportsUndo
|
||||
self.bypassesQueueing = bypassesQueueing
|
||||
self.sourceIdentifier = sourceIdentifier
|
||||
|
||||
context.parentTransaction = self
|
||||
context.isTransactionContext = true
|
||||
|
||||
@@ -49,7 +49,10 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction, CoreStor
|
||||
|
||||
return bridge(error) {
|
||||
|
||||
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
|
||||
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(
|
||||
waitForMerge: true,
|
||||
sourceIdentifier: nil
|
||||
) {
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -46,21 +46,24 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
|
||||
@objc
|
||||
public func commitWithSuccess(_ success: (() -> Void)?, _ failure: ((CSError) -> Void)?) {
|
||||
|
||||
self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (_, error) in
|
||||
|
||||
defer {
|
||||
|
||||
withExtendedLifetime(self, {})
|
||||
self.bridgeToSwift.context.saveAsynchronously(
|
||||
sourceIdentifier: nil,
|
||||
completion: { (_, error) in
|
||||
|
||||
defer {
|
||||
|
||||
withExtendedLifetime(self, {})
|
||||
}
|
||||
if let error = error {
|
||||
|
||||
failure?(error.bridgeToObjectiveC)
|
||||
}
|
||||
else {
|
||||
|
||||
success?()
|
||||
}
|
||||
}
|
||||
if let error = error {
|
||||
|
||||
failure?(error.bridgeToObjectiveC)
|
||||
}
|
||||
else {
|
||||
|
||||
success?()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,10 +74,13 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
|
||||
*/
|
||||
@objc
|
||||
public func commitAndWait(error: NSErrorPointer) -> Bool {
|
||||
|
||||
|
||||
return bridge(error) {
|
||||
|
||||
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
|
||||
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(
|
||||
waitForMerge: true,
|
||||
sourceIdentifier: nil
|
||||
) {
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -585,7 +585,7 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
|
||||
|
||||
return NSFetchIndexDescription.init(
|
||||
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "-"))",
|
||||
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "_"))",
|
||||
elements: compoundIndexes.map { (keyPath) in
|
||||
|
||||
return NSFetchIndexElementDescription(
|
||||
|
||||
@@ -35,12 +35,18 @@ extension DataStack {
|
||||
Performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `.success(T)` in the `completion`'s `Result<T>`. Any errors thrown from inside the `task` will be reported as `.failure(CoreStoreError)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
|
||||
|
||||
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- parameter completion: the closure executed after the save completes. The `Result` argument of the closure will either wrap the return value of `task`, or any uncaught errors thrown from within `task`. Cancelled `task`s will be indicated by `.failure(error: CoreStoreError.userCancelled)`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
|
||||
*/
|
||||
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void) {
|
||||
public func perform<T>(
|
||||
asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T,
|
||||
sourceIdentifier: Any? = nil,
|
||||
completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void
|
||||
) {
|
||||
|
||||
self.perform(
|
||||
asynchronous: task,
|
||||
sourceIdentifier: sourceIdentifier,
|
||||
success: { completion(.success($0)) },
|
||||
failure: { completion(.failure($0)) }
|
||||
)
|
||||
@@ -50,14 +56,21 @@ extension DataStack {
|
||||
Performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
|
||||
|
||||
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- parameter success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`.
|
||||
- parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
|
||||
*/
|
||||
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
|
||||
public func perform<T>(
|
||||
asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T,
|
||||
sourceIdentifier: Any? = nil,
|
||||
success: @escaping (T) -> Void,
|
||||
failure: @escaping (CoreStoreError) -> Void
|
||||
) {
|
||||
|
||||
let transaction = AsynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue
|
||||
queue: self.childTransactionQueue,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
transaction.transactionQueue.cs_async {
|
||||
|
||||
@@ -99,14 +112,20 @@ extension DataStack {
|
||||
|
||||
- parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
|
||||
- returns: the value returned from `task`
|
||||
*/
|
||||
public func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
|
||||
public func perform<T>(
|
||||
synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T),
|
||||
waitForAllObservers: Bool = true,
|
||||
sourceIdentifier: Any? = nil
|
||||
) throws -> T {
|
||||
|
||||
let transaction = SynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue
|
||||
queue: self.childTransactionQueue,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
return try transaction.transactionQueue.cs_sync {
|
||||
|
||||
@@ -141,15 +160,20 @@ extension DataStack {
|
||||
/**
|
||||
Begins a non-contiguous transaction where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- parameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public func beginUnsafe(supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
public func beginUnsafe(
|
||||
supportsUndo: Bool = false,
|
||||
sourceIdentifier: Any? = nil
|
||||
) -> UnsafeDataTransaction {
|
||||
|
||||
return UnsafeDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: DispatchQueue.serial("com.coreStore.dataStack.unsafeTransactionQueue", qos: .userInitiated),
|
||||
supportsUndo: supportsUndo
|
||||
supportsUndo: supportsUndo,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,10 +116,10 @@ extension DiffableDataSource {
|
||||
target.reload(
|
||||
using: changeset,
|
||||
animated: animatingDifferences,
|
||||
setData: setSections
|
||||
setData: setSections,
|
||||
completion: completion
|
||||
)
|
||||
},
|
||||
completion: completion
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -148,10 +148,21 @@ extension DiffableDataSource {
|
||||
target.reload(
|
||||
using: changeset,
|
||||
animated: animatingDifferences,
|
||||
setData: setSections
|
||||
setData: setSections,
|
||||
completion: completion
|
||||
)
|
||||
},
|
||||
completion: completion
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new empty `ListSnapshot` suitable for building custom lists inside subclass implementations of `apply(_:animatingDifferences:completion:)`.
|
||||
*/
|
||||
public func makeEmptySnapshot() -> ListSnapshot<O> {
|
||||
|
||||
return .init(
|
||||
diffableSnapshot: .init(),
|
||||
context: self.dataStack.unsafeContext()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,12 @@ extension DiffableDataSource {
|
||||
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
|
||||
*/
|
||||
@nonobjc
|
||||
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
|
||||
public init(
|
||||
collectionView: NSCollectionView,
|
||||
dataStack: DataStack,
|
||||
itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?,
|
||||
supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }
|
||||
) {
|
||||
|
||||
self.itemProvider = itemProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
@@ -97,19 +102,27 @@ extension DiffableDataSource {
|
||||
// MARK: - NSCollectionViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
|
||||
public dynamic func numberOfSections(
|
||||
in collectionView: NSCollectionView
|
||||
) -> Int {
|
||||
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
public dynamic func collectionView(
|
||||
_ collectionView: NSCollectionView,
|
||||
numberOfItemsInSection section: Int
|
||||
) -> Int {
|
||||
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
open dynamic func collectionView(
|
||||
_ collectionView: NSCollectionView,
|
||||
itemForRepresentedObjectAt indexPath: IndexPath
|
||||
) -> NSCollectionViewItem {
|
||||
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
|
||||
@@ -127,7 +140,11 @@ extension DiffableDataSource {
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
|
||||
open dynamic func collectionView(
|
||||
_ collectionView: NSCollectionView,
|
||||
viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind,
|
||||
at indexPath: IndexPath
|
||||
) -> NSView {
|
||||
|
||||
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
|
||||
|
||||
@@ -207,9 +224,9 @@ extension DiffableDataSource {
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
|
||||
|
||||
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
|
||||
self.base?.animator().performBatchUpdates(updates, completionHandler: { _ in completion() })
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
@@ -83,7 +83,12 @@ extension DiffableDataSource {
|
||||
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
|
||||
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
|
||||
*/
|
||||
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
|
||||
public init(
|
||||
collectionView: UICollectionView,
|
||||
dataStack: DataStack,
|
||||
cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?,
|
||||
supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }
|
||||
) {
|
||||
|
||||
self.cellProvider = cellProvider
|
||||
self.supplementaryViewProvider = supplementaryViewProvider
|
||||
@@ -97,19 +102,30 @@ extension DiffableDataSource {
|
||||
// MARK: - UICollectionViewDataSource
|
||||
|
||||
@objc
|
||||
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
@MainActor
|
||||
public dynamic func numberOfSections(
|
||||
in collectionView: UICollectionView
|
||||
) -> Int {
|
||||
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
@MainActor
|
||||
public dynamic func collectionView(
|
||||
_ collectionView: UICollectionView,
|
||||
numberOfItemsInSection section: Int
|
||||
) -> Int {
|
||||
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
@MainActor
|
||||
open dynamic func collectionView(
|
||||
_ collectionView: UICollectionView,
|
||||
cellForItemAt indexPath: IndexPath
|
||||
) -> UICollectionViewCell {
|
||||
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
|
||||
@@ -127,7 +143,12 @@ extension DiffableDataSource {
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
|
||||
@MainActor
|
||||
open dynamic func collectionView(
|
||||
_ collectionView: UICollectionView,
|
||||
viewForSupplementaryElementOfKind kind: String,
|
||||
at indexPath: IndexPath
|
||||
) -> UICollectionReusableView {
|
||||
|
||||
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
|
||||
|
||||
@@ -207,9 +228,9 @@ extension DiffableDataSource {
|
||||
self.base?.moveItem(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
|
||||
|
||||
self.base?.performBatchUpdates(updates, completion: nil)
|
||||
self.base?.performBatchUpdates(updates, completion: { _ in completion() })
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
@@ -82,7 +82,11 @@ extension DiffableDataSource {
|
||||
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
|
||||
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
|
||||
*/
|
||||
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
|
||||
public init(
|
||||
tableView: UITableView,
|
||||
dataStack: DataStack,
|
||||
cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?
|
||||
) {
|
||||
|
||||
self.cellProvider = cellProvider
|
||||
super.init(target: .init(tableView), dataStack: dataStack)
|
||||
@@ -102,31 +106,48 @@ extension DiffableDataSource {
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
@objc
|
||||
@MainActor
|
||||
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return self.numberOfSections()
|
||||
}
|
||||
|
||||
@objc
|
||||
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
@MainActor
|
||||
public dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
numberOfRowsInSection section: Int
|
||||
) -> Int {
|
||||
|
||||
return self.numberOfItems(inSection: section) ?? 0
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
@MainActor
|
||||
open dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
titleForHeaderInSection section: Int
|
||||
) -> String? {
|
||||
|
||||
return self.sectionID(for: section)
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
@MainActor
|
||||
open dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
titleForFooterInSection section: Int
|
||||
) -> String? {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
@MainActor
|
||||
open dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
cellForRowAt indexPath: IndexPath
|
||||
) -> UITableViewCell {
|
||||
|
||||
guard let objectID = self.itemID(for: indexPath) else {
|
||||
|
||||
@@ -144,28 +165,49 @@ extension DiffableDataSource {
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
@MainActor
|
||||
open dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
canEditRowAt indexPath: IndexPath
|
||||
) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
|
||||
@MainActor
|
||||
open dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
editingStyleForRowAt indexPath: IndexPath
|
||||
) -> UITableViewCell.EditingStyle {
|
||||
|
||||
return .delete
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
|
||||
@MainActor
|
||||
open dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
commit editingStyle: UITableViewCell.EditingStyle,
|
||||
forRowAt indexPath: IndexPath
|
||||
) {}
|
||||
|
||||
@objc
|
||||
open dynamic func sectionIndexTitles(for tableView: UITableView) -> [String]? {
|
||||
@MainActor
|
||||
open dynamic func sectionIndexTitles(
|
||||
for tableView: UITableView
|
||||
) -> [String]? {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc
|
||||
open dynamic func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
||||
@MainActor
|
||||
open dynamic func tableView(
|
||||
_ tableView: UITableView,
|
||||
sectionForSectionIndexTitle title: String,
|
||||
at index: Int
|
||||
) -> Int {
|
||||
|
||||
return index
|
||||
}
|
||||
@@ -241,13 +283,13 @@ extension DiffableDataSource {
|
||||
self.base?.moveRow(at: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
|
||||
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
|
||||
|
||||
guard let base = self.base else {
|
||||
|
||||
return
|
||||
}
|
||||
base.performBatchUpdates(updates)
|
||||
base.performBatchUpdates(updates, completion: { _ in completion() })
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
@@ -98,7 +98,7 @@ public protocol DiffableDataSourceTarget {
|
||||
/**
|
||||
Animates multiple insert, delete, reload, and move operations as a group.
|
||||
*/
|
||||
func performBatchUpdates(updates: () -> Void, animated: Bool)
|
||||
func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void)
|
||||
|
||||
/**
|
||||
Reloads all sections and items.
|
||||
@@ -114,9 +114,15 @@ extension DiffableDataSource.Target {
|
||||
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
|
||||
animated: Bool,
|
||||
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
|
||||
setData: (C) -> Void
|
||||
setData: (C) -> Void,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
|
||||
let group = DispatchGroup()
|
||||
defer {
|
||||
|
||||
group.notify(queue: .main, execute: completion)
|
||||
}
|
||||
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
|
||||
|
||||
setData(data)
|
||||
@@ -133,6 +139,7 @@ extension DiffableDataSource.Target {
|
||||
self.reloadData()
|
||||
return
|
||||
}
|
||||
group.enter()
|
||||
self.performBatchUpdates(
|
||||
updates: {
|
||||
|
||||
@@ -206,7 +213,8 @@ extension DiffableDataSource.Target {
|
||||
)
|
||||
}
|
||||
},
|
||||
animated: animated
|
||||
animated: animated,
|
||||
completion: group.leave
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,19 +44,30 @@ internal protocol DiffableDataSourceSnapshotProtocol {
|
||||
func indexOfItem(_ identifier: NSManagedObjectID) -> Int?
|
||||
func indexOfSection(_ identifier: String) -> Int?
|
||||
|
||||
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?)
|
||||
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID)
|
||||
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID)
|
||||
mutating func deleteItems(_ identifiers: [NSManagedObjectID])
|
||||
mutating func appendItems<C: Collection>(_ identifiers: C, toSection sectionIdentifier: String?) where C.Element == NSManagedObjectID
|
||||
mutating func insertItems<C: Collection>(_ identifiers: C, beforeItem beforeIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID
|
||||
mutating func insertItems<C: Collection>(_ identifiers: C, afterItem afterIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID
|
||||
mutating func deleteItems<C: Collection>(_ identifiers: C) where C.Element == 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: [String])
|
||||
mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String)
|
||||
mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String)
|
||||
mutating func deleteSections(_ identifiers: [String])
|
||||
mutating func reloadItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID
|
||||
mutating func appendSections<C: Collection>(_ identifiers: C) where C.Element == String
|
||||
mutating func insertSections<C: Collection>(_ identifiers: C, beforeSection toIdentifier: String) where C.Element == String
|
||||
mutating func insertSections<C: Collection>(_ identifiers: C, afterSection toIdentifier: String) where C.Element == String
|
||||
mutating func deleteSections<C: Collection>(_ identifiers: C) where C.Element == String
|
||||
mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String)
|
||||
mutating func moveSection(_ identifier: String, afterSection toIdentifier: String)
|
||||
mutating func reloadSections(_ identifiers: [String])
|
||||
mutating func reloadSections<C: Collection>(_ identifiers: C) where C.Element == String
|
||||
|
||||
mutating func unsafeAppendItems<C: Collection>(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID
|
||||
mutating func unsafeInsertItems<C: Collection>(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID
|
||||
mutating func unsafeDeleteItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath
|
||||
mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath)
|
||||
mutating func unsafeReloadItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath
|
||||
mutating func unsafeInsertSections<C: Collection>(_ identifiers: C, at sectionIndex: Int) where C.Element == String
|
||||
mutating func unsafeDeleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int
|
||||
mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int)
|
||||
mutating func unsafeReloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ extension Internals {
|
||||
|
||||
internal final class Closure<T, U> {
|
||||
|
||||
// MARK: FilePrivate
|
||||
// MARK: Internal
|
||||
|
||||
internal typealias Arguments = T
|
||||
internal typealias Result = U
|
||||
|
||||
internal init(_ closure: @escaping (T) -> U) {
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ extension Internals {
|
||||
self.structure.insert(itemIDs: identifiers, after: afterIdentifier)
|
||||
}
|
||||
|
||||
mutating func deleteItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
|
||||
mutating func deleteItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID {
|
||||
|
||||
self.structure.remove(itemIDs: identifiers)
|
||||
}
|
||||
@@ -220,7 +220,7 @@ extension Internals {
|
||||
self.structure.move(itemID: identifier, after: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func reloadItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
|
||||
mutating func reloadItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID {
|
||||
|
||||
self.structure.update(itemIDs: identifiers)
|
||||
}
|
||||
@@ -240,7 +240,7 @@ extension Internals {
|
||||
self.structure.insert(sectionIDs: identifiers, after: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func deleteSections<S: Sequence>(_ identifiers: S) where S.Element == String {
|
||||
mutating func deleteSections<C: Collection>(_ identifiers: C) where C.Element == String {
|
||||
|
||||
self.structure.remove(sectionIDs: identifiers)
|
||||
}
|
||||
@@ -255,10 +255,55 @@ extension Internals {
|
||||
self.structure.move(sectionID: identifier, after: toIdentifier)
|
||||
}
|
||||
|
||||
mutating func reloadSections<S: Sequence>(_ identifiers: S) where S.Element == String {
|
||||
mutating func reloadSections<C: Collection>(_ identifiers: C) where C.Element == String {
|
||||
|
||||
self.structure.update(sectionIDs: identifiers)
|
||||
}
|
||||
|
||||
mutating func unsafeAppendItems<C: Collection>(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID {
|
||||
|
||||
self.structure.unsafeAppend(identifiers, toSectionAt: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func unsafeInsertItems<C: Collection>(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID {
|
||||
|
||||
self.structure.unsafeInsert(itemIDs: identifiers, at: indexPath)
|
||||
}
|
||||
|
||||
mutating func unsafeDeleteItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath {
|
||||
|
||||
self.structure.unsafeRemove(itemsAt: indexPaths)
|
||||
}
|
||||
|
||||
mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) {
|
||||
|
||||
self.structure.unsafeMove(itemAt: indexPath, to: newIndexPath)
|
||||
}
|
||||
|
||||
mutating func unsafeReloadItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath {
|
||||
|
||||
self.structure.unsafeUpdate(itemsAt: indexPaths)
|
||||
}
|
||||
|
||||
mutating func unsafeInsertSections<C: Collection>(_ identifiers: C, at sectionIndex: Int) where C.Element == String {
|
||||
|
||||
self.structure.unsafeInsert(identifiers, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func unsafeDeleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
|
||||
|
||||
self.structure.unsafeRemove(sectionsAt: sectionIndices)
|
||||
}
|
||||
|
||||
mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int) {
|
||||
|
||||
self.structure.unsafeMove(sectionAt: sectionIndex, to: newSectionIndex)
|
||||
}
|
||||
|
||||
mutating func unsafeReloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
|
||||
|
||||
self.structure.unsafeUpdate(sectionsAt: sectionIndices)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
@@ -432,13 +477,23 @@ extension Internals {
|
||||
}
|
||||
return self.sections[sectionIndex].elements.map({ $0.differenceIdentifier })
|
||||
}
|
||||
|
||||
func unsafeItem(at indexPath: IndexPath) -> NSManagedObjectID {
|
||||
|
||||
return self.sections[indexPath.section]
|
||||
.elements[indexPath.item]
|
||||
.differenceIdentifier
|
||||
}
|
||||
|
||||
func section(containing itemID: NSManagedObjectID) -> String? {
|
||||
|
||||
return self.itemPositionMap(itemID)?.section.differenceIdentifier
|
||||
}
|
||||
|
||||
mutating func append<C: Collection>(itemIDs: C, to sectionID: String?) where C.Element == NSManagedObjectID {
|
||||
mutating func append<C: Collection>(
|
||||
itemIDs: C,
|
||||
to sectionID: String?
|
||||
) where C.Element == NSManagedObjectID {
|
||||
|
||||
let index: Array<Section>.Index
|
||||
if let sectionID = sectionID {
|
||||
@@ -461,8 +516,30 @@ extension Internals {
|
||||
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
|
||||
self.sections[index].elements.append(contentsOf: items)
|
||||
}
|
||||
|
||||
mutating func unsafeAppend<C: Collection>(
|
||||
_ itemIDs: C,
|
||||
toSectionAt sectionIndex: Int?
|
||||
) where C.Element == NSManagedObjectID {
|
||||
|
||||
let index: Array<Section>.Index
|
||||
if let sectionIndex = sectionIndex {
|
||||
|
||||
mutating func insert<C: Collection>(itemIDs: C, before beforeItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
|
||||
index = sectionIndex
|
||||
}
|
||||
else {
|
||||
|
||||
let section = self.sections
|
||||
index = section.index(before: section.endIndex)
|
||||
}
|
||||
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
|
||||
self.sections[index].elements.append(contentsOf: items)
|
||||
}
|
||||
|
||||
mutating func insert<C: Collection>(
|
||||
itemIDs: C,
|
||||
before beforeItemID: NSManagedObjectID
|
||||
) where C.Element == NSManagedObjectID {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
|
||||
|
||||
@@ -473,7 +550,10 @@ extension Internals {
|
||||
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
|
||||
}
|
||||
|
||||
mutating func insert<C: Collection>(itemIDs: C, after afterItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
|
||||
mutating func insert<C: Collection>(
|
||||
itemIDs: C,
|
||||
after afterItemID: NSManagedObjectID
|
||||
) where C.Element == NSManagedObjectID {
|
||||
|
||||
guard let itemPosition = self.itemPositionMap(afterItemID) else {
|
||||
|
||||
@@ -485,6 +565,16 @@ extension Internals {
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(contentsOf: items, at: itemIndex)
|
||||
}
|
||||
|
||||
mutating func unsafeInsert<C: Collection>(
|
||||
itemIDs: C,
|
||||
at indexPath: IndexPath
|
||||
) where C.Element == NSManagedObjectID {
|
||||
|
||||
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
|
||||
self.sections[indexPath.section].elements
|
||||
.insert(contentsOf: items, at: indexPath.item)
|
||||
}
|
||||
|
||||
mutating func remove<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
|
||||
|
||||
@@ -508,6 +598,23 @@ extension Internals {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func unsafeRemove<S: Sequence>(itemsAt indexPaths: S) where S.Element == IndexPath {
|
||||
|
||||
var removeIndexSetMap: [Int: IndexSet] = [:]
|
||||
for indexPath in indexPaths {
|
||||
|
||||
removeIndexSetMap[indexPath.section, default: []]
|
||||
.insert(indexPath.item)
|
||||
}
|
||||
for (sectionIndex, removeIndexSet) in removeIndexSetMap {
|
||||
|
||||
for range in removeIndexSet.rangeView.reversed() {
|
||||
|
||||
self.sections[sectionIndex].elements.removeSubrange(range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func removeAllItems() {
|
||||
|
||||
@@ -522,7 +629,10 @@ extension Internals {
|
||||
self.sections.removeAll(where: { $0.elements.isEmpty })
|
||||
}
|
||||
|
||||
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
|
||||
mutating func move(
|
||||
itemID: NSManagedObjectID,
|
||||
before beforeItemID: NSManagedObjectID
|
||||
) {
|
||||
|
||||
guard let removed = self.remove(itemID: itemID) else {
|
||||
|
||||
@@ -536,7 +646,10 @@ extension Internals {
|
||||
.insert(removed, at: itemPosition.itemRelativeIndex)
|
||||
}
|
||||
|
||||
mutating func move(itemID: NSManagedObjectID, after afterItemID: NSManagedObjectID) {
|
||||
mutating func move(
|
||||
itemID: NSManagedObjectID,
|
||||
after afterItemID: NSManagedObjectID
|
||||
) {
|
||||
|
||||
guard let removed = self.remove(itemID: itemID) else {
|
||||
|
||||
@@ -551,6 +664,17 @@ extension Internals {
|
||||
self.sections[itemPosition.sectionIndex].elements
|
||||
.insert(removed, at: itemIndex)
|
||||
}
|
||||
|
||||
mutating func unsafeMove(
|
||||
itemAt indexPath: IndexPath,
|
||||
to newIndexPath: IndexPath
|
||||
) {
|
||||
|
||||
let itemID = self.sections[indexPath.section].elements
|
||||
.remove(at: indexPath.item)
|
||||
self.sections[newIndexPath.section].elements
|
||||
.insert(itemID, at: newIndexPath.item)
|
||||
}
|
||||
|
||||
mutating func update<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
|
||||
|
||||
@@ -568,6 +692,18 @@ extension Internals {
|
||||
}
|
||||
self.reloadedItems.formUnion(newItemIDs)
|
||||
}
|
||||
|
||||
mutating func unsafeUpdate<S: Sequence>(itemsAt indexPaths: S) where S.Element == IndexPath {
|
||||
|
||||
var newItemIDs: Set<NSManagedObjectID> = []
|
||||
for indexPath in indexPaths {
|
||||
|
||||
self.sections[indexPath.section]
|
||||
.elements[indexPath.item].isReloaded = true
|
||||
newItemIDs.insert(self.unsafeItem(at: indexPath))
|
||||
}
|
||||
self.reloadedItems.formUnion(newItemIDs)
|
||||
}
|
||||
|
||||
mutating func append<C: Collection>(sectionIDs: C) where C.Element == String {
|
||||
|
||||
@@ -582,7 +718,10 @@ extension Internals {
|
||||
self.sections.append(contentsOf: newSections)
|
||||
}
|
||||
|
||||
mutating func insert<C: Collection>(sectionIDs: C, before beforeSectionID: String) where C.Element == String {
|
||||
mutating func insert<C: Collection>(
|
||||
sectionIDs: C,
|
||||
before beforeSectionID: String
|
||||
) where C.Element == String {
|
||||
|
||||
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
|
||||
|
||||
@@ -599,7 +738,10 @@ extension Internals {
|
||||
self.sections.insert(contentsOf: newSections, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func insert<C: Collection>(sectionIDs: C, after afterSectionID: String) where C.Element == String {
|
||||
mutating func insert<C: Collection>(
|
||||
sectionIDs: C,
|
||||
after afterSectionID: String
|
||||
) where C.Element == String {
|
||||
|
||||
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
|
||||
|
||||
@@ -616,6 +758,22 @@ extension Internals {
|
||||
}
|
||||
self.sections.insert(contentsOf: newSections, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func unsafeInsert<C: Collection>(
|
||||
_ sectionIDs: C,
|
||||
at sectionIndex: Int
|
||||
) where C.Element == String {
|
||||
|
||||
let sectionIndexTransformer = self.sectionIndexTransformer
|
||||
let newSections = sectionIDs.lazy.map {
|
||||
|
||||
return Section(
|
||||
differenceIdentifier: $0,
|
||||
indexTitle: sectionIndexTransformer($0)
|
||||
)
|
||||
}
|
||||
self.sections.insert(contentsOf: newSections, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func remove<S: Sequence>(sectionIDs: S) where S.Element == String {
|
||||
|
||||
@@ -624,6 +782,16 @@ extension Internals {
|
||||
self.remove(sectionID: sectionID)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func unsafeRemove<S: Sequence>(
|
||||
sectionsAt sectionIndices: S
|
||||
) where S.Element == Int {
|
||||
|
||||
for sectionIndex in sectionIndices.sorted(by: >) {
|
||||
|
||||
self.sections.remove(at: sectionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func move(sectionID: String, before beforeSectionID: String) {
|
||||
|
||||
@@ -651,8 +819,21 @@ extension Internals {
|
||||
let sectionIndex = self.sections.index(after: beforeIndex)
|
||||
self.sections.insert(removed, at: sectionIndex)
|
||||
}
|
||||
|
||||
mutating func unsafeMove(
|
||||
sectionAt sectionIndex: Int,
|
||||
to newSectionIndex: Int
|
||||
) {
|
||||
|
||||
self.sections.move(
|
||||
fromOffsets: .init(integer: sectionIndex),
|
||||
toOffset: newSectionIndex
|
||||
)
|
||||
}
|
||||
|
||||
mutating func update<S: Sequence>(sectionIDs: S) where S.Element == String {
|
||||
mutating func update<S: Sequence>(
|
||||
sectionIDs: S
|
||||
) where S.Element == String {
|
||||
|
||||
for sectionID in sectionIDs {
|
||||
|
||||
@@ -663,6 +844,16 @@ extension Internals {
|
||||
self.sections[sectionIndex].isReloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
mutating func unsafeUpdate<S: Sequence>(
|
||||
sectionsAt sectionIndices: S
|
||||
) where S.Element == Int {
|
||||
|
||||
for sectionIndex in sectionIndices {
|
||||
|
||||
self.sections[sectionIndex].isReloaded = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -59,16 +59,14 @@ extension Internals {
|
||||
Target,
|
||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
) -> Void,
|
||||
completion: @escaping () -> Void
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
self.apply(
|
||||
.init(),
|
||||
target: target,
|
||||
animatingDifferences: animatingDifferences,
|
||||
performUpdates: performUpdates,
|
||||
completion: completion
|
||||
performUpdates: performUpdates
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,8 +78,7 @@ extension Internals {
|
||||
Target,
|
||||
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
|
||||
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
|
||||
) -> Void,
|
||||
completion: @escaping () -> Void
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
self.dispatcher.dispatch { [weak self] in
|
||||
@@ -112,7 +109,6 @@ extension Internals {
|
||||
#if canImport(QuartzCore)
|
||||
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock(completion)
|
||||
|
||||
if !animatingDifferences {
|
||||
|
||||
@@ -122,11 +118,9 @@ extension Internals {
|
||||
|
||||
CATransaction.commit()
|
||||
|
||||
|
||||
#else
|
||||
|
||||
performDiffingUpdates()
|
||||
completion()
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -43,7 +43,10 @@ internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
|
||||
|
||||
var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? { get }
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot)
|
||||
func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -33,13 +33,28 @@ internal protocol FetchedResultsControllerHandler: AnyObject {
|
||||
|
||||
var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? { get }
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeObject anObject: Any, atIndexPath indexPath: IndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
|
||||
func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeObject anObject: Any,
|
||||
atIndexPath indexPath: IndexPath?,
|
||||
forChangeType type: NSFetchedResultsChangeType,
|
||||
newIndexPath: IndexPath?
|
||||
)
|
||||
|
||||
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
|
||||
func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
atIndex sectionIndex: Int,
|
||||
forChangeType type: NSFetchedResultsChangeType
|
||||
)
|
||||
|
||||
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
func controllerWillChangeContent(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>
|
||||
)
|
||||
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
func controllerDidChangeContent(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +117,6 @@ extension Internals {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.handler?.controllerDidChangeContent(controller)
|
||||
}
|
||||
|
||||
|
||||
@@ -393,19 +393,31 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
observer,
|
||||
willChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillChange(monitor)
|
||||
observer.listMonitorWillChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidChange(monitor)
|
||||
observer.listMonitorDidChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
willRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillRefetch(monitor)
|
||||
observer.listMonitorWillRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidRefetch(monitor)
|
||||
observer.listMonitorDidRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -428,38 +440,71 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
observer,
|
||||
willChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillChange(monitor)
|
||||
observer.listMonitorWillChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidChange(monitor)
|
||||
observer.listMonitorDidChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
willRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillRefetch(monitor)
|
||||
observer.listMonitorWillRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidRefetch(monitor)
|
||||
observer.listMonitorDidRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
self.registerObserver(
|
||||
observer,
|
||||
didInsertObject: { (observer, monitor, object, toIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didInsertObject: object, toIndexPath: toIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didInsertObject: object,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didDeleteObject: object, fromIndexPath: fromIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didUpdateObject: object, atIndexPath: atIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: atIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didMoveObject: object, fromIndexPath: fromIndexPath, toIndexPath: toIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didMoveObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -482,49 +527,92 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
observer,
|
||||
willChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillChange(monitor)
|
||||
observer.listMonitorWillChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didChange: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidChange(monitor)
|
||||
observer.listMonitorDidChange(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
willRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorWillRefetch(monitor)
|
||||
observer.listMonitorWillRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didRefetch: { (observer, monitor) in
|
||||
|
||||
observer.listMonitorDidRefetch(monitor)
|
||||
observer.listMonitorDidRefetch(
|
||||
monitor,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
self.registerObserver(
|
||||
observer,
|
||||
didInsertObject: { (observer, monitor, object, toIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didInsertObject: object, toIndexPath: toIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didInsertObject: object,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didDeleteObject: object, fromIndexPath: fromIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didUpdateObject: object, atIndexPath: atIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: atIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
||||
|
||||
observer.listMonitor(monitor, didMoveObject: object, fromIndexPath: fromIndexPath, toIndexPath: toIndexPath)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didMoveObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
toIndexPath: toIndexPath,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
self.registerObserver(
|
||||
observer,
|
||||
didInsertSection: { (observer, monitor, sectionInfo, toIndex) in
|
||||
|
||||
observer.listMonitor(monitor, didInsertSection: sectionInfo, toSectionIndex: toIndex)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didInsertSection: sectionInfo,
|
||||
toSectionIndex: toIndex,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didDeleteSection: { (observer, monitor, sectionInfo, fromIndex) in
|
||||
|
||||
observer.listMonitor(monitor, didDeleteSection: sectionInfo, fromSectionIndex: fromIndex)
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didDeleteSection: sectionInfo,
|
||||
fromSectionIndex: fromIndex,
|
||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -555,11 +643,18 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
|
||||
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
||||
*/
|
||||
public func refetch(_ fetchClauses: FetchClause...) {
|
||||
public func refetch(
|
||||
_ fetchClauses: FetchClause...,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.refetch(fetchClauses)
|
||||
self.refetch(
|
||||
fetchClauses,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -568,14 +663,21 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
|
||||
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
||||
*/
|
||||
public func refetch(_ fetchClauses: [FetchClause]) {
|
||||
public func refetch(
|
||||
_ fetchClauses: [FetchClause],
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.refetch { (fetchRequest) in
|
||||
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
}
|
||||
self.refetch(
|
||||
{ (fetchRequest) in
|
||||
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
},
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -627,7 +729,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal convenience init(dataStack: DataStack, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
|
||||
internal convenience init(
|
||||
dataStack: DataStack,
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: dataStack.mainContext,
|
||||
@@ -639,7 +746,13 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal convenience init(dataStack: DataStack, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<O>) -> Void) {
|
||||
internal convenience init(
|
||||
dataStack: DataStack,
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: dataStack.mainContext,
|
||||
@@ -651,7 +764,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
|
||||
internal convenience init(
|
||||
unsafeTransaction: UnsafeDataTransaction,
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: unsafeTransaction.context,
|
||||
@@ -663,7 +781,13 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<O>) -> Void) {
|
||||
internal convenience init(
|
||||
unsafeTransaction: UnsafeDataTransaction,
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: @escaping (ListMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: unsafeTransaction.context,
|
||||
@@ -675,7 +799,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>) -> Void) {
|
||||
internal func registerChangeNotification(
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (_ monitor: ListMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
Internals.NotificationObserver(
|
||||
@@ -695,7 +824,16 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>, _ object: O, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) {
|
||||
internal func registerObjectNotification(
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ indexPath: IndexPath?,
|
||||
_ newIndexPath: IndexPath?
|
||||
) -> Void) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
Internals.NotificationObserver(
|
||||
@@ -722,7 +860,16 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) {
|
||||
internal func registerSectionNotification(
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||
_ sectionIndex: Int
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
Internals.NotificationObserver(
|
||||
@@ -745,7 +892,24 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void) {
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
_ observer: U,
|
||||
willChange: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void,
|
||||
didChange: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void,
|
||||
willRefetch: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void,
|
||||
didRefetch: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>
|
||||
) -> Void) {
|
||||
|
||||
Internals.assert(
|
||||
Thread.isMainThread,
|
||||
@@ -805,7 +969,33 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) {
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
_ observer: U,
|
||||
didInsertObject: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ toIndexPath: IndexPath
|
||||
) -> Void,
|
||||
didDeleteObject: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ fromIndexPath: IndexPath
|
||||
) -> Void,
|
||||
didUpdateObject: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ atIndexPath: IndexPath
|
||||
) -> Void,
|
||||
didMoveObject: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ object: O,
|
||||
_ fromIndexPath: IndexPath,
|
||||
_ toIndexPath: IndexPath
|
||||
) -> Void) {
|
||||
|
||||
Internals.assert(
|
||||
Thread.isMainThread,
|
||||
@@ -866,7 +1056,20 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) {
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
_ observer: U,
|
||||
didInsertSection: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||
_ toIndex: Int
|
||||
) -> Void,
|
||||
didDeleteSection: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ListMonitor<O>,
|
||||
_ sectionInfo: NSFetchedResultsSectionInfo,
|
||||
_ fromIndex: Int
|
||||
) -> Void) {
|
||||
|
||||
Internals.assert(
|
||||
Thread.isMainThread,
|
||||
@@ -922,7 +1125,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
Internals.setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteSectionKey, inObject: observer)
|
||||
}
|
||||
|
||||
internal func refetch(_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
|
||||
internal func refetch(
|
||||
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
Thread.isMainThread,
|
||||
@@ -987,10 +1193,15 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
|
||||
self.isPendingRefetch = false
|
||||
|
||||
newFetchedResultsController.managedObjectContext.saveMetadata = .init(
|
||||
isSavingSynchronously: false,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name.listMonitorDidRefetchList,
|
||||
object: self
|
||||
)
|
||||
newFetchedResultsController.managedObjectContext.saveMetadata = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1051,7 +1262,15 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedResultsControllerDelegate) {
|
||||
private static func recreateFetchedResultsController(
|
||||
context: NSManagedObjectContext,
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
) -> (
|
||||
controller: Internals.CoreStoreFetchedResultsController,
|
||||
delegate: Internals.FetchedResultsControllerDelegate
|
||||
) {
|
||||
|
||||
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
|
||||
fetchRequest.fetchLimit = 0
|
||||
@@ -1077,7 +1296,14 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
private let from: From<O>
|
||||
private let sectionBy: SectionBy<O>?
|
||||
|
||||
private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListMonitor<O>) -> Void)?) {
|
||||
private init(
|
||||
context: NSManagedObjectContext,
|
||||
transactionQueue: DispatchQueue,
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: ((ListMonitor<O>) -> Void)?
|
||||
) {
|
||||
|
||||
self.isSectioned = (sectionBy != nil)
|
||||
self.from = from
|
||||
@@ -1124,7 +1350,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
|
||||
return
|
||||
}
|
||||
self.refetch(self.applyFetchClauses)
|
||||
self.refetch(self.applyFetchClauses, sourceIdentifier: nil)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1148,7 +1374,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
|
||||
if previousStores != currentStores {
|
||||
|
||||
self.refetch(self.applyFetchClauses)
|
||||
self.refetch(self.applyFetchClauses, sourceIdentifier: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1294,7 +1520,13 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
return self.sectionByIndexTransformer
|
||||
}
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeObject anObject: Any, atIndexPath indexPath: IndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
|
||||
internal func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeObject anObject: Any,
|
||||
atIndexPath indexPath: IndexPath?,
|
||||
forChangeType type: NSFetchedResultsChangeType,
|
||||
newIndexPath: IndexPath?
|
||||
) {
|
||||
|
||||
switch type {
|
||||
|
||||
@@ -1344,7 +1576,12 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
internal func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
atIndex sectionIndex: Int,
|
||||
forChangeType type: NSFetchedResultsChangeType
|
||||
) {
|
||||
|
||||
switch type {
|
||||
|
||||
@@ -1373,7 +1610,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
internal func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
internal func controllerWillChangeContent(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>
|
||||
) {
|
||||
|
||||
self.taskGroup.enter()
|
||||
NotificationCenter.default.post(
|
||||
@@ -1382,7 +1621,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
)
|
||||
}
|
||||
|
||||
internal func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
internal func controllerDidChangeContent(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>
|
||||
) {
|
||||
|
||||
defer {
|
||||
|
||||
|
||||
@@ -51,15 +51,54 @@ public protocol ListObserver: AnyObject {
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>)
|
||||
func listMonitorWillChange(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed list occurs. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
*/
|
||||
func listMonitorWillChange(
|
||||
_ monitor: ListMonitor<ListEntityType>
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed list occurs. (Required)
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitorDidChange(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed list occurs. (Required)
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
*/
|
||||
func listMonitorDidChange(_ monitor: ListMonitor<ListEntityType>)
|
||||
func listMonitorDidChange(
|
||||
_ monitor: ListMonitor<ListEntityType>
|
||||
)
|
||||
|
||||
/**
|
||||
This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. (Optional)
|
||||
|
||||
- Important: All `ListMonitor` access between `listMonitorWillRefetch(_:)` and `listMonitorDidRefetch(_:)` will raise and assertion. The actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitorWillRefetch(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. (Optional)
|
||||
@@ -67,7 +106,21 @@ public protocol ListObserver: AnyObject {
|
||||
- Important: All `ListMonitor` access between `listMonitorWillRefetch(_:)` and `listMonitorDidRefetch(_:)` will raise and assertion. The actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
*/
|
||||
func listMonitorWillRefetch(_ monitor: ListMonitor<ListEntityType>)
|
||||
func listMonitorWillRefetch(
|
||||
_ monitor: ListMonitor<ListEntityType>
|
||||
)
|
||||
|
||||
/**
|
||||
After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. (Required)
|
||||
|
||||
- Important: When `listMonitorDidRefetch(_:)` is called it should be assumed that all `ListMonitor`'s previous data have been reset, including counts, objects, and persistent stores.
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitorDidRefetch(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. (Required)
|
||||
@@ -75,7 +128,9 @@ public protocol ListObserver: AnyObject {
|
||||
- Important: When `listMonitorDidRefetch(_:)` is called it should be assumed that all `ListMonitor`'s previous data have been reset, including counts, objects, and persistent stores.
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
*/
|
||||
func listMonitorDidRefetch(_ monitor: ListMonitor<ListEntityType>)
|
||||
func listMonitorDidRefetch(
|
||||
_ monitor: ListMonitor<ListEntityType>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -83,9 +138,45 @@ public protocol ListObserver: AnyObject {
|
||||
|
||||
extension ListObserver {
|
||||
|
||||
public func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>) { }
|
||||
public func listMonitorWillChange(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitorWillChange(monitor)
|
||||
}
|
||||
|
||||
public func listMonitorWillRefetch(_ monitor: ListMonitor<ListEntityType>) { }
|
||||
public func listMonitorWillChange(
|
||||
_ monitor: ListMonitor<ListEntityType>
|
||||
) {}
|
||||
|
||||
public func listMonitorDidChange(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitorDidChange(monitor)
|
||||
}
|
||||
|
||||
public func listMonitorWillRefetch(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitorWillRefetch(monitor)
|
||||
}
|
||||
|
||||
public func listMonitorWillRefetch(
|
||||
_ monitor: ListMonitor<ListEntityType>
|
||||
) {}
|
||||
|
||||
public func listMonitorDidRefetch(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitorDidRefetch(monitor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,8 +201,44 @@ public protocol ListObjectObserver: ListObserver {
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the inserted object
|
||||
- parameter indexPath: the new `NSIndexPath` for the inserted object
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath)
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertObject object: ListEntityType,
|
||||
toIndexPath indexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that an object was inserted to the specified `NSIndexPath` in the list. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the inserted object
|
||||
- parameter indexPath: the new `NSIndexPath` for the inserted object
|
||||
*/
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertObject object: ListEntityType,
|
||||
toIndexPath indexPath: IndexPath
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that an object was deleted from the specified `NSIndexPath` in the list. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the deleted object
|
||||
- parameter indexPath: the `NSIndexPath` for the deleted object
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteObject object: ListEntityType,
|
||||
fromIndexPath indexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that an object was deleted from the specified `NSIndexPath` in the list. (Optional)
|
||||
@@ -121,7 +248,27 @@ public protocol ListObjectObserver: ListObserver {
|
||||
- parameter object: the entity type for the deleted object
|
||||
- parameter indexPath: the `NSIndexPath` for the deleted object
|
||||
*/
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath)
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteObject object: ListEntityType,
|
||||
fromIndexPath indexPath: IndexPath
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that an object at the specified `NSIndexPath` was updated. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the updated object
|
||||
- parameter indexPath: the `NSIndexPath` for the updated object
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didUpdateObject object: ListEntityType,
|
||||
atIndexPath indexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that an object at the specified `NSIndexPath` was updated. (Optional)
|
||||
@@ -131,7 +278,29 @@ public protocol ListObjectObserver: ListObserver {
|
||||
- parameter object: the entity type for the updated object
|
||||
- parameter indexPath: the `NSIndexPath` for the updated object
|
||||
*/
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath)
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didUpdateObject object: ListEntityType,
|
||||
atIndexPath indexPath: IndexPath
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that an object's index changed. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the moved object
|
||||
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
|
||||
- parameter toIndexPath: the new `NSIndexPath` for the moved object
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didMoveObject object: ListEntityType,
|
||||
fromIndexPath: IndexPath,
|
||||
toIndexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that an object's index changed. (Optional)
|
||||
@@ -142,7 +311,12 @@ public protocol ListObjectObserver: ListObserver {
|
||||
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
|
||||
- parameter toIndexPath: the new `NSIndexPath` for the moved object
|
||||
*/
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath)
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didMoveObject object: ListEntityType,
|
||||
fromIndexPath: IndexPath,
|
||||
toIndexPath: IndexPath
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -150,13 +324,88 @@ public protocol ListObjectObserver: ListObserver {
|
||||
|
||||
extension ListObjectObserver {
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) { }
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertObject object: ListEntityType,
|
||||
toIndexPath indexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitor(
|
||||
monitor,
|
||||
didInsertObject: object,
|
||||
toIndexPath: indexPath
|
||||
)
|
||||
}
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) { }
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertObject object: ListEntityType,
|
||||
toIndexPath indexPath: IndexPath
|
||||
) {}
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath) { }
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteObject object: ListEntityType,
|
||||
fromIndexPath indexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitor(
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: indexPath
|
||||
)
|
||||
}
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath) { }
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteObject object: ListEntityType,
|
||||
fromIndexPath indexPath: IndexPath
|
||||
) {}
|
||||
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didUpdateObject object: ListEntityType,
|
||||
atIndexPath indexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: indexPath
|
||||
)
|
||||
}
|
||||
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didUpdateObject object: ListEntityType,
|
||||
atIndexPath indexPath: IndexPath
|
||||
) {}
|
||||
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didMoveObject object: ListEntityType,
|
||||
fromIndexPath: IndexPath,
|
||||
toIndexPath: IndexPath,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitor(
|
||||
monitor,
|
||||
didMoveObject: object,
|
||||
fromIndexPath: fromIndexPath,
|
||||
toIndexPath: toIndexPath
|
||||
)
|
||||
}
|
||||
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didMoveObject object: ListEntityType,
|
||||
fromIndexPath: IndexPath,
|
||||
toIndexPath: IndexPath
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,8 +431,44 @@ public protocol ListSectionObserver: ListObjectObserver {
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section
|
||||
- parameter sectionIndex: the new section index for the new section
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
toSectionIndex sectionIndex: Int,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section
|
||||
- parameter sectionIndex: the new section index for the new section
|
||||
*/
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
toSectionIndex sectionIndex: Int
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section
|
||||
- parameter sectionIndex: the previous section index for the deleted section
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
fromSectionIndex sectionIndex: Int,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index. (Optional)
|
||||
@@ -193,7 +478,11 @@ public protocol ListSectionObserver: ListObjectObserver {
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section
|
||||
- parameter sectionIndex: the previous section index for the deleted section
|
||||
*/
|
||||
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
|
||||
func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
fromSectionIndex sectionIndex: Int
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +490,43 @@ public protocol ListSectionObserver: ListObjectObserver {
|
||||
|
||||
extension ListSectionObserver {
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
toSectionIndex sectionIndex: Int,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitor(
|
||||
monitor,
|
||||
didInsertSection: sectionInfo,
|
||||
toSectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { }
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
toSectionIndex sectionIndex: Int
|
||||
) {}
|
||||
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
fromSectionIndex sectionIndex: Int,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.listMonitor(
|
||||
monitor,
|
||||
didDeleteSection: sectionInfo,
|
||||
fromSectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
|
||||
public func listMonitor(
|
||||
_ monitor: ListMonitor<ListEntityType>,
|
||||
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
fromSectionIndex sectionIndex: Int
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -135,8 +135,19 @@ extension ListPublisher {
|
||||
|
||||
func cancel() {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
self.subscriber = nil
|
||||
|
||||
if Thread.isMainThread {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
else {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,13 +76,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
/**
|
||||
A snapshot of the latest state of this list
|
||||
*/
|
||||
public fileprivate(set) var snapshot: ListSnapshot<O> = .init() {
|
||||
|
||||
didSet {
|
||||
|
||||
self.notifyObservers()
|
||||
}
|
||||
}
|
||||
public private(set) var snapshot: ListSnapshot<O> = .init()
|
||||
|
||||
|
||||
// MARK: Public (Observers)
|
||||
@@ -111,7 +105,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
|
||||
)
|
||||
self.observers.setObject(
|
||||
Internals.Closure(callback),
|
||||
Internals.Closure({ callback($0.listPublisher) }),
|
||||
forKey: observer
|
||||
)
|
||||
if notifyInitial {
|
||||
@@ -119,6 +113,44 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
callback(self)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Registers an object as an observer to be notified when changes to the `ListPublisher`'s snapshot occur.
|
||||
|
||||
To prevent retain-cycles, `ListPublisher` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:_:)` multiple times on the same observer is safe.
|
||||
|
||||
- parameter observer: an object to become owner of the specified `callback`
|
||||
- parameter notifyInitial: if `true`, the callback is executed immediately with the current publisher state. Otherwise only succeeding updates will notify the observer. Default value is `false`.
|
||||
- parameter initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
|
||||
- parameter callback: the closure to execute when changes occur
|
||||
*/
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
initialSourceIdentifier: Any? = nil,
|
||||
_ callback: @escaping (
|
||||
_ listPublisher: ListPublisher<O>,
|
||||
_ sourceIdentifier: Any?
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
Thread.isMainThread,
|
||||
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
|
||||
)
|
||||
self.observers.setObject(
|
||||
Internals.Closure(callback),
|
||||
forKey: observer
|
||||
)
|
||||
if notifyInitial {
|
||||
|
||||
callback(self, initialSourceIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Unregisters an object from receiving notifications for changes to the `ListPublisher`'s snapshot.
|
||||
@@ -149,8 +181,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
)
|
||||
```
|
||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
*/
|
||||
public func refetch<B: FetchChainableBuilderType>(_ clauseChain: B) throws where B.ObjectType == O {
|
||||
public func refetch<B: FetchChainableBuilderType>(
|
||||
_ clauseChain: B,
|
||||
sourceIdentifier: Any? = nil
|
||||
) throws where B.ObjectType == O {
|
||||
|
||||
try self.refetch(
|
||||
from: clauseChain.from,
|
||||
@@ -158,7 +194,8 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
applyFetchClauses: { (fetchRequest) in
|
||||
|
||||
clauseChain.fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
}
|
||||
},
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
@@ -173,8 +210,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
)
|
||||
```
|
||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
*/
|
||||
public func refetch<B: SectionMonitorBuilderType>(_ clauseChain: B) throws where B.ObjectType == O {
|
||||
public func refetch<B: SectionMonitorBuilderType>(
|
||||
_ clauseChain: B,
|
||||
sourceIdentifier: Any? = nil
|
||||
) throws where B.ObjectType == O {
|
||||
|
||||
try self.refetch(
|
||||
from: clauseChain.from,
|
||||
@@ -182,7 +223,8 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
applyFetchClauses: { (fetchRequest) in
|
||||
|
||||
clauseChain.fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
}
|
||||
},
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
@@ -230,7 +272,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
|
||||
internal private(set) lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext
|
||||
|
||||
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
|
||||
internal convenience init(
|
||||
dataStack: DataStack,
|
||||
from: From<ObjectType>,
|
||||
sectionBy: SectionBy<ObjectType>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: dataStack.mainContext,
|
||||
@@ -241,7 +288,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void) {
|
||||
internal convenience init(
|
||||
dataStack: DataStack,
|
||||
from: From<ObjectType>,
|
||||
sectionBy: SectionBy<ObjectType>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: dataStack.mainContext,
|
||||
@@ -252,7 +305,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
|
||||
internal convenience init(
|
||||
unsafeTransaction: UnsafeDataTransaction,
|
||||
from: From<ObjectType>,
|
||||
sectionBy: SectionBy<ObjectType>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: unsafeTransaction.context,
|
||||
@@ -263,7 +321,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void) {
|
||||
internal convenience init(
|
||||
unsafeTransaction: UnsafeDataTransaction,
|
||||
from: From<ObjectType>,
|
||||
sectionBy: SectionBy<ObjectType>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void
|
||||
) {
|
||||
|
||||
self.init(
|
||||
context: unsafeTransaction.context,
|
||||
@@ -274,7 +338,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
internal func refetch(from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) throws {
|
||||
internal func refetch(
|
||||
from: From<O>,
|
||||
sectionBy: SectionBy<O>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
sourceIdentifier: Any?
|
||||
) throws {
|
||||
|
||||
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||
context: self.fetchedResultsController.managedObjectContext,
|
||||
@@ -291,7 +360,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = (newFetchedResultsController, newFetchedResultsControllerDelegate)
|
||||
|
||||
newFetchedResultsControllerDelegate.handler = self
|
||||
|
||||
newFetchedResultsController.managedObjectContext.saveMetadata = .init(
|
||||
isSavingSynchronously: true,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
try newFetchedResultsController.performFetchFromSpecifiedStores()
|
||||
newFetchedResultsController.managedObjectContext.saveMetadata = nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -301,6 +376,20 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate typealias ObserverClosureType = Internals.Closure<(listPublisher: ListPublisher<O>, sourceIdentifier: Any?), Void>
|
||||
|
||||
fileprivate func set(
|
||||
snapshot: ListSnapshot<O>,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.snapshot = snapshot
|
||||
self.notifyObservers(sourceIdentifier: sourceIdentifier)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var query: (
|
||||
@@ -315,9 +404,17 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
private var observerForWillChangePersistentStore: Internals.NotificationObserver!
|
||||
private var observerForDidChangePersistentStore: Internals.NotificationObserver!
|
||||
|
||||
private lazy var observers: NSMapTable<AnyObject, Internals.Closure<ListPublisher<O>, Void>> = .weakToStrongObjects()
|
||||
private lazy var observers: NSMapTable<AnyObject, ObserverClosureType> = .weakToStrongObjects()
|
||||
|
||||
private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate) {
|
||||
private static func recreateFetchedResultsController(
|
||||
context: NSManagedObjectContext,
|
||||
from: From<ObjectType>,
|
||||
sectionBy: SectionBy<ObjectType>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||
) -> (
|
||||
controller: Internals.CoreStoreFetchedResultsController,
|
||||
delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate
|
||||
) {
|
||||
|
||||
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
|
||||
fetchRequest.fetchLimit = 0
|
||||
@@ -339,7 +436,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
return (fetchedResultsController, fetchedResultsControllerDelegate)
|
||||
}
|
||||
|
||||
private init(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListPublisher<ObjectType>) -> Void)?) {
|
||||
private init(
|
||||
context: NSManagedObjectContext,
|
||||
from: From<ObjectType>,
|
||||
sectionBy: SectionBy<ObjectType>?,
|
||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||
createAsynchronously: ((ListPublisher<ObjectType>) -> Void)?
|
||||
) {
|
||||
|
||||
self.query = (
|
||||
from: from,
|
||||
@@ -359,15 +462,19 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
||||
}
|
||||
|
||||
private func notifyObservers() {
|
||||
private func notifyObservers(sourceIdentifier: Any?) {
|
||||
|
||||
guard let enumerator = self.observers.objectEnumerator() else {
|
||||
|
||||
return
|
||||
}
|
||||
let arguments: ObserverClosureType.Arguments = (
|
||||
listPublisher: self,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
for closure in enumerator {
|
||||
|
||||
(closure as! Internals.Closure<ListPublisher<O>, Void>).invoke(with: self)
|
||||
(closure as! ObserverClosureType).invoke(with: arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,11 +491,18 @@ extension ListPublisher: FetchedDiffableDataSourceSnapshotHandler {
|
||||
return self.query.sectionIndexTransformer
|
||||
}
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
|
||||
internal func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot
|
||||
) {
|
||||
|
||||
self.snapshot = .init(
|
||||
diffableSnapshot: snapshot,
|
||||
context: controller.managedObjectContext
|
||||
let context = controller.managedObjectContext
|
||||
self.set(
|
||||
snapshot: .init(
|
||||
diffableSnapshot: snapshot,
|
||||
context: context
|
||||
),
|
||||
sourceIdentifier: context.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will raise an exception.
|
||||
- returns: the `ObjectPublisher<O>` interfacing the object at the specified section and item index
|
||||
*/
|
||||
public subscript(sectionIndex: Int, itemIndex: Int) -> ObjectPublisher<O> {
|
||||
public subscript(
|
||||
sectionIndex: Int,
|
||||
itemIndex: Int
|
||||
) -> ObjectPublisher<O> {
|
||||
|
||||
let context = self.context!
|
||||
let snapshot = self.diffableSnapshot
|
||||
@@ -116,7 +119,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`.
|
||||
- returns: the `ObjectPublisher<O>` interfacing the object at the specified section and item index, or `nil` if out of bounds
|
||||
*/
|
||||
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectPublisher<O>? {
|
||||
public subscript(
|
||||
safeSectionIndex sectionIndex: Int,
|
||||
safeItemIndex itemIndex: Int
|
||||
) -> ObjectPublisher<O>? {
|
||||
|
||||
guard let context = self.context else {
|
||||
|
||||
@@ -333,7 +339,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter indices: the positions of the itemIDs to return. Specifying an invalid value will raise an exception.
|
||||
- returns: the `ItemID` array belonging to the given `SectionID` at the specified indices
|
||||
*/
|
||||
public func itemIDs<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices indices: S) -> [ItemID] where S.Element == Int {
|
||||
public func itemIDs<S: Sequence>(
|
||||
inSectionWithID sectionID: SectionID,
|
||||
atIndices indices: S
|
||||
) -> [ItemID] where S.Element == Int {
|
||||
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
return indices.map({ itemIDs[$0] })
|
||||
@@ -398,7 +407,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemIndices: the positions of items within the section. Specifying an invalid value will raise an exception.
|
||||
- returns: an array of `ObjectPublisher`s for the items in the specified `SectionID` and indices
|
||||
*/
|
||||
public func items<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices itemIndices: S) -> [ObjectPublisher<O>] where S.Element == Int {
|
||||
public func items<S: Sequence>(
|
||||
inSectionWithID sectionID: SectionID,
|
||||
atIndices itemIndices: S
|
||||
) -> [ObjectPublisher<O>] where S.Element == Int {
|
||||
|
||||
let context = self.context!
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
@@ -446,7 +458,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemIndices: the positions of items within the section. Specifying an invalid value will raise an exception.
|
||||
- returns: a lazy sequence of `ObjectPublisher`s for the items in the specified `SectionID` and indices
|
||||
*/
|
||||
public func lazy<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices itemIndices: S) -> LazyMapSequence<S, ObjectPublisher<O>> where S.Element == Int {
|
||||
public func lazy<S: Sequence>(
|
||||
inSectionWithID sectionID: SectionID,
|
||||
atIndices itemIndices: S
|
||||
) -> LazyMapSequence<S, ObjectPublisher<O>> where S.Element == Int {
|
||||
|
||||
let context = self.context!
|
||||
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
|
||||
@@ -466,9 +481,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemIDs: the object identifiers for the objects to append
|
||||
- parameter sectionID: the section to append the items to
|
||||
*/
|
||||
public mutating func appendItems<C: Collection>(withIDs itemIDs: C, toSectionWithID sectionID: SectionID? = nil) where C.Element == ItemID {
|
||||
public mutating func appendItems<C: Collection>(
|
||||
withIDs itemIDs: C,
|
||||
toSectionWithID sectionID: SectionID? = nil
|
||||
) where C.Element == ItemID {
|
||||
|
||||
self.diffableSnapshot.appendItems(itemIDs, toSection: sectionID)
|
||||
self.mutate {
|
||||
|
||||
$0.appendItems(itemIDs, toSection: sectionID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Appends extra items to the specified section index
|
||||
|
||||
- parameter itemIDs: the object identifiers for the objects to append
|
||||
- parameter sectionIndex: the section index to append the items to. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func appendItems<C: Collection>(
|
||||
with itemIDs: C,
|
||||
toSectionAt sectionIndex: Int
|
||||
) where C.Element == ItemID {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeAppendItems(itemIDs, toSectionAt: sectionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -477,9 +515,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemIDs: the object identifiers for the objects to insert
|
||||
- parameter beforeItemID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, beforeItemID: ItemID) where C.Element == ItemID {
|
||||
public mutating func insertItems<C: Collection>(
|
||||
withIDs itemIDs: C,
|
||||
beforeItemID: ItemID
|
||||
) where C.Element == ItemID {
|
||||
|
||||
self.diffableSnapshot.insertItems(itemIDs, beforeItem: beforeItemID)
|
||||
self.mutate {
|
||||
|
||||
$0.insertItems(itemIDs, beforeItem: beforeItemID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,9 +532,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemIDs: the object identifiers for the objects to insert
|
||||
- parameter beforeItemID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, afterItemID: ItemID) where C.Element == ItemID {
|
||||
public mutating func insertItems<C: Collection>(
|
||||
withIDs itemIDs: C,
|
||||
afterItemID: ItemID
|
||||
) where C.Element == ItemID {
|
||||
|
||||
self.diffableSnapshot.insertItems(itemIDs, afterItem: afterItemID)
|
||||
self.mutate {
|
||||
|
||||
$0.insertItems(itemIDs, afterItem: afterItemID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts extra items at a specified index path
|
||||
|
||||
- parameter itemIDs: the object identifiers for the objects to insert
|
||||
- parameter indexPath: an indexPath to insert the items into. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func insertItems<C: Collection>(
|
||||
withIDs itemIDs: C,
|
||||
at indexPath: IndexPath
|
||||
) where C.Element == ItemID {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeInsertItems(itemIDs, at: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,9 +565,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
- parameter itemIDs: the object identifiers for the objects to delete
|
||||
*/
|
||||
public mutating func deleteItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
|
||||
public mutating func deleteItems<C: Collection>(withIDs itemIDs: C) where C.Element == ItemID {
|
||||
|
||||
self.diffableSnapshot.deleteItems(itemIDs)
|
||||
self.mutate {
|
||||
|
||||
$0.deleteItems(itemIDs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the items at the specified index paths
|
||||
|
||||
- parameter itemIndexPaths: the index paths for the objects to delete. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func deleteItems<C: Collection>(at itemIndexPaths: C) where C.Element == IndexPath {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeDeleteItems(at: itemIndexPaths)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,7 +591,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
*/
|
||||
public mutating func deleteAllItems() {
|
||||
|
||||
self.diffableSnapshot.deleteAllItems()
|
||||
self.mutate {
|
||||
|
||||
$0.deleteAllItems()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,9 +603,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemID: an object identifier in the list to move. Specifying an invalid value will raise an exception.
|
||||
- parameter beforeItemID: another identifier to move the item before of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func moveItem(withID itemID: ItemID, beforeItemID: ItemID) {
|
||||
public mutating func moveItem(
|
||||
withID itemID: ItemID,
|
||||
beforeItemID: ItemID
|
||||
) {
|
||||
|
||||
self.diffableSnapshot.moveItem(itemID, beforeItem: beforeItemID)
|
||||
self.mutate {
|
||||
|
||||
$0.moveItem(itemID, beforeItem: beforeItemID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -528,9 +620,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter itemID: an object identifier in the list to move. Specifying an invalid value will raise an exception.
|
||||
- parameter beforeItemID: another identifier to move the item after of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func moveItem(withID itemID: ItemID, afterItemID: ItemID) {
|
||||
public mutating func moveItem(
|
||||
withID itemID: ItemID,
|
||||
afterItemID: ItemID
|
||||
) {
|
||||
|
||||
self.diffableSnapshot.moveItem(itemID, afterItem: afterItemID)
|
||||
self.mutate {
|
||||
|
||||
$0.moveItem(itemID, afterItem: afterItemID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Moves an item at an index path to a new index path
|
||||
|
||||
- parameter itemIndexPath: an index path in the list to move. Specifying an invalid value will raise an exception.
|
||||
- parameter newIndexPath: the new index path to move the item into. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func moveItem(
|
||||
at itemIndexPath: IndexPath,
|
||||
to newIndexPath: IndexPath
|
||||
) {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeMoveItem(at: itemIndexPath, to: newIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -538,9 +653,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
- parameter itemIDs: the object identifiers to reload
|
||||
*/
|
||||
public mutating func reloadItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
|
||||
public mutating func reloadItems<C: Collection>(withIDs itemIDs: C) where C.Element == ItemID {
|
||||
|
||||
self.diffableSnapshot.reloadItems(itemIDs)
|
||||
self.mutate {
|
||||
|
||||
$0.reloadItems(itemIDs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Marks the specified index paths as reloaded
|
||||
|
||||
- parameter itemIndexPaths: the index paths to reload. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func reloadItems<C: Collection>(at itemIndexPaths: C) where C.Element == IndexPath {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeReloadItems(at: itemIndexPaths)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,7 +681,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
*/
|
||||
public mutating func appendSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
|
||||
|
||||
self.diffableSnapshot.appendSections(sectionIDs)
|
||||
self.mutate {
|
||||
|
||||
$0.appendSections(sectionIDs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -559,9 +693,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter sectionIDs: the section identifiers for the sections to insert
|
||||
- parameter beforeSectionID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, beforeSectionID: SectionID) where C.Element == SectionID {
|
||||
public mutating func insertSections<C: Collection>(
|
||||
withIDs sectionIDs: C,
|
||||
beforeSectionID: SectionID
|
||||
) where C.Element == SectionID {
|
||||
|
||||
self.diffableSnapshot.insertSections(sectionIDs, beforeSection: beforeSectionID)
|
||||
self.mutate {
|
||||
|
||||
$0.insertSections(sectionIDs, beforeSection: beforeSectionID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -570,9 +710,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter sectionIDs: the section identifiers for the sections to insert
|
||||
- parameter beforeSectionID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, afterSectionID: SectionID) where C.Element == SectionID {
|
||||
public mutating func insertSections<C: Collection>(
|
||||
withIDs sectionIDs: C,
|
||||
afterSectionID: SectionID
|
||||
) where C.Element == SectionID {
|
||||
|
||||
self.diffableSnapshot.insertSections(sectionIDs, afterSection: afterSectionID)
|
||||
self.mutate {
|
||||
|
||||
$0.insertSections(sectionIDs, afterSection: afterSectionID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts new sections into an existing section index
|
||||
|
||||
- parameter sectionIDs: the section identifiers for the sections to insert
|
||||
- parameter sectionIndex: an existing section index to insert items into. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func insertSections<C: Collection>(
|
||||
_ sectionIDs: C,
|
||||
at sectionIndex: Int
|
||||
) where C.Element == String {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeInsertSections(sectionIDs, at: sectionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -580,9 +743,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
- parameter sectionIDs: the section identifiers for the sections to delete
|
||||
*/
|
||||
public mutating func deleteSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
|
||||
public mutating func deleteSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
|
||||
|
||||
self.diffableSnapshot.deleteSections(sectionIDs)
|
||||
self.mutate {
|
||||
|
||||
$0.deleteSections(sectionIDs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified section indices
|
||||
|
||||
- parameter sectionIndices: the section indices to delete. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func deleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeDeleteSections(at: sectionIndices)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -591,9 +770,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter sectionID: a section identifier in the list to move. Specifying an invalid value will raise an exception.
|
||||
- parameter beforeSectionID: another identifier to move the section before of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func moveSection(withID sectionID: SectionID, beforeSectionID: SectionID) {
|
||||
public mutating func moveSection(
|
||||
withID sectionID: SectionID,
|
||||
beforeSectionID: SectionID
|
||||
) {
|
||||
|
||||
self.diffableSnapshot.moveSection(sectionID, beforeSection: beforeSectionID)
|
||||
self.mutate {
|
||||
|
||||
$0.moveSection(sectionID, beforeSection: beforeSectionID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -602,9 +787,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
- parameter sectionID: a section identifier in the list to move. Specifying an invalid value will raise an exception.
|
||||
- parameter afterSectionID: another identifier to move the section after of. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func moveSection(withID sectionID: SectionID, afterSectionID: SectionID) {
|
||||
public mutating func moveSection(
|
||||
withID sectionID: SectionID,
|
||||
afterSectionID: SectionID
|
||||
) {
|
||||
|
||||
self.diffableSnapshot.moveSection(sectionID, afterSection: afterSectionID)
|
||||
self.mutate {
|
||||
|
||||
$0.moveSection(sectionID, afterSection: afterSectionID)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Moves a section at a specified index to a new index
|
||||
|
||||
- parameter sectionIndex: a section index in the list to move. Specifying an invalid value will raise an exception.
|
||||
- parameter newSectionIndex: the new section index to move into. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func moveSection(
|
||||
at sectionIndex: Int,
|
||||
to newSectionIndex: Int
|
||||
) {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeMoveSection(at: sectionIndex, to: newSectionIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -612,9 +820,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
- parameter sectionIDs: the section identifiers to reload
|
||||
*/
|
||||
public mutating func reloadSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
|
||||
public mutating func reloadSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
|
||||
|
||||
self.diffableSnapshot.reloadSections(sectionIDs)
|
||||
self.mutate {
|
||||
|
||||
$0.reloadSections(sectionIDs)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Marks the specified section indices as reloaded
|
||||
|
||||
- parameter sectionIndices: the section indices to reload. Specifying an invalid value will raise an exception.
|
||||
*/
|
||||
public mutating func reloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
|
||||
|
||||
self.mutate {
|
||||
|
||||
$0.unsafeReloadSections(at: sectionIndices)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -696,16 +920,18 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
// MARK: Equatable
|
||||
|
||||
public static func == (_ lhs: Self, _ rhs: Self) -> Bool {
|
||||
|
||||
return lhs.id == rhs.id
|
||||
|
||||
return lhs.context == rhs.context
|
||||
&& lhs.generation == rhs.generation
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
|
||||
hasher.combine(self.id)
|
||||
|
||||
hasher.combine(self.context)
|
||||
hasher.combine(self.generation)
|
||||
}
|
||||
|
||||
|
||||
@@ -732,6 +958,12 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let id: UUID = .init()
|
||||
private var generation: UUID = .init()
|
||||
|
||||
@inline(__always)
|
||||
private mutating func mutate<T>(_ mutation: (inout Internals.DiffableDataSourceSnapshot) -> T) -> T {
|
||||
|
||||
self.generation = .init()
|
||||
return mutation(&self.diffableSnapshot)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,10 @@ extension NSManagedObjectContext {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let saveMetadata = rootContext.saveMetadata
|
||||
context.saveMetadata = saveMetadata
|
||||
|
||||
let mergeChanges = { () -> Void in
|
||||
|
||||
if let updatedObjects = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) {
|
||||
@@ -123,8 +127,9 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
}
|
||||
context.mergeChanges(fromContextDidSave: note)
|
||||
context.saveMetadata = nil
|
||||
}
|
||||
if rootContext.isSavingSynchronously == true {
|
||||
if case true? = saveMetadata?.isSavingSynchronously {
|
||||
|
||||
context.performAndWait(mergeChanges)
|
||||
}
|
||||
|
||||
@@ -54,21 +54,21 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal var isSavingSynchronously: Bool? {
|
||||
internal var saveMetadata: SaveMetadata? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSNumber? = Internals.getAssociatedObjectForKey(
|
||||
&PropertyKeys.isSavingSynchronously,
|
||||
let value: SaveMetadata? = Internals.getAssociatedObjectForKey(
|
||||
&PropertyKeys.saveMetadata,
|
||||
inObject: self
|
||||
)
|
||||
return value?.boolValue
|
||||
return value
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.setAssociatedWeakObject(
|
||||
newValue.flatMap { NSNumber(value: $0) },
|
||||
forKey: &PropertyKeys.isSavingSynchronously,
|
||||
Internals.setAssociatedRetainedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.saveMetadata,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
@@ -140,7 +140,10 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func saveSynchronously(waitForMerge: Bool) -> (hasChanges: Bool, error: CoreStoreError?) {
|
||||
internal func saveSynchronously(
|
||||
waitForMerge: Bool,
|
||||
sourceIdentifier: Any?
|
||||
) -> (hasChanges: Bool, error: CoreStoreError?) {
|
||||
|
||||
var result: (hasChanges: Bool, error: CoreStoreError?) = (false, nil)
|
||||
self.performAndWait {
|
||||
@@ -151,9 +154,12 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
do {
|
||||
|
||||
self.isSavingSynchronously = waitForMerge
|
||||
self.saveMetadata = .init(
|
||||
isSavingSynchronously: waitForMerge,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
try self.save()
|
||||
self.isSavingSynchronously = nil
|
||||
self.saveMetadata = nil
|
||||
}
|
||||
catch {
|
||||
|
||||
@@ -167,7 +173,10 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
if let parentContext = self.parent, self.shouldCascadeSavesToParent {
|
||||
|
||||
let (_, error) = parentContext.saveSynchronously(waitForMerge: waitForMerge)
|
||||
let (_, error) = parentContext.saveSynchronously(
|
||||
waitForMerge: waitForMerge,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
result = (true, error)
|
||||
}
|
||||
else {
|
||||
@@ -179,7 +188,10 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func saveAsynchronouslyWithCompletion(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }) {
|
||||
internal func saveAsynchronously(
|
||||
sourceIdentifier: Any?,
|
||||
completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void = { (_, _) in }
|
||||
) {
|
||||
|
||||
self.perform {
|
||||
|
||||
@@ -193,9 +205,12 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
do {
|
||||
|
||||
self.isSavingSynchronously = false
|
||||
self.saveMetadata = .init(
|
||||
isSavingSynchronously: false,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
try self.save()
|
||||
self.isSavingSynchronously = nil
|
||||
self.saveMetadata = nil
|
||||
}
|
||||
catch {
|
||||
|
||||
@@ -212,10 +227,13 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
if self.shouldCascadeSavesToParent, let parentContext = self.parent {
|
||||
|
||||
parentContext.saveAsynchronouslyWithCompletion { (_, error) in
|
||||
|
||||
completion(true, error)
|
||||
}
|
||||
parentContext.saveAsynchronously(
|
||||
sourceIdentifier: sourceIdentifier,
|
||||
completion: { (_, error) in
|
||||
|
||||
completion(true, error)
|
||||
}
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -234,12 +252,32 @@ extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SaveMetadata
|
||||
|
||||
internal final class SaveMetadata {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let isSavingSynchronously: Bool
|
||||
internal let sourceIdentifier: Any?
|
||||
|
||||
internal init(
|
||||
isSavingSynchronously: Bool,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.isSavingSynchronously = isSavingSynchronously
|
||||
self.sourceIdentifier = sourceIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var parentTransaction: Void?
|
||||
static var isSavingSynchronously: Void?
|
||||
static var saveMetadata: Void?
|
||||
static var isTransactionContext: Void?
|
||||
static var isDataStackContext: Void?
|
||||
}
|
||||
|
||||
@@ -78,15 +78,28 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
observer,
|
||||
willChangeObject: { (observer, monitor, object) in
|
||||
|
||||
observer.objectMonitor(monitor, willUpdateObject: object)
|
||||
observer.objectMonitor(
|
||||
monitor,
|
||||
willUpdateObject: object,
|
||||
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didDeleteObject: { (observer, monitor, object) in
|
||||
|
||||
observer.objectMonitor(monitor, didDeleteObject: object)
|
||||
observer.objectMonitor(
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
},
|
||||
didUpdateObject: { (observer, monitor, object, changedPersistentKeys) in
|
||||
|
||||
observer.objectMonitor(monitor, didUpdateObject: object, changedPersistentKeys: changedPersistentKeys)
|
||||
observer.objectMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
changedPersistentKeys: changedPersistentKeys,
|
||||
sourceIdentifier: monitor.context.saveMetadata?.sourceIdentifier
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -197,7 +210,10 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(objectID: O.ObjectID, context: NSManagedObjectContext) {
|
||||
internal init(
|
||||
objectID: O.ObjectID,
|
||||
context: NSManagedObjectContext
|
||||
) {
|
||||
|
||||
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
|
||||
fetchRequest.entity = objectID.entity
|
||||
@@ -227,7 +243,25 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
self.lastCommittedAttributes = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:]
|
||||
}
|
||||
|
||||
internal func registerObserver<U: AnyObject>(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<O>, _ object: O) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<O>, _ object: O) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor<O>, _ object: O, _ changedPersistentKeys: Set<String>) -> Void) {
|
||||
internal func registerObserver<U: AnyObject>(
|
||||
_ observer: U,
|
||||
willChangeObject: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ObjectMonitor<O>,
|
||||
_ object: O
|
||||
) -> Void,
|
||||
didDeleteObject: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ObjectMonitor<O>,
|
||||
_ object: O
|
||||
) -> Void,
|
||||
didUpdateObject: @escaping (
|
||||
_ observer: U,
|
||||
_ monitor: ObjectMonitor<O>,
|
||||
_ object: O,
|
||||
_ changedPersistentKeys: Set<String>
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
Thread.isMainThread,
|
||||
@@ -323,7 +357,12 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
return self.fetchedResultsController.managedObjectContext
|
||||
}
|
||||
|
||||
private func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor<O>) -> Void) {
|
||||
private func registerChangeNotification(
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (_ monitor: ObjectMonitor<O>) -> Void
|
||||
) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
Internals.NotificationObserver(
|
||||
@@ -343,7 +382,12 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
)
|
||||
}
|
||||
|
||||
private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor<O>, _ object: O) -> Void) {
|
||||
private func registerObjectNotification(
|
||||
_ notificationKey: UnsafeRawPointer,
|
||||
name: Notification.Name,
|
||||
toObserver observer: AnyObject,
|
||||
callback: @escaping (_ monitor: ObjectMonitor<O>, _ object: O) -> Void
|
||||
) {
|
||||
|
||||
Internals.setAssociatedRetainedObject(
|
||||
Internals.NotificationObserver(
|
||||
@@ -384,7 +428,9 @@ extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
return { _ in nil }
|
||||
}
|
||||
|
||||
internal func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
internal func controllerWillChangeContent(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>
|
||||
) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name.objectMonitorWillChangeObject,
|
||||
@@ -392,9 +438,17 @@ extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
)
|
||||
}
|
||||
|
||||
internal func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { }
|
||||
internal func controllerDidChangeContent(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>
|
||||
) {}
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeObject anObject: Any, atIndexPath indexPath: IndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
|
||||
internal func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeObject anObject: Any,
|
||||
atIndexPath indexPath: IndexPath?,
|
||||
forChangeType type: NSFetchedResultsChangeType,
|
||||
newIndexPath: IndexPath?
|
||||
) {
|
||||
|
||||
switch type {
|
||||
|
||||
@@ -418,7 +472,12 @@ extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { }
|
||||
internal func controller(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
||||
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
|
||||
atIndex sectionIndex: Int,
|
||||
forChangeType type: NSFetchedResultsChangeType
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -49,8 +49,41 @@ public protocol ObjectObserver: AnyObject {
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `DynamicObject` instance being observed
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType)
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
willUpdateObject object: ObjectEntityType,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed `object` occurs. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `DynamicObject` instance being observed
|
||||
*/
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
willUpdateObject object: ObjectEntityType
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed `object` occurs. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `DynamicObject` instance being observed
|
||||
- parameter changedPersistentKeys: a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported.
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didUpdateObject object: ObjectEntityType,
|
||||
changedPersistentKeys: Set<KeyPathString>,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed `object` occurs. (Optional)
|
||||
@@ -60,7 +93,25 @@ public protocol ObjectObserver: AnyObject {
|
||||
- parameter object: the `DynamicObject` instance being observed
|
||||
- parameter changedPersistentKeys: a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported.
|
||||
*/
|
||||
func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPathString>)
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didUpdateObject object: ObjectEntityType,
|
||||
changedPersistentKeys: Set<KeyPathString>
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing right after `object` is deleted. (Optional)
|
||||
The default implementation does nothing.
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `DynamicObject` instance being observed
|
||||
- parameter sourceIdentifier: an optional identifier provided by the transaction source
|
||||
*/
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didDeleteObject object: ObjectEntityType,
|
||||
sourceIdentifier: Any?
|
||||
)
|
||||
|
||||
/**
|
||||
Handles processing right after `object` is deleted. (Optional)
|
||||
@@ -69,7 +120,10 @@ public protocol ObjectObserver: AnyObject {
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `DynamicObject` instance being observed
|
||||
*/
|
||||
func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, didDeleteObject object: ObjectEntityType)
|
||||
func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didDeleteObject object: ObjectEntityType
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +131,57 @@ public protocol ObjectObserver: AnyObject {
|
||||
|
||||
extension ObjectObserver {
|
||||
|
||||
public func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType) { }
|
||||
public func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
willUpdateObject object: ObjectEntityType,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.objectMonitor(
|
||||
monitor,
|
||||
willUpdateObject: object
|
||||
)
|
||||
}
|
||||
|
||||
public func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPathString>) { }
|
||||
public func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
willUpdateObject object: ObjectEntityType
|
||||
) {}
|
||||
|
||||
public func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, didDeleteObject object: ObjectEntityType) { }
|
||||
public func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didUpdateObject object: ObjectEntityType,
|
||||
changedPersistentKeys: Set<KeyPathString>,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.objectMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
changedPersistentKeys: changedPersistentKeys
|
||||
)
|
||||
}
|
||||
|
||||
public func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didUpdateObject object: ObjectEntityType,
|
||||
changedPersistentKeys: Set<KeyPathString>
|
||||
) {}
|
||||
|
||||
public func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didDeleteObject object: ObjectEntityType,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.objectMonitor(
|
||||
monitor,
|
||||
didDeleteObject: object
|
||||
)
|
||||
}
|
||||
|
||||
public func objectMonitor(
|
||||
_ monitor: ObjectMonitor<ObjectEntityType>,
|
||||
didDeleteObject object: ObjectEntityType
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -135,8 +135,19 @@ extension ObjectPublisher {
|
||||
|
||||
func cancel() {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
self.subscriber = nil
|
||||
|
||||
if Thread.isMainThread {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
else {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
self.publisher.removeObserver(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
|
||||
)
|
||||
self.observers.setObject(
|
||||
Internals.Closure(callback),
|
||||
Internals.Closure({ callback($0.objectPublisher) }),
|
||||
forKey: observer
|
||||
)
|
||||
_ = self.lazySnapshot
|
||||
@@ -97,6 +97,46 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
callback(self)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Registers an object as an observer to be notified when changes to the `ObjectPublisher`'s snapshot occur.
|
||||
|
||||
To prevent retain-cycles, `ObjectPublisher` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:_:)` multiple times on the same observer is safe.
|
||||
|
||||
- parameter observer: an object to become owner of the specified `callback`
|
||||
- parameter notifyInitial: if `true`, the callback is executed immediately with the current publisher state. Otherwise only succeeding updates will notify the observer. Default value is `false`.
|
||||
- parameter initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
|
||||
- parameter callback: the closure to execute when changes occur
|
||||
*/
|
||||
public func addObserver<T: AnyObject>(
|
||||
_ observer: T,
|
||||
notifyInitial: Bool = false,
|
||||
initialSourceIdentifier: Any? = nil,
|
||||
_ callback: @escaping (
|
||||
_ objectPublisher: ObjectPublisher<O>,
|
||||
_ sourceIdentifier: Any?
|
||||
) -> Void
|
||||
) {
|
||||
|
||||
Internals.assert(
|
||||
Thread.isMainThread,
|
||||
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
|
||||
)
|
||||
self.observers.setObject(
|
||||
Internals.Closure(callback),
|
||||
forKey: observer
|
||||
)
|
||||
_ = self.lazySnapshot
|
||||
|
||||
if notifyInitial {
|
||||
|
||||
callback(self, initialSourceIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Unregisters an object from receiving notifications for changes to the `ObjectPublisher`'s snapshot.
|
||||
@@ -215,6 +255,8 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate typealias ObserverClosureType = Internals.Closure<(objectPublisher: ObjectPublisher<O>, sourceIdentifier: Any?), Void>
|
||||
|
||||
fileprivate init(objectID: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot<O>?) {
|
||||
|
||||
@@ -237,12 +279,12 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
self.object = nil
|
||||
|
||||
self.$lazySnapshot.reset({ nil })
|
||||
self.notifyObservers()
|
||||
self.notifyObservers(sourceIdentifier: self.context.saveMetadata)
|
||||
}
|
||||
else if updatedIDs.contains(objectID) {
|
||||
|
||||
self.$lazySnapshot.reset({ initializer(objectID, context) })
|
||||
self.notifyObservers()
|
||||
self.notifyObservers(sourceIdentifier: self.context.saveMetadata)
|
||||
}
|
||||
}
|
||||
return initializer(objectID, context)
|
||||
@@ -258,18 +300,21 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
@Internals.LazyNonmutating(uninitialized: ())
|
||||
private var lazySnapshot: ObjectSnapshot<O>?
|
||||
|
||||
private lazy var observers: NSMapTable<AnyObject, Internals.Closure<ObjectPublisher<O>, Void>> = .weakToStrongObjects()
|
||||
private lazy var observers: NSMapTable<AnyObject, ObserverClosureType> = .weakToStrongObjects()
|
||||
|
||||
private func notifyObservers() {
|
||||
private func notifyObservers(sourceIdentifier: Any?) {
|
||||
|
||||
guard let enumerator = self.observers.objectEnumerator() else {
|
||||
|
||||
return
|
||||
}
|
||||
let arguments: ObserverClosureType.Arguments = (
|
||||
objectPublisher: self,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
for closure in enumerator {
|
||||
|
||||
(closure as! Internals.Closure<ObjectPublisher
|
||||
<O>, Void>).invoke(with: self)
|
||||
(closure as! ObserverClosureType).invoke(with: arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,15 +147,28 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) {
|
||||
internal init(
|
||||
mainContext: NSManagedObjectContext,
|
||||
queue: DispatchQueue,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
|
||||
super.init(
|
||||
mainContext: mainContext,
|
||||
queue: queue,
|
||||
supportsUndo: false,
|
||||
bypassesQueueing: false,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
internal func autoCommit(waitForMerge: Bool) -> (hasChanges: Bool, error: CoreStoreError?) {
|
||||
|
||||
self.isCommitted = true
|
||||
let result = self.context.saveSynchronously(waitForMerge: waitForMerge)
|
||||
let result = self.context.saveSynchronously(
|
||||
waitForMerge: waitForMerge,
|
||||
sourceIdentifier: self.sourceIdentifier
|
||||
)
|
||||
self.result = result
|
||||
defer {
|
||||
|
||||
|
||||
@@ -43,11 +43,14 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
*/
|
||||
public func commit(_ completion: @escaping (_ error: CoreStoreError?) -> Void) {
|
||||
|
||||
self.context.saveAsynchronouslyWithCompletion { (_, error) in
|
||||
|
||||
completion(error)
|
||||
withExtendedLifetime(self, {})
|
||||
}
|
||||
self.context.saveAsynchronously(
|
||||
sourceIdentifier: self.sourceIdentifier,
|
||||
completion: { (_, error) in
|
||||
|
||||
completion(error)
|
||||
withExtendedLifetime(self, {})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +60,10 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
*/
|
||||
public func commitAndWait() throws {
|
||||
|
||||
if case (_, let error?) = self.context.saveSynchronously(waitForMerge: true) {
|
||||
if case (_, let error?) = self.context.saveSynchronously(
|
||||
waitForMerge: true,
|
||||
sourceIdentifier: self.sourceIdentifier
|
||||
) {
|
||||
|
||||
throw error
|
||||
}
|
||||
@@ -126,23 +132,39 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- parameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||
- returns: an `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public func beginUnsafe(supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
public func beginUnsafe(
|
||||
supportsUndo: Bool = false,
|
||||
sourceIdentifier: Any? = nil
|
||||
) -> UnsafeDataTransaction {
|
||||
|
||||
return UnsafeDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.transactionQueue,
|
||||
supportsUndo: supportsUndo
|
||||
supportsUndo: supportsUndo,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, supportsUndo: Bool) {
|
||||
internal init(
|
||||
mainContext: NSManagedObjectContext,
|
||||
queue: DispatchQueue,
|
||||
supportsUndo: Bool,
|
||||
sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true)
|
||||
super.init(
|
||||
mainContext: mainContext,
|
||||
queue: queue,
|
||||
supportsUndo: supportsUndo,
|
||||
bypassesQueueing: true,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user