Subclass an NSManagedObject subclass - ios

Let's say I have an NSManagedObject subclass Instrument and I want to subclass that subclass to create something like Guitar. Is there a common practice for this? It doesn't seem to be as straightforward as subclassing NSObject.

For managed object subclasses, the subclass/parent class relationship
corresponds to the subentity/parent entity relationship of the
Core Data entities.
If you set the "Parent Entity" of "Guitar" to "Instrument" in the
Core Data model inspector and then create the managed object subclasses
in Xcode, you'll get
// Instrument.swift:
class Instrument: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
// Guitar.swift:
class Guitar: Instrument {
// Insert code here to add functionality to your managed object subclass
}
For more information, see the section "Entity Inheritance"
in the Core Data Programming Guide:
Entity inheritance works in a similar way to class inheritance, and is useful for the same reasons. If you have a number of entities that are similar, you can factor the common properties into a superentity, also known as a parent entity.
Also pay attention to the
NOTE
Be careful with entity inheritance when working with SQLite persistent stores. All entities that inherit from another entity will exist within the same table in SQLite. This factor in the design of the SQLite persistent store can create a performance issue.

There is nothing wrong in doing this:
class Instrument: NSManagedObject {
#NSManaged var name: String
}
class Guitar: Instrument {
#NSManaged var numberOfString: NSNumber
}

Related

swift / CoreData - Create "dummy" NSManagedObjecIDs for data models (to test things without the need of managed objects)

Say I have 2 NSManagedObjects in CoreData.
class House: NSManagedObject {}
class Location: NSManagedObject {}
I also have data model structs like this:
struct HouseModel {
var objectID: NSManagedObjectID
...
}
sruct LocationModel {
var objectID: NSManagedObjectID
...
}
For each loaded managedObject I basically use its attributes to initialize a new model struct to use for the UI and stuff (mainly collection views)
I have to have the NSManagedObjectID attribute in the structs in order to be able to make changes to the managedObject that struct belongs to. (I learned that I should use the mainViewContext only for reading while using something like persistentContainer.performBackgroundTask for writing. Thus, I need the NSManagedObjectID to load the objects on a background queue)
That's working but there is a problem with this approach:
I can't initialize one of these data models without a managed object. That's annoying when I want to create dummy data for UI testing or unit testing.
I know one solution: Create a Dummy managedObject with exactly one instance and use its objectID for stuff like that. But I don't really like this. Is there a better / more convenient way?
I mean, I would love to entirely remove the objectID attribute to keep CoreData separate from these model structs. But I don't see a way to do this. I need the connection.
For passing NSManagedObjects to a detail view for editing, it is often useful to do that on a new main queue managed object context, which simplifies your UI access and allows you to throw away the context if the user cancels changes.
But that's not what you asked.
Your problem is that you want to identify a managed object, but not use NSManagedObjectID. For this, you can use a URL property instead. NSManagedObjectID has a uriRepresentation() that returns a URL, and NSPersistentStoreCoordinator can convert a URL back into a managed object ID using managedObjectID(forURIRepresentation:). So you can store any old URL in the struct for testing purposes, and still be securely referring to managed objects in your app logic.

Will CoreData duplicate objects?

I have an NSManagedObject called "Routine," that has already been saved to my data model. It has a to-many relationship to another NSManagedObject called "Workout". I want to edit the Routine in order to add more workout relationships to it to it.
let routine = fetchedResultsController.objectAtIndexPath(indexPath) as! Routine
The ViewController where I edit the Routine in my data model contains an array of Workout objects:
var myWorkouts = [Workout]()
Some of the workouts in the "myWorkouts" array may have been already associated with Routine, whereas others may not have (a new workout). I create a relationship between each workout and the routine like this:
for workout in myWorkouts {
routine!.setValue(NSSet(objects: workout), forKey: "workout")
}
My Question: If a relationship between a Routine and a Workout has already been created, will the above for-loop create a duplicate of that workout to associate with my routine, or will it only create new relationships for previously unassociated workouts?
I hope my questions makes sense. Thanks in advance for the help!
Routine CoreDataProperties File
import Foundation
import CoreData
extension Routine {
#NSManaged var name: String?
#NSManaged var workout: Set<Workout>?
}
So, you're working with Sets, which means that they only always contain each value once. Therefore, regardless of the enclosing objects (in this case NSManagedObjects), there will only be one in there. You're good - re-stating the relationship won't change anything.
I might suggest, however, that you can do a couple of things to make your life easier:
If you haven't already, create the concrete subclasses using Xcode's built in tools, so you can directly access relationships and properties.
In the concrete subclasses +NSManagedObjectProperties file, redefine those to-many relationships from NSSets? to Set<MyClass>?. This allows you to call Swift-native functions, and works correctly, as Set is bridged from NSSet.
In the future, just call routine.workout = workout, which is much clearer than the way your code defines setting the relationship.

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)

How do I indicate to CoreData that an object has changed?

In my CoreData model, I have an entity called Contact. It has an attribute called profileImage, with the type set to Transformable.
In the Contact class (a subclass of NSManagedObject), I've changed profileImage from being a generic id to being an UploadedImage:
#property (nonatomic, retain) UploadedImage * profileImage;
The UploadedImage class has a few properties of its own.
The problem is that CoreData doesn't know when the properties on the UploadedImage object have changed. If only those properties are changed, the willSave method is never called on the Contact object when the managed object is saved. It works as expected if I change any other property on the Contact object: it just doesn't "see" changes to anything on the UploadedImage object within the Contact object.
What's the correct way to indicate that the managed object needs to be saved? Should I manually call willChangeValueForKey: and didChangeValueForKey: for the key 'profileImage' on my Contact object?
Edit to clarify a couple things
Contact is a CoreData entity, but the ImageUpload class is NOT.
CoreData will save the data in the ImageUpload object (because I implemented the NSCoding protocol methods, I think). If I change a property on the ImageUpload object, AND change a property on the Contact object, the Contact is saved, and that changed value persists on the ImageUpload object when the Contact is loaded from CoreData again.
Unless there's another way I'm not aware of, it sounds like marking the NSManagedObject as dirty is the way to go. You can do that using a separate dirtyFlag attribute, or by simply using the profileImage attribute itself.
One way to automate this would be KVO. You observe all properties of UploadedImage in the Contact class, and then just call self.profileImage = self.profileImage. I'm not sure if this is optimized by the compiler. If it is, you can also call willChangeValueForKey: and didChangeValueForKey:, which should trigger it. If Core Data noticed that the object didn't actually change, you can try to implement NSCopying and assign a copy of the original UploadedImage.
I don't believe Core Data will persist changes made to the subclass. The contract in Core data is between the parent entity and Core Data, so Core Data only responds to changes made to the properties in the entity class.
You can get around this by making that property a NSDictionary in the Entity and in the subclass add the image object to the dictionary.
Expanding upon Scott's solution (no copy necessary!), here is a method for Swift that works with any field:
static func markDirty<C, T>(_ obj: C, _ keyPath: ReferenceWritableKeyPath<C, T>) {
obj[keyPath: keyPath] = obj[keyPath: keyPath]
}
Used like so:
NSManagedObject.markDirty(playlist, \.rules)
Unfortunately it's not possible right now to define it as an instance method, due to limitations with generics.

Working with several NSManagedObject

My question is very simple. I have something like 10 different entities in CoreData, all with the same attributs (name, description ...). To access these attributes i doing in this way:
MyEntity *entity=...;
MyEntity2 *entity2=...;
...
MyEntity10 *entity10=...;
[self myfunction:AnEntity];
After I send a random object to a function
-(void)myfunction:(id)myentity
And here i would like to use a variable which can access the entity attributes whether it's a king of class MyEntity or MyEntity2... The problem is that i can't do:
id myobject=myentity;
NSLog(#"%#", myobject.name);
If someone have a solution to avoid testing the kind of class of the object :)
Thanks !
If you have 10 different entities, I think it's time to move to NSManagedObject subclasses. Then you can define a protocol that encompasses all of the shared attributes, and declare that your NSManagedObject subclasses comply with that protocol. Then your call becomes
-(void)myfunction:(id<SharedAttributesProtocol>)myObject
{
NSLog(#"%#", myObject.name);
}
You mentioned "description" as one of your shared attributes. The -description method is already defined, so you probably want to choose another name for that attribute.
This disadvantage of using a parent NSEntity for the common attributes is that you end up with one very wide table. This table has all of the common attributes, but also has all of the distinct attributes for each of the subentities. Depending on the size of your objects, this will be a performance hit under iOS, although it's not so awful on OS X.
In fact you could call
[myobject valueForKey:#"name"]
or even
[myobject name]
in your function, because the methods are resolved at runtime. If myobject has a "name" attribute, this will work, otherwise it will crash at runtime.
A cleaner solution would be to define one "parent entity" MyEntity with the common attributes name, description etc. Then you can define subentities MyEntity1, MyEntity2, ... which have MyEntity as "Parent Entity". These subentities inherit all attributes/relationships of the parent entity, and can have additional attributes and relationships.
The corresponding managed object subclasses are then subclasses of the MyEntity class. Your function could look like this:
- (void)myfunction:(MyEntity *)myentity
{
NSLog(#"%#", myentity.name);
}
and you can call it with instances of any of the subclasses:
MyEntity1 *myentity1 = ...;
[self myfunction:myentity1];
MyEntity2 *myentity2 = ...;
[self myfunction:myentity2];

Resources