Objective-C Overwrite only non-nil properties - objective-c

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.

Related

Objective-C - aligning class properties with json dictionary data

I'm relatively new to Objective-C (mainly using cocoa/apple foundation framework), but a long-time C++ programmer, so I'll start by explaining what I'm trying to accomplish; It's quite possible my approach isn't the best one, so I'd love to hear any suggestions for a different approach.
I've got a text file in json format, just made this quick example:
"section1" : {
"director" : "Sample Name 1",
"writers" : {
"name" : "Example Name 1",
"name" : "Example Name 2",
},
},
And I've got a class with properties which I want to match with the data inside this file, since I'm planning to parse this file and store some of the values in an instance of this class. My class header would look roughly like this:
#interface SongData : NSObject
#property (nonatomic, copy) NSString *director;
#property (nonatomic, copy) NSArray *writers;
#end
So I'm trying to find the cleanest, somewhat "data-driven" way to store this json data into the correct properties. Basically I want a way to loop through the json "dictionary" of data, and somehow use the "keys" to match with the correct property in my class and assign the value to that property. My first crack at it, I created a dictionary where the "key" is the actual key to lookup the proper data inside the json data, and the "value" would be a pointer to the correct class property. Sort of like this:
NSDictionary *descriptionDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
director, #"name",
writers, #"writers",
nil];
Then the idea was to loop through the json data (which is stored in another NSDictionary), use each key from my "descriptionDictionary" to lookup the appropriate value in the json data, then use the matching "value" of the description dictionary based on the same key (which I was hoping is sort of a pointer to the actual class property?) and set that properties value to the value from the json data. Perhaps an example will make it a bit more clear :-). Lets assume "jsonDictionary" is the result of parsing my json data, and I'm already inside section1, so there should be a 1-1 match between keys that make up the jsonData NSDictionary and the keys that make up my descriptionDictionary NSDictionary:
[descriptionDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
value = [jsonDictionary objectForKey:propertyName];
}];
Now "value" would technically be pointing to one of my classes properties, and I want to assign relevant json data to it (ie director = #"Sample Name 1"). I know there's problems with what I'm doing (I realize that I probably can't just dereference a pointer to a random class property and assign an arbitrary object of unknown type to it, hoping it all matches up :-) but hoping my example at least illustrates what I'm trying to do so someone can tell me a better way.
Have a look at KVC (source: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/BasicPrinciples.html)
Setting Attribute Values Using Key-Value Coding
The method setValue:forKey: sets the value of the specified key,
relative to the receiver, to the provided value. The default
implementation of setValue:forKey: automatically unwraps NSValue
objects that represent scalars and structs and assigns them to the
property. See “Scalar and Structure Support” for details on the
wrapping and unwrapping semantics.
If the specified key does not exist, the receiver is sent a
setValue:forUndefinedKey: message. The default implementation of
setValue:forUndefinedKey: raises an NSUndefinedKeyException; however,
subclasses can override this method to handle the request in a custom
manner.
The method setValue:forKeyPath: behaves in a similar fashion, but it
is able to handle a key path as well as a single key.
Finally, setValuesForKeysWithDictionary: sets the properties of the
receiver with the values in the specified dictionary, using the
dictionary keys to identify the properties. The default implementation
invokes setValue:forKey: for each key-value pair, substituting nil for
NSNull objects as required.
One additional issue that you should consider is what happens when an
attempt is made to set a non-object property to a nil value. In this
case, the receiver sends itself a setNilValueForKey: message. The
default implementation of setNilValueForKey: raises an
NSInvalidArgumentException. Your application can override this method
to substitute a default value or a marker value, and then invoke
setValue:forKey: with the new value.
As long as your class properties have the same names as the JSON fields you can use the setValuesForKeysWithDictionary: and pass in the JSON root dictionary.
For keys/properties that are named differently you can simply override the setValue:forUndefinedKey: and set the appropriate property yourself.
Finally there is the case of a value being represented by a different type in the JSON than in the property. For example NSURL would be an NSString instead. Here you can simply check the class of the passed parameter and if it does not match the IVAR, do a conversion.

Implementing a category within the implementation of another interface in Obj-C

I have a custom class in Obj-C called RouteManager which contains an array of NSStrings. Each string is a bus stop name which is used as a key for a dictionary to get the rest of the information for the bus stop (basically, just [busStopDictionary allkeys]). In one of the situations where my app uses this array, I want to return the array sorted by the distance from the user. I've started setting up the code to be able to call sortedArrayUsingSelector on my array with the following method:
- (NSComparisonResult)compareByDistance:(NSString*) otherStop
{
// Return appropriate NSOrdered enum here based on comparison of
// self and otherStop
}
My problem is that in the case where compareByDistance is a method of RouteManager, self refers to the instance of RouteManager. However, I need self to refer to the NSString that the compare is being called on. So, I assumed I needed to setup a category, as such:
#interface NSString (Support)
-(NSComparisonResult) compareByDistance:(NSString*)otherStop;
#end
This got my self reference correct, however this comparison uses values from the RouteManager class. When implemented as seen above, the NSString (Support) implementation obviously complains that those values are undeclared.
That should provide enough background info for my question. How do I go about doing this? I would like my category of NSString, which consists solely of the method compareByDistance, to be able to use values from the current instance of my class, RouteManager, which inherits from NSObject. Ideally, I feel as though the category should somehow be within RouteManager. I feel there has to be some way to accomplish this that is cleaner than passing the necessary values into compareByDistance. Thanks in advance for any and all help.
Your best bet would be to define a custom class for a bus stop, instead of storing them as strings and dictionaries.
Make the BusStop class have properties for Name, Location and whatever else. Implement the compareByDistance: method on the BusStop class.
You can still use a dictionary if you need to look them up by name. Just store them with the name as the dictionary's key, and the BusStop object as the dictionary's value.

How to test property existence and type based on NSString typed key?

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.

What is the difference between valueforKey:, objectForKey:, and valueForKeyPath:?

I have 2 questions:
What is the difference between valueForKey: and objectForKey:? Is it that one is for NSDictionarys (objectForKey:) and for others it is valueforKey:, or is it the reverse?
Also what is the difference between valueForKey: and valueForKeyPath:? Has it got something to do with Core Data?
Please help.
valueForKey: is part of the NSKeyValueCoding protocol and is therefore part of the key-value coding framework, which allows you to access class properties by name at runtime. That's how NIBs are loaded, for example — the names of properties associated with connections are loaded and then the values are set directly by name. This contrasts with the way that visual interface design tools often work in other languages, generating lots of hidden statically compiled code.
objectForKey: is defined on dictionaries only, and looks up an object by its key. An NSDictionary is a class that stores connections between values and keys.
So, valueForKey: could be used on an NSDictionary to return meta information about the dictionary, such as the count of objects inside it, the list of all keys, etc. objectForKey: would be used actually to look into the dictionary.
At runtime, the difference is that objectForKey: is a method with a completely opaque implementation. valueForKey: explicitly relies on subsequently calling named getters and setters. The reason for the latter is that you can extend key-value coding to key-value observing, where you ask to be informed every time a particular property on a particular object changes. At runtime that's achieved with a method swizzle, where the original setter is replaced by a new one that calls the previous setter and then sends out the required messages. Because all messages are dispatched dynamically, that's just achieved by modifying tables within the object instance.
So any object that is key-value coding compliant (which just means declaring and implementing your properties in the proper way, which the new-ish #property/#synthesize syntax does automatically) can be observed without the object itself having to implement any code.
There's further Apple stuff that uses key-value coding to achieve various things, including CoreData, but it's not specifically to enable any one other technology.
valueForKeyPath: is like valueForKey: except that it can traverse several objects. So you can have a root object with a bunch of properties, each of those properties is another object with another bunch of properties, etc, and using a key path you can retrieve a value way out at the leaf of that data structure rather than having to iterate through object after object for yourself.
In summary, valueForKey: and valueForKeyPath: provide information about object instances and interact with the dynamic nature of the Objective-C runtime. objectForKey: is a dictionary specific method that does dictionary tasks.
Additions:
An example, coded as I type and assuming that NSDictionary is key-value coding compliant:
NSDictionary *someDictionary;
// create someDictionary, populate it, for example (note: we assume photoOfKeys.jpg
// definitely exists, not a good idea for production code — if it doesn't we'll get
// a nil there and anything after it won't be added to the dictionary as it'll appear
// that we terminated the list):
someDictionary = #{ #"favouriteGarment": #"hat",
#"#allKeys" : [NSImage imageNamed:NSImageNameDotMac],
#(2) : NSArray.new };
NSObject *allKeys;
// we make no assumptions about which type #allKeys will be, but are going to assume
// we can NSLog it, so it needs to be a descendant of NSObject rather than 'id' so as
// to definitely respond to the 'description' message — actually this is just compile
// time semantics, but if someone else reads this code it'll make it obvious to them
// what we were thinking...
// some code to get all of the keys stored in the dictionary and print them out;
// should print an array containing the strings 'favouriteGarment', '#allKeys' and
// the number 2
allKeys = [someDictionary valueForKey:#"#allKeys"];
NSLog(#"%#", allKeys);
// some code to get the object named '#allKeys' from the dictionary; will print
// a description of the image created by loading photoOfKeys.jpg, above
allKeys = [someDictionary objectForKey:#"#allKeys"];
NSLog(#"%#", allKeys);
// `objectForKey is analogous to `objectForKeyedSubscript:`, aka
allKeys = someDictionary[#"#allKeys"];
allKeys is a property of NSDictionary as described here. I've also added a mapping from the NSString allKeys to a photograph of some keys. Whether I use the key-value coding valueForKey: methods or the NSDictionary objectForKey: lookup method dictates whether I read the property of the object instance or whether I send the object instance a message asking it to do its unique job.
objectForKey: is a method on NSDictionary for accessing the object associated with a key. valueForKey: is a method on NSObject for accessing any value associated with any object, through the name of a accessor method, property, and/or instance variable.
valueForKeyPath: can be seen as a shorthand for several calls to valueForKey:. You can think of it as sort of a xpath, if you will.
These two statements will result in the same output:
// Using nested valueForKey:
NSLog(#"%#", [[myObject valueForKey:#"foo"] valueForKey:#"bar"]);
// Can be done with a single valueForKeyPath;
NSLog(#"%#", [myObject valueForKeyPath:#"foo.bar"]);
valueForKey:and valueForKeyPath: are part of KVC (Key Value Coding). Introduction and in-depth documentation can be found here: http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/
valueForKey: and valueAtKeyPath: are methods defined in the NSKeyValueCoding informal protocol, and default implementations for both are provided by the root class NSObject.
objectForKey: is a method on NSDictionary.
valueForKey: takes a key to a property, while valueAtKeyPath: takes a so-called keypath. A keypath is a period-delimeted path to a specific property, like #"relationship.property".

Populating NSTableview from a mutable array

I've been attempting this for two days, and constantly running into dead ends.
I've been through Aaron Hillegass's Cocoa Programming for MAC OS X, and done all the relevant exercises dealing with NSTableview and mutable arrays, and I have been attempting to modify them to suit my needs.
However none of them seem to be using an array with objects as a data source, it seems to use the tableview as the datasource.
I'm trying to implement Jonas Jongejan's "reworking" of my code here, with a Cocoa front end to display the results.
Any pointers or suggestions I know this should be simple, but I'm lost in the wilderness here.
I can populate the table by setting the array
It's pretty simple really, once you get to understand it (of course!). You can't use an NSArray directly as a table source. You need to either create a custom object that implements NSTableViewDataSource or implement that protocol in some existing class - usually a controller. If you use Xcode to create a standard document based application, the document controller class - (it will be called MyDocument) is a good class to use.
You need to implement at least these two methods:
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
If you have a mutable array whose values you'd like to use in a table view with one column, something like the following should do as a start:
– numberOfRowsInTableView: (NSTableView*) aTableView
{
return [myMutableArray count];
}
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
return [myMutableArray objectAtIndex: rowIndex];
}
It has just occurred to me that you could add the above two methods as a category to NSArray replacing myMutableArray with self and then you can use an array as a data source.
Anyway, with a mutable array, it is important that any time you change it, you need to let the table view know it has been changed, so you need to send the table view -reloadData.
If your table view has more than one column and you want to populate it with properties of objects in your array, there's a trick you can do to make it easier for yourself. Let's say the objects in your array are instances of a class called Person with two methods defined:
-(NSString*) givenName;
-(NSString*) familyName;
and you want your table view to have a column for each of those, you can set the identifier property of each column to the name of the property in Person that that column displays and use something like the following:
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
Person* item = [myMutableArray objectAtIndex: rowIndex];
return [item valueForKey: [tableColumn identifier]];
}
If you replace valueForKey: with valueForKeyPath: and your Person class also has the following methods:
-(Person*) mother;
-(Person*) father;
-(NSString*) fullName; // concatenation of given name and family name
you can add table columns with identifiers like: father.fullName or mother.familyName and the values will be automatically populated.
You could go the datasource route and do all of the heavy lifting yourself, or you could let bindings do all the heavy lifting for you. Add an NSArrayController to the nib file that has the table view in it. Make sure that the File's Owner of the nib is set to the same class that has the mutable array in it. Bind the contentArray of the array controller to File's Owner.myMutableArray. For each column bind Value to the array controller arrangedObjects and add the appropriate key path. This will allow you to get things like user sorting for free if you ever need it.
On the iPhone (I know you're talking about Mac, but maybe this could help) you have to use delegation for loading a tableView. It asks for a cell and you use your array to fill-in the data where needed.
I'm not sure if this works for the Mac, but it'd be worth looking into.
Maybe set dataSource to self and use those delegate methods to access your array based on the row and column #
Apple has a whole guide for Table View Programming so I suggest you start with the Using a Table Data Source section of the that guide.