My Core Data stack is as follows:
Private Queue Managed Object Context (Parent)
Main Queue Managed Object Context (Child)
My issue is that I want to create a Search functionality using ONLY data downloaded from the server.
The ideal solution was to create a new-empty NSManagedObjectContext(to be used a temp write/read context) without the need to have a Persistent Store or a Parent but Xcode will crash.
Any alternatives?
PS: One other solution would be to have a tempAttribute flag (bool attribute) that would be set to true only in the current context (which has a parent the Private Queue Managed Object Context but I want to avoid it.
Your managed object context does need a NSPersistentStore, but there is one type of NSPersistentStore, NSInMemoryStoreType, which in fact does not persist data to the disk. I think that is what you are looking for. Here is some Swift code for creating a managed object context with such an in-memory persistent store:
// Create Core Data Stack
guard let dataModel = NSManagedObjectModel.mergedModel(from: [Bundle.main]) else {return}
let psc = NSPersistentStoreCoordinator(managedObjectModel: dataModel)
do {
try psc.addPersistentStore(ofType: NSInMemoryStoreType,
configurationName: nil,
at: nil,
options: nil);
} catch {
print("Error creating store: \(error)")
}
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.persistentStoreCoordinator = psc
A good follow-up question is: Why does Apple call it NSPersistentStore if not all of its types are persistent? Well, maybe they added the in-memory store store type at the last minute before macOS 10.4 was frozen for release and did not have time to change the name :)
You should also read through the differences among store types
Related
Recently I took over the development of an existing iOS project based on Core Data. The project uses a background sync for retrieving and updating data from the server and to write it to Core Data. However, in some cases this leads to a crash in the following block:
context.perform {
print("Replacing \(type) \(serverId)")
var modifiedObject = modifiedObject
modifiedObject.serverId = serverId
modifiedObject.modified = false
do {
try context.save()
}
catch {
print(error)
print("could not save context")
}
fulfill(())
}
The context of the above block is earlier created in a background context:
let context = self.persistentContainer.newBackgroundContext()
context.mergePolicy = NSMergePolicy(merge:
NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
context.stalenessInterval = 0
The app doesn't throw an error in the catch block, but stops working when accessing the context. No further log details can be found.
In most of the Core Data examples I've seen, persistentContainer.viewContext is used, or newBackgroundContext() for background operations. In this app, on the main thread the app by default uses the context registered to the object itself. See the following example:
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: location.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
The value of the property managedObjectContext is the context that is registered to the object. Therefore my assumption is the value changes and thereby an incorrect context is used. I think this causes a thread access issue.
Has anyone else experienced these issues when the registered context is used directly and how did you work around it?
Note:
This post does not apply since I actually use CoreData.
In this post the last answer suggests to fetch all items in the new background thread before adding new objects, but this is done in my code.
This post suggests to unfault an item before its context is saved, but this is also done in my code.
My app uses CoreData to store objects called shoppingItems. I wrote a class CoreDataManager that initialises CoreData and has essentially one function to overwrite the currently stored items, and one function to fetch all items. Both functions operate in the background, i.e. on a separate thread.
Here is my code (irrelevant parts are left out).
I setup core data on the main thread:
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
})
return container
}()
This is the write function:
func overwriteShoppingItems(_ shoppingItems: Set<ShoppingItem>, completion: #escaping (Error?) -> Void) {
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let viewContext = self.persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.performAndWait {
// Delete currently stored shopping items
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: CDEntityShoppingItem)
do {
let result = try backgroundContext.fetch(fetchRequest)
let resultData = result as! [NSManagedObject]
for object in resultData {
backgroundContext.delete(object)
}
if !shoppingItems.isEmpty {
// Save shopping items in managed context
let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: CDEntityShoppingItem, in: backgroundContext)!
for nextShoppingItem in shoppingItems {
let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity, insertInto: backgroundContext)
nextCdShoppingItem.name = nextShoppingItem.name
}
}
let saveError = self.saveManagedContext(managedContext: backgroundContext)
completion(saveError)
} catch let error as NSError {
completion(error)
}
}
}
func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
if !managedContext.hasChanges { return nil }
do {
try managedContext.save()
return nil
} catch let error as NSError {
return error
}
}
And this is the fetch function:
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let viewContext = self.persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.performAndWait {
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
let cdShoppingItems: [CDShoppingItem] = try backgroundContext.fetch(fetchRequest)
guard !cdShoppingItems.isEmpty else {
completion([], nil)
return
}
for nextCdShoppingItem in cdShoppingItems {
}
completion(shoppingItems, nil)
} catch let error as NSError {
completion(nil, error)
}
}
}
In normal operation, the code seems to work.
The problem:
I also wrote a unit test that tries to provoke multi-threading problems. This test uses a concurrent dispatch queue:
let concurrentReadWriteQueue = DispatchQueue(label: „xxx.test_coreDataMultithreading", attributes: .concurrent)
A timer defines the test time.
In the scheme for tests, I have set the arguments -com.apple.CoreData.Logging.stderr 1 and -com.apple.CoreData.ConcurrencyDebug 1.
During the test time, overwriteShoppingItems and fetchShoppingItems are inserted repeatedly in the queue, and executed concurrently.
This unit test executes a few reads and writes, before it stops at the line
let itemName = nextCdShoppingItem.name!
because nextCdShoppingItem.name is nil, which should never happen, because I never store nil.
Immediately before the crash, the following is logged:
CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600000e6c980, store PSC = 0x0)
If I do only fetches, or only writes, the CoreData warning is not logged. Thus it seems definitely to be a multi-threading issue.
However, CoreData.ConcurrencyDebug does not detect it.
It looks as if during a fetch operation on one thread, the other thread deletes the currently fetched item, so that its properties are read back as nil.
But this should not happen because fetches and saves are done with backgroundContext.performAndWait, i.e. serially.
And the stack trace shows that only a single thread accesses CoreData: Thread 3 Queue : NSManagedObjectContext 0x600003c8c000 (serial)
My questions:
What is my misuse of the CoreData API, and how to do it right?
Why is an item sometimes read back as nil?
EDIT:
Maybe this helps to identify the problem: When I comment out backgroundContext.delete(object) in overwriteShoppingItems, the error is no longer logged, and no item is fetched as nil.
Okay so i got same error but only while "archiving" the project. Took me several hours to find the issue, since I was getting the error only on uploading, in the assets catalog, I had some duplicate images, by removing them the error is gone.
Anyone else getting this error AND your Coredata setup is fine, check your assets folder.
If you are not using core data in your project then check your assets files are proper or not.
I am also getting error like this then i have findout this error from my project.
Problem seems to be solved.
It happened apparently, because the functions overwriteShoppingItems and fetchShoppingItems both setup a separate background context with let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) with their own queue so that fetches and saves were not serialized by a single queue.
I modified my code now in the following way:
I have now a property
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
that is initialized as
self.backgroundContext.persistentStoreCoordinator = self.persistentContainer.persistentStoreCoordinator
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
and fetches and saves are done using
backgroundContext.perform {…}
Now the CoreData error is no longer logged, and no item is fetched as nil.
In my case I created an new managedObjectContext without (yet) assigning a persistent store. Eventually I called -[psc metadataForPersistentStore:store] with the store being nil. This caused the log message to be written.
(side-note: you can always catch these outputs by setting a breakpoint on fprintf)
If you are seeing this during tear-down of a Core Data stack, check for un-removed observers or memory leaks.
In my case, I had managed objects which were each observing themselves – one of their relationships, using Swift's Key-Value Observing (NSKeyValueObservation objects). (Example use case: When the last a department's employees are deleted, delete the department.) The console messages appeared while the containing Core Data document was being closed. By breaking on fprintf I could see that the message was logged when a notification was being sent to an observer, and the number of messages was always equal to the number of non-faulted such objects in the managed object context (5 departments --> 5 console messages, etc., etc.). The problem was that I was not removing these observations, and the solution was of course to implement func willTurnIntoFault() in the observing objects, and in this func remove the observation.
In another situation, these messages stopped after I fixed a memory leak, but I did not study that in detail.
The full error messages were: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0)
CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0) and they occurred during the call to NSPersistentStoreCoordinator.remove(_:). So, this error is telling me that the store's PSC was nil, but I checked this with the debugger and found that the store's PSC was not nil before the call. I filed a bug to Apple on this (FB11669843) asking if they could improve the error message.
I have an NSFetchedResultsController and I am trying to update my data on a background context. For example, here I am trying to delete an object:
persistentContainer.performBackgroundTask { context in
let object = context.object(with: restaurant.objectID)
context.delete(object)
try? context.save()
}
There are 2 things I don't understand:
I would have expected this to modify, but not save the parent context. However, the parent context is definitely being saved (as verified by manually opening the SQLite file).
I would have expected the NSFetchedResultsController to update when the background content saves back up to its parent, but this is not happening. Do I need to manually trigger something on the main thread?
Obviously there is something I am not getting. Can anybody explain this?
I know that I have implemented the fetched results controller delegate methods correctly, because if I change my code to directly update the viewContext, everything works as expected.
Explanation
NSPersistentContainer's instance methods performBackgroundTask(_:) and newBackgroundContext() are poorly documented.
No matter which method you call, in either case the (returned) temporary NSManagedObjectContext is set up with privateQueueConcurrencyType and is associated with the NSPersistentStoreCoordinator directly and therefore has no parent.
See documentation:
Invoking this method causes the persistent container to create and
return a new NSManagedObjectContext with the concurrencyType set to
privateQueueConcurrencyType. This new context will be associated with
the NSPersistentStoreCoordinator directly and is set to consume
NSManagedObjectContextDidSave broadcasts automatically.
... or confirm it yourself:
persistentContainer.performBackgroundTask { (context) in
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
}
let context = persistentContainer.newBackgroundContext()
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
Due to the lack of a parent, changes won't get committed to a parent context like e.g. the viewContext and with the viewContext untouched, a connected NSFetchedResultsController won’t recognize any changes and therefore doesn’t update or call its delegate's methods. Instead changes will be pushed directly to the persistent store coordinator and after that saved to the persistent store.
I hope, that I was able to help you and if you need further assistance, I can add, how to get the desired behavior, as described by you, to my answer. (Solution added below)
Solution
You achieve the behavior, as described by you, by using two NSManagedObjectContexts with a parent-child relationship:
// Create new context for asynchronous execution with privateQueueConcurrencyType
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Add your viewContext as parent, therefore changes are pushed to the viewContext, instead of the persistent store coordinator
let viewContext = persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.perform {
// Do your work...
let object = backgroundContext.object(with: restaurant.objectID)
backgroundContext.delete(object)
// Propagate changes to the viewContext -> fetched results controller will be notified as a consequence
try? backgroundContext.save()
viewContext.performAndWait {
// Save viewContext on the main queue in order to store changes persistently
try? viewContext.save()
}
}
However, you can also stick with performBackgroundTask(_:) or use newBackgroundContext(). But as said before, in this case changes are saved to the persistent store directly and the viewContext isn't updated by default. In order to propagate changes down to the viewContext, which causes NSFetchedResultsController to be notified, you have to set viewContext.automaticallyMergesChangesFromParent to true:
// Set automaticallyMergesChangesFromParent to true
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
persistentContainer.performBackgroundTask { context in
// Do your work...
let object = context.object(with: restaurant.objectID)
context.delete(object)
// Save changes to persistent store, update viewContext and notify fetched results controller
try? context.save()
}
Please note that extensive changes such as adding 10.000 objects at once will likely drive your NSFetchedResultsController mad and therefore block the main queue.
The view context will not update unless you have set it to automatically merge changes from the parent. The viewContext is already set as child of any backgroundContext that you receive from the NSPersistentContainer.
Try adding just this one line:
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
Now, the viewContext WILL update after the backgroundContext has been saved and this WILL trigger the NSFetchedResultsController to update.
This works me perfectly in my project.
In function updateEnglishNewsListener(:), here parameter data is in anyobject and i further convert it into json formate for saving purpose.
Core Data uses thread (or serialized queue) confinement to protect managed objects and managed object contexts (see Core Data Programming Guide). A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method. You should not, therefore, initialize a context on one thread then pass it to a different thread.
There are three type
1. ConfinementConcurrencyType
2. PrivateQueueConcurrencyType
3. MainQueueConcurrencyType
The MainQueueConcurrencyType creates a context associated with the main queue which is perfect for use with NSFetchedResultsController.
In updateEnglishNewsListener(:) function, params data is your input. (data->Data you wants to update.)
private func updateEnglishNewsListener(data: [AnyObject] ){
//Here is your data
let privateAsyncMOC_En = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
// The context is associated with the main queue, and as such is tied into the application’s event loop, but it is otherwise similar to a private queue-based context. You use this queue type for contexts linked to controllers and UI objects that are required to be used only on the main thread.
privateAsyncMOC_En.parent = managedObjectContext
privateAsyncMOC_En.perform{
// The perform(_:) method returns immediately and the context executes the block methods on its own thread. Here it use background thread.
let convetedJSonData = self.convertAnyobjectToJSON(anyObject: data as AnyObject)
for (_ ,object) in convetedJSonData{
self.checkIFNewsIdForEnglishAlreadyExists(newsId: object["news_id"].intValue, completion: { (count) in
if count != 0{
self.updateDataBaseOfEnglishNews(json: object, newsId: object["news_id"].intValue)
}
})
}
do {
if privateAsyncMOC_En.hasChanges{
try privateAsyncMOC_En.save()
}
if managedObjectContext.hasChanges{
try managedObjectContext.save()
}
}catch {
print(error)
}
}
}
Checking data is already exist in coredata or not for avoiding the redundance data. Coredata have not primary key concept so we sequently check data already exist in coredata or not. Data is updated if and only if updating data is already exist in coredata. Here checkIFNewsIdForEnglishAlreadyExists(:) function return 0 or value . If it returns 0 then data is not saved in database else saved. I am using completion handle for knowing the new data or old data.
private func checkIFNewsIdForEnglishAlreadyExists(newsId:Int,completion:(_ count:Int)->()){
let fetchReq:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()
fetchReq.predicate = NSPredicate(format: "news_id = %d",newsId)
fetchReq.fetchLimit = 1 // this gives one data at a time for checking coming data to saved data
do {
let count = try managedObjectContext.count(for: fetchReq)
completion(count)
}catch{
let error = error as NSError
print("\(error)")
completion(0)
}
}
Replacing the old data to new one according to requirement.
private func updateDataBaseOfEnglishNews(json: JSON, newsId : Int){
do {
let fetchRequest:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "news_id = %d",newsId)
let fetchResults = try managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as? [TestEntity]
if let fetchResults = fetchResults {
if fetchResults.count != 0{
let newManagedObject = fetchResults[0]
newManagedObject.setValue(json["category_name"].stringValue, forKey: "category_name")
newManagedObject.setValue(json["description"].stringValue, forKey: "description1")
do {
if ((newManagedObject.managedObjectContext?.hasChanges) != nil){
try newManagedObject.managedObjectContext?.save()
}
} catch {
let saveError = error as NSError
print(saveError)
}
}
}
} catch {
let saveError = error as NSError
print(saveError)
}
}
Convertion anyobject to JSON for saving purpose in coredata
func convertAnyobjectToJSON(anyObject: AnyObject) -> JSON{
let jsonData = try! JSONSerialization.data(withJSONObject: anyObject, options: JSONSerialization.WritingOptions.prettyPrinted)
let jsonString = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String
if let dataFromString = jsonString.data(using: String.Encoding.utf8, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
return json
}
return nil
}
Hope it will help you. If any confusion then please ask.
I'm having a terrible problem. I'm saving a managedObject "Customer" with field sync as false using my main MOC. Then after syncing with server, I use a private MOC to save the Customer with sync field as true. When I save this, notification is passed and the main MOC saves the customer correctly. Then when I edit that customer, the same process happens. But this time, even if the sync field is true, upto the point where private MOC saves it, the notification got at the other end contains Customer with sync as false. If I repeat the process, sync becomes true. Instead of repeating, if I am saving some other data using the private MOC, then with that notification, Customer also comes with sync field as true. I don't understand why notification method is not working correctly. In addObserver of didsavenotification, I have specified the object as the private MOC itself. Somebody please help.
These are the code snippets prominent in this flow.
//Assigning false
selectedCustomer.synced = NSNumber(bool: false)
//Saving using main MOC
try managedObjectContext.save()
//Assigning synced details to a dictionary
let cust: CustomerDetails = CustomerDetails(data: customer)
cust.synced = true
//Assigning dictionary to customer managedObject fetched from coredata using private MOC
assignData(customerDetails, toCustomer: customer)
//Saving the private MOC
try syncManagedObjectContext.save()
//Getting notification and saving
managedObjectContext.performBlock { () -> Void in self.managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
}
//Registration of notification
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: #selector(AppDelegate.managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: syncManagedObjectContext)
EDIT:
These are the definitions of the MOCs I'm using
lazy var managedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
lazy var syncManagedObjectContext: NSManagedObjectContext = {
let coordinator = self.managedObjectContext.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
Show how you defined your MOCs as well.
Are they parent child?
Are they siblings against the NSPersistentStoreCoordinator?
This can have an impact.
I would also turn on -com.apple.CoreData.ConcurrencyDebug 1 and run your app again as this sounds like a threading issue.
Update 1
As your NSManagedObjectContext instances are siblings you really need to turn on the currency debug flag and run again. This really reads like a threading issue.
Update 2
There is not a lot of code in your question to try and deduce an answer if this is not a threading issue.
My next suggestion would be to move your private MOC from a sibling to a child (Apple's documentation on threading discusses this) and stop using the notification (unnecessary with child MOCs).
If the issue is still there then you are going to need to trace it in the debugger and find out at what point the issue surfaces. It can still be a threading issue without being a "touched the wrong object on the wrong thread" issue. Is the property being changed when you think it is? Using the debugger is the only way to be certain.
As mentioned in WWDC 2014 session 225 (Whatʼs New in Core Data), Core Data on iOS 8 and OS X Yosemite now support the command line argument -com.apple.CoreData.ConcurrencyDebug 1 to enable assertions that detect violations of Core Dataʼs concurrency contract.
In my experiments with this, I have found that it works under iOS 8 beta 1 (both on the device and in the simulator), but I seem to have found a false positive, i.e. the framework is throwing a multithreading violation exception where it should not do so. At least that's what I believe.
Question: is the code below correct or am I doing something that violates Core Dataʼs threading model?
What I do is set up a very simple Core Data stack (with an in-memory store, for simplicity's sake) with a managed object context named backgroundContext with private queue concurrency. I then invoke performBlockAndWait { } on that context and in the block I create a new managed object, insert it into the context, and save.
The save operation is where I get the multithreading violation exception from Core Data.
var backgroundContext: NSManagedObjectContext?
func setupCoreDataStackAndViolateThreadingContract()
{
let objectModelURL = NSBundle.mainBundle().URLForResource("CoreDataDebugging", withExtension: "momd")
let objectModel: NSManagedObjectModel? = NSManagedObjectModel(contentsOfURL: objectModelURL)
assert(objectModel)
// Set up a simple in-memory Store (without error handling)
let storeCoordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: objectModel)
assert(storeCoordinator)
let store: NSPersistentStore? = storeCoordinator!.addPersistentStoreWithType(NSInMemoryStoreType, configuration: nil, URL: nil, options: nil, error: nil)
assert(store)
// Set up a managed object context with private queue concurrency
backgroundContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
assert(backgroundContext)
backgroundContext!.persistentStoreCoordinator = storeCoordinator!
// Work on the background context by using performBlock:
// This should work but throws a multithreading violation exception on
// self.backgroundContext!.save(&potentialSaveError)
backgroundContext!.performBlockAndWait {
NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: self.backgroundContext!) as NSManagedObject
person.setValue("John Appleseed", forKey: "name")
var potentialSaveError: NSError?
// In the following line: EXC_BAD_INSTRUCTION in
// `+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__]:
let didSave = self.backgroundContext!.save(&potentialSaveError)
if (didSave) {
println("Saving successful")
} else {
let saveError = potentialSaveError!
println("Saving failed with error: \(saveError)")
}
}
}
I have tested essentially the same code in Objective-C and got the same result so I doubt it is a Swift problem.
Edit: If you want to run the code yourself, I have put a project on GitHub (requires Xcode 6/iOS 8 beta).
Apple confirmed this as a bug. It has been fixed with Xcode 6 beta 4.