一、KVC及KVO的介绍
KVC:即Key-Value-Coding,用于键值编码。
KVO:即Key-Value-Observing,用于键值监听。
KVC:
- 只针对类属性,设置键值对
- 设置 setValue: forKey: ,即forKey只能为类属性
- 取值 valueForKey
一个非正式的Protocol,提供一种机制来间接访问对象的属性。而不是通过调用Setter、Getter方法访问。
无论是Swift还是Objective-C,KVC的定义都是对NSObject的扩展来实现的(Objective-c中有个显式的NSKeyValueCoding类别名,而Swift没有,也不需要)所以对于所有继承了NSObject在类型,都能使用KVC(一些纯Swift类和结构体是不支持KVC的),下面是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来设值
KVC可以在运行时动态的访问和修改对象的属性,而不是在编译时确定。允许通过Key值访问对象属性,或者给对象赋值。
KVO:
KVO是Objective-C对观察这设计模式的一种实现。(另一种是:通知机制(notification))。
KVO提供一种机制,指定一个被观察对象,当对象某个属性发生改变时,对象会获得通知。当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
- 利用KVC对类属性进行设置
- 注册observing对象** addObserver:forKeyPath:options:context:**
- 观察者必须重写方法 observeValueForKeyPath:ofObject:change:context:
KVO是建立在KVC之上的,KVO能够观察一个对象的KVC key-path值的变化。
二、KVC及KVO的使用
<一>、kvc的使用
KVC在内部寻找key的是顺序:
- KVC机制,程序优先找set方法,set<key>
- 如果没有set方法,** accessInstanceVariablesDirectly 方法如果返回Yes,KVC会执行setValue:forUNdefinedKey:方法,找成员变量_<key>(默认返回YES)如果重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUNdefinedKey:**方法
- 如果即没有set<Key>:方法,也没有_<key>成员变量,KVC机制会搜索is<Key>的成员变量,,如果该类即没有set<Key>:方法,也没有<key>和_is<Key>成员变量,KVC机制再会继续搜索<key>和is<Key>的成员变量。再给它们赋值。
- 如果都没有,则会执行setValue:forUNdefinedKey:方法
#import "ViewController.h"
@interface ViewController ()
{
NSString *testNoSetFunc;
}
@property (nonatomic, strong) NSString *testStr;//默认生成成员变量及get、set方法
@end
@implementation ViewController
//accessInstanceVariablesDirectly重写
+ (BOOL)accessInstanceVariablesDirectly{
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
return NO;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *test;//既没有成员变量也没有setter方法
//fuzhi
[self setValue:@"测试" forKey:@"test"];
[self setValue:@"测试" forKey:@"testNoSetFunc"];
[self setValue:@"测试" forKey:@"testStr"];
//取值
NSString *getTestNoSetFunc = [self valueForKey:@"testNoSetFunc"];
NSString *getTest = [self valueForKey:@"test"];
NSString *getTestStr = [self valueForKey:@"testStr"];
}
-(id)valueForUndefinedKey:(NSString *)key{
NSLog(@"该key不存在%@",key);
return nil;
}
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"该key不存在%@",key);
}
@end
如果一个类的成员变量是其他自定义类 可用valueForKeyPath 及
setValue:forKeyPath取值、赋值
valueForKeyPath
[类名 valueForKeyPath:@"类对象.类属性"];
setValue:forKeyPath:
[类名 setValue:@"值" forKeyPath:@"类对象.类属性"];
使用场景
<一>、kvc的使用
KVC是基于运行时的编程,极大的提高了灵活性,简化了代码。
(1)、动态地取值和赋值
(2)、用KVC来访问和修改私有变量
==对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的
(3)、Model和字典转换
(4)、修改一些控件的内部属性
//1.添加textfield
UITextField *textField = [[UITextField alloc]init];
[self.view addSubview:textField];
textField.frame = CGRectMake(10, 100, self.view.frame.size.width - 20, 30);
//2.利用KVC修改placeholder的颜色
textField.placeholder = @"请输入";
[textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
<二>、kvo的使用
如果赋值没有通过setter方法或者KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这时是不会触发kvo机制,更加不会调用回调方法的。
所以使用KVO机制的前提是遵循 KVO 的属性设置方式来变更属性值。
- 注册观察者,实施监听;
- 在回调方法中处理属性发生的变化;
- 移除观察者
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"测试";
//1.注册通知
[self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
//2.监听值改变
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"name"]&&object == self) {
//
self.view.backgroundColor = [UIColor redColor];
NSLog(@"name的新值%@",[change valueForKey:@"new"]);
NSLog(@"name的旧值%@",[change valueForKey:@"old"]);
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.name = @"更改name的值";
}
//3.移除通知
- (void)dealloc{
[self removeObserver:self forKeyPath:@"name"];
}
@end
KVO可监听自定义类属性的变化
//testModel.h
#import <Foundation/Foundation.h>
@interface testModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *number;
@end
//ViewController.m
#import "ViewController.h"
#import "testModel.h"
@interface ViewController ()
@property (nonatomic, strong) testModel *model;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
testModel *model = [[testModel alloc]init];
self.model = model;
model.name = @"测试";
//1.注册通知
[self.model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
//2.监听值改变
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"name"]&&object == self.model) {
//
self.view.backgroundColor = [UIColor redColor];
NSLog(@"name的新值%@",[change valueForKey:@"new"]);
NSLog(@"name的旧值%@",[change valueForKey:@"old"]);
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (![self.model.name isEqualToString:@"更改name的值"]) {
self.model.name = @"更改name的值";
}
}
//3.移除通知
- (void)dealloc{
[self.model removeObserver:self forKeyPath:@"name"];
}
@end