Core Data and a content manager - ios

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.

Related

Is there a way to access properties of an x-coredata:// object returned from an NSFetchRequest?

TL;DR: Is there a way to programmatically read/recall (NOT write!) an instance of a Core Data entity using the p-numbered "serial number" that's tacked on to the instance's x-coredata:// identifier? Is this a good/bad idea?
I'm using a method similar to the following to retrieve the instances of an Entity called from a Core Data data store:
var managedContext: NSManagedObjectContext!
let fetchRequest : NSFetchRequest<TrackInfo> = TrackInfo.fetchRequest()
fetchResults = try! managedContext.fetch(fetchRequest)
for (i, _) in Global.Vars.numberOfTrackButtons! {
let workingTrackInfo = fetchResults.randomElement()!
print("current track is: \(workingTrackInfo)")
The list of tracks comes back in fetchResults as an array, and I can select one of them at random (fetchResults.randomElement()). From there, I can examine the details of that one item by coercing it to a string and displaying it in the console (the print statement). I don't list the code below, but using workingTrackInfo I am able to see that instance, read its properties into other variables, etc.
In the console, iOS/Xcode lists the selected item as follows:
current track is: <MyProjectName.TrackInfo: 0x60000374c2d0> (entity:
TrackInfo; id: 0xa7dc809ab862d89d
<x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22>;
data: <fault>)
The line beginning with x-coredata: got my attention. It's formatted like a URL, consisting of what I assume is a UUID for the specific Core Data store associated with the current build of the app (i.e. not a stable address that you could hardcode; you'd need to programmatically look up the Core Data store, similar to the functions we use for programmatically locating the Documents Folder, App Bundle, etc.) The third item is the name of the Entity in my Core Data model -- easy enough.
But that last number is what I'm curious about. From examining the SQLite database associated with this data store, it appears to be a sort of "instance serial number" associated with the Z_PK field in the data model.
I AM NOT interested in trying to circumvent Core Data's normal mechanisms to modify the contents of a managed object. Apple is very clear about that being a bad idea.
What I AM interested in is whether it's possible to address a particular Core Data instance using this "serial number".**
In my application, where I'm randomly selecting one track out of what might be hundreds or even thousands of tracks, I'd be interested in, among other things, the ability to select a single track on the basis of that p-number serial, where I simply ask for an individual instance by generating a random p-number, tack it on to a x-coredata:// statement formatted like the one listed above, and loading the result (on a read-only basis!) into a variable for further use elsewhere in the app.
For testing purposes, I've tried simply hardcoding x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22 as a URL, but XCode doesn't seem to like it. Is there some other data Type (e.g. an NSManagedObject?) that allows you to set an x-coredata:// "URL" as its contents?
QUESTIONS: Has anyone done anything like this; are there any memory/threading considerations why grabbing instance names in this manner is a bad idea (I'm an iOS/Core Data noob, so I don't know what I don't know; please humor me!); what would the syntax/method for these types of statements be?
Thanks!
You are quite close.
x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22
is the uriRepresentation() of the NSManagedObjectID of the record.
You get this URL from an NSManagedObject with
let workingTrackInfo = fetchResults.randomElement()!
let objectIDURL = workingTrackInfo.objectID.uriRepresentation()
With this URL you can get the managed Object ID from the NSPersistentStoreCoordinator and the coordinator from the managed object context.
Then call object(with: on the context to get the object.
let persistentStoreCoordinator = managedContext.persistentStoreCoordinator!
if let objectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: objectIDURL) {
let object = managedContext.object(with: objectID) as! TrackInfo
print(object)
}

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 ;)

CoreData - Benefits of NSManagedObject Subclass

I am trying to insert into CoreData without creating a subclass of NSManagedObject. but my app crashes with NSManagedObject setValue:forUndefinedKey "name" in Category.
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let entitiyDesc = NSEntityDescription()
entitiyDesc.name = "Category"
guard let data = model.data else { return }
for d in data {
let managedObject = NSManagedObject.init(entity: entitiyDesc, insertIntoManagedObjectContext: managedObjectContext)
managedObject.setValue(d.name, forKey: "name")
}
try! managedObjectContext.save()
What are the benefits of subclassing NSManagedObjects
There is nothing wrong with the way you are using Core Data. Generally you start wanting to subclass when:
You want convenience methods on your NSManagedObject
You are using transient values that are not in the model at all
Want to react to the creation or insertion of an NSManagedObject
Want to present an easier to use object to your UI developers
Want to avoid using "magic strings" when accessing properties
and I am sure there are more in that list.
You never need to subclass an NSManagedObject but in many situations it does make your code cleaner and easier to maintain.
A couple of comments on your code:
NSEntityDescription and NSManagedObject should not be created that way. You should be using the convenience method NSEntityDescription.insertNewObjectForEntityForName(_:) instead
Using a forced try like that is very bad for error handling. You are FAR better off using a do/catch so that you can interrogate the error completely. This is especially important with Core Data that will send you sub errors.
Accessing the NSManagedObjectContext through the AppDelegate like that is a poor design (yes I know its in the Apple template). It is far better to use dependency injection as discussed in the Core Data Programming Guide and avoid the tight coupling that is inherent with accessing the AppDelegate
Subclassing NSManagedObject honestly makes your life easier as a developer.
No need to hassle with setValue:forUndefinedKey.
Easy initializer for every entity
Easier to manage your entities with auto generated models by Xcode
You should check out the official documentation or this tutorial to get started.
Marcus Zarra's answer is 100% correct, but I would just like to stress that considering the risks of "undefined key" and type-casting errors, and the debugging cost while mentally remembering which entities have which attribute keys, to subclass or not should not even be a choice.
Strongly-typing your NSManagedObjects (i.e. by subclassing) also opens up a whole lot of compile-time checks and utility, especially in Swift.
If you are new to Core Data let me introduce you to the library I wrote, CoreStore, which was heavily designed around Swift's type-safety:
Generics allow for zero type-casting
Entity-aware NSFetchedResultsController-like observers
Protocol-based data importing
You also get powerful features such as transactions, progressive migrations, and more. It's like Core Data on training wheels; if there's a bad practice in Core Data, CoreStore will most likely not let you write that code in the first place.

Why I don't get the same objects when using objectWithID with CoreData?

I have a NSManagedObjectContext where two NSManagedObject are saved.
I'm calling a method in another thread and I need to access those two NSManagedObject so I created a child context like the following:
let childManagedContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
childManagedContext.parentContext = self.managedContext
When I do:
let myNSManagedObject1 = childManagedContext.objectWithID(self.myNSManagedObject1.objectID) as! MyNSManagedObject
let myNSManagedObject2 = childManagedContext.objectWithID(self.myNSManagedObject2.objectID) as! MyNSManagedObject
myNSManagedObject1 and myNSManagedObject2 are not the same objects as self.myNSManagedObject1 and self.myNSManagedObject2. Can someone explain me why?
Plus if I use existingObjectWithID instead of objectWithID, it seems I still have a fault object for my relationship in myNSManagedObject1 and myNSManagedObject2:
relationShipObject = "<relationship fault: 0x170468a40 'relationShipObject'>"
Understand that they are the "same" in the sense that they refer to the same object in your object graph. If you compare all attributes, you will find that they are equal.
However, because they are in different contexts, they will be two separate instances of this object. So the machine address you see will be different. I hope that clears up the confusion.
As for the "fault", that only means that the underlying object (or attribute) has not yet been fetched into memory. This is simply an optimization mechanism to minimize memory footprint. If you were to log the object or attribute explicitly, it would be fetched from the store and displayed as expected. See "Faulting and Uniquing" in the Core Data Programming Guide.
You have one object, that is the version that's in Core Data. When you use objectWithID: you create an instance of that object. So, if you do it twice you get two instances of the same object. (Much in the same way that you can create two objects of the same class.)
Of course, if you try to save your context, having changed one but not the other, weird things might happen.
A common pattern is where you create a new "editing" managed object context and create a new instance there. Then if the user pressed Cancel, you can just delete the context and not have to worry about rolling back any changes. I can't think where having two instances on the same context would be useful.

Passing around Core Data Stack

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.

Resources