views:

37

answers:

2

Does Cocoa provide a built-in method to convert a key string into a properly-formatted set property accessor? i.e. "lineSpacing" -> setLineSpacing:

For example:

NSString * key = @"lineSpacing";
SEL selector = [key magicallyConvertIntoSetPropertyAccessor];

or even:

NSString * key = @"lineSpacing";
SEL selector = NSSelectorFromString([key toSetPropertyAccessor]);

Background:

I'm working on a method that will set a number of properties, by name, using property values stored in a dictionary. I can't use setObject:forKey because I also use this method for properties that are of built-in types like NSInteger and CGFloat. Here is an example of the method I use for CGFloat, to illustrate:

- (void)setFloatProperty:(NSString *)key value:(CGFloat)value target:(id)object
{
    NSString * setPropertyString = [NSString stringWithFormat:@"set%@",
                                    [key capitalizedString]];
    SEL selector = NSSelectorFromString(setPropertyString);

    NSMethodSignature * signature = ...
    NSInvocation * invocation = ...

    CGFloat arg = value;
    void * argument = &arg;

    [invocation setArgument:argument atIndex:2];
    [invocation invoke];
}

This method works for one-word properties like "color" or "width", but fails on mulit-word properties like "lineSpacing" or "meaningOfLifeTheUniverseAndEverything" because the capitalize method capitalizes the first letter while setting all subsequent letters to lowercase. Calling the above method using "lineSpacing" as the key results in failure, since the correct property setter would be setLineSpacing: and not setLinespacing:

+4  A: 

You're looking for setValue:forKey:, which not only finds the correct setter selector but sends the message for you. It's part of the Key-Value Coding protocol; note that getters like myProperty and setters like setMyProperty: are called “Key-Value-Coding-compliant” for this reason.

I'm working on a method that will set a number of properties, by name, using property values stored in a dictionary.

See also the setValuesForKeysWithDictionary: method, also part of the KVC protocol.

Peter Hosey
Ah, sorry, I should have been more specific. That works for object-style properties, but not for primitive types. I posted a simplified version of my setProperty method, but the actual one uses `NSInvocation`, which requires more complex setup. I'll change my question to make that clear.
e.James
@e.James it does work with primitives:http://pastie.org/1049035
Dave DeLong
e.James: Why not just implement your `setFloatProperty:forKey:target:` method to box up the value and pass that to `setValue:forKey:`? It'd be much simpler than trying to duplicate KVC's algorithm. (And if you're worried about performance, remember what they say about premature optimization. You should do it the simpler way first, then profile it and see whether it's bogging you down.)
Peter Hosey
I had no idea you could send `NSNumber` to set primitive types. That's going to simplify things a lot. Thank you both!
e.James
@e.James yep, you can also send `NSValue` objects if the target is a `void*` or struct or something more "C-ish" :)
Dave DeLong
@Dave DeLong: Beautiful. I need to do that, too. Thanks again `:)`
e.James
+1  A: 

Apple hasn't made a public-facing function to do this, but the general algorithm is pretty simple:

SEL SetterSelectorForKey(NSString *key) {
    SEL result = 0;

    NSString *strFirstCharacter = [[key substringToIndex: 1] uppercaseString];
    NSString *strRemainder = [key substringFromIndex: 1];

    result = NSSelectorFromString([NSString stringWithFormat: @"set%@%@:", strFirstCharacter, strRemainder]);

    return result;
}

Before you go using this, however, keep in mind that there may be other ways to accomplish your goal. Investigate the existing APIs you're using before you go off and do this (which, I might add, will not work correctly if the accessor of a @property is set to something different. I'm not aware of any function in the Objective-C runtime that will let you get the setter of a @property.)

Jonathan Grynspan
While this generally works, you *can* specify a custom setter name using property syntax: `@property(setter=setMyFoo:, getter=isMyFoo) BOOL foo;`. `setValue:forKey:` will know of this and is the best approach.
Dave DeLong
Yep, hence my caveat. :)
Jonathan Grynspan