KVC详解

kvc是什么?

kvc(Key-value-coding)键值编码,一个非正式的协议.提供一种机制间接访问对象的属性.而不是通过调用setter,getter方法获取.就是说在ios开发中,可以允许开发者通过key名直接访问对象的属性,或者给对象的属性赋值.而不需要调用明确存取方法.

kvc有什么作用?

kvc的作用就是可以给对象的私有变量进行赋值.即在运行时动态的访问或修改对象的属性.

kvc的赋值和取值

kvc中定义都是对NSObject的扩展类实现的,oc中的kvc最基本的几个方法:

  • (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
  • (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
  • (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来 取值
  • (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过 KeyPath来设值

NSKeyValueCoding类别中其他的一些方法

  • (BOOL)accessInstanceVariablesDirectly;
    //默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
  • (BOOL)validateValue:(inout id __nullable * _nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
    //KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因.
  • (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    //这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回.
  • (nullable id)valueForUndefinedKey:(NSString *)key;
    //如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
  • (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    //和上一个方法一样,但这个方法是设值.
  • (void)setNilValueForKey:(NSString *)key;
    //如果你在SetValue方法时面给Value传nil,则会调用这个方法
  • (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    //输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典.

kvc设值

设值是通过setValue:forKey:的默认实现,给定输入参数value和key。试图在接收调用对象的内部,设置属性名为key的value,通过下面的步骤
1.查找set<Key>、_ set<Key>命名的setter,按照这个顺序,如果找到的话,调用这个方法并将值传进去(根据需要进行对象转换).
2.如果没有发现一个简单的setter,但是accessInstanceVariablesDirectly类属性返回YES,则查找一个命名规则为__<key>、_is<Key>、<key>、is<Key>的实例变量.根据这个顺序,如果发现则将value赋值给实例变量.
3.如果没有发现setter或实例变量,则调用setValue:forUndefinedKey:方法,并默认提出一个异常,但是一个NSObject的子类可以提出合适的行为
eg:

   @interface Person : NSObject{
      NSString *_name;
   }  
  @end

//创建对象
Person *p = [[Person alloc] init];
//通过kvc赋值
[p setValue:@"小明" forKey:@"name"];
//通过kvc取值
NSLog(@"p的名字是%@",[p valueForKey:@"name"]);

//打印结果
//p的名字是小明

在以上代码中添加,accessInstanceVariablesDirectly为NO,结果如下.

 @implementation Person
 +(BOOL)accessInstanceVariablesDirectly{
     return NO;
 }
 -(id)valueForUndefinedKey:(NSString *)key{
   NSLog(@"出现异常,此key不存在%@",key);
   return nil;
 }
 -setValue:(id)value forUndefinedKey:(NSString *)key{
     NSLog(@"设置值时出现异常,该key不存在%@",key);
 }
 @end
  //打印结果
  设置值时出现异常,该key不存在name
  出现异常,此key不存在name
  p的名字是(null)

如果accessInstanceVariablesDirectly为YES,打印结果和首次显示一样.这里不在重复结果.在修改成员变量名为_name,_isName,name,isName时都会打印出正确的结果.所以简单来说就是*如果没有找到set<key>,会按照_key,_isKey,key,isKey的顺序搜索成员并进行赋值操作.

kvc取值

取值是同valueForKey:来实现的,给定一个key当做输入参数,下面的步骤,在这个接收valueForKey:方法调用的类内部进行操作.
1.通过getter方法搜索实例,例如get<Key>, <key>, is<Key>, _ <key>的拼接方案。按照这个顺序,如果发现符合的方法,就调用对应的方法并拿着结果
2.如果没有找到上面getter方法,kvc会查找countOf<Key>
objectIn<Key>AtIndex、<key>AtIndexes格式的方法.如果这个三个方法倍找到,那么返回一个可以响应NSArray所有方法的代理集合(NSKeyValueArray,是NSArray的子类),给这个代理集合发NSSet的消息,就会以这个几个方法组合的形式调用
3.如果上面的方法名没有找到,那么会同时查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法,如果这三个方式都找到,那么返回一个可以响应NSSet所有的方法的代理集合,给这个代理集合发NSSet的消息,就会以这个几个方法组合的形式调用
4.如果还没有找到,再检查类方法 accessInstanceVariablesDirectly是否为YES,如果是YES,会按_ <key>、_is<key>、<key>、is<key>的顺序搜索成员变量名,如果发现将value赋值给实例变量.如果accessInstanceVariablesDirectly为NO,那么会直接调用forUndefinedKey方法,抛出异常
eg:

    @implementation Person
     -(NSInteger)getAge{
      return 10;
    }
   @end

//创建对象
Person *p = [[Person alloc] init];
//通过kvc取值
NSLog(@"p的年龄是%@",[p valueForKey:@"age"]);

//打印结果
//p的年龄是10

从上面的代码可以看到,找到getAge方法,就获取到age的值.
分别把getAge方法名改为age、_age、isAge都可以获取到正确的值.上面代码充分说明了kvc在调用valueforkey是搜索key的机制

kvc中keyPath的使用

在开发过程中,一个类的成员变量有可能是自定义类或者其他的复杂数据类型,那么可以使用kvc获取该属性.然后再用kvc来获取这个自定义类的属性.但是这个比较繁琐,所以,kvc提供一个解决方案,那就是键路径keypath

  • (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来
    取值
  • (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过
    KeyPath来设值

eg:

@interface Person : NSObject{ 
  NSString *_name;
}
@end

@interface SubPerson : NSObject{
    Person *_person;
}
@end

//创建对象Person
Person *p = [[Person alloc] init];
//创建对象SubPerson
SubPerson *subp = [[SubPerson alloc] init];
//通过kvc设置person 值
[subp setValue:p forKey:@"person"];

//通过kvc设值person的给name设值
[subp setValue:@"小花" forKeyPath:@"person.name"];
//通过kvc取值
NSLog(@"SubPerson的person名字是%@",[p valueForKey:@"name"]);

//打印结果
//SubPerson的person名字是小花

从上面例子可以看出我们成功的使用了keypath设置person的name属性值.

kvc异常处理

kvc中最常见的异常就是不小心使用了错误的key,或者设置值不小心传递了nil值.kvc中有专门的方法来处理这些异常

kvc处理nil异常

kvc中通常情况下是不能传递nil的,如果不小心传递了nil,kvc会调用setNilValueForkey方法,这个方法默认是抛出异常.
eg:

@interface Person : NSObject{
    NSInteger age;
}
@end

//创建对象Person
Person *p = [[Person alloc] init];
//通过kvc设置age 值
[p setValue:nil forKey:@"age"];

//通过kvc取值
NSLog(@"person的名字是%@",[p valueForKey:@"age"]);

//打印结果
//    不能将age设置为nil
//    person的名字是0

kvc处理Undefinekey异常

通常kvc不允许调用一个不存在的key赋值,不然会报错forUndefinekey发生崩溃,所以重写forUndefinekey就可以避免崩溃
eg:

@interface Person : NSObject{

}
@end

//创建对象Person
Person *p = [[Person alloc] init];
//通过kvc设置person 值
[p setValue:nil forKey:@"age"];

//通过kvc取值
NSLog(@"person的名字是%@",[p valueForKey:@"age"]);

//打印结果
//    设置值时出现异常,该key不存在age
//    出现异常,此key不存在age
//    person的名字是(null)

KVC使用

KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,甚至实现很多难以想像的功能,KVC也是许多iOS开发黑魔法的基础。
下面列举iOS开发中KVC的使用场景.
1.动态地取值和设值
利用KVC动态的取值和设值是最基本的用途了.
2.用KVC来访问和修改私有变量
对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的.
3.Model和字典转换
使用kvc和objc的runtime组合可以实现mode和字典的转换(YYModel)
4.访问和修改这些控件的样式
而KVC在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField中的placeHolderText了.

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

推荐阅读更多精彩内容

  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    萨缪阅读 809评论 0 5
  • 源码加翻译 #import <Foundation/NSArray.h> #import <Foundation/...
    CAICAI0阅读 1,146评论 0 50
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    萨缪阅读 4,627评论 1 13
  • 关于KVC的详细介绍可以参考官方文档 键值编码(KVC)是由NSKeyValueCoding非正式协议启用的一种机...
    你duck不必呀阅读 628评论 0 1
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    祀梦_阅读 915评论 0 7