Why would there be inconsistency when saving NSManagedObjectContext after adding NSSecureUnarchiveFromDataTransformer? - ios

I have an app that uses Core Data to persist a store of Events.
An Event has an optional location (stored as a CLLocation) as one of its attributes. In my model, this location attribute has the type Transformable:
My app has been in production for several years and everything's been working reliably, but some time in the past year I started getting an error in the Xcode console telling me I should switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead.
After doing some research (I'd consider myself a novice at Core Data) I determined I should write a NSSecureUnarchiveFromDataTransformer subclass and put the name of that class in the Transformer field for the location attribute, which was blank, with the Value Transformer Name placeholder text:
From what I found online, the subclass could be pretty straightforward for a Transformable attribute that contains a CLLocation:
#objc(CLLocationValueTransformer)
final class CLLocationValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: CLLocationValueTransformer.self))
override static var allowedTopLevelClasses: [AnyClass] {
return [CLLocation.self]
}
public static func register() {
let transformer = CLLocationValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}
So, I made this subclass in my project and put the class name in the Transformer field for the location attribute:
But now, here's the problem:
Once I started using my app after implementing the Transformer, I started getting unpredictable results.
Sometimes, when I created a new Event, it disappeared at the next app launch. It was not persisted by Core Data across app launches, like it was before the change.
Sometimes Events were saved, but sometimes they were not.
I couldn't figure out a clear pattern. They were not always saved if the location was included when it was first created, but sometimes they were. It seemed like other times the original Event was saved, but without location if the location was added later.
I've left out the boilerplate Core Data code, but basically I have baseManagedObjectContext, which is the layer that's connected to the NSPersistentStoreCoordinator to save data to disk.
baseManagedObjectContext is a parent to mainObjectContext, which is used by most of my UI. Then I create private contexts to write changes to first before saving them.
Here is example code to create a new Event with a possible location and save it, which was working consistently for years before adding the NSSecureUnarchiveFromDataTransformer subclass as the Transformer on location. I added fatalError for debugging, but it was never called, even when my data didn't fully save to disk:
private func addEvent(location: CLLocation?) {
let privateLocalContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateLocalContext.parent = coreDataStack.mainObjectContext
privateLocalContext.undoManager = nil
let entity = NSEntityDescription.entity(forEntityName: "Event",
in: privateLocalContext)
let newEvent = Event(entity: entity!,
insertInto: privateLocalContext)
if let location = location {
newEvent.location = location
}
privateLocalContext.performAndWait {
do {
try privateLocalContext.save()
}
catch { fatalError("error saving privateLocalContext") }
}
coreDataStack.mainObjectContext.performAndWait {
do {
try coreDataStack.mainObjectContext.save()
}
catch { fatalError("error saving mainObjectContext") }
}
coreDataStack.baseManagedObjectContext.perform {
do {
try coreDataStack.baseManagedObjectContext.save()
}
catch { fatalError("error saving baseManagedObjectContext") }
}
}
With further debugging, I found that sometimes, even if the change was making it to mainObjectContext, it was not making it all the way to baseManagedObjectContext, or to disk. I created a whole separate Core Data stack for testing to read directly from disk.
This issue with the saves not propogating (like they did before, for years) is what was causing the data to not persist across app launches, but I do not understand why this would suddenly start happening after my addition of the Transformer on location.
What am I missing here with how Core Data works?
I did not think I was fundamentally changing anything when I switched from a blank Transformer field to a subclass of NSSecureUnarchiveFromDataTransformer, but clearly something is going on that I don't understand.
I'd like to adopt NSSecureUnarchiveFromDataTransformer, since Apple is recommending it. How can I change what I'm doing to be able to adopt it and have data save consistently?
For now, I've switched back to the blank Transformer field to keep things working like they did before.

Related

Coredata duplicates all objects in a list on update

I've come across an issue which rarely happens, and (of course) it works perfectly when I test it myself. It has only happened for a few users, and I know I have at least a couple hundred who use the same App daily.
The issue
When updating a list of coredata objects in a tableview, it not only updates the objects (correctly), it also creates duplicates of all these objects.
Coredata setup
It's a NSPersistentCloudKitContainer with these settings:
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
Tableview setup
I have a tableview which displays a list of the 'ActivityType' objects. It's very simple, they have a name (some other basic string/int properties), and an integer called 'index'. This 'index' exists so that users can change the order in which they should be displayed.
Here is some code for how each row is setup:
for activityType in activityTypes {
row = BlazeRow()
row.title = activityType.name
row.cellTapped = {
self.selectedActivityType(activityType)
}
row.object = activityType
row.cellReordered = {
(index) in
self.saveNewOrder()
}
section.addRow(row)
}
As you can see, it has 2 methods. One for selecting the activity which shows its details in a new viewcontroller, and one which is called whenever the order is changed.
Here's the method that is called whenever the order is changed:
func saveNewOrder() {
Thread.printCurrent()
let section = self.tableArray[0] as! BlazeSection
for (index, row) in section.rows.enumerated() {
let blazeRow = row as! BlazeRow
let object = blazeRow.object as! ActivityType
object.index = Int32(index)
}
BDGCoreData.saveContext()
}
And here's the code that saves the context (I use a singleton to easily access the viewcontext):
class func saveContext(context: NSManagedObjectContext = BDGCoreData.viewContext) {
if(context.hasChanges) {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
Now, I swear to god it never calls the method in this viewcontroller to create a new object:
let activity = ActivityType(context: BDGCoreData.viewContext). I know how that works, and it truly is only called in a completely different view controller. I searched for it again in my entire project just in case, and it's really never called/created in any other places.
But somehow, in very rase cases, it saves the correct new order but also duplicates all objects in this list. Since it only happens rarely, I thought it might have something to do with threads? Which is why, as you can see in the code, I printed out the current thread, but at least when testing on my device, it seems to be on the main thread.
I'm truly stumped. I have a pretty good understanding of coredata and the app itself is quite complex with full of objects with different kind of relationships.
But why this happens? I have no clue...
Does anyone have an idea?

KVO not working for custom property of NSManagedObject

I have a subclass of NSManagedObject Folder with a state of Availability
#objc enum Availability: Int16 {
case unknown
case available
case unavailable
}
Folder has to do extra stuff (like delete related files) whenever it's availability changes. So I have
internalAvailability saved in core data
Computed property availability using above property
`
extension Folder {
#NSManaged private var internalAvailability: Availability
}
extension Folder {
private func deleteFiles(...) {
...
}
#objc dynamic public var availability: Availability {
get {
return internalAvailability
}
set {
willChangeValue(forKey: "availability")
deleteFiles()
internalAvailability = newValue
didChangeValue(forKey: "availability")
}
}
}
Using Reactive, I want to change navigation item's title based on availability but the signal is never called after once!
```
let property = DynamicProperty<NSNumber>(object: folder, keyPath: "availability")
internalVariable = property // To have a reference of property
navigationItem.reactive.title <~ property.map { (stateNumber) -> String in
guard let a = Availability(rawValue: stateNumber.int16Value) else {
assertionFailure()
return ""
}
let prefix = a == .available ? "" : "(Nope) "
return "\(prefix)\(folder.name)"
}
I have explicitly added KVO compliance to the property in hopes that this starts working, but alas no results.
Edit: if I create the DynamicProperty on internalAvailability instead of availability, everything works smoothly..
Adding as an answer since it became a learning exercise. Hopefully someone else too would be benefitted.
The app uses a multiple managedObjectContext(moc) architecture. 1 private moc to make changes and 1 main thread moc that synchronises itself using mergeChanges.
In above code, navigationItem is using the folder instance kept with main-moc. The DynamicProperty is listening to KVO changes on this main-moc's folder instance. Let's call this main-folder. When I make changes, I modify the folder instance we have on private-moc. Let's call it private-folder.
On modifying private-folder and calling save on private-moc, a notification of name NSManagedObjectContextDidSave is broadcasted. main-moc synchronizes itself using mergeChanges.
mergeChanges changes main-folder, but notice that it would never call the computed-property-setter availability. It directly changes internalAvailability.
And thus, no KVO notifications are posted of our computed property.
TL;DR When doing KVO on a NSManagedObject subclass, use a stored property instead of computed one. In case you have a multi-moc (managed object context) scenario and use mergeChanges to synchronise, setter for your computed property is not called when synchronising.
Edit (Solution): add method of the pattern keyPathsForValuesAffecting<KeyName> KVO relevant documentation
#objc class func keyPathsForValuesAffectingAvailability() -> Set<NSObject> {
return [#keyPath(Folder.internalAvailability) as NSObject]
}
When using Core Data we use the NSManagedObjectContextObjectsDidChange notification instead of KVO. This brings many advantages including coalescing of change events and undo support. If we need to know what attributes changed on an object we can examine changedValuesForCurrentEvent which even includes transient attributes that have a matching keyPathsForValuesAffecting.... These advantages likely outweigh those from a KVO binding framework aka reactive.

Core Data seems to delete my data right after insertion

I have a lot of data stored in Core Data without any trouble. Now I need to add some additional data. Instead of creating an entity for all this new data, I have decided to use the fact that the object to be stored(and all its children) implement NSCoding, and rather store the object as a Transformable (NSObject). I have done this before (in Obj-c), but for some reason I can't get it to work this time.
Let's say I have a huge class named Event:NSObject,NSCoding which contains name, date, and a metric ton of additional variables. Then imagine you'd want the ability to let the user receive a notification a given number of days before the event starts. Like, let the user "watch" the event. I want to keep track of which events are being watched, and how long before I should send the notification. With this, I can get a list of all the "watched" events, and know how many days before the event they want a notification. This is just a poor example of the real situation, just bear with me. Don't think about the "notification" part, just storing the data.
I have my Event-object, now I have created a WatchEvent-entity in my CoreData-database which has two attributes: event:Transformable and days:Integer. Neither are optional.
My entire Event-class and all its children now implement NSCoding, so to store this in the database, I simply set it to the transformable attribute. Here you can see how I create the WatchEvent-object and put it in the database, and a function to get all WatchEvents from the DB, and a function to print out the contents of each WatchEvent.
func storeWatchEvent(someEvent:Event, numberOfDays:Int){
let watchEvent = WatchEvent(entity: NSEntityDescription.entity(forEntityName: "WatchEvent", in: managedObjectContext)!, insertInto: managedObjectContext)
watchEvent.days = numberOfDays //e.g 3
watchEvent.event = someEvent
saveContext()
}
func saveContext(){
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func getWatchedEvents()->[WatchEvent]?{
return managedObjectContext.fetch(WatchEvent.fetchRequest())
}
func printOutAllWatchedEvents(){
if let watchedEvents = getWatchedEvents(){
watchedEvents.foreach{ (w) in
print("numberOfDays: ", w.days)
print("event: ", w.event)
}
}
}
func saveButtonClicked(){
storeWatchEvent(someEvent, numberOfDays:3)
// For testing, attempt to get all my events immediately, several times
printOutAllWatchedEvents() //Prints out `numberOfDays: 3` and a valid `event` correctly
printOutAllWatchedEvents() //Prints out `numberOfDays: 3` and a valid `event` correctly
printOutAllWatchedEvents() //Prints out `numberOfDays: 3` and a valid `event` correctly
}
func verifyButtonClicked(){
printOutAllWatchedEvents() //Prints out `numberOfDays: 3` and `nil` for event.
}
Let's say I have two buttons. One "Save" and another "Verify". If I click "Save", I save the valid objects, and as you can see in the code, I immediately query the database for all stored WatchEvents, and print them out. At that time, everything looks good. If I click the "verify"-button, it should've printed out the same thing, but it has deleted my event. It manages to keep the days-attribute stored, but the event is deleted. Why?:(
It doesn't matter how long I wait to click the verify-button. If I click them nearly at the same time, this still happens.
I call printOutAllWatchedEvents in saveButtonClick, three times, and since it manages to return a valid event-object every time, I assume that the storing/NSCoding-part of this was successful?
But if I click the verify-button, which I assume will happen at least a few "run loops" later, the transformable event-object has been deleted..
I have no idea what's happening.
Why does it manage to return my valid events if I request them immediately after inserting them, but not if I request them later? Why does this only affect the transformable object? The integer days is correctly retained for all WatchEvents. And since I have marked event as not optional, how can it return nil and never give me any errors? There are no errors when I call save on the context, I have checked.
I figured it out.. It had nothing to do with CoreData, really. It was a faulty implementation of NSCoding. I never got any errors of any kind, so it was hard to figure out, especially with so many variables, but the problem was essentially this:
class Event:NSObject,NSCoding{
let hasBeer:Bool
func encode(with aCoder: NSCoder) {
aCoder.encode(hasBeer, forKey: "hasBeer")
}
required init?(coder aDecoder: NSCoder) {
//This line:
self.hasBeer = aDecoder.decodeObject(forKey: "hasBeer") as? Bool ?? false
//Should be like this:
self.hasBeer = aDecoder.decodeBool(forKey: "hasBeer")
//Because it is a primitive type Bool, and not an Object of any kind.
super.init()
}
}
I'm still not entirely sure why I experienced it like this though..
The encoding always succeeded, so the Event was stored in the database (even though it looked empty when inspecting the blob-field in the sqlite-file using Liya). It succeeded encoding because the functions are named the same for all types (Bool, Int, Double, and NSObject). They all work with aCoder.encode(whatever, forKey: "whatever"). However, when DEcoding them, you have to pick the right decoding-function, e.g decodeBool, decodeInt32, decodeObject etc.
As I stated, I have done this in the past, in Objective-C, where I had no trouble at all, because both the encoding and decoding-functions are named for the type ([aDecoder decodeBoolForKey:#"key"]; and [aCoder encodeBool:hasBeer, forKey:#"hasBeer"];), and threw compile-time-errors when using the wrong one.
I guess I would've picked up on this if I had some fatalError("something") in my required init? instead of just using return nil when something didn't go as planned.

Does managedObjectContext.object(with:) always refetch data if another (private) managedObjectContext changed and saved it?

(I'm sorry if this question is kind of confusing/imprecise. I'm just learning advanced CoreData usage and I don't know the terminology and stuff very well).
I have a singleton Game that holds certain data you need during the game. For example, you can access currentSite (Site is a CoreData Entity) from there to get the Site the user is currently at:
// I created the Site in a background queue (when the game started), then saved the objectID and here I load the objectID
public var currentSiteObjectID: NSManagedObjectID {
let objectIDuri = UserDefaults.standard.url(forKey: Keys.forGameObject.currentSiteObjectIDURI)!
return appDelegate.persistentContainer.persistentStoreCoordinator.managedObjectID(forURIRepresentation: objectIDuri)!
}
// managedObjectContext is the one running on the main queue
public var currentSite: Site! {
return managedObjectContext.object(with: currentSiteObjectID) as! Site
}
You see, I retrieve the currentSite by using the managedObjectContext.object(with:) method.
The documentation of this method says:
Returns the object for a specified ID.
If the object is not registered
in the context, it may be fetched or returned as a fault. (...)
I'm not quite sure about the following:
// Each Site has resources that you can access like this
print(Game.shared.currentSite!.resourceSet!.iron)
appDelegate.persistentContainer.performBackgroundTask { (context) in
let currentSite = context.object(with: Game.shared.currentSiteObjectID) as! Site
// Here I increase the iron resource
currentSite.resourceSet!.iron += 42
do {
try context.save()
} catch let error as NSError {
fatalError("\(error.debugDescription)")
}
DispatchQueue.main.async {
print(Game.shared.currentSite!.resourceSet!.iron)
}
}
The second print function is using the managedObjectContext of the main queue (which is different to the private one used inside performBackgroundTask {...}).
It actually does print:
50 // the start value
92
My question: Is it guaranteed that managedObjectContext.object(with:) returns the current object (that is up-to-date), even if it has been changed in another context? The documentation says that it will be fetched if it's a new object that's not known to the context.
But what if an object changes?
I'm not sure if it's just a coincidence that the example code from above is working like expected.
Thanks for any help/explanation! I'm eager to learn about this kind of stuff.
No it is not guaranteed. If managed object is already registered in context then it will return this object. What's more, if object with given id (NSManagedObjectId) doesn't exist in persistent store then your app will crash as soon as you try to use any of its properties.

Error creating a separate NSManagedObjectContext

Before getting into my issue, please have a look at this image.
Here is the actual data model:
I retrieve a set of Records from a web API, create objects out of them, save them in core data and display them in the Today view. By default these records are returned for the current date.
The user can tap on Past button to go to a separate view where he can choose a past or future date from a date picker view and view Records for that selected date. This means I have to call the API again passing the selected date, retrieve the data and save that data in core data and display them. When the user leaves this view, this data should be discarded.
This is the important part. Even though I get a new set of data, the old original data for the current date in the Today view must not go away. So if/when the user returns to the Today view, that data should be readily available as he left it without the app having to call the API and get the data for the current date again.
I thought of creating a separate NSManagedObjectContext to hold these temporary data.
I have a separate class called DatabaseManager to handle core data related tasks. This class initializes with an instance of `NSManagedObjectContext. It creates the managed object classes in the given context.
import CoreData
import Foundation
import MagicalRecord
import SwiftyJSON
public class DatabaseManager {
private let context: NSManagedObjectContext!
init(context: NSManagedObjectContext) {
self.context = context
}
public func insertRecords(data: AnyObject, success: () -> Void, failure: (error: NSError?) -> Void) {
let json = JSON(data)
if let records = json.array {
for recordObj in records {
let record = Record.MR_createInContext(context) as Record
record.id = recordObj["Id"].int
record.name = recordObj["Name"].string!
record.date = NSDate(string: recordObj["Date"].string!)
}
context.MR_saveToPersistentStoreAndWait()
success()
}
}
}
So in the Today view I pass NSManagedObjectContext.MR_defaultContext() to insertRecords() method. I also have a method to fetch Records from the given context.
func fetchRecords(context: NSManagedObjectContext) -> [Record]? {
return Record.MR_findAllSortedBy("name", ascending: true, inContext: context) as? [Record]
}
The data is retrieved from the API, saved in core data and gets displayed successfully. All good so far.
In the Past View, I have to do basically the same thing. But since I don't want the original data to change. I tried to do this a few ways which MagicalRecord provides.
Attempt #1 - NSManagedObjectContext.MR_context()
I create a new context with NSManagedObjectContext.MR_context(). I change the date in Past view, the data for that selected date gets retrieved and saved in the database successfully. But here's the issue. When I fetch the objects from core data, I get that old data as well. For example, each day has only 10 records. In Today view I display 10 records. When the fetch objects in the Past view, I get 20 objects! I assume it's the old 10 objects plus the new ones. Also when I try to display them in the tableview, it crashes with a EXC_BAD_ACCESS error in the cellForRowAtIndexPath method.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let record = records[indexPath.row]
cell.textLabel?.text = record.name // EXC_BAD_ACCESS
cell.detailTextLabel?.text = record.date.toString()
return cell
}
Attempt #2 - NSManagedObjectContext.MR_newMainQueueContext()
The app crashes when I change the date with the following error.
'+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Record''
Attempt #3 - NSManagedObjectContext.MR_contextWithParent(NSManagedObjectContext.MR_defaultContext())
Same result as Attempt #1.
Attempt #4 - From Hal's Answer I learned that even though I create two MOCs, they both refer to the same NSPersistentStore. So I created another new store to hold the temporary data in my AppDelegate.
MagicalRecord.setupCoreDataStackWithStoreNamed("Records")
MagicalRecord.setupCoreDataStackWithStoreNamed("Records-Temp")
Then when I change the date to get the new data, I set that temporary store as the default store like this.
func getDate(date: NSDate) {
let url = NSPersistentStore.MR_urlForStoreName("Records-Temp")
let store = NSPersistentStore(persistentStoreCoordinator: NSPersistentStoreCoordinator.MR_defaultStoreCoordinator(), configurationName: nil, URL: url, options: nil)
NSPersistentStore.MR_setDefaultPersistentStore(store)
let context = NSManagedObjectContext.MR_defaultContext()
viewModel.populateDatabase(date, context: context)
}
Note that I'm using the default context. I get the data but it's the same result as Attempt 1 and 3. I get 20 records. They include data from both the old date and the new date. If I use NSManagedObjectContext.MR_context(), it would simply crash like in Attempt 1.
I also discovered something else. After creating the stores in App Delegate, I printed out the default store name println(MagicalRecord.defaultStoreName()) in the Today's view. Strangely it didn't print the name I gave the store which is Records. Instead it showed Reports.sqlite. Reports being the project's name. Weird.
Why do I get the old data as well? Am I doing something with when initializing a new context?
Sorry if my question is a little confusing so I uploaded a demo project to my Dropbox. Hopefully that will help.
Any help is appreciated.
Thank you.
Thread Safety
First of all I want to mention the Golden Rule of Core Data. NSManagedObject's are not thread safe, hence, "Thou shalt not cross the streams" (WWDC). What this means is that you should always access a Managed Object in its context and never pass it outside of its context. This is why your importer class worries me, you are inserting a bunch of objects into a context without guaranteeing that you are running the insert inside the Context.
One simple code change would fix this:
public func insertRecords(data: AnyObject, success: () -> Void, failure: (error: NSError?) -> Void) {
let json = JSON(data)
context.performBlock { () -> Void in
//now we are thread safe :)
if let records = json.array {
for recordObj in records {
let record = Record.MR_createInContext(context) as Record
record.id = recordObj["Id"].int
record.name = recordObj["Name"].string!
record.date = NSDate(string: recordObj["Date"].string!)
}
context.MR_saveToPersistentStoreAndWait()
success()
}
}
}
The only time you don't need to worry about this is when you are using the Main Queue Context and accessing objects on the main thread, like in tableview's etc.
Don't forget that MagicalRecord also has convenient save utilities that create context's ripe for saving :
MagicalRecord.saveWithBlock { (context) -> Void in
//save me baby
}
Displaying Old Records
Now to your problem, the following paragraph in your post concerns me:
The user can tap on Past button to go to a separate view where he can
choose a past or future date from a date picker view and view Records
for that selected date. This means I have to call the API again
passing the selected date, retrieve the data and save that data in
core data and display them. When the user leaves this view, this data
should be discarded.
I don't like the idea that you are discarding the information the user has requested once they leave that view. As a user I would expect to be able to navigate back to the old list and see the results I just queried without another unecessary network request. It might make more sense to maybe have a deletion utility that prunes your old objects on startup rather than while the user is accessing them.
Anyways, I cannot illustrate how important it is that you familiarize yourself with NSFetchedResultsController
This class is intended to efficiently manage the results returned from
a Core Data fetch request.
You configure an instance of this class using a fetch request that
specifies the entity, optionally a filter predicate, and an array
containing at least one sort ordering. When you execute the fetch, the
instance efficiently collects information about the results without
the need to bring all the result objects into memory at the same time.
As you access the results, objects are automatically faulted into
memory in batches to match likely access patterns, and objects from
previous accessed disposed of. This behavior further serves to keep
memory requirements low, so even if you traverse a collection
containing tens of thousands of objects, you should never have more
than tens of them in memory at the same time.
Taken from Apple
It literally does everything for you and should be your go-to for any list that shows objects from Core Data.
When I fetch the objects from core data, I get that old data as well
Thats to be expected, you haven't specified anywhere that your fetch should include the reports in a certain date range. Here's a sample fetch:
let fetch = Record.MR_createFetchRequest()
let maxDateForThisController = NSDate()//get your date
fetch.predicate = NSPredicate(format: "date < %#", argumentArray: [maxDateForThisController])
fetch.fetchBatchSize = 10// or an arbitrary number
let dateSortDescriptor = NSSortDescriptor(key: "date", ascending: false)
let nameSortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetch.sortDescriptors = [dateSortDescriptor,nameSortDescriptor]//the order in which they are placed in the array matters
let controller = NSFetchedResultsController(fetchRequest: fetch,
managedObjectContext: NSManagedObjectContext.MR_defaultContext(),
sectionNameKeyPath: nil, cacheName: nil)
Importing Discardable Records
Finally, you say that you want to see old reports and use a separate context that won't save to the persistent store. Thats also simple, your importer takes a context so all you would need to do is make sure that your importer can support imports without saving to the persistent store. That way you can discard the context and the objects will go with it. So your method signature could look like this:
public func insertRecords(data: AnyObject, canSaveToPersistentStore: Bool = true,success: () -> Void, failure: (error: NSError?) -> Void) {
/**
Import some stuff
*/
if canSaveToPersistentStore {
context.MR_saveToPersistentStoreWithCompletion({ (complete, error) -> Void in
if complete {
success()
} else {
error
}
})
} else {
success()
}
}
The old data that was in your persistent store, and addressed with the original MOC, is still there, and will be retrieved when the second MOC does a fetch. They're both looking at the same persistent store. It's just that the second MOC also has new data fetched from your API.
A synchronous network operation saving to Core Data will hang your app, and (for a large enough set of records) cause the system to kill your app, appearing to the user as a crash. Your client is wrong on that point, and needs to be educated.
Break apart your logic for fetching, saving, and viewing. Your view that shows a particular date's records should just do that--which it can do, if it accepts a date and uses a predicate.
Your 'cellForRowAtIndexPath' crash smells like a problem with a missing or misspelled identifier. What happens if you hard code a string instead of using 'record.name'?

Resources