美文网首页iOSiOS面试总结
KVO原理探究+自定义KVO实现

KVO原理探究+自定义KVO实现

作者: it_Xiong | 来源:发表于2019-04-12 16:56 被阅读141次
  • 概念
  • 基本使用
  • 触发模式
  • 属性依赖
  • 容器类的使用
  • 自定义KVO

概念

KVO全称Key-Value Observing,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。

基本使用

使用KVO分为三个步骤:
1.通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keyPath属性的变化事件。
2.在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
3.当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除。需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。
注册方法

/**
 注册KVO监听

 @param observer 观察者对象
 @param keyPath 需要观察的属性,由于是字符串形式,容易crash,一般利用系统的反射机制 NSStringFromSeletor(seletor(keypath))
 @param options 监听枚举类型
                OptionNew 接收新值,默认为只接收新值
                OptionOld 接收旧值
                OptionInitial在注册时立即接收一次回调,在改变时也会发送通知
                OptionPrior 改变之前发一次,改变之后发一次
 @param context 传入任意类型的对象,在接收消息回调的代码中可以接收到这个对象,是KVO中的一种传值方式,主要用于多个监听器对象监听相同keypath时进行区分
 */
-(void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

tips:在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的carsh.
KVO的addObserver和removeObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash。
苹果官方推荐的方式是,在init的时候进行addObserver,在dealloc时removeObserver,这样可以保证add和remove是成对出现的,是一种比较理想的使用方式。

监听方法
观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash。

/**
 监听回调方法

 @param keyPath 监听的属性路径
 @param object 被观察对象
 @param change 监听内容的变化,是个字典
 @param context 一个用来传值的对象,由注册方法添加
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context

触发模式

自动触发
控制当前对象的自动调用过程

/**
 调用模式,是否响应

 @param key KVO观察属性
 @return YES 正常发送通知; NO 不发送通知
 */
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

手动触发

- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

属性依赖

A类的属性是一个拥有多个属性的对象B,要观察该对象,而不想写多个addObserver方法,
可以在A类的.m文件中重写

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
   
   NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
   
   //dog属性的相关属性发生变化, 避免因为监听dog的多个属性而写多份KVO监听代码
   if ([key isEqualToString:@"dog"]) {
       keyPaths = [NSSet setWithObjects:@"_dog.age",@"_dog.color",nil];
   }
   return keyPaths;
}

容器类的使用

    //监听容器类
    [self.person addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew context:nil];
    //该方法无法触发
    [self.person.array  addObject:@"元素"];
    //使用以下方法可以触发,
    NSMutableArray *tempArray = [self.person mutableArrayValueForKey:@"array"];
    [tempArray addObject:@"元素"];

自定义KVO

KVO是通过isa-swizzling技术实现的
1.创建一个子类 NSKVONotifying_Person,继承于被观察的类
2.重写setter方法
3.外界改变isa指针
object_getClassName(p)
开始是原类: isa指向Person
p addObserver:forKeyPath:options:context:
注册监听之后:isa指向 NSKVONotifying_Person
根据这个思路,我自己写了一个自定义KVO,仅供技术交流,不能在项目中直接使用


#import "MyKVOPerson+KVO.h"
#import <objc/message.h>
@implementation MyKVOPerson (KVO)

- (void)my_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    
    //创建子类
    //创建一个新的类  模仿系统类 NSKVONotifying_MyKVOPerson
    NSString *oldClassName = NSStringFromClass(self.class);
    NSString *newClassName = [@"MYKVONotifying_" stringByAppendingString:oldClassName];
    
    //创建类 并且注册类
    Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
    objc_registerClassPair(myClass);
    
    //重写setName方法 实际是添加一个和父类同名的方法
    class_addMethod(myClass, @selector(setName:), (IMP)setName, "v@:@");
    
    //外部改变isa指针指向
    object_setClass(self, myClass);
    
    //属性绑定  将观察者保存到当前对象 OBJC_ASSOCIATION_ASSIGN weak 防止循环引用
    objc_setAssociatedObject(self, @"Observer", observer, OBJC_ASSOCIATION_ASSIGN);
}

void setName(id self,SEL _cmd,NSString *newName) {
  
    NSLog(@"修改成功");
    //调用父类的setName方法,改变name的值
    Class class = [self class];
    object_setClass(self, class_getSuperclass(class));
//
    ((void(*)(id,SEL,NSString *))objc_msgSend)(self,@selector(setName:),newName);
    
    //拿到观察者
    id observer = objc_getAssociatedObject(self, @"Observer");
    if (observer) {
        //发消息
        ((void (*)(id, SEL,NSString*,id, NSDictionary<NSKeyValueChangeKey,id> *,void *))objc_msgSend)(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"name":newName,@"kind":@1},nil);
    }
    
    //修改为self,改回子类
    object_setClass(self, class);
    
}

详细Dmeo请移步GitHub👉KVODemo

相关文章

  • iOS - 自定义KVO

    之前我们已经了解过了KVO的底层实现原理,不过呢,在我们开始实现自定义KVO之前再来简单回顾下KVO的实现原理 1...

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

  • iOS-底层原理-自定义KVO

    1.自定义KVO 1.上一篇博客了解了iOS 系统KVO的底层实现原理,那么这里进行自定义KVO,更好的理解原理和...

  • KVO原理探究+自定义KVO实现

    概念 基本使用 触发模式 属性依赖 容器类的使用 自定义KVO 概念 KVO全称Key-Value Observi...

  • 2018-02-14

    探究KVO的底层实现原理 addObserver:forKeyPath:options:context:各个参数的...

  • iOS KVO

    KVO 示例 KVO的实现原理

  • 自定义KVO

    上篇文章KVO我们探究了KVO的一些使用方法和其原理,今天我们来自定义一个KVO。首先我们创建一个NSObject...

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

  • 知识集锦

    https://github.com/starainDou 欢迎点星 KVO实现原理 KVO基本原理: 1 kvo...

  • KVC/ KVO

    1、kvc原理: 45页 2、自定义KVO KVO参考链接 KVO默认观察setter,使用isa-swizzli...

网友评论

    本文标题:KVO原理探究+自定义KVO实现

    本文链接:https://www.haomeiwen.com/subject/bmlfwqtx.html