Objective-C 之 KVC 原理

苹果官网地址
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.

一、KVC简介

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。常见的API有:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath可以设置属性的属性
- (void)setValue:(id)value forKey:(NSString *)key;//通过key设置自己的属性
- (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath访问属性的属性
- (id)valueForKey:(NSString *)key; //通过key访问自己的属性

二、赋值:-setValue:forKey:

2.1规则:

  1. 先查找setter方法,set<Key>_set<Key>
  2. 未找到,且+(BOOL)accessInstanceVariablesDirectly返回YES(默认为YES),则查找实例变量;
  3. 查找 _<key>, _is<Key>, <key>, or is<Key>,查到即执行
  4. 抛出异常,也可以执行-setValue:forUndefinedKey:处理异常

2.2不同数据类型的使用

新建一个工程,添加一个类Animal.h

typedef struct {
    float a;
    float b;
    float c;
}ThreeFloats;


@interface Animal : NSObject


@property (nonatomic,copy) NSString *nickname;

@property (nonatomic,assign) int age;

@property (nonatomic,strong) NSArray *friends;

@property (nonatomic,assign) ThreeFloats floats;


@end

在viewcontroller里面

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    Animal *animal = [Animal alloc];
    
    //1、基本对象类型
    [animal setValue:@"dog" forKey:@"nickname"];
    NSLog(@"nickname:%@", animal.nickname);

    //2、集合类型
    [animal setValue:@[@"Jack", @"Rose"] forKey:@"friends"];
    NSLog(@"friends:%@", animal.friends);
    NSMutableArray *tmpFriends = [animal mutableArrayValueForKey:@"friends"];
    tmpFriends[1] = @"Lili";
    NSLog(@"friends:%@", animal.friends);
    
    //3、 NSNumber支持的基础类型
    [animal setValue:@3 forKey:@"age"];
    NSLog(@"age:%d", animal.age);
    
    //4、 其他类型,除上述类型外,其他类型要包装为NSValue进行赋值。
    ThreeFloats threeFloats = {1.1, 2.2, 3.3};
    NSValue *value = [NSValue valueWithBytes:&threeFloats
                                    objCType:@encode(ThreeFloats)];
    [animal setValue:value forKey:@"floats"];
    
    NSValue *floatsValue = [animal valueForKey:@"floats"];
    ThreeFloats floats;
    [floatsValue getValue:&floats];
    NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);
    
    
}

三、取值:valueForKey:

2.1规则:

看下图,也可以到苹果官网查看,文章开头有链接


1111.png
  1. 先查找该类中是否有直接的getter,如果有则调用getter,执行第5步;
  2. 没有直接的getter,查找类中是否有上述NSArray块中的方法,如果实现第一个后面两个中的一个或两个方法,则返回一个集合对象,这个集合对象可以响应所有NSArray的方法
  3. 查找实例是否实现了上述NSSet块中的3个方法,如果有,则返回一个集合,这个集合可以响应NSSet的所有方法。
  4. 检查类方法accessInstanceVariablesDirectly如果返回YES则查找对象的实例变量是否匹配下列各式:_<key>->_is<Key>-><key>->is<Key>,如果匹配,执行第5步,否则执行第6步
  5. 如果属性类型是对象则直接返回;如果属性类型是被NSNumber支持的类型,则返回一个NSNumber对象;否则返回一个NSValue对象。
  6. 抛出异常,也可以执行- (nullable id)valueForUndefinedKey:(NSString *)key处理异常

2.2不同数据类型的使用

在上面代码的基础上修改,在Animal.h

- (NSInteger)countOfNames {
    return _friends.count;
}

- (id)objectInNamesAtIndex:(NSUInteger)index {
    return _friends[index];
}


-(NSArray *)namesAtIndexes:(NSIndexSet *)indexes{
    return [_friends objectsAtIndexes:indexes];
}

- (nullable id)valueForUndefinedKey:(NSString *)key{
    
    return @"UndefinedKey";
}

在viewcontroller里面

Animal *animal = [[Animal alloc] init];
    
    //1、基本对象
    animal.nickname = @"Jack";
    NSString *nickname = [animal valueForKey:@"nickname"];
    NSLog(@"nickname: %@", nickname);
    
    
    //2、数组类型的使用,通过-valueForKey:取值时
    //   需要实现-countOf<Key>和-objectIn<Key>AtIndex:两个方法,我们以names为key,来获取friends属性:
    animal.friends = @[@"Jack", @"Rose"];
    NSArray *names = [animal valueForKey:@"names"];
    NSLog(@"names: %@", names);
    
    
    //3、NSNumber支持的基本类型
    animal.age = 3;
    NSNumber *number = [animal valueForKey:@"age"];
    NSLog(@"age: %d", [number intValue]);
    
    
    //4、NSValue类型
    ThreeFloats threeFloats = {1.1, 2.2, 3.3};
    animal.floats = threeFloats;
    NSValue *floatsValue = [animal valueForKey:@"floats"];
    ThreeFloats floats;
    [floatsValue getValue:&floats];
    NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);

四、KVC模式匹配的顺序及验证

4.1 setter和getter方法

添加代码

@interface Animal () {
    NSString *_nickname;
}

@end

@implementation Animal

#pragma mark - setter
// set<Key>
//- (void)setNickname:(NSString *)nickname {
//    printf("%s\n", __func__);
//    _nickname = nickname;
//}

// _set<Key>
- (void)_setNickname:(NSString *)nickname {
    printf("%s\n", __func__);
    _nickname = nickname;
}

// setIs<Key>
- (void)setIsNickname:(NSString *)nickname {
    printf("%s\n", __func__);
    _nickname = nickname;
}

#pragma mark - getter
//// get<Key>
//- (NSString *)getNickname {
//    printf("%s\n", __func__);
//    return _nickname;
//}

// <key>
- (NSString *)nickname {
    printf("%s\n", __func__);
    return _nickname;
}

// _<key>
- (NSString *)_nickname {
    printf("%s\n", __func__);
    return _nickname;
}

// is<Key>
- (NSString *)isNickname {
    printf("%s\n", __func__);
    return _nickname;
}
@end

viewController相关代码

    Animal *animal = [[Animal alloc] init];
    [animal setValue:@"Jack" forKey:@"nickname"];
    NSLog(@"\nnickname: %@", [animal valueForKey:@"nickname"]);

这里不在一一测试,可自行注释方法来验证

4.2 实例变量

@interface Animal : NSObject{
    @public
    //NSString *_nickname;
    NSString *nickname;
    NSString *_isNickname;
    NSString *isNickname;
}

@end

@implementation Animal

@end

    Animal *animal = [[Animal alloc] init];
    [animal setValue:@"Jack" forKey:@"nickname"];
    //NSLog(@"_nickname: %@", animal->_nickname);
    NSLog(@"nickname: %@", animal->nickname);
    NSLog(@"_isNickname: %@", animal->_isNickname);
    NSLog(@"isNickname: %@", animal->isNickname);

打印结果:

2020-04-10 11:23:20.867793+0800 kvc[31577:17258752] nickname: (null)
2020-04-10 11:23:20.868476+0800 kvc[31577:17258752] _isNickname: Jack
2020-04-10 11:23:20.868991+0800 kvc[31577:17258752] isNickname: (null)

4.3 + (BOOL)accessInstanceVariablesDirectly方法

添加代码

@implementation Animal

+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}

@end

再次运行,抛出异常:'NSUnknownKeyException', reason: '[<Animal 0x6000015fb5c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key nickname.'

五、kvc特殊异常用法

5.1 自动类型转换

当赋值对象是intbool等基本类型时,赋值NSString,取值会自动转换为对象类型。如下,当age赋值为NSString,取值时对应类型时__NSCFNumber

//@property (nonatomic, assign) int  age;
[person setValue:@"20" forKey:@"age"]; // int - string
 NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);

5.2 设置空值

赋值nil,当方法参数类型为NSNumber或者NSValue时,可以重写setNilValueForKey方法重定向。

5.3

设值或者取值找不到key,也可以重写对应的方法setValue: forUndefinedKey:valueForUndefinedKey重定向,这个上面有说过。

5.4 键值验证

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError{
    if([inKey isEqualToString:@"name"]){
        [self setValue:[NSString stringWithFormat:@"里面修改一下: %@",*ioValue] forKey:inKey];
        return YES;
    }
    *outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的属性",inKey,self] code:10088 userInfo:nil];
    return NO;
}

我们可以用这个方法,进行容错派发消息转发等操作。

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

推荐阅读更多精彩内容

  • KVC(Key-value coding)键值编码,iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,...
    CALayer_Sai阅读 2,526评论 0 4
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    祀梦_阅读 937评论 0 7
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    萨缪阅读 814评论 0 5
  • 原文:iOS 关于KVC的一些总结 本文参考: KVC官方文档 KVC原理剖析 iOS KVC详解 KVC 简介 ...
    liyoucheng2014阅读 942评论 0 3
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    萨缪阅读 4,645评论 1 13