按名称调用Property setter的最佳方法是什么?

时间:2010-12-28 21:52:44

标签: objective-c

我正在构建一个可以挂钩到一系列底层对象属性的通用UI,所以我想按名称调用getter和setter。我已经玩过使用NSInvocation,并且还看到其他人使用setValue:forKey。但我想用最快的方法。

如果我持有对NSInvocation实例的引用,我可以非常快速地调用invoke,但是我有一个问题 - 如何使用它调用属性setter?它在getter上效果很好:

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];

但是我找不到为选择器赋值的方法。使用setArgument:atIndex:似乎不起作用。

所以,我回归setValue:forKey:方法,但我担心随着时间的推移,性能会迅速提升。

有什么想法吗?

4 个答案:

答案 0 :(得分:4)

我并不完全理解您要实现的目标,但我已经快速测试了valueForKey:与您的调用方法的性能。在我的测试中,下面的代码valueForKey:几乎是NSInvocation方法的5倍。

  1. 1.620685 millisec
  2. 30.357355 millisec
  3. 7.593611 millisec
  4. 代码:

    #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;
    }
    

答案 1 :(得分:0)

setArgument:atIndex:确实有效,但您必须将选择器的名称更改为正确的形式。也就是说,set + propertyNameWithCapitalizedFirstLetter + :

答案 2 :(得分:0)

setValue:forKey:绝对是正确的方法。许多可可使用这种机制。如果我没记错的话,属性的Objective-C点语法实际上只是在编译时被翻译成setValue:forKey:/ valueForKey :(或至少功能相似的东西)。如果我是你,我会使用它,直到你可以通过实际测量确定它对速度产生负面影响。否则,你正在以一种奇怪的,非惯用的方式运作,没有真正的好处。

答案 3 :(得分:0)

你担心表现setValue:forKey:是对的。对于直接发送给对象的消息,它必然会有很大的开销。 然而,与所有性能优化问题一样,您过早地编写额外(或难以维护)代码,直到您证明为止在您的用例中,性能损失是不可接受的。在您的情况下,setValue:forKey:可能是正确的解决方案。

在幕后,setValue:forKey:必须做一些像你正在尝试的NSInvocation舞蹈。然而,我怀疑它至少也会比你的前几次尝试更好(并且可能更好)。毕竟,setValue:forKey:是Cocoa框架的核心部分,你可以打赌Apple工程师已经优化了它。