zoukankan      html  css  js  c++  java
  • (bug更正)利用KVC和associative特性在NSObject中存储键值

    KVC 

     一直没仔细看过KVC的用法,想当然的认为可以在NSObject对象中存入任意键值对,结果使用时碰到问题了。

      一个简单的位移动画:

    CAKeyframeAnimation *keyPosi=[CAKeyframeAnimation animationWithKeyPath:@"position"];
    keyPosi.path=path.CGPath;
    keyPosi.delegate=self;
    [label.layer addAnimation:keyPosi forKey:@"x"];

      我想要在动画结束后把UILabel从屏幕上移除,于是加上动画结束后的回调:

    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    
    }

      然而看上去从回调方法的参数中似乎无法得到UILabel对象(也许提供的有api可以获得UIlabel,希望各位看官不吝赐教),如果只是为了在这里得到UILabel对象而去设置一个全局变量指针指向它感觉没必要,这时候我想起了KVC,于是在创建动画的时候加上一句:

    [keyPosi setValue:label forKey:@"xx"];

      这样在动画结束的回调方法中可以通过anim参数获得UILabel对象了:

    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{

        if (flag) {

            UIView *vie=[anim valueForKey:@"xx"];

            [vie removeFromSuperview];

        }

    }

       效果已经实现,然而KVC是这样使用的吗?再看一个例子:

    NSObject *obj = [[NSObject alloc] init];
    [obj setValue:@"asd"  forKey:@"xx"];

      运行报错:

    *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0xf65f090> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key xx.'

      原来setValue:forKey、setValue:forKeyPath的key(Path)参数不是随便写的,必须是类中定义的成员变量名(或者实例方法),这篇文章写的很清楚:http://www.cnblogs.com/jay-dong/archive/2012/12/13/2815778.html。话说回来,为啥前面CAKeyframeAnimation使用的setValue:forKey的key可以成功设置?类里肯定没有xx这个成员变量。继续查资料发现有个方法setValue:forUndefinedKey:,

    setValue:forUndefinedKey:
    Invoked by setValue:forKey: when it finds no property for a given key.
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key Discussion Subclasses can override this method to handle the request in some other way. The default implementation raises an NSUndefinedKeyException.

      当setValue方法的key参数在类中找不到对应成员时,会调用这个方法,重写它可以阻止抛出NSUndefinedKeyException异常。

      这样看来,CAKeyFrameAnimation类(或者它的父类)应该是重写了setValue:forUndefinedKey:,然而方法里是怎样处理从而使得valueForKey可以正确取到值呢?

    associative

      objective-c的扩展机制有两个特性:category和associative。category扩展类别,associative扩展属性。使用associative需要导入<objc/runtime.h>头文件。

      利用category扩展NSObject类别,利用associative和setValue:forUndefinedKey:让NSObject能够存入任意键值对。

    #import <objc/runtime.h>
    @interface NSObject (KVC)
    - (id)valueForUndefinedKey:(NSString *)key;
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key;
    @end
    
    
    @implementation NSObject(KVC)
    - (id)valueForUndefinedKey:(NSString *)key{
        return objc_getAssociatedObject(self, key);
    }
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key{
        if ([value isKindOfClass:[NSString class]]) {
            objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_COPY_NONATOMIC);
        }else{
            objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    
    }
    @end

      现在再试下:

    NSObject *obj = [[NSObject alloc] init];
    [obj setValue:@"asd"  forKey:@"xx"];
    NSLog(@"%@",[obj valueForKey:@"xx"]);

      可以输出了吧。

    更正!

        对各位说声抱歉,之前犯了一个想当然的错误,setValue:forUndefinedKey:和valueForUndefinedKey:两个方法中的key不能直接传给setAssociatedObject和getAssociatedObject方法,如下所示的做法是错误的:

    NSString *a=[NSString stringWithFormat:@"hello"];
    NSString *b=[NSString stringWithFormat:@"hello"];
    [self setValue:@"cannotfind" forKey:a];
    NSString *result=[self valueForKey:b];
    NSLog(@"%@",result);

      这里result是空,因为a、b是两个不同指针,setAssociatedObject和getAssociatedObject使用了不同的key。正确的做法是定义全局静态变量作为key:

    static NSString *kHello=@"hello";


    [self setValue:@"canfind" forKey:kHello]; NSString *result=[self valueForKey:kHello]; NSLog(@"%@",result);

      或者这样:

    static char kHello;
    
    
    - (id)valueForUndefinedKey:(NSString *)key{
        const void *newkey=nil;
        if ([key isEqualToString:@"hello"]) {
            newkey=&kHello;
        }else{
            NSAssert(false, @"undefined key");
        }
        return objc_getAssociatedObject(self, newkey);
    }
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key{
        const void *newkey=nil;
        if ([key isEqualToString:@"hello"]) {
            newkey=&kHello;
        }else{
            NSAssert(false, @"undefined key");
        }
    
        if ([value isKindOfClass:[NSString class]]) {
            objc_setAssociatedObject(self, newkey, value, OBJC_ASSOCIATION_COPY_NONATOMIC);
        }else{
            objc_setAssociatedObject(self, newkey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        
    }
    作者:PowerAuras
    出处:http://www.cnblogs.com/powerauras/
    转载请注明出处及作者的署名。
  • 相关阅读:
    SQL Server 2005中NTEXT与NVARCHAR(MAX)
    WiX安装选项开始菜单项
    Linq to SQL 查询Tips
    WCF的Message Logging 和Tracing
    Publish Server Performance Monitors with MsChart
    Pushing Data to a Silverlight Client with a WCF Duplex Service
    IronPython 2.0 发布了
    设置系统环境变量立即生效的VBS脚本
    微软网络数据包分析工具 Microsoft Network Monitor 3.2
    SQL Server 2005最新Service Pack 3
  • 原文地址:https://www.cnblogs.com/powerauras/p/3323497.html
Copyright © 2011-2022 走看看