Compare commits

...

13 Commits

Author SHA1 Message Date
John Estropia
bf10f4668c revert Objc method changes 2021-09-22 15:03:46 +09:00
John Estropia
a1a04aaf8a tag refetch methods with source identifiers 2021-09-15 19:38:59 +09:00
John Estropia
4ddfa95140 added mechanism to track transaction sources 2021-09-15 14:45:13 +09:00
John Estropia
45215c7a18 WIP 2021-08-25 20:13:14 +09:00
John Estropia
d2f1656fdd test copy-on-write for ListSnapshot 2021-08-25 09:37:20 +09:00
John Estropia
5fbb1ab09d Merge pull request #421 from xquezme/develop
Fix compound indexes for dynamic models
2021-07-20 09:26:17 +09:00
John Estropia
d0cc01877e Merge pull request #433 from tackgyu/unset-library-type
Unset library type in Swift package manifests
2021-07-19 13:35:39 +09:00
tackgyu
a434628249 Unset library type in Swift package manifests 2021-07-19 12:52:51 +09:00
John Estropia
9cc616f720 fix compiler error on Xcode 13 2021-06-19 15:49:45 +09:00
John Estropia
4534bc06f5 Add utility for DiffableDataSources to create an empty snapshot for custom list construction 2021-06-16 23:28:46 +09:00
John Estropia
798d30bbd9 allow ListPublisher and ObjectPublisher combine Publishers to cancel subscription from any thread 2021-06-13 14:36:22 +09:00
John Estropia
7938aa2447 Added fast index-based ListSnapshot mutators 2021-06-12 11:06:43 +09:00
Pimenov Sergey
6a2394052c fix compound indexes for dynamic models 2021-04-04 20:12:40 -07:00
36 changed files with 1925 additions and 307 deletions

View File

@@ -197,7 +197,7 @@ class DynamicModelTests: BaseTestDataTestCase {
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Dog>("Dog"),
Entity<Dog>("Dog", indexes: [[\Dog.$nickname, \Dog.$age]]),
Entity<Person>("Person")
],
versionLock: [

View File

@@ -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 }
)
}

View File

@@ -65,9 +65,22 @@ extension Modern {
sectionIndexTransformer: { $0?.first?.uppercased() }
)
.where(self.filter.whereClause())
.orderBy(.ascending(\.$hue))
.orderBy(.ascending(\.$hue)),
sourceIdentifier: TransactionSource.refetch
)
}
}
// MARK: - TransactionSource
enum TransactionSource {
case add
case delete
case shuffle
case clear
case refetch
}
}
}

View File

@@ -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)

View File

@@ -32,17 +32,26 @@ extension Modern.ColorsDemo.UIKit {
)
/**
Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `dataSource.apply()` once.
Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `dataSource.apply()` once. This example inspects the optional `transactionSource` to determine the source of the update which is helpful for debugging or for fine-tuning animations.
*/
private func startObservingList() {
let dataSource = self.dataSource
self.listPublisher.addObserver(self) { (listPublisher) in
self.listPublisher.addObserver(self, notifyInitial: true) { (listPublisher, transactionSource) in
dataSource.apply(listPublisher.snapshot, animatingDifferences: true)
switch transactionSource as? Modern.ColorsDemo.TransactionSource {
case .add,
.delete,
.shuffle,
.clear:
dataSource.apply(listPublisher.snapshot, animatingDifferences: true)
case nil,
.refetch:
dataSource.apply(listPublisher.snapshot, animatingDifferences: false)
}
}
dataSource.apply(self.listPublisher.snapshot, animatingDifferences: false)
}
/**
@@ -74,6 +83,7 @@ extension Modern.ColorsDemo.UIKit {
transaction.delete(objectIDs: [itemID])
},
sourceIdentifier: Modern.ColorsDemo.TransactionSource.delete,
completion: { _ in }
)

View File

@@ -32,7 +32,7 @@ let package = Package(
.macOS(.v10_13), .iOS(.v11), .tvOS(.v11), .watchOS(.v4)
],
products: [
.library(name: "CoreStore", type: .static, targets: ["CoreStore"])
.library(name: "CoreStore", targets: ["CoreStore"])
],
dependencies: [],
targets: [

View File

@@ -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()
}

View File

@@ -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

View File

@@ -49,7 +49,10 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction, CoreStor
return bridge(error) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(
waitForMerge: true,
sourceIdentifier: nil
) {
throw error
}

View File

@@ -46,21 +46,24 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
@objc
public func commitWithSuccess(_ success: (() -> Void)?, _ failure: ((CSError) -> Void)?) {
self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (_, error) in
defer {
withExtendedLifetime(self, {})
self.bridgeToSwift.context.saveAsynchronously(
sourceIdentifier: nil,
completion: { (_, error) in
defer {
withExtendedLifetime(self, {})
}
if let error = error {
failure?(error.bridgeToObjectiveC)
}
else {
success?()
}
}
if let error = error {
failure?(error.bridgeToObjectiveC)
}
else {
success?()
}
}
)
}
/**
@@ -71,10 +74,13 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
*/
@objc
public func commitAndWait(error: NSErrorPointer) -> Bool {
return bridge(error) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(
waitForMerge: true,
sourceIdentifier: nil
) {
throw error
}

View File

@@ -585,7 +585,7 @@ public final class CoreStoreSchema: DynamicSchema {
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
return NSFetchIndexDescription.init(
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "-"))",
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "_"))",
elements: compoundIndexes.map { (keyPath) in
return NSFetchIndexElementDescription(

View File

@@ -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
)
}

View File

@@ -116,10 +116,10 @@ extension DiffableDataSource {
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
setData: setSections,
completion: completion
)
},
completion: completion
}
)
}
@@ -148,10 +148,21 @@ extension DiffableDataSource {
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
setData: setSections,
completion: completion
)
},
completion: completion
}
)
}
/**
Creates a new empty `ListSnapshot` suitable for building custom lists inside subclass implementations of `apply(_:animatingDifferences:completion:)`.
*/
public func makeEmptySnapshot() -> ListSnapshot<O> {
return .init(
diffableSnapshot: .init(),
context: self.dataStack.unsafeContext()
)
}

View File

@@ -83,7 +83,12 @@ extension DiffableDataSource {
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
*/
@nonobjc
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
public init(
collectionView: NSCollectionView,
dataStack: DataStack,
itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?,
supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }
) {
self.itemProvider = itemProvider
self.supplementaryViewProvider = supplementaryViewProvider
@@ -97,19 +102,27 @@ extension DiffableDataSource {
// MARK: - NSCollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
public dynamic func numberOfSections(
in collectionView: NSCollectionView
) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
public dynamic func collectionView(
_ collectionView: NSCollectionView,
numberOfItemsInSection section: Int
) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
open dynamic func collectionView(
_ collectionView: NSCollectionView,
itemForRepresentedObjectAt indexPath: IndexPath
) -> NSCollectionViewItem {
guard let objectID = self.itemID(for: indexPath) else {
@@ -127,7 +140,11 @@ extension DiffableDataSource {
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
open dynamic func collectionView(
_ collectionView: NSCollectionView,
viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind,
at indexPath: IndexPath
) -> NSView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
@@ -207,9 +224,9 @@ extension DiffableDataSource {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
self.base?.animator().performBatchUpdates(updates, completionHandler: { _ in completion() })
}
public func reloadData() {

View File

@@ -83,7 +83,12 @@ extension DiffableDataSource {
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
*/
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
public init(
collectionView: UICollectionView,
dataStack: DataStack,
cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?,
supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }
) {
self.cellProvider = cellProvider
self.supplementaryViewProvider = supplementaryViewProvider
@@ -97,19 +102,30 @@ extension DiffableDataSource {
// MARK: - UICollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
@MainActor
public dynamic func numberOfSections(
in collectionView: UICollectionView
) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
@MainActor
public dynamic func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
@MainActor
open dynamic func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
guard let objectID = self.itemID(for: indexPath) else {
@@ -127,7 +143,12 @@ extension DiffableDataSource {
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
@MainActor
open dynamic func collectionView(
_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath
) -> UICollectionReusableView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
@@ -207,9 +228,9 @@ extension DiffableDataSource {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
self.base?.performBatchUpdates(updates, completion: nil)
self.base?.performBatchUpdates(updates, completion: { _ in completion() })
}
public func reloadData() {

View File

@@ -82,7 +82,11 @@ extension DiffableDataSource {
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
public init(
tableView: UITableView,
dataStack: DataStack,
cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?
) {
self.cellProvider = cellProvider
super.init(target: .init(tableView), dataStack: dataStack)
@@ -102,31 +106,48 @@ extension DiffableDataSource {
// MARK: - UITableViewDataSource
@objc
@MainActor
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@MainActor
public dynamic func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
titleForHeaderInSection section: Int
) -> String? {
return self.sectionID(for: section)
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
titleForFooterInSection section: Int
) -> String? {
return nil
}
@objc
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
guard let objectID = self.itemID(for: indexPath) else {
@@ -144,28 +165,49 @@ extension DiffableDataSource {
}
@objc
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
canEditRowAt indexPath: IndexPath
) -> Bool {
return true
}
@objc
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
editingStyleForRowAt indexPath: IndexPath
) -> UITableViewCell.EditingStyle {
return .delete
}
@objc
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath
) {}
@objc
open dynamic func sectionIndexTitles(for tableView: UITableView) -> [String]? {
@MainActor
open dynamic func sectionIndexTitles(
for tableView: UITableView
) -> [String]? {
return nil
}
@objc
open dynamic func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
sectionForSectionIndexTitle title: String,
at index: Int
) -> Int {
return index
}
@@ -241,13 +283,13 @@ extension DiffableDataSource {
self.base?.moveRow(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
guard let base = self.base else {
return
}
base.performBatchUpdates(updates)
base.performBatchUpdates(updates, completion: { _ in completion() })
}
public func reloadData() {

View File

@@ -98,7 +98,7 @@ public protocol DiffableDataSourceTarget {
/**
Animates multiple insert, delete, reload, and move operations as a group.
*/
func performBatchUpdates(updates: () -> Void, animated: Bool)
func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void)
/**
Reloads all sections and items.
@@ -114,9 +114,15 @@ extension DiffableDataSource.Target {
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
animated: Bool,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
setData: (C) -> Void,
completion: @escaping () -> Void
) {
let group = DispatchGroup()
defer {
group.notify(queue: .main, execute: completion)
}
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
setData(data)
@@ -133,6 +139,7 @@ extension DiffableDataSource.Target {
self.reloadData()
return
}
group.enter()
self.performBatchUpdates(
updates: {
@@ -206,7 +213,8 @@ extension DiffableDataSource.Target {
)
}
},
animated: animated
animated: animated,
completion: group.leave
)
}
}

View File

@@ -44,19 +44,30 @@ internal protocol DiffableDataSourceSnapshotProtocol {
func indexOfItem(_ identifier: NSManagedObjectID) -> Int?
func indexOfSection(_ identifier: String) -> Int?
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?)
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID)
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID)
mutating func deleteItems(_ identifiers: [NSManagedObjectID])
mutating func appendItems<C: Collection>(_ identifiers: C, toSection sectionIdentifier: String?) where C.Element == NSManagedObjectID
mutating func insertItems<C: Collection>(_ identifiers: C, beforeItem beforeIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID
mutating func insertItems<C: Collection>(_ identifiers: C, afterItem afterIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID
mutating func deleteItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID
mutating func deleteAllItems()
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID)
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID)
mutating func reloadItems(_ identifiers: [NSManagedObjectID])
mutating func appendSections(_ identifiers: [String])
mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String)
mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String)
mutating func deleteSections(_ identifiers: [String])
mutating func reloadItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID
mutating func appendSections<C: Collection>(_ identifiers: C) where C.Element == String
mutating func insertSections<C: Collection>(_ identifiers: C, beforeSection toIdentifier: String) where C.Element == String
mutating func insertSections<C: Collection>(_ identifiers: C, afterSection toIdentifier: String) where C.Element == String
mutating func deleteSections<C: Collection>(_ identifiers: C) where C.Element == String
mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String)
mutating func moveSection(_ identifier: String, afterSection toIdentifier: String)
mutating func reloadSections(_ identifiers: [String])
mutating func reloadSections<C: Collection>(_ identifiers: C) where C.Element == String
mutating func unsafeAppendItems<C: Collection>(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID
mutating func unsafeInsertItems<C: Collection>(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID
mutating func unsafeDeleteItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath
mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath)
mutating func unsafeReloadItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath
mutating func unsafeInsertSections<C: Collection>(_ identifiers: C, at sectionIndex: Int) where C.Element == String
mutating func unsafeDeleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int
mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int)
mutating func unsafeReloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int
}

View File

@@ -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) {

View File

@@ -200,7 +200,7 @@ extension Internals {
self.structure.insert(itemIDs: identifiers, after: afterIdentifier)
}
mutating func deleteItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
mutating func deleteItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID {
self.structure.remove(itemIDs: identifiers)
}
@@ -220,7 +220,7 @@ extension Internals {
self.structure.move(itemID: identifier, after: toIdentifier)
}
mutating func reloadItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
mutating func reloadItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID {
self.structure.update(itemIDs: identifiers)
}
@@ -240,7 +240,7 @@ extension Internals {
self.structure.insert(sectionIDs: identifiers, after: toIdentifier)
}
mutating func deleteSections<S: Sequence>(_ identifiers: S) where S.Element == String {
mutating func deleteSections<C: Collection>(_ identifiers: C) where C.Element == String {
self.structure.remove(sectionIDs: identifiers)
}
@@ -255,10 +255,55 @@ extension Internals {
self.structure.move(sectionID: identifier, after: toIdentifier)
}
mutating func reloadSections<S: Sequence>(_ identifiers: S) where S.Element == String {
mutating func reloadSections<C: Collection>(_ identifiers: C) where C.Element == String {
self.structure.update(sectionIDs: identifiers)
}
mutating func unsafeAppendItems<C: Collection>(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID {
self.structure.unsafeAppend(identifiers, toSectionAt: sectionIndex)
}
mutating func unsafeInsertItems<C: Collection>(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID {
self.structure.unsafeInsert(itemIDs: identifiers, at: indexPath)
}
mutating func unsafeDeleteItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath {
self.structure.unsafeRemove(itemsAt: indexPaths)
}
mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) {
self.structure.unsafeMove(itemAt: indexPath, to: newIndexPath)
}
mutating func unsafeReloadItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath {
self.structure.unsafeUpdate(itemsAt: indexPaths)
}
mutating func unsafeInsertSections<C: Collection>(_ identifiers: C, at sectionIndex: Int) where C.Element == String {
self.structure.unsafeInsert(identifiers, at: sectionIndex)
}
mutating func unsafeDeleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.structure.unsafeRemove(sectionsAt: sectionIndices)
}
mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int) {
self.structure.unsafeMove(sectionAt: sectionIndex, to: newSectionIndex)
}
mutating func unsafeReloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.structure.unsafeUpdate(sectionsAt: sectionIndices)
}
// MARK: Private
@@ -432,13 +477,23 @@ extension Internals {
}
return self.sections[sectionIndex].elements.map({ $0.differenceIdentifier })
}
func unsafeItem(at indexPath: IndexPath) -> NSManagedObjectID {
return self.sections[indexPath.section]
.elements[indexPath.item]
.differenceIdentifier
}
func section(containing itemID: NSManagedObjectID) -> String? {
return self.itemPositionMap(itemID)?.section.differenceIdentifier
}
mutating func append<C: Collection>(itemIDs: C, to sectionID: String?) where C.Element == NSManagedObjectID {
mutating func append<C: Collection>(
itemIDs: C,
to sectionID: String?
) where C.Element == NSManagedObjectID {
let index: Array<Section>.Index
if let sectionID = sectionID {
@@ -461,8 +516,30 @@ extension Internals {
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[index].elements.append(contentsOf: items)
}
mutating func unsafeAppend<C: Collection>(
_ itemIDs: C,
toSectionAt sectionIndex: Int?
) where C.Element == NSManagedObjectID {
let index: Array<Section>.Index
if let sectionIndex = sectionIndex {
mutating func insert<C: Collection>(itemIDs: C, before beforeItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
index = sectionIndex
}
else {
let section = self.sections
index = section.index(before: section.endIndex)
}
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[index].elements.append(contentsOf: items)
}
mutating func insert<C: Collection>(
itemIDs: C,
before beforeItemID: NSManagedObjectID
) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
@@ -473,7 +550,10 @@ extension Internals {
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
}
mutating func insert<C: Collection>(itemIDs: C, after afterItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
mutating func insert<C: Collection>(
itemIDs: C,
after afterItemID: NSManagedObjectID
) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap(afterItemID) else {
@@ -485,6 +565,16 @@ extension Internals {
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemIndex)
}
mutating func unsafeInsert<C: Collection>(
itemIDs: C,
at indexPath: IndexPath
) where C.Element == NSManagedObjectID {
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[indexPath.section].elements
.insert(contentsOf: items, at: indexPath.item)
}
mutating func remove<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
@@ -508,6 +598,23 @@ extension Internals {
}
}
}
mutating func unsafeRemove<S: Sequence>(itemsAt indexPaths: S) where S.Element == IndexPath {
var removeIndexSetMap: [Int: IndexSet] = [:]
for indexPath in indexPaths {
removeIndexSetMap[indexPath.section, default: []]
.insert(indexPath.item)
}
for (sectionIndex, removeIndexSet) in removeIndexSetMap {
for range in removeIndexSet.rangeView.reversed() {
self.sections[sectionIndex].elements.removeSubrange(range)
}
}
}
mutating func removeAllItems() {
@@ -522,7 +629,10 @@ extension Internals {
self.sections.removeAll(where: { $0.elements.isEmpty })
}
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
mutating func move(
itemID: NSManagedObjectID,
before beforeItemID: NSManagedObjectID
) {
guard let removed = self.remove(itemID: itemID) else {
@@ -536,7 +646,10 @@ extension Internals {
.insert(removed, at: itemPosition.itemRelativeIndex)
}
mutating func move(itemID: NSManagedObjectID, after afterItemID: NSManagedObjectID) {
mutating func move(
itemID: NSManagedObjectID,
after afterItemID: NSManagedObjectID
) {
guard let removed = self.remove(itemID: itemID) else {
@@ -551,6 +664,17 @@ extension Internals {
self.sections[itemPosition.sectionIndex].elements
.insert(removed, at: itemIndex)
}
mutating func unsafeMove(
itemAt indexPath: IndexPath,
to newIndexPath: IndexPath
) {
let itemID = self.sections[indexPath.section].elements
.remove(at: indexPath.item)
self.sections[newIndexPath.section].elements
.insert(itemID, at: newIndexPath.item)
}
mutating func update<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
@@ -568,6 +692,18 @@ extension Internals {
}
self.reloadedItems.formUnion(newItemIDs)
}
mutating func unsafeUpdate<S: Sequence>(itemsAt indexPaths: S) where S.Element == IndexPath {
var newItemIDs: Set<NSManagedObjectID> = []
for indexPath in indexPaths {
self.sections[indexPath.section]
.elements[indexPath.item].isReloaded = true
newItemIDs.insert(self.unsafeItem(at: indexPath))
}
self.reloadedItems.formUnion(newItemIDs)
}
mutating func append<C: Collection>(sectionIDs: C) where C.Element == String {
@@ -582,7 +718,10 @@ extension Internals {
self.sections.append(contentsOf: newSections)
}
mutating func insert<C: Collection>(sectionIDs: C, before beforeSectionID: String) where C.Element == String {
mutating func insert<C: Collection>(
sectionIDs: C,
before beforeSectionID: String
) where C.Element == String {
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
@@ -599,7 +738,10 @@ extension Internals {
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func insert<C: Collection>(sectionIDs: C, after afterSectionID: String) where C.Element == String {
mutating func insert<C: Collection>(
sectionIDs: C,
after afterSectionID: String
) where C.Element == String {
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
@@ -616,6 +758,22 @@ extension Internals {
}
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func unsafeInsert<C: Collection>(
_ sectionIDs: C,
at sectionIndex: Int
) where C.Element == String {
let sectionIndexTransformer = self.sectionIndexTransformer
let newSections = sectionIDs.lazy.map {
return Section(
differenceIdentifier: $0,
indexTitle: sectionIndexTransformer($0)
)
}
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func remove<S: Sequence>(sectionIDs: S) where S.Element == String {
@@ -624,6 +782,16 @@ extension Internals {
self.remove(sectionID: sectionID)
}
}
mutating func unsafeRemove<S: Sequence>(
sectionsAt sectionIndices: S
) where S.Element == Int {
for sectionIndex in sectionIndices.sorted(by: >) {
self.sections.remove(at: sectionIndex)
}
}
mutating func move(sectionID: String, before beforeSectionID: String) {
@@ -651,8 +819,21 @@ extension Internals {
let sectionIndex = self.sections.index(after: beforeIndex)
self.sections.insert(removed, at: sectionIndex)
}
mutating func unsafeMove(
sectionAt sectionIndex: Int,
to newSectionIndex: Int
) {
self.sections.move(
fromOffsets: .init(integer: sectionIndex),
toOffset: newSectionIndex
)
}
mutating func update<S: Sequence>(sectionIDs: S) where S.Element == String {
mutating func update<S: Sequence>(
sectionIDs: S
) where S.Element == String {
for sectionID in sectionIDs {
@@ -663,6 +844,16 @@ extension Internals {
self.sections[sectionIndex].isReloaded = true
}
}
mutating func unsafeUpdate<S: Sequence>(
sectionsAt sectionIndices: S
) where S.Element == Int {
for sectionIndex in sectionIndices {
self.sections[sectionIndex].isReloaded = true
}
}
// MARK: Private

View File

@@ -59,16 +59,14 @@ extension Internals {
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) -> Void
) {
self.apply(
.init(),
target: target,
animatingDifferences: animatingDifferences,
performUpdates: performUpdates,
completion: completion
performUpdates: performUpdates
)
}
@@ -80,8 +78,7 @@ extension Internals {
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) -> Void
) {
self.dispatcher.dispatch { [weak self] in
@@ -112,7 +109,6 @@ extension Internals {
#if canImport(QuartzCore)
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
if !animatingDifferences {
@@ -122,11 +118,9 @@ extension Internals {
CATransaction.commit()
#else
performDiffingUpdates()
completion()
#endif

View File

@@ -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
)
}

View File

@@ -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)
}

View File

@@ -393,19 +393,31 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
observer,
willChange: { (observer, monitor) in
observer.listMonitorWillChange(monitor)
observer.listMonitorWillChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didChange: { (observer, monitor) in
observer.listMonitorDidChange(monitor)
observer.listMonitorDidChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
willRefetch: { (observer, monitor) in
observer.listMonitorWillRefetch(monitor)
observer.listMonitorWillRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didRefetch: { (observer, monitor) in
observer.listMonitorDidRefetch(monitor)
observer.listMonitorDidRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
}
@@ -428,38 +440,71 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
observer,
willChange: { (observer, monitor) in
observer.listMonitorWillChange(monitor)
observer.listMonitorWillChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didChange: { (observer, monitor) in
observer.listMonitorDidChange(monitor)
observer.listMonitorDidChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
willRefetch: { (observer, monitor) in
observer.listMonitorWillRefetch(monitor)
observer.listMonitorWillRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didRefetch: { (observer, monitor) in
observer.listMonitorDidRefetch(monitor)
observer.listMonitorDidRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
self.registerObserver(
observer,
didInsertObject: { (observer, monitor, object, toIndexPath) in
observer.listMonitor(monitor, didInsertObject: object, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didInsertObject: object,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
observer.listMonitor(monitor, didDeleteObject: object, fromIndexPath: fromIndexPath)
observer.listMonitor(
monitor,
didDeleteObject: object,
fromIndexPath: fromIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didUpdateObject: { (observer, monitor, object, atIndexPath) in
observer.listMonitor(monitor, didUpdateObject: object, atIndexPath: atIndexPath)
observer.listMonitor(
monitor,
didUpdateObject: object,
atIndexPath: atIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
observer.listMonitor(monitor, didMoveObject: object, fromIndexPath: fromIndexPath, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didMoveObject: object,
fromIndexPath: fromIndexPath,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
}
@@ -482,49 +527,92 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
observer,
willChange: { (observer, monitor) in
observer.listMonitorWillChange(monitor)
observer.listMonitorWillChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didChange: { (observer, monitor) in
observer.listMonitorDidChange(monitor)
observer.listMonitorDidChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
willRefetch: { (observer, monitor) in
observer.listMonitorWillRefetch(monitor)
observer.listMonitorWillRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didRefetch: { (observer, monitor) in
observer.listMonitorDidRefetch(monitor)
observer.listMonitorDidRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
self.registerObserver(
observer,
didInsertObject: { (observer, monitor, object, toIndexPath) in
observer.listMonitor(monitor, didInsertObject: object, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didInsertObject: object,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
observer.listMonitor(monitor, didDeleteObject: object, fromIndexPath: fromIndexPath)
observer.listMonitor(
monitor,
didDeleteObject: object,
fromIndexPath: fromIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didUpdateObject: { (observer, monitor, object, atIndexPath) in
observer.listMonitor(monitor, didUpdateObject: object, atIndexPath: atIndexPath)
observer.listMonitor(
monitor,
didUpdateObject: object,
atIndexPath: atIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
observer.listMonitor(monitor, didMoveObject: object, fromIndexPath: fromIndexPath, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didMoveObject: object,
fromIndexPath: fromIndexPath,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
self.registerObserver(
observer,
didInsertSection: { (observer, monitor, sectionInfo, toIndex) in
observer.listMonitor(monitor, didInsertSection: sectionInfo, toSectionIndex: toIndex)
observer.listMonitor(
monitor,
didInsertSection: sectionInfo,
toSectionIndex: toIndex,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didDeleteSection: { (observer, monitor, sectionInfo, fromIndex) in
observer.listMonitor(monitor, didDeleteSection: sectionInfo, fromSectionIndex: fromIndex)
observer.listMonitor(
monitor,
didDeleteSection: sectionInfo,
fromSectionIndex: fromIndex,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
}
@@ -555,11 +643,18 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
*/
public func refetch(_ fetchClauses: FetchClause...) {
public func refetch(
_ fetchClauses: FetchClause...,
sourceIdentifier: Any?
) {
self.refetch(fetchClauses)
self.refetch(
fetchClauses,
sourceIdentifier: sourceIdentifier
)
}
/**
@@ -568,14 +663,21 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
*/
public func refetch(_ fetchClauses: [FetchClause]) {
public func refetch(
_ fetchClauses: [FetchClause],
sourceIdentifier: Any?
) {
self.refetch { (fetchRequest) in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
self.refetch(
{ (fetchRequest) in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
},
sourceIdentifier: sourceIdentifier
)
}
@@ -627,7 +729,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
// MARK: Internal
internal convenience init(dataStack: DataStack, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -639,7 +746,13 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal convenience init(dataStack: DataStack, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<O>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListMonitor<O>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -651,7 +764,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -663,7 +781,13 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<O>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListMonitor<O>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -675,7 +799,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>) -> Void) {
internal func registerChangeNotification(
_ notificationKey: UnsafeRawPointer,
name: Notification.Name,
toObserver observer: AnyObject,
callback: @escaping (_ monitor: ListMonitor<O>) -> Void
) {
Internals.setAssociatedRetainedObject(
Internals.NotificationObserver(
@@ -695,7 +824,16 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>, _ object: O, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) {
internal func registerObjectNotification(
_ notificationKey: UnsafeRawPointer,
name: Notification.Name,
toObserver observer: AnyObject,
callback: @escaping (
_ monitor: ListMonitor<O>,
_ object: O,
_ indexPath: IndexPath?,
_ newIndexPath: IndexPath?
) -> Void) {
Internals.setAssociatedRetainedObject(
Internals.NotificationObserver(
@@ -722,7 +860,16 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) {
internal func registerSectionNotification(
_ notificationKey: UnsafeRawPointer,
name: Notification.Name,
toObserver observer: AnyObject,
callback: @escaping (
_ monitor: ListMonitor<O>,
_ sectionInfo: NSFetchedResultsSectionInfo,
_ sectionIndex: Int
) -> Void
) {
Internals.setAssociatedRetainedObject(
Internals.NotificationObserver(
@@ -745,7 +892,24 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void) {
internal func registerObserver<U: AnyObject>(
_ observer: U,
willChange: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void,
didChange: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void,
willRefetch: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void,
didRefetch: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void) {
Internals.assert(
Thread.isMainThread,
@@ -805,7 +969,33 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) {
internal func registerObserver<U: AnyObject>(
_ observer: U,
didInsertObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ toIndexPath: IndexPath
) -> Void,
didDeleteObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ fromIndexPath: IndexPath
) -> Void,
didUpdateObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ atIndexPath: IndexPath
) -> Void,
didMoveObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ fromIndexPath: IndexPath,
_ toIndexPath: IndexPath
) -> Void) {
Internals.assert(
Thread.isMainThread,
@@ -866,7 +1056,20 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) {
internal func registerObserver<U: AnyObject>(
_ observer: U,
didInsertSection: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ sectionInfo: NSFetchedResultsSectionInfo,
_ toIndex: Int
) -> Void,
didDeleteSection: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ sectionInfo: NSFetchedResultsSectionInfo,
_ fromIndex: Int
) -> Void) {
Internals.assert(
Thread.isMainThread,
@@ -922,7 +1125,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
Internals.setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteSectionKey, inObject: observer)
}
internal func refetch(_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal func refetch(
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
sourceIdentifier: Any?
) {
Internals.assert(
Thread.isMainThread,
@@ -987,10 +1193,15 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
self.isPendingRefetch = false
newFetchedResultsController.managedObjectContext.saveMetadata = .init(
isSavingSynchronously: false,
sourceIdentifier: sourceIdentifier
)
NotificationCenter.default.post(
name: Notification.Name.listMonitorDidRefetchList,
object: self
)
newFetchedResultsController.managedObjectContext.saveMetadata = nil
}
}
}
@@ -1051,7 +1262,15 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
}
}
private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedResultsControllerDelegate) {
private static func recreateFetchedResultsController(
context: NSManagedObjectContext,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) -> (
controller: Internals.CoreStoreFetchedResultsController,
delegate: Internals.FetchedResultsControllerDelegate
) {
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
fetchRequest.fetchLimit = 0
@@ -1077,7 +1296,14 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
private let from: From<O>
private let sectionBy: SectionBy<O>?
private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListMonitor<O>) -> Void)?) {
private init(
context: NSManagedObjectContext,
transactionQueue: DispatchQueue,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: ((ListMonitor<O>) -> Void)?
) {
self.isSectioned = (sectionBy != nil)
self.from = from
@@ -1124,7 +1350,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
return
}
self.refetch(self.applyFetchClauses)
self.refetch(self.applyFetchClauses, sourceIdentifier: nil)
}
)
@@ -1148,7 +1374,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
if previousStores != currentStores {
self.refetch(self.applyFetchClauses)
self.refetch(self.applyFetchClauses, sourceIdentifier: nil)
}
}
@@ -1294,7 +1520,13 @@ extension ListMonitor: FetchedResultsControllerHandler {
return self.sectionByIndexTransformer
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeObject anObject: Any, atIndexPath indexPath: IndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
internal func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeObject anObject: Any,
atIndexPath indexPath: IndexPath?,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?
) {
switch type {
@@ -1344,7 +1576,12 @@ extension ListMonitor: FetchedResultsControllerHandler {
}
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
internal func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
atIndex sectionIndex: Int,
forChangeType type: NSFetchedResultsChangeType
) {
switch type {
@@ -1373,7 +1610,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
}
}
internal func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
internal func controllerWillChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
self.taskGroup.enter()
NotificationCenter.default.post(
@@ -1382,7 +1621,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
)
}
internal func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
internal func controllerDidChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
defer {

View File

@@ -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
) {}
}

View File

@@ -135,8 +135,19 @@ extension ListPublisher {
func cancel() {
self.publisher.removeObserver(self)
self.subscriber = nil
if Thread.isMainThread {
self.publisher.removeObserver(self)
}
else {
DispatchQueue.main.async {
self.publisher.removeObserver(self)
}
}
}

View File

@@ -76,13 +76,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
/**
A snapshot of the latest state of this list
*/
public fileprivate(set) var snapshot: ListSnapshot<O> = .init() {
didSet {
self.notifyObservers()
}
}
public private(set) var snapshot: ListSnapshot<O> = .init()
// MARK: Public (Observers)
@@ -111,7 +105,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
)
self.observers.setObject(
Internals.Closure(callback),
Internals.Closure({ callback($0.listPublisher) }),
forKey: observer
)
if notifyInitial {
@@ -119,6 +113,44 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
callback(self)
}
}
/**
Registers an object as an observer to be notified when changes to the `ListPublisher`'s snapshot occur.
To prevent retain-cycles, `ListPublisher` only keeps `weak` references to its observers.
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
Calling `addObserver(_:_:)` multiple times on the same observer is safe.
- parameter observer: an object to become owner of the specified `callback`
- parameter notifyInitial: if `true`, the callback is executed immediately with the current publisher state. Otherwise only succeeding updates will notify the observer. Default value is `false`.
- parameter initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
- parameter callback: the closure to execute when changes occur
*/
public func addObserver<T: AnyObject>(
_ observer: T,
notifyInitial: Bool = false,
initialSourceIdentifier: Any? = nil,
_ callback: @escaping (
_ listPublisher: ListPublisher<O>,
_ sourceIdentifier: Any?
) -> Void
) {
Internals.assert(
Thread.isMainThread,
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
)
self.observers.setObject(
Internals.Closure(callback),
forKey: observer
)
if notifyInitial {
callback(self, initialSourceIdentifier)
}
}
/**
Unregisters an object from receiving notifications for changes to the `ListPublisher`'s snapshot.
@@ -149,8 +181,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
*/
public func refetch<B: FetchChainableBuilderType>(_ clauseChain: B) throws where B.ObjectType == O {
public func refetch<B: FetchChainableBuilderType>(
_ clauseChain: B,
sourceIdentifier: Any? = nil
) throws where B.ObjectType == O {
try self.refetch(
from: clauseChain.from,
@@ -158,7 +194,8 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
applyFetchClauses: { (fetchRequest) in
clauseChain.fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
},
sourceIdentifier: sourceIdentifier
)
}
@@ -173,8 +210,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
```
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
*/
public func refetch<B: SectionMonitorBuilderType>(_ clauseChain: B) throws where B.ObjectType == O {
public func refetch<B: SectionMonitorBuilderType>(
_ clauseChain: B,
sourceIdentifier: Any? = nil
) throws where B.ObjectType == O {
try self.refetch(
from: clauseChain.from,
@@ -182,7 +223,8 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
applyFetchClauses: { (fetchRequest) in
clauseChain.fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
},
sourceIdentifier: sourceIdentifier
)
}
@@ -230,7 +272,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
internal private(set) lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -241,7 +288,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -252,7 +305,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -263,7 +321,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -274,7 +338,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal func refetch(from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) throws {
internal func refetch(
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
sourceIdentifier: Any?
) throws {
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
context: self.fetchedResultsController.managedObjectContext,
@@ -291,7 +360,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = (newFetchedResultsController, newFetchedResultsControllerDelegate)
newFetchedResultsControllerDelegate.handler = self
newFetchedResultsController.managedObjectContext.saveMetadata = .init(
isSavingSynchronously: true,
sourceIdentifier: sourceIdentifier
)
try newFetchedResultsController.performFetchFromSpecifiedStores()
newFetchedResultsController.managedObjectContext.saveMetadata = nil
}
deinit {
@@ -301,6 +376,20 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
}
// MARK: FilePrivate
fileprivate typealias ObserverClosureType = Internals.Closure<(listPublisher: ListPublisher<O>, sourceIdentifier: Any?), Void>
fileprivate func set(
snapshot: ListSnapshot<O>,
sourceIdentifier: Any?
) {
self.snapshot = snapshot
self.notifyObservers(sourceIdentifier: sourceIdentifier)
}
// MARK: Private
private var query: (
@@ -315,9 +404,17 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
private var observerForWillChangePersistentStore: Internals.NotificationObserver!
private var observerForDidChangePersistentStore: Internals.NotificationObserver!
private lazy var observers: NSMapTable<AnyObject, Internals.Closure<ListPublisher<O>, Void>> = .weakToStrongObjects()
private lazy var observers: NSMapTable<AnyObject, ObserverClosureType> = .weakToStrongObjects()
private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate) {
private static func recreateFetchedResultsController(
context: NSManagedObjectContext,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) -> (
controller: Internals.CoreStoreFetchedResultsController,
delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate
) {
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
fetchRequest.fetchLimit = 0
@@ -339,7 +436,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
return (fetchedResultsController, fetchedResultsControllerDelegate)
}
private init(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListPublisher<ObjectType>) -> Void)?) {
private init(
context: NSManagedObjectContext,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: ((ListPublisher<ObjectType>) -> Void)?
) {
self.query = (
from: from,
@@ -359,15 +462,19 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
}
private func notifyObservers() {
private func notifyObservers(sourceIdentifier: Any?) {
guard let enumerator = self.observers.objectEnumerator() else {
return
}
let arguments: ObserverClosureType.Arguments = (
listPublisher: self,
sourceIdentifier: sourceIdentifier
)
for closure in enumerator {
(closure as! Internals.Closure<ListPublisher<O>, Void>).invoke(with: self)
(closure as! ObserverClosureType).invoke(with: arguments)
}
}
}
@@ -384,11 +491,18 @@ extension ListPublisher: FetchedDiffableDataSourceSnapshotHandler {
return self.query.sectionIndexTransformer
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
internal func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot
) {
self.snapshot = .init(
diffableSnapshot: snapshot,
context: controller.managedObjectContext
let context = controller.managedObjectContext
self.set(
snapshot: .init(
diffableSnapshot: snapshot,
context: context
),
sourceIdentifier: context.saveMetadata?.sourceIdentifier
)
}
}

View File

@@ -100,7 +100,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will raise an exception.
- returns: the `ObjectPublisher<O>` interfacing the object at the specified section and item index
*/
public subscript(sectionIndex: Int, itemIndex: Int) -> ObjectPublisher<O> {
public subscript(
sectionIndex: Int,
itemIndex: Int
) -> ObjectPublisher<O> {
let context = self.context!
let snapshot = self.diffableSnapshot
@@ -116,7 +119,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`.
- returns: the `ObjectPublisher<O>` interfacing the object at the specified section and item index, or `nil` if out of bounds
*/
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectPublisher<O>? {
public subscript(
safeSectionIndex sectionIndex: Int,
safeItemIndex itemIndex: Int
) -> ObjectPublisher<O>? {
guard let context = self.context else {
@@ -333,7 +339,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter indices: the positions of the itemIDs to return. Specifying an invalid value will raise an exception.
- returns: the `ItemID` array belonging to the given `SectionID` at the specified indices
*/
public func itemIDs<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices indices: S) -> [ItemID] where S.Element == Int {
public func itemIDs<S: Sequence>(
inSectionWithID sectionID: SectionID,
atIndices indices: S
) -> [ItemID] where S.Element == Int {
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
return indices.map({ itemIDs[$0] })
@@ -398,7 +407,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndices: the positions of items within the section. Specifying an invalid value will raise an exception.
- returns: an array of `ObjectPublisher`s for the items in the specified `SectionID` and indices
*/
public func items<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices itemIndices: S) -> [ObjectPublisher<O>] where S.Element == Int {
public func items<S: Sequence>(
inSectionWithID sectionID: SectionID,
atIndices itemIndices: S
) -> [ObjectPublisher<O>] where S.Element == Int {
let context = self.context!
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
@@ -446,7 +458,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndices: the positions of items within the section. Specifying an invalid value will raise an exception.
- returns: a lazy sequence of `ObjectPublisher`s for the items in the specified `SectionID` and indices
*/
public func lazy<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices itemIndices: S) -> LazyMapSequence<S, ObjectPublisher<O>> where S.Element == Int {
public func lazy<S: Sequence>(
inSectionWithID sectionID: SectionID,
atIndices itemIndices: S
) -> LazyMapSequence<S, ObjectPublisher<O>> where S.Element == Int {
let context = self.context!
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
@@ -466,9 +481,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to append
- parameter sectionID: the section to append the items to
*/
public mutating func appendItems<C: Collection>(withIDs itemIDs: C, toSectionWithID sectionID: SectionID? = nil) where C.Element == ItemID {
public mutating func appendItems<C: Collection>(
withIDs itemIDs: C,
toSectionWithID sectionID: SectionID? = nil
) where C.Element == ItemID {
self.diffableSnapshot.appendItems(itemIDs, toSection: sectionID)
self.mutate {
$0.appendItems(itemIDs, toSection: sectionID)
}
}
/**
Appends extra items to the specified section index
- parameter itemIDs: the object identifiers for the objects to append
- parameter sectionIndex: the section index to append the items to. Specifying an invalid value will raise an exception.
*/
public mutating func appendItems<C: Collection>(
with itemIDs: C,
toSectionAt sectionIndex: Int
) where C.Element == ItemID {
self.mutate {
$0.unsafeAppendItems(itemIDs, toSectionAt: sectionIndex)
}
}
/**
@@ -477,9 +515,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to insert
- parameter beforeItemID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, beforeItemID: ItemID) where C.Element == ItemID {
public mutating func insertItems<C: Collection>(
withIDs itemIDs: C,
beforeItemID: ItemID
) where C.Element == ItemID {
self.diffableSnapshot.insertItems(itemIDs, beforeItem: beforeItemID)
self.mutate {
$0.insertItems(itemIDs, beforeItem: beforeItemID)
}
}
/**
@@ -488,9 +532,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to insert
- parameter beforeItemID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, afterItemID: ItemID) where C.Element == ItemID {
public mutating func insertItems<C: Collection>(
withIDs itemIDs: C,
afterItemID: ItemID
) where C.Element == ItemID {
self.diffableSnapshot.insertItems(itemIDs, afterItem: afterItemID)
self.mutate {
$0.insertItems(itemIDs, afterItem: afterItemID)
}
}
/**
Inserts extra items at a specified index path
- parameter itemIDs: the object identifiers for the objects to insert
- parameter indexPath: an indexPath to insert the items into. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems<C: Collection>(
withIDs itemIDs: C,
at indexPath: IndexPath
) where C.Element == ItemID {
self.mutate {
$0.unsafeInsertItems(itemIDs, at: indexPath)
}
}
/**
@@ -498,9 +565,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to delete
*/
public mutating func deleteItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
public mutating func deleteItems<C: Collection>(withIDs itemIDs: C) where C.Element == ItemID {
self.diffableSnapshot.deleteItems(itemIDs)
self.mutate {
$0.deleteItems(itemIDs)
}
}
/**
Deletes the items at the specified index paths
- parameter itemIndexPaths: the index paths for the objects to delete. Specifying an invalid value will raise an exception.
*/
public mutating func deleteItems<C: Collection>(at itemIndexPaths: C) where C.Element == IndexPath {
self.mutate {
$0.unsafeDeleteItems(at: itemIndexPaths)
}
}
/**
@@ -508,7 +591,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
*/
public mutating func deleteAllItems() {
self.diffableSnapshot.deleteAllItems()
self.mutate {
$0.deleteAllItems()
}
}
/**
@@ -517,9 +603,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemID: an object identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter beforeItemID: another identifier to move the item before of. Specifying an invalid value will raise an exception.
*/
public mutating func moveItem(withID itemID: ItemID, beforeItemID: ItemID) {
public mutating func moveItem(
withID itemID: ItemID,
beforeItemID: ItemID
) {
self.diffableSnapshot.moveItem(itemID, beforeItem: beforeItemID)
self.mutate {
$0.moveItem(itemID, beforeItem: beforeItemID)
}
}
/**
@@ -528,9 +620,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemID: an object identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter beforeItemID: another identifier to move the item after of. Specifying an invalid value will raise an exception.
*/
public mutating func moveItem(withID itemID: ItemID, afterItemID: ItemID) {
public mutating func moveItem(
withID itemID: ItemID,
afterItemID: ItemID
) {
self.diffableSnapshot.moveItem(itemID, afterItem: afterItemID)
self.mutate {
$0.moveItem(itemID, afterItem: afterItemID)
}
}
/**
Moves an item at an index path to a new index path
- parameter itemIndexPath: an index path in the list to move. Specifying an invalid value will raise an exception.
- parameter newIndexPath: the new index path to move the item into. Specifying an invalid value will raise an exception.
*/
public mutating func moveItem(
at itemIndexPath: IndexPath,
to newIndexPath: IndexPath
) {
self.mutate {
$0.unsafeMoveItem(at: itemIndexPath, to: newIndexPath)
}
}
/**
@@ -538,9 +653,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers to reload
*/
public mutating func reloadItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
public mutating func reloadItems<C: Collection>(withIDs itemIDs: C) where C.Element == ItemID {
self.diffableSnapshot.reloadItems(itemIDs)
self.mutate {
$0.reloadItems(itemIDs)
}
}
/**
Marks the specified index paths as reloaded
- parameter itemIndexPaths: the index paths to reload. Specifying an invalid value will raise an exception.
*/
public mutating func reloadItems<C: Collection>(at itemIndexPaths: C) where C.Element == IndexPath {
self.mutate {
$0.unsafeReloadItems(at: itemIndexPaths)
}
}
/**
@@ -550,7 +681,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
*/
public mutating func appendSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
self.diffableSnapshot.appendSections(sectionIDs)
self.mutate {
$0.appendSections(sectionIDs)
}
}
/**
@@ -559,9 +693,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter beforeSectionID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, beforeSectionID: SectionID) where C.Element == SectionID {
public mutating func insertSections<C: Collection>(
withIDs sectionIDs: C,
beforeSectionID: SectionID
) where C.Element == SectionID {
self.diffableSnapshot.insertSections(sectionIDs, beforeSection: beforeSectionID)
self.mutate {
$0.insertSections(sectionIDs, beforeSection: beforeSectionID)
}
}
/**
@@ -570,9 +710,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter beforeSectionID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, afterSectionID: SectionID) where C.Element == SectionID {
public mutating func insertSections<C: Collection>(
withIDs sectionIDs: C,
afterSectionID: SectionID
) where C.Element == SectionID {
self.diffableSnapshot.insertSections(sectionIDs, afterSection: afterSectionID)
self.mutate {
$0.insertSections(sectionIDs, afterSection: afterSectionID)
}
}
/**
Inserts new sections into an existing section index
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter sectionIndex: an existing section index to insert items into. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections<C: Collection>(
_ sectionIDs: C,
at sectionIndex: Int
) where C.Element == String {
self.mutate {
$0.unsafeInsertSections(sectionIDs, at: sectionIndex)
}
}
/**
@@ -580,9 +743,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to delete
*/
public mutating func deleteSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
public mutating func deleteSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
self.diffableSnapshot.deleteSections(sectionIDs)
self.mutate {
$0.deleteSections(sectionIDs)
}
}
/**
Deletes the specified section indices
- parameter sectionIndices: the section indices to delete. Specifying an invalid value will raise an exception.
*/
public mutating func deleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.mutate {
$0.unsafeDeleteSections(at: sectionIndices)
}
}
/**
@@ -591,9 +770,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionID: a section identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter beforeSectionID: another identifier to move the section before of. Specifying an invalid value will raise an exception.
*/
public mutating func moveSection(withID sectionID: SectionID, beforeSectionID: SectionID) {
public mutating func moveSection(
withID sectionID: SectionID,
beforeSectionID: SectionID
) {
self.diffableSnapshot.moveSection(sectionID, beforeSection: beforeSectionID)
self.mutate {
$0.moveSection(sectionID, beforeSection: beforeSectionID)
}
}
/**
@@ -602,9 +787,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionID: a section identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter afterSectionID: another identifier to move the section after of. Specifying an invalid value will raise an exception.
*/
public mutating func moveSection(withID sectionID: SectionID, afterSectionID: SectionID) {
public mutating func moveSection(
withID sectionID: SectionID,
afterSectionID: SectionID
) {
self.diffableSnapshot.moveSection(sectionID, afterSection: afterSectionID)
self.mutate {
$0.moveSection(sectionID, afterSection: afterSectionID)
}
}
/**
Moves a section at a specified index to a new index
- parameter sectionIndex: a section index in the list to move. Specifying an invalid value will raise an exception.
- parameter newSectionIndex: the new section index to move into. Specifying an invalid value will raise an exception.
*/
public mutating func moveSection(
at sectionIndex: Int,
to newSectionIndex: Int
) {
self.mutate {
$0.unsafeMoveSection(at: sectionIndex, to: newSectionIndex)
}
}
/**
@@ -612,9 +820,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers to reload
*/
public mutating func reloadSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
public mutating func reloadSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
self.diffableSnapshot.reloadSections(sectionIDs)
self.mutate {
$0.reloadSections(sectionIDs)
}
}
/**
Marks the specified section indices as reloaded
- parameter sectionIndices: the section indices to reload. Specifying an invalid value will raise an exception.
*/
public mutating func reloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.mutate {
$0.unsafeReloadSections(at: sectionIndices)
}
}
@@ -696,16 +920,18 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
// MARK: Equatable
public static func == (_ lhs: Self, _ rhs: Self) -> Bool {
return lhs.id == rhs.id
return lhs.context == rhs.context
&& lhs.generation == rhs.generation
}
// MARK: Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
hasher.combine(self.context)
hasher.combine(self.generation)
}
@@ -732,6 +958,12 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
// MARK: Private
private let id: UUID = .init()
private var generation: UUID = .init()
@inline(__always)
private mutating func mutate<T>(_ mutation: (inout Internals.DiffableDataSourceSnapshot) -> T) -> T {
self.generation = .init()
return mutation(&self.diffableSnapshot)
}
}

View File

@@ -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)
}

View File

@@ -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?
}

View File

@@ -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
) {}
}

View File

@@ -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
) {}
}

View File

@@ -135,8 +135,19 @@ extension ObjectPublisher {
func cancel() {
self.publisher.removeObserver(self)
self.subscriber = nil
if Thread.isMainThread {
self.publisher.removeObserver(self)
}
else {
DispatchQueue.main.async {
self.publisher.removeObserver(self)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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 {

View File

@@ -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
)
}
}