OC中具有很多的动态特性:例如动态类型,动态加载,动态绑定等,这里KVC(键值编码)与KVO(键值监听)也是比较常见的。
1:键值编码(KVC),用于动态属性赋值操作。
KVC的操作时由NSKeyValueCoding协议提供,NSObject对象就实现了这个协议,也就是说OC中几乎所有的对象都支持KVC操作,常用的KVC操作方法如下:
(1):
/**
KVC的一般操作用于简单路径
@param value 要设置的对象value
@param key 要设置的对象的key
*/
- (void)setValue:(nullable id)value forKey:(NSString *)key;
(2):
/**
KVC的常用方法二(用于复合路径)
给某个自定义对象的某个属性赋值就是复合路径
@param value 要设置的对象value
@param keyPath 要设置的对象的的属性路径
*/
-(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
下面通过一个例子理解KVC
//
// info.h
// KVC
//
// Created by MiHu on 2016/12/8.
// Copyright © 2016年 MiHu. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface info : NSObject
/**
属性:成绩
*/
@property(nonatomic,assign)NSInteger score;
@end
//
// info.m
// KVC
//
// Created by MiHu on 2016/12/8.
// Copyright © 2016年 MiHu. All rights reserved.
//
#import "info.h"
@implementation info
@end
// person.h
// KVC
//
// Created by MiHu on 2016/12/8.
// Copyright © 2016年 MiHu. All rights reserved.
//
#import <Foundation/Foundation.h>
@class info;
@interface person : NSObject{
@private
int _age;
}
/**
属性:姓名
*/
@property(nonatomic,strong)NSString *name;
/**
属性:info
*/
@property(nonatomic,strong)info * info;
/**
公共方法
*/
-(void)showMessage;
@end
// person.m
// KVC
//
// Created by MiHu on 2016/12/8.
// Copyright © 2016年 MiHu. All rights reserved.
//
#import "person.h"
@implementation person
-(void)showMessage
{
NSLog(@"name=%@,age=%d",_name,_age);
}
@end
// ViewController.m
// KVC
//
// Created by MiHu on 2016/12/8.
// Copyright © 2016年 MiHu. All rights reserved.
//
#import "ViewController.h"
#import "info.h"
#import "person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
person * per = [[person alloc]init];
[per setValue:@"jac" forKey:@"name"];
[per setValue:@20 forKey:@"age"];//注意即使是私有变量也能访问
[per showMessage];
//输出:name=jac,age=20
info * Info = [[info alloc]init];
per.info = Info;//注意这一步一定要先给account属性赋值,否则下面按路径赋值无法成功,因为account为nil,当然这一步骤也可以写成:[per setValue:account1 forKeyPath:@"account"];
[per setValue:@100 forKeyPath:@"info.score"];
NSLog(@"per's balance is :%.2f",[[per valueForKeyPath:@"info.score"] floatValue]);
//输出:per's balance is :100.00
}
@end
KVC使用起来比较简单,但是它如何查找一个属性进行读取呢?具体查找规则(假设现在要利用KVC对a进行读取):
如果是动态设置属性,则优先考虑调用setA方法,如果没有该方法则优先考虑搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的setValue:forUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确设置);
如果是动态读取属性,则优先考虑调用a方法(属性a的getter方法),如果没有搜索到则会优先搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的valueforUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确读取)
2: 键值监听KVO
KVO其实是一种观察者模式,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器 的视图组件就会被激发,激发时就会回调监听器自身。在ObjC中要实现KVO则必须实现NSKeyValueObServing协议,不过幸运的是 NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO
在OC中使用KVO的一般方法为:
/**
注册指向指定key路径的监听器
@param observer 谁来监听
@param keyPath key路径
@param options NSKeyValueObservingOptions
@param context 上下文
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
/**
移除指定Key路径的监听器
@param observer 谁来监听
@param keyPath key路径
@param context 上下文
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
/**
回调监听:
@param keyPath key路径
@param object 谁发起的
@param change NSKeyValueChangeKey
@param context 上下文
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
KVO的使用方法也很简单:
通过addObserver: forKeyPath: options: context:为被监听对象(它通常是数据模型)注册监听器重写监听器的observeValueForKeyPath: ofObject: change: context:方法
继续上面的例子介绍KVO 我们希望当对象的姓名发生改变的时会回调监听方法。那么此时姓名就作为我们的被监听对象,需要Person为它注册监听(使用addObserver: forKeyPath: options: context:);而人员Person作为监听器需要重写它的observeValueForKeyPath: ofObject: change: context:方法,当监听的成绩发生改变后会回调监听器Person监听方法(observeValueForKeyPath: ofObject: change: context:)
#import "ViewController.h"
#import "info.h"
#import "person.h"
@interface ViewController()
@property(nonatomic,strong)person * per;
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_per = [[person alloc]init];
info *Info = [[info alloc]init];
_per.info = Info;
[_per setValue:@"jac" forKey:@"name"];
[_per setValue:@90 forKeyPath:@"info.score"];
[_per addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
_per.name = @"jenny";
}
#pragma mark - 覆盖方法
#pragma mark 重写observeValueForKeyPath方法,当姓名变化后此处获得通知
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//只处理姓名改变
if([keyPath isEqualToString:@"name"])
{
NSLog(@"我改名字了");
}
}
#pragma mark 重写销毁方法
-(void)dealloc
{
[self removeObserver:self forKeyPath:@"name"];
}