ios-KVO原理

KVO允许一个对象去监听另一个对象的某个属性,当该属性改变时系统会去通知监听的对象。KVO的开启方式我们可以手动开启也可以自动开启,现在这个添加观察者的方法中的options有四个值可以去取

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options 
context:(nullable void *)context;
NSKeyValueObservingOptionNew,就是说如果包含了这个参数,观察者收到的change参数中就会包含NSKeyValueChangeNewKey和它对应的值,观察者可以得到属性在被改变之后的新值。
NSKeyValueObservingOptionOld 包含这个参数可以得到这个属性的旧值。

NSKeyValueObservingOptionInitial如果包含这个参数,那么在addObserver的时候就会去发送一条通知,我们可以获取到初始的值。
NSKeyValueObservingOptionPrior 当包含这个参数,那么在被观察的属性的值改变前和改变后,系统都会给观察者发送一个通知;在属性的值改变之前发送的通知中,change参数会包含NSKeyValueChangeNotificationIsPriorKey并且值为YES。打印的change

我们在监听方法中可以进行监听属性的变化
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)
change context:(void *)context
{
    NSLog(@"%@",[object valueForKey:keyPath]);
    NSLog(@"%@",change);
}
KVO如何设置手动调用,我们必须要重写NSObject实现的automaticallyNotifiesObserversForKey的方法,对我们需要实现手动发送的key返回NO,其他的就去调用super的方法,也就是下面的这个方法。


+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)Key {
    
    BOOL automatic;
    if ([Key isEqualToString:@"name"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:Key];
    }
    return automatic;
}
我们有的时候可能会想要满足某种条件之后再去发送通知那可以用下面的方法手动的发送通知。


 [_family willChangeValueForKey:@"name"];
 [_family didChangeValueForKey:@"name"];
KVO的实现原理,其实当我们自动的去监听对象A的属性变化,在添加观察者的时候,系统会动态的在运行时去给这个对象A所属的类,创建一个子类,就比如说我原先的类是ZXFamily,我们使用KVO添加观察者之后会创建的子类是NSKVONotifying_ZXFamily。实际上就是在这个过程中,被观察对象的 isa 指针从指向原来的ZXFamily类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_Family类,来实现当前类属性值改变的监听;会在这个子类中重写本类中任何被观察属性的 setter 方法。setter 方法随后负责通知观察对象属性的改变。如下所示,在添加观察者之前和之后

关于isa指针,sa是一个指向Class类指针(专业术语是指向元类,pointer to the metaclass),用来指向类的类型,我们可以通过object_getClass方法来获取这个值; 正常来说,class方法内部的实现就是获取这个isa指针代表的元类(metaclass),但在KVO机制中苹果注册监听对象后 通过objc_allocateClassPair动态重新创建了一个元类,此时object_getClass()获取的事就不是原来isa指向的元类 而是是新建的元类。
可以这么说class分为两种,一种是实例方法,另外一种是类方法。

+ (Class)class {
    return self;
}
 
- (Class)class {
    return object_getClass(self);
}
关于runtime的使用我们还可以这么做,可以动态改变isa的指向。,这里下面本来上面那个打印调用的是数组的description会把数组中的元素打印出来,但是如果把所属的类变成NSObject的话打印出来的就是类名和内存地址了。


    NSArray *Obj = @[@"hh", @"kkk"];
    NSLog(@"Obj:%@", Obj);
    object_setClass(Obj, [NSObject class]);
    NSLog(@"Obj:%@", Obj);

KVO其实是基于KVC来实现的

如果我们有一个对象,这个对象中有数组属性,我们想要去监听这个数组的变化,就比如如下所示要去监听family中的persons数组的添加删除和替换操作我们可以这么做先去监听这个数组,利用下面的方法来做就可以让监听方法监听到值的改变。其实也就是通过KVC可能去改变了addObject和removeObject和replace的方法。


    [_family addObserver:self forKeyPath:@"persons" options:NSKeyValueObservingOptionNew context:nil];
    
    [[_family mutableArrayValueForKeyPath:@"persons"]addObject:@"sda"];
    
    [[_family mutableArrayValueForKeyPath:@"persons"]removeObject:@"sda"];
    
    [[_family mutableArrayValueForKeyPath:@"persons"] replaceObjectAtIndex:0 withObject:@"dasdadwdw"];
如果我们的对象中有另一个对象,想要去监听这个另外一个对象的属性的变化。我们当然可以直接用keyPath来,就是如下所示
 [_family addObserver:self forKeyPath:@"person.name" options:NSKeyValueObservingOptionNew context:nil];
但是这样写未免太过麻烦,我们以后都还是要这么写。我们可以直接在一个方法中进行操作,也就是去设置子级路径关联,也就是说下面这个属性person跟我们设置的person的name属性相关联起来。
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    
    NSSet *keySet = [super keyPathsForValuesAffectingValueForKey:key];
    
    if ([key isEqual:@"person"]) {
        
        NSSet *set = [NSSet setWithObject:@"_person.name"];
        keySet = [keySet setByAddingObjectsFromSet:set];
        
    }
    return  keySet;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值