KVC

2013-08-30 5 views
6

ARCを使用して、タイプがSELのプロパティに割り当てられます。合成さ@property SEL mySelector;KVC

SEL someSelector = @selector(doSomething:) 
NSValue* someSelectorValue = [NSValue value:someSelector withObjCType:@encode(SEL)]; 
[target setValue:someSelectorValue forKey:@"mySelector"]; 

私が手:@synthesize mySelector;

は、その後、私はKVC使用してそれに値を代入しようとし

私はSEL型のプロパティを持つクラスを持っていますエラーメッセージ:

[<LACMyClass 0x101b04bc0> setValue:forUndefinedKey:]: 
this class is not key value coding-compliant for the key mySelector. 

これはobです私が渡している値が好きではありません。タイプvoid*のプロパティをSELの代わりに定義すると動作するように見えますが、これは私の要件を満たしていません。 value:withObjCType:を使用することに加えて

、私もvalueWithBytes:objCType:valueWithPointer:

を試してみました誰もがこれを適切に行うためにどのように私に

で何が起こっているのか
  1. 、および
  2. を説明してもらえますか?

答えて

7

デフォルトのsetValue:forKey:の実装では、自動ボクシング/アンボックスのためにサポートされているプリミティブタイプのサブセットのみがあるようです。 「Key-Value Coding Programming Guide」の"Scalar and Structure Support" chapterの表1および表2を参照してください。ここでの意味は唯一BOOLchardoublefloatintlonglong longshort、及びその符号なしの対応が完全に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"]; 
    } 
} 

我々はうまくintidプロパティを設定することができます - でも__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 

スウィズリングはもちろんのないリスクなしであるので、注意して続行!

+0

SELは単にボクシング/アンボクシングに対応していないようです。ジェフリー・トーマスの答えも同じですが、この回避策が役立ちます。しかし、私はまだ真のボクシング/ unboxingの任意の並べ替えが可能かどうか疑問に思う。 –

+0

@BrandonHorstありがとう。私の編集を見てください - あなたが興味を持っているなら、より一般的な回避策を追加しました。 –

2

エラーは、クラスがキーmySelectorに準拠していないことを伝えようとしています。また、障害が発生し

STAssertNoThrow([target setValue:nil forKey:@"mySelector"], nil); 

は、トラブルがないSELボクシングでmySelectorです。


私は興味があったので、私はちょうどこのテストを実行しました:

@interface KVCOnSELTester : NSObject 
@property (assign, nonatomic) SEL mySelector; 
@property (assign, nonatomic) int myInteger; 
@end 

@implementation KVCOnSELTester 
@end 

@implementation KVCOnSELTests 

- (void)testKVCOnSEL 
{ 
    KVCOnSELTester *target = [KVCOnSELTester new]; 

    STAssertNoThrow((target.myInteger = 1), nil); 
    STAssertNoThrow([target setMyInteger:1], nil); 
    STAssertNoThrow([target setValue:nil forKey:@"myInteger"], nil); 

    STAssertNoThrow((target.mySelector = @selector(setUp)), nil); 
    STAssertNoThrow([target setMySelector:@selector(setUp)], nil); 
    STAssertNoThrow([target setValue:nil forKey:@"mySelector"], nil); 
} 

@end 

STAssertNoThrow([target setValue:nil forKey:@"myInteger"], nil)[target setValue:nil forKey:@"myInteger"] raised [<KVCOnSELTester 0x8a74840> setNilValueForKey]: could not set nil as the value for the key myInteger..

を投げたこれは正しくnilmyIntegerのために適切ではないと述べています。

STAssertNoThrow([target setValue:nil forKey:@"mySelector"], nil)はあなたが前になったように、これは、同じエラーである[target setValue:nil forKey:@"mySelector"] raised [<KVCOnSELTester 0x8a74840> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key mySelector..

を投げました。 SELはKVCではないと思います。私はこれを見てみましたが、私はどこにもいません。

関連する問題