I am using MagicalRecord (MR) to delete all records belonging to a selected client (I successfully delete the client record, then go after the appointment records for that client). In doing so, I am getting the error.
[_PFArray MR_deleteInContext:]: unrecognized selector sent to instance
Here is the code, along with the pertinent definitions:
// set up predicate using selectedClientKey
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"aClientKey == %#", selectedClientKey];
ClientInfo *clientSelected = [ClientInfo MR_findFirstWithPredicate:predicate inContext:localContext];
if(clientSelected) {
[clientSelected MR_deleteInContext:localContext];
[localContext MR_saveToPersistentStoreAndWait];
}
// delete clients appointments...
predicate = [NSPredicate predicateWithFormat:#"aApptKey == %#", selectedClientKey]; // use client key
AppointmentInfo *apptSelected = [AppointmentInfo MR_findAllWithPredicate:predicate inContext:localContext];
if(apptSelected) {
[apptSelected MR_deleteInContext:localContext];
[localContext MR_saveToPersistentStoreAndWait];
}
Here is the definition of AppointmentInfo:
#interface AppointmentInfo : NSManagedObject
#property (nonatomic, retain) NSString * aApptKey;
#property (nonatomic, retain) NSDate * aEndTime;
#property (nonatomic, retain) NSString * aServiceTech;
#property (nonatomic, retain) NSDate * aStartTime;
On the findAllWithPredicate statement, I am getting this compiler warning:
CalendarViewController.m:80:43: Incompatible pointer types assigning
to 'NSMutableArray *' from 'NSArray *__strong'
I understand that the findAllWithPredicate statement will return a NSArray; however I have seen examples using NSManagedObject, which is what AppointmentInfo is. ClientInfo in the 3rd line down is also a NSManagedObject and it has NO compiler warning. I thought that it might be because there was only one (1) record returned from the find statement, but it makes no difference, one record or multiple records.
Am I getting the run error due to the compiler warning, or is there something else wrong? (I have looked at Google and SO, and found nothing that addresses this particular issue).
You are correct that findAllWithPredicate: will return an array. The examples you've seen are most likely using the findFirstWithPredicate: or similar style method. Find First, as the name implies, will return the first object in the results returned from the request. This is most likely what you want as well.
I figured it out... for those who might have the same issue, MR_findAll returns a NSArray which you have to "walk through" and delete each individually. Here's the corrected code from above:
// delete clients appointments...
predicate = [NSPredicate predicateWithFormat:#"aApptKey == %#", selectedClientKey]; // use client key
NSArray *apptDataArray = [AppointmentInfo MR_findAllWithPredicate:predicate inContext:localContext];
for(int i = 0; i < apptDataArray.count; i++) {
AppointmentInfo *ai = [apptDataArray objectAtIndex: i];
[ai MR_deleteEntity];
}
[localContext MR_saveToPersistentStoreAndWait];
Related
I need to create a predicate to get object from core data,
My entity "Form" looks like that:
#interface EEForm (CoreDataProperties)
#property (nullable, nonatomic, retain) NSDate *date;
#property (nullable, nonatomic, retain) NSString *descriptor;
#property (nullable, nonatomic, retain) NSString *location;
#property (nullable, nonatomic, retain) NSMutableSet<Participants *> *participants;
entity "Participants" look like that:
#interface Participants (CoreDataProperties)
#property (nonatomic, assign) NSInteger indexRow;
#property (nullable, nonatomic, retain) NSString *participant;
#property (nullable, nonatomic, retain) EEForm *form;
I want to get objects on the base of participants field, that contains all objects from a given array (this array consist of string objects and it changes dependently of what the user selects).
Fetch request is performed by FetchResultController. I set the predicate in initialisation of it.
I did it in that way, but it includes all objects that contains at least one object from the given array.
- (NSFetchedResultsController *)fetchResultController {
if(_fetchResultController != nil) {
return _fetchResultController;
}
NSPredicate *lPredicate = [NSPredicate predicateWithFormat:#"ANY participants.participant IN %#", _selectedForPredicatel];
NSFetchRequest *lRequest = [[NSFetchRequest alloc]init];
[lRequest setPredicate:lPredicate];
NSEntityDescription *lEntity = [NSEntityDescription entityForName:#"Form" inManagedObjectContext:self.managedObjectContext];
[lRequest setEntity:lEntity];
[lRequest setFetchBatchSize:10];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"date" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[lRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *fetchResultController = [[NSFetchedResultsController alloc]initWithFetchRequest:lRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
fetchResultController.delegate = self;
self.fetchResultController = fetchResultController;
NSError *error = nil;
if(![[self fetchResultController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchResultController;
}
Could you please advice how to set predicate properly to filter objects that contain all elements from the given array.
EDIT:
array that is used for predicate looks like that:
_selectedForPredicatel = [[NSMutableArray alloc]initWithObjects:#"Child",#"Adult", #"Group of people", #"Family", #"Teenager", nil];
every time user selects parameters by which filter 'Form' objects, this array is updating, so I need fetch request to be done according to it. So if 'Form' has included participants: #"Child",#"Adult", but in an array _selectedForPredicatel there are objects: #"Adult", #"Group of people", in this case this 'Form' shouldn't be fetched. Should be fetched only that 'Form'that includes both elements of _selectedForPredicatel array.
Thanks a lot for any advice.
You want to include the forms if, for each of the user's selections, there is at least one Participant that has a participant attribute that matches.
If the participant names were unique (at least for each Form), you could count the matches and compare to the count of the user's selections. But if not, you need to create a predicate test each of the user's selections separately, and then combine the results into one big predicate:
NSPredicate *template = [NSPredicate predicateWithFormat:#"(SUBQUERY(participants, $p, $p.participant == $SELECTED).#count > 0)"];
NSMutableArray *predicateArray = [NSMutableArray array];
for (NSString *testString in _selectedForPredicatel) {
NSDictionary *subsVars = #{#"SELECTED" : testString};
NSPredicate *testPredicate = [template predicateWithSubstitutionVariables:subsVars];
[predicateArray addObject:testPredicate];
}
NSPredicate *finalPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicateArray];
NSLog(#"finalPredicate is %#", finalPredicate);
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"ListEntity"];
fetch.predicate = finalPredicate;
Here template is a dummy predicate using $SELECTED as a placeholder for the user's selection. The for loop uses this to create a predicate for each of the user's selections and adds it to a mutable array. The final predicate is built by compounding the array of predicates using AND clauses.
It's pretty messy (the final predicate is bad enough, the underlying SQL is horrible) so performance might be dismal.
Instead of ANY you can use ALL :
NSPredicate *lPredicate = [NSPredicate predicateWithFormat:#"ALL participants.participant IN %#", _sortByParticipants];
This will check if all objects are in your collection.
I just started Swift and usually in Objective-C I create a category for each one of my NSManagedObject subclasses so the category isn't erased each time I have to generate my subclass. Moreover, it can simplify the creation of CoreData objects, especially when populated by data coming from a JSON.
Example :
My NSManagedObject subclass :
#class Book;
#interface Book : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * author;
#property (nonatomic, retain) NSString * plot;
#end
Its "helper" category implementation :
+ (Book*)bookFromDictionary:(NSDictionary *)dictionary inContext:(NSManagedObjectContext *)context {
Book *book = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Book"];
request.predicate = [NSPredicate predicateWithFormat:#"name == %#", dictionary[#"name"]];
request.fetchLimit = 1;
NSError *error;
NSArray *result = [context executeFetchRequest:request error:&error];
if (!result || error) {
NSLog(#"Error getting Book : %#", error.localizedDescription);
} else if ([result count] == 0) {
book = [NSEntityDescription insertNewObjectForEntityForName:#"Book" inManagedObjectContext:context];
} else {
book = [result lastObject];
}
self.name = dictionary[#"name"];
self.author = dictionary[#"author"];
self.plot = dictionary[#"plot"];
return book;
}
I'm trying to reproduce this concept in Swift but I don't have any idea how.
It seems that extensions replace categories in Swift but if I implement an extension into a NSManagedObject subclass in Swift, it will be erased each time I have to generate my NSManagedObject subclass (because it's in the same file...). And that's why I used to create categories in Obj-C.
Can someone tell me what are the good practices about this in Swift ?
I would greatly appreciate any help !
A Swift extension is the right way to define additional methods for NSManagedObject subclasses.
You can put the extension into a separate file
(e.g. "BookExtensions.swift") so that they are not overwritten when
"Book.swift" is recreated in Xcode.
There are issues in generating the subclasses for NSManagedObjects in Swift. Typically, you have to go through them again manually to e.g. change types into optionals, include markers like #objc(Class) etc.
Therefore, I have changed my workflow: I am now including the custom methods in the generated file without using an extension (which is the Swift equivalent of a category). When updating my managed object model, I just change the the file marginally by adding, renaming, changing etc. the attributes and relationships.
I'm trying to implement a custom getter and setter for my custom object HFObject and my app crashed with a Message sent to deallocated instance error despite using ARC.
I've read every single related post, the ones that were written pre-ARC don't apply, and everything else didn't help. I have the zombie object debugger option turned on.
Setting up the custom HObject
Within HObject.h I have declared these four properties:
#property (retain) NSString *email; //Will use custom getter/setter
#property (retain) NSString *firstName; //Will use custom getter/setter
#property (retain) NSDate *date; //Will use custom getter/setter
#property (nonatomic, retain) NSMutableDictionary *values;
In the implementation of HObject, I have removed the automatic getting and setting of email. firstName, and date by utilizing #dynamic like so
#dynamic email;
#dynamic firstName;
#dynamic date;
I also allocate the values dictionary in my HObject init
- (id)init {
self = [super init];
if (self) {
self.values = [NSMutableDictionary dictionary];
}
return self;
}
Implementing Custom Getter & Sender
For my custom getter/setter. I have overridden
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
and
- (void)forwardInvocation:(NSInvocation *)invocation
As shown below:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:#"set"].location == 0) {
return [NSMethodSignature signatureWithObjCTypes:"v#:#"];
} else {
return [NSMethodSignature signatureWithObjCTypes:"##:"];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:#"set"].location == 0) {
key = [key substringWithRange:NSMakeRange(3, [key length]-4)];
id obj;
[invocation getArgument:&obj atIndex:2];
[self.values setObject:obj forKey:key];
} else {
id obj = [self.values objectForKey:key];
[invocation setReturnValue:&obj];
}
}
What I'm trying to do here is store all of the values of the property into my values dictionary and retrieve them from there as well.
App Crashing
In the implementation of my view controller, I try to create a HObject and set values for my properties, and then I log the values dictionary to see all of my values.
- (void)buttonPressed:(id)sender {
HObject *obj = [[HObject alloc] init];
NSString *name = #"this is a string object";
obj.date = [NSDate date];
obj.email = #"email#website.com";
obj.firstName = [NSString stringWithFormat:#"%#", name];
NSLog(#"Values %#", [obj values]);
}
At that point the app crashes and this is my console log
2014-07-27 04:12:37.899 App[61501:60b] Values {
Date = "2014-07-27 08:12:37 +0000";
Email = "email#website.com";
FirstName = "this is a string object";
}
2014-07-27 04:12:37.901 HeadsUp[61501:60b] *** -[CFString release]: message sent to deallocated instance 0x109473fe0
If you can help me from here, I would greatly appreciate it. I am also including my debugging process in case that will help you
My debugging Process (Long, skip if you can already help me)
I originally created many of these objects and stored them in an array, and when I do that, as opposed to creating a single object. my app crashed a bit different.
My array:
#property (nonatomic, strong) NSArray *array;
Methods:
- (void)createArray
{
int i = 1; //number of testobjs
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:i];
for (int j = 0; j<i; j++) {
HFObject *obj = [[User alloc] init];
NSString *name = #"this is a string object";
[obj setObject:[NSDate date] forKey:#"Date"];
obj.email = #"email#website.com";
obj.firstName = [NSString stringWithFormat:#"%#", name];
[objects addObject:obj];
}
self.array = [NSArray arrayWithArray:objects];
}
- (void)buttonPressed:(id)sender {
HObject *object = [self.array objectAtIndex:0];
NSLog(#"Values %#", [object values]);
}
Crash log:
2014-07-27 04:34:02.893 App[61623:60b] *** -[CFString isNSString__]: message sent to deallocated instance 0x1094988f0
(lldb)
Now this crash log is almost the same as the one before, except this didn't log the values inside of [object values]
Investigating the issue a bit, I looked at the left window (not sure what it is actually called) of the debugger and I saw this:
(Treat HFObject as HObject and dirtyValues as values; I renamed them for presentational purposes)
You can see that under the key #"FirstName" there is no value.
I did several similar tests where I changed the values of the properties I was setting and changed the data types. More often than not, not only did FirstName not have a value, neither did Date. However, the value of email was always present.
After researching about dataTypes, I realized it was because email was a string literal which can't deallocated. On the other hand firstName and date were objects, which can be deallocated.
The crash log refers to a CFString property, which I learned doesn't use ARC. I never created a Core Foundation object, so I set out to found out it was being created in setter by logging the [obj class]:
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:#"set"].location == 0) {
key = [key substringWithRange:NSMakeRange(3, [key length]-4)];
id obj;
[invocation getArgument:&obj atIndex:2];
NSLog(#"%#", [obj class]); //I ADDED THIS LOG
[self.values setObject:obj forKey:key];
} else {
id obj = [self.values objectForKey:key];
[invocation setReturnValue:&obj];
}
}
After crashing one more time, I got the obj classes
2014-07-27 04:58:03.893 HeadsUp[61765:60b] __NSDate
2014-07-27 04:58:03.894 HeadsUp[61765:60b] __NSCFConstantString
2014-07-27 04:58:03.894 HeadsUp[61765:60b] __NSCFString
2014-07-27 04:58:03.904 HeadsUp[61765:60b] *** -[__NSDate release]: message sent to deallocated instance 0x109554370
(lldb)
Here you can see Date is being deallocated for some reason, and my string are now __NSCF strings.
I tried resetting the strings to NSStrings using (__brigde NSString *)obj and every other possible way you can bridge a CF object to ARC, however that didn't work either.
Here is everything I've done. I appreciate any and all help.
The problem is here:
id obj;
[invocation getArgument:&obj atIndex:2];
getArgument simply copies the object pointer into obj without retaining it.
However, since obj is (by default) a __strong variable, it will be released at
the end of the current method. To solve the problem, use
__unsafe_unretained id obj;
[invocation getArgument:&obj atIndex:2];
Note also that your getter implementation does not work. For example, setFirstName:
stores the key in the dictionary using the key "FirstName", but the getter firstName
tries to read the value for the key "firstName".
(As already mentioned in a comment, it would probably easier and less error-prone
to just override the accessor methods for the three properties separately, instead
of dynamic forwarding.)
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.
I have an entity in my core data model like this:
#interface Selection : NSManagedObject
#property (nonatomic, retain) NSString * book_id;
#property (nonatomic, retain) NSString * contenu;
#property (nonatomic, retain) NSNumber * page_id;
#property (nonatomic, retain) NSNumber * nbrOfOccurences;
#property (nonatomic, retain) NSString * next;
#property (nonatomic, retain) NSString * previous;
I have created many Selections and saved them in Core Data and now I would like to delete some selections with some criteria. For example, I would like to delete a Selection object if matches the following:
content = test
page_id = 5
book_id = 1331313
How I can do this?
What Mike Weller wrote is right. I'll expand the answer a little bit.
First you need to create a NSFetchRequest like the following:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Selection" inManagedObjectContext:context]];
Then you have to set the predicate for that request like the following:
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"content == %# AND page_id == %# AND book_id == %#", contentVal, pageVal, bookVal]];
where
NSString* contentVal = #"test";
NSNumber* pageVal = [NSNumber numberWithInt:5];
NSString* bookVal = #"1331313";
I'm using %# since I'm supposing you are using objects and not scalar values.
Now you perform a fetch in the context with the previous request:
NSError* error = nil;
NSArray* results = [context executeFetchRequest:fetchRequest error:&error];
results contains all the managed objects that match that predicate.
Finally you could grab the objects and call a deletion on them.
[context deleteObject:currentObj];
Once done you need to save the context as per the documentation.
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
Hence
NSError* error = nil;
[context save:&error];
Note that save method returns a bool value. So you can use an approach like the following or display an alert to the user. Source NSManagedObjectContext save error.
NSError *error = nil;
if ([context save:&error] == NO) {
NSAssert(NO, #"Save should not fail\n%#", [error localizedDescription]);
abort();
}
You should perform a fetch request using an NSPredicate with the appropriate conditions, and then call the deleteObject: method on NSManagedObjectContext with each object in the result set.
In addition to Mike Weller and flexaddicted, after calling [context deleteObject:currentObj]; you need to save: context:
NSError *error = nil;
[context save:&error];
As from documentation:
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
That made matter in my case.