Skip to content

Commit

Permalink
re-work stack structure (#72). close #60
Browse files Browse the repository at this point in the history
- Change CoreDataStack returned by Factory
- mainContext is now directly connected to the persistent store
coordinator instead of being a child of the background context.
- Update CoreDataStack to reflect new stack setup
- Changes are merged between background and main contexts using
`mergeChangesFromContextDidSaveNotification(_:)`
  • Loading branch information
Liquidsoul authored and jessesquires committed Apr 26, 2016
1 parent b03b73d commit 1c9574e
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 28 deletions.
32 changes: 23 additions & 9 deletions Source/CoreDataStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import Foundation

It is composed of a main context and a background context.
These two contexts operate on the main queue and a private background queue, respectively.
The background context is the root level context, which is connected to the persistent store coordinator.
The main context is a child of the background context.
Both are connected to the persistent store coordinator.

Data between these two primary contexts and child contexts is kept in sync.
Changes to a context are propagated to its parent context and eventually the persistent store when saving.
Data between the main and background contexts is perpetually kept in sync.

Changes to a child context are propagated to its parent context and eventually the persistent store when saving.

- warning: **You cannot create a `CoreDataStack` instance directly. Instead, use a `CoreDataStackFactory` for initialization.**
*/
Expand Down Expand Up @@ -72,10 +72,15 @@ public final class CoreDataStack: CustomStringConvertible, Equatable {
self.backgroundContext = backgroundContext
self.storeCoordinator = storeCoordinator

NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(didReceiveChildContextDidSaveNotification(_:)),
name: NSManagedObjectContextDidSaveNotification,
object: mainContext)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self,
selector: #selector(didReceiveMainContextDidSaveNotification(_:)),
name: NSManagedObjectContextDidSaveNotification,
object: mainContext)
notificationCenter.addObserver(self,
selector: #selector(didReceiveBackgroundContextDidSaveNotification(_:)),
name: NSManagedObjectContextDidSaveNotification,
object: backgroundContext)
}

/// :nodoc:
Expand Down Expand Up @@ -144,5 +149,14 @@ public final class CoreDataStack: CustomStringConvertible, Equatable {

saveContext(parentContext)
}


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

@objc
private func didReceiveMainContextDidSaveNotification(notification: NSNotification) {
backgroundContext.mergeChangesFromContextDidSaveNotification(notification)
}
}
4 changes: 2 additions & 2 deletions Source/CoreDataStackFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public struct CoreDataStackFactory: CustomStringConvertible, Equatable {

dispatch_async(dispatch_get_main_queue()) {
let mainContext = self.createContext(.MainQueueConcurrencyType, name: "main")
mainContext.parentContext = backgroundContext
mainContext.persistentStoreCoordinator = storeCoordinator

let stack = CoreDataStack(
model: self.model,
Expand Down Expand Up @@ -128,7 +128,7 @@ public struct CoreDataStackFactory: CustomStringConvertible, Equatable {
backgroundContext.persistentStoreCoordinator = storeCoordinator

let mainContext = self.createContext(.MainQueueConcurrencyType, name: "main")
mainContext.parentContext = backgroundContext
mainContext.persistentStoreCoordinator = storeCoordinator

let stack = CoreDataStack(
model: model,
Expand Down
12 changes: 11 additions & 1 deletion Tests/ContextSyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ class ContextSyncTests: TestCase {

// WHEN: we save the main context
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: inMemoryStack.mainContext, handler: nil)
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: inMemoryStack.backgroundContext, handler: nil)

saveContext(inMemoryStack.mainContext) { result in
XCTAssertTrue(result == .success)
Expand All @@ -76,6 +75,17 @@ class ContextSyncTests: TestCase {
let companies = generateDataInContext(inMemoryStack.backgroundContext, companiesCount: 3, employeesCount: 3)
let companyNames = companies.map { $0.name }

// WHEN: we save the background context
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: inMemoryStack.backgroundContext, handler: nil)

saveContext(inMemoryStack.backgroundContext) { result in
XCTAssertTrue(result == .success)
}

waitForExpectationsWithTimeout(DefaultTimeout) { (error) in
XCTAssertNil(error, "Expectation should not error")
}

// WHEN: we fetch the objects from the main context
let request = FetchRequest<Company>(entity: entity(name: Company.entityName, context: inMemoryStack.mainContext))
let results = try! fetch(request: request, inContext: inMemoryStack.mainContext)
Expand Down
66 changes: 51 additions & 15 deletions Tests/SaveTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ class SaveTests: TestCase {
return true
}

var didSaveBackground = false
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: stack.backgroundContext) { (notification) -> Bool in
didSaveBackground = true
var didUpdateBackground = false
expectationForNotification(NSManagedObjectContextObjectsDidChangeNotification, object: stack.backgroundContext) { (notification) -> Bool in
didUpdateBackground = true
return true
}

Expand All @@ -74,7 +74,7 @@ class SaveTests: TestCase {
waitForExpectationsWithTimeout(DefaultTimeout, handler: { (error) -> Void in
XCTAssertNil(error, "Expectation should not error")
XCTAssertTrue(didSaveMain, "Main context should be saved")
XCTAssertTrue(didSaveBackground, "Background context should be saved")
XCTAssertTrue(didUpdateBackground, "Background context should be updated")
})
}

Expand All @@ -90,9 +90,9 @@ class SaveTests: TestCase {
return true
}

var didSaveBackground = false
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: stack.backgroundContext) { (notification) -> Bool in
didSaveBackground = true
var didUpdateBackground = false
expectationForNotification(NSManagedObjectContextObjectsDidChangeNotification, object: stack.backgroundContext) { (notification) -> Bool in
didUpdateBackground = true
return true
}

Expand All @@ -105,7 +105,7 @@ class SaveTests: TestCase {
waitForExpectationsWithTimeout(DefaultTimeout, handler: { (error) -> Void in
XCTAssertNil(error, "Expectation should not error")
XCTAssertTrue(didSaveMain, "Main context should be saved")
XCTAssertTrue(didSaveBackground, "Background context should be saved")
XCTAssertTrue(didUpdateBackground, "Background context should be updated")
})
}

Expand Down Expand Up @@ -135,9 +135,9 @@ class SaveTests: TestCase {
return true
}

var didSaveBackground = false
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: stack.backgroundContext) { (notification) -> Bool in
didSaveBackground = true
var didUpdateBackground = false
expectationForNotification(NSManagedObjectContextObjectsDidChangeNotification, object: stack.backgroundContext) { (notification) -> Bool in
didUpdateBackground = true
return true
}

Expand All @@ -155,7 +155,7 @@ class SaveTests: TestCase {
waitForExpectationsWithTimeout(DefaultTimeout, handler: { (error) -> Void in
XCTAssertNil(error, "Expectation should not error")
XCTAssertTrue(didSaveMain, "Main context should be saved")
XCTAssertTrue(didSaveBackground, "Background context should be saved")
XCTAssertTrue(didUpdateBackground, "Background context should be updated")
})
}

Expand All @@ -178,9 +178,9 @@ class SaveTests: TestCase {
return true
}

var didSaveBackground = false
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: stack.backgroundContext) { (notification) -> Bool in
didSaveBackground = true
var didUpdateBackground = false
expectationForNotification(NSManagedObjectContextObjectsDidChangeNotification, object: stack.backgroundContext) { (notification) -> Bool in
didUpdateBackground = true
return true
}

Expand All @@ -199,7 +199,43 @@ class SaveTests: TestCase {
XCTAssertNil(error, "Expectation should not error")
XCTAssertTrue(didSaveChild, "Child context should be saved")
XCTAssertTrue(didSaveMain, "Main context should be saved")
XCTAssertTrue(didUpdateBackground, "Background context should be updated")
})
}

func test_ThatSavingBackgroundContext_SucceedsAndUpdateMainContext() {
// GIVEN: a stack and context with changes
let stack = self.inMemoryStack

generateCompaniesInContext(stack.backgroundContext, count: 3)

var didSaveBackground = false
expectationForNotification(NSManagedObjectContextDidSaveNotification, object: stack.backgroundContext) { (notification) -> Bool in
didSaveBackground = true
return true
}

var didUpdateMain = false
expectationForNotification(NSManagedObjectContextObjectsDidChangeNotification, object: stack.mainContext) { (notification) -> Bool in
didUpdateMain = true
return true
}

let saveExpectation = expectationWithDescription("\(#function)")

// WHEN: we attempt to save the context asynchronously
saveContext(stack.backgroundContext, wait: false) { result in

// THEN: the save succeeds without an error
XCTAssertTrue(result == .success, "Save should not error")
saveExpectation.fulfill()
}

// THEN: then the main and background contexts are saved and the completion handler is called
waitForExpectationsWithTimeout(DefaultTimeout, handler: { (error) -> Void in
XCTAssertNil(error, "Expectation should not error")
XCTAssertTrue(didSaveBackground, "Background context should be saved")
XCTAssertTrue(didUpdateMain, "Main context should be updated")
})
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/StackFactoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class StackFactoryTests: TestCase {

XCTAssertEqual(stack.mainContext.name, "JSQCoreDataKit.CoreDataStack.context.main")
XCTAssertEqual(stack.mainContext.concurrencyType, NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
XCTAssertEqual(stack.mainContext.parentContext, stack.backgroundContext)
XCTAssertNil(stack.mainContext.parentContext)
XCTAssertNotNil(stack.mainContext.persistentStoreCoordinator)

XCTAssertEqual(stack.backgroundContext.name, "JSQCoreDataKit.CoreDataStack.context.background")
Expand Down

0 comments on commit 1c9574e

Please # to comment.