美文网首页
从MJExtension引发的对KVC和KVO的见解

从MJExtension引发的对KVC和KVO的见解

作者: 程序狗 | 来源:发表于2018-06-07 17:58 被阅读17次

    题外话:
    最近在看MJExtension的源码,整体思路与其他数据映射模型一样。
    以字典转模型为例,MJ会把模型的属性剥离出来,分别建立一个对象去存储该属性的类型(例如,该属性的类,是否为引用类型还是数据类型等等),最后利用KVC将字典的值一一赋值到属性中,从而完成映射。YYModel是采用的setter和getter方法,据他测试,速度会比KVC快。
    题内话:
    什么是KVC,KVC就是键值编码。键值编码看起来有点抽象,你可以理解成一个对象就是一个字典,属性则为它的键,属性值则为该键的值。全名叫NSKeyValueCoding,提供一种机制来间接访问对象的属性。

    KVC常用方法
    - (void)setValue:(nullable id)value forKey:(NSString *)key; // 为对象的属性赋值
       - (id)valueForKey:(NSString *)key;  // 根据key取值
       - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  // 为对象的属性赋值(包含了setValue:forKey:的功能,并且还可以对对象内的类的属性进行赋值)
       - (nullable id)valueForKeyPath:(NSString *)keyPath; // 根据keyPath取值
       - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues; // 对模型一次性赋值,前提是必须声明好所有对应的属性(key)
    

    假设存在以下模型

    #import <Foundation/Foundation.h>
    
    typedef enum {
        SexMale,
        SexFemale
    } Sex;
    
    @interface MJUser : NSObject
    /** 名称 */
    @property (copy, nonatomic) NSString *name;
    /** 头像 */
    @property (copy, nonatomic) NSString *icon;
    /** 年龄 */
    @property (assign, nonatomic) unsigned int age;
    /** 身高 */
    @property (strong, nonatomic) NSNumber *height;
    /** 财富 */
    @property (strong, nonatomic) NSDecimalNumber *money;
    /** 性别 */
    @property (assign, nonatomic) Sex sex;
    @end
    

    那么我们可以使用

        MJUser *user = [MJUser new];
        [user setValue:@"jack" forKey:@"name"];
        NSLog(@"userName : %@",[user valueForKey:@"name"]);//jack
    

    这是基本取值赋值
    1.我们可以发现value的值都是id,所以数据类型需要装箱成引用类型。
    2.key和keyPath的区别:keyPath方法是集成了key的所有功能。但是对对象中的对象的属性进行赋值,只有keyPath能够实现
    3.当key值是没有定义的,也就是对象没有这个属性时,如果还调用setValue:forKey:,那么setValue:forUndefinedKey这个方法会被调用。

    #import "MJUser.h"
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        MJUser *user = [MJUser new];
        [user setValue:@(100) forKey:@"scroe"];
    }
    #import "MJUser.h"
    @implementation MJUser
    
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key
    {
        if ([key isEqualToString:@"scroe"]) {
            NSLog(@"设置了scroe的值为 : %ld",[value integerValue]);//设置了scroe的值为 : 100
        }
    @end
    

    底层原理分析
    我们看下setValue:forKey的赋值原理
    1.去模型中查找有没有对应的setter方法:例如setIcon方法,有就直接调用这个setter方法给这个属性赋值[self setIcon:dic[@"icon"]];(PS:这里是不是能看出来为什么YY比MJ快了吧,YY是直接调用setter和getter方法,不用去寻找)
    2.如果找不到setter方法,接着会去找有没有icon属性,如果有,就直接访问模型中的icon属性,进行赋值,icon=dict[@"icon"];
    3.如果找不到icon属性,接着会去找_icon属性,如果有,直接进行赋值,_icon = dict[@"icon"];
    4.如果都找不到就会报错,[setValue:forUndefined:]将会被调用。
    内部实现

    [user setValue:@"jack" forKey:@"name"];
    

    就会被编译器处理成

    SEL sel = sel_get_uid ("setValue:forKey:");
    
    IMP method = objc_msg_lookup (user->isa,sel);
    
    method(user, sel, @"jack", @"name");
    

    我们可知,对象在调用setValue的时候,会直接获取这个方法子,再寻找方法实现的接口,再直接查找方法的实现.

    KVO

    KVO,即key-value-observing,利用一个key来找到某个属性并监听它的值的改变。
    这也是设计模式观察者模式的体现。
    使用非常简单
    在观察者实现监听方法observeValueForKeyPath: ofObject: change: context:

    #import "MJUser.h"
    
    
    @implementation MJUser
    
    - (instancetype)init
    {
        if (self = [super init]) {
            [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        }
        return self;
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        if ([keyPath isEqual:@"name"]) {
            NSLog(@"newValue: %@",change[NSKeyValueChangeNewKey]);
        }
    }
    
          MJUser *user = [[MJUser alloc]init];
            user.name = @"oldName";//newValue: oldName
            user.name = @"newName";//newValue: newName
    

    KVO的底层实现
    当一个类的某个属性被监听的时候,系统会通过runtime动态的去创建继承于该类的子类,并重写了这个子类属性的setter方法,同时,会将父类的isa指针指向子类,从而实现调用子类被重写的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象属性值的改变

    //被重写的setter方法
    - (void)setName:(NSString *)name
    {
        [self willChangeValueForKey:@"name"];
        [super setValue:name forKey:@"name"];
        [self didChangeValueForKey:@"name"];
    }
    

    那么我们还是不知道怎么通知到对象属性值的改变。
    其实就是在didChangeValueForKey:方法内部调用的。
    我们可以手写一个KVO验证一下

    @interface ViewController ()
    
    @property (nonatomic, assign) BOOL new;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self addObserver:self forKeyPath:@"new" options:NSKeyValueObservingOptionNew context:nil];
        NSLog(@"1");
        [self willChangeValueForKey:@"new"];
        NSLog(@"2");
        [self didChangeValueForKey:@"new"];
        NSLog(@"4");
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"3");
    }
    //
    2018-06-07 19:31:11.140346+0800 KVOTest[30805:1532911] 1
    2018-06-07 19:31:11.140498+0800 KVOTest[30805:1532911] 2
    2018-06-07 19:31:11.140620+0800 KVOTest[30805:1532911] 3
    2018-06-07 19:31:11.140714+0800 KVOTest[30805:1532911] 4
    

    假如我们注释掉didChangeValueForKey:
    结果会是怎样的

    @interface ViewController ()
    
    @property (nonatomic, assign) BOOL new;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self addObserver:self forKeyPath:@"new" options:NSKeyValueObservingOptionNew context:nil];
        NSLog(@"1");
        [self willChangeValueForKey:@"new"];
        NSLog(@"2");
    //    [self didChangeValueForKey:@"new"];
        NSLog(@"4");
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"3");
    }
    2018-06-07 19:32:11.295735+0800 KVOTest[30833:1536910] 1
    2018-06-07 19:32:11.295883+0800 KVOTest[30833:1536910] 2
    2018-06-07 19:32:11.295964+0800 KVOTest[30833:1536910] 4
    

    结果一看而知

    相关文章

      网友评论

          本文标题:从MJExtension引发的对KVC和KVO的见解

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