mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-11 22:30:34 +01:00
added mechanism to track transaction sources
This commit is contained in:
@@ -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 }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,5 +69,16 @@ extension Modern {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - TransactionSource
|
||||
|
||||
enum TransactionSource {
|
||||
|
||||
case add
|
||||
case delete
|
||||
case shuffle
|
||||
case clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,25 @@ 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:
|
||||
dataSource.apply(listPublisher.snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
|
||||
dataSource.apply(self.listPublisher.snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,6 +82,7 @@ extension Modern.ColorsDemo.UIKit {
|
||||
|
||||
transaction.delete(objectIDs: [itemID])
|
||||
},
|
||||
sourceIdentifier: Modern.ColorsDemo.TransactionSource.delete,
|
||||
completion: { _ in }
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,7 +37,9 @@ extension CSDataStack {
|
||||
- parameter closure: the block 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`.
|
||||
*/
|
||||
@objc
|
||||
public func beginAsynchronous(_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void) {
|
||||
public func beginAsynchronous(
|
||||
_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void
|
||||
) {
|
||||
|
||||
self.bridgeToSwift.perform(
|
||||
asynchronous: { (transaction) in
|
||||
@@ -57,6 +59,37 @@ extension CSDataStack {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- parameter closure: the block 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.
|
||||
*/
|
||||
@objc
|
||||
public func beginAsynchronous(
|
||||
_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void,
|
||||
_ sourceIdentifier: Any?
|
||||
) {
|
||||
|
||||
self.bridgeToSwift.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let csTransaction = transaction.bridgeToObjectiveC
|
||||
closure(csTransaction)
|
||||
if !transaction.isCommitted && transaction.hasChanges {
|
||||
|
||||
Internals.log(
|
||||
.warning,
|
||||
message: "The closure for the \(Internals.typeName(csTransaction)) completed without being committed. All changes made within the transaction were discarded."
|
||||
)
|
||||
}
|
||||
try transaction.cancel()
|
||||
},
|
||||
sourceIdentifier: sourceIdentifier,
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
@@ -65,7 +98,9 @@ extension CSDataStack {
|
||||
- returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information.
|
||||
*/
|
||||
@objc
|
||||
public func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void, error: NSErrorPointer) -> Bool {
|
||||
public func beginSynchronous(
|
||||
_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void,
|
||||
error: NSErrorPointer) -> Bool {
|
||||
|
||||
return bridge(error) {
|
||||
|
||||
@@ -94,6 +129,49 @@ extension CSDataStack {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- parameter closure: the block 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 error: the `CSError` pointer that indicates the reason in case of an failure
|
||||
- returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information.
|
||||
*/
|
||||
@objc
|
||||
public func beginSynchronous(
|
||||
_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void,
|
||||
sourceIdentifier: Any?,
|
||||
error: NSErrorPointer
|
||||
) -> Bool {
|
||||
|
||||
return bridge(error) {
|
||||
|
||||
do {
|
||||
|
||||
try self.bridgeToSwift.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
let csTransaction = transaction.bridgeToObjectiveC
|
||||
closure(csTransaction)
|
||||
if !transaction.isCommitted && transaction.hasChanges {
|
||||
|
||||
Internals.log(
|
||||
.warning,
|
||||
message: "The closure for the \(Internals.typeName(csTransaction)) completed without being committed. All changes made within the transaction were discarded."
|
||||
)
|
||||
}
|
||||
try transaction.cancel()
|
||||
},
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
catch CoreStoreError.userCancelled {
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
@@ -112,15 +190,61 @@ extension CSDataStack {
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` 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 `YES`, otherwise those method will raise an exception. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
To support "undo" methods such as `-undo`, `-redo`, and `-rollback`, use the `-beginSafeWithSupportsUndo:` method passing `YES` to the argument. Without "undo" support, calling those methods will raise an exception.
|
||||
- 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 `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@objc
|
||||
public func beginUnsafeWithSupportsUndo(_ supportsUndo: Bool) -> CSUnsafeDataTransaction {
|
||||
public func beginUnsafeWithSourceIdentifier(
|
||||
_ sourceIdentifier: Any?
|
||||
) -> CSUnsafeDataTransaction {
|
||||
|
||||
return bridge {
|
||||
|
||||
self.bridgeToSwift.beginUnsafe(supportsUndo: supportsUndo)
|
||||
self.bridgeToSwift.beginUnsafe(
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- parameter supportsUndo: `-undo`, `-redo`, and `-rollback` methods are only available when this parameter is `YES`, otherwise those method will raise an exception. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@objc
|
||||
public func beginUnsafeWithSupportsUndo(
|
||||
_ supportsUndo: Bool
|
||||
) -> CSUnsafeDataTransaction {
|
||||
|
||||
return bridge {
|
||||
|
||||
self.bridgeToSwift.beginUnsafe(
|
||||
supportsUndo: supportsUndo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- parameter supportsUndo: `-undo`, `-redo`, and `-rollback` methods are only available when this parameter is `YES`, otherwise those method will raise an exception. 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 `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@objc
|
||||
public func beginUnsafeWithSupportsUndo(
|
||||
_ supportsUndo: Bool,
|
||||
sourceIdentifier: Any?
|
||||
) -> CSUnsafeDataTransaction {
|
||||
|
||||
return bridge {
|
||||
|
||||
self.bridgeToSwift.beginUnsafe(
|
||||
supportsUndo: supportsUndo,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,11 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction, CoreStor
|
||||
|
||||
return bridge(error) {
|
||||
|
||||
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
|
||||
let transaction = self.bridgeToSwift
|
||||
if case (_, let error?) = transaction.context.saveSynchronously(
|
||||
waitForMerge: true,
|
||||
sourceIdentifier: transaction.sourceIdentifier
|
||||
) {
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -46,21 +46,25 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
|
||||
@objc
|
||||
public func commitWithSuccess(_ success: (() -> Void)?, _ failure: ((CSError) -> Void)?) {
|
||||
|
||||
self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (_, error) in
|
||||
|
||||
defer {
|
||||
let transaction = self.bridgeToSwift
|
||||
transaction.context.saveAsynchronously(
|
||||
sourceIdentifier: transaction.sourceIdentifier,
|
||||
completion: { (_, error) in
|
||||
|
||||
withExtendedLifetime(self, {})
|
||||
defer {
|
||||
|
||||
withExtendedLifetime(self, {})
|
||||
}
|
||||
if let error = error {
|
||||
|
||||
failure?(error.bridgeToObjectiveC)
|
||||
}
|
||||
else {
|
||||
|
||||
success?()
|
||||
}
|
||||
}
|
||||
if let error = error {
|
||||
|
||||
failure?(error.bridgeToObjectiveC)
|
||||
}
|
||||
else {
|
||||
|
||||
success?()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +78,11 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
|
||||
|
||||
return bridge(error) {
|
||||
|
||||
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
|
||||
let transaction = self.bridgeToSwift
|
||||
if case (_, let error?) = transaction.context.saveSynchronously(
|
||||
waitForMerge: true,
|
||||
sourceIdentifier: transaction.sourceIdentifier
|
||||
) {
|
||||
|
||||
throw error
|
||||
}
|
||||
@@ -152,15 +160,61 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` 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 `YES`, otherwise those method will raise an exception. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
To support "undo" methods such as `-undo`, `-redo`, and `-rollback`, use the `-beginSafeWithSupportsUndo:` method passing `YES` to the argument. Without "undo" support, calling those methods will raise an exception.
|
||||
- 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 `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@objc
|
||||
public func beginUnsafeWithSupportsUndo(_ supportsUndo: Bool) -> CSUnsafeDataTransaction {
|
||||
public func beginUnsafeWithSourceIdentifier(
|
||||
_ sourceIdentifier: Any?
|
||||
) -> CSUnsafeDataTransaction {
|
||||
|
||||
return bridge {
|
||||
|
||||
self.bridgeToSwift.beginUnsafe(supportsUndo: supportsUndo)
|
||||
self.bridgeToSwift.beginUnsafe(
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- parameter supportsUndo: `-undo`, `-redo`, and `-rollback` methods are only available when this parameter is `YES`, otherwise those method will raise an exception. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@objc
|
||||
public func beginUnsafeWithSupportsUndo(
|
||||
_ supportsUndo: Bool
|
||||
) -> CSUnsafeDataTransaction {
|
||||
|
||||
return bridge {
|
||||
|
||||
self.bridgeToSwift.beginUnsafe(
|
||||
supportsUndo: supportsUndo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- parameter supportsUndo: `-undo`, `-redo`, and `-rollback` methods are only available when this parameter is `YES`, otherwise those method will raise an exception. 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 `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@objc
|
||||
public func beginUnsafeWithSupportsUndo(
|
||||
_ supportsUndo: Bool,
|
||||
sourceIdentifier: Any?
|
||||
) -> CSUnsafeDataTransaction {
|
||||
|
||||
return bridge {
|
||||
|
||||
self.bridgeToSwift.beginUnsafe(
|
||||
supportsUndo: supportsUndo,
|
||||
sourceIdentifier: sourceIdentifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -627,7 +627,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 +644,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 +662,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 +679,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 +697,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 +722,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 +758,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 +790,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 +867,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 +954,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,
|
||||
@@ -1077,7 +1178,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
|
||||
@@ -1294,7 +1402,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 +1458,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 +1492,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
internal func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
||||
internal func controllerWillChangeContent(
|
||||
_ controller: NSFetchedResultsController<NSFetchRequestResult>
|
||||
) {
|
||||
|
||||
self.taskGroup.enter()
|
||||
NotificationCenter.default.post(
|
||||
@@ -1382,7 +1503,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
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -301,6 +333,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,7 +361,7 @@ 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) {
|
||||
|
||||
@@ -359,15 +405,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 +434,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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -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