iOSiOS面试总结

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

2019-04-12  本文已影响141人  it_Xiong

概念

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

上一篇下一篇

猜你喜欢

热点阅读