KVC和KVO介绍
概述
1、KVC:键值编码,使用字符串的方式管理对象的成员、属性
2、KVO:键值监听,一种观察者模式,监听属性的改变,可实现UI和数据模型的分离
键值编码KVC(NSKeyValueCoding)
作用:动态管理对象属性的读写操作。
KVC的操作方法有是由NSKeyValueCoding协议提供,NSObject实现了这个协议,这意味着OC中几乎所有的对象都支持KVC操作。
使用方式:
- 简单路径
设值:[对象 setValue:属性值 forKey:属性名]
取值:[对象 valueForKey:属性名]
- 复合路径
设值:[对象 setValue:属性值 forKeyPath:属性路径]
取值:[对象 valueForKeyPath:属性路径]
示例:
我们定义一个Person类,并声明一个name的属性
Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (copy ,nonatomic) NSString *name;
@end
我们在主程序中使用KVC来控制name的取、设值操作
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person new];
[person setValue:@"LOLITA" forKey:@"name"];
NSLog(@"-->%@",[person valueForKey:@"name"]);
}
return 0;
}
运行结果
取值name事实上,KVC不仅可以设置person的属性,person的成员变量也可以操作,不管是公有还是私有
我们给Person类新增成员变量
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
@private
NSString *_sex;
@public
CGFloat _height;
}
@property (copy ,nonatomic) NSString *name;
@end
主程序中
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person new];
[person setValue:@"fale" forKey:@"sex"];
NSLog(@"-->%@",[person valueForKey:@"sex"]);
[person setValue:@"170.0" forKey:@"_height"];
NSLog(@"-->%@",[person valueForKey:@"height"]);
}
return 0;
}
运行结果
sex和height我们可以看到,Person的成员变量是_sex和_height,设值和取值的时候是否带"_"效果都是一样的,这跟KVC设置的机制有关
- 设值:优先寻找setter方法,如果没有该方法则寻找成员变量_a,如果仍然不存在,则寻找成员变量a,如果还是没找到则会调用这个类的setValue:forUndefinedKey:方法,并且不管这些方法、成员变量是私有还是公有的甚至是只读的都可以正确设置;
优先级为:setter方法-->_a-->a-->setValue:forUndefinedKey:方法
- 取值:优先寻找getter方法,如果没有找到该方法则寻找成员变量_a,如果仍然不存在,则寻找成员变量a,如果还是没有找到则会待用这个类valueforUndefinedKey:方法
优先级为:getter方法-->_a-->a-->valueforUndefinedKey:方法
补充:复合路径
如果Person中有一个Accont类,表示账户余额,要怎么使用KVC呢?
Account.h文件
#import <Foundation/Foundation.h>
@interface Account : NSObject
{
float _balance; // 账户余额
}
@end
Person.h文件
#import <Foundation/Foundation.h>
#import "Account.h"
@interface Person : NSObject
@property (strong ,nonatomic) Account *account; // 账户余额
@end
Person.m文件
#import "Person.h"
@implementation Person
-(instancetype)init{
if (self = [super init]) {
self.account = [Account new]; // 需要初始化该对象
}
return self;
}
@end
主程序中
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person new];
[person setValue:@"1234.6" forKeyPath:@"account.balance"];
NSLog(@"-->%@",[person valueForKeyPath:@"account.balance"]);
}
return 0;
}
运行结果:
复合路径使用键值监听KVO(NSKeyValueObserving)
作用:实现UI和数据模型的分离
KVO其实是一种观察者模式,可以监听某对象的属性值的变化,当该属性值发生变化时,作为监听者就可以做出相应的响应动作,利用这一模式,我们可以在MVC模式下实现Module和View的之间的通信,即当Module发生变化时,UI作为观察者就可以发生相应变化。
使用方式:
- 注册成观察者: addObserver: forKeyPath: options: context:
- 重写监听回调方法:observeValueForKeyPath: ofObject: change: context:
- 注销观察者:removeObserver: forKeyPath或者removeObserver: forKeyPath: context:
示例:
这里使用控制器作为观察者,观察某个模型的属性来掩饰KVO的使用
首先我们创建一个项目,并新建一个数据模型
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface DataModule : NSObject
{
NSString *_title; // 标题
}
@end
在ViewController里面定义一个label来表示UI上的显示
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 30)];
self.label.text = @"a value";
self.label.textAlignment = NSTextAlignmentCenter;
self.label.font = [UIFont systemFontOfSize:16];
self.label.center = self.view.center;
[self.view addSubview:self.label];
步骤一:初始化模型,并将控制器注册为该模型的观察者
self.module = [DataModule new];
[self.module addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil]; // 注册为观察者
步骤二:重写KVO的监听回调
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"title"]) {
if (object == self.module) {
self.label.text = [change objectForKey:@"new"];
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
}
步骤三:必须手动注销观察者
-(void)dealloc{
// 注销观察者
[self.module removeObserver:self forKeyPath:@"title"];
}
这样,当数据模型发生变化时,我们就可以监听到,并作UI上的改变了
// 3秒之后,改变数据模型title的值
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.module setValue:@"a new value" forKey:@"title"];
});
运行结果
KVO示例注意:一定要在合适的时间移除观察者
参考资料