KVO
原理
-
基本使用
//添加监听 [self addObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(nullable void *)#>] //参数详解: Observer:观察者对象,一般self; keyPath:观察的属性,字符串属性,一般采用系统的反射机制 NSStringFromSelector(@selector(keypath)) options:枚举 NSKeyValueObservingOptionNew //接受新值,默认 NSKeyValueObservingOptionOld //接受旧值 NSKeyValueObservingOptionInitial //在注册时立即接受一次回调,改变是也会 NSKeyValueObservingOptionPrior //改变之前发送一次,改变之后再发一次 可采用如下形式:NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; //实现监听方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context //参数详解: keyPath:监听属性的名称 Object:被观察对象 Change:字典,key一般为:new、old Context:入口,用于传值,添加监听时原样传来的 //移除监听对象 [self removeObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#>]
对象添加监听之后,运行时新增了类
NSKVONotifying_对象的类名
,并会重写对应的set方法:
NSKVONotifying_对象的类名的组成结构:
isa
superclass
set对象方法---内部调用 _NSSet*ValueAndNotify(如:_NSSetObjectValueAndNotify,*代表监听对象对应的类型,如:int double object等)--详情见下图
class:[类名 class],重写后返回本身类(屏蔽内部实现,隐藏实际类NSKVONotifying_对象的类名的存在),
dealloc:做一些销毁操作,
_isKVOA
//实际证明方法如下:
//监听方法
- (void) observeFunction {
NSLog(@"class = %@, metaclass = %@ \n",object_getClass(self.model),object_getClass(object_getClass(self.model)));
//class = ZTKVORootM, metaclass = ZTKVORootM
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionPrior;
[self.model addObserver:self forKeyPath:@"name" options:options context:nil];
//打印 类对象 和 元类对象(注意:不能使用[self.model class],因为内部已经被重写,不是实际isa指向的类,需用运行时直接获取才是真正的类)
NSLog(@"class = %@, metaclass = %@ \n",object_getClass(self.model),object_getClass(object_getClass(self.model)));
//打印结果:class = NSKVONotifying_ZTKVORootM, metaclass = NSKVONotifying_ZTKVORootM
//打印方法
NSLog(@"Method = %p",[self.model methodForSelector:@selector(setName:)]);
//打印结果:p (IMP)0x223413e04
//(IMP) $3 = 0x0000000223413e04 (Foundation`_NSSetObjectValueAndNotify)
//证明:添加监听后,运行时新增了类NSKVONotifying_ZTKVORootM,并重新了监听的属性的set方法
//打印监听后的类 和 没监听类的对象方法
ZTKVORootM *notModel = [[ZTKVORootM alloc]init];
[self getAllMethodListWithClass:object_getClass(notModel)];
[self getAllMethodListWithClass:object_getClass(self.model)];
/** 打印如下
ZTKVORootM下所有的方法列表:
.cxx_destruct
name
setName:
2020-09-23 14:25:06.560550+0800 testApp[862:180083] NSKVONotifying_ZTKVORootM下所有的方法列表:
setName:
class
dealloc
_isKVOA
*/
}
//添加依赖:返回(对象中,需监听的属性)
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"person"]) {
//此时如果person这个对象中的name或age值改变就会触发
keyPaths = [[NSSet alloc] initWithObjects:@"person.name",@"person.age", nil];
}
return keyPaths;
}
//KVO是否 开启自动监听(值变后自动通知消息)
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return YES;
}
//获取类-所有的对象方法
- (void) getAllMethodListWithClass:(Class)class {
unsigned int count = 0;
Method *methodList = class_copyMethodList(class, &count);
NSMutableString *methodStr = [[NSMutableString alloc] init];
for (NSInteger i = 0; i < count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
[methodStr appendFormat:@"%@ \n",NSStringFromSelector(sel)];
}
NSLog(@"%@下所有的方法列表:\n %@",NSStringFromClass(class),methodStr);
}
-
set
内部实现如下图
面试题
1、 iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
2、 如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
3、 直接修改成员变量会触发KVO么?
不会触发KVO,只有调用了setter方法才会触发
KVC
-
主要方法
//get方法 - (id)valueForKeyPath:(NSString *)keyPath; - (id)valueForKey:(NSString *)key; - (id)valueForUndefinedKey:(NSString *)key; //set方法 - (void)setValue:(id)value forKeyPath:(NSString *)keyPath; - (void)setValue:(id)value forKey:(NSString *)key; - (void)setValue:(id)value forUndefinedKey:(NSString *)key;
-
- (void)setValue:(id)value forKey:(NSString *)key;
方法解析:- 1.按照
setKey:
or_setKey:
顺序查方法赋值,找到直接调用,否则往下 走2; - 2.查找
+ (BOOL)accessInstanceVariablesDirectly;//是否允许查找成员变量,默认返回YES
返回值:YES-走3,NO-走4 - 3.按照
_key _isKey key isKey
顺序查找成员变量,找到了直接取值,否则往下 走4 - 4.
- (void)setValue:(id)value forUndefinedKey:(NSString *)key; 并抛出异常NSUnknownKeyException
- 1.按照
-
- (id)valueForKey:(NSString *)key;
方法解析:- 1.按照
getKey
、key
、isKey
和_key:
顺序查方法,找到直接调用,否则往下 走2; - 2.查找
+ (BOOL)accessInstanceVariablesDirectly;//是否允许查找成员变量,默认返回YES
返回值:YES-走3,NO-走4 - 3.按照
_key _isKey key isKey
顺序查找成员变量,找到了直接取值,否则往下 走4 - 4.
- (id)valueForUndefinedKey:(NSString *)key; 并抛出异常NSUnknownKeyException
- 1.按照
面试题
1、 通过KVC修改属性会触发KVO么?
会触发KVO。
原理:不论有没有实现setter方法,都会触发KVO,内部应该实现了:调用willChangeValueForKey:和didChangeValueForKey: