KVO底层实现原理,仿写KVO

这篇文章简单介绍苹果的KVO底层是怎么实现的,自己仿照KVO的底层实现,写一个自己的KVO监听

#pragma mark--KVO底层实现

第一步:新建一个Person类继承NSObject

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

//字符串类型的属性name
@property (nonatomic, strong) NSString *name;

@end

Person.m

#import "Person.h"

@implementation Person
- (void)setName:(NSString *)name
{ //别问为什么(下面有用处),就是要自己处理set方法
    _name = [NSString stringWithFormat:@"%@aaaa",name];
}
@end

第二步:在控制器中创建一个Person类型的对象p,利用苹果的KVO来监听该对象p的name属性的变化

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

ViewController.m

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@property (nonatomic, strong) Person *p;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    Person *p = [[Person alloc] init];

    // 监听name属性有没有改变
    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    self.p = p;
}

//点击屏幕修改self.p的name属性的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    static int i = 0;
    i++;
    self.p.name = [NSString stringWithFormat:@"%d",i];
}

// 所有的KVO监听都会来到这个方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"%@",self.p.name);
}

@end

会打印1aaaa,2aaaa,3aaaa...... //从打印可以看出KVO监听的是set方法的调用!!!

实际上:KVO的本质就是监听一个对象有没有调用set方法!!!

怎么验证呢?

1.将Person.h中的代码修改为

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
    @public
    NSString *_name;//为了验证KVO监听的是setter方法
}

@end

将ViewController.m的viewDidLoad中的代码修改为

- (void)viewDidLoad
{
    [super viewDidLoad];

    Person *p = [[Person alloc] init];

    // 监听_name有没有改变
    [p addObserver:self forKeyPath:@"_name" options:NSKeyValueObservingOptionNew context:nil];
    self.p = p;
}

运行后,点击屏幕是没有任何打印的-->结论1:KVO的本质就是监听一个对象有没有调用set方法!!!

第三步:打断点看一下addObserver 到底干了什么事情?

继续走一步:

这个NSKVONotifying_Person类是什么鬼?

估计你已经猜到了! 没错这个NSKVONotifying_Person类就是系统帮我们实现的!!!

怎么验证呢? 想到了吗?我们自己把这个类给重写了!看看会发生什么?

第四步:创建NSKVONotifying_Person类

NSKVONotifying_Person.h

#import <Foundation/Foundation.h>

@interface NSKVONotifying_Person : NSObject

@end

NSKVONotifying_Person.m

#import "NSKVONotifying_Person.h"

@implementation NSKVONotifying_Person

@end

第五步运行:

恭喜猜测没错,验证通过!!!

结论2:系统创建了一个NSKVONotifying_XXX的类

结论3:修改了对象p的isa指针

那么系统帮我们创建的NSKVONotifying_XXX类有没有继承自我们自己常见的XXX类呢?

我既然这么问,肯定是继承啦!!因为我们已经把属性对应的set方法给重写了!!!因为已经修改了对象的指针(用户调用对象p的方法就不是Person的方法了,是isa指针指向的类的对应的方法),如果不继承的话,相当于把用户重写的set方法给覆盖了,用户调用自己写的set方法就不起作用了!!!也就有了 结论4:重写对应的set方法再内部实现父类做法,通知观察者

KVO底层实现结论:(都是系统自动帮我们实现的)

1> 创建NSKVONotifying_XXX的类

2> 重写对应属性的set方法,在内部实现父类做法,通知观察者

3> 修改当前对象的isa指针,指向创建的NSKVONotifying_XXX(这样实际调用的时候就会走NSKVONotifying_XXX类中对应的方法)

#pragma mark--仿写KVO实现

知道了KVO底层实现的原理就可以仿照KVO写一个自己的"KVO"了

步骤一:给NSObject写一个分类

NSObject+KVO.h

#import <Foundation/Foundation.h>

@interface NSObject (KVO)

//添加监听
- (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

//移除监听
- (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context;

@end

NSObject+KVO.m

    //
    //  NSObject+KVO.m
    //  KVO底层实现
    //
    //  Created by lieryang on 2017/6/17.
    //  Copyright ? 2017年 lieryang. All rights reserved.
    //

#import "NSObject+KVO.h"
#import <objc/message.h>

static NSString * const EYKVONotifying_ = @"EYKVONotifying_";
static NSString * const observerKey = @"observer";
@implementation NSObject (KVO)

    //添加监听
- (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
    if (keyPath.length == 0) {//如果传进来的keyPath为@""或者为nil 直接返回
        return;
    }

    // 1. 检查对象的类有没有相应的 setter 方法。
    SEL setterSelector = NSSelectorFromString([self setterForGetter:keyPath]);

    Method setterMethod = class_getInstanceMethod([self class], setterSelector);

    if (!setterMethod) {//如果没有直接返回,不需要任何处理
        NSLog(@"找不到该方法");
        return;
    }

    // 2. 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类
    Class clazz = object_getClass(self);
    NSString *className = NSStringFromClass(clazz);

    if (![className hasPrefix:EYKVONotifying_]) {
        clazz = [self ey_KVOClassWithOriginalClassName:className];
        object_setClass(self, clazz);
    }

    // 3. 为EYKVONotifying_XXX添加setter方法的实现
    const char *types = method_getTypeEncoding(setterMethod);
    class_addMethod(clazz, setterSelector, (IMP)ey_setter, types);

    // 4. 添加该观察者到观察者列表中
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey));
    if (!observers) {
        observers = [NSMutableArray array];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    if ([observers indexOfObject:observer] == NSNotFound) {
        [observers addObject:observer];
    }
}

    //移除监听
- (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context
{

    NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(observerKey));

    [observers removeObject:observer];
}
#pragma mark - 注册自己的EYKVONotifying_XXX
- (Class)ey_KVOClassWithOriginalClassName:(NSString *)className
{
    // 生成EYKVONotifying_XXX的类名
    NSString *kvoClassName = [EYKVONotifying_ stringByAppendingString:className];
    Class kvoClass = NSClassFromString(kvoClassName);

    // 如果EYKVONotifying_XXX已经被注册过了, 则直接返回
    if (kvoClass) {
        return kvoClass;
    }

    // 如果EYKVONotifying_XXX不存在, 则创建这个类
    Class originClass = object_getClass(self);
    kvoClass = objc_allocateClassPair(originClass, kvoClassName.UTF8String, 0);

    // 修改EYKVONotifying_XXX方法的实现, 学习Apple的做法, 隐瞒这个EYKVONotifying_XXX
    Method classMethod = class_getInstanceMethod(kvoClass, @selector(class));
    const char *types = method_getTypeEncoding(classMethod);
    class_addMethod(kvoClass, @selector(class), (IMP)ey_class, types);

    // 注册EYKVONotifying_XXX
    objc_registerClassPair(kvoClass);

    return kvoClass;
}

Class ey_class(id self, SEL cmd)
{
    Class clazz = object_getClass(self); // EYKVONotifying_XXX
    Class superClazz = class_getSuperclass(clazz); // origin_class
    return superClazz; // origin_class
}

/**
 *  重写setter方法, 新方法在调用原方法后, 通知每个观察者
 */
static void ey_setter(id self, SEL _cmd, id newValue)
{
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = [self getterForSetter:setterName];

    if (!getterName) {
        NSLog(@"找不到getter方法");
    }

    // 调用原类的setter方法
    struct objc_super superClazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    // 这里需要做个类型强转, 否则会报too many argument的错误
    ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);

    // 找出观察者的数组
    NSArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey));
        // 遍历数组
    for (id observer in observers) {
        // 调用监听者observer的observeValueForKeyPath 方法,因为observer为id类型,所以就偷懒调用了系统的监听回调,要是自己定义方法,会报找方法的错误,可以在添加监听的时候,传进来一个block代码块,在此处回调block,更方便外界的调用
        [observer observeValueForKeyPath:getterName ofObject:self change:nil context:nil];
    }
}

#pragma mark - 生成对应的setter方法字符串
- (NSString *)setterForGetter:(NSString *)key
{
    // 1. 首字母转换成大写
    NSString * firstString = [[key substringToIndex:1] uppercaseString];
    // 2. 剩下的字母
    NSString * remainingString = [key substringFromIndex:1];

    // 3. 最前增加set, 最后增加: setName:
    NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstString, remainingString];

    return setter;

}
#pragma mark - 生成对应的getter方法字符串
- (NSString *)getterForSetter:(NSString *)key
{
    // setName
    if (key.length <=0 || ![key hasPrefix:@"set"] || ![key hasSuffix:@":"]) {
        return nil;
    }

    // 移除set和:
    NSRange range = NSMakeRange(3, key.length - 4);
    NSString *getter = [key substringWithRange:range];

    // 小写
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                       withString:firstString];

    return getter;
}
@end

外界使用

创建Person类

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString * name;

@end

Person.m

#import "Person.h"

@implementation Person

@end
#import "ViewController.h"
#import "Person.h"
#import "NSObject+KVO.h"

@interface ViewController ()

@property (nonatomic, strong) Person * p;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    Person * p = [[Person alloc] init];
    [p ey_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    self.p = p;
}

// 监听的回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"%@", self.p.name);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];

    self.p.name = [NSString stringWithFormat:@"%u", arc4random_uniform(20)];
}

@end

更多内容--> 博客导航 每周一篇哟!!!

有任何关于iOS开发的问题!欢迎下方留言!!!或者邮件[email protected] 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!

时间: 2024-12-20 23:21:28

KVO底层实现原理,仿写KVO的相关文章

KVO底层实现原理

(KVO)键值观察者底层解析 涉及到了runtime,关于isa指针 手动实现键值观察(代码示例) 被观察的对象Target(重写setter/getter方法)Target.h @interface Target : NSObject { int age; } // for manual KVO - age- (int) age; - (void) setAge:(int)theAge; @end Target.m @implementation Target - (id) init{ sel

KVO的实现原理

摘自:iOS--KVO的实现原理与具体应用 1 KVO是什么? KVO是Objective-C对观察者模式的一种实现,另外一种是通知机制(notification) KVO提供一种机制,指定一个被观察对象(例如A类),当对象的某个属性(例如A中的字符串name)发生更改时,对象会获得通知,并做出相应的处理. 在MVC设计架构下的项目,KVO机制很适合实现model模型和view视图之间的通讯. 例如:代码中,在模型类A创建属性数据,在控制器中创建观察者,一旦属性数据发生改变,观察者就会收到通知,

通过子类实现KVO,浅析KVO底层原理

通过手动实现KVO,对KVO底层原理有一定认识. KVO只要是通过监听set方法,从而实现对该对象的监听. 要监听set方法,有两种实现方式,第一就是使用分类,重写set方法,但是这样就会覆盖父类的set方法,所以不可行,pass掉. 第二就是使用子类,把父类的isa指针改为子类.然后调用父类色set方法,最后调用回调方法,该方案可行. 首先是注册监听,在调用监听方法的时候,会动态实现子类,把observer保存到子类的属性中(弱引用weak类型,不能使用strong,会造成循环引用),并且把类

iOS 的 KVC &amp; KVO的底层实现原理

KVO 内部实现原理 1. KVO 是基于runtime机制实现的. 2. 当某个类的对象第一次被观察时,系统就会在运行期动态的创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法; 派生类在被重写的setter方法中实现真正的通知机制 (Person -> NSKVONotifying_Person) 1.KVC,即是指 NSKeyValueCoding,一个非正式的Protocol,提供一种机制来间接访问对象的属性.而不是通过调用Setter.Getter方法访问.

ios KVO的实现原理

首先给大家介绍一下KVO的使用场景:当某个对象的某个属性改变的时候,需要我们做出相应的处理事件.比如我们自定义下拉刷新,那么我们是如何得知用户要进行的下拉刷新数据操作呢,我们可以监听控件的frame,通过用户下拉该控件的时候,会修改该控件的frame.y属性,我们使用KVO监听这个属性.当这个属性的y值在某个范围,我们认为是下拉刷新操作.我们可以去进行数据请求.因为现在下拉刷新的第三方框架有很多,所以很少有人关注下拉刷新的实现原理. 又比如,我们经常使用的网络请求的第三方AFN,它内部监听网络下

KVO内部实现原理

// // ViewController.m // KVO内部实现原理 // // Created by sw on 15/4/13. // Copyright © 2015年 sw. All rights reserved. // #import "ViewController.h" #import "Person.h" @interface ViewController () @end @implementation ViewController - (void

仿写及比较标哥的iOS时钟动画

一.前言 以前看各种绚丽的UI特效动画代码,采用的方法是会先运行一篇,然后直接去看实现代码.初学时抱着瞻仰的态度去接触,去认识,是没有错的.但是在了解了像素.动画渲染机制,CoreAnimation API,推导过二维.三维的仿射矩阵之后,我们可以改变阅读UI动画博文或者是源码的方式了. Talk is cheap, show me the code——Linus Torvalds. 大量的仿写:一定一定要多写——叶孤城__ 在CodeReview线下大会上的发言. 最近安居客.猿题库.蘑菇街.

怎么样仿写已知网址的网页?

今天上午在实验室里学习,无意中看到湖北老乡群里爆出了一则外包的消息. 是问有没有回搭建网站.我接了这单活儿.需求很简单,仿照这一个已知的网站做一个静态页面. 工作量不大.他说了,做一个静态网站.因为之前,我曾做过类似的工作,所以我就答应了. 遇到的第一个问题就是评价.他让我开个价.说实话,我当时也蒙了.这个外包估价这个事情,我之前也没做过.在网上搜了下,每个人说法都不一样.看到一个我觉得可以参考的回答.按照做的页面收费. 每个30~50.于是我给他报价是500,理由就是 大概做10个左右的页面,

由PHP底层工作原理说起

之前做过.net,java开发,也写过几个Php的网站,似乎3种主要编程语言都接触了.但是越来越觉得自己对编程的整个流程缺乏一个整体的认识,尤其是底层的机制.譬如网络编程,编译原理,服务器端,数据库存储引擎原理等.于是看了一些书,比较经典的有apue,unp,tcp/ip,nginx,mysql的innodb存储引擎,深入理解jvm.渐渐发现无论用什么语言做开发,背后都有linux,shell,c/c++,nginx服务器,mysql的身影.也许只有掌握了这些核心的原理知识,一个程序员才具有核心