KVO使用及实现原理
KVO使用
- 对属性进行监听
- 对属性的属性进行监听
- 容器监听
- 触发(手动触发,kvc赋值)
添加监听
// 1.kvo对属性的监听
[_person addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil];
// 2.kvo属性关联,这个我们是监听dog的变化,那如果是dog属性的变化,如果不做处理,是监听不到的
[_person addObserver:self forKeyPath:NSStringFromSelector(@selector(dog)) options:NSKeyValueObservingOptionNew context:nil];
// 3.容器监听
[_person addObserver:self forKeyPath:NSStringFromSelector(@selector(mArr)) options:NSKeyValueObservingOptionNew context:nil];
触发代码
// 触发基本知识点:KVO得通过set方法才可以触发
NSString *name = NSStringFromSelector(@selector(name));
// 手动触发 这两个方法必须是成对的,然后回调observeValueForKeyPath方法,至于为什么成对呢,猜想应该是苹果做了处理,点进去官方的注释也有说明这两个方法必须成对存在
// 如果想要手动触发需要在被监听类中实现+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key返回NO
[_person willChangeValueForKey:name];
[_person didChangeValueForKey:name];
// kvc
[_person setValue:@"123" forKey:name];
// 容器
NSMutableArray *arr1 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
[arr1 addObject:@"123"];
NSMutableArray *arr2 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
[arr2 replaceObjectAtIndex:0 withObject:@"1234"];
NSMutableArray *arr3 = [_person mutableArrayValueForKey:NSStringFromSelector(@selector(mArr))];
[arr3 removeAllObjects];
// 属性关联
// 属性关联需要在被监听类中实现+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
_person.dog.name = @"myDog";
被监听类中实现的代码
// 这个方法不重写,就是默认返回YES,如果重写了返回NO,那么就是需要手动触发了,当然这个可以根据参数key来判断,区分监听的key是手动还是自动
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
return YES;
}
// 这个方法用于监听属性的属性的(属性关联)
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
if ([key isEqualToString:@"dog"]) {
return [[NSSet alloc] initWithObjects:@"_dog.name", nil];
} else {
return [super keyPathsForValuesAffectingValueForKey:key];
}
}
移除监听
懒得写
KVO原理
1.在运行时的时候创建被监听类的子类
/** 1.动态生成一个类 */
/** 1.1 动态生成一个类名 */
NSString *oldClassName = NSStringFromClass(self.class);
NSString *newClassName = [@"KVO_" stringByAppendingString:oldClassName];
/** 定义一个类,继承传进来的类 */
Class MyClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
2.在子类中重写父类属性的set方法(所以KVO只能监听属性)
/** 添加set方法 */
class_addMethod(MyClass, @selector(setName:), (IMP)setName, "V@:@");
3.注册这个子类
/** 注册该类 */
objc_registerClassPair(MyClass);
4.修改当前被监听对象的isa指针指向子类
/** 修改isa指针 */
object_setClass(self, MyClass);
5.实现set函数
/** set方法 */
void setName(id self,SEL _cmd,NSString * newName){
/** 保存当前类型 */
Class class = [self class];
/** 调用父类方法 */
object_setClass(self, class_getSuperclass(class));
objc_msgSend(self, @selector(setName:),newName);
/** 通知观察者 */
// 这里有个属性绑定,所以在添加观察者的时候,就是将这个观察者持有(就是使用属性绑定来持有)
id observer = objc_getAssociatedObject(self, @"objc");
if (observer) {
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":[self valueForKey:@"name"],@"kind:":@1},nil);
}
/** 改回子类 */
object_setClass(self, class);
// kvo就是在set方法中调用
willChangeValueForKey:
didChangeValueForKey:;
那我们这里需不需要呢,当然不需要,因为我们是自己实现了一套了,调这两句也没有用的
}
思考点
1.为什么kvc可以触发,kvc的原理
2.创建一个子类,如果创建的子类的类名,项目中刚好存在呢
如果项目中存在,那个创建的子类会nil,那么这时候我们可以使用递归创建,为nil就在类名后拼1或者其他字符吧,直到成功为止
3.objc_msgSend(self, @selector(setName:),newName);
可以传值,那这个跟performSelector:
有什么关系,而且发现直接用objc_msgSend
还比performSelector
方便
4.为什么oc的每个方法都有两个隐式参数,这两个是哪里来的
oc调用方法是消息机制,表现形式就是objc_msgSend(self, @selector(setName:),newName);
那么每个方法在调用的本质都是使用objc_msgSend
那这个刚好就需要传两个参数,调用者和方法编号,也就是isa和sel