关于KVC

来源:互联网 发布:大学生网络家教 兼职 编辑:程序博客网 时间:2024/06/07 06:46

KVC(Key-value coding)键值编码: 指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。(一个非正式的Protocol,提供一种机制来间接访问对象的属性。而不是通过调用Setter、Getter方法访问。)这样就可以在运行时动态在访问和修改对象的属性,而不是在编译时确定,这也是iOS开发中的黑魔法之一。

KVC最为重要的四个方法:

-(nullableid)valueForKey:(NSString *)key;                          //直接通过Key来取值
-(void)setValue:(nullableid)valueforKey:(NSString *)key;          //通过Key来设值
-(nullableid)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
-(void)setValue:(nullableid)valueforKeyPath:(NSString *)keyPath;  //通过KeyPath来设值

NSKeyValueCoding类别中其他的方法:

+(BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
-(BOOL)validateValue:(inoutid__nullable *__nonnull)ioValueforKey:(NSString *)inKeyerror:(outNSError **)outError;
//KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
-(NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回
-(nullableid)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
-(void)setValue:(nullableid)valueforUndefinedKey:(NSString *)key;
//和上一个方法一样,只不过是设值。
-(void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
-(NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。

在KVC中使用KeyPath

然而在开发过程中,一个类的成员变量有可能是其他的自定义类,你可以先用KVC获取出来再该属性,然后再次用KVC来获取这个自定义类的属性,但这样是比较繁琐的,对此,KVC提供了一个解决方案,那就是键路径KeyPath。

-(nullableid)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
-(void)setValue:(nullableid)valueforKeyPath:(NSString *)keyPath;  //通过KeyPath来设值
例子:
@interfaceAddress:NSObject
 
@end
@interfaceAddress()
@property(nonatomic,copy)NSString*country;
@end
@implementationAddress
@end
@interfacePeople:NSObject
@end
@interfacePeople()
@property(nonatomic,copy)NSString*name;
@property(nonatomic,strong)Address*address;
@property(nonatomic,assign)NSIntegerage;
@end
@implementationPeople
@end
intmain(intargc,constchar*argv[]){
    @autoreleasepool{
        People*people1=[Peoplenew];
        Address*add=[Addressnew];
        add.country=@"China";
        people1.address=add;
        NSString*country1=people1.address.country;
        NSString *country2=[people1valueForKeyPath:@"address.country"];
        NSLog(@"country1:%@   country2:%@",country1,country2);
        [people1setValue:@"USA"forKeyPath:@"address.country"];
        country1=people1.address.country;
        country2=[people1valueForKeyPath:@"address.country"];
        NSLog(@"country1:%@   country2:%@",country1,country2);
    }
    return0;
}
//打印结果
2016-04-1715:55:22.487KVCDemo[1190:82636]country1:China  country2:China
2016-04-1715:55:22.489KVCDemo[1190:82636]country1:USA  country2:USA
上面的代码简单在展示了KeyPath是怎么用的。如果你不小心错误的使用了key而非KeyPath的话,KVC会直接查找address.country这个属性,很明显,这个属性并不存在,所以会再调用UndefinedKey相关方法。而KVC对于KeyPath是搜索机制第一步就是分离key,用小数点.来分割key,然后再像普通key一样按照先前介绍的顺序搜索下去。

KVC如何处理异常

KVC中最常见的异常就是不小心使用了错误的Key,或者在设值中不小心传递了nil的值,KVC中有专门的方法来处理这些异常。
如果你不小心传了nil,KVC会调用setNilValueForKey:方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。

 [people1setValue:nilforKey:@"age"]
  ***Terminatingappduetouncaughtexception'NSInvalidArgumentException',reason:'[ setNilValueForKey]: could not set nil as the value for the key age.'// 调用setNilValueForKey抛出异常

如果重写setNilValueForKey:就没问题了

@implementationPeople
 
-(void)setNilValueForKey:(NSString *)key{
    NSLog(@"不能将%@设成nil",key);
}
 
@end
//打印出
2016-04-1716:19:55.298KVCDemo[1304:92472]不能将age设成nil

KVC处理非对象和自定义对象

不是每一个方法都返回对象,但是valueForKey:总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。这两个类会处理从数字,布尔值到指针和结构体任何类型。然后开以者需要手动转换成原来的类型。尽管valueForKey:会自动将值类型封装成对象,但是setValue:forKey:却不行。你必须手动将值类型转换成NSNumber或者NSValue类型,才能传递过去。

对于自定义对象,KVC也会正确以设值和取值。因为传递进去和取出来的都是id类型,所以需要开发者自己担保类型的正确性,运行时Objective-C在发送消息的会检查类型,如果错误会直接抛出异常。

KVC与容器类

@interfacedemo:NSObject
@property(nonatomic,strong)NSMutableArray*arr;
@end
@implementationdemo
-(id)init{
    if(self==[superinit]){
        _arr=[NSMutableArraynew];
        [selfaddObserver:selfforKeyPath:@"arr"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOldcontext:nil];
    }
    returnself;
}
-(void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void*)context{
    NSLog(@"%@",change);
}
-(void)dealloc{
    [selfremoveObserver:selfforKeyPath:@"arr"];//一定要在dealloc里面移除观察
}
-(void)addItem{
    [_arraddObject:@"1"];
}
-(void)addItemObserver{
    [[selfmutableArrayValueForKey:@"arr"]addObject:@"1"];
}
-(void)removeItemObserver{
    [[selfmutableArrayValueForKey:@"arr"]removeLastObject];
}
@end
然后再:
demo*d=[demonew];
[daddItem];
[daddItemObserver];
[dremoveItemObserver];
 
打印结果
2016-04-1817:48:22.675KVCDemo[32647:505864]{
    indexes="[number of indexes: 1 (in 1 ranges), indexes: (1)]";
    kind=2;
    new=    (
        1
    );
}
2016-04-1817:48:22.677KVCDemo[32647:505864]{
    indexes="[number of indexes: 1 (in 1 ranges), indexes: (1)]";
    kind=3;
    old=    (
        1
    );
}

从上面的代码可以看出,当只是普通地调用[_arr addObject:@"1"]时,Observer并不会回调,只有[[self mutableArrayValueForKey:@"arr"] addObject:@"1"];这样写时才能正确地触发KVO。打印出来的数据中,可以看出这次操作的详情,kind可能是指操作方法(我还不是很确认),old和new并不是成对出现的,当加添新数据时是new,删除数据时是old

而对于无序的容器,可以用下面的方法:

-(NSMutableSet *)mutableSetValueForKey:(NSString *)key;
同样,它们也有对应的keyPath版本
-(NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
-(NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

KVC和字典

当对NSDictionary对象使用KVC时,valueForKey:的表现行为和objectForKey:一样。所以使用valueForKeyPath:用来访问多层嵌套的字典是比较方便的。

KVC里面还有两个关于NSDictionary的方法

-(NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
-(void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;

dictionaryWithValuesForKeys:是指输入一组key,返回这组key对应的属性,再组成一个字典。
setValuesForKeysWithDictionary是用来修改Model中对应key的属性。下面直接用代码会更直观一点

Address*add=[Addressnew];
add.country=@"China";
add.province=@"Guang Dong";
add.city=@"Shen Zhen";
add.district=@"Nan Shan";
NSArray*arr=@[@"country",@"province",@"city",@"district"];
NSDictionary*dict=[adddictionaryWithValuesForKeys:arr];//把对应key所有的属性全部取出来
NSLog(@"%@",dict);
 
NSDictionary*modifyDict=@{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};
[addsetValuesForKeysWithDictionary:modifyDict];            //用key Value来修改Model的属性
NSLog(@"country:%@  province:%@ city:%@",add.country,add.province,add.city);
 
//打印结果
2016-04-1911:54:30.846KVCDemo[6607:198900]{
    city="Shen Zhen";
    country=China;
    district="Nan Shan";
    province="Guang Dong";
}
2016-04-1911:54:30.847KVCDemo[6607:198900]country:USA  province:californiacity:Losangle

KVC的使用

KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,甚至实现很多难以想像的功能,KVC也是许多iOS开发黑魔法的基础。下面我来列举iOS开发中KVC的使用场景

动态地取值和设值

利用KVC动态的取值和设值是最基本的用途了。相信每一个iOS开发者都能熟练掌握,

用KVC来访问和修改私有变量

对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的,请参考本文前面的Dog类的例子。

Model和字典转换

  1. -(id) initWithDictionary:(NSMutableDictionary*) jsonObject 
  2.     if((self = [super init])) 
  3.     { 
  4.         [self init]; 
  5.         [self setValuesForKeysWithDictionary:jsonObject]; 
  6.     } 
  7.     return self; 
  1. - (void)setValue:(id)value forUndefinedKey:(NSString *)key 
  2.     if([key isEqualToString:@"nameXXX"]) 
  3.         self.name = value; 
  4.     if([key isEqualToString:@"ageXXX"]) 
  5.         self.age = value; 
  6.     else 
  7.         [super setValue:value forKey:key]; 

修改一些控件的内部属性

这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些空间的API,这样我们就无法正常地访问和修改这些控件的样式。而KVC在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField中的placeHolderText了。
下面演示如果修改placeHolder的文字样式。这里的关键点是如果获取你要修改的样式的属性名,也就是key或者keyPath名。





0 0
原创粉丝点击