KVC的原理实现

一、什么是KVC

官方文档:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107i

KVC(Key-Value Coding)是Objective-C提供的一种利用字符串来间接访问对象属性的一种机制,它是通过访问器去访问对象属性的另一个可选方案。非正式协议NSKeyValueCoding对其接口进行了定义,NSObject中提供了接口的默认实现。

二、常见方法


//获取方法
- (nullable id)valueForKey:(NSString *)key;

- (nullable id)valueForKeyPath:(NSString *)keyPath;

- (nullable id)valueForUndefinedKey:(NSString *)key;

//可变集合获取方法
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);

- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;

- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath NS_AVAILABLE(10_7, 5_0);

- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

//设置方法
- (void)setValue:(nullable id)value forKey:(NSString *)key;

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

- (void)setNilValueForKey:(NSString *)key;

三、KVC原理

通过查找头文件NSKeyValueCoding.h注释可以发现KVC的执行过程

  1. valueForKey:(valueForKeyPath:类似)

    • 首先,在对象类中按顺序查找存取器名称为-get<key>, -<key>, -is<key>的方法,如果找到则直接调用,如果方法返回结果类型为指针类型,则直接返回。如果方法返回结果类型是数量类型支持NSNumber转换,则返回NSNumber,否则转化成NSValue并返回(任意类型的结果转换成NSValue,不仅NSPoint,NSRect和NSSize);
    • 否则(找不到存取器方法),查找-countOf<Key>, -indexIn<Key>OfObject:, -objectIn<Key>AtIndex:-<key>AtIndexes:,如果count方法,index方法和另外两个方法中至少一个方法找到,返回一个能够响应NSOrderedSet所有方法的代理集合对象(NSKeyValueOrderedSet)。每个NSOrderedSet消息发送给代理集合对象时,当消息发送给原始接收器的valueForKey:都会被转换成-countOf<Key>, -indexIn<Key>OfObject:, -objectIn<Key>AtIndex:-<key>AtIndexes:的组合来返回。如果实现了可选方法-get<Key>:range:,方法也将被调用来进行性能优化。
    • 否则(找不到存取器方法和NSOrderedSet访问方法),查找-countOf<Key>, -objectIn<Key>AtIndex:-<key>AtIndexes:,如果count方法和另外两个方法中至少一个方法找到,返回一个能够响应NSArray所有方法的代理集合对象。每个NSArray消息发送给代理集合对象时(NSKeyValueArray),当消息发送给原始接收器的valueForKey:都会被转换成-countOf<Key>, -objectIn<Key>AtIndex:-<key>AtIndexes:的组合来返回。如果实现了可选方法-get<Key>:range:,方法也将被调用来进行性能优化。
    • 否则(找不到存取器方法、NSOrderedSet访问方法和array访问方法),查找-countOf<Key>, -enumeratorOf<Key>-memberOf<Key>:,如果三个方法都找到,返回一个能够响应NSSet所有方法的代理集合对象(NSKeyValueSet)。每个NSSet消息发送给代理集合对象时,当消息发送给原始接收器的valueForKey:都会被转换成-countOf<Key>, -enumeratorOf<Key>-memberOf<key>:的组合来返回。
    • 否则(找不到存取器方法和集合访问方法),如果+accessInstanceVariablesDirectly属性返回YES,则按顺序查找符合名称_< key >,_is< Key >,< key >, 或者 is< Key >的实例变量。如果找到实例变量,则返回实例变量的值,转换NSNumber和NSValue同步骤1.
    • 否则(找不到存取器方法,集合访问方法和实例变量),调用-valueForUndefinedKey:返回结果。-valueForUndefinedKey:的默认实现是抛出一个NSUndefinedKeyException异常,但是你覆盖此方法。

    注意:

    对于集合(NSArray, NSSet, NSOrderSet)像使用普通对象一样,则返回代理对象,需要实现以下方法

NSArray NSSet NSOrderSet
-countOf<Key> -countOf<Key> -countOf<Key>
-enumeratorOf<Key> -indexIn<Key>OfObject:
One of -memberOf<Key>: One of
-objectIn<Key>AtIndex: -objectIn<Key>AtIndex:
-<key>AtIndexes: -<key>AtIndexes:
Optional (performance) Optional (performance)
-get<Key>:range: -get<Key>:range:
  1. setValue: forKey: (setValue: forKeyPath:类似)

    • 首先,查找类存取器方法-set<Key>:。如果找到此方法则检测参数类型。如果参数类型不是对象指针类型但值是nil,则调用-setNilValueForKey:方法,-setNilValueForKey:方法的默认实现是抛出一个NSInvalidArgumentException异常,但是你可以覆盖此方法。否则如果方法参数类型是对象指针类型,则直接调用此方法并传入value做为参数。如果方法参数类型是其他类型,NSNumber/NSValue的逆转换在方法-valueFor<Key>被调用的时候执行。
    • 否则(没有存取器方法),如果+accessInstanceVariablesDirectly属性返回YES,则按顺序查找符合名称_< key >,_is< Key >,< key >, 或者 is< Key >的实例变量。如果找到实例变量,并且它的类型是对象指针类型,则对旧值进行release操作,然后对value进行retain操作并赋值给实例变量。如果是其他类型,则同步骤1进行NSNumber/NSValue进行转换然后再赋值。
    • 否则(没有存取器方法和实例变量),调用setValue:forUndefinedkey:方法,setValue:forUndefinedkey:方法的默认实现是抛出一个NSUndefinedKeyException异常,但是你可以覆盖此方法。
  2. 可变集合-mutableArrayValueForKey:key-mutableOrderedSetValueForKey:key-mutableSetValueForKey:key

    默认实现存取器方法时和-valueForKey:key一样。返回代理对象时需要实现的方法有差别。如下:

NSMutableArray / NSMutableOrderedSet NSMutableSet
At least 1 insertion and 1 removal method * At least 1 addition and 1 removal method
-insertObject:in<Key>AtIndex: -add<Key>Object:
-removeObjectFrom<Key>AtIndex: -remove<Key>Object:
-insert<Key>:atIndexes: -add<Key>:
-remove<Key>AtIndexes: -remove<Key>:
Optional (performance) one of * Optional (performance)
-replaceObjectIn<Key>AtIndex:withObject: -intersect<Key>:
-replace<Key>AtIndexes:with<Key>: -set<Key>:

四、实例

1. 创建YKNoteKVCObject类

//
//  YKNoteKVCObject.h
//  YKNote
//
//  Created by wanyakun on 2016/11/10.
//  Copyright © 2016年 com.ucaiyuan. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface YKNoteKVCObject : NSObject {
    NSInteger _intVar;
    NSString *_strVar;
}

@property (nonatomic, assign) NSInteger intProperty;
@property (nonatomic, copy) NSString *strProperty;

@end

//
//  YKNoteKVCObject.m
//  YKNote
//
//  Created by wanyakun on 2016/11/10.
//  Copyright © 2016年 com.ucaiyuan. All rights reserved.
//

#import "YKNoteKVCObject.h"


@implementation YKNoteKVCObject

@synthesize intProperty = _intProperty;
@synthesize strProperty = _strProperty;

#pragma mark - method for orderSet
- (NSUInteger)countOfOrderSet {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return 5;
}

- (NSInteger)indexInOrderSetOfObject:(id)element {
    return 2;
}

- (id)objectInOrderSetAtIndex:(NSUInteger)index {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return [NSString stringWithFormat:@"orderSet_%ld", index];
}

#pragma mark - method for array
- (NSUInteger)countOfArray {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return 10;
}

- (id)objectInArrayAtIndex:(NSUInteger)index {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return [NSString stringWithFormat:@"array_%ld", index];
}

#pragma mark - mehtod for set
- (NSUInteger)countOfSet {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return 15;
}

- (NSEnumerator *)enumeratorOfSet {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    NSEnumerator *enumerator = [[NSEnumerator alloc] init];
    return enumerator;
}

- (NSString *)memberOfSet:(NSString *)object {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return [NSString stringWithFormat:@"member of set: %@", object];
}

#pragma mark - method for MutableOrderedSet
- (void)insertObject:(NSString *)object inMOrderSetAtIndex:(NSUInteger)index {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)removeObjectFromMOrderSetAtIndex:(NSUInteger)index {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

#pragma mark - method for MutableArray
- (void)insertObject:(NSString *)object inMArrayAtIndex:(NSUInteger)index {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)removeObjectFromMArrayAtIndex:(NSUInteger)index {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

#pragma mark - method for mutableSet
- (void)addMSetObject:(NSString *)object {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)removeMSetObject:(NSString *)object {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

#pragma mark - private method
- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(@"%s\nValueForUndefinedKey:%@", __PRETTY_FUNCTION__, key);
    return @"undefinedKeyValue";
}

- (void)setNilValueForKey:(NSString *)key {
    NSLog(@"%s\nNilValueKey:%@", __PRETTY_FUNCTION__, key);
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"%s\nundefineKey:%@", __PRETTY_FUNCTION__, key);
}

+ (BOOL)accessInstanceVariablesDirectly {
    return YES;
}


#pragma mark getter setter
- (NSInteger)intProperty {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return _intProperty;
}

- (void)setIntProperty:(NSInteger)intProperty {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    _intProperty = intProperty;
}

- (NSString *)strProperty {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    return _strProperty;
}

- (void)setStrProperty:(NSString *)strProperty {
    NSLog(@"%s", __PRETTY_FUNCTION__);
    _strProperty = [strProperty copy];
}

@end

2. 创建Controller,用来调用setValue:ForKey:和valueForKey:等

//
//  YKNoteKVCViewController.m
//  YKNote
//
//  Created by wanyakun on 2016/11/11.
//  Copyright © 2016年 com.ucaiyuan. All rights reserved.
//

#import "YKNoteKVCViewController.h"
#import "YKNoteKVCObject.h"
#import <objc/runtime.h>

@interface YKNoteKVCViewController ()

@property (nonatomic, strong) YKNoteKVCObject *yKNoteKVCObject;

@end

@implementation YKNoteKVCViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.title = @"KVC";
    self.view.backgroundColor = [UIColor whiteColor];
    //通过存取器访问
    [self.yKNoteKVCObject setValue:[NSNumber numberWithInteger:10] forKey:@"intProperty"];
    NSInteger intProperty = [[self.yKNoteKVCObject valueForKey:@"intProperty"] integerValue];
    NSLog(@"intProperty = %ld", intProperty);
    
    [self.yKNoteKVCObject setValue:@"I am strProperty" forKey:@"strProperty"];
    NSString *strProperty = [self.yKNoteKVCObject valueForKey:@"strProperty"];
    NSLog(@"strProperty = %@", strProperty);
    
    //通过实例变量访问
    [self.yKNoteKVCObject setValue:[NSNumber numberWithInteger:20] forKey:@"intVar"];
    NSInteger intVar = [[self.yKNoteKVCObject valueForKey:@"intVar"] integerValue];
    NSLog(@"intVar = %ld", intVar);

    [self.yKNoteKVCObject setValue:@"I am strVar" forKey:@"strVar"];
    NSString *strVar = [self.yKNoteKVCObject valueForKey:@"strVar"];
    NSLog(@"strVar = %@", strVar);

    //set undefineKey
    [self.yKNoteKVCObject setValue:@"undefine value" forKey:@"undefinedKey"];

    //为非Object pointer参数类型设置nil
    [self.yKNoteKVCObject setValue:nil forKey:@"intProperty"];

    //NSOrderSet, NSArray, NSSet代理对象
    id orderSet = [self.yKNoteKVCObject valueForKey:@"orderSet"];
    id array = [self.yKNoteKVCObject valueForKey:@"array"];
    id set = [self.yKNoteKVCObject valueForKey:@"set"];
    NSLog(@"\norderSet class:%@\narray class:%@\nset class:%@", object_getClass(orderSet), object_getClass(array), object_getClass(set));
    
    //NSMutableOrderSet, NSMutableArray, NSMutableSet代理对象
    id mutableOrderSet = [self.yKNoteKVCObject mutableOrderedSetValueForKey:@"mOrderSet"];
    id mutableArray = [self.yKNoteKVCObject mutableArrayValueForKey:@"mArray"];
    id mutableSet = [self.yKNoteKVCObject mutableSetValueForKey:@"mSet"];
    NSLog(@"\nmutableOrderSet class:%@\nmutableArray class:%@\nmutableSet class:%@", object_getClass(mutableOrderSet), object_getClass(mutableArray), object_getClass(mutableSet));
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

#pragma mark - getter
- (YKNoteKVCObject *)yKNoteKVCObject {
    if (_yKNoteKVCObject == nil) {
        _yKNoteKVCObject = [[YKNoteKVCObject alloc] init];
    }
    return _yKNoteKVCObject;
}

@end

3. 输出结果

-[YKNoteKVCObject setIntProperty:]
-[YKNoteKVCObject intProperty]
intProperty = 10
-[YKNoteKVCObject setStrProperty:]
-[YKNoteKVCObject strProperty]
strProperty = I am strProperty
intVar = 20
strVar = I am strVar
-[YKNoteKVCObject setValue:forUndefinedKey:]
undefineKey:undefinedKey
-[YKNoteKVCObject setNilValueForKey:]
NilValueKey:intProperty
orderSet class:NSKeyValueOrderedSet
array class:NSKeyValueArray
set class:NSKeyValueSet
mutableOrderSet class:NSKeyValueFastMutableOrderedSet2
mutableArray class:NSKeyValueFastMutableArray2
mutableSet class:NSKeyValueFastMutableSet2
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容