iOS - Core Data Stack as singleton with main NSManagedObjectContext - ios

I've seen many tutorials and they really help me with understand parent-child managed object context and other things related to this. I am ready to start using it in my app but I have a question. Why nobody use singleton for keeping main managed object context. I guess it would be much better to extract Core Data related objects from AppDelegate and set it to own class right? Something like in this Tutorial at raywenderlich.com. But they still instantiate CoreDataStack class (no problem with this, singleton must be instantiate too) and when it's need they set managedObjectContext in prepareForSegue (and set it to first view controller from AppDelegate). Why not to remove this need and just use singleton CoreDataStack and have possible to use managedObjectContext in each controller if needed?
Second and bonus question: I think it's better to have less code in controller and more in other classes. I think it helps with readability. So what if I move this code from controller and set it for example to CoreDataStack class or some other class that helps with Core Data requests and responses:
func surfJournalFetchRequest() -> NSFetchRequest {
let fetchRequest =
NSFetchRequest(entityName: "JournalEntry")
fetchRequest.fetchBatchSize = 20
let sortDescriptor =
NSSortDescriptor(key: "date", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
I know it's possible but is it better? If you get app codes from me would it be better if in controller it would be one line CoreDataStack.fetchRequest("JournalEntry", sortedKey: "date")?
And what about if I take this code and insert it to singleton and created function with closure? I would created child managed context in singleton and do needed operations in there and in controller I would just changed UI:
func exportCSVFile() {
navigationItem.leftBarButtonItem = activityIndicatorBarButtonItem()
let privateContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = coreDataStack.context.persistentStoreCoordinator
privateContext.performBlock { () -> Void in
var fetchRequestError:NSError? = nil
let results = privateContext.executeFetchRequest(self.surfJournalFetchRequest(), error: &fetchRequestError)
if results == nil {
println("ERROR: \(fetchRequestError)")
}
let exportFilePath = NSTemporaryDirectory() + "export.csv"
let exportFileURL = NSURL(fileURLWithPath: exportFilePath)!
NSFileManager.defaultManager().createFileAtPath(exportFilePath, contents: NSData(), attributes: nil)
var fileHandleError: NSError? = nil
let fileHandle = NSFileHandle(forWritingToURL: exportFileURL, error: &fileHandleError)
if let fileHandle = fileHandle {
for object in results! {
let journalEntry = object as! JournalEntry
fileHandle.seekToEndOfFile()
let csvData = journalEntry.csv().dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
fileHandle.writeData(csvData!)
}
fileHandle.closeFile()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.navigationItem.leftBarButtonItem =
self.exportBarButtonItem()
println("Export Path: \(exportFilePath)")
self.showExportFinishedAlertView(exportFilePath)
})
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.navigationItem.leftBarButtonItem = self.exportBarButtonItem()
println("ERROR: \(fileHandleError)")
})
}
}
}
I just want to be sure that my aproach would be okay and would be better than original. Thanks

I built my first core data app with a singleton pattern. It seemed logical for me because there is only one core data stack anyway. I was very wrong, the singleton pattern turned into a big mess quickly. I added more and more code to bend the singleton stack to something that works. In the end I gave up and I invested the time to replace the singleton mess with dependency injection.
Here are some of the problems I encountered before I dumped the singleton:
Since the app kept important data, my users requested a backup functionality. To restore from a backup I switched the sqlite file and then I would just create a new Core Data stack. Doing this in a clean way is next to impossible if you use a pull-pattern to get the managedObjectContext from a singleton. So my way to switch the Core Data stack was to tell the user that they have to restart the app. Followed by an exit(). Not the most elegant way to handle this.
After Apple added childContexts I decided to get rid of undo managers and context rollbacks, because that never worked 100% for me. But changing my editing viewControllers so they use child contexts which are discarded when the user hits cancel, was an incredible painful act because I now had a mix of singleton contexts and viewController local contexts in one viewController.
For editing the targets of relationships I had editViewControllers inside editViewController. Because I created the edit context inside the edit viewControllers I ended up saving data to the main context that shouldn't have been saved. It's a bit complicated to explain, but the second viewController saved stuff like new objects to the main context even if the user in the outer edit viewController hit cancel. Which always lead to orphaned objects. So I added more code to bend the singleton in a way that would make it less of a singleton.
I also had a CSV import function and I wanted to preview the data to the user before they press "Import". I build a totally new infrastructure for that. First I parsed the CSV into a data structure that basically duplicated my core data classes. Then I build a viewController to display these non core data classes, with even more code duplication. I would only start to create core data objects when the user pressed import.
After I got rid of the singleton pattern I could reuse the existing data display viewController. I would just give it a different context, in this case an in-memory context that contained the data that will be imported. Much cleaner, less duplicated code.
I guess some of these problems were not really the singletons fault. I was just very inexperienced.
But I still would strongly recommend against singleton core data.
would be one line CoreDataStack.fetchRequest("JournalEntry", sortedKey: "date")?
You don't need a singleton for this. Stuff like this should be in the NSManagedObject subclass you create for JournalEntry.
And what about if I take this code and insert it to singleton and created function with closure? I would created child managed context in singleton and do needed operations in there and in controller I would just changed UI:
And why don't you create a method that doesn't require internal state at all?
class func export(#context: NSManagedObjectContext, toCSVAtPath path: String,
progress: ((current: Int, totalCount: Int) -> Void)?,
completion: ((success: Bool, error: NSError?) -> Void)?) {
Much more flexible.

Related

Swift Closures and order of execution

I'm struggling a little bit trying to create an application for my own education purposes using Swift.
Right now I have the following (desired) order of execution:
TabView
FirstViewController - TableView
Check into CoreData
If data exists update an array using a closure
If data doesn't exists then download it using Alamofire from API and store it into Core Data
SecondViewController - CollectionView
Checks if data of images exists in Core Data, if it does, loads it from there, otherwise download it.
The problem that I'm struggling the most is to know if the code after a closure is executed after (synchronously) the closure ends or it might be executed before or while the closure is executed.
For example:
FirstViewController
var response: [DDGCharacter]
//coreData is an instance of such class
coreData.load(onFinish: { response in //Custom method in another class
print("Finished loading")
self.response = response
})
print("Executed after loading data from Core Data")
//If no data is saved, download from API
if response.count == 0 {
//Download from API
}
I have done the above test with the same result in 10 runs getting:
Finished loading
Executed after loading data from Core Data
In all 10, but it might be because of load is not taking too much time to complete and thus, appear to be synchronous while it's not.
So my question is, is it going to be executed in that order always independent of amount of data? Or it might change? I've done some debugging as well and both of them are executed on the main thread as well. I just want to be sure that my suppositions are correct.
As requested in the comments, here's the implementation done in the load() method:
func load(onFinish: ([DDGCharacter]) -> ()) {
var characters: [DDGCharacter] = []
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject> (entityName: "DDGCharacter")
do {
characters = try managedContext.fetch(fetchRequest) as! [DDGCharacter]
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
onFinish(characters)
}
Your implementation of load(onFinish:) is very surprising and over-complicated. Luckily, though, that helps demonstrate the point you were asking about.
A closure is executed when something calls it. So in your case, onFinish is called at the end of the method, which makes it synchronous. Nothing about being "a closure" makes anything asynchronous. It's just the same as calling a function. It is completely normal to call a closure multiple times (map does this for instance). Or it might never be called. Or it might be called asynchronously. Fundamentally, it's just like passing a function.
When I say "it's slightly different than an anonymous function," I'm just referring to the "close" part of "closure." A closure "closes over" the current environment. That means it captures variables in the local scope that are referenced inside the closure. This is slightly different than a function (though it's more about syntax than anything really deep; functions can actually become closures in some cases).
The better implementation would just return the array in this case.

Working with Core Data (multiple ViewControllers)

There is 1 thing I don't completely understand about working with CoreData (and I can't find a good answer to my question): how do you use CoreData in an app with multiple UIViewcontrollers ?
At the moment I was playing around with core data in multiple controllers and I typed this in each VC:
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let entity = NSEntityDescription.entityForName("EntityName", inManagedObjectContext: context)!
let entityName = EntityName(entity: entity, insertIntoManagedObjectContext: context)
But I don't think this is the best practice for working with CoreData on multiple controllers (side question: does this have an impact on my app's preformance?). What is the most preferred way?
Do I create a managed object context in each viewcontroller?
(Lots of copy-paste, like I'm doing now)
Do I create a singleton for the creation of CoreData ? I've read somewhere that this was not the preferred way
Do I pass the managed object context to the next controller using
segues (or set a public context variable in the app delegate's function '*didFinishLaunchingWithOptions*')?
Something else?
Thanks in advance!
3-> Setting a public main queue MOC is a good practice. Because main queue is a serial queue.
1-> Using the main queue MOC only for fetching and creating child MOC for each editing is a good practice. Because editing MOCs that can be fetched may cause conflicts.
2-> You can try creating a singleton instance for getting the shared main queue MOC and define some functions that create child context for editing.
4-> But actually theres no best practice for abstract situations.
I think so.
What you're trying to do is to avoid code duplication. Therefore just bring the code into one single function and place it somewhere. It could be an extension on UIViewControler or AppDelegate or just a helper function
This is how an extension on UIViewController would look like:
extension UIViewController
{
func doCoreDataWork()
{
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let entity = NSEntityDescription.entityForName("EntityName", inManagedObjectContext: context)!
let entityName = EntityName(entity: entity, insertIntoManagedObjectContext: context)
}
}
then call doCoreDataWork() from any view controller.
And no, it has nothing to do with performance

Where should NSManagedObjectContext be created?

I've recently been learning about Core Data and specifically how to do inserts with a large number of objects. After learning how to do this and solving a memory leak problem that I met, I wrote the Q&A Memory leak with large Core Data batch insert in Swift.
After changing NSManagedObjectContext from a class property to a local variable and also saving inserts in batches rather than one at a time, it worked a lot better. The memory problem cleared up and the speed improved.
The code I posted in my answer was
let batchSize = 1000
// do some sort of loop for each batch of data to insert
while (thereAreStillMoreObjectsToAdd) {
// get the Managed Object Context
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil // if you don't need to undo anything
// get the next 1000 or so data items that you want to insert
let array = nextBatch(batchSize) // your own implementation
// insert everything in this batch
for item in array {
// parse the array item or do whatever you need to get the entity attributes for the new object you are going to insert
// ...
// insert the new object
let newObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
// save the context
do {
try managedObjectContext.save()
} catch {
print(error)
}
}
This method seems to be working well for me. The reason I am asking a question here, though, is two people (who know a lot more about iOS than I do) made comments that I don't understand.
#Mundi said:
It seems in your code you are using the same managed object context,
not a new one.
#MartinR also said:
... the "usual" implementation is a lazy property which creates the
context once for the lifetime of the app. In that case you are reusing
the same context as Mundi said.
Now I don't understand. Are they saying I am using the same managed object context or I should use the same managed object context? If I am using the same one, how is it that I create a new one on each while loop? Or if I should be using just one global context, how do I do it without causing memory leaks?
Previously, I had declared the context in my View Controller, initialized it in viewDidLoad, passed it as a parameter to my utility class doing the inserts, and just used it for everything. After discovering the big memory leak is when I started just creating the context locally.
One of the other reasons I started creating the contexts locally is because the documentation said:
First, you should typically create a separate managed object context
for the import, and set its undo manager to nil. (Contexts are not
particularly expensive to create, so if you cache your persistent
store coordinator you can use different contexts for different working
sets or distinct operations.)
What is the standard way to use NSManagedObjectContext?
Now I don't understand. Are they saying I am using the same managed
object context or I should use the same managed object context? If I
am using the same one, how is it that I create a new one on each while
loop? Or if I should be using just one global context, how do I do it
without causing memory leaks?
Let's look at the first part of your code...
while (thereAreStillMoreObjectsToAdd) {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil
Now, since it appears you are keeping your MOC in the App Delegate, it's likely that you are using the template-generated Core Data access code. Even if you are not, it is highly unlikely that your managedObjectContext access method is returning a new MOC each time it is called.
Your managedObjectContext variable is merely a reference to the MOC that is living in the App Delegate. Thus, each time through the loop, you are merely making a copy of the reference. The object being referenced is the exact same object each time through the loop.
Thus, I think they are saying that you are not using separate contexts, and I think they are right. Instead, you are using a new reference to the same context each time through the loop.
Now, your next set of questions have to do with performance. Your other post references some good content. Go back and look at it again.
What they are saying is that if you want to do a big import, you should create a separate context, specifically for the import (Objective C since I have not yet made time to learn Swift).
NSManagedObjectContext moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
You would then attach that MOC to the Persistent Store Coordinator. Using performBlock you would then, in a separate thread, import your objects.
The batching concept is correct. You should keep that. However, you should wrap each batch in an auto release pool. I know you can do it in swift... I'm just not sure if this is the exact syntax, but I think it's close...
autoreleasepool {
for item in array {
let newObject = NSEntityDescription.insertNewObjectForEntityForName ...
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
}
In pseudo-code, it would all look something like this...
moc = createNewMOCWithPrivateQueueConcurrencyAndAttachDirectlyToPSC()
moc.performBlock {
while(true) {
autoreleasepool {
objects = getNextBatchOfObjects()
if (!objects) { break }
foreach (obj : objects) {
insertObjectIntoMoc(obj, moc)
}
}
moc.save()
moc.reset()
}
}
If someone wants to turn that pseudo-code into swift, it's fine by me.
The autorelease pool ensures that any objects autoreleased as a result of creating your new objects are released at the end of each batch. Once the objects are released, the MOC should have the only reference to objects in the MOC, and once the save happens, the MOC should be empty.
The trick is to make sure that all object created as part of the batch (including those representing the imported data and the managed objects themselves) are all created inside the autorelease pool.
If you do other stuff, like fetching to check for duplicates, or have complex relationships, then it is possible that the MOC may not be entirely empty.
Thus, you may want to add the swift equivalent of [moc reset] after the save to ensure that the MOC is indeed empty.
This is a supplemental answer to #JodyHagins' answer. I am providing a Swift implementation of the pseudocode that was provided there.
let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator // or wherever your coordinator is
managedObjectContext.performBlock { // runs asynchronously
while(true) { // loop through each batch of inserts
autoreleasepool {
let array: Array<MyManagedObject>? = getNextBatchOfObjects()
if array == nil { break }
for item in array! {
let newEntityObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
}
// only save once per batch insert
do {
try managedObjectContext.save()
} catch {
print(error)
}
managedObjectContext.reset()
}
}
These are some more resources that helped me to further understand how the Core Data stack works:
Core Data Stack in Swift – Demystified
My Core Data Stack

Core Data passing objectID to another thread

I've tried Googling and searching SO, thinking it would be quite easy to find but I'm surprised there were little to no examples of my problem in Swift.
I have several threads and from what I've read, it's best to have a separate managed object context for each thread. If I want to access an object from another context, I should pass around the objectID.
My question is, how should I pass the objectID to the context in a new thread?
Below is how the construction of my situation is:
func doSomething(context: NSManagedObjectContext) {
let person = Persons(context: context) // NSManagedObject
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
let background = (UIApplication.sharedApplication().delegate as! AppDelegate).cdh.backgroundContext!
Birthdays(context: background, person) // NSManagedObject
saveContext(background)
}
}
Birthdays has a one to one relationship with Persons. If I execute this code it will give me an error saying:
Illegal attempt to establish a relationship 'person1' between objects in different contexts
Obviously because they are in separated contexts. So I tried to get the objectID of person1 by let personsObjectID = person1.objectID, but I don't know how I should be using it to pass it to the other thread. Any help is appreciated.
Seems like the only thing I needed to do was to add:
let person = background.objectWithID(orders.objectID as! Persons
If I've understood it correctly, objectWithID fetches the object from the store and puts it in the context, returning the managed object. It would be nice if someone could verify this if it's true.
func doSomething(context: NSManagedObjectContext) {
let person = Persons(context: context) // NSManagedObject
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
let background = (UIApplication.sharedApplication().delegate as! AppDelegate).cdh.backgroundContext!
let person = background.objectWithID(orders.objectID as! Persons
Birthdays(context: background, person) // NSManagedObject
saveContext(background)
}
}
Hendrik, you are correct, you should use objectWithID to instantiate the NSManagedObject on the required context.

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