What's the best way to call a Property setter by name?
I'm building a generic UI that can hook into a range of underlying object properties, so I'd like to call the getter and setter by name. I've played around with using NSInvocation, and have also seen others use setValue:forKey
. But I'd like to use the fastest method.
If I hold reference to the NSInvocation instance, I can call invoke pretty rapidly, but I have a problem - how do I call a开发者_运维知识库 property setter using it? It works great on the getter like so:
SEL propSelector = NSSelectorFromString(propertyName);
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[target class]instanceMethodSignatureForSelector:propSelector]];
[inv setSelector:propSelector];
[inv setTarget:target];
[inv invoke];
float value;
[inv getReturnValue:&value];
But I haven't been able to find a way to assign a value to the selector. Using setArgument:atIndex:
doesn't seem to work.
So, I fall back on the setValue:forKey:
method, but I'm concerned about performance hitting this rapidly over time.
Any ideas?
I don't exactly understand what you're trying to achieve, but I've quickly tested the performance of valueForKey:
versus your invocation method. In my tests, code below, valueForKey:
is almost 5 times faster than the NSInvocation method.
- 1.620685 millisec
- 30.357355 millisec
- 7.593611 millisec
Code:
#import <Foundation/Foundation.h>
#include <mach/mach_time.h>
@interface Foo : NSObject
{
NSNumber *value;
}
@property (nonatomic, retain) NSNumber *value;
@end
@implementation Foo
@synthesize value;
- (void) dealloc
{
[value release];
[super dealloc];
}
@end
#pragma mark MAIN
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// prepare loops
NSUInteger i;
NSUInteger max = 10000;
Foo *target = [[[Foo alloc] init] autorelease];
target.value = [NSNumber numberWithFloat:12.345f];
NSString *valueKey = @"value";
// prepare mach timebase
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
double ticksToNanoseconds = (double)timebase.numer / timebase.denom;
// old-fashioned
uint64_t startTime = mach_absolute_time();
for (i = 0; i < max; i++) {
NSNumber *numValue = [target valueForKey:valueKey];
}
uint64_t elapsedTime = mach_absolute_time() - startTime;
double elapsedTimeInNanoseconds = elapsedTime * ticksToNanoseconds;
NSLog(@"1: %f millisec", elapsedTimeInNanoseconds / 1000000);
// invocation
startTime = mach_absolute_time();
for (i = 0; i < max; i++) {
SEL propSelector = NSSelectorFromString(valueKey);
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:propSelector]];
[inv setSelector:propSelector];
[inv setTarget:target];
[inv invoke];
NSNumber *numValue;
[inv getReturnValue:&numValue];
}
elapsedTime = mach_absolute_time() - startTime;
elapsedTimeInNanoseconds = elapsedTime * ticksToNanoseconds;
NSLog(@"2: %f millisec", elapsedTimeInNanoseconds / 1000000);
// reused invocation
SEL propSelector = NSSelectorFromString(valueKey);
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:propSelector]];
startTime = mach_absolute_time();
for (i = 0; i < max; i++) {
[inv setSelector:propSelector];
[inv setTarget:target];
[inv invoke];
NSNumber *numValue;
[inv getReturnValue:&numValue];
}
elapsedTime = mach_absolute_time() - startTime;
elapsedTimeInNanoseconds = elapsedTime * ticksToNanoseconds;
NSLog(@"3: %f millisec", elapsedTimeInNanoseconds / 1000000);
// clean and return
[pool drain];
return 0;
}
setArgument:atIndex:
does work, but you have to change the name of the selector to the correct form. That is, set
+ propertyNameWithCapitalizedFirstLetter + :
.
setValue:forKey: is definitely the correct way to go about this. A number of pieces of Cocoa use this mechanism. If I recall correctly, the Objective-C dot syntax for properties is actually just translated to setValue:forKey:/valueForKey: (or at least something functionally similar) at compile time. If I were you I would use that until such a point as you can identify, with actual measurements, that it is negatively impacting the speed. Otherwise you are operating an a strange and non-idiomatic way for no real benefit.
You are right to be worried about the performance setValue:forKey:
. It necessarily has significant overhead over a direct message to the object. However, as with all performance optimization questions, you should not prematurely write extra (or hard-to-maintain) code until you can prove that the performance hit is unacceptable in your use case. setValue:forKey:
is probably the correct solution in your situation.
Under the hood, setValue:forKey:
must do something like the NSInvocation
dance you're trying. I suspect, however, that it will do it at least as well (and likely much better) than your first several attempts. setValue:forKey:
is, after all, a core piece of the Cocoa framework and you can bet that Apple engineers have optimized the hell out of it.
精彩评论