Objective-C之KVC(键值编码)详解
2015-07-24 07:09
573 查看
一、简单的KVC
最基本的KVC由NSKeyValueCoding协议提供支持的,最基本的操作属性的两个监听方法如下:
setValue:属性值 forKey:属性名,作用是为指定属性设置值。
valueForKey:属性名 ,作用是获取指定的值。
有如下程序:
在如下程序中测试。
在KVC编程方式中,无论调用setValue:ForKey:方法,还是调用valueForKey:方法,都是通过NSString对象来指定被操作属性的,其中forKey标签用于传入属性名。
对于“setValue:属性值 forKey:@”name””代码,底层的执行机制如下:
(1)程序优先调用“setName:属性值”代码通过setter方法完成设置。
(2)如果该类没有setName方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实现上就是对_name成员变量赋值。
(3)如果该类既没有setName方法,也没有定义_name成员变量,那么KVC机制会搜索该类中name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是对name成员变量赋值。
(4)如果上面3步没有找到,那么系统会执行该对象的setValue: forUndefinedKey:方法,这个方法的实现就是引发一个异常,这个异常将会导致程序因为异常结束。
对于“valueForKey:@”name””代码,底层的执行机制如下:
(1)程序优先考虑调用“name;”代码来获取getter方法的返回值。
(2)如果该类没有name方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实现上就是返回_name成员变量的值。
(3)如果该类既没有name方法,也没有定义_name成员变量,那么KVC机制会搜索该类中name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是返回name成员变量的值。
(4)如果上面3步没有找到,那么系统会执行该对象的valueforUndefinedKey:方法,这个方法的实现就是引发一个异常,这个异常将会导致程序因为异常结束。
如下程序定义了一个Dog类的接口部分。
上面Dog类的接口部分定义了两个成员变量:name和_name,但是没有定义setName:和name方法。按照前面的介绍,如果程序通过KVC方式来操作name属性,那么程序实际操作的将是_name成员变量。
接下来是Dog的实现部分,实现部分定义了一个age成员变量。
下面的程序将会使用KVC方式来操作name和age。
//创建Dog对象 Dog*dog = [[Dog alloc] init]; //使用KVC方式为name赋值 //1.setName:方法, 2._name成员变量,3.name成员变量 [dog setValue:@"旺财" forKey:@"name"]; NSLog(@"dog->name:%@",dog->name); NSLog(@"dog->_name为:%@",dog->_name); //使用KVC方式对age赋值,将会导致类实现部分定义的age成员变量被赋值 [dog setValue:[NSNumber
numberWIthInt:2] forKey:@"age"]; NSLog(@"dog的age为:%@",[dog valueForKey:@"age"]);
}
}
上面程序代码[dog setValue:@"旺旺" forKey:@"name"];执行时,程序通过KVC方式对dog的name属性赋值,但由于Dog类并不存在setName方法,因此程序接下来尝试对dog对象_name成员变量赋值,在代码dog->_name时,将可以看到输出“旺财”,但dog->name将输出空。
程序最后通过KVC方式对age属性赋值并访问age属性,由于Dog在类实现部分定义了age成员变量,因此,KVC可以正常工作,编译、运行程序,可以看到如下输出:
dog->name:(null)
dog->_name:旺旺
dog的age:5
二、处理不存在的key
前面已经提到,当使用KVC方式操作属性时,这些属性可能并不存在-既不存在对应的setter、getter方法,也不存在对应的成员变量,KVC将会自动调用setValue: forUndefinedKey:和valueforUndefinedKey:方法-但系统默认实现的这两个方法仅是引发异常,并没有进行特别处理。
例如,如下程序定义了一个Apple类,该类没有定义任何成员变量和方法。
在AppleTest.m文件中,通过KVC方式来操作Apple对象的name属性。
上面程序代码操作了Apple对象并不存在的name属性。编译、运行该程序,可以看到程序输出异常:
上面的异常信息提示:程序尝试设置的name不存在,因此,程序引发了NSUnknownKeyException异常。
三、处理nil值
当调用KVC来设置对象的属性时,如果属性的类型是基本类型(如int、float、double),且传入了对应类型的值,那么程序可以正确地进行设置。但如果尝试为基本类型的属性设置为一个nil,则会导致什么结果呢?KVC会把这个nil当成0还是当成1?
如下程序定义了Item类的接口部分Item.h。
上面Item类中定义了两个属性,其中,name的类型是NSString,这个属性可以接受nil值,price属性的类型是int,它只可能接受int类型的值,不能接受nil值。
使用如下程序尝试将Item对象的name、price设置为nil值,看看程序执行结果。
在上面的程序中,尝试将NSString类型的name属性值设置为nil,这是合法的,完全可以正常执行,但是尝试将int类型的price设置为nil时,程序就要有可能引发错误。编译、运行该程序,可以看到如下输出:
从上面的提示结果来看,程序引发了一个NSinvalidArgumentException异常,这是由于int类型的属性不能接受nil值所导致的。上面这段提示信息实际上是由程序中的setNilValueForKey:方法所产生的。也就是说。当程序尝试为某个属性值设置nil值时,如果该属性并不接受nil值,那么程序将会自动执行该对象的setNilValueForKey:方法。
四、Key路径
KVC除了可操作对象的属性之外,还可操作对象的“复合属性”。所谓‘复合属性’,KVC机制将其称为key路径。比如,Order对象内包含了一个Item类型的item属性,而Item对象又包含了name属性和prie属性,那么KVC可以通过item。Name、item.price这种key路径来支持操作Order对象的item属性的name、price属性。
在KVC协议中操作key路径的方法如下。
setValue: forKeyPath:,根据key路径设置属性值。
valueForKeyPath:,根据key路径获取属性值。
如下程序定义了一个Order(订单)类,该Order类中包含了一个Item类型的属性。
在测试程序中OrderTest.m
上面程序使用了setValue: forKeyPath:和valueForKeyPath:设置复合属性并获取复合属性的值,这样的程序可以直接操作Order对象中的item属性的name属性,也可以直接操作Order对象中的item属性的price属性。
前面介绍了这么多内容,可能会有人感到疑惑:为什么要用KVC方式来操作呢?直接调用对象的setter和getter方法进行操作不可以吗?是不是KVC方式的性能更好呢?
实际上,通过KVC操作对象的性能要比通过setter和getter方法操作的性能更差,使用KVC的优势在于编程更加灵活,更适合提炼一些通用性质的代码。由于KVC方式允许通过字符串形式来操作对昂的属性,这个字符串既可以是变量,也可以是常量,因此具有极高的灵活性。
最基本的KVC由NSKeyValueCoding协议提供支持的,最基本的操作属性的两个监听方法如下:
setValue:属性值 forKey:属性名,作用是为指定属性设置值。
valueForKey:属性名 ,作用是获取指定的值。
有如下程序:
User.h #import <Foundation/Foundation.h> @interface User : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *pass; @property (nonatomic, strong) NSDate *birth; @end
在如下程序中测试。
UserTest.m #import "User.h" int main(int argc, char * argv[]){ @autoreleasepool{ //创建User对象 User *user = [[User alloc] init]; //使用KVC方式为name,pass和birth赋值 [user setValue:@"孙悟空" forKey:@"name"]; [user setValue:@"500" forKey:@"pass"]; [user setValue:[[NSDate alloc] init] forKey:@"birth"]; //使用KVC方式取值 NSLog(@"user的name为:%@",[user valueForKey:@"name"]); NSLog(@"user的pass为:%@",[user valueForKey:@"pass"]); NSLog(@"user的birth为:%@",[user valueForKey:@"birth"]); } }
在KVC编程方式中,无论调用setValue:ForKey:方法,还是调用valueForKey:方法,都是通过NSString对象来指定被操作属性的,其中forKey标签用于传入属性名。
对于“setValue:属性值 forKey:@”name””代码,底层的执行机制如下:
(1)程序优先调用“setName:属性值”代码通过setter方法完成设置。
(2)如果该类没有setName方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实现上就是对_name成员变量赋值。
(3)如果该类既没有setName方法,也没有定义_name成员变量,那么KVC机制会搜索该类中name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是对name成员变量赋值。
(4)如果上面3步没有找到,那么系统会执行该对象的setValue: forUndefinedKey:方法,这个方法的实现就是引发一个异常,这个异常将会导致程序因为异常结束。
对于“valueForKey:@”name””代码,底层的执行机制如下:
(1)程序优先考虑调用“name;”代码来获取getter方法的返回值。
(2)如果该类没有name方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实现上就是返回_name成员变量的值。
(3)如果该类既没有name方法,也没有定义_name成员变量,那么KVC机制会搜索该类中name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是返回name成员变量的值。
(4)如果上面3步没有找到,那么系统会执行该对象的valueforUndefinedKey:方法,这个方法的实现就是引发一个异常,这个异常将会导致程序因为异常结束。
如下程序定义了一个Dog类的接口部分。
@interface Dog : NSObject { NSString *name; NSString *_name; } @end
上面Dog类的接口部分定义了两个成员变量:name和_name,但是没有定义setName:和name方法。按照前面的介绍,如果程序通过KVC方式来操作name属性,那么程序实际操作的将是_name成员变量。
接下来是Dog的实现部分,实现部分定义了一个age成员变量。
#import “Dog.h” @implementation Dog { int age; }
下面的程序将会使用KVC方式来操作name和age。
<span style="font-size:14px;">DogTest.m #import "Dog.h" int main(int argc, char * argv[]){ <span style="font-size:14px;"><span style="font-family:宋体;"></span></span></span><pre name="code" class="objc"> @autoreleasepool{
//创建Dog对象 Dog*dog = [[Dog alloc] init]; //使用KVC方式为name赋值 //1.setName:方法, 2._name成员变量,3.name成员变量 [dog setValue:@"旺财" forKey:@"name"]; NSLog(@"dog->name:%@",dog->name); NSLog(@"dog->_name为:%@",dog->_name); //使用KVC方式对age赋值,将会导致类实现部分定义的age成员变量被赋值 [dog setValue:[NSNumber
numberWIthInt:2] forKey:@"age"]; NSLog(@"dog的age为:%@",[dog valueForKey:@"age"]);
}
}
上面程序代码[dog setValue:@"旺旺" forKey:@"name"];执行时,程序通过KVC方式对dog的name属性赋值,但由于Dog类并不存在setName方法,因此程序接下来尝试对dog对象_name成员变量赋值,在代码dog->_name时,将可以看到输出“旺财”,但dog->name将输出空。
程序最后通过KVC方式对age属性赋值并访问age属性,由于Dog在类实现部分定义了age成员变量,因此,KVC可以正常工作,编译、运行程序,可以看到如下输出:
dog->name:(null)
dog->_name:旺旺
dog的age:5
二、处理不存在的key
前面已经提到,当使用KVC方式操作属性时,这些属性可能并不存在-既不存在对应的setter、getter方法,也不存在对应的成员变量,KVC将会自动调用setValue: forUndefinedKey:和valueforUndefinedKey:方法-但系统默认实现的这两个方法仅是引发异常,并没有进行特别处理。
例如,如下程序定义了一个Apple类,该类没有定义任何成员变量和方法。
#import <Foundation/Foundation.h> @interface Apple: NSObject @end
在AppleTest.m文件中,通过KVC方式来操作Apple对象的name属性。
#import "Apple.h" int main(int argc, char * argv[]){ @autoreleasepool{ //创建Apple对象 Apple *apple = [[Apple alloc] init]; [apple setValue:@”小苹果” forKey:@”name”]; [apple valueForKey:@”name”]; } }
上面程序代码操作了Apple对象并不存在的name属性。编译、运行该程序,可以看到程序输出异常:
Terminating apple due to uncaught exception ‘NSUnknownKeyException’,reason:’[<Apple 0x7f8fd0c097b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.’
上面的异常信息提示:程序尝试设置的name不存在,因此,程序引发了NSUnknownKeyException异常。
三、处理nil值
当调用KVC来设置对象的属性时,如果属性的类型是基本类型(如int、float、double),且传入了对应类型的值,那么程序可以正确地进行设置。但如果尝试为基本类型的属性设置为一个nil,则会导致什么结果呢?KVC会把这个nil当成0还是当成1?
如下程序定义了Item类的接口部分Item.h。
#import <Foundation/Foundation.h> @interface Item: NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) int price; @end
上面Item类中定义了两个属性,其中,name的类型是NSString,这个属性可以接受nil值,price属性的类型是int,它只可能接受int类型的值,不能接受nil值。
使用如下程序尝试将Item对象的name、price设置为nil值,看看程序执行结果。
ItemTest.m #import "User.h" int main(int argc, char * argv[]){ <span style="font-family:宋体;"> </span>@autoreleasepool{ //创建Item对象 Item *item = [[Item alloc] init]; //使用KVC方式尝试将name,price设置为nil [item setValue:nil forKey:@"name"]; [item setValue:nil forKey:@"price"]; NSLog(@"item的name为:%@",[item valueForKey:@"name"]); NSLog(@"item的price为:%@",[item valueForKey:@"price"]); } }
在上面的程序中,尝试将NSString类型的name属性值设置为nil,这是合法的,完全可以正常执行,但是尝试将int类型的price设置为nil时,程序就要有可能引发错误。编译、运行该程序,可以看到如下输出:
Terminating apple due to uncaught exception ‘NSinvalidArgumentException’,reason:’[<Apple 0x7f8601c06ce0> setNilValueForKey:]: could not set nil as the value for the key price.’
从上面的提示结果来看,程序引发了一个NSinvalidArgumentException异常,这是由于int类型的属性不能接受nil值所导致的。上面这段提示信息实际上是由程序中的setNilValueForKey:方法所产生的。也就是说。当程序尝试为某个属性值设置nil值时,如果该属性并不接受nil值,那么程序将会自动执行该对象的setNilValueForKey:方法。
四、Key路径
KVC除了可操作对象的属性之外,还可操作对象的“复合属性”。所谓‘复合属性’,KVC机制将其称为key路径。比如,Order对象内包含了一个Item类型的item属性,而Item对象又包含了name属性和prie属性,那么KVC可以通过item。Name、item.price这种key路径来支持操作Order对象的item属性的name、price属性。
在KVC协议中操作key路径的方法如下。
setValue: forKeyPath:,根据key路径设置属性值。
valueForKeyPath:,根据key路径获取属性值。
如下程序定义了一个Order(订单)类,该Order类中包含了一个Item类型的属性。
Order.h #import <Foundation/Foundation.h> #import “Item.h” @interface Dog : NSObject //使用@property定义两个属性 @property (nonatomic, strong) Item *item; @property (nonatomic, assign) int amount; -(int) totalPrice; @end
在测试程序中OrderTest.m
#import "Order.h" int main(int argc, char * argv[]){ <span style="font-family:宋体;"> </span>@autoreleasepool{ //创建Order对象 Order *order = [[Order alloc] init]; //使用KVC方式为amount赋值 [order setValue:@"12" forKey:@"amount"]; [order setValue:[[Item alloc] init] forKey:@"item"]; //使用setValue: forKeyPath:设置item属性的name和price属性 [order setValue:@"鼠标" forKeyPath:@"item.name"]; [order setValue:[NSNumber numberWithint:20] forKeyPath:@"item.price"]; //使用valueForKeyPath:来获取复合属性值 NSLog(@”订单包含%@个%@,总价为%@”,[order valueForKey:@”amount”], [order valueForKeyPath:@”item.name”], [order valueForKey:@”totalPrice”]); } }
上面程序使用了setValue: forKeyPath:和valueForKeyPath:设置复合属性并获取复合属性的值,这样的程序可以直接操作Order对象中的item属性的name属性,也可以直接操作Order对象中的item属性的price属性。
前面介绍了这么多内容,可能会有人感到疑惑:为什么要用KVC方式来操作呢?直接调用对象的setter和getter方法进行操作不可以吗?是不是KVC方式的性能更好呢?
实际上,通过KVC操作对象的性能要比通过setter和getter方法操作的性能更差,使用KVC的优势在于编程更加灵活,更适合提炼一些通用性质的代码。由于KVC方式允许通过字符串形式来操作对昂的属性,这个字符串既可以是变量,也可以是常量,因此具有极高的灵活性。
相关文章推荐
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- js判断客户端是iOS还是Android等移动终端的方法
- IOS开发环境windows化攻略
- Objective-C的内省(Introspection)用法小结
- .net平台推送ios消息的实现方法
- 探讨Android与iOS,我们将何去何从?
- Android、iOS和Windows Phone中的推送技术详解
- IOS 改变键盘颜色代码
- Android和IOS的浏览器中检测是否安装某个客户端的方法
- javascript实现阻止iOS APP中的链接打开Safari浏览器
- Objective-C中常用的结构体NSRange,NSPoint,NSSize(CGSize),NSRect实例分析
- iOS开发之路--微博OAuth授权_取得用户授权的accessToken
- ios通过按钮点击异步加载图片
- ios中图像进行压缩方法汇总