KVC是KeyValueCoding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性(实例变量)的机制。而不是通过调用Setter、Getter方法访问。(和Java中是使用反射机制去访问类的private权限的变量类似,很暴力的.这样做就会破坏类的封装性!)当使用KVO、Core Data、CocoaBindings、AppleScript(Mac支持)时,KVC是关键技术。
关键方法定义在:NSKeyValueCodingprotocol
KVC支持类对象和内建基本数据类型。
获取值
valueForKey:,传入NSString属性的名字。
valueForKeyPath:,传入NSString属性的路径,xx.xx形式。
valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。
修改值
setValue:forKey:
setValue:forKeyPath:
setValue:forUndefinedKey:
setNilValueForKey:当对非类对象属性设置nil时,调用,默认抛出异常。
一对多关系成员的情况
mutableArrayValueForKey:有序一对多关系成员 NSArray
mutableSetValueForKey:无序一对多关系成员 NSSet
实例
Person.h
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Person : NSObject
{
@private
NSString *_name;
NSInteger _age;
Dog *_dog;
}
- (void)setName:(NSString *)name;
@end
Person.m
#import "Person.h"
@implementation Person
- (void)setName:(NSString *)name
{
_name = name;
NSLog(@"KVC在赋值是会优先调用setter方法");
}
- (NSString *)description
{
return [NSString stringWithFormat:@"name = %@,dog = %@, age = %ld, dog.weight = %.2f", _name, _dog, _age, _dog.weight];
}
@end
Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic, assign) double weight;
@end
Dog.m
#import "Dog.h"
@implementation Dog
@end
Main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"
/**
KVC:即使一个类的属性是私有的,而且也没有setter/getter方法,同样可以读写. 很暴力
相当于JAVA中的反射,破坏类的封装性
*/
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Dog *d = [[Dog alloc] init];
//设置值
//KVC设置值时,如果属性中有setter方法,则优先调用setter方法,如果没有则直接设置上去,getter方法类似
[p setValue:@"ly" forKey:@"name"];
//如果在设置Value时,没有对应的key,程序就会崩溃
[p setValue:d forKeyPath:@"dog"];
//[p setValue:@"0.5" forKey:@"dog.weight"];(错误)
[p setValue:@"0.5" forKeyPath:@"dog.weight"];
//设置基本数据类型
//这里需要将基本类型转化成NSNumber,在设置值的时候,会有自动解包,NSNumber会解包赋值给age
[p setValue:@23 forKeyPath:@"_age"];
NSDictionary *personDict = @{@"name" : @"sunny",
@"age" : @"22",
@"dog" : [[Dog alloc] init]};
Person *person = [[Person alloc] init];
// 字典转模型:setValuesForKeysWithDictionary
// 1>必须字典中对应的key和模型中对应的属性是一致
// 2>字典中存在的属性,在模型中必须有对应的属性
[person setValuesForKeysWithDictionary:personDict];
NSLog(@"person:%@", person);
//读取值
NSString *name = [p valueForKey:@"_name"];
NSLog(@"%@",name);
NSLog(@"%@",p);
}
return 0;
}
下面再来看一下KVC中强大的功能:键值路径
键值路径是对于一个类中有数组对象的属性进行便捷操作。
Author.h
#import <Foundation/Foundation.h>
@interface Author : NSObject
{
NSString *_name;
//一个作者对应多个出版书籍
NSArray *_issueBook;
}
@end
Author.m
#import "Author.h"
@implementation Author
@end
Book.h
#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject
{
Author *_author;
}
@property NSString *name;
@property float price;
@end
Book.m
#import "Book.h"
@implementation Book
@end
Main.m
#import <Foundation/Foundation.h>
#import "Author.h"
#import "Book.h"
/**
键值路径是对于一个类中有数组对象的属性进行便捷操作;
*/
int main(int argc, const char * argv[]) {
@autoreleasepool {
Author *author = [[Author alloc] init];
[author setValue:@"ly" forKey:@"name"];
Book *book1 = [[Book alloc] init];
book1.name = @"iOS开发进阶";
book1.price = 98.00f;
Book *book2 = [[Book alloc] init];
book2.name = @"Android内核解析";
book2.price =88.00f;
NSArray *books = [NSArray arrayWithObjects:book1, book2, nil];
[author setValue:books forKey:@"issueBook"];
//基本数据类型会自动装箱成NSNumber,装到数组中;
//得到所有书籍的价格
NSArray *prices = [author valueForKeyPath:@"issueBook.price"];
NSLog(@"prices = %@", prices);
//获取数组的大小
NSNumber *count = [author valueForKeyPath:@"issueBook.@count"];
NSLog(@"count = %@", count);
//获取书籍价格的总和
NSNumber *priceSum = [author valueForKeyPath:@"issueBook.@sum.price"];
NSLog(@"priceSum = %@", priceSum);
//获取书籍价格的平均值
NSNumber *priceAvg = [author valueForKeyPath:@"issueBook.@avg.price"];
NSLog(@"priceAvg = %@", priceAvg);
//获取书籍价格的最大值和最小值
NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"];
NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"];
NSLog(@"max = %@, min = %@", max, min);
}
return 0;
}
一,KVO概述
KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
二,使用方法
系统框架已经支持KVO,所以程序员在使用的时候非常简单。
1. 注册,指定被观察者的属性,
2. 实现回调方法
3. 移除观察
三,实例:
现在有一个小孩类,他有两个属性:开心值,饥饿值,然后还有一个护士类,用来监听孩子类的这两个属性值的
Children.h
#import <Foundation/Foundation.h>
/**
定义一个小孩类,它由两个属性,开心值和饥饿值
*/
@interface Children : NSObject
@property (nonatomic, assign) NSInteger happyValue;
@property (nonatomic, assign) NSInteger hurryValue;
@end
Children .m
#import "Children.h"
@implementation Children
- (instancetype)init
{
self = [super init];
if (self) {
//启动定时器 每隔一秒去修改孩子类的值
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
self.happyValue = 100;
}
return self;
}
- (void)timerAction:(NSTimer *)timer
{
//使用setter方法修改属性值才能触发KVO
self.hurryValue = --_hurryValue;
self.happyValue = --_happyValue;
}
@end
Nure.h
#import <Foundation/Foundation.h>
@class Children;
@interface Nure : NSObject
@property (nonatomic, strong) Children *children;
- (instancetype)initWithChildren:(Children *)children;
@end
Nure.m
#import "Nure.h"
#import "Children.h"
@implementation Nure
- (instancetype)initWithChildren:(Children *)children
{
if (self = [super init]) {
_children = children;
//观察孩子的happyValue
//使用KVO为_children对象添加一个观察者,用于观察监听happyValue属性值是否被修改
[_children addObserver:self forKeyPath:@"happyValue" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"context"];
//观察孩子的hurryValue
[_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"context"];
}
return self;
}
//触发方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
//通过打印输出change,可以看到对应的key
NSLog(@"%@",change);
//通过keyPath来判断不同属性的观察者
if ([keyPath isEqualToString:@"happyValue"]) {
//这里change中有old和new的值是因为在调用addObserver方法时,用到了
//options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
NSNumber *happyValue = [change objectForKey:@"new"];//修改之后的最新值
NSInteger value = [happyValue integerValue];
if (value < 80) {
NSLog(@"孩子的开心值小于90");
}
}else if ([keyPath isEqualToString:@"hurryValue"])
{
NSNumber *hurryValue = [change objectForKey:@"new"];//修改之后的最新值
NSInteger value = [hurryValue integerValue];
if (value < 0) {
NSLog(@"孩子的饥饿值小于0");
}
}
//打印addObserver方法的context参数
NSLog(@"%@", context);
//使用KVO去修改属性的值,也会触发事件
}
- (void)dealloc
{
//移除观察者(这个并不属于KVO的内容)
[_children removeObserver:self forKeyPath:@"happyValue"];
[_children removeObserver:self forKeyPath:@"hurryValue"];
}
@end
Main.m
#import <Foundation/Foundation.h>
#import "Children.h"
#import "Nure.h"
/**
KVO 这种机制在JAVA中是不存在的
它的作用就是用来监听类中属性值的变化,实现原理是观察者模式,当然我们也可以使用观察者模式在java中实现这样的机制
*/
int main(int argc, const char * argv[]) {
@autoreleasepool {
Children *children = [[Children alloc] init];
Nure *nure = [[Nure alloc] initWithChildren:children];
//启动
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
小结:
KVO/KVC这种编码方式使用起来很简单,很适用与datamodel修改后,引发的UIVIew的变化这种情况,就像上边的例子那样,当更改属性的值后,监听对象会立即得到通知。