• 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开发的问题!欢迎下方留言!!!或者邮件lieryangios@126.com 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!

  • 相关阅读:
    AESUtil_1
    ELK配置
    Centos7上安装docker
    Excel大批量数据导出
    Redis5.0.6安装完整步骤
    idea远程打断点
    [HNOI2016] 序列
    [TJOI2017] 异或和
    洛谷 P4933 大师
    洛谷 P1950 长方形_NOI导刊2009提高(2)
  • 原文地址:https://www.cnblogs.com/CoderEYLee/p/Object-C-0025.html
Copyright © 2020-2023  润新知