One of my NSManagedObject's attribute depend on various attributes, some of them in a related NSManagedObject.
In a first run, I implemented a simple Transient Attribute for such attribute, but I just discover that it's not possible to use fetch predicate with transient properties.
I need to create an attribute so that:
Its value it's calculated using different attributes
1 of the dependent attribute is present in a related NSManagedObject
I can do fetch using this attribute as predicate.
If I update one of the dependent attributes, the calculated value must be updated
You can use Key value observing to monitor a property being changed in order to keep some calculated property up to date. You can add an observer to the property you want to monitor.
KVO Programming Guide.
edit: Reference here
To do this correctly, you'll want to override the property getter for your calculated property, and create a keyPathsFOrValuesAffecting<Key> function.
Apple's example is pretty good, it gives a case where a fullName property should be gathered from the firstName and lastName.
So you would need to implement KVO with this function:
+ (NSSet *)keyPathsForValuesAffectingFullName
{
return [NSSet setWithObjects:#"lastName", #"firstName", nil];
}
This will allow your app to be notified anytime those values are modified. Then you'd just override the getter that would be called upon this notification
- (NSString *)fullName
{
return [NSString stringWithFormat:#"%# %#",firstName, lastName];
}
Related
Let's say I have a class Car, with the following property scheme:
#interface Car : NSObject
#property int carID;
#property Driver *driver;
#property NSString *brandName;
...
#end
Now, I have a dictionary of Car instances, and whenever I download a new instance from my server, I overwrite with carDictionary[#(newCar.carID)] = newCar; to keep my instances updated. The only issue is that sometimes my instances are downloaded with some properties not filled out.
When I download a new instance, is there a way to only overwrite using the non-nil properties I download and leave the nil properties alone? Obviously, I could manually set each property, but I don't want to have to change this every time I add a new property.
There's no built-in way in Objective-C to do this. That said, I think this is how I would approach it.
I would start by adding a method on Car which updated it with the new information:
-(void)updateWithNewCarInfo(Car *)newCar
The straightforward way is to just manually copy the properties yourself. But if you want to do a bit of future-proofing, you can use Objective-C's introspection capabilities to get a list of the properties on your class and iterate through them yourself. This question has some pointers on how to do that. You'll need to implement your own logic as to which properties to overwrite when, though.
Since there is already a Car object in carDictionary at this spot, instead of wantonly replacing it, write a Car method updateWithValuesFromCar: that takes another Car and does what you want done, and call that.
I would start with an array of property names #["carID", "driver", "brandName"] (and so on) and cycle through that, using key-value coding to check each incoming property value and decide whether to replace my own corresponding property value.
Let's say you get a dictionary as a result of your web service call. Now you want to use that dictionary to instantiate or update a Car object, where an update does not overwrite existing valid data with nil data from the dictionary.
Assume the property names in your class correspond to the key names in your dictionary.
Here's an approach:
Get the existing Car object for the current dictionary from the carDictionary:
Car *currentCar = carDictionary[#(newCar.carID)];
If no car is returned, instantiate a new one:
if (currentCar == nil) {
currentCar = [[Car alloc] init];
}
Assign all the values from your downloaded dictionary to the car instance:
[currentCar setValuesForKeysWithDictionary:yourDownloadedDictionary];
That method will map the keys from the dictionary with the keys in your Car instance. The dictionary keys need to match the keys in your class, and often it's worth just using the same names so you get to use this convenient method.
It will throw an exception if there is a new key added to your web service dictionary that does not correlate to a key/property in your class. This would break your app if your web service started giving out new keys while some users had outdated versions of your app. To solve that..
Override - (void)setValue:(id)value forUndefinedKey:(NSString *)key; so it does nothing, other than perhaps log out the fact that a key was attempted to be set but your class doesn't implement that key/property.
Then you have the problem of "Don't set nil values". You can tell your class not to overwrite existing content with nil values by...
Override -(void)setValue:forKey:
-(void)setValue:(id)value forKey:(NSString *)key {
if (value != nil) {
[super setValue:value forKey:key];
} else {
NSLog(#"Not changing %# because its value is nil.", key);
}
}
It's a bit more work in setting up the class, but it gives you future proofing for when your web service expands its returned attribute set.
I'm working with a core data entity that has four date attributes - syncDate, historicSyncDate, etc.
Is there a way I can write a single method that would take a NSString name of one of these attributes and assign a proper date for it?
Ex:
-(void)updateDate:(NSDate*) date forAttribute:(NSString*)attribute forService:(Service*)service
{
//based on attribute name, set service.syncDate or service.historicSyncDate, etc
}
Sure:
[service setValue:date forKey:attribute];
From the "NSManagedObject Class Reference":
setValue:forKey:
Sets the specified property of the receiver to the
specified value.
...
If key identifies a
to-one relationship, relates the object specified by value to the
receiver, unrelating the previously related object if there was one.
Given a collection object and a key that identifies a to-many
relationship, relates the objects contained in the collection to the
receiver, unrelating previously related objects if there were any.
This method is overridden by NSManagedObject to access the managed
object’s generic dictionary storage unless the receiver’s class
explicitly provides key-value coding compliant accessor methods for
key.
You should be able to do...
-(void)updateDate:(NSDate*) date forAttribute:(NSString*)attribute forService:(Service*)service
{
[service setValue:date forKey:attribute];
}
Assuming Service is a sub-class of NSManagedObject
I have a simple Core Data iPhone app where Parents have a one-to-many relationship with Children. In the first view, you are presented with a list of Parents; tapping on one provides the corresponding list of Children. In the Child view, I want the each cell to show the Child name, and then the Parent name as a subtitle. I have used the following command:
cell.detailTextLabel.text = [[managedObject valueForKey:#"Parent"] description];
But instead of getting the parent name, the subtitle displays something like:
<Parent: 0x4d5a520> (entity: Parent; id...
Obviously I'm printing out the actual relationship, rather than the object's name. How can I show the actual Parent name ("Mr Smith")?
Thanks.
I managed to work it out! I'm gradually getting the hang of this programming thing... :-)
I'll leave the answer here in case someone else searches for it one day...
Instead of using a generic NSManagedObjectContext with KVC, I used my own subclass:
Child *child = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [child.name description];
cell.detailTextLabel.text = [child.parent.name description];
Your basic problem is that description is a method of NSObject that returns a description useful for programmers. If you have an attribute called description then it will collide with the built-in description method. You may have been confused because when you call description on a NSString, you get the string but if you call any other class, you don't get the literal data e.g. When you call description on a managed object you get the object UUID, whether it is a fault or not, it's attributes and the objects it's related to. All that is useless for anything but debugging.
Never use description as an attribute name and never use the return of description for anything the user ever sees.
If you have a tableview of Child objects as described above then your fetched results controller will return a managed object configured to the Child entity. To access the name of the related Parent object you would use:
cell.detailTextLabel.text = [[childMo valueForKey:#"parent"] valueForKey:#"name"];
Of course, if either the relationship or the parent.name attribute is optional, you should first check that either has a value before attempting to use the value.
I have an NSManagedObject derived class OrderMO that has a property OrderDate of type NSDate. I would like to have the default value for OrderDate be the first Monday of the month for the first month that does not already have an order associated with it.
To do this I am overriding the awakeFromInsert function of NSManagedObject and setting the value of OrderDate like this:
- (void) awakeFromInsert
{
[super awakeFromInsert];
[self setValue:[self getNextOrderDate] forKey:#"OrderDate"];
}
The custom function getNextOrderDate calculates the appropriate date and returns it. The problem is that getNextOrderDate calls executeFetchRequest which is apparently a no-no during the awakeFromInsert call. I've tried doing what is described in this article, but it did not solve my problem. In that article the problem is solved by calling self performSelector to delay the call to executeFetchRequest until after the NSManagedObject is initialized. In my case I just get 2 garbage rows.
I considered sub-classing my Orders ArrayController to override the add function, but don't really know what to do there either.
Can someone please tell me the proper way to initialize an NSManagedObject property based on existing NSManagedObjects of the same entity type, in the same array with a value that depends on the existing objects' values?
Well, one method would be to do a fetch for specific value. That returns just a dictionary and I'm pretty sure it won't trigger all the notifications associated with fetching faults or objects. You should be able to extract just the one date you want without setting off all the bells and whistles. However, I've never tested that in this particular circumstance.
Another approach would be to create the managedObject directly i.e. just like a non-managedObject, populate the attribute as you want and only then insert the managedObject into the context. Not very elegant but it will work and it won't set off a bunch of notifications.
Some Core Data hands write their own insertion class methods for their NSManagedObject subclass. You might want to employ that here. E.g.
+(OrderMO *) insertOrderMOIntoManagedObjectContext:(NSManagedObjectContext *) aContext{
OrderMO *newOrder=[[OrderMO alloc] init];
// pefrom fetch on aContext to find the next order date
newOrder.orderDate=//... fetched order date
[aContext insertObject:newOrder];
}
In my quest to update a Core Data model within my iOS project, I'm querying a server for JSON objects that correspond - to some extent - with the managed entities of my model. The end result I'm striving for is a reliable update solution from JSON output.
For the examples in this question, I'll name the core data managed object existingObj and the incoming JSON deserialized dictionary updateDict. The tricky part is dealing with these facts:
Not all properties of the existingObj are present in the updateDict
Not all properties of the updateDict are available in the extistingObj.
Not all types of existingObj's properties match the JSON deserialized properties. (some strings may need a custom Objective-C wrapper).
updateDict may contain values for keys that are uninitialized (nil) in existingObj.
This means that while iterating through the updated dictionaries, there has to be some testing of properties back and forth. First I have to test whether the properties of the updateDict exist in existingObj, then I set the value using KVC, like so:
// key is an NSString, e.g. #"displayName"
if ([existingObj respondsToSelector:NSSelectorFromString(key)) {
[existingObj setValue:[updateDict objectForKey:key] forKey:key];
}
Although this part works, I don't like the fact that I'm actually testing for displayName as a getter, while I'm about to call the setDisplayName: setter (indirectly via KVC). What I'd rather to is something like [existingObj hasWritablePropertyWithName:key], but something that does this I can't find.
This makes for subquestion A: How does one test for a property setter, if you only have the property's name?
The next part is where I'd like to automate the property identification based on their types. If both the updateDict and the existingObj have an NSString for key #"displayName", setting the new value is easy. However, if the updateDict contains an NSString for key #"color" that is #"niceShadeOfGreen", I'd like to transform this into the right UIColor instance. But how do I test the type of the receiving property in existingObj so I know when to convert values and when to simply assign? I was hoping for something along the lines of typeOfSelector:
if ([existingObj typeOfSelector:sel] == [[updateDict objectForKey:key] class]) {
// regular assignment
} else {
// perform custom assignment
}
Of course this is boguscode. I can't rely on testing the type of the existingObj-property's value, for it may be unitialized or nil.
Subquestion B: How does one test for the type of a property, if you only have the property's name?
I guess that's it. I figured this must be a dupe of something that's already on here, but I couldn't find it. Maybe you guys can?
Cheers, EP.
P.S. If you'd have a better way to synchronize custom Objective-C objects to deserialized JSON objects, please do share! In the end, the result is what counts.
If you want to query whether an object has a setter for a given KVC key called key which corresponds to a declared property, you need to check whether it responds to a selector method called setKey: (starts with set, capitalise the first character in key, add a trailing colon). For instance,
NSString *key = #"displayName";
NSString *setterStr = [NSString stringWithFormat:#"set%#%#:",
[[key substringToIndex:1] capitalizedString],
[key substringFromIndex:1]];
if ([obj respondsToSelector:NSSelectorFromString(setterStr)]) {
NSLog(#"found the setter!");
[obj setValue:someValue forKey:key];
}
Two remarks:
Even though properties can have setters with names that do not follow the pattern described above, they wouldn’t be KVC compliant, so it is safe to check for set<Key>: since you’re using KVC to set the corresponding value.
KVC doesn’t use the setter method only. If it doesn’t find a setter method, it checks whether the class allows direct access to instance variables and, if so, use the instance variable to set the value. Also, if no setter method or instance variable is found, it sends -setValue:forUndefinedKey: to the receiver, whose class might have overridden the standard implementation that throws an exception. This is described in the Key-Value Coding Programming Guide.That said, if you’re always using properties, checking for the setter method should be safe.
As for your second question, it is not possible to query the runtime to know the actual Objective-C class of a property. From the runtime perspective, there’s an implementation specific type encoding for properties and general types (such as method parameters/return types). This type encoding uses a single encoding (namely #) for any Objective-C object, so the type encoding of an NSString property is the same as the type encoding of a UIColor property since they’re both Objective-C classes.
If you do need this functionality, one alternative is to process your classes and add a class method that returns a dictionary with keys and corresponding types for every property (or the ones you’re interested in) declared in that class and superclasses, or maybe some sort of description language. You’d have to do this on your own and rely on information not available during runtime.