I used KVC until now to access object's properties.
In my object i have a method like this:
-(Address *)mainAddress {
if (self.addresses != nil) {
return [self.addresses anyObject]; //stub method
}
else {
return nil;
}
}
I can use this method with KVC using
mystring = [cliente valueForKeyPath:#"mainAddress.city"];
but i cannot use to create a NSFetchRequestController (this code use MagicalRecord)
NSFetchedResultsController *acontroller = [Customer fetchAllSortedBy:#"mainAddress.city" ascending:ascending withPredicate:companyPredicate groupBy:nil delegate:self];
This is the error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath mainAddress.city not found in entity <NSSQLEntity Customer id=4>'
In order to use the sorting with an NSFetchedResultsController, your mainAddress keyPath needs to be an attribute on your entity. NSFRC will sort the data not using KVC in memory, but using the underlying data store. Bottom line answer: make mainAddress a field on your entity in the data model.
Related
I have a mutable array property declared and synthesized:
#property (nonatomic, strong) NSMutableArray *arrayOfTasks;
I am using KVC collection Accessors for the same property and also I have other methods which will internally call this KVC Collection accessor method like this:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
I had to do some modifications and add the object into the array only when a particular condition is satisfied, so to make sure that this check goes into the designated method, I included the following in the -insertObject KVC Collection accessor method:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
if ([inTask isOperatable])
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
Now each time when I trigger -addObjectINArrayOfTasks method and if the -isOperatable condition returns boolean NO, the app crashes with no stack trace at all! (Stack trace is at main() of the application). All it says is "index 0 beyond bounds for empty array error".
I am not understanding the reason for this, I am not trying to access the array yet, so I am not giving a chance for framework to complain me that there is no element at index 0. Moreover, I am doing the count of array items check everywhere before accessing the objects out of array. For, if I was trying to access and element out of the bounds index, the app would crash at the same point and let me know exactly where I was trying to access the index out of bounds. That would have been a simple straightforward fix.
Now, to just cross verify, I made a small change in the code like this, which seems to work:
-(void)insertObject:(CSTaskAbstract *)inTask inArrayOfTasksAtIndex:(NSUInteger)index
{
[[self arrayOfTasks] insertObject:inTask
atIndex:index];
}
-(void)addObjectInArrayOfTasks:(CSTaskAbstract *)inTask
{
if ([inTask isOperatable])
{
[self insertObject:inTask
inArrayOfTasksAtIndex:[[self arrayOfTasks] count]];
}
}
I can go ahead with this approach which is working and does not crash, but my concerns are the following:
Adding the same check in designated method would be an added advantage in future if some other programmer would want to invoke the designated method from somewhere else.
Why would the app crash in first case when I wont insert the object into the array in KVC collection accessors based on some condition check?
Thanks for any inputs,
Raj
I think the crash you are seeing is more likely related to internal KVC behavior than your array. That might be the reason you don't see a usable stack trace. Have you enabled the exception breakpoint in Xcode?
KVC basically expects that -insertObject:in<Key>AtIndex: will insert a new object at the given index (presumably 0 in your case). Since it assumes that the object was inserted it should now be accessible by queuing the data structure (NSMutableArray) for the object at the given index. When the condition evolves to NO, you fail to insert this object, which means that an index out of bounds exception is possible when KVO tries to query using the provided index.
The second code snipped you posted avoids this error by not calling the KVC collection accessor when an insertion is not needed.
If you want to minimize the chance of someone incorrectly using those methods, expose just -addObjectInArrayOfTasks: in your public header. In addition you can document this. If you want to make it absolutely certain that -insertObject:in<Key>AtIndex: can't be accessed on int's own, you can add an NSAssert, that checks if the method was called from -addObjectInArrayOfTasks:.
I have a workaround for my problem, but I really want to understand why I'm having this problem and how to solve it.
I have an entity A related to another entity B, and some of the rows of A have a special mark in one field.
I want to count how many of those A that are related to B, have this special mark.
All works perfectly, if after I create the NSSet I count the set:
NSSet *productsSet = currentList.ingredients;
int countProducts = productsSet.count;
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"self IN %# AND isInList = %d", productsSet,1];
[fetchRequest setPredicate:predicate];
int totalProductsInList = [context countForFetchRequest:fetchRequest error:error];
If I comment the
int countProducts = productsSet.count;
I have those errors:
-[NSNull unsignedIntValue]: unrecognized selector sent to instance 0x22b9cd8
2011-12-05 12:10:41.418 xxxxxxxxxx[32964:1bb03] *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[NSNull unsignedIntValue]: unrecognized selector sent to instance 0x22b9cd8'
isInList is a Int16
thanks,
EDIT:
If I move the 1 inside the NSPredicate, without a count, I get the same error:
[NSPredicate predicateWithFormat: #"self IN %# AND isInList = 1", productsSet;
EDIT 2:
As I can't find why it doesn't work, I check for productsSet.count and if it's greater than 0 I make the NSFetchrequest, problem solved.
Well, after creating the NSSet I check for nsset.count, and in case it's greater than zero I issue the NSFetchrequest, in case it's zero, don't.
Problem solved, but I still have the curiosity for this strange error that I have if I don't use the nsset.count.
thanks,
NSSet object returned by relationship is not an ordinary NSSet but an object of _NSFaultingMutableSet class. And it seems it's not safe to use it in NSPredicate. May be it turns into fault state or something. The reason of the problem looks the same as in Core Data/NSOperation: crash while enumerating through and deleting objects.
So the best way is to use result of allObjects or create a new NSSet object:
NSArray *productsArray = [currentList.ingredients allObjects];
or
NSSet *productsSet = [NSSet setWithSet:currentList.ingredients];
I have a Uitableview that loads data from a parsed xml feed. when the feed is first parsed all the text data is stored in an entity NewsItems in core data. after the table is loaded the images asociated with each object are fetched asynchronously and stored in a separate entity NewsImages, after the feeds/images are stored locally all data is fetched locally next time the app starts. NewsItems and NewsImages have a one to one relationship with each other.
I have a refresh button which when clicked deletes all entries in NewsItems, this will also delete all objects in NewsImages associated with objects in NewsItems aswell, since the relationship delete rules are cascade. After deleteion, the feed is parsed again and data is stored locally again.
My problem is when I do this multiple number of times quickly. I get this error while saving Images locally.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-one relationship: property = "ItemImage"; desired type = NewsImages; given type = NewsImages; value = <NewsImages: 0x68c49f0> (entity: NewsImages; id: 0x6804730 <x-coredata:///NewsImages/t5444BEE7-6193-4C25-8AAB-F64113BEAB7546> ; data: {
Image = <ffd8ffe0 00104a46 49460001 01000001 00010000 ffe10058 45786966 00004d4d 002a0000 00080002 01120003 00000001 0001>;
ImageItem = nil;
}).'
This is the function responsible for inserting images
-(void)setImage:(UIImage*)moImage ForObject:(NSManagedObjectID*)moID{
NewsItems *newsItem = (NewsItems*)[self.managedObjectContext objectWithID:moID];
NewsImages *newsImage = (NewsImages*)[NSEntityDescription insertNewObjectForEntityForName:#"NewsImages" inManagedObjectContext:self.managedObjectContext];
newsImage.Image = UIImageJPEGRepresentation(moImage,1.0);
newsItem.ItemImage = newsImage;
[self commitSave];
}
I think the most likely problem is that you are passing the objectID for a NewsImages object instead of a for NewsItems object in setImage:ForObject: and in this line:
NewsItems *newsItem = (NewsItems*)[self.managedObjectContext objectWithID:moID];
...you are actually getting a NewsImages object returned cast as a NewsItems.
Cast can be problematical in Objective-C because an object will not complain at return if you send it a message it does not understand. The compiler won't catch the error because of the cast.
I would remove the cast, set the return item to id like so:
id newsItem = (NewsItems*)[self.managedObjectContext objectWithID:moID];
... then log its class:
NSLog(#"class=%#",[newItem class]);
... or check it in the debugger. Since you seem to be using custom NSManagedObject subclasses this should confirm the source of the error.
I had the same problem.
I fixed it by providing a class in the entity editor for the derived class.
The following setup was producing the error you've described:
Entity name : Contact <-------- User
ObjC Class : Contact Default to NSManagedObject.
The following setup fixed it:
Entity name : Contact <-------- User
ObjC Class : Contact Contact
I'm facing some difficulty in retrieving properties of "id" type object. This is how I'm accessing it:
I'm doing following to assign an object to id type object from a generic array containing different types of objects and calling method "savedata" to which I'm passing the object as well as its type:
for(id objInArray in genericArray){
NSString *objType = [objInArray valueForKey:#"type"];
[objInArray retain];
[self saveData:objInArray :objType];
}
In savedata method I'm writing following code to retrieve the properties of id object:
-(void)saveData:(id)object :(NSString *)objectType
{
self.managedObjectContext = appDelegate.managedObjectContext;
if([objectType isEqualToString:#"event"])
{
Event * newEvent = (Event *)[NSEntityDescription
insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
[newEvent setEletitle:[NSString stringWithFormat:#"%#", [object valueForKey:#"eletitle"]]];
[self saveAction];
}
But the object "object" containing the values fails to assign them to object newEvent.
I also tried to retrive this value in a string object like this:
NSString *eletit = [object valueForKey:#"eletitle"];
[eletit retain];
But eletit is also invalid at the end of this transaction.
Can anybody please help? This' really urgent.
Thanx in advance.
I don't have you answer unfortunately but I have few comments on your code.
Are you sure it's normal you array contain so generics object? It's strange because all your object contained in your array need to respond to "type" or "eletitle" messages, so I guess objInArray is less generic than just "id".
Second, it's not recommended to have selector like saveData::, in Objective-C it's usual and recommended to name the arguments, it's more understandable.
I did some little experimentation on this.
I created an boolean attribute, named isFault. You know, that's a method of NSManagedObject and therefore actually not allowed for an attribute name because of KVC.
Simply, I used the default Core Data template for this test, but created the data model programmatically so I can show you what I do.
So here we go:
NSAttributeDescription *badAttr = [[NSAttributeDescription alloc] init];
[badAttr setName:#"isFault"];
[badAttr setAttributeType:NSBooleanAttributeType];
[badAttr setOptional:YES];
// don't want to occupy you with the whole, non-important rest ...
Next, I've modified the -insertNewObject method of the controller, added these lines:
// Assume: An managed object is created into the context, but not saved yet...
NSLog(#"isFault = %d", [newManagedObject isFault]); // 0 = NO
BOOL isFault = [[newManagedObject valueForKey:#"isFault"] boolValue];
NSLog(#"isFault = %d", isFault); // 0 = NO
[newManagedObject setValue:[NSNumber numberWithBool:YES] forKey:#"isFault"];
isFault = [[newManagedObject valueForKey:#"isFault"] boolValue];
NSLog(#"isFault = %d", isFault); // 0 = NO
Like you can see, I'm not able to set the isFault attribute to YES. It remains NO. Now, changing the attribute name to isFaultXYZ, will allow that.
So actually, what I wanted to ask is... since this stuff seems to depend on KVC, does the rule only apply to methods that return something and have no parameter? And does it matter what data type is returned? For example, -changedValues has no parameter and returns an NSDictionary. But since there is no attribute type like that, would this cause a collision anyways?
So, here is the evidence. When naming the attribute changedValues, THIS happens after attempting to access it via KVC:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFDictionary boolValue]: unrecognized selector sent to instance 0x1205680'
2010-06-10 14:52:40.232 CoreData[33996:207] Stack: (
8035,
4437,
8825,