美文网首页
自定义KVO

自定义KVO

作者: 有梦想的程序员 | 来源:发表于2019-08-14 17:47 被阅读0次

说好的风雨无阻,又拖延了3天,代码早都整理好了,但是没有整理成文章。这是上周日的任务~

说起来KVO(Key-Value Observing),肯定都用过,经常用来监听某一个值的变化。

照常,我们有一个Book类。

//Book.h
@interface Book : NSObject

@property (nonatomic,strong) NSString *name;

@end
//Book.m
@implementation Book

@end

现在需要在ViewController中监听Book属性name的变化~

//ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化
    _book1 = [[Book alloc] init];

    [_book1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    _book1.name = @"Tom";

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSString *name = change[NSKeyValueChangeNewKey];
    NSLog(@"%@",name);
}

这是我们通常监听一个类的一个属性,但是呢,偶然发现了一个好玩的事情,激发了对KVO原理的研究。

observeValueForKeyPath:ofObject:change:context的方法中,打印一下Book类的实例对象_book1的类名。

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{

    //打印类名称
    NSLog(@"KVO类名称:%s   %@ ",object_getClassName(_book1) ,[_book1 class]);
}

2019-08-14 16:28:36.745982+0800 自定义KVO[13369:6261090] 
_book1 getClassName = :NSKVONotifying_Book 
_book1 class =  Book

emmmm?这到底发生了什么?苹果又干了什么见不得光的事情?然后看一下是不是addObserver:forKeyPath:options:context:发生了点什么事情?
输出(断点)一下addObserver前后。

    //方法编号
    SEL sel = NSSelectorFromString(@"setName:");
    
    NSLog(@"监听之前-----setName:%p 类名称:%s",[_book1 methodForSelector:sel],object_getClassName(_book1));
    
    [_book1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    NSLog(@"监听之后-----setName:%p 类名称:%s",[_book1 methodForSelector:sel],object_getClassName(_book1));

    _book1.name = @"Tom";

2019-08-14 16:28:36.744947+0800 自定义KVO[13369:6261090] 监听之前-----setName:0x1079c5ef0 类名称:Book
2019-08-14 16:28:36.745478+0800 自定义KVO[13369:6261090] 监听之后-----setName:0x107d20b5e 类名称:NSKVONotifying_Book

打印了一下方法地址和_book1的类名称,发现都不一样了,出现了我们刚才打印的类名称NSKVONotifying_Book,接着看看NSKVONotifying_Book这个类。

image.png

发现了NSKVONotifying_Book的父类是Book,呵!苹果。

也就是说当我们监听了Book类的name之后,Runtime动态生成了一个Book类的子类NSKVONotifying_Book并且重写了setName:方法。

明白了原理,我们自己动手玩玩~。
自定义custom_addObserver:forKeyPath:options:context:方法,之后调用我们自己的方法。

//Book.m

NSString *getVauleKeyPath(NSString *methodName);

void customSetName(id self, SEL _cmd , NSString *name){
    /*我们肯定要发消息给它
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
     */
    
    //既然我们重写了setName:方法,当然self.name 就不会赋值了,我们需要自己发消息了
    //id self  : CustomKVONotifying_Book 父类是 Book 我们需要向父类发送setName:的消息
    
    struct objc_super superClass = {
        self,
        class_getSuperclass([self class])
    };
    
    ((void (*)(id,SEL,NSString *))objc_msgSendSuper)((__bridge id)(&superClass), _cmd, name);
    
    
    ///给我们的观察者发消息
    
    //发消息的对象
    id observer = objc_getAssociatedObject(self, "observer");
    
    //我们的Key
    NSString *methodName = NSStringFromSelector(_cmd);
    NSString *keyPath = getVauleKeyPath(methodName);
    
    //变更的值
    NSDictionary <NSKeyValueChangeKey,id> * change = @{NSKeyValueChangeNewKey:name};

    ((void (*)(id,SEL, NSString * , id , id , void *))objc_msgSend)(observer, @selector(observeValueForKeyPath:ofObject:change:context:) ,keyPath, self, change, nil);
}

NSString *getVauleKeyPath(NSString *methodName){
    //重写的是setName:方法 我们的keyPath 当然是 ”name”了
    //'set' 和 ':' 不要
    NSRange range = NSMakeRange(3, methodName.length - 4);
    
    //keyPath 当前是 'Name';需要首字母小写
    NSString *keyPath = [methodName substringWithRange:range];
    
    NSString *first = [[keyPath substringToIndex:1] lowercaseString];
    
    //替换
    keyPath = [keyPath stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:first];
    
    return keyPath;
}

@implementation Book

- (void)custom_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    
    ///先创建类
    
    //当前类名
    NSString *currentClass = [NSString stringWithUTF8String:object_getClassName(self)];
    
    //根据KVO命名法,NSKVONotifying_Book 我们改一下
    NSString *custom_Class = [NSString stringWithFormat:@"CustomKVONotifying_%@",currentClass];
    
    //创建类
    Class cutsom_Class =  objc_allocateClassPair([self class], custom_Class.UTF8String, 0);
    
    //向系统注册类,不然怎么用??
    objc_registerClassPair(cutsom_Class);
    
    //修改isa指针 指向我们的子类,不然无法处理我们发送的setName:消息
    object_setClass(self, cutsom_Class);
    
    //重写SetName方法,我们来处理。
    
    //1、首先获取key keyPath,要监听的对象
    
    //keyPath = name,  capitalizedString 首字母大写 setName:
    NSString *methodName = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString];
    
    //方法编号
    SEL sel = NSSelectorFromString(methodName);
    
    //add_Method,照旧,之前文章有详细的描述。(参考消息转发机制)
    class_addMethod(cutsom_Class, sel, (IMP)customSetName, "v@:@");
    
    
    //将监听者绑定到当前类,以便于之后获取监听者,给监听者发消息。
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
    
}

@end

代码和注释都很清晰了,接下来我们整理一下:
1、首先我们需要获取当前类名称object_getClassName
2、我们子类的类名CustomKVONotifying_Book
3、创建我们的子类objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes)
4、向系统注册我们的子类objc_registerClassPair(Class _Nonnull cls)
5、修改我们的isa指针,因为我们需要子类实现setName:方法。
6、动态添加方法class_addMethod,setName:
7、将我们的观察者绑定到当前类,以便于后续获取观察者,像观察者发消息;
8、自己实现setName:;
9、自己实现了setName:我们需要修改Bookname的值,通过调用向父类发消息的方法objc_msgSendSuper
10、向我们绑定的观察者发消息objc_msgSend

这样我们基本复刻了一下KVO苹果见不得光的事情。

相关文章

  • iOS runtime自定义实现KVO

    1、了解KVO 打印结果: 2、自定义实现KVO .h .m

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

  • KVC/ KVO

    1、kvc原理: 45页 2、自定义KVO KVO参考链接 KVO默认观察setter,使用isa-swizzli...

  • iOS-底层原理-自定义KVO

    1.自定义KVO 1.上一篇博客了解了iOS 系统KVO的底层实现原理,那么这里进行自定义KVO,更好的理解原理和...

  • iOS 自定义KVO

    利用Runtime 实现简单的自定义kvo 代码githubgithub.com/zswj/custom-KVO ...

  • 自定义KVO,自动释放,监听多个属性

    自定义的 KVO,支持多属性监听,支持自动释放。 使用系统 KVO 监听属性 先来回顾下系统 KVO 是如何使用的...

  • 21.iOS底层学习之KVO自定义

    本篇文章提纲:1、自定义KVO2、函数式KVO3、KVO的自动销毁机制4、FBKVOController5、GNU...

  • KVO 本质 & 自定义实现

    KVO 本质 & 自定义实现 KVO 是什么? Key-Value Observer 即键值观察者。作用为监听某个...

  • Key-Value Observing(kvo)二:自定义kvo

    一、自定义kvo 在上篇文章 kvo原理分析[https://www.jianshu.com/u/a569f590...

  • iOS - 自定义KVO

    之前我们已经了解过了KVO的底层实现原理,不过呢,在我们开始实现自定义KVO之前再来简单回顾下KVO的实现原理 1...

网友评论

      本文标题:自定义KVO

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