Saving one-to-many relationship CoreData - ios

I am having troubles with the relationship I have setup in CoreData. Its one to many, a Customer can have many Contact, these contacts are from address book.
My model it looks like this:
Customer <---->> Contact
Contact <-----> Customer
Contact.h
#class Customer;
#interface Contact : NSManagedObject
#property (nonatomic, retain) id addressBookId;
#property (nonatomic, retain) Customer *customer;
#end
Customer.h
#class Contact;
#interface Customer : NSManagedObject
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSSet *contact;
#end
#interface Customer (CoreDataGeneratedAccessors)
- (void)addContactObject:(Contact *)value;
- (void)removeContactObject:(Contact *)value;
- (void)addContact:(NSSet *)values;
- (void)removeContact:(NSSet *)values;
#end
And trying save with:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
Customer *customer = (Customer *)[NSEntityDescription insertNewObjectForEntityForName:#"Customer" inManagedObjectContext:context];
[customer setValue:name forKey:#"name"];
for (id contact in contacts) {
ABRecordRef ref = (__bridge ABRecordRef)(contact);
Contact *contact = [NSEntityDescription insertNewObjectForEntityForName:#"Contact" inManagedObjectContext:context];
[contact setValue:(__bridge id)(ref) forKey:#"addressBookId"];
[customer addContactObject:contact];
}
NSError *error;
if ([context save:&error]) { // <----------- ERROR
// ...
}
With my code, I have this error:
-[__NSCFType encodeWithCoder:]: unrecognized selector sent to instance 0x9c840c0
*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFType encodeWithCoder:]: unrecognized selector sent to instance 0x9c840c0'
Any suggestions would be appreciated.

The problem is that addressBookId is (as you mentioned in a comment) defined as a transformable attribute on the Contact entity. However (as you also mentioned in a comment) you don't have any custom code to actually transform an ABRecordRef into something that Core Data knows how to store. With no custom transformer, Core Data is going to try and transform the value by calling encodeWithCoder: on the value. But ABRecordRef doesn't conform to NSCoding, so this fails and your app crashes.
If you want to store the ABRecordRef in Core Data, you'll need to create an NSValueTransformer subclass and configure that in your data model. Your transformer would need to convert ABRecordRef into one of the types Core Data knows. I haven't worked with the address book API enough to advise on the details of this, but Apple documents NSValueTransformer pretty well.
The fact that it's a one-to-many relationship is irrelevant; the problem is that ABRecordRef can't go into your data store without some transformation.

Related

[NSManagedObject setSwitchState:]: unrecognized selector sent in Objective-C

after checking all the answers of "Unrecognised selector sent" questions such as unrecognized selector sent to instance, and Unrecognized selector sent to instance? did not satisfy my situation so for that here is my full scenario:
Description:
in my app there exist a settings View which contain a UISwitch either to add all his reservation to the phone calendar or not.
so I need to save the choice of the user inside CoreData to add reservations to calendar or not i.e the state of the UISwitch.
initially when the app start for the first time the UISwitch will be ON and his state will be saved inside the CoreData with fixed ID 1 because I don`t want to add many Objects I need to keep only one object and when the user change the value of the UISwitch the app should update the object with new state I try this solution How to update existing object in Core Data? also I got an error
Full Code:
SwitchStateEntity.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface switchState : NSManagedObject
#property(strong,nonatomic) NSNumber *switchId;
#property (nonatomic,strong) NSNumber *switchState;
#property (nonatomic,strong) NSNumber *showReminderAlert;
#end
SwitchStateEntity.m
#import "switchState.h"
#implementation switchState
#dynamic switchId;
#dynamic switchState;
#dynamic showReminderAlert;
#end
SettingsViewController.h
#import <UIKit/UIKit.h>
#import "MakeReservationViewController.h"
#interface SettingsViewController : UIViewController
#property (weak, nonatomic) IBOutlet UISwitch *isAdedToCalendarOrNot;
#property (nonatomic) Boolean isSwitchOnOrOf;
#property (nonatomic,strong) NSString *savedEventId;
#end
SettingsViewController.m
- (IBAction)DoneButtonPressed:(id)sender {
// check the state of the switch //
// save this state //
NSError *error;
CoreData *coreDataStack = [CoreData defaultStack];
NSArray *fetchedObjects;
NSFetchRequest *fetchRequest;
NSEntityDescription *entity;
// setting up the variable needed //
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription entityForName:#"SwitchState" inManagedObjectContext:coreDataStack.managedObjectContext];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"remindMeSwitchId== %d", 1]];
[fetchRequest setEntity:entity];
fetchedObjects = [coreDataStack.managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"%#",fetchedObjects);
for( NSEntityDescription *name in fetchedObjects)
{
NSLog(#"Switch Id is: %#",[name valueForKey:#"remindMeSwitchId"]);
NSLog(#"Switch State is: %#",[name valueForKey:#"remindMeSwitchState"]);
SwitchStateEntity *h = [ fetchedObjects firstObject];
[h setSwitchState:[NSNumber numberWithBool:self.isSwitchOnOrOf]];
// after this statement i go into Unrecognised selector had been sent//
[coreDataStack saveContext];
NSLog(#"Switch Id is: %#",[name valueForKey:#"remindMeSwitchId"]);
NSLog(#"Switch State is: %#",[name valueForKey:#"remindMeSwitchState"]);
}
[self dismissSelf];
}
- (IBAction)isAdedToCalendarValueChanged:(id)sender {
if ([self.isAdedToCalendarOrNot isOn]) {
self.isSwitchOnOrOf = YES;
}
else
{
self.isSwitchOnOrOf = NO;
}
}
and for the exception that the app goes through:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSManagedObject setSwitchState:]: unrecognised selector sent to instance 0x7fb6f3411d20'
and for my xcdatamodel:
SwitchStateEntity *h = [ fetchedObjects firstObject];
//try this code
NSLog(#"it should not be nil = %#",self.isSwitchOnOrOf);
[h setSwitchState:[NSNumber numberWithBool:self.isSwitchOnOrOf]];
Update Code in Such File
SwitchStateEntity.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface SwitchStateEntity : NSManagedObject
#property(strong,nonatomic) NSNumber *switchId;
#property (nonatomic,strong) NSNumber *switchState;
#property (nonatomic,strong) NSNumber *showReminderAlert;
#end
SwitchStateEntity.m
#import "SwitchStateEntity.h"
#implementation SwitchStateEntity
#dynamic switchId;
#dynamic switchState;
#dynamic showReminderAlert;
#end
Also Update Entity name and class name in .xcdatamodeld file
Do a NSLog("%#", NSStringFromClass(h.class)) to see what kind of class the SwitchState object really is. Chances are you've incorrectly configured something in your Core Data Model file.
Also, seriously, all your class names should be UpperCaseWords...

Fetch objects in Core Data through one to many relationship

I have this One-to-Many relationship in Core Data:
Each SBQChrono can have many SBQLaps.
In my model I have the class SBQLap:
#interface CILap : NSObject
#property (strong, nonatomic) NSDate * lapDate;
#end
And the class SBQChrono:
#interface CIChrono : NSObject
#property (strong, nonatomic) NSDate * startDate;
#property (strong, nonatomic) NSDate * stopDate;
#property (strong, nonatomic) NSOrderedSet *laps;
#end
I get all the SBQChrono entities doing:
NSFetchRequest *request=[[NSFetchRequest alloc] initWithEntityName:kChronoEntityName];
NSError *error;
NSArray *objects = [appContext.managedObjectContext executeFetchRequest:request error:&error];
How can I get the specified SBQLap entitys per each SBQChrono I find? Ive read a lot of posts and cant get the solution.
UPDATE:
After trying to cast the NSManagedObject, I realized that the laps is returned as nil value.
Doing:
for (NSManagedObject *oneObject in objects) {
CIChrono *chrono=(CIChrono *)oneObject;
NSLog(#"startDate %#", chrono.startDate);
NSLog(#"stopDate %#", chrono.stopDate);
NSLog(#"laps %#",chrono.laps);
}
I get the message:
2014-01-28 14:39:48.379 Chrono[2341:70b] startDate 2014-01-28 12:27:53 +0000
2014-01-28 14:39:48.380 Chrono[2341:70b] stopDate 2014-01-28 12:27:54 +0000
2014-01-28 14:39:48.380 Chrono[2341:70b] -[NSManagedObject laps]: unrecognized selector sent to instance 0x8b959b0
2014-01-28 14:39:48.383 Chrono[2341:70b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSManagedObject laps]: unrecognized selector sent to instance 0x8b959b0'
Thanks
Lots of interesting answers on this one.
The answer is very simple and does not require subclassing NSManagedObject to access the objects in a relationship. You just need to use KVC (Key Value Coding) to access those objects. This is true of relationships and attributes.
If you want to use synthesized methods you can by subclassing as some have hinted at but that is not necessary.
Here is an extension to your example that you posted in your question:
for (NSManagedObject *oneObject in objects) {
CIChrono *chrono=(CIChrono *)oneObject;
NSLog(#"startDate %#", [chrono valueForKey:#"startDate");
NSLog(#"stopDate %#", [chrono valueForKey:#"stopDate"]);
NSLog(#"laps %#", [chrono valueForKey:#"laps"]);
NSSet *laps = [chrono valueForKey:#"laps"];
for (NSManagedObject *lap in laps) {
NSLog(#"Lap entity: %#", lap);
}
}
Note the -valueForKey: calls. Those are Key Value Coding which NSManagedObject instances (and all other Objective-C objects) respond to. I would suggest reading the documentation on KVC and KVO.
Each SQBChrono object contains a set of SBQLap objects that are associated with it. Look at your SQBChrono properties
#property (strong, nonatomic) NSOrderedSet *laps;
laps is a property that contains the many SBQLap objects to one SQBChrono object.
You can get an array from the set like this:
NSFetchRequest *request=[[NSFetchRequest alloc] initWithEntityName:kChronoEntityName];
NSError *error;
NSArray *chronoObjectArray = [appContext.managedObjectContext executeFetchRequest:request error:&error];
for (SQBChrono * chrono in chronoObjectArray) {
NSArray *lapsArray = [chrono.laps array];
NSLog("Chrono: %# laps: %#", chrono.startDate, lapsArray);
}
As you've added the SBQChrono objects to an array you will need to cast it:
SQBChrono *anObject = (SQBChrono *)objects[0];
Then you can:
anObject.laps;
Will give you the NSSet of all associated objects ?
Your array objects contains all your SBQChrono objects.
You can get all the SBQLaps associated with one chrono by doing:
SQBChrono * myChrono = objects[indexOfTheDesiredChrono];
The laps of the chrono are then in myChrono.laps, which is a NSOrderedSet (collection) of SBQLaps. Can be turn into an array like this :
NSArray * myChronoLaps = [myChrono.laps array]
You can fetch objects which have a relationship to 'SBQChrono'.
This can be done with two fetch requests, first fetch all the 'SBQChrono' objects & then perform a second request for 'SBQLap' entities with a predicate:
[NSPredicate predicateWithFormat:#"chrono IN %#",objects]
Looking at the model screenshot and code that you posted, it appears that the model has the relationship name set as lap (singular) but your class definition is using laps (plural). Core Data would be implementing an accessor for lap, but doesn't understand laps since it doesn't match the name in the model. Changing the model to say laps should fix that problem.

Core Data One to Many Relationship: unrecognized selector sent to instance

I am having two classes or objects: User and Medicine. Here is my User class:
#interface User : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSSet *medicine;
#end
#interface User (CoreDataGeneratedAccessors)
- (void)addMedicineObject:(Medicine *)value;
- (void)removeMedicineObject:(Medicine *)value;
- (void)addMedicine:(NSSet *)values;
- (void)removeMedicine:(NSSet *)values;
#end
and then the Medicine class
#interface Medicine : NSManagedObject
#property (nonatomic, retain) NSString * medName;
#property (nonatomic, retain) NSString * medType;
#property (nonatomic, retain) Dose *dose;
#property (nonatomic, retain) User *user;
#end
As my interfaces clearly show that user may have multiple medicines. (I am from database background thats why I am interpreting it like this).
When I add new object in User, it is easily done, but when I try to add new Medicine for existing object of user, I feel that my Xcode has a deep wish to shoot me at that time (vice versa).
Now here is the code that describes what I am doing:
Medicine *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
User *user = [NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:self.managedObjectContext];
if(![self checkUser])
{
object.user = user;
[user setName:self.userName];
NSLog(#"%# %#",object, user);
self.detailViewController.detailItem = object;
}
else
{
Medicine *medicine = [NSEntityDescription insertNewObjectForEntityForName:#"Medicine" inManagedObjectContext:self.managedObjectContext];
user = [self getUser:self.userName];
medicine.medName = object.medName;
medicine.medType = object.medType;
[medicine setUser:user];
self.detailViewController.detailItem = medicine;
}
I am having a simple logic: if name entered by user in text field, already exist in DB, then return the User object. Then just add another Medicine record for the same User. Other wise add new User and a Medicine object as well.
but I get error at:
[medicine setUser:user];
I also tried this one: (as I got it in another question like mine)
[user addMedicineObject:medicine];
Now I think that I have to override addMedicineObject method or something else.
Oh I forgot the error. I get this error: (the real villain)
[__NSArrayM managedObjectContext]: unrecognized selector sent to instance 0x8136bf0
Now any suggestion?
It is because you trying to mutate NSSet object. You need to create a mutable copy before you edit it.

NSManagedObject unrecognized selector sent to instance

I have a Core Data model as follows, where children is a to-many relationship.
.h
#implementation MyEntity
#dynamic name;
#dynamic children;
#end
.m
#interface MyEntity : NSManagedObject
#property (nonatomic) NSString *name;
#property (nonatomic) NSOrderedSet *children;
#end
I then try to set it using:
MYAppDelegate *delegate = (MYAppDelegate *)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *managedObjectContext = [delegate managedObjectContext];
NSEntityDescription *categoryEntity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:managedObjectContext];
NSManagedObject *newCategory = [[NSManagedObject alloc] initWithEntity:categoryEntity insertIntoManagedObjectContext:managedObjectContext];
[newCategory setValue:key forKey:#"name"];
NSOrderedSet *testSet = [[NSOrderedSet alloc] initWithArray:#[#"This", #"is", #"a", #"test"]];
[newCategory setValue:testSet forKey:#"children"];
}
}
Yet on that last line, I get this error:
NSCFConstantString managedObjectContext]: unrecognized selector sent
to instance 0xe8fa0'
If I change NSOrderedSet to NSSet the compiler complains that it expects an NSOrderedSet.
How can I assign the set to the NSManagedObject?
The problem isn't the NSOrderedSet, its the NSString instances that you put inside the set. These need to be replaces with instances of the entity which is configured in the data model at the destination of the relationship. You can't fill the relationship with the wrong kind of object.

RestKit 0.20.1 mapping local JSON "this class is not key value coding-compliant for the key..."

Hi—I'm using RestKit to map local JSON data (Twitter feed) that I've already received (which I've verified is happening), and am running into a problem when the mapping is taking place. The error I'm receiving is:
2013-05-26 17:23:57.541 FoodTrucks[25932:c07] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSEntityDescription 0x7663950> valueForUndefinedKey:]: this class is not key value coding-compliant for the key tweet.'
From my log (Gist), it appears as though it is finding mappable values (I have RestKit's ObjectMapping and CoreData logging turned on) from my JSON. I've looked online a bunch to try and find out why it's receiving this error, but can't seem to find anything that applies to my situation. This is how I perform the mapping:
-(void)performMapping
{
RKEntityMapping *mapping = [ObjectMappings FoodTruckArticleMapping];
RKManagedObjectStore *store = [[FoodTruckDataModel sharedDataModel] objectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"FoodTruck" inManagedObjectContext:store.mainQueueManagedObjectContext];
RKManagedObjectMappingOperationDataSource *mappingDS = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:store.mainQueueManagedObjectContext cache:store.managedObjectCache];
mappingDS.operationQueue = [NSOperationQueue new];
RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:self.moreStatuses destinationObject:entity mapping:mapping];
operation.dataSource = mappingDS;
NSError *error = nil;
[operation performMapping:&error];
[mappingDS.operationQueue waitUntilAllOperationsAreFinished];
}
I'm also not sure if this class that performs the mapping needs to inherit from anything in particular, and/or if it needs to possibly to adopt the RKMappingOperationDelegate. Right now, it's just inheriting from a NSObject. This is how my mapping class looks:
ObjectMappings.h:
#interface ObjectMappings : RKEntityMapping
+(RKEntityMapping *)FoodTruckArticleMapping;
#end
ObjectMappings.m:
#implementation ObjectMappings
+(RKEntityMapping *)FoodTruckArticleMapping
{
RKEntityMapping *jsonMapping = [RKEntityMapping mappingForEntityForName:#"FoodTruck" inManagedObjectStore:[[FoodTruckDataModel sharedDataModel] objectStore]];
jsonMapping.identificationAttributes = #[#"tweetID"];
[jsonMapping addAttributeMappingsFromDictionary:#{
#"text": #"tweet", #"user.screen_name": #"foodTruckName", #"id_str": #"tweetID", #"created_at": #"timeStamp"}];
return jsonMapping;
}
#end
And my object class is a NSManagedObject with all #dynamic method implementation. Any help would be greatly appreciated!
Edit:
NSManagedObject class, FoodTruck.h:
#interface FoodTruck : NSManagedObject
#property (nonatomic, retain) NSString *foodTruckName;
#property (nonatomic, retain) NSString *tweet;
#property (nonatomic, retain) NSDate *timeStamp;
#property (nonatomic, retain) NSString *tweetID;
#end
FoodTruck.m
#implementation FoodTruck
#dynamic foodTruckName;
#dynamic tweet;
#dynamic timeStamp;
#dynamic tweetID;
#end
I eventually figured out my problem— my error was not creating a new instance of a NSManagedObject that was initialized with my entity:
in -(void)performMapping
NSManagedObject *newManagedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:store.persistentStoreManagedObjectContext];
That NSManagedObject then becomes the destination object in RKMappingOperation, and not my entity, as I was doing before:
RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:self.moreStatuses destinationObject:newManagedObject mapping:mapping];
It's also necessary to call -(BOOL)saveToPersistentStore:(NSError **)error :
on your RKManagedObjectStore to save it to your SQLite database:
[store.persistentStoreManagedObjectContext saveToPersistentStore:&error];
That's something else that took me a while to figure out!

Resources