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.
Related
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)
}
In order to initiate the coredata stack in app delegate is not recommended as it will create issue in a multi threaded environment. Therefore, I created a separate class for coredata stack and then a separate class to handle operations. And in the Coredata stack class it crashes. What am I missing here ?
CoreData Stack Class
Code looks like this
class CoreDataManager {
static let sharedManager = CoreDataManager()
private init() {} //Singleton
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyDatabase")
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
lazy var viewContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
lazy var cacheContext: NSManagedObjectContext = {
return self.persistentContainer.newBackgroundContext()
}()
lazy var updateContext: NSManagedObjectContext = {
let _updateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
_updateContext.parent = self.viewContext
return _updateContext
}()
}
This is how I access it
class StorageManager: NSObject {
lazy var managedObjectContext: NSManagedObjectContext = {
return CoreDataManager.sharedManager.persistentContainer.viewContext
}()
lazy var privateMOC: NSManagedObjectContext = {
return CoreDataManager.sharedManager.updateContext
}()
private func synchronize(privateMOC: NSManagedObjectContext) {
do {
try privateMOC.save()
self.managedObjectContext.performAndWait {
do {
try self.managedObjectContext.save()
// "Saved to main context"
} catch {
print("Could not synchonize data. \(error), \(error.localizedDescription)")
}
}
} catch {
print("Could not synchonize data. \(error), \(error.localizedDescription)")
}
}
}
A sample operation would look like this
func deleteContact(contactID: String) {
let privateMOC = self.privateMOC
privateMOC.performAndWait {
let request: NSFetchRequest<Contact> = Contact.fetchRequest()
request.predicate = NSPredicate(format: "contact_id= %#", contactID)
let result = try? privateMOC.fetch(request)
for object in result! {
privateMOC.delete(object)
}
self.synchronize(privateMOC: privateMOC)
}
}
I would like to test my core data methode.
There is multiples entities in my coredataModel and for each I have a NSManagedObject class
there is methode inside those classes to add, delete and remove data of the corresponding entity.
public class StoredGame: NSManagedObject {
static private let storage = DataManager.shared.storage
static var all: [Game] {
let request: NSFetchRequest<StoredGame> = StoredGame.fetchRequest()
guard let storedGame = try? storage.viewContext.fetch(request) else { return [] }
var games: [Game] = .init()
storedGame.forEach { (storedGame) in
games.append(convert(storedGame))
}
return games
}
static func add(new game: Game) {
let entity = NSEntityDescription.entity(forEntityName: "StoredGame", in: storage.viewContext)!
let newGame = StoredGame(entity: entity, insertInto: storage.viewContext)
try? storage.saveContext()
}
}
and then I have a class responsible of the core data stack
class CoreDataManager {
private lazy var persistentContainer: NSPersistentContainer! = {
guard let modelURL = Bundle.main.url(forResource: "CoreData", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let container = NSPersistentContainer(name: "CoreData", managedObjectModel: mom)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
func saveContext () throws {
let context = viewContext
if context.hasChanges {
do {
try context.save()
} catch(let error) {
print(error)
}
}
}
}
Then when it goes to the tests. I've created a mockContainer and a mockCoreData
lazy var mockContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "CoreData")
container.persistentStoreDescriptions[0].url = URL(fileURLWithPath: "/dev/null")
container.loadPersistentStores(completionHandler: { (_, error) in
XCTAssertNil(error)
})
return container
}()
lazy var mockCoreData = {
return StoredGame(context: mockContainer.viewContext)
}()
So now I dont know how to run tests in that configuration, I've tried a
XCTAssert(StoredGame.all.isEmpty) for exemple ( i have a all var in the StoredEntity class)
but it fails with an error telling
'NSInvalidArgumentException', reason: '-[CoreData.StoredEntity setId:]: unrecognized selector sent to instance
any idea?
This might be occurring with passing an invalid URL for the store description. Unless you need to run tests with a NSSQLiteStoreType, which is the default for NSPersistentContainer, you may want to consider using an NSInMemoryStoreType for unit testing. A small tweak to your CoreDataManager class could allow you to initialize the class both for your app and unit tests. For example:
class CoreDataManager {
private let persisted: Bool
init(persisted: Bool = true) {
self.persisted = persisted
}
lazy var persistentContainer: NSPersistentContainer = {
let description = NSPersistentStoreDescription()
if persisted {
description.type = NSSQLiteStoreType
description.url = // location to store the db.
} else {
description.type = NSInMemoryStoreType
}
let container = NSPersistentContainer(name: "CoreData")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores //...
return container
}()
}
Then you can use this exact class in your unit tests without need to create a mock. Just initialize it without persistence:
let manager = CoreDataManager(persisted: false)
I'm trying to save a string array to core data, but every time I try to read the array a get an empty one(I'm using NSString because of an answer I've read here, but it doesn't seem to work). Here is the code I use for saving :
let i = UserDefaults.standard.integer(forKey: "Index")
let fetchRequest: NSFetchRequest<PersonalTask> = PersonalTask.fetchRequest()
do {
let tasks = try PersistanceService.context.fetch(fetchRequest)
self.personalTasks = tasks
} catch {
print("Error")
}
let pTask = personalTasks[i]
var subtasks = pTask.subtasks
print(subtasks as Any)
let subtask = textView!.text as NSString
subtasks?.append(subtask)
print(subtasks)
PersistanceService.saveContext()
Here is the function saveContext() :
static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
static var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Tasks")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No descriptions found")
}
description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// MARK: - Core Data Saving support
static func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
And here I am reading the data and getting an empty array :
let i = UserDefaults.standard.integer(forKey: "Index")
let fetchRequest: NSFetchRequest<PersonalTask> = PersonalTask.fetchRequest()
do {
let tasks = try PersistanceService.context.fetch(fetchRequest)
self.personalTasks = tasks
} catch {
print("Error")
}
let pTask = personalTasks[i]
print(pTask.title)
print(pTask.notes)
print(pTask.subtasks)
subtasks = pTask.subtasks as [String]? ?? [NSString]() as [String]
print(subtasks)
What should I do to fix this?
I am rewriting my previous objC app in Swift 2.2. This is cocoa application where I am using NSArrayController to fill NSTableView contents.
The error is obvious, although similar setup worked in Objective C app.
Here is my AppDelegate:
var coreStack:AP_CoreDataStack!
var mainContext:NSManagedObjectContext!
override func awakeFromNib() {
coreStack = AP_CoreDataStack(){ (result) -> () in
if result {
self.mainContext = self.coreStack.mainContext
}
}
}
Setup of Core Data Stack
// MARK: - AP_CoreDataStack Class
class AP_CoreDataStack {
let mainContext: NSManagedObjectContext
let mastercontext: NSManagedObjectContext
var workerContext: NSManagedObjectContext?
internal typealias CallBack = (result:Bool) -> Void
init ( callback: CallBack) {
let modelURL = NSBundle.mainBundle().URLForResource("appNameSWIFT", withExtension: "momd")
if (modelURL == nil) {
print("Failed to initialize modelURL: \(modelURL)")
}
let mom = NSManagedObjectModel(contentsOfURL: modelURL!)
if mom == nil {
print("Failed to initialize model")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom!)
mastercontext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
mastercontext.persistentStoreCoordinator = psc
mainContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainContext.parentContext = mastercontext
// add store to psc in background thread
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
//BACKGROUND THREAD
// adding store to persistent store coordinator
let options = [NSInferMappingModelAutomaticallyOption:true,
NSMigratePersistentStoresAutomaticallyOption:true]
do {
// store = try psc.addP
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: applicationDocumentDirectory(), options: options)
} catch let error as NSError {
print("Error: Failed to load store \(error.localizedDescription), \(error.userInfo)")
}
// MAIN THREAD
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// On Main thread pass message that stack setup is complete
callback(result: true)
})
})
}
Above is the Swift version of my Obj C code which worked fine. I have an NSArrayController in xib file which is bound to Entity and NSManagedObjectContext in IB:
// Bind To Delegate
self.mainContext
It seems Array controller is accessing mainContext before it is initialised, but this is the same setup which worked in objC, so why it is causing error in Swift.
EDIT: I am using regular xib file.
EDIT 2:
Evidently mainContext is not nil as calling it here works correctly
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
let request = NSFetchRequest(entityName: "AP_EntityA")
let list:Array<AnyObject>
do {
list = try coreStack.mainContext.executeFetchRequest(request)
for item in list {
let product = item as! AP_EntityA
print("item name is: \(product.uniqueName)")
}
} catch let error as NSError {
// failure
print("Fetch failed: \(error.localizedDescription)")
}
}
Add the dynamic keyword to make Swift properties KVO compliant.
dynamic var mainContext:NSManagedObjectContext!