加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-16 03:43:10 所属栏目:百科 来源:网络整理
导读:ARC已启用. 我有一个类型为SEL的属性:@property SEL mySelector; 它是合成的:@synthesize mySelector; 然后我尝试使用KVC为它分配一个值: SEL someSelector = @selector(doSomething:)NSValue* someSelectorValue = [NSValue value:someSelector withObjC
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:

任何人都可以向我解释

>发生了什么事,以及
>如何正确地做到这一点?

解决方法

看起来默认的setValue:forKey:implementation只能支持自动装箱/拆箱的原始类型的某个子集.请参阅“键 – 值编码编程指南”的 “Scalar and Structure Support” chapter中的表1和表2.这里的含义是完全支持BOOL,char,double,float,int,long,long long,short和它们的无符号对应物,以及通过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"];
    }
}

我们可以设置int和id属性 – 甚至使用__unsafe_unretained和桥接强制转换来让我们传递选择器值.但是,不支持尝试设置两种指针类型中的任何一种.

我们如何从这里开始?例如,我们可以在MyObject中覆盖valueForKey:和setValueForKey:以支持取消装箱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”文章,并且还使用了this answer on SO中的Swizzle()函数.请注意,我为了演示而偷工减料,并且此代码仅适用于SEL与默认的KVC实现不同,具有适当命名的getter和setter的属性,并且不会直接检查实例变量.

#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,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

调酒当然不是没有风险,所以要小心!

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读