对已经学会的知识,不断地总结,可以让你变得更强的。
什么情况下会调用kvo。
什么情况下不会调用kvo。
kvo的底层原理实现。
什么情况下会调用kvo呢?
@interface customObject : NSObject
{
NSString *age; //
}
@property (copy, nonatomic) NSString *name;
@end
// 1
_person.name = @"名字";
// 2
[_person setValue:@"名字" forKey:@"name"];
// 3
[_person setValue:@"名字" forKey:@"_name"];
//4
self.name = @"名字";
// 5
_name = @"名字";
// 6
[self setValue:@"名字" forKey:@"name"];
// 7
[self setValue:@"名字" forKey:@"_name"];
//8
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
if ([[NSString stringWithUTF8String:ivar_getName(ivar)] isEqualToString:@"_name"]) {
// 8
object_setIvar(_person, ivar, @"名字");
}
}
//9
self ->name = @"名字"
我们对name,哪些会真正的触发kvo呢
我可以告诉你,1 2 4 6会监听到kvo的变化,而其他的则检测不到。
又比如下面这个例子。
#import <Foundation/Foundation.h>
@class AnimalClass;
@interface PersonClass : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) AnimalClass *animal;
@end
@interface AnimalClass : NSObject
@property (nonatomic, copy) NSString *name;
@end
当我们对animal进行kvo监听,然后修改name的值,是否会被kvo监听到。
当我们对personclass对象重新赋值一个AnimalClass对象,是否会被kvo监听到。
我可以告诉你,第一种情况是监听不到的,第二种情况是可以监听到的。
然后,为什么呢?
我这里有两张图片。


当我们对一个对象进行kvo监听的时候,会生成一个NSKVONotifying_前缀的类,然后我们实际的操作是对这个类进行的。
通俗的讲,对对象的进行kvo监听后,这个对象的isa指针已经指向了NSKVONotifying_前缀的类,NSKVONotifying_Person。这个类是person的子类,他的superclass就是person类对象。
我们在调用setage方法的时候,会根据对象的isa找到NSKVONotifying_Person,然后在他的类里面找setage的实现。
那么有没有疑惑,setage中到底做了什么操作呢?
_NSsetIntValueAndNotify记住这个函数。
// 通过methodForSelector找到方法实现的地址
NSLog(@"添加KVO监听之前 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"添加KVO监听之后 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);
我们可以这样打印出setage的方法实现。
可以打印出
p1进行监听之前:[person setAge:]
p1进行监听之后:(foundation _NSsetIntValueAndNotify)
这个_NSsetIntValueAndNotify方法就是setage的实现,具体的实现,请再往后看。
Foundation框架中还有很多例如_NSSetBoolValueAndNotify、_NSSetCharValueAndNotify、_NSSetFloatValueAndNotify、_NSSetLongValueAndNotify等等函数。
想不想知道NSKVONotifyin_Person内部结构是怎样的?
继续往下看。
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc] init];
p1.age = 1.0;
Person *p2 = [[Person alloc] init];
p1.age = 2.0;
// self 监听 p1的 age属性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[p1 addObserver:self forKeyPath:@"age" options:options context:nil];
[self printMethods: object_getClass(p2)];
[self printMethods: object_getClass(p1)];
[p1 removeObserver:self forKeyPath:@"age"];
}
- (void) printMethods:(Class)cls
{
unsigned int count ;
Method *methods = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
[methodNames appendFormat:@"%@ - ", cls];
for (int i = 0 ; i < count; i++) {
Method method = methods[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString: methodName];
[methodNames appendString:@" "];
}
NSLog(@"%@",methodNames);
free(methods);
}
打印结果如下
2021-06-07 19:43:56.574173+0800 DSKVO[31160:4348683] Person - setAge: age
2021-06-07 19:43:56.574346+0800 DSKVO[31160:4348683] NSKVONotifying_Person - setAge: class dealloc _isKVOA
在kvo监听下,包含了四个方法,setAge: class dealloc _isKVOA
我们先从clas来进行入手。
NSLog(@"%@,%@",[p1 class],[p2 class]);
打印结果都是person。为什么呢?
进行kvo监听之后,不是进行变为NSKVONotifyin_Person这个类吗?怎么打印出来都是person。
其实是苹果不希望将这个NSKVONotifyin_Person暴露出来。然后在类的内部,重写了clas方法。
- (Class) class {
// 得到类对象,在找到类对象父类
return class_getSuperclass(object_getClass(self));
}
setAge方法的再深一层的内部实现?
- (void)setName:(NSString *)name {
_NSSetObjectValueAndNotify()
}
- (void)willChangeValueForKey:(NSString *)key {
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key {
[super didChangeValueForKey:key];
[observer observeValueForKeyPath:@"name"];
}
void _NSSetObjectValueAndNotify() {
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
}
我相信看完都会有所收获。
看完这些最开始的例子,我觉得会懂了。
我们来看下kvc的原理:
当调用setValue:forKey:时 ,程序会先通过setter(set:)方法,对属性进行设置;
如果没有找到setKey:方法,KVC机制会检查+
(BOOL)accessInstanceVariablesDirectly方法有没有返回YES
,默认该方法是返回YES的,如果重写返回了NO,那么这一步会执行setValue
forUndefinedKey:方法。若为YES,KVC机制会搜索该类中是否有名为key的成员变量,不管变量在类接口处定义没有,只要存在以key命名的变量,KVC都可以对该成员变量赋值。
如果该类既没有setKey方法,也没有_key成员变量,KVC机制会搜索_isKey的成员变量;如果_isKey成员变量也没有,KVC机制再会继续搜索和is的成员变量给它们赋值,如果上面列出的方法或者成员变量都不存在,系统会执行该对象的setValue:forUndefinedKey:方法
抛出异常(既 如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作)
触发kvc的set方法才可以触发kvo机制。
所以这两种情况
这个可以完美触发kvo
[_person setValue:@"名字" forKey:@"name"];
这个直接给name赋值,所以不能触发kvo
[_person setValue:@"名字" forKey:@"_name"];
网友评论