Seemingly inconsistent behavior among NSManagedObject subclasses - core-data

I have several subclasses of NSManagedObject. They are all instantiated with code something like this:
MeasurementDescriptor *descriptor = (MeasurementDescriptor *)[NSEntityDescription
insertNewObjectForEntityForName:#"MeasurementDescriptor"
inManagedObjectContext:context];
or like this:
Experiment *experiment = (Experiment *)[NSEntityDescription
insertNewObjectForEntityForName:#"Experiment"
inManagedObjectContext:context];
What is odd, though, is that (from code above)
NSLog(#" descriptor's class = %#", NSStringFromClass([descriptor class]));
prints out 'NSManagedObject', while
NSLog(#" experiment's class = %#", NSStringFromClass([experiment class]));
prints out 'Experiment'.
Does anyone know why this would be? MeasurementDescriptor, unlike my other NSManagedObject subclasses, had no ivars (not including its Core Data properties). Adding an ivar did not change anything. Similarly, MeasurementDescriptor is the only NSManagedObject subclass without 'relationship' properties. Perhaps this accounts for this strangeness...???

The only explaination is that your MeasurementDescriptor subclass is not actually known to the code. The most common causes of this are:
In the data model editor not setting the Class attribute of the entity to the correct class.
Not adding the source file for the subclass to the target.
This is easy to do with Core Data because if it can't find a dedicated subclass it doesn't complain but just returns a generic NSManagedObject initialized with the entity's property key names.

Related

No Core Data entity subclasses during custom migrations

I have a core data database. I'm performing a custom migration. I have a subclass of NSEntityMigrationPolicy. My policy's migration method buildValueFromSectionFieldManagedObject: is being called by a mapping rule : FUNCTION($entityPolicy, "buildValueFromSectionFieldManagedObject:" , $source).
This part is actually working.
However, the implementation of buildValueFromSectionFieldManagedObject: uses methods in the custom entity NSManagedObject subclass of the $source, which is Choice.
The methods of Choice do not seem to be available to the migration function, and instead it gets just a vanilla NSManagedObject.
When I try to use Choice methods, I get an exception. If I po the choice in the debugger, I get something like this:
<NSManagedObject: 0x600000281860> (entity: Choice; id: 0xd00000000038001a ; data: )
Whereas, out of a migration I would usually see something like this:
<Choice: 0x60800028bdb0> (entity: Choice; id: 0x6080002225a0 ; data: {
Is this just how it is, or is there some way that I can use the entity objects during migration?
Possibly relevant – this particular entity, Choice, is removed during this migration. It does not exist in the target managed object model, but does exist in the source managed object model. However, I don't think this is the case as other entity classes that are in the target model are also unavailable as that class during migration – they have class NSManagedObject and their entity methods are not available.
That is correct, you only have access to basic NSManagedObjects during migration.
Three-Stage Migration
The migration process itself is in three stages. It uses a copy of the source and destination models in which the validation rules are disabled and the class of all entities is changed to NSManagedObject.
From: Core Data Model Versioning and Data Migration Guide
Dave's answer clarified that during a migration, core data object are only available as NSManagedObject instances. You don't get to use their Entity classes.
Worse, if you're using a tool like mogenerator, then any handy logic that you've extended the entity classes with is inaccessible.
Poor solutions
Working with an NSManagedObject directly feels dangerous to me. Using [managedObject valueForKey:#"someKey"] is verbose, but worse, there's no compiler checking that you've got your key name correct, so you might be asking for something managedObject doesn't have. There's also no compiler checking of the returned type either – it could be anything that you can put in to a managed object.
Slightly better is [managedObject valueForKey: NSStringFromSelector(#selector(someKey))] is safer, but horribly verbose and awkward, and still isn't that safe – lots of things might implement the method someKey.
You might also declare your keys as literals:
NSString *const someKey = #"someKey";
[managedObject valueForKey: someKey];
Again – this is slightly safer, less error prone and verbose, but you've got no guarantee still that this managed object has someKey.
None of these approaches will give us access to custom logic, either.
Better solution
What I did instead of this was to define a protocol for the properties that I know my managed object has. Here's are examples for entities Choice and ChoiceType.
#protocol ChoiceProtocol
- (NSSet<id <ChoiceTypeProtocol> > *)choiceTypes;
- (NSNumber *)selected;
- (NSNumber *)order;
#end
#protocol ChoiceTypeProtocol
- (NSNumber *)selected;
- (NSString *)name;
- (NSString *)textCustom;
- (NSNumber *)order;
#end
Now in my migration code, instead of having a custom migration function similar to:
- (NSString *)migratedRepresentationOfChoice:(NSManagedObject *)choice;
I have:
- (NSString *)migratedRepresentationOfChoice:(id <ChoiceProtocol>)choice;
In the body of this function I can use choice exactly as I would any regular object. I get code completion as I type, I get the right syntax highlighting, the compiler will complain if I call a non existent method.
I also get return type checking, so the compiler will complain if I use the NSNumber property selected as an NSString or NSSet. And it'll also be helpful and suggest NSNumber methods as completions while I type.
How about the logic?
This approach doesn't provide the logic you've added to entity classes.
In Swift you might be able to use a protocol extension to achieve that.
I was retiring these entities (hence the migration), so I moved the entity logic functions I needed in to a helper functions of my custom migration. As an example choice.orderedChoiceTypes becomes [self choiceOrderedChoiceTypes:choice].
In future I will probably avoid ever adding logic to NSManagedObject entities. I think it's probably a better plan to put any such logic in domain objects that you build from your managed objects. Further, I will probably avoid defining entity classes and instead only access NSManagedObject instances through a protocol as during the migration. It seems clean, simple, removes magic, and has benefits for testing – not just for migrations.

separating entity mapping codes from domain model in RestKit

I'm following this tutorial for organizing code in rest kit
http://restkit-tutorials.com/code-organization-in-restkit-based-app/
I want to separate my domain model from restKit mappings.
so I added all my mapping to a class named mappingProvider.
RKEntityMapping *tableMapping = [RKEntityMapping mappingForEntityForName:#"Table" inManagedObjectStore:[HAObjectManager sharedManager].managedObjectStore];
tableMapping.identificationAttributes = #[#"tableID"];
[tableMapping addAttributeMappingsFromDictionary:#{
#"ID":#"tableID",
#"TableNumber":#"tableNumber",
#"NumberOfChairs":#"numberOfChairs"}];
mapping provider needs a reference to ManagedObjectStore which I try to get from my custom subclass of RKObjectManager named HAObjectManager.
HAObjectManager calls [self setupResponseDescriptor] which this method calls
RKResponseDescriptor *tableResponseDescriptors = [RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider tableMapping] method:RKRequestMethodGET pathPattern:#"/api/table" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self addResponseDescriptor:tableResponseDescriptors];
again this method calls [MappingProvider tableMapping] which again this calls [HAObjectManager sharedManager] to get managedObjectStore . as you see this causes and infinite loop.
does anyone know how should I solve it?
for example a sharedInsatnce of coreData managedObjectStore.
You need to change your initialisation order, which isn't necessarily trivial given your described structure. Most options are unpleasnt without doing significant rework.
A simple option for breaking the recurs ion is to add a parameter to tableMapping such that you can pass the object store as a parameter (so you don't need to call back to the partially initialised singleton to get it).

Problems casting NSManagedObject to subclass type

I'm using custom classes for my CoreData stack. Class properties are set properly in the model. There's some points in the app that are abstracted to use setValue... on an NSManagedObject, but I have a few cases where that fails with an NSInvalidArgumentException, specifically when setting a related object; error indicates it wants the specific type, and it's' getting an NSManagedObject, hence the error. So, I thought I would just take the short route and cast my instance prior to the offending call, if it's of a certain entity; like this:
NSManagedObject *addressObject = [NSEntityDescription insertNewObjectForEntityForName:#"Address" inManagedObjectContext:[object managedObjectContext]];
if ([[[object entity]name] isEqualToString:#"Hospital"]) {
Contact *contact = (Contact*)object;
DLog(#"The class of contact is: %#", [contact class]);
contact.Address = addressObject;
}
else{
[object setValue:addressObject forKey:#"Address"];
}
I know, Address shouldn't be capitalized; I inherited this mess... anyway, I would totally expect that the contact object is a Contact, but it's not, it's an NSManagedObject! What am I doing wrong with the cast? Everything I've uncovered says this is the right way to cast, but for some reason, it's not working for me here. Of course, this wouldn't be necessary if the addressObject didn't complain about getting an NSManagedObject instead of a Contact (sorry, Hospital inherits from Contact here), and that's another baffling thing, but first things first. How can I coerce object to type Contact, which it really is?
Here's the relevant trace:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-one relationship: property = "Contact"; desired type = Contact; given type = NSManagedObject; value = ...
For completeness sake, the Address class has declaration for Contact as:
#property (nonatomic, retain) NSManagedObject * Contact;
with implementation for Contact as the normal dynamic, like:
#dynamic Contact;
Maybe I need some sleep? ;-) Thanks
The cast isn't your problem. Casting doesn't change what class an object is — just what the compiler thinks it is.
You object is an NSManagedObject but not a Contact instance. In you code the object you have is a Hospital entity. Double-check that the Hospital entity is set to use the Contact class (or a subclass)).
It is important to note that Entity inheritance and Objective-C class inheritance don't have to match. I.e. you can have Hospital be a subentry of Contact and still have the Contact entity map to a Contact class without Hospital map to a subclass of the Contact class. It is valid for the Hospital entity to map to the (same) Contact class or even to NSManagedObject (which is what I suspect you've done).
This may seem confusing but can be very powerful if used correctly.
So yeah, turns out I did need some sleep;-) The problem was the subclass not being included in the target. I know I've experienced similar issues when you have a subclass, and it's in the target, but you forget to define the custom subclass in the model, too.

"Core Data could not fulfill a fault.." error

I am developing an application in cocoa. I am facing a critical problem.
I am deleting entries of an object named "Directory" in Core Data using the following code:
NSEnumerator *tempDirectories = [[folderArrayController arrangedObjects] objectEnumerator];
id tempDirectory;
while (tempDirectory = [tempDirectories nextObject]){
[managedObjectContext deleteObject:tempDirectory];
}
But sometimes an exception like "Core Data could not fulfill a fault.." occurs while trying to save after deletion. I am using the code [managedObjectContext save];
I am new in Core Data... Looking forward to a solution.
This is an old question, I have struggled resolving this for some time now. So, thought it would be best to document it.
As Weichsel above mentioned, the Apple documentation rightly points out reason for this exception. But it is a hectic job to identify the module due to which the NSManagedObject subclass' object is being retained (if 1st cited reason in the documentation is the root cause of the problem).
So, I started out by identifying the parts of my code which was retaining the NSManagedObject, instead I retained the NSManagedObjectID and create the managed object out of it whenever needed. The discussion in similar lines can be found in Restkit documentation:
https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39
https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
Updated my setter and getter so that the interface with rest of the modules remain same while internally we now depend upon NSManagedObjectID and avoid retaining of NSManageObject:
-(CSTaskAbstract*)task
{
CSTaskAbstract *theTask = nil;
if (self.taskObjectID)
{
NSManagedObjectContext *moc = [(CSAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39 &
// https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
NSError *theError = nil;
NSManagedObject *theObject = [moc existingObjectWithID:self.taskObjectID
error:&theError];
if ([theObject isKindOfClass:[CSTaskAbstract class]])
{
theTask = (CSTaskAbstract*)theObject;
}
}
return theTask;
}
-(void)setTask:(CSTaskAbstract *)inTask
{
if (inTask!=self.task)
{
// Consequences of retaining a MO when it is detached from its MOC
[self setTaskObjectID:[inTask objectID]];
}
}
The above is the first half of the problem solved. We need to find out dependency in suspicious parts of our app and eliminate.
There was some other problem too, instruments -> allocations is a good source to find out which modules are actually retaining the managed objects, the exception object would have details about which managed object is creating the problem, filter results for that object as shown below:
We were performing KVO on a managed object. KVO retains the observed managed object and hence the exception is thrown and it's back trace would not be from within our project. These are very hard to debug but guess work and tracking the object's allocation and retain-release cycle will surely help. I removed the KVO observation part and it all started working.

KVC compliance for numbers in NSManagedObject subclass (CoreData)

I'm trying a basic test of sorting an NSManagedObject subclass. I set up a basic subclass "TestClass" with two attributes: stringField and numberField. They use the standard Obj-C 2.0 accessor protocol:
#interface TestClass : NSManagedObject
#property (retain) NSString *stringField;
#property (retain) NSNumber *numberField;
#end
#implementation TestClass
#dynamic stringField;
#dynamic numberField;
#end
When I try to fetch instances of this entity, I can fetch based on either attribute. However, if I use a sort descriptor, the numberField is said to not be KVC-compliant.
Within the model, I set the numberField to Int64, but I'm confused. I thought the wrapper (NSNumber) would handle the KVC problem. What do I need to do to make this work?
Some initial "Is the computer on?"-type questions:
Does your model specify that the managed object class for your entity is TestClass?
Are you sure you spelled numberField correctly when specifying the key in your sort descriptor?
Is numberField a transient attribute in your model?
These are the common issues that I can think of that might cause such an error when fetching with a sort descriptor, the first one especially.
Also, this won't affect KVC, but your attributes' property declarations should be (copy) rather than (retain) since they're "value" classes that conform to the NSCopying protocol and may have mutable subclasses. You don't want to pass a mutable string in and mutate it underneath Core Data. (Yeah, there's no NSMutableNumber or NSMutableDate in Cocoa, but that doesn't prevent creating MyMutableNumber or MyMutableDate subclasses...)

Resources