ios KVO原理和实现简易KVO

2019-03-03  本文已影响0人  songjk

一内容概述

本文主要分析kvo的底层原理,以及如何自己实现简易kvo。kvo的官方解释:Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.

二详细步骤

1.ios系统自带kvo实现原理

A.如何使用系统kvo

需求是要控制器要观察属性myBoy中属性name的变化,myBoy为boy类的对象,实现方式如下:

// 添加观察 myBoy添加控制器也就是下面的self为观察者,观察的key为name
[self.myBoy addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil];
//观察回调方法 当myBoy通过.name或者setValue:forKey:方法改变name值时就会调用下面的方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"change:%@",change);
}

B.原理说明

1.当myBoy调用addObserver这个方法之后,系统会创建一个boy的派生类:NSKVONotifying_boy,在该子类中重写观察的属性(name)的set方法,然后会把当前对象myBoy的isa指针指向这个子类。注意虽然当前对象的isa指针变了,但是当前对象在内存中没有变,并没有生成子类的对象。

2.当myBoy的name属性发生变化的时候,会调用子类对象的set方法,在这个方法中,会先调用willChangeValueForKey:方法,然后调用父类(boy类)的set方法设置属性值,最后调用didChangeValueForKey:方法,这个时候就会调用观察者的observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context方法,告诉观察者观察的属性改变了。

3.手动调用系统kvo,首先要完成前面1、2代码,然后在boy类种重写方法:

//设置手动或者自动开启KVO 默认为YES 如果设置为NO 需要手动调用
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    return YES;
}

然后在需要触发通知的地方调用以下方法,也就是说即使不改变myBoy种name的值,只要调用以下两个方法也可以实现KVO通知:

[self.myBoy willChangeValueForKey:@"name"];
[self.myBoy didChangeValueForKey:@"name"];

2.实现简易kvo,我们在上面的需求上实现一个简易kvo

A. 实现逻辑为下面三个步骤:

a>创建子类
b>为子类添加该属性的set方法
c>将myBoy的isa指针指向子类

B. 具体实现代码

@implementation NSObject (kvo)
-(void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
    // a创建子类 需要在子类的set方法中实现通知观察者observer
    NSString*subname = [NSString stringWithFormat:@"JK_%@",NSStringFromClass([self class])] ;
    Class newclass = NSClassFromString(subname);
    if (!newclass)
     {
        newclass = objc_allocateClassPair([self class], subname.UTF8String, 0);
     }
    objc_registerClassPair(newclass);
    
    // b为子类添加该属性的set方法 注意oc的方法实际上会变成调用函数 该函数的前两个参数必须是id self, SEL _cmd
    // 最后一个参数表示该函数的参数 v表示void @表示对象 :表示SEL
    class_addMethod(newclass, @selector(setName:), (IMP)setName, @"v@:@".UTF8String);
    
    // cisa指针指向新子类
    object_setClass(self, newclass);
    // 把观察者关联到自己身上 用assign的方式 避免循环引用
    objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN);
}
// 实现子类的set方法
void setName(id self, SEL _cmd, NSString* name)
{
    
    Class subclass = [self class];
    Class superclass = class_getSuperclass(subclass);
    object_setClass(self, superclass);
    // 调用本身的set方法设置值
    objc_msgSend(self, _cmd, name);
    object_setClass(self, subclass);
    //通知观察者
    id observer = objc_getAssociatedObject(self, @"observer");
    if (observer) { //当观察者被销毁后不会崩溃
        [observer observeValueForKeyPath:NSStringFromSelector(_cmd) ofObject:self change:@{@"new":name} context:nil];
    }
}

调用以下方法添加观察者,就可以如同系统kvo方法一样观察到属性的变化了:

-(void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context

备注

kvo官方地址:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html
本文demon地址:
https://github.com/songjk/ioskvo.git

上一篇 下一篇

猜你喜欢

热点阅读