举例:
/**
kvc (key value coding) 和 对象
自动的做类型转换
为对象的name属性赋值
[p1 setValue:@"lisi" forKey:@"name"];
// 取出p1对象中 age属性的值
[p1 valueForKey:@"age"]
// 设置book对象的属性
[p1 setValue:@"随便" forKeyPath:@"book.bookName"];
*/
Person *p1 = [[Personalloc] init];
// p1.name = @"zhangsan";
// p1.age = 20;
[p1 setValue:@"lisi"forKey:@"name"];
// [p1 setValue:@"20" forKey:@"age"];
[p1 setValue:@"30"forKeyPath:@"age"];
// 实例化book对象
Book *b1 = [[Bookalloc] init];
b1.bookName = @"吃货大全";
p1.book = b1;
// 通过kvc修改book的名字
[p1 setValue:@"随便"forKeyPath:@"book.bookName"];
// NSLog(@"%@, %@ , bookName: %@",p1.name,[p1 valueForKey:@"age"],p1.book.bookName);
/**
kvc 和字典
字典里有的key值 --> p1对象中一定要由对应的属性名
字典里的key值 ,一定要和 对象中属性名保持一致
*/
NSDictionary *dict =@{@"name":@"wangwu",@"age":@"40"};
[p1 setValuesForKeysWithDictionary:dict];
// NSLog(@"%@, %@ , bookName: %@",p1.name,[p1 valueForKey:@"age"],p1.book.bookName);
/**
kvc 和数组
根据keyPath找到数组中对象的 name 属性, 并返回(数组)
NSArray *nameArray = [array valueForKeyPath:@"name"];
*/
Person *p2 = [[Personalloc] init];
p2.name = @"yellow Mokey";
Person *p3 = [[Personalloc] init];
p3.name = @"red dog";
Person *p4 = [[Personalloc] init];
p4.name =@"green chicken";
NSArray *array = @[p2,p3,p4];
// 把三个对象中的name 取出来
NSArray *nameArray = [array valueForKeyPath:@"name"];
NSLog(@"%@",nameArray);
}
==================
Key-Value-Coding:KVC顾名思义就是键值编码,通过[setValue:@"value" forKey:@"key"]来添加一组对象,编译器会把这行代码处理为SEL sel = sel_get_uid(@"setValue:forKey");
IMP method = objc_msg_lookup(site->isa,sel);
method(site, sel, @"value", @"key");这三步。
1.SEL数据类型就是编译器运行Objective-C里的方法的环境参数.
2.IMP数据类型其实就是一个编译器内部实现时候的函数指针,当Objective-C编译器去处理实现一个方法的时候,就会指向个IMP对象,这个对象是C语言表述的类型
3.所以当一个对象在调用SetValue的时候,首先根据方法名找到运行方法的时候所需要的环境参数,然后该对象会从自己的isa指针结合环境参数,找到具体的方法实现的接口,最后直接按查找到的具体的方法实现。
KVC是OC特有的,本质是在运行时动态的给对象发送setValue:forKey 消息,设置数值 -调用super.init 保证对象已经被创建完成 .当给对象发送setValue:forKey 消息时要判断对象是否存在key所对应的属性,直接赋值 如果没有就调用undefinedKey(默认崩溃,需要重写)
setValue:forKey的调用顺序首先会寻找set<key>方法,如果没有就去找_<key> _is<Key> <key> is<key> 顺序寻找,如何还没找到就调用undefinedKey(默认崩溃,需要重写).
a. setValue:forKey的搜索方式:
1. 首先搜索set<Key>:方法
如果成员用@property,@synthsize处理,因为@synthsize告诉编译器自动生成set<Key>:格式的setter方法,所以这种情况下会直接搜索到。
注意:这里的<Key>是指成员名,而且首字母大写。下同。
2. 上面的setter方法没有找到,如果类方法accessInstanceVariablesDirectly返回YES(注:这是NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)。
那么按_<key>,_is<Key>,<key>,is<key>的顺序搜索成员名。
b. valueForKey:的搜索方式:
1. 首先按get<Key>、<key>、is<Key>的顺序查找getter方法,找到直接调用。如果是bool、int等内建值类型,会做NSNumber的转换。
2. 上面的getter没有找到,查找countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。
如果countOf<Key>和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<Key>:range:方法。
3. 还没查到,那么查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。
如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:组合的形式调用。
4. 还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,_is<Key>,<key>,is<key>的顺序直接搜索成员名。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues;/这个方法用来字典转模型
第一、在OC中访问变量的几种方式:
1、设置为public型,通过->直接访问:
代码为:
@interface Book : NSObject
{
@public
NSString *name;
}
Book *book=[[Bookalloc]init];
book->name=@"hello";
NSLog(@"val is %@",book->name);
2.利用属性访问
3.利用KVC,即使该属性是private也可以访问
@interface Book : NSObject
{
@private
NSString *name;
}
Book *book=[[Book alloc]init];
[book setValue:@"hello"forKey:@"name"];
NSLog(@"val is %@",[bookvalueForKey:@"name"]);
除了通过键设置值外,键/值编码还支持指定路径,像文件系统一样,用“点”号隔开
[book valueForKeyPath:@"authorObj.name"]
author *authorObj=[[author alloc] init];
[authorObj setValue:@"niudun" forKey:@"name"];
[book setValue:authorObj forKey:@"authorObj"];
NSLog(@"the author of book is%@",[book valueForKeyPath:@"authorObj.name"]);
第三、一对多
@interface Book : NSObject
{
@private
NSString *name;
author *authorObj;
NSArray *relativeBooks;
}
//一对多
NSMutableArray *array=[NSMutableArray arrayWithCapacity:3];
for (int i=0; i<3; i++) {
Book *bookObj=[[Book alloc] init];
NSString *name=[NSString stringWithFormat:@"job_%d",i];
[bookObj setValue:name forKey:@"name"];
[array addObject:bookObj];
}
[book setValue:array forKey:@"relativeBooks"];
NSArray *arr=[book valueForKeyPath:@"relativeBooks.name"];
NSLog(@"arr is %@",arr);
第五、KVC对数值和结构体类型的支持
一套机制如果不支持数值和结构体型的数据,那么它的实用性就会大大折扣。幸运的是KVC中苹果对这方面的支持做的很好。KVC可以自动的将数值或结构体型的数据打包或解包成NSNumber或NSValue对象,以达到适配的目的。
举个例子,Person类有个个NSInteger类型的num属性
①修改值
我们通过KVC技术使用如下方式设置age属性的值:
[_person setValue:[NSNumber numberWithInteger:5] forKey:@"num"];
我们赋给num的是一个NSNumber对象,KVC会自动的将NSNumber对象转换成NSInteger对象,然后再调用相应的访问器方法设置age的值。
②获取值
同样,以如下方式获取age属性值:
[person valueForKey:@"num"];
这时,会以NSNumber的形式返回num的值。
第六、KVC实现原理的方法定义
在iOS中,通过KVC可以直接用字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。
KVC是KVO、Core Data、CocoaBindings的技术基础,他们都是利用了OC的动态性。
NSKeyValueCodingprotocol
runtime 的关联机制
1.什么是关联机制
OC提供了两种共享机制,一种是category,一种是associative,category只能扩展方法,associative可以扩展属性。
关联机制是基于关键字的,我们可以为任何对象增加任意多的关联,每个都使用不同的关键字即可。关联是可以保证被关联的对象在关联对象的整个生命周期都是可用的(在垃圾自动回收环境下也不会导致资源不可回收)。使用关联机制必须引入<objc/runtime.h>头文件
2.objc_setAssociatedObject创建关联
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
objc_setAssociatedObject来把一个对象与另外一个对象进行关联。该函数需要四个参数:源对象,关键字,关联的对象和一个关联策略
参数说明:
object:表示源对象
key:关键词
value:表示关联的对象
policy:关联策略,关联策略表明了相关的对象是通过赋值,保留引用还是复制的方式进行关联的;还有这种关联是原子的还是非原子的。这里的关联策略和声明属性时的很类似。
policy有四个值:
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
3. objc_getAssociatedObject获取关联对象
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
4.objc_removeAssociatedObjects删除该对象的所有关联,通常情况下不建议使用这个函数,因为他会断开所有关联。只有在需要把对象恢复到“原始状态”的时候才会使用这个函数。
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
NSArray *arr=[NSArray arrayWithObjects:@"one",nil];
objc_setAssociatedObject(arr, @"hello", @"world", OBJC_ASSOCIATION_RETAIN);
NSString *ret=(NSString *)objc_getAssociatedObject(arr, @"hello");
NSLog(@"the result is %@",ret);
5. objc_setAssociatedObject with nil 来断开指定key得关联
6. 重写方法的实现
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if (value == nil) {
value = [NSNull null];
}
objc_setAssociatedObject(self, (__bridge const void *)(key), value, OBJC_ASSOCIATION_RETAIN);
}
- (id)valueForUndefinedKey:(NSString *)key {
id target = objc_getAssociatedObject(self, (__bridge const void *)(key));
if ([target isKindOfClass:[NSNull class]])
return nil;
return target;
}
==============================