Skip to content

Commit

Permalink
update CoreDataStack. initial implementation of main and background c…
Browse files Browse the repository at this point in the history
…ontext.
  • Loading branch information
jessesquires authored and Jesse Squires committed Oct 5, 2015
1 parent 5ee6d6d commit 28b9e4e
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 101 deletions.
4 changes: 4 additions & 0 deletions JSQCoreDataKit/JSQCoreDataKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
881219DD1B361F13005E5AA7 /* TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881219D81B361F13005E5AA7 /* TestCase.swift */; };
8851E2DB1B37423000E875DC /* StackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8851E2DA1B37423000E875DC /* StackTests.swift */; };
887CC6871BC107300069022A /* StoreType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887CC6861BC107300069022A /* StoreType.swift */; settings = {ASSET_TAGS = (); }; };
887CC68B1BC1F0A40069022A /* CoreDataStackInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887CC68A1BC1F0A40069022A /* CoreDataStackInitializer.swift */; settings = {ASSET_TAGS = (); }; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -51,6 +52,7 @@
881219D81B361F13005E5AA7 /* TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCase.swift; sourceTree = "<group>"; };
8851E2DA1B37423000E875DC /* StackTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackTests.swift; sourceTree = "<group>"; };
887CC6861BC107300069022A /* StoreType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreType.swift; sourceTree = "<group>"; };
887CC68A1BC1F0A40069022A /* CoreDataStackInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStackInitializer.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -96,6 +98,7 @@
88002D7F1AB8FF4F001787DB /* CoreDataExtensions.swift */,
88002D801AB8FF4F001787DB /* CoreDataModel.swift */,
88002D811AB8FF4F001787DB /* CoreDataStack.swift */,
887CC68A1BC1F0A40069022A /* CoreDataStackInitializer.swift */,
88002D541AB8F546001787DB /* JSQCoreDataKit.h */,
887CC6861BC107300069022A /* StoreType.swift */,
88002D521AB8F546001787DB /* Supporting Files */,
Expand Down Expand Up @@ -251,6 +254,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
887CC68B1BC1F0A40069022A /* CoreDataStackInitializer.swift in Sources */,
88002D841AB8FF4F001787DB /* CoreDataStack.swift in Sources */,
887CC6871BC107300069022A /* StoreType.swift in Sources */,
88002D831AB8FF4F001787DB /* CoreDataModel.swift in Sources */,
Expand Down
146 changes: 106 additions & 40 deletions JSQCoreDataKit/JSQCoreDataKit/CoreDataStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,26 @@
// Released under an MIT license: http://opensource.org/licenses/MIT
//

import Foundation
import CoreData
import Foundation


/// Describes a child managed object context.
public typealias ChildManagedObjectContext = NSManagedObjectContext
public typealias ChildContext = NSManagedObjectContext


private let DefaultStackOptions = [
NSMigratePersistentStoresAutomaticallyOption : true,
NSInferMappingModelAutomaticallyOption : true
]


/**
An instance of `CoreDataStack` encapsulates the entire Core Data stack for a SQLite store type.
It manages the managed object model, the persistent store coordinator, and the main managed object context.
It provides convenience methods for initializing a stack for common use-cases as well as creating child contexts.
An instance of `CoreDataStack` encapsulates the entire Core Data stack.
It manages the managed object model, the persistent store coordinator, and managed object contexts.
It is composed of a main and a background context, both of which are connected to the same persistent store coordinator.
These two contexts ooperate on the main thread and a background thread, respectively.
*/
public final class CoreDataStack: CustomStringConvertible {

Expand All @@ -36,45 +45,67 @@ public final class CoreDataStack: CustomStringConvertible {
/// The model for the stack.
public let model: CoreDataModel

/// The main managed object context for the stack.
public let mainQueueContext: NSManagedObjectContext
/// The main managed object context for the stack, which operates on the main thread.
public let mainContext: NSManagedObjectContext

/// The persistent store coordinator for the stack.
/// The background managed object context for the stack, which operates on a background thread.
public let backgroundContext: NSManagedObjectContext

/**
The persistent store coordinator for the stack.
Both the `mainContext` and `backgroundContext` are connected to this coordinator.
*/
public let storeCoordinator: NSPersistentStoreCoordinator


// MARK: Initialization

/**
Constructs a new `CoreDataStack` instance with the specified `model`, `storeType`, `options`, and `concurrencyType`.
Constructs a new `CoreDataStack` instance with the specified `model` and `options`.
- parameter model: The model describing the stack.
- parameter options: A dictionary containing key-value pairs that specify options for the store.
The default contains `true` for the following keys:
`NSMigratePersistentStoresAutomaticallyOption`, `NSInferMappingModelAutomaticallyOption`.
- parameter concurrencyType: The concurrency pattern to use for the managed object context.
The default is `.MainQueueConcurrencyType`.
- parameter model: The model describing the stack.
- parameter options: A dictionary containing key-value pairs that specify options for the store.
The default contains `true` for the following keys:
`NSMigratePersistentStoresAutomaticallyOption`, `NSInferMappingModelAutomaticallyOption`.
- returns: A new `CoreDataStack` instance.
*/
public init(model: CoreDataModel,
options: [NSObject : AnyObject]? = [NSMigratePersistentStoresAutomaticallyOption : true, NSInferMappingModelAutomaticallyOption : true],
concurrencyType: NSManagedObjectContextConcurrencyType = .MainQueueConcurrencyType) {

self.model = model
storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model.managedObjectModel)

do {
try storeCoordinator.addPersistentStoreWithType(model.storeType.description,
configuration: nil,
URL: model.storeURL,
options: options)
}
catch {
fatalError("*** Error adding persistent store: \(error)")
}

mainQueueContext = NSManagedObjectContext(concurrencyType: concurrencyType)
mainQueueContext.persistentStoreCoordinator = storeCoordinator
public init(model: CoreDataModel, options: [NSObject : AnyObject]? = DefaultStackOptions) {

self.model = model
storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model.managedObjectModel)

mainContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainContext.persistentStoreCoordinator = storeCoordinator
mainContext.name = "JSQCoreDataKit.context.primary.main"

backgroundContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
backgroundContext.persistentStoreCoordinator = storeCoordinator
backgroundContext.name = "JSQCoreDataKit.context.primary.background"

do {
try storeCoordinator.addPersistentStoreWithType(model.storeType.type,
configuration: nil,
URL: model.storeURL,
options: options)
} catch {
fatalError("everything is broken")
}

NSNotificationCenter.defaultCenter().addObserver(self,
selector: Selector("didReceiveMainContextDidSaveNotification:"),
name: NSManagedObjectContextDidSaveNotification,
object: mainContext)

NSNotificationCenter.defaultCenter().addObserver(self,
selector: Selector("didReceiveBackgroundContextDidSaveNotification:"),
name: NSManagedObjectContextDidSaveNotification,
object: backgroundContext)
}

/// :nodoc:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}


Expand All @@ -83,28 +114,63 @@ public final class CoreDataStack: CustomStringConvertible {
/**
Creates a new child managed object context with the specified `concurrencyType` and `mergePolicyType`.
- parameter concurrencyType: The concurrency pattern to use for the managed object context. The default is `.MainQueueConcurrencyType`.
- parameter concurrencyType: The concurrency pattern to use for the managed object context. The default is `.PrivateQueueConcurrencyType`.
- parameter mergePolicyType: The merge policy to use for the manged object context. The default is `.MergeByPropertyObjectTrumpMergePolicyType`.
- returns: A new child managed object context with the given concurrency and merge policy types.
*/
public func childManagedObjectContext(concurrencyType concurrencyType: NSManagedObjectContextConcurrencyType = .MainQueueConcurrencyType,
mergePolicyType: NSMergePolicyType = .MergeByPropertyObjectTrumpMergePolicyType) -> ChildManagedObjectContext {
public func mainChildContext(concurrencyType concurrencyType: NSManagedObjectContextConcurrencyType = .PrivateQueueConcurrencyType,
mergePolicyType: NSMergePolicyType = .MergeByPropertyObjectTrumpMergePolicyType) -> ChildContext {

let childContext = NSManagedObjectContext(concurrencyType: concurrencyType)
childContext.parentContext = mainQueueContext
childContext.parentContext = mainContext
childContext.mergePolicy = NSMergePolicy(mergeType: mergePolicyType)
childContext.name = "JSQCoreDataKit.context.child.main"

NSNotificationCenter.defaultCenter().addObserver(self,
selector: Selector("didReceiveChildContextDidSaveNotification:"),
name: NSManagedObjectContextDidSaveNotification,
object: childContext)

return childContext
}


// MARK: CustomStringConvertible

/// :nodoc:
public var description: String {
get {
return "<\(CoreDataStack.self): model=\(model.name), context=\(mainQueueContext)>"
return "<\(CoreDataStack.self): model=\(model.name); mainContext=\(mainContext)>"
}
}


// MARK: Private

@objc
private func didReceiveMainContextDidSaveNotification(notification: NSNotification) {
backgroundContext.mergeChangesFromContextDidSaveNotification(notification)
}

@objc
private func didReceiveBackgroundContextDidSaveNotification(notifcation: NSNotification) {
mainContext.mergeChangesFromContextDidSaveNotification(notifcation)
}

@objc
private func didReceiveChildContextDidSaveNotification(notification: NSNotification) {
guard let context = notification.object as? NSManagedObjectContext else {
assertionFailure("\(notification.name) posted from object of type \(notification.object.self). "
+ "Expected \(NSManagedObjectContext.self) instead.")
return
}

guard let parentContext = context.parentContext else {
return
}

saveContext(parentContext)
}

}
38 changes: 38 additions & 0 deletions JSQCoreDataKit/JSQCoreDataKit/CoreDataStackInitializer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://www.jessesquires.com/JSQCoreDataKit
//
//
// GitHub
// https://github.com/jessesquires/JSQCoreDataKit
//
//
// License
// Copyright (c) 2015 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//

import CoreData
import Foundation

public struct StackInitializer {

public typealias StackInitHandler = (Void) -> CoreDataStack?

public typealias StackCompletionHandler = (CoreDataStack?) -> Void

public static func Create(initialize: StackInitHandler, completion: StackCompletionHandler) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let stack = initialize()

dispatch_async(dispatch_get_main_queue()) {
completion(stack)
}
}
}

}
36 changes: 18 additions & 18 deletions JSQCoreDataKit/JSQCoreDataKitTests/DeleteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ class DeleteTests: TestCase {
let count = 10
var objects = [MyModel]()
for _ in 1...count {
objects.append(MyModel(context: stack.mainQueueContext))
objects.append(MyModel(context: stack.mainContext))
}

let request = FetchRequest<MyModel>(entity: entity(name: MyModelEntityName, context: stack.mainQueueContext))
let results = try! fetch(request: request, inContext: stack.mainQueueContext)
let request = FetchRequest<MyModel>(entity: entity(name: MyModelEntityName, context: stack.mainContext))
let results = try! fetch(request: request, inContext: stack.mainContext)
XCTAssertEqual(results.count, count)

// WHEN: we delete the objects
deleteObjects(objects, inContext: stack.mainQueueContext)
deleteObjects(objects, inContext: stack.mainContext)

// THEN: the objects are removed from the context
let resultAfterDelete = try! fetch(request: request, inContext: stack.mainQueueContext)
let resultAfterDelete = try! fetch(request: request, inContext: stack.mainContext)
XCTAssertEqual(resultAfterDelete.count, 0, "Fetch should return 0 objects")

saveContext(stack.mainQueueContext) { error in
saveContext(stack.mainContext) { error in
XCTAssertNil(error, "Save should not error")
}
}
Expand All @@ -59,33 +59,33 @@ class DeleteTests: TestCase {
let count = 10
var objects = [MyModel]()
for _ in 1..<count {
objects.append(MyModel(context: stack.mainQueueContext))
objects.append(MyModel(context: stack.mainContext))
}

let myModel = MyModel(context: stack.mainQueueContext)
let myModel = MyModel(context: stack.mainContext)

let request = FetchRequest<MyModel>(entity: entity(name: MyModelEntityName, context: stack.mainQueueContext))
let results = try! fetch(request: request, inContext: stack.mainQueueContext)
let request = FetchRequest<MyModel>(entity: entity(name: MyModelEntityName, context: stack.mainContext))
let results = try! fetch(request: request, inContext: stack.mainContext)
XCTAssertEqual(results.count, count, "Fetch should return all \(count) objects")

let requestForObject = FetchRequest<MyModel>(entity: entity(name: MyModelEntityName, context: stack.mainQueueContext))
let requestForObject = FetchRequest<MyModel>(entity: entity(name: MyModelEntityName, context: stack.mainContext))
requestForObject.predicate = NSPredicate(format: "myString == %@", myModel.myString)

let resultForObject = try! fetch(request: requestForObject, inContext: stack.mainQueueContext)
let resultForObject = try! fetch(request: requestForObject, inContext: stack.mainContext)
XCTAssertEqual(resultForObject.count, 1, "Fetch should return specific object \(myModel.description)")
XCTAssertEqual(resultForObject.first!, myModel, "Fetched object should equal expected model")

// WHEN: we delete a specific object
deleteObjects([myModel], inContext: stack.mainQueueContext)
deleteObjects([myModel], inContext: stack.mainContext)

// THEN: the specific object is removed from the context
let resultAfterDelete = try! fetch(request: request, inContext: stack.mainQueueContext)
let resultAfterDelete = try! fetch(request: request, inContext: stack.mainContext)
XCTAssertEqual(resultAfterDelete.count, count - 1, "Fetch should return remaining objects")

let resultForObjectAfterDelete = try! fetch(request: requestForObject, inContext: stack.mainQueueContext)
let resultForObjectAfterDelete = try! fetch(request: requestForObject, inContext: stack.mainContext)
XCTAssertEqual(resultForObjectAfterDelete.count, 0, "Fetch for specific object should return no objects")

saveContext(stack.mainQueueContext) { error in
saveContext(stack.mainContext) { error in
XCTAssertNil(error, "Save should not error")
}
}
Expand All @@ -96,10 +96,10 @@ class DeleteTests: TestCase {
let stack = CoreDataStack(model: inMemoryModel)

// WHEN: we delete an empty array of objects
deleteObjects([], inContext: stack.mainQueueContext)
deleteObjects([], inContext: stack.mainContext)

// THEN: the operation is ignored
saveContext(stack.mainQueueContext) { error in
saveContext(stack.mainContext) { error in
XCTAssertNil(error, "Save should not error")
}
}
Expand Down
Loading

0 comments on commit 28b9e4e

Please sign in to comment.