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

题外话:
最近在看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

结果一看而知

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,412评论 6 532
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,514评论 3 416
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,373评论 0 374
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,975评论 1 312
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,743评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,199评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,262评论 3 441
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,414评论 0 288
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,951评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,780评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,983评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,527评论 5 359
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,218评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,649评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,889评论 1 286
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,673评论 3 391
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,967评论 2 374

推荐阅读更多精彩内容

  • KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过K...
    暮年古稀ZC阅读 2,161评论 2 9
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,478评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,121评论 1 32
  • 该文章属于刘小壮原创,转载请注明:刘小壮[https://www.jianshu.com/u/2de707c93d...
    刘小壮阅读 21,051评论 29 115
  • KVC(Key-valuecoding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iOS...
    榕樹頭阅读 721评论 0 2