前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >KVO简单使用及其实现原理

KVO简单使用及其实现原理

作者头像
Oceanlong
发布2018-09-21 12:02:59
1.3K0
发布2018-09-21 12:02:59
举报
文章被收录于专栏:移动开发面面观

前言

在应用开发中,我们经常需要使用到,观察者模式,能监听某些对象属性的变化,进行相应的操作。在iOS中,OC为我们提供了一套更加简洁优雅的观察方式——KVO。

KVO全称Key Value Observing,键值监听机制,由NSKeyValueObserving协议提供支持,NSObject类继承了该协议,所以NSObject的子类都可使用该方法。

KVO的使用

KVO的使用非常简单,我们只需要给对象的指定属性绑定监听,并设置监听类型。每当我们对指定的属性进行读写操作时,就会,OC就会调用observeValueForKeyPath方法。我们在observeValueForKeyPath中进行判断,确定是哪个对象的事件。

代码语言:javascript
复制
    Person *person = [Person alloc]int];
    /*
     作用:给对象绑定一个监听器(观察者)
     - Observer 观察者
     - KeyPath 要监听的属性
     - options 选项(方便在监听的方法中拿到属性值)
     */
    [person addObserver:person forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

    // 移除监听
    [person removeObserver:person forKeyPath:@"name"];

    /**
     *  当监听的属性值发生改变
     *
     *  @param keyPath 要改变的属性
     *  @param object  要改变的属性所属的对象
     *  @param change  改变的内容
     *  @param context 上下文
     */
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
        NSLog(@"%@------%@------%@", keyPath, object, change);
    }

在上面的代码中,我们对Personname属性进行监听。当我们读写name时,系统就会调用observeValueForKeyPath方法了。

KVO的原理概述

那么,系统是如何实现KVO的呢? 其实,每当我们使用KVO的addObserver时,系统会默默地创建一个类。我们估且把它叫作:KVO_Person。然后,系统动态地让其继承Person类,并添加方法:setNamegetName。在KVO_PersonsetNamegetName方法实现中,添加observeValueForKeyPath的方法的调用。

此时,我们如果调用KVO_PersonsetNamegetName,则会调用observeValueForKeyPath

但是,在KVO的使用中,我们对KVO_Person是完全不知情的。那么,我们如何调用它的setNamegetName呢?其实,在创建KVO_Person时,我们就将Personisa指针,换成了KVO_Person

Runtime一瞥

isa是什么呢?这里涉及到iOS的Runtime知识。iOS的Runtime博大精深,在此只讲其中一小部分。

在iOS中,所有的方法调用,都是以发消息的方式进行的。

我们先来了解一下OC中类的结构

代码语言:javascript
复制
//对象
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
//类
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
//方法
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

一个类接收到消息后,处理的流程是:

  1. 系统首先找到消息的接收对象,然后通过对象的isa找到它的类。
  2. 在它的类中查找method_list,是否有selector方法。
  3. 没有则查找父类的method_list。
  4. 找到对应的method,执行它的IMP。
  5. 转发IMP的return值。

所以,我们知道,当我们替换掉isa时,其实就是替换掉了消息的处理类。在上面的例子中,当我们调用addObserver时,其实就是将Person的isa替换为KVO_Person

手动实现一个小小的KVO

最后,让我们自己来实现一个小小的KVO,帮助读者理解iOS的Runtime特性和KVO的实现原理。

代码语言:javascript
复制
#import "ViewController.h"
#include <objc/runtime.h>
#import "Person.h"


@interface ViewController ()

@property(nonatomic, strong) UIButton* button;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    self.button = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
    [_button setTitle:@"test" forState:UIControlStateNormal];
    [_button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [_button addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:_button];
}

- (void)click{
    
    
    Person *person = [[Person alloc]init];
//-------------------------------------------------------------------
    // 创建类 KVO_Person
    Class kvo_person = objc_allocateClassPair([Person class], "KVO_Person", 0);
    // 添加属性
    objc_property_attribute_t type = { "T", "@\"NSString\"" };
    objc_property_attribute_t ownership = { "C", "" }; // C = copy
    objc_property_attribute_t backingivar  = { "V", "_privateName" };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    class_addProperty(kvo_person, "name", attrs, 3);
    // 添加方法
    class_addMethod(kvo_person, @selector(setName:), (IMP)nameSetter, "v@:@");
    // 注册该类
    objc_registerClassPair(kvo_person);
    // 替换 isa
    object_setClass(person, kvo_person);
//-------------------------------------------------------------------
    [person setName:@"test"];
}

//set方法
void nameSetter(id self, SEL _cmd, NSString *newName) {
    NSLog(@" -------------------- Person set start ");
    Ivar ivar = class_getInstanceVariable([self class], "_privateName");
    id oldName = object_getIvar(self, ivar);
    if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
    NSLog(@" -------------------- Person set end ");
}

@end

以上就是KVO的简单使用及其实现原理。

如有问题,欢迎指正。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.08.20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • KVO的使用
  • KVO的原理概述
  • Runtime一瞥
  • 手动实现一个小小的KVO
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档