美文网首页
KVO 、category实现原理

KVO 、category实现原理

作者: sudhengshi | 来源:发表于2017-07-11 17:42 被阅读82次

    一、KVO原理

    1. KVO 简介

    KVO 是 Objective-C 对观察者设计模式的一种实现。KVO 提供一种机制,指定一个被观察对象,当对象某个属性发生改变时,对象会获得通知,并做出相应处理;

    在 MVC 设计架构的项目,KVO机制很适合实现 model 模型和 View 视图之间的通讯。

    2. 实现原理

    KVO 的实现依赖于 Objective-C 强大的 Runtime。
    当观察某对象α时,KVO 机制动态创建一个对象α当前类的子类,并为这个新的子类重写被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

    3. 深入剖析:

    Apple 使用了 isa-swizzling 来实现 KVO。当观察对象α时,KVO 机制动态创建一个新的名为:NSKVONotifying_α的新类,该类继承自对象α的本类,且 KVO 为 NSKVONotifying_α重写观察属性的 setter 方法,setter 方法会负责在调用原setter 方法之前和之后,通知所有观察对象属性值的更改情况。

    NSKVONotifying_α类剖析:在这个过程,被观察对象的 isa 指针从原来的α类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_α类,来实现当前类属性值改变的监听。

    所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统对 KVO 的底层实现过程,此时如果我们创建一个新的名为“NSKVONotifying_α”的类,就会发现系统运行到注册 KVO 的那段代码时程序就会崩溃。因为系统在注册监听的时候创建了名为 NSKVONotifying_α的中间类,并指向这个中间类。

    (isa 指针的作用: 每个对象都有 isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa 指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。)因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。

    子类 setter 方法剖析:KVO 的键值观察通知依赖于 NSOjbect 的两个方法:willChangeValueForKey:和 didChangeValueForKey:,在存取数值的前后分别调用2个方法:
    被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后,didChangeValueForKey:被调用,通知系统该 keyPath 的属性值已经变更;之后,observeValueForKey:ofObject:change:context:也会被调用。且重写观察属性的 setter 方法这种继承方式的注入是在运行时而不是编译时实现的。

    KVO 为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

    -  (void)setName:(NSString *)newName{
        [self willChangeValueForKey:@"name"];
        [super setValue:newName forKey:@"name"];
        [self didiChangeValueForKey:@"name"];
    }
    

    4.特点:

    观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行 KVO 的回调方法,例如是否执行了 setter 方法、或者是否使用了 KVC 赋值。如果赋值没有通过 setter 方法或者 KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这事是不会触发 KVO 机制,更加不会调用回调方法的。所以使用 KVO 机制的前提是遵循 KVO 的属性设置方式来变更属性值。

    5.步骤

    • 1.注册观察者,实施监听;
    • 2.在回调方法中处理属性发生的变化;
    • 3.移除观察者

    6. 实现方法

    A. 注册观察者:

    //第一个参数 observer:观察者(这里观察 self.myKVO 对象的属性变化)
    //第二个参数 keyPath: 被观察的属性名称(这里 self.myKVO 中 num属性值的改变)
    //第三个参数 options:观察属性的新值、旧值等的一些配置(枚举值,可以根据需要设置,例如这里可以使用两项)
    //第四个参数 context: 上下文,可以为 kvo 的回调方法传值(例如设定为一个放置数据的字典)
    [self.myKVO addObserver:self forKeyPath:@"num" options: NSKeyValueObservingOptionOld|NSKeyValueObservingOpitonNew context:nil];
    

    B.属性(keyPath)的值发生变化时,收到通知,调用以下方法:

    //keyPath:属性名称
    //object: 被观察的对象
    //change:变化前后的值都存储在 change 字典中
    //context: 注册观察者时,context 传过来的值
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *) change context:(void *)context{
    }
    

    7.拓展

    1.KVC 和 KVO 的不同
    KVC(键值编码),即 Key-Value Coding,一个非正式的 Protocol,使用字符串(键)访问一个对象实例变量的机制。而不是通过调用 Setter、Getter 方法等显式的存取方法去访问。KVO(键值监听),即 Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了 setter 方法、或者使用了 KVC 赋值。

    2.和 notification(通知)的区别?
    notification 比 KVO 多了发送通知的一步。
    两者都是一对多,但是对象之间直接的交互,notification 明显很多,需要 notificationCenter 来作为中间交互。而 KVO 如我们介绍的,设置观察者->处理属性变化,

    notification 的优点是监听不局限与属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更更显灵活方便。

    1. 与 delegate 的不同

    和 delegate 一样,KVO 和 NSNotification 的作用都是类与类之间的通信。但是与 delegate 不同的是:
    这两个都是负责发送接收通知,剩下的事情由系统处理,所以不同返回值;而 delegate 则需要通信的对象通过变量(代理)联系;
    delegate 一般是一对一,而这两个可以一对多。

    二、Category实现原理

    1. 简介

    category 是 Objective-C 2.0 之后添加的语言特性。主要作用是为已经存在的类添加方法。还有以下两种用法:

    可以把类的实现分开在几个不同的文件里面。1)可以减少单个文件的体积, 2)可以把不同的功能组织到不同的 category 里 3)可以由多个开发者共同完成一个类 4)可以按需加载想要的 category 等等

    声明私有方法。

    2. 比较 category 和 extension

    extension 在编译期决定,它是类的一部分,在编译期和头文件里的@interface 以及实现文件里的@implement 一起形成一个完整的类,它伴随类的产生而产生,消亡而消亡。extension 一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加 extension,所以无法为系统的类添加 extension。

    但是 category 实在运行期决定的,就 category 和 extension 的区别来看,extension 可以添加实例变量,而 category 无法添加实例变量(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。

    3. category 原理

    所有 OC 类和对象,在 Runtime 层都是用 struct 表示的,category 也不例外,在 runtime 层,category 用结构体 category_t (在 objc-runtime-new.h 中可以找到此定义),它包括了:
    1)类的名字(name)
    2)类(cls)
    3)category 中所有给类添加的实例方法的列表(instanceMethods)

    1. category 中所有添加的类方法的列表(classMethods)
      5) category 实现的所有协议的列表(Protocols)
      6) category 中添加的所有属性(instanceProperties)
    typedef  struct category_t {
            const  char  * name;
            classref_t  cls;
            struct  method_list_t  *instanceMethods;
            struct  method_list_t  * classMethods;
            struct  protocol_list_t  * protocols;
            struct  property_list_t  * instanceProperties;
    } category_t;
    

    从 category 的定义也可以看出 category 的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
    MyClass.h:

    #import  <Foundation/Foundation.h>
    @interface MyClass:NSObject
    - (void)printName;
    @end
    
    @interface MyClass(MyAddition)
    @property(nonatomic,copy) NSString *name;
    - (void)printName;
    @end
    

    MyClass.m

    #import"MyClass.h"
    @implementation MyClass
    - (void)printName{
        NSLog(@"%@",@"MyClass");
    }
    @end
    
    @implementation MyClass(MyAddition)
    - (void)printName{
        NSLog(@"%@",@"MyAddition");
    }
    

    4. category 和关联对象

    在 category 里是无法为 category 添加实例变量的。但是我们很多时候需要在 category 中添加和对象关联的值,这个时候可以求助关联对象来实现。

    MyClass + Category.h:

    #import  “MyClass.h”
    @interface MyClass(Category1)
    @property(nonatomic,copy) NSString *name;
    @end
    

    MyClass+Category.m:

    #import "MyClass+Category.h"
    #import <objc/runtime.h>
    
    @implementation MyClass(Category)
    
    - (void)setName:(NSString *)name{
        objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY);
    }
    
    -(NSString *)name{
    
        NSString *nameObject = objc_getAssociatedObject(self, "name");
        return nameObject;
    
    }
    
    
    @end 
    

    相关文章

      网友评论

          本文标题:KVO 、category实现原理

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