开发者

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. 1.620685 millisec
  2. 30.357355 millisec
  3. 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜