参考官方文档(developer.apple.com/library/arc… ) html
来看下官方文档,KVO实现细节:ios
接下来咱们用代码深刻探讨下KVO到底作了些什么?上代码macos
#import "ViewController.h"
@interface Person : NSObject
@end
@implementation Person
//对类方法(类对象)进行动态方法决议(亦称方法动态解析)
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
//对实例方法(实例对象)进行动态方法决议(亦称方法动态解析)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
//动态方法决议失败后,首先查找备用接收者,是否能直接处理消息(亦称快速转发)
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
//动态方法决议失败后,查找备用接收者也失败(快速转发失败),那么就进行慢转发(须要进行方法签名)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__ ,anInvocation);
return [super forwardInvocation:anInvocation];
}
//对慢转发进行方法签名处理
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector));
return [super methodSignatureForSelector:aSelector];
}
- (IMP)methodForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector));
return [super methodForSelector:aSelector];
}
- (void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector));
return [super doesNotRecognizeSelector:aSelector];
}
//是否容许自动触发KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
NSLog(@"%s",__func__);
return [super automaticallyNotifiesObserversForKey:key];
}
//是否容许直接获取实例
+ (BOOL)accessInstanceVariablesDirectly{
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
@end
@interface ViewController ()
@property (nonatomic, strong) Person * person;
@property (nonatomic ,assign) IMP imp,imp2,imp3,classImp,classImp2,classImp3;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_person = [[Person alloc] init];
//添加属性观察,注意这里Person类并无添加任何属性
[_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%s",__func__);
NSLog(@"keyPath = %@",keyPath);
NSLog(@"change = %@",change);
}
- (void)dealloc
{
NSLog(@"%s",__func__);
//移除
[_person removeObserver:self forKeyPath:@"age"];
}
@end
复制代码
运行结果以下:bash
2019-12-27 09:44:14.613488+0800 KVC&KVO[1353:39658] +[Person resolveClassMethod:] - keyPathsForValuesAffectingAge
2019-12-27 09:44:14.613624+0800 KVC&KVO[1353:39658] +[Person automaticallyNotifiesObserversForKey:]
2019-12-27 09:44:14.613719+0800 KVC&KVO[1353:39658] +[Person automaticallyNotifiesObserversOfAge]
2019-12-27 09:44:14.613844+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - setAge:
2019-12-27 09:44:14.613946+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - _setAge:
2019-12-27 09:44:14.614041+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - setIsAge:
2019-12-27 09:44:14.614126+0800 KVC&KVO[1353:39658] +[Person accessInstanceVariablesDirectly]
2019-12-27 09:44:14.614227+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - getAge
2019-12-27 09:44:14.614460+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - age
2019-12-27 09:44:14.614869+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - isAge
2019-12-27 09:44:14.615145+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - _getAge
2019-12-27 09:44:14.615406+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - _age
2019-12-27 09:44:14.615723+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - countOfAge
2019-12-27 09:44:14.615869+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - objectInAgeAtIndex:
2019-12-27 09:44:14.616045+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - ageAtIndexes:
2019-12-27 09:44:14.616200+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - indexInAgeOfObject:
2019-12-27 09:44:14.616381+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - enumeratorOfAge
2019-12-27 09:44:14.616540+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - memberOfAge:
2019-12-27 09:44:14.619180+0800 KVC&KVO[1353:39658] +[Person accessInstanceVariablesDirectly]
2019-12-27 09:44:14.619283+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex:
2019-12-27 09:44:14.619391+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertAge:atIndexes:
2019-12-27 09:44:14.619484+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex:
2019-12-27 09:44:14.619584+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAgeAtIndexes:
2019-12-27 09:44:14.619698+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex:
2019-12-27 09:44:14.619791+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - insertAge:atIndexes:
2019-12-27 09:44:14.619884+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex:
2019-12-27 09:44:14.619974+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAgeAtIndexes:
2019-12-27 09:44:14.620089+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - addAgeObject:
2019-12-27 09:44:14.620274+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAge:
2019-12-27 09:44:14.620441+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - removeAgeObject:
2019-12-27 09:44:14.620610+0800 KVC&KVO[1353:39658] +[Person resolveInstanceMethod:] - addAge:
复制代码
结果分析:架构
1.首先Person类并无添加任何age属性,咱们仍然注册观察Person对象属性age进行监听(这里不建议对一个不存在的属性进行观察,会带来异常),经过log你会看到KVC的影子,或许正好验证了 KVO是基于KVC的说法.app
2.Person类并无age属性,添加KVO观察后,使用了动态方法解析,首先keyPathsForValuesAffectingAge(这里的Age就是咱们的age属性)这个方法解析经过(由于并无走消息转发流程,此方法主要来指明age是依赖哪些键,能够跟随依赖键更新而更新age自身)ui
3.动态方法解析keyPathsForValuesAffectingAge成功后,而后调用automaticallyNotifiesObserversForKey:和 automaticallyNotifiesObserversOfAge是否容许Age这个key自动触发KVO(这里默认都是返回YES容许的),若是返回YES,而且option没有指定NSKeyValueObservingOptionInitial(由于这个并不会被拦截),则开始进行KVC对属性age进行处理,不然,直接返回,没有进行KVC的必要了(这里你能够重写automaticallyNotifiesObserversForKey:返回NO 或者 automaticallyNotifiesObserversOfAge返回NO,它确实中止了KVC流程)atom
4.若是容许对age属性自动KVO,那么接下来就对age属性开始执行KVC流程 (注意option指定NSKeyValueObservingOptionInitial,则会抛出NSUnknownKeyException崩溃,由于KVC首先走age属性的getter流程,发现没有getter一类方法,也没有实例变量,会动态解析生成getPrimitiveAge和primitiveAge方法,但同时并无生成age这个实例变量,因此Peson类没有age这个key致使崩溃)lua
5.KVC首先走age属性的setter流程(注意option指定NSKeyValueObservingOptionInitial则会先走getter流程),发现没有setter一类的存取方法,就去调用accessInstanceVariablesDirectly,查看是否容许直接获取实例变量(这里是容许),若是返回NO不容许,则会去动态解析setPrimitiveAge:,getPrimitiveAge 和 primitiveAge 方法,保证可以正确处理KVO. 注意此时KVC对age进行取值或者设值操做 都会抛出valueForUndefinedKey异常.同理接着顺序处理age属性的getter流程,以及age集合属性处理流程spa
主要分三步:
对简单非集合对象: -(void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
对于NSArray集合对象: -(void)addObserver:(NSObject *)observer toObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
重要:添加观察后,不须要监听的时候必须移除监听.通常在dealloc方法里移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
或者 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
在NSObject分类NSKeyValueObserverRegistration中有如下经常使用方法:
/*
添加键值观察
observer:观察者,也就是通知的订阅者(或者说监听者),观察者是必须的,并且不能为空
keyPath :被观察的属性
options :KVO配置相关,既会影响通知中提供的更改字典的内容,又会影响生成通知的方式,有四个选项,下面会介绍
context :上下文,主要区分通知来源
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
/*移除监听
注意:必须保证在观察者被释放销毁以前移除,不然程序崩溃
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
/*
* 移除监听
注意:必须保证在观察者被释放销毁以前移除,不然程序崩溃
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
复制代码
NSKeyValueObservingOptions有四个选项
NSKeyValueObservingOptionNew 观察者回调监听中change字典中包含改变后的值
NSKeyValueObservingOptionOld 观察者回调监听中change字典中包含改变前的值
NSKeyValueObservingOptionInitial 注册后马上触发KVO通知,可是须要注意的是 NSKeyValueObservingOptions参数同时指定了NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial,首次触发KVO change字典中并不包含old值
NSKeyValueObservingOptionPrior 值改变前是否通知(改变前通知一次,改变后再通知一次)
复制代码
来看点例子:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
{
@public //暴露成员变量,仅仅为了演示给成员变量直接赋值,验证不能触发KVO
NSUInteger _age;
}
-(void)setAge:(NSUInteger)age;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@interface Person ()
@end
@implementation Person
-(void)setAge:(NSUInteger)age{
NSLog(@"%s",__func__);
_age = age;
}
@end
复制代码
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Person * person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_person = [[Person alloc] init];
//添加属性观察
[_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
//注意这里直接给成员变量赋值,不会触发KVO
_person->_age = 20;
//这里调用自定义setter方法,验证是否会触发KVO
[_person setAge:65];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%s",__func__);
NSLog(@"keyPath = %@",keyPath);
NSLog(@"change = %@",change);
}
- (void)dealloc
{
NSLog(@"%s",__func__);
//移除
[_person removeObserver:self forKeyPath:@"age"];
}
@end
复制代码
运行结果分析: 咱们能够看到_person->_age = 20 直接赋值并无触发KVO(你能够将 [_person setAge:65];注释掉去验证,它确实没有触发KVO),而调用setter方法触发了KVO,由此能够猜想setter方法触发了KVO.可是有一点必定很疑惑,由于才刚说了直接赋值操做不能触发KVO,而咱们的setter方法仅仅作了一个赋值操做,但结果确很意外,它居然触发了KVO!由此能够下结论了,被观察的对象类中setter方法是无关紧要的,它不是必须的!!!接下来咱们将Person类中setter方法注释掉。因为如今没有明确的存取方法了,这个时候KVC能够来展现下本身的才能了!咱们将 [_person setAge:65] 换成KVC方法 [_person setValue:@25 forKey:@"age"];而且Persron添加以下方法
+ (BOOL)accessInstanceVariablesDirectly{
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
复制代码
运行结果如咱们预期猜测的那样,可是也有疑惑,由于看到KVC打印结果,也是直接获取的对象实例,咱们也没有提供setter方法。
对象类中setter方法确实不是必须的.那么问题又来了,到底是什么触发的KVO? 官方文档中手动KVO有这么一段:
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Person * person;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_person = [[Person alloc] init];
//添加属性观察
[_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
[_person willChangeValueForKey:@"age"];
_person->_age = 30;
[_person didChangeValueForKey:@"age"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%s",__func__);
NSLog(@"keyPath = %@",keyPath);
NSLog(@"change = %@",change);
}
- (void)dealloc
{
NSLog(@"%s",__func__);
//移除
[_person removeObserver:self forKeyPath:@"age"];
}
@end
复制代码
运行结果也如预期的同样触发KVO( willChangeValueForKey:和 didChangeValueForKey:确实触发KVO通知了,中间直接赋值操做无关紧要)
接下来咱们来验证第1种猜测 代码以下:
其中Person.m增长方法resolveInstanceMethod: 便于验证
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
复制代码
#import "ViewController.h"
#import "Person.h"
#import <objc/message.h>
@interface ViewController ()
@property (nonatomic, strong) Person * person;
@property (nonatomic ,assign) IMP imp,imp2,imp3,classImp,classImp2,classImp3;
@end
@implementation ViewController
//获取对象全部属性
NSArray<NSString*>*getAllProperties(Class cls){
unsigned int count;
objc_property_t * properties = class_copyPropertyList(cls, &count);
NSMutableArray* arr = [NSMutableArray array];
for (int i = 0; i < count; i++) {
objc_property_t property= properties[i];
const char * p_name = property_getName(property);
NSString* name = [NSString stringWithCString:p_name encoding:NSUTF8StringEncoding];
[arr addObject:name];
}
free(properties);
return arr.copy;
}
//获取对象全部实例变量
NSArray<NSString*>*getAllIvars(Class cls){
unsigned int count;
Ivar * ivars = class_copyIvarList(cls, &count);
NSMutableArray* arr = [NSMutableArray array];
for (int i = 0; i < count; i++) {
Ivar ivar= ivars[i];
const char * ivar_name = ivar_getName(ivar);
NSString* name = [NSString stringWithCString:ivar_name encoding:NSUTF8StringEncoding];
[arr addObject:name];
}
free(ivars);
return arr.copy;
}
//获取对象全部方法
NSArray<NSString*>*getAllMethods(Class cls){
unsigned int count;
Method * methods = class_copyMethodList(cls, &count);
NSMutableArray* arr = [NSMutableArray array];
for (int i = 0; i < count; i++) {
Method method = methods[i];
SEL sel = method_getName(method);
NSString* name = NSStringFromSelector(sel);
[arr addObject:name];
}
free(methods);
return arr.copy;
}
- (void)viewDidLoad {
[super viewDidLoad];
_person = [[Person alloc] init];
Class classP = object_getClass(_person);
//观察前
NSLog(@"观察前对象 :%@",_person);
NSLog(@"观察前对象类 :%@",classP);
NSLog(@"观察前对象父类 :%@",[classP superclass]);
NSLog(@"观察前全部属性 :%@",getAllProperties(classP));
NSLog(@"观察前全部变量 :%@",getAllIvars(classP));
NSLog(@"观察前全部方法 :%@",getAllMethods(classP));
self.imp = [_person methodForSelector:@selector(setAge:)];
self.classImp = [_person methodForSelector:@selector(class)];
//添加属性观察
[_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
//触发KVO
[_person setAge:200];
//观察后
Class classP2 = object_getClass(_person);// 与 _person->isa等价 注意这里不能_person->isa调用,由于默认isa是@protected
NSLog(@"观察后对象 :%@",_person);
NSLog(@"观察后对象类:%@",classP2);
NSLog(@"观察后对象父类 :%@",[classP2 superclass]);
NSLog(@"观察后全部属性 :%@",getAllProperties(classP2));
NSLog(@"观察后全部变量 :%@",getAllIvars(classP2));
NSLog(@"观察后全部方法 :%@",getAllMethods(classP2));
self.imp2 = [_person methodForSelector:@selector(setAge:)];
self.classImp2 = [_person methodForSelector:@selector(class)];
//移除观察,通常放在dealloc方法移除,这里仅仅为了演示移除观察后的效果
[_person removeObserver:self forKeyPath:@"age"];
Class classP3 = object_getClass(_person);
NSLog(@"移除观察后对象 :%@",_person);
NSLog(@"移除观察后对象类:%@",classP3);
NSLog(@"移除观察后对象父类 :%@",[classP3 superclass]);
NSLog(@"移除观察后全部属性 :%@",getAllProperties(classP3));
NSLog(@"移除观察后全部变量 :%@",getAllIvars(classP3));
NSLog(@"移除观察后全部方法 :%@",getAllMethods(classP3));
self.imp3 = [_person methodForSelector:@selector(setAge:)];
self.classImp3 = [_person methodForSelector:@selector(class)];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%s",__func__);
NSLog(@"keyPath = %@",keyPath);
NSLog(@"change = %@",change);
}
//- (void)dealloc
//{
// NSLog(@"%s",__func__);
// //移除
// [_person removeObserver:self forKeyPath:@"age"];
//}
@end
复制代码
打印有点多,具体看log吧
2019-12-26 14:20:01.558958+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - get_isKVOA
2019-12-26 14:20:01.559114+0800 KVC&KVO[3067:148054] 观察前_isKVOA返回值:0
2019-12-26 14:20:01.559212+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 14:20:01.559305+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - descriptionWithLocale:
2019-12-26 14:20:01.559403+0800 KVC&KVO[3067:148054] 观察前对象 :<Person: 0x6000008745e0>
2019-12-26 14:20:01.559489+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 14:20:01.559576+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - descriptionWithLocale:
2019-12-26 14:20:01.559685+0800 KVC&KVO[3067:148054] 观察前对象类 :Person
2019-12-26 14:20:01.559933+0800 KVC&KVO[3067:148054] 观察前对象父类 :NSObject
2019-12-26 14:20:01.560332+0800 KVC&KVO[3067:148054] 观察前全部属性 :(
)
2019-12-26 14:20:01.560978+0800 KVC&KVO[3067:148054] 观察前全部变量 :(
"_age"
)
2019-12-26 14:20:01.561398+0800 KVC&KVO[3067:148054] 观察前全部方法 :(
"setAge:"
)
2019-12-26 14:20:01.561817+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - keyPathsForValuesAffectingAge
2019-12-26 14:20:01.562166+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - automaticallyNotifiesObserversOfAge
2019-12-26 14:20:01.562608+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - getAge
2019-12-26 14:20:01.562920+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - age
2019-12-26 14:20:01.563081+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - isAge
2019-12-26 14:20:01.563286+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _getAge
2019-12-26 14:20:01.563470+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _age
2019-12-26 14:20:01.563638+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - countOfAge
2019-12-26 14:20:01.567939+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - objectInAgeAtIndex:
2019-12-26 14:20:01.568043+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - ageAtIndexes:
2019-12-26 14:20:01.568134+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - indexInAgeOfObject:
2019-12-26 14:20:01.568232+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - enumeratorOfAge
2019-12-26 14:20:01.568324+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - memberOfAge:
2019-12-26 14:20:01.568427+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex:
2019-12-26 14:20:01.568520+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertAge:atIndexes:
2019-12-26 14:20:01.568606+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex:
2019-12-26 14:20:01.568782+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAgeAtIndexes:
2019-12-26 14:20:01.569057+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex:
2019-12-26 14:20:01.569271+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - insertAge:atIndexes:
2019-12-26 14:20:01.569484+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex:
2019-12-26 14:20:01.569751+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAgeAtIndexes:
2019-12-26 14:20:01.570040+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - addAgeObject:
2019-12-26 14:20:01.570286+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAge:
2019-12-26 14:20:01.570553+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - removeAgeObject:
2019-12-26 14:20:01.570863+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - addAge:
2019-12-26 14:20:01.571093+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - getAge
2019-12-26 14:20:01.571345+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - age
2019-12-26 14:20:01.571596+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - isAge
2019-12-26 14:20:01.571841+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _getAge
2019-12-26 14:20:01.572090+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _age
2019-12-26 14:20:01.572272+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - countOfAge
2019-12-26 14:20:01.572501+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - objectInAgeAtIndex:
2019-12-26 14:20:01.572756+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - ageAtIndexes:
2019-12-26 14:20:01.573008+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - indexInAgeOfObject:
2019-12-26 14:20:01.573191+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - enumeratorOfAge
2019-12-26 14:20:01.573421+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - memberOfAge:
2019-12-26 14:20:01.573698+0800 KVC&KVO[3067:148054] -[Person setAge:]
2019-12-26 14:20:01.573972+0800 KVC&KVO[3067:148054] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 14:20:01.574154+0800 KVC&KVO[3067:148054] keyPath = age
2019-12-26 14:20:01.574458+0800 KVC&KVO[3067:148054] change = {
kind = 1;
new = 300;
old = 0;
}
2019-12-26 14:20:01.574798+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - get_isKVOA
2019-12-26 14:20:01.575064+0800 KVC&KVO[3067:148054] 观察后_isKVOA返回值:1
2019-12-26 14:20:01.575333+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 14:20:01.575560+0800 KVC&KVO[3067:148054] +[Person resolveInstanceMethod:] - descriptionWithLocale:
2019-12-26 14:20:01.575835+0800 KVC&KVO[3067:148054] 观察后对象 :<Person: 0x6000008745e0>
2019-12-26 14:20:01.576035+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 14:20:01.576254+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - descriptionWithLocale:
2019-12-26 14:20:01.576486+0800 KVC&KVO[3067:148054] 观察后对象类:NSKVONotifying_Person
2019-12-26 14:20:01.576697+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 14:20:01.576935+0800 KVC&KVO[3067:148054] +[Person resolveClassMethod:] - descriptionWithLocale:
2019-12-26 14:20:01.577174+0800 KVC&KVO[3067:148054] 观察后对象父类 :Person
2019-12-26 14:20:01.577406+0800 KVC&KVO[3067:148054] 观察后全部属性 :(
)
2019-12-26 14:20:01.577623+0800 KVC&KVO[3067:148054] 观察后全部变量 :(
)
2019-12-26 14:20:01.577849+0800 KVC&KVO[3067:148054] 观察后全部方法 :(
"setAge:",
class,
dealloc,
"_isKVOA"
)
2019-12-26 14:20:01.578021+0800 KVC&KVO[3067:148054] 移除观察后_isKVOA返回值:0
2019-12-26 14:20:05.452737+0800 KVC&KVO[3067:148054] 移除观察后对象 :<Person: 0x6000008745e0>
2019-12-26 14:20:05.452894+0800 KVC&KVO[3067:148054] 移除观察后对象类:Person
2019-12-26 14:20:05.452995+0800 KVC&KVO[3067:148054] 移除观察后对象父类 :NSObject
2019-12-26 14:20:05.453081+0800 KVC&KVO[3067:148054] 移除观察后全部属性 :(
)
2019-12-26 14:20:05.453169+0800 KVC&KVO[3067:148054] 移除观察后全部变量 :(
"_age"
)
2019-12-26 14:20:05.453263+0800 KVC&KVO[3067:148054] 移除观察后全部方法 :(
"setAge:"
)
复制代码
运行结果分析:
到这里理论上说完了,可是还存在一种状况,就是父类Person没有提供setAge:方法,只提供一个成员变量_age,可是使用KVC一样能够触发KVO,这又是为何呢?下面经过结合KVC来验证,这里使用 [_person setValue:@300 forKey:@"age"];来触发KVO
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
{
@public //暴露成员变量,仅仅为了演示给成员变量直接赋值,验证不能触发KVO
NSUInteger _age;
}
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@interface Person ()
@end
@implementation Person
+ (BOOL)accessInstanceVariablesDirectly{
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
@end
复制代码
运行结果分析: 这里Person并无提供setAge:方法,使用KVC设值,也是直接获取的对象实例,注册KVO后,一样生成了一个派生类NSKVONotifying_Person,这个派生类没有添加setter方法,只重写了dealloc方法和class方法,增长了一个_isKVOA方法,不一样的是imp和imp2变成了_objc_msgForward,这样相似于上面的猜测2,使用了runtime动态方法解析或转发,接下来咱们就向Person类种重写消息转发的几个经常使用方法:
#import "Person.h"
@interface Person ()
@end
@implementation Person
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(sel));
return [super resolveClassMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__ ,anInvocation);
return [super forwardInvocation:anInvocation];
}
- (IMP)methodForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector));
return [super methodForSelector:aSelector];
}
- (void)doesNotRecognizeSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__ ,NSStringFromSelector(aSelector));
return [super doesNotRecognizeSelector:aSelector];
}
+ (BOOL)accessInstanceVariablesDirectly{
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
@end
复制代码
运行结果以下(能够顺便看下KVC执行流程):
2019-12-26 10:52:51.962332+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 10:52:51.962469+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - descriptionWithLocale:
2019-12-26 10:52:51.962572+0800 KVC&KVO[1368:47095] 观察前对象 :<Person: 0x600003894530>
2019-12-26 10:52:51.962659+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 10:52:51.962744+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - descriptionWithLocale:
2019-12-26 10:52:51.962824+0800 KVC&KVO[1368:47095] 观察前对象类 :Person
2019-12-26 10:52:51.962902+0800 KVC&KVO[1368:47095] 观察前对象父类 :NSObject
2019-12-26 10:52:51.963032+0800 KVC&KVO[1368:47095] 观察前全部属性 :(
)
2019-12-26 10:52:51.963425+0800 KVC&KVO[1368:47095] 观察前全部变量 :(
"_age"
)
2019-12-26 10:52:51.963819+0800 KVC&KVO[1368:47095] 观察前全部方法 :(
"doesNotRecognizeSelector:",
"forwardingTargetForSelector:",
"methodForSelector:",
"forwardInvocation:"
)
2019-12-26 10:52:51.964023+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - setAge:
2019-12-26 10:52:51.964311+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge:
2019-12-26 10:52:51.964684+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - class
2019-12-26 10:52:51.965136+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - keyPathsForValuesAffectingAge
2019-12-26 10:52:51.965465+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - automaticallyNotifiesObserversOfAge
2019-12-26 10:52:51.965831+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge:
2019-12-26 10:52:51.966028+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _setAge:
2019-12-26 10:52:51.966324+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setIsAge:
2019-12-26 10:52:51.973082+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly]
2019-12-26 10:52:51.973216+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - getAge
2019-12-26 10:52:51.973330+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - age
2019-12-26 10:52:51.973428+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - isAge
2019-12-26 10:52:51.973524+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _getAge
2019-12-26 10:52:51.973612+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _age
2019-12-26 10:52:51.973702+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - countOfAge
2019-12-26 10:52:51.973792+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - objectInAgeAtIndex:
2019-12-26 10:52:51.973891+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - ageAtIndexes:
2019-12-26 10:52:51.973989+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - indexInAgeOfObject:
2019-12-26 10:52:51.974202+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - enumeratorOfAge
2019-12-26 10:52:51.974378+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - memberOfAge:
2019-12-26 10:52:51.974552+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly]
2019-12-26 10:52:51.974779+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex:
2019-12-26 10:52:51.975018+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertAge:atIndexes:
2019-12-26 10:52:51.975243+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex:
2019-12-26 10:52:51.975466+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAgeAtIndexes:
2019-12-26 10:52:51.975685+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertObject:inAgeAtIndex:
2019-12-26 10:52:51.975868+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - insertAge:atIndexes:
2019-12-26 10:52:51.976051+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeObjectFromAgeAtIndex:
2019-12-26 10:52:51.976241+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAgeAtIndexes:
2019-12-26 10:52:51.976468+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - addAgeObject:
2019-12-26 10:52:51.976682+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAge:
2019-12-26 10:52:51.976888+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - removeAgeObject:
2019-12-26 10:52:51.977102+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - addAge:
2019-12-26 10:52:51.977341+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge:
2019-12-26 10:52:51.977544+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _setAge:
2019-12-26 10:52:51.977741+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setIsAge:
2019-12-26 10:52:51.977934+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly]
2019-12-26 10:52:51.978175+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - getAge
2019-12-26 10:52:51.978365+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - age
2019-12-26 10:52:51.978580+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - isAge
2019-12-26 10:52:51.978813+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _getAge
2019-12-26 10:52:51.979030+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _age
2019-12-26 10:52:51.979249+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - countOfAge
2019-12-26 10:52:51.979461+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - objectInAgeAtIndex:
2019-12-26 10:52:51.979712+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - ageAtIndexes:
2019-12-26 10:52:51.979951+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - indexInAgeOfObject:
2019-12-26 10:52:51.980183+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - enumeratorOfAge
2019-12-26 10:52:51.980433+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - memberOfAge:
2019-12-26 10:52:51.980666+0800 KVC&KVO[1368:47095] +[Person accessInstanceVariablesDirectly]
2019-12-26 10:52:51.980926+0800 KVC&KVO[1368:47095] -[ViewController observeValueForKeyPath:ofObject:change:context:]
2019-12-26 10:52:51.981215+0800 KVC&KVO[1368:47095] keyPath = age
2019-12-26 10:52:51.981500+0800 KVC&KVO[1368:47095] change = {
kind = 1;
new = 300;
old = 0;
}
2019-12-26 10:52:51.981763+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 10:52:51.982011+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - descriptionWithLocale:
2019-12-26 10:52:51.982258+0800 KVC&KVO[1368:47095] 观察后对象 :<Person: 0x600003894530>
2019-12-26 10:52:51.982467+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 10:52:51.982684+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - descriptionWithLocale:
2019-12-26 10:52:51.982925+0800 KVC&KVO[1368:47095] 观察后对象类:NSKVONotifying_Person
2019-12-26 10:52:51.983145+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - _dynamicContextEvaluation:patternString:
2019-12-26 10:52:51.983355+0800 KVC&KVO[1368:47095] +[Person resolveClassMethod:] - descriptionWithLocale:
2019-12-26 10:52:51.983568+0800 KVC&KVO[1368:47095] 观察后对象父类 :Person
2019-12-26 10:52:51.983824+0800 KVC&KVO[1368:47095] 观察后全部属性 :(
)
2019-12-26 10:52:51.984010+0800 KVC&KVO[1368:47095] 观察后全部变量 :(
)
2019-12-26 10:52:51.984210+0800 KVC&KVO[1368:47095] 观察后全部方法 :(
class,
dealloc,
"_isKVOA"
)
2019-12-26 10:52:51.984429+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - setAge:
2019-12-26 10:52:51.984654+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge:
2019-12-26 10:52:51.984875+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - class
2019-12-26 10:52:51.985099+0800 KVC&KVO[1368:47095] 移除观察后对象 :<Person: 0x600003894530>
2019-12-26 10:52:51.985277+0800 KVC&KVO[1368:47095] 移除观察后对象类:Person
2019-12-26 10:52:51.985466+0800 KVC&KVO[1368:47095] 移除观察后对象父类 :NSObject
2019-12-26 10:52:51.985674+0800 KVC&KVO[1368:47095] 移除观察后全部属性 :(
)
2019-12-26 10:52:51.985875+0800 KVC&KVO[1368:47095] 移除观察后全部变量 :(
"_age"
)
2019-12-26 10:52:51.986070+0800 KVC&KVO[1368:47095] 移除观察后全部方法 :(
"doesNotRecognizeSelector:",
"forwardingTargetForSelector:",
"methodForSelector:",
"forwardInvocation:"
)
2019-12-26 10:52:51.986264+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - setAge:
2019-12-26 10:52:51.986515+0800 KVC&KVO[1368:47095] +[Person resolveInstanceMethod:] - setAge:
2019-12-26 10:52:51.986763+0800 KVC&KVO[1368:47095] -[Person methodForSelector:] - class
复制代码
结果分析: KVO并不必定会重写setter方法,前提是父类中存在setter方法则会重写,不存在,则会动态解析setter方法,再获取实例并在改变实例的先后注入willChangeValueForKey:和didChangeValueForKey:方法来触发KVO
当一个NSObejct对象或者其子类对象(暂且记为XXX)某个属性或者某个成员变量(上面已经验证能够对成员变量进行KVO,不必定要求是属性,这里暂且成员变量记为m)被观察时,会派生一个子类NSKVONotifying_XXX,此时父类XXX的isa指针会指向其派生类NSKVONotifying_XXX,该子类会先检查父类XXX是否存在setM:方法,若是存在,则会重写setM:方法,并注入willChangeValueForKey:和didChangeValueForKey:方法来触发KVO .若是不存在setM:方法,则不会添加该方法,而是动态解析setM:方法,再获取父类实例变量,而且在赋值实例变量的先后注入willChangeValueForKey:和didChangeValueForKey:方法来触发KVO. 子类NSKVONotifying_XXX重写的方法还有class,和dealloc方法。子类NSKVONotifying_XXX同时增长了一个_isKVOA方法. 当移除观察时,被观察的对象isa又从新指向自身类XXX
若有不正确的的地方(主要是:是否重写setter方法),欢迎进行交流!