前言
在应用开发中,我们经常需要使用到,观察者模式,能监听某些对象属性的变化,进行相应的操作。在iOS中,OC为我们提供了一套更加简洁优雅的观察方式——KVO。
KVO全称Key Value Observing,键值监听机制,由NSKeyValueObserving协议提供支持,NSObject类继承了该协议,所以NSObject的子类都可使用该方法。
KVO的使用
KVO的使用非常简单,我们只需要给对象的指定属性绑定监听,并设置监听类型。每当我们对指定的属性进行读写操作时,就会,OC就会调用observeValueForKeyPath
方法。我们在observeValueForKeyPath
中进行判断,确定是哪个对象的事件。
Person *person = [Person alloc]int];
/*
作用:给对象绑定一个监听器(观察者)
- Observer 观察者
- KeyPath 要监听的属性
- options 选项(方便在监听的方法中拿到属性值)
*/
[person addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
// 移除监听
[person removeObserver:person forKeyPath:@"name"];
/**
* 当监听的属性值发生改变
*
* @param keyPath 要改变的属性
* @param object 要改变的属性所属的对象
* @param change 改变的内容
* @param context 上下文
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
NSLog(@"%@------%@------%@", keyPath, object, change);
}
在上面的代码中,我们对Person
的name
属性进行监听。当我们读写name
时,系统就会调用observeValueForKeyPath
方法了。
KVO的原理概述
那么,系统是如何实现KVO的呢?
其实,每当我们使用KVO的addObserver
时,系统会默默地创建一个类。我们估且把它叫作:KVO_Person
。然后,系统动态地让其继承Person类,并添加方法:setName
和getName
。在KVO_Person
的setName
和getName
方法实现中,添加observeValueForKeyPath
的方法的调用。
此时,我们如果调用KVO_Person
的setName
和getName
,则会调用observeValueForKeyPath
。
但是,在KVO的使用中,我们对KVO_Person
是完全不知情的。那么,我们如何调用它的setName
和getName
呢?其实,在创建KVO_Person
时,我们就将Person
的isa指针,换成了KVO_Person
。
Runtime一瞥
isa是什么呢?这里涉及到iOS的Runtime知识。iOS的Runtime博大精深,在此只讲其中一小部分。
在iOS中,所有的方法调用,都是以发消息的方式进行的。
我们先来了解一下OC中类的结构
//对象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
//类
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
//方法
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
一个类接收到消息后,处理的流程是:
- 系统首先找到消息的接收对象,然后通过对象的isa找到它的类。
- 在它的类中查找method_list,是否有selector方法。
- 没有则查找父类的method_list。
- 找到对应的method,执行它的IMP。
- 转发IMP的return值。
所以,我们知道,当我们替换掉isa时,其实就是替换掉了消息的处理类。在上面的例子中,当我们调用addObserver
时,其实就是将Person
的isa替换为KVO_Person
。
手动实现一个小小的KVO
最后,让我们自己来实现一个小小的KVO,帮助读者理解iOS的Runtime特性和KVO的实现原理。
#import "ViewController.h"
#include <objc/runtime.h>
#import "Person.h"
@interface ViewController ()
@property(nonatomic, strong) UIButton* button;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.button = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
[_button setTitle:@"test" forState:UIControlStateNormal];
[_button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_button addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_button];
}
- (void)click{
Person *person = [[Person alloc]init];
//-------------------------------------------------------------------
// 创建类 KVO_Person
Class kvo_person = objc_allocateClassPair([Person class], "KVO_Person", 0);
// 添加属性
objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t backingivar = { "V", "_privateName" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
class_addProperty(kvo_person, "name", attrs, 3);
// 添加方法
class_addMethod(kvo_person, @selector(setName:), (IMP)nameSetter, "v@:@");
// 注册该类
objc_registerClassPair(kvo_person);
// 替换 isa
object_setClass(person, kvo_person);
//-------------------------------------------------------------------
[person setName:@"test"];
}
//set方法
void nameSetter(id self, SEL _cmd, NSString *newName) {
NSLog(@" -------------------- Person set start ");
Ivar ivar = class_getInstanceVariable([self class], "_privateName");
id oldName = object_getIvar(self, ivar);
if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
NSLog(@" -------------------- Person set end ");
}
@end
以上就是KVO的简单使用及其实现原理。
如有问题,欢迎指正。
网友评论