CoreData - Benefits of NSManagedObject Subclass - ios

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.

Related

Continue using NSManagedObject types but migrate off Core Data

I am working with a massive, existing code base that uses Core Data.
I have types that extend NSManagedObject and are persisted in Core Data.
I need to migrate off of Core Data entirely (the reasons for this don't matter, but I have good reasons). However, I have a fundamental constraint.
I cannot change these types to NSObject (some use cases must continue using Core Data).
These NSManagedObject types are heavily passed around my business logic. I don't want to refactor that business logic and introduced a new/"unmanaged" type.
Let's say I have some type Foo that's an NSManagedObject. I tried something like:
Foo *foo = [[Foo alloc] init];
foo.name = "beebunny";
The foo.name call causes a crash.
name is #dynamic and has a custom set method, something like:
- (void)setFoo:(Foo *)fooIn
{
[self setFoo:fooIn];
}
The [self setFoo:fooIn]; call causes an exception (unknown selector).
It seems like I have to use Core Data if I'm working with any type that extends NSManagedObject.
Is there a proper/recommended pattern for the type of migration I'm wanting to perform off of Core Data?
Instances of NSManagedObject depend very heavily on the data model. You don't have to save the instances with Core Data, but they must have a data model backing them up or they won't work. Your [[Foo alloc] init] doesn't work because (a) it doesn't use a designated initializer, and (b) it doesn't have a data model supporting it.
You can create instances that you don't save. For example you can use -initWithEntity:insertIntoManagedObjectContext: to create an instance, but have the context argument be NULL. It'll never be saved unless you insert it, but it sounds like you won't do that. But that initializer requires an NSEntityDescription, and you need to get that from a managed object model. (You can also create them in code, but that's not going to make it easier or remove the need to import Core Data into your code).
In short, you don't have to save these objects to Core Data, but you do need to have some Core Data support if you'll still be subclassing NSManagedObject. You can't use those classes independently of the data model.

Can I make realm.writes directly in the Object data model?

I'm writing an app using Realm data persistence of certain objects.
In an attempt to clean up/remodel my code (getting realm.writes out of the Views and Controllers), I tried to put them directly in the persisted object class.
The logic is basically this:
class PersistedObject: Object {
public var data: String {
get { _saved_data }
set {
do { try realm?.write { _saved_data = newValue }
} catch { print(error) }
}
}
#objc dynamic private var _saved_data = "hello there"
}
This way, I'd be able to access and rewrite realm object properties from view controllers, without needing realm.writes directly in there. That's the idea, anyway.
This works sometimes. Other times, the app crashes with the error...
"Realm accessed from incorrect thread"
...which is what I'm currently trying to solve.
This is my first iOS app and my first time using Realm.
Does it make sense to organize the code like this (I've found little in terms of support in this approach, but also generally little at all, in terms of MVC best-practices when working with Realm)
If it does make sense, how can I solve the problem with accessing Realm from the incorrect thread, while still doing the realm.writes directly in the object class?
Thanks in advance! :)
Simon
There is no sense to organize code like this. You will be able to write only from same thread it was created
to modify objects from different thread you can use ThreadSafeReference for example
You're not going to want to do that.
There's no reason not to realm.write whenever you want to write to realm - that's what it's there for. This pattern works:
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
// Get the default Realm
let realm = try! Realm()
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Obviously there should be better error catching in the above code.
Another downside is if you want to write 10 objects, they are written as soon as the data property is set - what if there are three vars you want to set and heep in memory before writing it? e.g. your user is creating a list of items in your app - if the user decides not to do that and hit's Cancel, you would then have to hit the database again to delete the object(s).
Consider a case where you want to write 10 objects 'at the same time'?
realm.add([obj0, obj1, obj2...])
is a lot cleaner.
Another issue comes up if you want to guarantee objects are written within a transaction - either it all succeeds or all fails. That can't be done with your current object.
The last issue is that often you'll want to instantiate an object and add some data to it, populating the object before writing to realm. With the code in the question, you're writing it as soon as data is populated. You would have to add that same code to every property.

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.

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.

How to use Core Data model subclasses outside a Core Data context?

I'm trying to make a weather app in Swift that will save the cities I add to Core Data, each city contain a weather object that is also saved to Core Data and other various variables.
But I soon figured out, using Core Data NSManagedObjects subclasses outside a Core Data context is close to impossible (dealing with NSNumber and similar, no custom init, forced to save them somewhere, what if I stop using Core Data tomorrow, ...).
So what's the best practice to keep using Core Data but also use models outside of its context?
My solution right now is to create a Class for each Model, so :
class City
{
var country: String?
var name: String?
// ...
}
Is the corresponding class of :
class CD_City
{
#NSManaged var country: String?
#NSManaged var name: String?
// ...
}
So I can use City anywhere and anyhow I want. But I need a func to turn a City into CD_City and opposite. So I'm really not sure I'm doing it the best way.
Also what would you recommend as a conversion method ?
(FYI I'm using MagicalRecord as a Core Data helper)
TL;DR - Don't do that or things will break.
There used to be various hacks for getting it to sort of work, but they all rely on undocumented behavior in CoreData. I would never use anything like that in code I wanted to show another human being, much less ship to customers. CoreData needs to insert proxy objects that hook into property change events on your model objects, and the only way it can reliably do that and track the original data values were is if it is responsible for creating those entities in the first place; That also makes the faulting & uniquing system work. Don't think of Core Data as an ORM, it really is an object graph management framework, and as such it is designed to be used a certain way with no easy solution to side step it safely.
If you don't want to save an NSManagedObject or a subclass of it, then you can create it with
init(entity entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?)
and pass nil for insertIntoManagedObjectContext this will create you an instance but it will be not be saved to the MOC.
In case you have to save it to the MOC later, you can use NSMangedObjectContext's
func insertObject(_ object: NSManagedObject)

Resources