Passing around Core Data Stack - ios

I am still struggling a lot with the best way to pass around objects within the core data stack. How I started for reference:
App was super simple and single threaded core data access was perfect, only a few views and each needed a MOC. Started with example code that created the Core Data Stack and one MOC. MOC was stored in App Delegate.
App started to get more complex and finally realized why storing the MOC in the App Delegate was a bad idea. Refactored code to create the MOC inside the App Delegate and inject the MOC into the root view controller. From there App Delegate did not hold onto the MOC and view controllers injected it into other controllers that might need it.
Started to refactor app views in storyboard. New tab bar, some nav controllers, split view controller, you know just some different ideas. Step #2 turned into a nightmare. Every time I made a change to the app view hierarchy in the storyboard I had to revamp each and every view controller to pass the MOC through the new hierarchy. Sure apple says this is the right way, but I am not sure I am buying it, very tough to make simple view hierarchy changes without killing code. Also now I have 4 views at the start of my app that don't even need the MOC. However those views are the only link from the app delegate to the view controllers that do need the MOC. So I am stuck injecting the MOC into all those view controllers just so they can pass it along to another view controller without every using the MOC.
App is even more complex and now I want a threaded Core Data Stack. This means passing around the persistent store such that some 'processing' objects can create their own MOC on a background thread. Should I create some sort of CoreDataStack object that can help manager this? I.E. object that I can ask for the main thread MOC, or ask for a new 'worker' background MOC. Seems like now step #3 was even more pointless, every and I mean every view in my app will need access to the main thread MOC and nothing else. I guess I don't see this one changing for a while, but who knows I have changed a lot since I started ;)
I think the idea of a 'CoreDataStack' object that can manage the distribution of MOCs might be a good idea. That way at least that object can abstract away the implementation details of the way I choose to implement a threaded stack. I.E. provide methods to shell out main MOC and background MOC.
That seems great until I start to think about how to pass this stack object around. I can do exactly what apple recommends and pass this around from controller to controller injecting the main thread MOC into each view controller. But as I said above about 5 minutes of re-work in a storyboard makes that fall apart extremely quickly. Not to mention pass MOC to views that don't really need it just so they can pass to the next view in the hierarchy that may need it.
Finally my question. Is there a better solution for my use case rather than passing around / injecting the MOC into each view controller???

You stated
App started to get more complex and finally realized why storing the MOC in the App Delegate was a bad idea.
What exactly did you not like about this? For the problems you mention the app delegate or a singleton are quite appropriate solutions. I would just remove the app delegate code from all controllers that
don't need core data
have an object as an ivar (you can get the managed object context from the managed object subclass)
Unless you have lots of records and need a fetched results controller you can just use the object graph without fetch requests, so the Core Data layer is nicely "abstracted away".

A new technique I use, is proxy objects. Basically what I do is make a struct that mimics the Managed Object. For all of the read operations I use this mimic object. If the user is editing the data but hasn't "Saved" it yet, I also use the mimic object. Only when the user commits to saving the object do I use Core Data. At that point I call upon my MOC singleton which is created here,
// My `AppDelegate` conforms to `UIApplicationDelegateWithPSC`
protocol UIApplicationDelegateWithPSC {
func providePersistentStoreCoordinator() -> NSPersistentStoreCoordinator
}
class ContextManager {
static let sharedInstance = ContextManager(concurrencyType: .mainQueueConcurrencyType)
private init(concurrencyType: NSManagedObjectContextConcurrencyType ) { }
lazy var context: NSManagedObjectContext = {
return {
let modelURL = Bundle.main.url(forResource: "Foo", withExtension: "momd")
let mom = NSManagedObjectModel(contentsOf: modelURL!)
let appDelegateWithPSC = UIApplication.shared.delegate as! UIApplicationDelegateWithPSC
let psc = appDelegateWithPSC.providePersistentStoreCoordinator()
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let storeURL = (urls[urls.endIndex-1]).appendingPathComponent("Bar")
var error: NSError? = nil
var store: NSPersistentStore?
do {
store = try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil)
} catch let error1 as NSError {
error = error1
store = nil
} catch {
fatalError()
}
let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = psc
return managedObjectContext
}()
}()
}
And I call the context as follows:
ContextManager.sharedInstance.context.performAndWait({
do {
let foo = NSEntityDescription.insertNewObject(forEntityName: "Foo", into: ContextManager.sharedInstance.context) as? Foo
// `mimicStruct` is the struct i've been using to avoid the headaches of complying with rules for manipulating ManagedObjects.
foo?.name = mimicStruct.name
try ContextManager.sharedInstance.context.save()
} catch {
}
})
By using the singleton object for my context, i avoid the complexity of multi-threaded code. Note that I also make this a performAndWait which means I am blocking, but since I only do this when I read/write to the database, the user is usually expecting a little pause while a spinner indicates Loading... or Saving.... I end up make fewer trips to the MOC and avoiding a lot of complexity.

Related

CoreStore create object in context without saving to database

I want to solve next problem:
I would like to work with some NSManagedObject in context and change some properties in runtime, but without telling SQLite about any changes in it.
I just want to save NSManagedObject to database when I hit save button or similar.
As I found out from source code demo we need to use beginUnsafe for this purposes (maybe I am wrong)
func unstoredWorkout() -> WorkoutEntity {
let transaction = CoreStore.beginUnsafe()
let workout = transaction.create(Into<WorkoutEntity>())
return workout
}
let workout = unstoredWorkout()
workout.muscles = []
Now when I try to update workout.muscles = [] app crashes with error:
error: Mutating a managed object 0x600003f68b60 <x-coredata://C00A3E74-AC3F-47FD-B656-CA0ECA02832F/WorkoutEntity/tC3921DAE-BA43-45CB-8271-079CC0E4821D82> (0x600001c2da90) after it has been removed from its context.
My question how we can create object without saving it and how we can save it then when we modify some properties and avoid this crash as well.
The reason for the crash is that your transaction only lives in your unstoredWorkout() method, so it calles deinit, which resets the context (and deletes all unsaved objects).
You have to retain that unsafe transaction somewhere to keep your object alive - such as in the viewcontroller that will eventually save the changes.
But I would rather encourage you to think about that if you really want to do that. You might run into other synchronization issues with various context or other async transactions alive, like when API calls are involved.

What happens to local variables storing references to deleted NSManagedObjects

When I delete an NSMangedObject from the database, what happens to local variables who were assigned to it?
For example, I have a simple NSManagedObject:
class MyManagedObject: NSManagedObject {
#NSManaged var name: String
}
And then in my ViewController, I pull it out of the database, and assign it locally:
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
}
Then I delete it from the database.
If print the object name I get the following in the console
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
As if the object isn't there? But if I turn the variable into an optional and check it for nil, I am told it's not nil.
I'm not quite sure how to reconcile this in my mind. There seems to be something pointing to the local variable, but its properties are gone.
If I have many disparate UI objects that rely on that object for its properties, I can't assume that there is some local deep copy of it in memory?
Here is more complete code:
In viewDidLoad I create the new object, save the context, fetch the object, then assign it locally.
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
//1 Create the new object
let newObject = NSEntityDescription.insertNewObject(forEntityName: "MyManagedObject", into: coreDataManager.mainContext) as! MyManagedObject
newObject.name = "My First Managed Object"
//2 Save it into the context
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
//3 Fetch it from the database
let request = NSFetchRequest<MyManagedObject>(entityName: "MyManagedObject")
do {
let saved = try coreDataManager.mainContext.fetch(request)
//4 Store it in a local variable
self.myManagedObject = saved.first
} catch {
//handle errors
}
}
}
At this point if I print the local variable's name property, I get the correct response:
print("The object's name is: \(myManagedObject.name)")
//prints: The object's name is: My First Managed Object
So, now I delete it from the database:
if let storedObject = myManagedObject {
coreDataManager.mainContext.delete(storedObject)
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
}
But now, when I print I get the strangest output:
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
This is totally not the way I'm expecting memory to work. If I create a instance of a class Foo, and then pass that instance around to different objects, it's the same instance. It only goes away once no one is pointing to it.
In this case--- what is the variable, myManagedObject? It's not nil. And what is the string, name? Is it an empty string? Or is it some other weird meta-type?
The main thing what you are probably looking here for is the core data context. The context is a connection between your memory and the actual database.
Whenever you fetch the data you fetch it through context. These are managed objects which can be modified or even deleted. Still none of these really happen on the database until you save the context.
When you delete an object it is marked for deletion but it is not deleted from the memory, it must not be since if nothing else it will still be used by the context to actually delete the object from the database itself.
What happens to the managed object once you call to delete it is pretty much irrelevant, even if documented it may change as it is a part of the framework. So it is your responsibility to check these cases and to refetch the objects once needed. So you must ensure your application has a proper architecture and uses core data responsibly.
There are very many ways on how you use your database and more or less any of them has a unique way of using it optimally. You need to be more specific on what you are doing and where do you see potential issues so we can get you on the right track.
To give you an example consider data synchronization from remote server. Here you expect that data can be synchronized at any time no matter what user is doing or what part of the application he is.
For this I suggest you have a single context which operates on a separate thread. All the managed objects should be wrapped and its properties copied once retrieved from database. On most entities you would have something like:
MyEntity.findAll { items in
...the fetch happens on context thread and returns to main, items are wrappers
}
MyEntity.find(id: idString, { item in
...the fetch happens on context thread and returns to main, items are wrappers
})()
Then since you do not have any access to the managed object directly you need some kind of method to copy the data to the managed object like:
myEntityInstance.commit() // Copies all the data to core data object. The operation is done on a context thread. A callback is usually not needed
And then to save the database
MyEntity.saveDatabse {
... save happens on the context thread and callback is called on main thread
}
Now the smart part is that saveDatabse method will report to a delegate that changes have been made. A delegate is usually the current view controller so it makes sense to have a superclass like DataBaseViewController which on view did appear assigns itself as a delegate MyEntity.delegate = self, on view did load calls some method reloadData and in the databaseDidChange delegate method calls reloadData and same in viewWillAppear.
Now each of your view controllers that are subclass of DataBaseViewController will override the reloadData and in that method you will fetch the data from the database again. Either you are fetching all items or a single one. So for those singe ones you need to save the id of the object and fetch it again by that id. If the returned object is nil then item was deleted so you catch the issue you seem to be mentioning.
All of these things are oversimplified but I hope you get a basic idea about core data and how to use it. It is not easy, it never was and it most likely never will be. It is designed for speed to be able to access the data even from a very large database in shortest time possible. The result is that it might not be very safe.

Core Data Best Practices - Am I utilizing C.D. Correctly and Efficiently? - iOS Swift

How I am Currently Using Core Data:
My app loads: On this page i create a Managed Object Context, fetch the managed objects, and load / display them. This is a tableview so I allow deleting. In order to delete i create a managed object context and delete the managed object and reload the tableview. This same method is used throughout my app, there are other operations such as updating ect ect. The main point is for each action i create a new managed object context.
My current understanding of Core Data is that a Managed Object Context is sort of like a queue, that fills up with actions. Where as the Managed Object is the item being modified and placed into the queue for an action to occur. Using this logic, shouldn't there only be one queue throughout the entire app?
Main Question:
Do I need to create a managed object context before every action? or could i create one managed object context say in app delegate did finish launching? and utilize this throughout the entire app?
Edit:
For future viewers(and if i understood the answers provided) my code now generally looks like this:
I have a class for Core Data, in this one function creates a ManagedObjectContext.
This function to create a MOC is called in app delegate, which on success returns the MOC, that I then store in a singleton.
Throughout my app as I need to make changed to my core data objects, I do the following:
if let managedContext = ShareData.sharedInstance.managedObjectContext {
// DO STUFF, update, delete, create ect ect ect
}
Edit 2: I may have misinterpreted the "Do not create MOC in App delegate", Currently looking into this and attempting to learn best placement for the creation of MOC. Seems tedious to create one elsewhere if EVERY view you can possibly start the app on requires the MOC.
I feel compelled to add a minority report to Fogmeister's answer:
In the latest SDK, Core Data has provided us with NSPersistentContainer which is definitely worth looking into. You don't always need to add a third party dependency to manage Core Data. I usually create set this up in the first visible View Controller and then inject that instance to other view controllers as needed. No need for a singleton; it's much easier to test if you can configure your VC with a persistentContainer.
Use the viewContext of this persistent store for fetch requests. This runs on the main queue and is suitable for driving the UI.
The persistentContainter can be asked for a background context by calling newBackgroundContext. You can perform any importing on this queue. When you save this, changes are automatically propagated to the viewContext. Alternatively, use the container's performBackgroundTask() method which takes a closure, which will be run on a background queue that is created for you.
Edited to add
Here is a very basic example of saving data in a background queue via the NSPersistentContainer's performBackgroundTask method. It can be downloaded from Github: https://github.com/Abizern/so-41984004
You should really only need to create one core data stack throughout the lifetime of the app. (There are exceptions to this but for most apps).
I am currently using JSQCoreDataKit to manage the creation of the stack and saving contexts. It would definitely be worth taking a look at this.
The normal approach to core data is something like...
Create the core data stack on launch of the app. Normally accessed through a singleton (NOT IN THE APP DELEGATE).
For reading data from core data get the mainContext from the core data stack and perform fetches on this mainContext.
For writing (adding, updating, deleting) data back you can use the mainContext but can also get a backgroundContext or childContext from the core data stack. The perform the updates and saveContext inside a perform block on the context. (This will merge changes to the main context for you to read).
This should cover most of what you want to do.
Have a look at JSQCoreDataKit. It makes the creation of the managed object context much much simpler.
Edit to clarify point 1
Putting things into the AppDelegate is a very clunky and lazy way to get at data across the board. The AppDelegate is a singleton so it seems like the perfect place to put it. But then you add more and more and suddenly you have a huge app delegate that drives your entire app.
Using the Single responsibility principle your app delegate should do one thing... be a delegate for you app. It should respond to app state changes etc...
I forgot to add... If you create a second target (say a TVOS target) for your app. It will not use the same AppDelegate. If all your CoreData code (and other code) is inside the AppDelegate then the TVOS app will not be able to access it. Putting it in another class that is accessible to both apps means that both apps can share the code you use for CoreData (et al).
It is very easy to create another file that holds your core data stack and initiate it whenever you first need access to core data. (Not necessarily from the AppDelegate but from the first place you need to make a read/write).
RE placing the core data stack setup in the initial view controller. You could do that. You then have the issue of how do you get to that core data stack from every other view controller in the app. You could either make the initial view controller a singleton (don't do this) or you could pass the stack around.
Both approaches can be done but CoreData is inherently a singleton. There is only one set of data on your phone's disc. So creating a singleton here is not a bad thing.
If you do make a singleton then make it purely a core data stack singleton. I have one called something like CoreDataStackManager. All it does is hold the coreDataStack property.
There is no need to create a manger object context each time you want do some operation on database. The best practice is to create a class containing the MOC and you can centralise in it all possible database operation. this class will be you data access manager
Here an example of what you could look like :
import Foundation
import CoreData
enum CommitError: Error {
case failureToSave(error:Error?)
}
class CoreDataManager: NSObject {
var managedObjectContext: NSManagedObjectContext
override init() {
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = Bundle.main.url(forResource: "YOUR_DATABASE_NAME", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = psc
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.appendingPathComponent("YOUR_DATABASE_NAME.sqlite")
do {
let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true, NSSQLitePragmasOption: ["journal_mode": "DELETE"]] as [String : Any]
try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
} catch {
fatalError("Error migrating store: \(error)")
}
}
func commitChanges() throws{
if self.managedObjectContext.hasChanges{
do {
try self.managedObjectContext.save()
} catch {
throw CommitError.failureToSave(error: error)
}
}
}
func createObject(_ entityName:String) -> NSManagedObject? {
let result:NSManagedObject? = NSEntityDescription.insertNewObject(forEntityName: entityName, into: self.managedObjectContext)
return result
}
func deleteEntity(_ entity:NSManagedObject){
self.managedObjectContext.delete(entity)
}
}
You can add other function to search or create objects.
Please let me know if this resolve your problem ;)

DATAStack library in existing app with working core-data

I have an application in which I already have working core-data and persistent storage functionality. I'd like use DATAStack in this app, but I can't get it to work.
Without DATAStack I had al these standard core-data lazy vars in my AppDelegate. In my ViewController I retrieved the managedObjectContext like this: let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext. Passed the moc variable to the function that would take care of the storing and called moc.save() somewhere down the line. I know that this works, because I also retrieve and show the stored entries on the screen, which also works after completely closing the app. And I inspect the sqlite database with a sqlite database viewer (SQLPro for SQLite Read-Only).
Now with DATAStack I added a new line in my AppDelegate: lazy var dataStack: DATAStack = DATAStack(modelName: "Database"). The name of my database model is indeed Database.xcdatamodeld so the initialisation should be right. In my ViewController I replaced the var moc = ... described above with let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).dataStack.mainContext. Again this moc variable gets passed around and I call moc.save() somewhere down the line, but now it doesn't store anything.. As I described, the only thing that has changed is where the managed object context comes from.
I must be missing something with this library, but I have no clue what I'm missing.
I've also looked at a Sync example (Sync uses DATAStack), but the dataStack object is retrieved in a whole different way there.
Apparently you shouldn't use the mainContext to save, but always a new background context. So the only way to get the right moc is like this:
dataStack.performInNewBackgroundContext() { moc in
//Do things with moc and call moc.save() somewhere
}

Core Data and a content manager

First time working with Core Data en its .xcdatamodeld
I am using a contentManager for a notes app.
But I am difficulties creating standerd notes.
Is a contentManager normally used for core data? My Problem is where the ?? is.
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let entity = NSEntityDescription.insertNewObjectForEntityForName("Note", inManagedObjectContext: ?? ) as! Note
}
It isn't exactly clear what the issue is, but it looks like you simply need to replace ?? with managedObjectContext. But, this code should be in a function, not just as variable definitions in the class itself. If you did want to make them variables then you can make them lazy so they're populated on demand the first time they're used.
As for your content manager, I'm guessing you mean some kind of data controller. That's generally a matter of personal preference and how long you've been developing. Generally you want to move the context ownership and management out of the app delegate as that's an inappropriate place to have it and using a data controller for that is clearly better.

Resources