关于KVC

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

KVC最为重要的四个方法:

- ( nullable id ) valueForKey : ( NSString * ) key ;                            //直接通过Key来取值
- ( void ) setValue : ( nullable id ) value forKey : ( NSString * ) key ;            //通过Key来设值
- ( nullable id ) valueForKeyPath : ( NSString * ) keyPath ;                    //通过KeyPath来取值
- ( void ) setValue : ( nullable id ) value forKeyPath : ( NSString * ) keyPath ;    //通过KeyPath来设值

NSKeyValueCoding类别中其他的方法:

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

在KVC中使用KeyPath

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

- ( nullable id ) valueForKeyPath : ( NSString * ) keyPath ;                    //通过KeyPath来取值
- ( void ) setValue : ( nullable id ) value forKeyPath : ( NSString * ) keyPath ;    //通过KeyPath来设值
例子:
@ interface Address : NSObject
 
@ end
@ interface Address ( )
@ property ( nonatomic , copy ) NSString* country ;
@ end
@ implementation Address
@ end
@ interface People : NSObject
@ end
@ interface People ( )
@ property ( nonatomic , copy ) NSString* name ;
@ property ( nonatomic , strong ) Address* address ;
@ property ( nonatomic , assign ) NSInteger age ;
@ end
@ implementation People
@ end
int main ( int argc , const char * argv [ ] ) {
     @ autoreleasepool {
         People* people1 = [ People new ] ;
         Address* add = [ Address new ] ;
         add . country = @ "China" ;
         people1 . address = add ;
         NSString* country1 = people1 . address . country ;
         NSString * country2 = [ people1 valueForKeyPath : @ "address.country" ] ;
         NSLog ( @ "country1:%@   country2:%@" , country1 , country2 ) ;
         [ people1 setValue : @ "USA" forKeyPath : @ "address.country" ] ;
         country1 = people1 . address . country ;
         country2 = [ people1 valueForKeyPath : @ "address.country" ] ;
         NSLog ( @ "country1:%@   country2:%@" , country1 , country2 ) ;
     }
     return 0 ;
}
//打印结果
2016 - 04 - 17 15 : 55 : 22.487 KVCDemo [ 1190 : 82636 ] country1 : China   country2 : China
2016 - 04 - 17 15 : 55 : 22.489 KVCDemo [ 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: 方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。

  [ people1 setValue : nil forKey : @ "age" ]
   * * * Terminating app due to uncaught exception 'NSInvalidArgumentException' , reason : '[ setNilValueForKey]: could not set nil as the value for the key age.' // 调用setNilValueForKey抛出异常

如果重写 setNilValueForKey: 就没问题了

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

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

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

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

KVC与容器类

@ interface demo : NSObject
@ property ( nonatomic , strong ) NSMutableArray* arr ;
@ end
@ implementation demo
- ( id ) init {
     if ( self == [ super init ] ) {
         _arr = [ NSMutableArray new ] ;
         [ self addObserver : self forKeyPath : @ "arr" options : NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context : nil ] ;
     }
     return self ;
}
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath ofObject : ( id ) object change : ( NSDictionary * ) change context : ( void * ) context {
     NSLog ( @ "%@" , change ) ;
}
- ( void ) dealloc {
     [ self removeObserver : self forKeyPath : @ "arr" ] ; //一定要在dealloc里面移除观察
}
- ( void ) addItem {
     [ _arr addObject : @ "1" ] ;
}
- ( void ) addItemObserver {
     [ [ self mutableArrayValueForKey : @ "arr" ] addObject : @ "1" ] ;
}
- ( void ) removeItemObserver {
     [ [ self mutableArrayValueForKey : @ "arr" ] removeLastObject ] ;
}
@ end
然后再 :
demo* d = [ demo new ] ;
[ d addItem ] ;
[ d addItemObserver ] ;
[ d removeItemObserver ] ;
 
打印结果
2016 - 04 - 18 17 : 48 : 22.675 KVCDemo [ 32647 : 505864 ] {
     indexes = "[number of indexes: 1 (in 1 ranges), indexes: (1)]" ;
     kind = 2 ;
     new =      (
         1
     ) ;
}
2016 - 04 - 18 17 : 48 : 22.677 KVCDemo [ 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 = [ Address new ] ;
add . country = @ "China" ;
add . province = @ "Guang Dong" ;
add . city = @ "Shen Zhen" ;
add . district = @ "Nan Shan" ;
NSArray* arr = @ [ @ "country" , @ "province" , @ "city" , @ "district" ] ;
NSDictionary* dict = [ add dictionaryWithValuesForKeys : arr ] ; //把对应key所有的属性全部取出来
NSLog ( @ "%@" , dict ) ;
 
NSDictionary* modifyDict = @ { @ "country" : @ "USA" , @ "province" : @ "california" , @ "city" : @ "Los angle" } ;
[ add setValuesForKeysWithDictionary : modifyDict ] ;              //用key Value来修改Model的属性
NSLog ( @ "country:%@  province:%@ city:%@" , add . country , add . province , add . city ) ;
 
//打印结果
2016 - 04 - 19 11 : 54 : 30.846 KVCDemo [ 6607 : 198900 ] {
     city = "Shen Zhen" ;
     country = China ;
     district = "Nan Shan" ;
     province = "Guang Dong" ;
}
2016 - 04 - 19 11 : 54 : 30.847 KVCDemo [ 6607 : 198900 ] country : USA   province : california city : Los angle

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值