Refetch monitors when persistent stores are added/removed. Allow unsafe transactions to create their own monitors

This commit is contained in:
John Rommel Estropia
2016-02-15 07:56:17 +09:00
parent e58b3b0131
commit 8efd6572f0
11 changed files with 534 additions and 62 deletions

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "1.4.4"
s.version = "1.5.0"
s.license = "MIT"
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
s.homepage = "https://github.com/JohnEstropia/CoreStore"

View File

@@ -208,6 +208,12 @@
B56965241B356B820075EE4A /* MigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965231B356B820075EE4A /* MigrationResult.swift */; };
B59D5C221B5BA34B00453479 /* NSFileManager+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59D5C211B5BA34B00453479 /* NSFileManager+Setup.swift */; };
B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A261201B64BFDB006EB6D3 /* MigrationType.swift */; };
B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */; };
B5C976E41C6C9F9A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */; };
B5C976E51C6C9F9B00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */; };
B5C976E71C6E3A5A00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */; };
B5C976E81C6E3A5D00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */; };
B5C976E91C6E3A5E00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */; };
B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; };
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; };
B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; };
@@ -317,6 +323,8 @@
B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.swift; sourceTree = "<group>"; };
B5BDC91A1C202269008147CD /* CartFile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CartFile; sourceTree = "<group>"; };
B5BDC9271C2024F2008147CD /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = SOURCE_ROOT; };
B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeDataTransaction+Observing.swift"; sourceTree = "<group>"; };
B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreFetchedResultsController.swift; sourceTree = "<group>"; };
B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+CoreStore.swift"; sourceTree = "<group>"; };
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = "<group>"; };
@@ -627,6 +635,7 @@
B56007131B3F6C2800A9A8F9 /* SectionBy.swift */,
B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */,
B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */,
B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */,
B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */,
B5E84F1F1AFF84860064E85B /* ObjectObserver.swift */,
B5E84F1D1AFF84860064E85B /* ListMonitor.swift */,
@@ -654,6 +663,7 @@
B5FAD6AB1B51285300714891 /* MigrationManager.swift */,
B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */,
B59D5C211B5BA34B00453479 /* NSFileManager+Setup.swift */,
B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */,
B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */,
B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */,
B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */,
@@ -943,6 +953,7 @@
buildActionMask = 2147483647;
files = (
B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
B5C976E71C6E3A5A00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */,
B5F1DA901B9AA991007C5CBB /* ImportableUniqueObject.swift in Sources */,
B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */,
B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */,
@@ -952,6 +963,7 @@
B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */,
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */,
B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */,
B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */,
@@ -1018,6 +1030,7 @@
buildActionMask = 2147483647;
files = (
82BA18B61C4BBD3F00A0916E /* DataStack+Querying.swift in Sources */,
B5C976E81C6E3A5D00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */,
82BA18A21C4BBD1D00A0916E /* NSError+CoreStore.swift in Sources */,
82BA18B21C4BBD3900A0916E /* ImportableObject.swift in Sources */,
82BA18AE1C4BBD3100A0916E /* DataStack+Transaction.swift in Sources */,
@@ -1027,6 +1040,7 @@
82BA18C21C4BBD5300A0916E /* ObjectMonitor.swift in Sources */,
82BA18A51C4BBD2200A0916E /* CoreStore+Setup.swift in Sources */,
82BA18BD1C4BBD4A00A0916E /* GroupBy.swift in Sources */,
B5C976E41C6C9F9A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
82BA18B31C4BBD3900A0916E /* ImportableUniqueObject.swift in Sources */,
82BA18A11C4BBD1D00A0916E /* CoreStore.swift in Sources */,
82BA18CF1C4BBD7100A0916E /* Functions.swift in Sources */,
@@ -1159,6 +1173,7 @@
buildActionMask = 2147483647;
files = (
B56321A91BD65219006C9394 /* NSProgress+Convenience.swift in Sources */,
B5C976E91C6E3A5E00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */,
B56321801BD65216006C9394 /* NSError+CoreStore.swift in Sources */,
B56321AD1BD6521C006C9394 /* MigrationManager.swift in Sources */,
B563219D1BD65216006C9394 /* DataStack+Observing.swift in Sources */,
@@ -1168,6 +1183,7 @@
B563219E1BD65216006C9394 /* CoreStore+Observing.swift in Sources */,
B56321891BD65216006C9394 /* AsynchronousDataTransaction.swift in Sources */,
B56321831BD65216006C9394 /* CoreStore+Setup.swift in Sources */,
B5C976E51C6C9F9B00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
B563217F1BD65216006C9394 /* CoreStore.swift in Sources */,
B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */,
B56321941BD65216006C9394 /* CoreStore+Querying.swift in Sources */,

View File

@@ -31,20 +31,31 @@ import CoreData
public extension NSFetchedResultsController {
public convenience init<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
// MARK: Public
public func createForStack<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
let context = dataStack.mainContext
from?.applyToFetchRequest(fetchRequest, context: context)
for clause in fetchClauses {
clause.applyToFetchRequest(fetchRequest)
}
self.init(
return CoreStoreFetchedResultsController<T>(
context: dataStack.mainContext,
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: sectionBy?.sectionKeyPath,
cacheName: nil
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
// MARK: Internal
internal func createFromContext<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return CoreStoreFetchedResultsController<T>(
context: context,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
}

View File

@@ -36,22 +36,25 @@ public struct From<T: NSManagedObject> {
// MARK: Public
/**
Initializes a `From` clause with the specified entity type and configuration.
Sample Usage:
let person = transaction.fetchOne(From<MyPersonEntity>())
*/
public init(){
self.entityClass = T.self
self.findPersistentStores = { _ in nil }
self.init(entityClass: T.self)
}
public init(_ entity: T.Type) {
self.entityClass = entity
self.findPersistentStores = { _ in nil }
self.init(entityClass: entity)
}
public init(_ entityClass: AnyClass) {
self.entityClass = entityClass
self.findPersistentStores = { _ in nil }
self.init(entityClass: entityClass)
}
public init(_ configuration: String?, otherConfigurations: String?...) {
@@ -147,10 +150,20 @@ public struct From<T: NSManagedObject> {
// MARK: Internal
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) {
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) {
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
fetchRequest.affectedStores = self.findPersistentStores(context: context)
if applyAffectedStores {
self.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
}
}
internal func applyAffectedStoresForFetchedRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) -> Bool {
let stores = self.findPersistentStores(context: context)
fetchRequest.affectedStores = stores
return stores?.isEmpty == false
}
@@ -160,6 +173,15 @@ public struct From<T: NSManagedObject> {
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
private init(entityClass: AnyClass) {
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)
}
}
private init(entityClass: AnyClass, configurations: [String?]) {
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.4.4</string>
<string>1.5.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@@ -0,0 +1,101 @@
//
// CoreStoreFetchedResultsController.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStoreFetchedResultsController
@available(OSX, unavailable)
internal final class CoreStoreFetchedResultsController<T: NSManagedObject>: NSFetchedResultsController {
// MARK: Internal
internal convenience init<T>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
self.init(
context: dataStack.mainContext,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
internal init<T>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
from?.applyToFetchRequest(fetchRequest, context: context, applyAffectedStores: false)
for clause in fetchClauses {
clause.applyToFetchRequest(fetchRequest)
}
if let from = from {
self.reapplyAffectedStores = {
return from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
}
}
else {
guard let from = (fetchRequest.entity.flatMap { $0.managedObjectClassName }).flatMap(NSClassFromString).flatMap(From.init) else {
fatalError("Attempted to create an \(typeName(NSFetchedResultsController)) without a From clause or an NSEntityDescription.")
}
self.reapplyAffectedStores = {
return from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
}
}
super.init(
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: sectionBy?.sectionKeyPath,
cacheName: nil
)
}
internal func performFetchFromSpecifiedStores() throws {
if !self.reapplyAffectedStores() {
CoreStore.log(
.Warning,
message: "Attempted to perform a fetch on an \(typeName(NSFetchedResultsController)) but could not find any persistent store for the entity \(typeName(self.fetchRequest.entityName))"
)
}
try self.performFetch()
}
// MARK: Private
private let reapplyAffectedStores: () -> Bool
}

View File

@@ -882,12 +882,15 @@ public final class ListMonitor<T: NSManagedObject> {
"Attempted to refetch a \(typeName(self)) outside the main thread."
)
self.isPendingRefetch = true
NSNotificationCenter.defaultCenter().postNotificationName(
ListMonitorWillRefetchListNotification,
object: self
)
if !self.isPendingRefetch {
self.isPendingRefetch = true
NSNotificationCenter.defaultCenter().postNotificationName(
ListMonitorWillRefetchListNotification,
object: self
)
}
self.taskGroup.notify(.Main) { [weak self] () -> Void in
@@ -904,14 +907,14 @@ public final class ListMonitor<T: NSManagedObject> {
clause.applyToFetchRequest(fetchRequest)
}
strongSelf.parentStack?.childTransactionQueue.async {
strongSelf.transactionQueue.async {
guard let strongSelf = self else {
return
}
try! strongSelf.fetchedResultsController.performFetch()
try! strongSelf.fetchedResultsController.performFetchFromSpecifiedStores()
GCDQueue.Main.async { () -> Void in
@@ -938,36 +941,54 @@ public final class ListMonitor<T: NSManagedObject> {
internal convenience init(dataStack: DataStack, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause]) {
self.init(
dataStack: dataStack,
context: dataStack.mainContext,
transactionQueue: dataStack.childTransactionQueue,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses,
prepareFetch: { _, performFetch in performFetch() }
createAsynchronously: nil
)
}
internal convenience init(dataStack: DataStack, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause], createAsynchronously: (ListMonitor<T>) -> Void) {
let queue = dataStack.childTransactionQueue
self.init(
dataStack: dataStack,
context: dataStack.mainContext,
transactionQueue: queue,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses,
prepareFetch: { listMonitor, performFetch in
dataStack.childTransactionQueue.async {
performFetch()
GCDQueue.Main.async {
createAsynchronously(listMonitor)
}
}
}
createAsynchronously: createAsynchronously
)
}
private init(dataStack: DataStack, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause], prepareFetch: (ListMonitor<T>, () -> Void) -> Void) {
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause]) {
self.init(
context: unsafeTransaction.context,
transactionQueue: unsafeTransaction.transactionQueue,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses,
createAsynchronously: nil
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause], createAsynchronously: (ListMonitor<T>) -> Void) {
let queue = unsafeTransaction.transactionQueue
self.init(
context: unsafeTransaction.context,
transactionQueue: queue,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses,
createAsynchronously: createAsynchronously
)
}
private init(context: NSManagedObjectContext, transactionQueue: GCDQueue, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause], createAsynchronously: ((ListMonitor<T>) -> Void)?) {
let fetchRequest = NSFetchRequest()
fetchRequest.fetchLimit = 0
@@ -976,8 +997,8 @@ public final class ListMonitor<T: NSManagedObject> {
fetchRequest.includesPendingChanges = false
fetchRequest.shouldRefreshRefetchedObjects = true
let fetchedResultsController = NSFetchedResultsController(
dataStack: dataStack,
let fetchedResultsController = CoreStoreFetchedResultsController<T>(
context: context,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
@@ -988,7 +1009,6 @@ public final class ListMonitor<T: NSManagedObject> {
self.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
self.parentStack = dataStack
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
@@ -998,26 +1018,118 @@ public final class ListMonitor<T: NSManagedObject> {
self.sectionIndexTransformer = { $0 }
}
self.transactionQueue = transactionQueue
fetchedResultsControllerDelegate.handler = self
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
prepareFetch(self, { try! fetchedResultsController.performFetch() })
guard let coordinator = context.parentStack?.coordinator else {
return
}
self.observerForWillChangePersistentStore = NotificationObserver(
notificationName: NSPersistentStoreCoordinatorStoresWillChangeNotification,
object: coordinator,
closure: { [weak self] (note) -> Void in
guard let `self` = self else {
return
}
self.isPersistentStoreChanging = true
guard let removedStores = (note.userInfo?[NSRemovedPersistentStoresKey] as? [NSPersistentStore]).flatMap(Set.init)
where !Set(self.fetchedResultsController.fetchRequest.affectedStores ?? []).intersect(removedStores).isEmpty else {
return
}
self.refetch(fetchClauses)
}
)
self.observerForDidChangePersistentStore = NotificationObserver(
notificationName: NSPersistentStoreCoordinatorStoresDidChangeNotification,
object: coordinator,
closure: { [weak self] (note) -> Void in
guard let `self` = self else {
return
}
if !self.isPendingRefetch {
let previousStores = Set(self.fetchedResultsController.fetchRequest.affectedStores ?? [])
let currentStores = previousStores
.subtract(note.userInfo?[NSRemovedPersistentStoresKey] as? [NSPersistentStore] ?? [])
.union(note.userInfo?[NSAddedPersistentStoresKey] as? [NSPersistentStore] ?? [])
if previousStores != currentStores {
self.refetch(fetchClauses)
}
}
self.isPersistentStoreChanging = false
}
)
if let createAsynchronously = createAsynchronously {
transactionQueue.async {
try! fetchedResultsController.performFetchFromSpecifiedStores()
self.taskGroup.notify(.Main) {
createAsynchronously(self)
}
}
}
else {
try! fetchedResultsController.performFetchFromSpecifiedStores()
}
}
deinit {
self.fetchedResultsControllerDelegate.fetchedResultsController = nil
self.isPersistentStoreChanging = false
}
private var isPersistentStoreChanging: Bool = false {
didSet {
let newValue = self.isPersistentStoreChanging
guard newValue != oldValue else {
return
}
if newValue {
self.taskGroup.enter()
}
else {
self.taskGroup.leave()
}
}
}
// MARK: Private
private let fetchedResultsController: NSFetchedResultsController
private let fetchedResultsController: CoreStoreFetchedResultsController<T>
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
private let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
private var observerForWillChangePersistentStore: NotificationObserver!
private var observerForDidChangePersistentStore: NotificationObserver!
private let taskGroup = GCDGroup()
private weak var parentStack: DataStack?
private let transactionQueue: GCDQueue
private var willChangeListKey: Void?
private var didChangeListKey: Void?

View File

@@ -165,7 +165,17 @@ public final class ObjectMonitor<T: NSManagedObject> {
// MARK: Internal
internal init(dataStack: DataStack, object: T) {
internal convenience init(dataStack: DataStack, object: T) {
self.init(context: dataStack.mainContext, object: object)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: T) {
self.init(context: unsafeTransaction.context, object: object)
}
private init(context: NSManagedObjectContext, object: T) {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = object.entity
@@ -175,8 +185,8 @@ public final class ObjectMonitor<T: NSManagedObject> {
fetchRequest.includesPendingChanges = false
fetchRequest.shouldRefreshRefetchedObjects = true
let fetchedResultsController = NSFetchedResultsController(
dataStack: dataStack,
let fetchedResultsController = CoreStoreFetchedResultsController<T>(
context: context,
fetchRequest: fetchRequest,
fetchClauses: [Where("SELF", isEqualTo: object.objectID)]
)
@@ -185,11 +195,10 @@ public final class ObjectMonitor<T: NSManagedObject> {
self.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
self.parentStack = dataStack
fetchedResultsControllerDelegate.handler = self
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
try! fetchedResultsController.performFetch()
try! fetchedResultsController.performFetchFromSpecifiedStores()
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
}
@@ -202,10 +211,9 @@ public final class ObjectMonitor<T: NSManagedObject> {
// MARK: Private
private let fetchedResultsController: NSFetchedResultsController
private let fetchedResultsController: CoreStoreFetchedResultsController<T>
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
private var lastCommittedAttributes = [String: NSObject]()
private weak var parentStack: DataStack?
private var willChangeObjectKey: Void?
private var didDeleteObjectKey: Void?

View File

@@ -0,0 +1,201 @@
//
// UnsafeDataTransaction+Observing.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - UnsafeDataTransaction
@available(OSX, unavailable)
public extension UnsafeDataTransaction {
// MARK: Public
/**
Creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
@warn_unused_result
public func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
return ObjectMonitor(
unsafeTransaction: self,
object: object
)
}
/**
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.monitorList(from, fetchClauses)
}
/**
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
return ListMonitor(
unsafeTransaction: self,
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
/**
Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
/**
Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
_ = ListMonitor(
unsafeTransaction: self,
from: from,
sectionBy: nil,
fetchClauses: fetchClauses,
createAsynchronously: createAsynchronously
)
}
/**
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.monitorSectionedList(from, sectionBy, fetchClauses)
}
/**
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
return ListMonitor(
unsafeTransaction: self,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
/**
Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
_ = ListMonitor(
unsafeTransaction: self,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses,
createAsynchronously: createAsynchronously
)
}
}

View File

@@ -367,11 +367,12 @@ public final class DataStack {
self.configurationStoreMapping[configurationName] = persistentStore
for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) ?? []) {
if self.entityConfigurationsMapping[entityDescription.managedObjectClassName] == nil {
let managedObjectClassName = entityDescription.managedObjectClassName
if self.entityConfigurationsMapping[managedObjectClassName] == nil {
self.entityConfigurationsMapping[entityDescription.managedObjectClassName] = []
self.entityConfigurationsMapping[managedObjectClassName] = []
}
self.entityConfigurationsMapping[entityDescription.managedObjectClassName]?.insert(configurationName)
self.entityConfigurationsMapping[managedObjectClassName]?.insert(configurationName)
}
}
}

View File

@@ -714,7 +714,7 @@ class func uniqueIDFromImportSource(source: ImportSource, inTransaction transact
```
For `ImportableUniqueObject`, the extraction and assignment of values should be implemented from the `updateFromImportSource(...)` method. The `didInsertFromImportSource(...)` by default calls `updateFromImportSource(...)`, but you can separate the implementation for inserts and updates if needed.
You can then call create/update an object by calling a transaction's `importUniqueObject(...)` method:
You can then create/update an object by calling a transaction's `importUniqueObject(...)` method:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let json: [String: AnyObject] = // ...