(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);
}
}
(bug更正)利用KVC和associative特性在NSObject中存储键值的更多相关文章
- 利用js对象的特性,去掉数组中的重复项
- 利用js取到下拉框中选择的值
现在的需求是:下拉框中要是选择加盟商让其继续选择学校,要是选择平台管理员则不需要选择学校.隐藏选择下拉列表. 选择枚举值: /// <summary> /// 平台角色 /// </ ...
- 利用pandas映射替换两个字典中的映射值
在公司处理报表,中英文映射表与数值表替换 import pandas as pd data = { "a":"值一", "b":" ...
- iOS - KVC 键值编码
1.KVC KVC 是 Key-Value Coding 的简写,是键值编码的意思,属于 runtime 方法.Key Value Coding 是 cocoa 的一个标准组成部分,是间接给对象属性设 ...
- [原创]obj-c编程16:键值编码(KVC)
原文链接:obj-c编程16:键值编码(KVC) 我们可以借助obj-c中的键值编码(以后简称KVC,Key-Value Coding)来存取类的属性,通过指定所要访问的属性名字符串标示符,可以使用存 ...
- obj-c编程16:键值编码(KVC)
我们可以借助obj-c中的键值编码(以后简称KVC,Key-Value Coding)来存取类的属性,通过指定所要访问的属性名字符串标示符,可以使用存取器方法来获取或设置类的属性.下面的例子,借助于K ...
- ios中键值编码kvc和键值监听kvo的特性及详解
总结: kvc键值编码 1.就是在oc中可以对属性进行动态读写(以往都是自己赋值属性) 2. 如果方法属性的关键字和需要数据中的关键字相同的话 ...
- 利用atimicInteger cas的特性实现一个锁
利用atimicInteger cas的特性实现一个锁 主要是使用的是 atomicIntegerAPI 的compareAndSet()方法,让线程不在阻塞,获取不到直接失败. 我们先定义一个异常类 ...
- 《芒果TV》UWP版利用Windows10通用平台特性,率先支持Xbox One平台
在Windows开发者中心开放提交Xbox平台应用之后,<芒果TV>UWP版迅速更新v3.1.2版,通过升级兼容目标,利用Windows10通用平台特性,率先覆盖Xbox平台用户. 芒果T ...
随机推荐
- 简单总结一下NotificationCenter、KVC、KVO、Delegate
将最近总结的最常用的几种设计模式优势与区别自己小结一下,分享给大家. kvo只能用来对属性作出反应,而不会用来对方法或者动作作出反应,是实现一个对象与另外一个对象保持同步的一种方法,能够提供观察的属性 ...
- MVC URL处理
需要web.config在system.webServer节点添加 <modules runAllManagedModulesForAllRequests="true"/ ...
- Big Data應用:以"玩家意見"之數據分析來探討何謂"健康型線上遊戲"(上)
首先,所有資料都可以從網路上找到,只是我做了一些分析與整理而已.純粹分享心得~~ 最近再做研究的時候我跟我的同事K先生在某次偶然的討論中發現了一件有趣的事情. [疑~~~~~~~新楓之谷的玩家人氣指數 ...
- media queries 媒体查询使用
media queries 翻译过来就是媒体查询,media 指的媒体类型.那么有哪些类型呢,常用的有 screen(屏幕).打印(print),个人理解就是它所在的不同终端. 常用的用法:1,< ...
- CCProcxy代理服务器的配置使用
资源准备及设置 1.资源:http://www.ccproxy.com/ 下载官方正式版本. 2.解压之后打开,界面如下: 打开“设置”,如图做设置,点击确定: 打开“账号”: 点击新建,在ip地址/ ...
- 功能点分析法FPA笔记
转载请注明出处:http://www.cnblogs.com/lidabnu/p/5700412.html 主要参考资料来自百度文库:http://wenku.baidu.com/link?url=y ...
- .Net之美读书系列(二):委托进阶
这次看书的知识点: 事件访问器 如果一个委托中注册了多个事件且需要获取其返回值的方法 委托的异常处理 委托处理超时的方法 异步委托 事件访问器 职能有: 1.对委托属性进行封装,不再直接该委托变量直接 ...
- win7 iis7.5 配置错误解决办法
win7 iis7.5 配置HTTP 错误 404.3 在初次使用IIS7的时候经常遇到的一个错误解决办法1: 找到Visual Studio命令提示工具,运行aspnet_regiis.exe -i ...
- 微信Demo导入遇到的问题
最近做支付宝和微信接入自己APP工程的功能,遇到了一些问题,跟大家分享: 这里先说Android开发微信支付接入. 首先根据官方文档进行,对比支付宝的官方文档,微信部分更显得“摘要”一些. 导入后自行 ...
- OPENQUERY
SELECT * FROM OPENQUERY(saql007,' SELECT col1,col2,col3 FROM dbname.shemaname.tablename WHERE (1=1 ...