How to test property existence and type based on N

2020-07-14 09:35发布

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:

  1. Not all properties of the existingObj are present in the updateDict
  2. Not all properties of the updateDict are available in the extistingObj.
  3. Not all types of existingObj's properties match the JSON deserialized properties. (some strings may need a custom Objective-C wrapper).
  4. 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.

1条回答
成全新的幸福
2楼-- · 2020-07-14 10:14

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.

查看更多
登录 后发表回答