NSFetchedResultsController: NSSortDescriptor with relation as key sends unrecognized selector compare: to core data entity object - core-data

I am trying to save a one-to-many relation in core data. There is a user decision involved to determine whether the new child list object needs to be attached to a new parent object. In the other case an existing database entry is used as a parent object. Under certain circumstances after saving, the app crashes.
FINAL EDIT: Sorry if you mind me keeping all of the edits, I still will. The process of enlightenment was quite convoluted. After all I started out thinking it was a data conflict... Thanks again to Tom, who helped point me in the right direction: I am still using a relation for sorting and grouping core data entities with an NSFetchedResultsController. I have written a valid compare: method for my entity class now and so far from what I can see it is working. I am about to write an answer for my question. I will still be very grateful for any information or warnings from you concerning this!
EDIT 3: The save procedure and the user alert seem to be incidental to the problem. I have zoomed in on the NSFetchedResultsController now and on the fact that I am using a relation ('examination') as sectionNameKeyPath. I will now try to write a compare: method in a category to my Examination entity class. If that does not work either, I will have to write a comparable value into my Image entity class in addition to the relation and use that for sections. Are y'all agreed?
EDIT 1: The crash only occurs after the user has been asked whether she wants a new examination and has answered YES. The same method is also entered when there was no user prompt (when the creation of a new examination has been decided by facts (no examination existing = YES, existing examination not timed-out = NO). In these cases the error does NOT occur. It must be that the view finishes loading while the alert view is open and then the collection view and its NSFetchedResultsController join the fun.
EDIT 2: Thanks to Tom, here is the call stack. I did not think it was relevant, but the view controller displays images in a collection view with sections of images per examination descending. So both the section key and the sort descriptor of the NSFetchedResultsController are using the examination after the MOCs change notification is sent. It is not the save that crashes my app: it is the NSSortDescriptor (or, to be fair, my way to use all of this).
The code for the NSFetchedResultsController:
#pragma mark - NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (m_fetchedResultsController != nil) {
return m_fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([Image class]) inManagedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext]];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key, identical sort to section key path must be first criterion
NSSortDescriptor *examinationSortDescriptor = [[NSSortDescriptor alloc] initWithKey:kexaminationSortDescriptor ascending:NO];
NSSortDescriptor *editDateSortDescriptor = [[NSSortDescriptor alloc] initWithKey:keditDateSortDescriptor ascending:NO];
NSArray *sortDescriptors =[[NSArray alloc] initWithObjects:examinationSortDescriptor, editDateSortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext] sectionNameKeyPath:kSectionNameKeyPath cacheName:NSStringFromClass([Image class])];
aFetchedResultsController.delegate = self;
m_fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
HLSLoggerFatal(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return m_fetchedResultsController;
}
#pragma mark - NSFetchedResultsControllerDelegate - optional
/* Asks the delegate to return the corresponding section index entry for a given section name. Does not enable NSFetchedResultsController change tracking.
If this method isn't implemented by the delegate, the default implementation returns the capitalized first letter of the section name (seee NSFetchedResultsController sectionIndexTitleForSectionName:)
Only needed if a section index is used.
*/
- (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName
{
return sectionName;
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// In the simplest, most efficient, case, reload the table view.
[[self collectionView] reloadData];
}
/* THE OTHER DELEGATE METHODS ARE ONLY FOR UITableView! */
The code for saving examination (existing or new) and the new image:
-(BOOL)saveNewImage
{
BOOL done = NO;
// remove observer for notification after alert
[[NSNotificationCenter defaultCenter] removeObserver:self name:kExaminationTimedoutAlertDone object:nil];
Examination * currentExamination = [self getCurrentExamination];
if ([self userWantsNewExamination] == YES)
{ // if an examination was found
if (currentExamination != nil)
{ // if the found examination is not closed yet
if ([currentExamination endDate] == nil)
{ // close examination & save!
[currentExamination closeExamination];
NSError *savingError = nil;
[HLSModelManager saveCurrentModelContext:(&savingError)];
if (savingError != nil)
{
HLSLoggerFatal(#"Failed to save old, closed examination: %#, %#", savingError, [savingError userInfo]);
return NO;
}
}
}
currentExamination = nil;
}
// the examination to be saved, either new or old
Examination * theExamination = nil;
// now, whether user wants new examination or no current examination was found - new examination will be created
if (currentExamination == nil)
{
// create new examination
theExamination = [Examination insert];
if (theExamination == nil)
{
HLSLoggerFatal(#"Failed to create new examination object.");
currentExamination = nil;
return NO;
}
// set new examinations data
[theExamination setStartDate: [NSDate date]];
}
else
{
theExamination = currentExamination;
}
if (theExamination == nil)
{ // no image without examination!
HLSLoggerFatal(#"No valid examination object.");
return NO;
}
Image *newImage = [Image insert];
if (newImage != nil)
{
// get users last name from app delegate
AppDelegate * myAppDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
// set image data
[newImage setEditUser: [[myAppDelegate user] lastName]];
[newImage setEditDate: [NSDate date]];
[newImage setExamination: theExamination];
[newImage setImage: [self stillImage]];
[newImage createImageThumbnail];
// update edit data
[theExamination setEditUser: [[myAppDelegate user] lastName]];
[theExamination setEditDate: [NSDate date]];
// unnecessary! CoreData does it automatically! [theExamination addImagesObject:newImage];
//! Important: save all changes in one go!
NSError *savingError = nil;
[HLSModelManager saveCurrentModelContext:(&savingError)];
if (savingError != nil)
{
HLSLoggerFatal(#"Failed to save new image + the examination: %#, %#", savingError, [savingError userInfo]);
}
else
{
// reload data into table view
[[self collectionView] reloadData];
return YES;
}
}
else
{
HLSLoggerFatal(#"Failed to create new image object.");
return NO;
}
return done;
}
The error:
2013-05-22 17:03:48.803 MyApp[11410:907] -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0
2013-05-22 17:03:48.809 MyApp[11410:907] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0 with userInfo (null)
2013-05-22 17:03:48.828 MyApp[11410:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0'
And here are the entity class files, too:
//
// Examination.h
// MyApp
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Image;
#interface Examination : NSManagedObject
#property (nonatomic, retain) NSDate * editDate;
#property (nonatomic, retain) NSString * editUser;
#property (nonatomic, retain) NSDate * endDate;
#property (nonatomic, retain) NSDate * startDate;
#property (nonatomic, retain) NSSet *images;
#end
#interface Examination (CoreDataGeneratedAccessors)
- (void)addImagesObject:(Image *)value;
- (void)removeImagesObject:(Image *)value;
- (void)addImages:(NSSet *)values;
- (void)removeImages:(NSSet *)values;
#end
//
// Examination.m
// MyApp
//
#import "Examination.h"
#import "Image.h"
#implementation Examination
#dynamic editDate;
#dynamic editUser;
#dynamic endDate;
#dynamic startDate;
#dynamic images;
#end

This error had nothing to do with the saving of data to the MOC.
Because the saving of the new image data is triggered in the prepareForSegue of the previous view controller and the user alert gives the next view controller the time to finish loading, also creating the NSFetchedResultsController and its connection to its delegate, the exception was raised in the temporary context of the save to the MOC and only after the user alert.
The NSFetchedResultsController had started listening for changes of the MOC only in this case. It seems that if it gets alerted to an MOC change it will fetch only the changes and only then it needs to compare the new data with the existing data. Further information on this would be very welcome!
Then, because I had set a sort descriptor (and also the sectionNameKeyPath) to a relation and not provided the means to sort the entity objects in my core data entity class, the NSFetchedResultsController could not continue. Looking back it seems all so easy and natural, I really become suspicious of the simplicity of my solution...
I find it interesting that it could fetch the initial data in one go, when no change interfered. After all it was using the same NSSortDescriptor. Any ideas?
This is my solution:
//
// MyCategoryExamination.m
// MyApp
//
#import "MyCategoryExamination.h"
#implementation Examination (MyCategoryExamination)
- (NSComparisonResult)compare:(Examination *)anotherExamination;
{
return [[self startDate] compare:[anotherExamination startDate]];
}
#end
Please tell me if there is something wrong with this.

Related

Assigning fetched NSManagedObject to a property

This question may seem long, but I'm sure it's relativity simple for Core-Data experts. Showing the configuration made this Question long. Thanks!
In CoreData I have a User entity and an NSManagedObject subclass (User.h/.m) created from that entity. Not sure if it's relevant, but I'm using remote Database with Stackmob.
Here is what my fetch request looks like in Review.m:
Fetch Request:
-(NSArray *)fetchRequest
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//Please Note the line below:
[fetchRequest setReturnsObjectsAsFaults:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username == %#", self.usernameField.text];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
return fetchedObjects;
}
if (fetchedObjects.count>=1)
{
NSLog(#"Number of Objects Returned %i", fetchedObjects.count);
NSManagedObject *fetchedObject = [fetchedObjects objectAtIndex:0];
}
Here is the log:
2013-08-18 10:26:25.082 Time[995:c07] Number of Objects Returned 1
2013-08-18 10:26:25.082 Time[995:c07] User Object: <User: 0xe07b260> (entity: User; id: 0xa65ab70 <x-coredata://392983AD-D649-4D68-A93F-D86109BC009C-995-000004B584F1BB06/User/piphone5> ; data: <fault>)
Several weeks ago I had created the User Object successfully with the following:
User *newUser = [[User alloc] initIntoManagedObjectContext:self.managedObjectContext];
[newUser setValue:self.usernameField.text forKey:[newUser primaryKeyField]];
[newUser setValue:self.emailAddressField.text forKey:#"email"];
[newUser setPassword:self.passwordField.text];
[newUser setValue:self.gender forKey:#"gender"];
[self.managedObjectContext saveOnSuccess:^{
[self.client loginWithUsername:self.usernameField.text password:self.passwordField.text onSuccess:^(NSDictionary *results) {
NSLog(#"Login Success %#",results);
} onFailure:^(NSError *error) {
NSLog(#"Login Fail: %#",error);
}];
This is what I would like to do in Review.m:
#interface Review()
#property (strong, nonatomic) User *user;
#end
#sythesize = user;
....
if (fetchedObjects.count>=1)
{
NSLog(#"Number of Objects Returned %i", fetchedObjects.count);
NSManagedObject *fetchedObject = [fetchedObjects objectAtIndex:0];
NSLog(#"fetchObject Object %#", fetchedObject);
//Addition
user = (User*)(fetchedObject);
NSLog(#"User %#", user);
}
Here is the log:
2013-08-18 11:07:13.313 Time[1177:c07] Number of Objects Returned 1
2013-08-18 11:07:13.314 Time[1177:c07] fetchObject Object <User: 0xa532cc0> (entity: User; id: 0xb426de0 <x-coredata://3336122B-7117-4D92-B0A1-DDBAF80DDBF7-1177-000006EEE046E326/User/piphone5> ; data: <fault>)
2013-08-18 11:07:13.314 Time[1177:c07] User <User: 0xa532cc0> (entity: User; id: 0xb426de0 <x-coredata://3336122B-7117-4D92-B0A1-DDBAF80DDBF7-1177-000006EEE046E326/User/piphone5> ; data: <fault>)
Why is this not working? User still shows up as Fault? I have the following line in fetch Request: [fetchRequest setReturnsObjectsAsFaults:NO];
The Following is the reason I need to do this:
Notification *notificationObject = [NSEntityDescription insertNewObjectForEntityForName:#"Notification" inManagedObjectContext:self.managedObjectContext];
[notificationObject setValue:[NSNumber numberWithInt:1] forKey:#"appType"];
[notificationObject setValue:[notificationObject assignObjectId] forKey:[notificationObject primaryKeyField]];
NSError *error = nil;
if (![self.managedObjectContext saveAndWait:&error])
{
NSLog(#"There was an error");
}
else
{
NSLog(#"Notification Object Created");
}
//I need to Add the fetchedObject as UsersObject (which is a relationship)
[notificationObject addUsersObject:user];
[self.managedObjectContext saveOnSuccess:^{ ....
Here is my CoreData Setup:
Why is this not working? User still shows up as Fault? I have the following line in fetch Request: [fetchRequest setReturnsObjectsAsFaults:NO];
This is a StackMob restriction. In https://developer.stackmob.com/ios-sdk/core-data-guide
it is explicitly stated that returnObjectsAsFaults / setReturnObjectsAsFaults:
is not (yet) supported.
A managed object showing up in the log as fault is fine. Use it or access its properties, and Core Data will fill the faults in the most efficient way with a minimum strain on resources.
This last part is important because it is the reason you don't really want to change the faulting behavior of Core Data. The necessity to send a notification does not change that.
Also, referring to the faulting section in the Core Data Programming Guide:
Batch faulting
You can batch fault a collection of objects by executing a fetch request using a predicate with an IN operator, as illustrated by the following example. [...]
NSArray *array = [NSArray arrayWithObjects:fault1, fault2, ..., nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self IN %#", array];
In OS X v10.5 and later, when you create a fetch request you can use the NSFetchRequest method setReturnsObjectsAsFaults: to ensure that managed objects are not returned as faults.
So, you see, the special fetch request shortcut is only available to OS X. Read on at above source to check out the alternative, pre-fetching.

Save causing 'Illegal attempt to establish a relationship...'

I am performing an NSOperation on a background thread that imports data into Core Data. I do this by first creating a record of the import ('Import') and then import an object that relates to the import record. If I save the managed object context, the next attempt to link an imported object to the import record will fail:
Illegal attempt to establish a relationship 'import' between objects in different contexts (source = <NSManagedObject: 0x1067bb730> (entity: Genre; id: 0x1053330c0 <x-coredata:///Genre/tC6A85CFE-3D0A-4E29-9186-4FD46104AEBC60> ; data: {
import = nil;
name = Polka;
}) , destination = <NSManagedObject: 0x106736170> (entity: Import; id: 0x103b571e0 <x-coredata://440D80CF-7C56-4B6F-9778-990032A76B8B/Import/p1> ; data: <fault>))
Here is the boiled-down code. I modified the code slightly to demonstrate the effect by adding a superfluous save; normally there'd be no reason to have one there.
NSError *writeError = nil;
TNAppDelegate *del = (TNAppDelegate *)[[NSApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:[del persistentStoreCoordinator]];
[moc setUndoManager:nil];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
// create import instance
NSManagedObject *import = [NSEntityDescription insertNewObjectForEntityForName:#"Import" inManagedObjectContext:moc];
[import setValue:[NSDate date] forKey:#"start"];
[moc save:&writeError];
[moc reset];
NSString *newGenre = [songDictItem objectForKey:#"Genre"];
NSManagedObject *newGenreObject = [NSEntityDescription insertNewObjectForEntityForName:#"Genre" inManagedObjectContext:moc];
[newGenreObject setValue:newGenre forKey:#"name"];
[newGenreObject setValue:import forKey:#"import"]; // BOOM!
UPDATE: By request, I am providing the code for mergeChanges:. It is found in the NSOperation. I have tried a number of variations on saving changes to the main MOC, but they've all ended the same way.
- (void)mergeChanges:(NSNotification*)notification
{
TNAppDelegate *del = (TNAppDelegate *)[[NSApplication sharedApplication] delegate];
if ([notification object] == [del managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(mergeChanges:) withObject:notification waitUntilDone:YES];
return;
}
[[del managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
To this day I never figured out exactly what was going on. In the end I rebooted my project and designed it like this tutorial from the ground up. I had used it in the past for reference but somehow it took a full adoption of their code to work.

A Core-Data Relashionship not working after stopping the app

I have had one issue with Core Data and Relationship. Since this has kept me without a solution for a while and has at the same time been easy to locate I have made a tiny sample application to reproduce the problem.
Under XCode I created a barebone Window-based application, checking "Use Core Data for storage".
I called this application "CDR" for Core-Data-Relationship.
I then added a subclass of UIViewController called CDR_ViewController; as I usually do.
Here is the relevant code that I added :
First in CDR_ViewController.h :
#import <UIKit/UIKit.h>
#import "AppDelegate_Shared.h"
#interface CDR_ViewController : UIViewController {
UILabel *cdrLabel;
NSManagedObject *currentItem;
}
#property (nonatomic, retain) IBOutlet UILabel *cdrLabel;
-(IBAction) handleButtonClick:(id)sender;
#end
Then in CDR_ViewController.m the viewDidLoad method is as follow :
- (void)viewDidLoad {
[super viewDidLoad];
NSFetchRequest *request;
NSError *error;
AppDelegate_Shared *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
request=[[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName:#"CDR_Entity" inManagedObjectContext:context]];
error=nil;
NSUInteger count = [context countForFetchRequest:request error:&error];
[request release];
if (count!=0) {
request=[[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName:#"CDR_Entity" inManagedObjectContext:context]];
error=nil;
NSArray *objects=[context executeFetchRequest:request error:&error];
NSLog(#"Error:%#",error);
[request release];
currentItem=[objects objectAtIndex:0];
return;
}
NSManagedObject *newItemOne,*newItemTwo,*newItemThree;
request=[[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName:#"CDR_Entity" inManagedObjectContext:context]];
newItemOne=[NSEntityDescription insertNewObjectForEntityForName:#"CDR_Entity" inManagedObjectContext:context];
newItemTwo=[NSEntityDescription insertNewObjectForEntityForName:#"CDR_Entity" inManagedObjectContext:context];
newItemThree=[NSEntityDescription insertNewObjectForEntityForName:#"CDR_Entity" inManagedObjectContext:context];
[newItemOne setValue:[NSNumber numberWithInteger:1] forKey:#"Value"];
[newItemTwo setValue:[NSNumber numberWithInteger:2] forKey:#"Value"];
[newItemThree setValue:[NSNumber numberWithInteger:3] forKey:#"Value"];
[newItemOne setValue:newItemThree forKey:#"Previous"];
[newItemOne setValue:newItemTwo forKey:#"Next"];
[newItemTwo setValue:newItemOne forKey:#"Previous"];
[newItemTwo setValue:newItemThree forKey:#"Next"];
[newItemThree setValue:newItemTwo forKey:#"Previous"];
[newItemThree setValue:newItemOne forKey:#"Next"];
error=nil;
[context save:&error];
[request release];
currentItem=newItemOne;
}
And the viewWillAppear method is as follow :
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
cdrLabel.text=[NSString stringWithFormat:#"%#",[currentItem valueForKey:#"Value"]];
}
Finally the handleButtonClick method is as follow :
-(IBAction) handleButtonClick:(id)sender
{
if (((UIButton*)sender).tag==101) {// Previous item.
currentItem=[currentItem valueForKey:#"Previous"];
} else /*(((UIButton*)sender).tag==102)*/ {// Next item.
currentItem=[currentItem valueForKey:#"Next"];
}
cdrLabel.text=[NSString stringWithFormat:#"%#",[currentItem valueForKey:#"Value"]];
}
The CDR_ViewController.xib contains one Label and two buttons.
This code works fine for start, meaning just after I compile the app and reset the contents of the iPhone simulator.
I can then cycle the contents of the label : 1,2,3,1,2,3,1,2,3 ---etc… and backward with the buttons.
As soon as I terminate the application using Command-Q. When I want to start it again, it crashes on :
currentItem=[currentItem valueForKey:#"Previous"];
or:
currentItem=[currentItem valueForKey:#"Next"];
inside the handleButtonClick method.
And it is the same when I put the app on my iPod touch.
Can anyone see in my code anything that could explain this behavior?
If you terminate an app in the simulator by stopping it, a opposed to clicking the home button in the simulator or otherwise, then the program is sent a SIGKILL message which immediately stop it running. Your application delegate methods (will terminate, will enter background etc) will not be called.
In all likelihood this will have meant that your managed object context has not been saved, so when re-running the app the object graph cannot be restored properly. In your sample app, add another button which saves the context. If you click this button, then terminate your app, does that solve the problem?

How do I check if an NSSet contains an object of a kind of class?

How would you implement the following instance method for NSSet:
- (BOOL)containsMemberOfClass:(Class)aClass
Here's why I want to know:
Core Data model:
How do I add a Facebook authorization to a user's authorizations NSSet, but only if one doesn't already exist. In other words, a user can have many authorizations (in case I choose to add a Twitter (e.g.) authorization in the future) but should only have one of each kind of authorization. So, if (![myUser.authorizations containsMemberOfClass:[Facebook class]]), then add a Facebook authorization instance to myUser.
Unfortunately, you have to loop through all of the authorizations and check:
#interface NSSet (ContainsAdditions)
- (BOOL)containsKindOfClass:(Class)class;
- (BOOL)containsMemberOfClass:(Class)class;
#end
#implementation NSSet (ContainsAdditions)
- (BOOL)containsKindOfClass:(Class)class {
for (id element in self) {
if ([element isKindOfClass:class])
return YES;
}
return NO;
}
- (BOOL)containsMemberOfClass:(Class)class {
for (id element in self) {
if ([element isMemberOfClass:class])
return YES;
}
return NO;
}
#end
With Core Data it is actually best practice to use an NSFetchRequest with an NSPredicate. To do that you would have to add an attribute to your Authorization object, something like authorizationType. You could then do the following:
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY user.authorizations.authorizationType == %#", #"facebook"];
[request setEntity:[NSEntityDescription entityForName:#"Authorization" inManagedObjectContext:moc]];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *result = [moc executeFetchRequest:request error:&error];
You can then check the count of result to see if it exists or not. Using NSPredicate allows you to use any optimizations Apple has added around CoreData. Here are the Apple Docs.
What if you add this instance method to the User class?
- (BOOL)hasFacebookAuthorization {
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"user.id == %#", [self id]];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Facebook"
inManagedObjectContext:managedObjectContext]];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *result = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (!result) {
// TODO: Handle the error appropriately.
NSLog(#"hasFacebookAuthorization error %#, %#", error, [error userInfo]);
}
return [result count] > 0;
}
Your going about this the wrong way. If the types of authorization are something common and important then you should model that in your data model directly instead of trying to impose that structure in external controller code.
If you have a fixed set of authorizations then you should create an entity to model those authorizations.
Facebook{
accessToken:string
experationDate:date
authorization<-->Authorizations.facebook
}
Google{
username:string
password:string
authorization<-->Authorizations.google
}
Authorizations{
user<-->User.authorizations
facebook<-->Facebook.authorization
google<-->Google.authorization
}
Now you have all your authorizations captured in the data model where they belong. To tighten things up more. You could add a custom method to the User class to control adding and removing authorizations.
The key idea here is that all the logic that manages data should be encapsulated in the data model to the greatest extent possible. This allows you to (1) test all data operations independent of the interface and (2) easily add and remove interface elements without breaking the data model.
Old question, but slightly different answer:
NSPredicate *objectTypeFilter = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject isKindOfClass:NSClassFromString(#"MyObject")];
}];
NSSet *myObjects =[allObjects filteredSetUsingPredicate:objectTypeFilter];
if (myObjects.count>0) {
// There was at least one object of class MyObject in the allObjects set
}

after i add new data in core data collection it can't be saved (multiply validation error occured)

The code bellow add to Core data issues, but after it added, I can't save with error (multiply validation error occured)
MySQLIXC *ixcDatabase = [[MySQLIXC alloc] init];
NSArray *destinationsForSaleList = [ixcDatabase destinationsForSaleList:carrier];
NSFetchRequest *request1 = [[[NSFetchRequest alloc] init] autorelease];
[request1 setEntity:[NSEntityDescription entityForName:#"DestinationsList"
inManagedObjectContext:managedObjectContext]];
for (NSDictionary *destinationsForSale in destinationsForSaleList) {
NSManagedObject *object1 = [NSEntityDescription
insertNewObjectForEntityForName:#"DestinationsList"
inManagedObjectContext:managedObjectContext];
NSLog(#"Moc: %#",managedObjectContext);
[object1 setValue:#"y" forKey:#"weAreSoldIt"];
// changeDate
NSString *chdate = [destinationsForSale objectForKey:#"chdate"];
NSDateFormatter *changeDate = [[[NSDateFormatter alloc] init] autorelease];
[object1 setValue:[changeDate dateFromString:chdate] forKey:#"changeDate"];
NSLog(#"Carrier :%# Destination name:%#",carrier, destinationsForSale);
//Country
[object1 setValue:[destinationsForSale objectForKey:#"country"] forKey:#"country"];
//rate
NSNumberFormatter *rate = [[[NSNumberFormatter alloc]init ]autorelease];
[object1 setValue:[rate numberFromString:[destinationsForSale objectForKey:#"price"]] forKey:#"rate"];
Unfortunately I can't fix a bug by the way which u propose bellow.
Bcs Entity DestinationList must have relations with Entity Carriers by project understanding.
That is how I try to fix it:
[objectDestinationList setValue:objectCarrier forKey:#"carrier"];
I was send to method my carrier object as object, but it doesn't work.
In this case, I don't know how is a way to fix it around. Bcs I see error, but don't see case why error is start.
Do u know a simple code to correct add relationships to Entity? All what I catch around internet is a core data book ,my Marcus Zarra and his very hard to understanding example. His showing a complex solution, I can understand it, but using programming style which not very easy for me at this moment (according my one month experience in cocoa programming ;)
Here is additional information: How I create Carrier instance. I have managedObjectContext, which I receive to class from AppDelegate.
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Carrier"
inManagedObjectContext:managedObjectContext]];
NSManagedObject *carrier = [managedObjectContext executeFetchRequest:request error:&error]
by the same way I prepare NSManagedObject for DestinationsList Entity.
After that I add all values to NSManagedObject for destinationsList, I have to make relationship between Carrer NSManagedObject and destinationsList. In this case I have trouble. Bellow is how I try to update relationship for Carrier entity:
NSSet *newDestSet = [NSSet setWithObjects:objectDestination,nil];
[objectCarrier setValue:newDestSet forKey:#"destinationsList"];
finally I have 2010-11-03 21:22:56.968 snow[20301:a0f] -[NSCFArray initialize]: unrecognized selector sent to instance 0x1c44e40
Bellow is my class interface deescription:
#interface InitUpdateIXC : NSObject {
NSInteger destinationType;
}
-(void) updateCarrierList:(NSManagedObjectContext *)managedObjectContext;
-(void)updateDestinationList:(NSManagedObjectContext *)managedObjectContext
forCarrier:(NSString *)carrierName
forCarrierObject:(NSManagedObject *)objectCarrier
destinationType:(NSInteger)destinationType;
#end
Yep, bellow in answer present correct model, but some different is here.
At first, i don't have separate class for Entity as u present in you model. My current class is just NSManagedObject
In second, relationship "carrier" is non-optional for Entity DestinationsList.
SOLUTION AND ERROR DESCRIPTION:
In case of trouble, what happened with my code:
When i try to add setValue forKey with back relationship from DestinationsList to Carrier, i forget that NSManagmentObject return every time array, not just object.
This is a reason why i receive error about array init problem.
Solution is not sent Carrier object to method, bcs for me was too hard to extract from array correct object without key value. I was using a predicate access to extract object to array and lastObject function to extract correct object from array. After that i set accroding value and everything working fine.
A solution not look like cocoa-style, so better way is refactor it in future, any suggestion wellcome.
Here is appropriate code:
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"Carrier"
inManagedObjectContext:managedObjectContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name =%#",carrierName];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *currentCarriers = [managedObjectContext executeFetchRequest:request error:&error];
[objectDestination setValue:[currentCarriers lastObject] forKey:#"carrier"];
Try adding something like this for you 'save'
NSError *error = nil;
if (![managedObjectContext save:&error])
{
// Handle the error.
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0)
{
for(NSError* detailedError in detailedErrors)
{
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else
{
NSLog(#" %#", [error userInfo]);
}
}
At least, then you can see what the multiple errors are. If you post those, someone may be able to offer more help.
One thought, though, is that there is something buggy about your data model - like non-optional attribute with no value, etc.
If you create NSManagedObject subclassed Carrier and DestinationsList, then in Carrier.h you should have some method declarations like this. (Assuming that Carrier to-many DestinationsList is called 'destinationsLists'.)
#interface Carrier (CoreDataGeneratedAccessors)
- (void)addDestinationsListsObject:(Run *)destinationsList;
- (void)removeDestinationsListsObject:(Run *)destinationsList;
- (void)addDestinationsLists:(NSSet *)destinationsLists;
- (void)removeDestinationsLists:(NSSet *)destinationsLists;
#end
Once these are declared, you should be able to add a DestinationsList to a Carrier with a line like this:
[myCarrier addDestinationsListsObject:myDestinationsList];
Not seeing your full data model, it is difficult to know what is happening and what would help fix it.
Do you have something like this for your model definition?

Resources