我正在构建一个可以挂钩到一系列底层对象属性的通用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:
方法,但我担心随着时间的推移,性能会迅速提升。
有什么想法吗?
答案 0 :(得分:4)
我并不完全理解您要实现的目标,但我已经快速测试了valueForKey:
与您的调用方法的性能。在我的测试中,下面的代码valueForKey:
几乎是NSInvocation方法的5倍。
代码:
#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工程师已经优化了它。