My app has a reproducible CoreData error.
I use the viewContext for display, and a backgroundContext for object updates. Both contexts belong to the same NSPersistentCloudKitContainer.
At some point, I save in the backgroundContext an object after updating its status attribute from 2 to 1, and its updatedAt attribute from nil to Date().
Later, I want to fetch back this updated object, and my understanding is that a fetch always returns the content of the persistent store.
Thus, the fetched object should be the same regardless into which context it is fetched. However, this is not the case.
I have also set -com.apple.CoreData.ConcurrencyDebug 1 as a launch argument, so this is not a CoreData multithreading error.
Here is my test code:
The object is saved here:
let context = backgroundContext!
context.performAndWait {
assert(ItemStatus(rawValue: item.status) == .isBought)
item.status = ItemStatus.isToBuy.rawValue
item.updatedAt = Date()
_ = saveContext(context)
}
with
func saveContext(_ context: NSManagedObjectContext) -> Error? {
if !context.hasChanges { return nil }
let inserts = context.insertedObjects; if !inserts.isEmpty { print("Will save inserted objects: \(inserts)") }
let updates = context.updatedObjects; if !updates.isEmpty { print("Will save updated objects: \(updates)") }
let deletes = context.deletedObjects; if !deletes.isEmpty { print("Will save deleted objects: \(deletes)") }
do {
try context.save()
print("\(context.name!) saved")
} catch {
fatalError("Unresolved error")
}
return nil
}
Later, I fetch the object into both contexts using:
let mwFetchRequest = NSFetchRequest<Item>(entityName: Item.entityName)
let passwordPredicate = NSPredicate(format: "\(Schema.Item.password) == %#", password)
let namePredicate = NSPredicate(format: "\(Schema.Item.name) == %#", "Mineral water")
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [passwordPredicate, namePredicate])
mwFetchRequest.predicate = compoundPredicate
mwFetchRequest.returnsObjectsAsFaults = false
backgroundContext.performAndWait {
let bcItem = try! backgroundContext.fetch(mwFetchRequest)
print("backgroundContext: \(bcItem)")
}
viewContext.performAndWait {
let vcItem = try! viewContext.fetch(mwFetchRequest)
print(„viewContext: \(vcItem)")
}
And here is the log when I set a breakpoint after this code:
Will save updated objects: [<ShopEasy.Item: 0x600000d7cf50> (entity: Item; id: 0x9698d776a7665623 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Item/p1617>; data: {
buyPlaces = "<relationship fault: 0x600002e296a0 'buyPlaces'>";
fixedAtTopAt = nil;
howOftenBought = 1;
lastBoughtDate = "2021-01-24 13:02:09 +0000";
name = "Mineral water";
password = "PW_1";
status = 1;
updatedAt = "2021-01-24 13:32:14 +0000";
})]
backgroundContext saved
…
backgroundContext: [<ShopEasy.Item: 0x600000d7cf50> (entity: Item; id: 0x9698d776a7665623 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Item/p1617>; data: {
buyPlaces = "<relationship fault: 0x600002e296a0 'buyPlaces'>";
fixedAtTopAt = nil;
howOftenBought = 1;
lastBoughtDate = "2021-01-24 13:02:09 +0000";
name = "Mineral water";
password = "PW_1";
status = 1;
updatedAt = "2021-01-24 13:32:14 +0000";
})]
viewContext: [<ShopEasy.Item: 0x600000d75ae0> (entity: Item; id: 0x9698d776a7665623 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Item/p1617>; data: {
buyPlaces = (
"0x9698d776b10e5621 <x-coredata://35BF43D6-4CF7-490D-B944-9DDFF2823AA1/Place/p971>"
);
fixedAtTopAt = nil;
howOftenBought = 1;
lastBoughtDate = "2021-01-24 13:02:09 +0000";
name = "Mineral water";
password = "PW_1";
status = 2;
updatedAt = nil;
})]
Obviously, the object is first correctly saved using the backgroundContext, and should thus be in the persistent store.
It is then fetched back correctly into the backgroundContext.
But after fetching the same object into the viewContext, the two changed attributes, status and updatedAt, have the values as they were before the save.
My questions:
Are my assumptions wrong? Is something wrong with my code?
Later, I want to fetch back this updated object, and my understanding
is that a fetch always returns the content of the persistent store.
The fetch selects the objects to return based on the content of the persistent store, but it does not by default update the in-memory copies of the objects based on the store’s content. There is an option to do this, which in my experience doesn’t work. To update an existing object from the store, you could refresh it or set up merging on your context so that changes to the store are automatically propagated.
Related
developing a little app for my comic collection encountered this issue:
in my second "add comic" VC I have a button and the func below, but I save TWICE entities in manged context (ate least, I think this is the issue)
for example if I have 2 comics yet shown in main VC tableview, go to "add comic VC" and save a third one, going back to main VC I'll print 3 objects with title, number etc but also print 2 new objects with no data as I had saved twice a manger context a "right one" and another one with same number of object but empty. If I keep adding a 4th comic, I'll get 6 complete comic + the 4th and more 6 "blank itmes" with default values "no title"
let kComicEntityName = "Comic"
func addingSingleComic(gotTitle: String, gotIssue: Int16, gotInCollection: Bool ) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: kComicEntityName, in: managedContext)!
let comicToAdd = Comic(entity: entity, insertInto: managedContext)
comicToAdd.comicTitle = gotTitle
comicToAdd.issueNumber = gotIssue
comicToAdd.inCollection = gotInCollection
do {
try managedContext.save()
} catch let error as NSError {
print("could not save. \(error), \(error.userInfo)")
}
print("new single comic crated: title: \(comicToAdd.comicTitle ?? "!! not title !!"), n. \(comicToAdd.issueNumber), owned?: \(comicToAdd.inCollection)")
}
in the main VC I use this to check items in core data
func asyncPrintEntities() {
self.asyncComicEntityArray.removeAll(keepingCapacity: true)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
let comicFetch : NSFetchRequest<Comic> = Comic.fetchRequest()
asyncFetchRequest = NSAsynchronousFetchRequest<Comic>(fetchRequest: comicFetch) {
[unowned self] (result: NSAsynchronousFetchResult) in
guard let AllComicEntityResult = result.finalResult else {
return
}
self.asyncComicEntityArray = AllComicEntityResult
//************************************
do {
self.asyncComicEntityArray = try managedContext.fetch(comicFetch)
if self.asyncComicEntityArray.count > 0 {
print("Ok! model is not empty!")
} else {
print("No entites availabe")
}
} catch let error as NSError {
print("Fetch error: \(error) description: \(error.userInfo)")
}
guard self.asyncComicEntityArray != nil else {return}
for comicFoundInArray in self.asyncComicEntityArray {
let entity = NSEntityDescription.entity(forEntityName: self.kComicEntityName, in: managedContext)!
var comicTouse = Comic(entity: entity, insertInto: managedContext)
// var comicTouse = Comic() //to be deleted since this kind of init is not allowed, better above insertInto
comicTouse = comicFoundInArray as! Comic
print("comic title: \(comicTouse.comicTitle ?? "error title"), is it in collection? : \(comicTouse.inCollection)")
}
self.MyTableView.reloadData()
//************************************
}
// MARK: - async fetch request 3
do {
try managedContext.execute(asyncFetchRequest)
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
//end of function
}
In your addingSingleComic you create a new Comic here:
let comicToAdd = Comic(entity: entity, insertInto: managedContext)
Then you assign values to the object's properties.
Separately, in asyncPrintEntities, you also create new Comic objects here:
var comicTouse = Comic(entity: entity, insertInto: managedContext)
This time you do not assign values to the object's properties. They will have no title, etc, because you created them but never assigned a title. This line executes once for every object in asyncComicEntityArray, so if the array has two objects, you create two new objects that contain no data. You don't use comicToUse anywhere except in the one print, but it still exists in the managed object context and will still get saved the next time you save changes.
This is why you're getting extra entries-- because you're creating them in this line of code. It's not clear why you're creating them here. You just executed a fetch request, and then you immediately create a bunch of no-data entries which you don't use. It looks like that entire for loop could just be deleted, because the only thing it does is create these extra entries.
Hello All & Thanks in Advance!
I am a noob with Core Data and I need to change data in one of my fields which is named: recid.
I have created a index which again is: recid as int 16 in my core data model.
What I am needing to do is fetch the record and changed recid from we will say 5 to 1 how would I go about doing this?
Here is the code I have built so far & I will take care of my loop after I understand how to change the data in the record.
-(void)awakeFromNib
{
NSMenu *theMenu;
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
[statusItem setMenu:statusMenu];
[statusItem setImage:[NSImage imageNamed:#"TheJournal_16x16x32"]];
[statusItem setHighlightMode:YES];
theMenu = [[NSMenu alloc] initWithTitle:#""];
[theMenu addItemWithTitle:#"The Journal" action:#selector(showTheWindow:) keyEquivalent:#"W"];
[theMenu addItemWithTitle:#"Quit" action:#selector(terminate:) keyEquivalent:#"Q"];
[statusItem setMenu:theMenu];
[theMenu release];
NSUInteger count;
count = 0;
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
count = [prefs integerForKey:#"recid"];
NSLog(#"counter is >>>>%lu",(unsigned long)count);
[prefs setInteger:count forKey:#"recid"];
count++;
NSUserDefaults *prefs1 = [NSUserDefaults standardUserDefaults];
[prefs1 setInteger:count forKey:#"recid"];
NSLog(#"counter is >>>>%lu",(unsigned long)count);
// How I fetch the record & change the value from 5 to 1?
}
Here's a code sample that may help you. In this example, the YourManagedClass Core Data entity uses a UUID string as a unique record identifier. The extension contains a static function that fetches the unique record, sets the new recid value and then saves the NSManagedObjectContext.
import Foundation
import CoreData
class YourManagedClass: NSManagedObject {
#NSManaged var uuid: String?
#NSManaged var recid: NSNumber?
}
extension YourManagedClass {
static func set(recID: Int16, forObject uuid: String, `in` context: NSManagedObjectContext) {
let fetchRequest = NSFetchRequest<YourManagedClass>(entityName: "YourManagedClass")
fetchRequest.predicate = NSPredicate(format: "uuid = %#", argumentArray: [uuid])
let object: YourManagedClass
do {
let objects = try context.fetch(fetchRequest)
guard let foundObject = objects.first else {
return
}
object = foundObject
} catch {
// Handle Error
return
}
object.recid = NSNumber(value: recID)
do {
try context.save()
} catch {
// Handle Error
}
}
}
You would then call this function with a reference to your NSManagedObjectContext ('context' here):
YourManagedClass.set(recID: 15, forObject: "909455F3-C812-4399-83B4-F96A5C32A71D", in: context)
I have the problem about SIGBART. i did google it and try to solve it.
but i only have it on one of my buttons.
This is how my tool bar said.
CoreData: error: -addPersistentStoreWithType:SQLite
configuration:(null)
URL:file:///var/mobile/Containers/Data/Application/D223A294-3617-494F-8774-9CA6DCB61C2D/Documents/SingleViewCoreData.sqlite
options:(null) ... returned error Error Domain=NSCocoaErrorDomain
Code=134100 "The managed object model version used to open the
persistent store is incompatible with the one that was used to create
the persistent store." UserInfo={metadata={
NSPersistenceFrameworkVersion = 637;
NSStoreModelVersionHashes = {
Item = ;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "7711BB5D-CAF4-4F2E-9122-AF54B74D3850";
"_NSAutoVacuumLevel" = 2; }, reason=The model used to open the store is incompatible with the one used to create the store} with
userInfo dictionary {
metadata = {
NSPersistenceFrameworkVersion = 637;
NSStoreModelVersionHashes = {
Item = ;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "7711BB5D-CAF4-4F2E-9122-AF54B74D3850";
"_NSAutoVacuumLevel" = 2;
};
reason = "The model used to open the store is incompatible with the one used to create the store"; } 2016-01-07 12:18:39.271
test7[2483:1077844] Unresolved error Error Domain=YOUR_ERROR_DOMAIN
Code=9999 "Failed to initialize the application's saved data"
UserInfo={NSLocalizedDescription=Failed to initialize the
application's saved data, NSLocalizedFailureReason=There was an error
creating or loading the application's saved data.,
NSUnderlyingError=0x134e63a80 {Error Domain=NSCocoaErrorDomain
Code=134100 "The managed object model version used to open the
persistent store is incompatible with the one that was used to create
the persistent store." UserInfo={metadata={
NSPersistenceFrameworkVersion = 637;
NSStoreModelVersionHashes = {
Item = ;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "7711BB5D-CAF4-4F2E-9122-AF54B74D3850";
"_NSAutoVacuumLevel" = 2; }, reason=The model used to open the store is incompatible with the one used to create the store}}},
[NSLocalizedDescription: Failed to initialize the application's saved
data, NSLocalizedFailureReason: There was an error creating or loading
the application's saved data., NSUnderlyingError: Error
Domain=NSCocoaErrorDomain Code=134100 "The managed object model
version used to open the persistent store is incompatible with the one
that was used to create the persistent store." UserInfo={metadata={
NSPersistenceFrameworkVersion = 637;
NSStoreModelVersionHashes = {
Item = ;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "7711BB5D-CAF4-4F2E-9122-AF54B74D3850";
"_NSAutoVacuumLevel" = 2; }, reason=The model used to open the store is incompatible with the one used to create the store}] (lldb)
Code on board:
import UIKit
import CoreData
class RecordTVC: UITableViewController, NSFetchedResultsControllerDelegate {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc : NSFetchedResultsController = NSFetchedResultsController()
func fetchRequest() ->NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "users")
let sortDescriptior = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptior]
return fetchRequest
}
func getFRC() ->NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
return frc
}
override func viewDidLoad() {
super.viewDidLoad()
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("failed to perform fetch")
return
}
self.tableView.rowHeight = 100
}
override func viewDidAppear(animated: Bool) {
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("failed to appear")
return
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
_ = frc.sections?.count
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
_ = frc.sections?[section].numberOfObjects
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
cell.textLabel?.textColor = UIColor.blueColor()
let users = frc.objectAtIndexPath(indexPath) as! Users
cell.textLabel?.text = users.name
let date = users.data
let note = users.note
cell.detailTextLabel!.text = " Date: \(date!) Note: \(note!)"
return cell
}
}
And on the appDelegate:
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "mis-dentmate.com.tw.test7" in the application's documents Application Support directory.
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1]
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("test7", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
My managedObjectContext hierarchy is as follows: (PSC)<-(writerMOC -- private)<-(mainMOC -- main)<-(backgroundMOC -- private)
I have an NSManagedObject who "name" property is "Banana".
In the backgroundMOC, I get a reference to the object with backgroundMOC.objectWithID, change the NSManagedObject's "name" property to "Apple", and subsequently set it's "syncStatus" property to 1 (flagged for synchronization), then recursively save the moc's with the following routine:
func saveManagedContext(moc: NSManagedObjectContext, shouldSync: Bool = true, completion: (() -> Void)? = nil)
{
print("\nSaving managed object context...")
do {
try moc.save()
if let parentContext = moc.parentContext {
parentContext.performBlock {
self.saveManagedContext(parentContext, shouldSync: shouldSync, completion: completion)
}
}
else {
if shouldSync { SyncEngine.sharedInstance.synchronize(shouldPushUpdates: true) }
completion?()
}
print("Finished saving managed object context...")
} catch {
logger.error("\(error)")
}
}
Once the last moc is saved, a sync routine is called which does its work on the backgroundMOC, which queries the local store for all objects whose syncStatus is 1, again this fetch is called on the backgroundMOC.
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
This correctly returns the updated object in the array, however, that object's syncStatus property equals 0, and its "name" property is still set to "Banana".
This is really causing me headaches, I felt like i had totally understood how managedObjectContext blocks should work, but this has proven to be quite a puzzle.
UPDATE
Here's the code that prompts the update. This is called from the main thread when the cell is tapped.
func updateNameForCell(cell: UITableViewCell)
{
///gets the object id from the fetchedResultsController
guard let fruitMetaID = tableController.objectIDForCell(cell) else { return }
let backgroundMOC = CoreDataController.sharedInstance.newBackgroundManagedObjectContext()
backgroundMOC.performBlock {
do {
guard let fruit = (backgroundMOC.objectWithID(fruitMetaID) as? FruitMetaData)?.fruit else {
throw //Error
}
print(fruit.name) // "Banana"
fruit.name = "Apple"
fruit.needsSynchronization() //Sets syncStatus to 1
CoreDataController.sharedInstance.saveManagedContext(backgroundMOC)
}
catch {
//handle error
}
}
}
UPDATE AGAIN
Maybe I'm not creating the contexts right. Enlighten me please!
/// The parent to all other NSManagedObjectContexts. Responsible for writting to the store.
lazy var writerManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
}
return managedObjectContext
}()
lazy var mainManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.parentContext = self.writerManagedObjectContext
}
return managedObjectContext
}()
/// The context associated with background syncing..
func newBackgroundManagedObjectContext() -> NSManagedObjectContext
{
let backgroundManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
backgroundManagedObjectContext.performBlockAndWait {
backgroundManagedObjectContext.parentContext = self.mainManagedObjectContext
}
return backgroundManagedObjectContext
}
Holding onto child MOCs (children of the main context) is fraught with issues. I would recommend creating a new child (aka backgroundMOC) for each operation that you do.
Without seeing all of your code this looks like an issue with the child context getting out of sync.
Update
Assuming that your creation of the backgroundMOC sets the mainMOC as its parent then I wonder about the -objectWithID: and what it is returning.
I also wonder about your -performBlock: calls. In my head the threading looks fine but better to test. Try changing to -performBlockAndWait: just to test and see if there is a threading race condition. Not a permanent change but eliminates that part of the code as a source of the issue.
Before fetchRequest is called, you should reset context.
backgroundMOC.reset() // add this line
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
The reason is FruitMetaData is an object(or class) so changing one of its properties/Core Data attributes does not register as a change to the results array ... the object references in the array remain the same.
And NSFetchRequest still returns the same result(by using cache). When use context.reset().This tells the context in the extension to fetch new data every time and ignore the cache.
I have seen many examples with one view controller for adding or updating core data items. Any thoughts on pros or cons of doing in separate view controllers?
My code for trying to do the update I think I am missing one key part to get it to work.
#IBAction func saveItem(sender: AnyObject) {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("Items", inManagedObjectContext: context)
var existingItem = dataModel.self
if (row > 0) {
println(teaname.text)
existingItem.setValue(teaname.text as String, forKey: "name")
existingItem.setValue(teatype.text as String, forKey: "type")
existingItem.setValue(qty.text as String, forKey: "amount")
existingItem.setValue(temp.text as String, forKey: "temp")
existingItem.setValue(time.text as String, forKey: "time")
} else {
}
context.save(nil)
self.navigationController?.popViewControllerAnimated(true)
}
I get (lldb) with a thread breakpoint at existingItem.setValue(teaname.text as String, forKey: "name")
It does not appear you actually have a specific object to update. I use the following function to fetch an object by its unique ID. Only once you have an object (mine is called Event) can you update it.
func fetchEvent(eventID: Int) -> Event? {
// Define fetch request/predicate/sort descriptors
var fetchRequest = NSFetchRequest(entityName: "Event")
let sortSections = NSSortDescriptor(key: "eTitle", ascending: true)
let sortDescriptor = NSSortDescriptor(key: "eID", ascending: true)
let predicate = NSPredicate(format: "eID == \(eventID)", argumentArray: nil)
var error = NSErrorPointer()
// Assign fetch request properties
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [sortSections, sortDescriptor]
fetchRequest.fetchBatchSize = 1
fetchRequest.fetchLimit = 1
// Handle results
let fetchedResults = managedObjectContext?.executeFetchRequest(fetchRequest, error: error)
if fetchedResults?.count != 0 {
if let fetchedEvent: Event = fetchedResults![0] as? Event {
println("Fetched object with ID = \(eventID). The title of this object is '\(fetchedEvent.eTitle)'")
return fetchedEvent
}
}
return nil
}
Once you have fetched an object and have a core data object to update, then you can update it like so.
func updateEvent(eventDict: Dictionary<String, AnyObject>, id: Int) {
if let event: Event = fetchEvent(id) {
println(event)
event.eID = id
event.eTitle = getString(eventDict["title"])
event.eLocation = getString(eventDict["location"])
event.eDescription = getString(eventDict["description"])
event.eStart = getDate(eventDict["startDate"])
event.eEnd = getDate(eventDict["endDate"])
event.eMod = NSDate()
event.eSecID = getSecID(event)
}
}
And then you may want to save your managed object context.