gpt4 book ai didi

objective-c - 使用 KVC 分配给 SEL 类型的属性

转载 作者:太空狗 更新时间:2023-10-30 03:41:55 24 4
gpt4 key购买 nike

ARC 已启用。

我有一个具有 SEL 类型属性的类:@property SEL mySelector;

合成:@synthesize mySelector;

然后我尝试使用 KVC 为其分配一个值:

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

我收到错误信息:

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

这显然不是真的——这个类是 KVC 兼容的,它只是不喜欢我传入的值。当我定义 void* 类型的属性时它似乎起作用了SEL,但这不符合我的要求。

除了使用value:withObjCType:,我还尝试了valueWithBytes:objCType:valueWithPointer:

谁能给我解释一下

  1. 发生了什么,以及
  2. 如何正确地做到这一点?

最佳答案

默认情况下,自动装箱/拆箱似乎只支持原始类型的特定子集setValue:forKey:执行。请参阅 "Scalar and Structure Support" chapter 中的表 1 和表 2 “键值编码编程指南”。这里的言外之意就是只有BOOL , char , double , float , int , long , long long , short , 以及它们未签名的副本,以及 struct 都得到了完全支持。通过 NSValue .其他类型,例如 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和桥接转换让我们传递选择器值。但是,不支持尝试设置这两种指针类型中的任何一种。

我们如何从这里开始?例如,我们可以覆盖 valueForKey: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);

这会将“selector string = description”记录到控制台。

当然,这有可维护性问题,因为您现在必须在每个需要使用 KVC 设置选择器的类中实现这些方法,并且还必须与硬编码键进行比较。一种有风险的解决方法是使用方法调配并替换替换 NSObject使用我们自己的 KVC 方法的实现,它处理 SEL 的装箱和拆箱类型。

以下程序建立在第一个示例的基础上,很大程度上源自 Mike Ash 的精彩 "let's build KVC"文章,还使用了 Swizzle()来自 this answer on SO 的函数.请注意,我为了演示目的偷工减料,并且此代码仅适用于 SEL。具有适当命名的 getter 和 setter 的属性,并且不会直接检查实例变量,这与默认的 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

调配当然有风险,所以要小心!

关于objective-c - 使用 KVC 分配给 SEL 类型的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18542664/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com