How to test NSPersistentDocument in XCTestCase? - swift

I'm just starting an OS X document-based app using Core Data. To test my model relationships, I'd like to create some NSManagedObject entities, save them to a file, and read them back in an XCTestCase object. Using code like below, I can create entities in the managed object context, but I cannot figure out how to create a functioning persistent store coordinator and/or save to a file.
class MyTests: XCTestCase {
var doc: Document!
var moc: NSManagedObjectContext!
//...//
override func setUp() {
super.setUp()
doc = Document()
moc = doc.managedObjectContext
}
//...//
}

Problem with my initial approach above was that I was getting the managedObjectContext before using configurePersistentStoreCoordinatorForURL. Once I resolved that, the solution was fairly straightforward.
class DocTests : XCTestCase {
let testFileURL = NSURL.fileURLWithPath("/Users/Me/Temp/DocTests", isDirectory: false)
override func setUp() {
super.setUp()
}
func testWriteAndRead() {
writeSimpleTestDocument()
let doc = Document()
do {
try doc.readFromURL(testFileURL, ofType: "mydoctype")
} catch let error as NSError {
print("Failed to read, \(error), \(error.userInfo)")
}
let moc = doc.managedObjectContext!
// fetch from moc and verify test entities here ...
// ...
}
func writeSimpleTestDocument() {
// if test document exists, delete it
let fm = NSFileManager.defaultManager()
if fm.fileExistsAtPath(testFileURL.path!) {
do {
try fm.removeItemAtURL(testFileURL)
} catch let error as NSError {
print("Failed to remove, \(error), \(error.userInfo)")
}
}
let doc = Document()
do {
try doc.configurePersistentStoreCoordinatorForURL(testFileURL, ofType: "mydoctype", modelConfiguration: nil, storeOptions: nil)
} catch let error as NSError {
print("Failed to configure coordinator, \(error), \(error.userInfo)")
}
let moc = doc.managedObjectContext!
// create test entities in moc here ...
// ...
do {
try moc.save()
} catch let error as NSError {
print("Failed to save, \(error), \(error.userInfo)")
}
}
}

Related

Core Data where do I need to call save method

I am using core data and I am confused about NSManagedObjectContext. I used following code to save my book data. Book data is saving in my one of my application extension side like following. Extension side code block is calling in different than main thread like in the image.
I am wondering does it cause a problem because of misusing of my contexts?
func saveBook() {
let book: Book = Book(context: viewContext)
let uuid = UUID()
book.sessionId = uuid
book.appId = session.appId
book.startedAt = session.startedAt
book.createdBy = AppData.installId
coreData.saveSync()
}
func saveSync() {
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = viewContext
privateContext.perform {
do {
try privateContext.save()
viewContext.performAndWait {
do {
try viewContext.save()
} catch {
let nsError = error as NSError
print("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
} catch {
let nsError = error as NSError
print("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
I see two problems in your code:
The privateContext in the saveSync function is ponintless, as the new Book object was created in the mainContext. So, when you save the privateContext you are saving an empty context that has no changes.
If I understand well, your saveBook() function is being executed in a background thread, and you are using the mainContext, which is always associated with the main thread.
When dealing with Core Data in a multi-thread environment, you need to remember the following rule: Never use an NSManagedObjectContext or an NSManagedObject in a queue/thread other than the one assigned to your context, or you get unexpected results and your app will crash at some point.
So, to fix both issues, you can do the following:
func saveBook() {
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = viewContext
privateContext.perform {
let book: Book = Book(context: privateContext)
let uuid = UUID()
book.sessionId = uuid
book.appId = session.appId
book.startedAt = session.startedAt
book.createdBy = AppData.installId
do {
try privateContext.save()
viewContext.performAndWait {
do {
try viewContext.save()
} catch {
let nsError = error as NSError
print("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
} catch {
let nsError = error as NSError
print("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
Notice that, when you save the privateContext, the data are not persisted to the persistent store but are "saved" to its parent context. And it will only be persisted when the mainContext is saved. That's why you need to save both contexts after the Book object is created.
If you have the persistentStoreContainer handy in your class or structure, you can use the following approach to create a background context and save it:
func saveBook() {
let privateContext = persistentStoreContainer.newBackgroundContext()
privateContext.perform {
let book: Book = Book(context: privateContext)
let uuid = UUID()
book.sessionId = uuid
book.appId = session.appId
book.startedAt = session.startedAt
book.createdBy = AppData.installId
do {
try privateContext.save()
} catch {
let nsError = error as NSError
print("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
The benefit of this approach is that the data is persisted when you save the privateContext, since its parent is the persistentStoreCoordinator.
Edit:
Here's another approach for creating a background context from the persistent container:
func saveBook() {
persistentStoreContainer.performBackgroundTask { context in
let book: Book = Book(context: privateContext)
let uuid = UUID()
book.sessionId = uuid
book.appId = session.appId
book.startedAt = session.startedAt
book.createdBy = AppData.installId
do {
try context.save()
} catch {
let nsError = error as NSError
print("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}

print all items saved in a core data string1

I want my swift code to print out the strings attributes. Right now when calling the function I am getting a runtime error at context. I just want to print out all of each string entry. I have added the function in question below.
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
let appDelegate = UIApplication.shared.delegate as! AppDelegate //Singlton instance
var context:NSManagedObjectContext!
#objc func pressRight(){
let fetchRequest = NSFetchRequest<Place>(entityName: "Name")
do {
let result = try context.fetch(fetchRequest)
let nameArray = result.map{$0.name}
print(nameArray)
} catch {
print("Could not fetch \(error) ")
}
}
pic
select manual in code gen
then create custom class of place add to your project
You are using the wrong entity name "Name" instead of "Place"
import Foundation
import CoreData
class CoreDataManager {
static let shared = CoreDataManager()
private init() {}
lazy var coreDataStack = CoreDataStack(modelName: "Place")
func allNames() -> [String]? {
let request: NSFetchRequest<Place> = Place.fetchRequest()
do {
// Peform Fetch Request
let places = try coreDataStack.managedContext.fetch(request)
return places.map({$0.name})
} catch {
print("Unable to Fetch Workouts, (\(error))")
}
return nil
}
func allPlaces() -> [Place]? {
let request: NSFetchRequest<Place> = Place.fetchRequest()
do {
// Peform Fetch Request
let places = try coreDataStack.managedContext.fetch(request)
return places
} catch {
print("Unable to Fetch Workouts, (\(error))")
}
return nil
}
}
if you still getting error then before this initialize your context
managedObjectContext/context you force unwrapping it
add this stack class
import Foundation
import CoreData
class CoreDataStack {
private let modelName: String
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
init(modelName: String) {
self.modelName = modelName
}
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
func saveContext() {
guard managedContext.hasChanges else {return}
do{
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
func updateContext() {
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
func clearChange() {
managedContext.rollback()
}
}
then how to use it
in your view controller viewDidLoad() function or any other button tap action you can get your place names like this
override func viewDidLoad() {
super.viewDidLoad()
// here you get all names
let names = CoreDataManager.shared.allNames()
print(names)
let places = CoreDataManager.shared.allPlaces()
print(places)
let namesAgain = places.map({$0.name})
print(namesAgain)
}

Using CoreData managedContext from background thread...how do you do it correctly?

I have the CoreData concurrency debugger on and I am asserting every where. I cannot figure it out.
I created what I thought was a context on a background thread. Please take a look at my CoreData Stack:
import Foundation
import CoreData
class CoreDataManager {
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
lazy var backgroundContext: NSManagedObjectContext = {
return self.storeContainer.newBackgroundContext()
}()
lazy var privateMOC: NSManagedObjectContext = {
let pMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
pMOC.parent = self.storeContainer.viewContext
return pMOC
}()
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "XXXX")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
self.reset()
}
func reset() {
managedContext.reset()
}
}
Then I try to perform a task from a background thread from within a repository type class:
func deleteAllData() {
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Pairing")
let pairings = (try? self.coreDataManager.privateMOC.fetch(fetchRequest)) as! [Pairing_ManagedObject]. // I GET THE ASSERT HERE
for pairing in pairings {
self.coreDataManager.privateMOC.delete(pairing)
}
self.coreDataManager.saveContext()
}
How do I do this so I don't get he core data concurrency assert and do it correctly? Please help.
You're creating a new managed object context with private queue concurrency, but that's not enough to avoid concurrency issues. Core Data's policy is that you must use either performBlock or performBlockAndWait for everything you do that uses that context. In your code that means that the delete and save calls must be be done with one of those functions.
There's a second issue though. You're using your saveContext function to save changes. But that doesn't save changes on your private-queue context. It saves on some other context called managedContext. Saving on one context doesn't automatically save other contexts. If you want to make changes on your private queue context and save those changes, you need to save that context at some point.

Delete from CoreData not working on app retstart

I have a function that deletes an NSManagedObject from CoreData (Test is a subclass of NSManagedObject:
public func delete(_ test: Test, completion: #escaping (Bool) -> Void) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return completion(false) }
let managedContext = appDelegate.persistentContainer.viewContext
do {
managedContext.delete(test)
completion(true)
} catch let error as NSError {
print("Could not delete. \(error), \(error.userInfo)")
completion(false)
}
}
Right now, it appears that the object is being deleted from CoreData in the moment, but if I rerun my app, the object that I just deleted appears again. What am I doing wrong when trying to delete this object?
You have to save the context to make changes persist, including deletions.
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}

Proper singleton class to use CoreData

I'm trying to create a singleton class which works with an NSManagedObjectContext.
This is the class:
import Foundation
import CoreData
class PersistenceService{
init(){}
// MARK: - Core Data stack
static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "frazeit")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
static func saveContext () {
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = mainContext
privateContext.perform {
if privateContext.hasChanges {
do {
try privateContext.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
}
In some occasion, it does not push changes into the persistent store, while the app is open the persistent container is changed but when I re-run the app changes are gone. What's the right way to save the changes into the persistent store.
This the class that does not work properly:
class func add(word: String, quotes:[Quotes], language: String){
for item in quotes {
if let phrase = item.phrase, let author = item.author {
let quote = CachedQuotes(context: PersistenceService.context)
quote.phrase = phrase
quote.date = Date() as NSDate
quote.keyword = word
quote.language = language
quote.author = author
PersistenceService.saveContext()
}
}
}
I call it to save quotes which are fetched from the network:
override func viewDidLoad() {
let quotes = CachedQuotes.getAllQuotes()
//Prints the number of saved records which is 0 now
self.getQuote { (result, error) in
if let qoutes = result?.quotes {
CachedQuotes.add(word: "friend", quotes: qoutes, language: "en")
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let quotes = CachedQuotes.getAllQuotes()
//Prints the number of saved records which is 10 now
}
But when I re-run the app, nothing is saved into the persistance container.
UPDATE:
The code below works now
static func saveContext () {
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.automaticallyMergesChangesFromParent = true
privateContext.parent = mainContext
privateContext.perform {
do {
try privateContext.save()
mainContext.perform({
do {
try mainContext.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
})
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
First it saves the private quoue then saves the main.
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = mainContext
You edit a context and then save the same context to persist the changes. Creating a child context to .viewContext and saving said child context does not save the .viewContext itself, where you made changes.
If you want to use background queues, first set var automaticallyMergesChangesFromParent: Bool on the .viewContext where you want to receive changes from the background queue. Then you create a background context, set on it the same persistentStoreCoordinator from .viewContext, make changes on it and then save the background queue.
Using privateContext.perform is a good start. You can do better if you wrap the changes to quote in a perform through the context in which the quote was created in the first place, so you access quote through the same thread the context uses.
Here is the singleton from Apple's Refreshing and Maintaining Your App Using Background Tasks sample.
import Foundation
import CoreData
class PersistentContainer: NSPersistentContainer {
private static let lastCleanedKey = "lastCleaned"
static let shared: PersistentContainer = {
ValueTransformer.setValueTransformer(ColorTransformer(), forName: NSValueTransformerName(rawValue: String(describing: ColorTransformer.self)))
let container = PersistentContainer(name: "ColorFeed")
container.loadPersistentStores { (desc, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
print("Successfully loaded persistent store at: \(desc.url?.description ?? "nil")")
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
return container
}()
var lastCleaned: Date? {
get {
return UserDefaults.standard.object(forKey: PersistentContainer.lastCleanedKey) as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: PersistentContainer.lastCleanedKey)
}
}
override func newBackgroundContext() -> NSManagedObjectContext {
let backgroundContext = super.newBackgroundContext()
backgroundContext.automaticallyMergesChangesFromParent = true
backgroundContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
return backgroundContext
}
}
Personally I prefer passing the NSPersistentContainer around via dependency injection but it requires a lot more effort.