KVC: NSKeyValueCoding的缩写(键值编码)非正式协议(Informal Protocol)
非正式协议:不需要遵循
能做什么?
可以通过name(key)去隐式地访问某个对象的属性;
注意点:name(key)必须和属性名字一样
适用场景(什么样的类型满足KVC): 只要继承NSObject的类都是满足KVC
显示地访问某个对象的属性的方式:
方式一: setter/getter方法(accessor methods)
方式二:点语法直接访问(读/写)
怎么做?
方式一:
设置属性值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
获取属性值
- (id)valueForKey:(NSString *)key;
方式二:
设置属性值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
获取属性值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//先用init方式初始化person对象
TRPerson *firstPerson = [[TRPerson alloc] initWithName:@"Maggie" withAge:18];
//点语法获取
NSLog(@"使用点语法获取名字:%@;年龄:%d", firstPerson.name, firstPerson.age);
//KVC方式一获取(针对都是OC对象类型)
/* 注意点:
1.key和属性名字一模一样;
2.重写方法,获取一个不存在的属性的值
3.给C语言的基本类型设置为nil,会抛异常;需要重写方法,给定初始值
*/
NSString *nameFromKVC = [firstPerson valueForKey:@"name"];
NSLog(@"使用KVC方式一获取名字:%@;年龄:%@", nameFromKVC, [firstPerson valueForKey:@"age"]);
//如果使用KVC方式取一个不存在的属性值(给定不存在的key)
NSLog(@"使用KVC方式获取不存在的属性值:%@", [firstPerson valueForKey:@"salary"]);
//使用KVC的方式设置name/age
TRPerson *secondPerson = [TRPerson new];
[secondPerson setValue:@"Bob" forKey:@"name"];
[secondPerson setValue:nil forKey:@"age"];
NSLog(@"使用KVC方式一设置name:%@; 设置age:%@",[secondPerson valueForKey:@"name"], [secondPerson valueForKey:@"age"]);
KVC设置和获取属性值的方式二(keyPath)
TRPerson *thirdPerson = [[TRPerson alloc] initWithName:@"Jonny" withAge:20];
TRAddress *address = [TRAddress new];
thirdPerson.address = address;
//使用KVC方式设置
[thirdPerson setValue:@"北京市-朝阳区-中国" forKeyPath:@"address.simpleAddress"];
[thirdPerson setValue:@"潘家园-xxx大厦-6层" forKeyPath:@"address.detailAddress"];
//使用KVC方式取值
NSLog(@"简单地址:%@; 详细地址:%@",[thirdPerson valueForKeyPath:@"address.simpleAddress"], [thirdPerson valueForKeyPath:@"address.detailAddress"]);
Person.h
/** 名字*/
@property (nonatomic, copy) NSString *name;
/** 年龄*/
@property (nonatomic, assign) int age;
/** 地址*/
@property (nonatomic, strong) TRAddress *address;
//初始化方式
- (instancetype)initWithName:(NSString *)name withAge:(int)age;
Person.m
- (instancetype)initWithName:(NSString *)name withAge:(int)age {
if (self = [super init]) {
self.name = name;
self.age = age;
}
return self;
}
//重写valueForUndefinedKey方法,返回警告/文本log
//不会再报NSUnknownKeyException异常错误
- (id)valueForUndefinedKey:(NSString *)key {
return @"当前的这个key所对应的属性不存在";
}
//如果设置某个key对应属性的值为空(设置c语言的基本的类型;翻译标量类型),自动调用下面的方法;手动设置一个初始值
- (void)setNilValueForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
//给定初始的年龄值
[self setValue:@18 forKey:@"age"];
}
}
Address.h
<span style="font-size:14px;">/** 简单地址*/
@property (nonatomic, copy) NSString *simpleAddress;
/** 详细地址*/
@property (nonatomic, copy) NSString *detailAddress;
</span>
总结:
1. KVC利用动态语言(OC/Ruby...)运行的机制
2. KVC两种设置和获取的方式(key/keyPath); 遵循原则:key/keyPath中包含值和属性名字一模一样
3.优势:
a. 可以使用KVC方式对私有属性进行访问(设置/获取)
b. 可以访问(设置/获取)嵌套属性的值(只能用keyPath)
KVO:(NSKeyValueObserving: 键值观察机制)
适用范围:只要是继承与NSObject所有类,适用KVO机制
做什么(机制)?
1. 监听某个/某些属性值的变化,做出响应的机制;
2. 当被观察者的值发生变化,会自动发送通知给观察者,进而观察者做出相应的反应(更新数据到界面…)
如何做? 执行四个步骤
步骤一:明确观察者和被观察者;创建两个对象
步骤二:被观察者对象需要调用addObserver方法,注册观察者
步骤三:观察者需要实现observeValueForKeyPath方法,获取观察(监听)的值(包含初始值/老值/改变后的新值)
步骤四:在观察者对象和被观察者对象释放前,需要移除观察者 (需要在适当的时候移除,否则编译器会抛异常)
KVO和NSNotification的区别?
1. 前者是观察者和被观察者对象直接联系(监听和被监听联系);后者监听对象和被监听对象和通知中心建立联系
2. 前者当监听到值变化,自动触发方法; 后者需要自己写监听的触发方法(selector方法)
ViewController.m
//1.创建观察者对象;创建被观察者对象
TRPerson *person = [TRPerson new];
TRBank *bank = [TRBank new];
//2.使用KVC方式给被观察者对象设置余额
[bank setValue:@1000.5 forKey:@"accountBalance"];
//3.被观察者调用addObserver方法,添加观察动作
/* 参数一:指定观察对象的属性名字(属性路径)
参数二:给定观察的枚举值(接收到通知的时候,获取新值/老值/初始值)
描述:注册一个观察者对象Register
NSKeyValueObservingOptionInitial:当给定余额初始值的时候,自动发送通知
*/
[bank addObserver:person forKeyPath:@"accountBalance" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial) context:nil];
//4.需要使用KVC指定变化的值
[bank setValue:@2000 forKey:@"accountBalance"];
//移除观察者(在观察者对象和被观察者对象释放之前,移除观察者对象)
[bank removeObserver:person forKeyPath:@"accountBalance"];
Person.m
//只要观察值发生变化,系统自动发送通知到TRPerson(调用下面的方法)
/* keyPath:观察key路径
object:被观察者对象
change:包含被观察对象的新值(New)/老值(Old)/初始化值(Inital)
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
//判断哪个属性发生变化两种方式(context的值;keyPath的值)
if ([keyPath isEqual:@"accountBalance"]) {
//观察者已经观察到余额变化(更新界面控件值)
NSLog(@"object:%@; change New:%@; change Old:%@", object, [change objectForKey:NSKeyValueChangeNewKey], [change objectForKey:NSKeyValueChangeOldKey]);
}
}
Bank.h
@interface TRBank : NSObject {
//账户余额
float accountBalance;
}
@end