デフォルトのsetValue:forKey:
の実装では、自動ボクシング/アンボックスのためにサポートされているプリミティブタイプのサブセットのみがあるようです。 「Key-Value Coding Programming Guide」の"Scalar and Structure Support" chapterの表1および表2を参照してください。ここでの意味は唯一BOOL
、char
、double
、float
、int
、long
、long long
、short
、及びその符号なしの対応が完全にNSValue
経由struct
Sと一緒に、サポートされていることです。他のタイプ(SEL
など)およびその他のポインタ値appear to be unsupported。
は、次のプログラムを考えてみましょう:
#import <Foundation/Foundation.h>
@interface MyObject : NSObject
@property (nonatomic) SEL mySelector;
@property (nonatomic) void *myVoid;
@property (nonatomic) int myInt;
@property (nonatomic,unsafe_unretained) id myObject;
@end
@implementation MyObject
@end
int main(int argc, char *argv[]) {
@autoreleasepool {
SEL selector = @selector(description);
NSValue *selectorValue = [NSValue valueWithPointer:selector];
NSValue *voidValue = [NSValue valueWithPointer:selector];
NSValue *intValue = @1;
__unsafe_unretained id obj = (__bridge id)(const void *)selector;
MyObject *object = [[MyObject alloc] init];
// The following two calls succeed:
[object setValue:intValue forKey:@"myInt"];
[object setValue:obj forKey:@"myObject"];
// These two throw an exception:
[object setValue:voidValue forUndefinedKey:@"myVoid"];
[object setValue:selectorValue forKey:@"mySelector"];
}
}
我々はうまくint
とid
プロパティを設定することができます - でも__unsafe_unretained
を使用して、私たちは、セレクタ値を通過させるためにキャストをブリッジ。ただし、2つのポインタ型のいずれかを設定しようとすると、サポートされません。
ここからどのように進んでいますか?例えば、とsetValueForKey:
をMyObject
に上書きして、SEL
タイプのアンボックスをサポートしたり、特定のキーを傍受することができます。後者のアプローチの例:使用中
@implementation MyObject
- (id)valueForKey:(NSString *)key
{
if ([key isEqualToString:@"mySelector"]) {
return [NSValue valueWithPointer:self.mySelector];
}
return [super valueForKey:key];
}
- (void)setValue:(id)value forKey:(NSString *)key
{
if ([key isEqualToString:@"mySelector"]) {
SEL toSet;
[(NSValue *)value getValue:&toSet];
self.mySelector = toSet;
}
else {
[super setValue:value forUndefinedKey:key];
}
}
@end
、我々が期待どおりに動作します見つける:
[object setValue:selectorValue forKey:@"mySelector"];
NSString *string = NSStringFromSelector(object.mySelector);
NSLog(@"selector string = %@", string);
これは、コンソールに「セレクタ文字列=説明」をログに記録します。
もちろん、これはKVCでセレクタを設定する必要があるすべてのクラスでこれらのメソッドを実装する必要があり、ハードコードされたキーと比較する必要があるため、保守上の懸念があります。これを回避する1つの方法は、方法swizzlingを使用して、NSObject
のKVCメソッドの実装をSEL
タイプのボクシングとアンボックスを処理する私たち自身のものに置き換えることです。
次のプログラムは、最初の例に基づいて構築された、マイク・アッシュの華麗な"let's build KVC"記事から重く導出し、またthis answer on SOからSwizzle()
機能を使用しています。デモンストレーションの目的でコーナーを切り取り、このコードはgetterとsetterの名前が適切に設定されたSEL
属性でのみ機能し、デフォルトのKVC実装とは異なり、インスタンス変数を直接チェックしないことに注意してください。
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface MyObject : NSObject
@property (nonatomic) SEL mySelector;
@property (nonatomic) int myInt;
@end
@implementation MyObject
@end
@interface NSObject (ShadyCategory)
@end
@implementation NSObject (ShadyCategory)
// Implementations of shadyValueForKey: and shadySetValue:forKey: Adapted from Mike Ash's "Let's Build KVC" article
// http://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
// Original MAObject implementation on github at https://github.com/mikeash/MAObject
- (id)shadyValueForKey:(NSString *)key
{
SEL getterSEL = NSSelectorFromString(key);
if ([self respondsToSelector: getterSEL]) {
NSMethodSignature *sig = [self methodSignatureForSelector: getterSEL];
char type = [sig methodReturnType][0];
IMP imp = [self methodForSelector: getterSEL];
if (type == @encode(SEL)[0]) {
return [NSValue valueWithPointer:((SEL (*)(id, SEL))imp)(self, getterSEL)];
}
}
// We will have swapped implementations here, so this call's NSObject's valueForKey: method
return [self shadyValueForKey:key];
}
- (void)shadySetValue:(id)value forKey:(NSString *)key
{
NSString *capitalizedKey = [[[key substringToIndex:1] uppercaseString] stringByAppendingString:[key substringFromIndex:1]];
NSString *setterName = [NSString stringWithFormat: @"set%@:", capitalizedKey];
SEL setterSEL = NSSelectorFromString(setterName);
if ([self respondsToSelector: setterSEL]) {
NSMethodSignature *sig = [self methodSignatureForSelector: setterSEL];
char type = [sig getArgumentTypeAtIndex: 2][0];
IMP imp = [self methodForSelector: setterSEL];
if (type == @encode(SEL)[0]) {
SEL toSet;
[(NSValue *)value getValue:&toSet];
((void (*)(id, SEL, SEL))imp)(self, setterSEL, toSet);
return;
}
}
[self shadySetValue:value forKey:key];
}
@end
// Copied from: https://stackoverflow.com/a/1638940/475052
void Swizzle(Class c, SEL orig, SEL new)
{
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
int main(int argc, char *argv[]) {
@autoreleasepool {
Swizzle([NSObject class], @selector(valueForKey:), @selector(shadyValueForKey:));
Swizzle([NSObject class], @selector(setValue:forKey:), @selector(shadySetValue:forKey:));
SEL selector = @selector(description);
MyObject *object = [[MyObject alloc] init];
object.mySelector = selector;
SEL fromProperty = object.mySelector;
NSString *fromPropertyString = NSStringFromSelector(fromProperty);
NSValue *fromKVCValue = [object valueForKey:@"mySelector"];
SEL fromKVC;
[fromKVCValue getValue:&fromKVC];
NSString *fromKVCString = NSStringFromSelector(fromKVC);
NSLog(@"fromProperty = %@ fromKVC = %@", fromPropertyString, fromKVCString);
object.myInt = 1;
NSNumber *myIntFromKVCNumber = [object valueForKey:@"myInt"];
int myIntFromKVC = [myIntFromKVCNumber intValue];
int myIntFromProperty = object.myInt;
NSLog(@"int from kvc = %d from propety = %d", myIntFromKVC, myIntFromProperty);
selector = @selector(class);
NSValue *selectorValue = [NSValue valueWithPointer:selector];
[object setValue:selectorValue forKey:@"mySelector"];
SEL afterSettingWithKVC = object.mySelector;
NSLog(@"after setting the selector with KVC: %@", NSStringFromSelector(afterSettingWithKVC));
[object setValue:@42 forKey:@"myInt"];
int myIntAfterSettingWithKVC = object.myInt;
NSLog(@"after setting the int with KVC: %d", myIntAfterSettingWithKVC);
}
}
このプログラムの出力はそのボクシングとアンボクシング機能を示しています
2013-08-30 19:37:14.287 KVCSelector[69452:303] fromProperty = description fromKVC = description
2013-08-30 19:37:14.288 KVCSelector[69452:303] int from kvc = 1 from propety = 1
2013-08-30 19:37:14.289 KVCSelector[69452:303] after setting the selector with KVC: class
2013-08-30 19:37:14.289 KVCSelector[69452:303] after setting the int with KVC: 42
スウィズリングはもちろんのないリスクなしであるので、注意して続行!
SELは単にボクシング/アンボクシングに対応していないようです。ジェフリー・トーマスの答えも同じですが、この回避策が役立ちます。しかし、私はまだ真のボクシング/ unboxingの任意の並べ替えが可能かどうか疑問に思う。 –
@BrandonHorstありがとう。私の編集を見てください - あなたが興味を持っているなら、より一般的な回避策を追加しました。 –