美文网首页
iOS进阶之详解KVC流程

iOS进阶之详解KVC流程

作者: 枫叶无处漂泊 | 来源:发表于2019-04-10 19:47 被阅读0次

简介

KVC(Key-value coding)键值编码。就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。

KVC在iOS的定义

  • KVC的定义是对NSObject的扩展来实现的(Objective-C中有个显式的NSKeyValueCoding类别名)。
  • 几乎所有的Objective-C对象都能使用KVC

下面介绍一下KVC四个最为重要的方法

//通过key来取值  
- (nullable id)valueForKey:(NSString *)key; 

//通过key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;  

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

当然 NSKeyValueCoding类别中还有其他的一些方法,下面列举一些

//默认返回YES,表示如果没有找到Set方法的话,
会按照_key,_iskey,key,iskey的顺序搜索成员,
设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;

//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、
为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue 
forKey:(NSString *)inKey error:(out NSError **)outError;    

//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段
或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;   

//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
  

//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;

//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

KVC底层执行流程

当调用setValue:@"Jack" forKey:@"name"的代码时,底层的执行机制如下:

  1. 程序优先遍历类对象的方法列表去找调用setName:方法,找到setter方法
  2. 找到setter方法之后才会遍历类对象的成员列表找到_name赋值。
  3. 如果没有找到setName:方法,那就继续找_setName方法,
  4. 如果还没有没有的KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES. 返回NO就是不允许遍历类的成员变量
  5. 如果你重写了该方法让其返回NO的话,,那么这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。
  6. KVC机制会搜索该类成员变量列表里面有没有名为的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。
  7. 类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量。
  8. 该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。
  9. 上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。

下面我们用代码验证一下:

//Person.h
@interface Person : NSObject  {
//不声明@public,Person无法用->来访问成员变量
@public
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}
//系统会自动生成setter getter方法
@property (nonatomic, copy) NSString *name;

@end
//person.m文件
@implementation Person

//当找不到set方法,会调用用这个方法
+ (BOOL)accessInstanceVariablesDirectly {

    NSLog(@"accessInstanceVariablesDirectly");
    return NO;
}
@end

然后在调用该对象并且setValue: forKey:赋值

Person *p = [[Person alloc] init];
[p setValue:@"张三" forKey:@"name"];

NSLog(@"getName: %@", p.name);
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_name: %@", p->_name);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

getName: 张三
valueforkey: 张三
_name: 张三
_isName1: (null)
name: (null)
isName: (null)

说明setValue: forKey:会对该对象的类对象的方法列表进行遍历,如果找到setName方法成功之后,才会遍历类对象的成员变量找到_name进行赋值。

当注释掉@property (nonatomic, copy) NSString *name;,不让Person自动生成对象setter getter方法,看看怎么执行的

//Person.h
@interface Person : NSObject  {
//不声明@public,Person无法用->来访问成员变量
@public
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;
}
//系统会自动生成setter getter方法
//@property (nonatomic, copy) NSString *name;

@end
//person.m文件
@implementation Person

//当找不到set方法,会调用用这个方法
+ (BOOL)accessInstanceVariablesDirectly {

    NSLog(@"accessInstanceVariablesDirectly");
    return NO;
}

-(id)valueForUndefinedKey:(NSString *)key{
    
    NSLog(@"获取值出现异常,该key不存在%@",key);
    return nil;
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    
    NSLog(@"赋值出现异常,该key不存在%@",key);
}
@end

然后在调用该对象并且setValue: forKey:赋值

Person *p = [[Person alloc] init];
[p setValue:@"张三" forKey:@"name"];

//NSLog(@"getName: %@", p.name);//没有getter方法了
NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_name: %@", p->_name);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

// 遍历set、get方法没找到,执行两次
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
赋值出现异常,该key不存在name
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
获取值出现异常,该key不存在name
valueforkey: (null)
_name: (null)
_isName: (null)
name: (null)
isName: (null)

说明了accessInstanceVariablesDirectly返回NO的话,不在遍历类对象成员变量赋值。就会调用setValue: forUndefinedKey。没有setter方法,就没再类对象成员列表去找_name.

那我们把accessInstanceVariablesDirectly返回的事YES,执行结果:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
_name: 张三
_isName: (null)
name: (null)
isName: (null)

说明先找遍历_name变量,找到之后就退出遍历。不在遍历其他变量。
那就把对象的_name去掉

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"_isName: %@", p->_isName);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
_isName: 张三
name: (null)
isName: (null)

说明在类的成员列表里遍历没见到_name变量,在遍历会找_isName,找到之后就退出遍历。不在遍历其他变量。那我们把_isName也去掉。打印一下:

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"name: %@", p->name);
NSLog(@"isName: %@", p->isName);

执行结果:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
name: 张三
isName: (null)

说明在类的成员列表里遍历没见到_name、_isName变量,在遍历会找name变量,找到之后就退出遍历。不在遍历其他变量。那我们把name也去掉。打印一下:

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);
NSLog(@"isName: %@", p->isName);

执行结果打印一下:

accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
valueforkey: 张三
isName: 张三

说明在类的成员列表里遍历没见到_name、_isName、name变量,在遍历找到isName变量,找到之后就退出遍历,不在遍历其他变量。把变量都注释掉。打印一下:

NSLog(@"valueforkey: %@",[p valueForKey:@"name"]);

// 遍历set、get方法没找到,执行两次
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
赋值出现异常,该key不存在name
accessInstanceVariablesDirectly
accessInstanceVariablesDirectly
获取值出现异常,该key不存在name
valueforkey: (null)

当执行setvalue:forkey没有set方法、并且依次遍历_name、_isName、name、isName没有的时候就会执行setValue:(id)value forUndefinedKey并默认是报错的。重写方法做处理不会报错。

KVC的使用

  • 动态地取值和设值

    • 利用KVC动态的取值和设值是最基本的用途了。相信每一个iOS开发者都能熟练掌握,
  • 用KVC来访问和修改私有变量

    • 对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的。
  • Model和字典转换

    • 充分地运用了KVC和Objc的runtime组合的技巧,只用了短短数行代码就是完成了很多功能
  • KVO是基于KVC实现的

总结

通过分析KVC整个执行过程,了解NSKeyValueCoding这个分类的方法,是自己对KVC使用加深印象。

相关文章

  • iOS进阶之详解KVC流程

    简介 KVC(Key-value coding)键值编码。就是指iOS的开发中,可以允许开发者通过Key名直接访问...

  • iOS开发技巧系列---详解KVC(我告诉你KVC的一切)

    iOS开发技巧系列---详解KVC(我告诉你KVC的一切) iOS开发技巧系列---详解KVC(我告诉你KVC的一切)

  • iOS 关于KVC的一些总结(转)

    原文:iOS 关于KVC的一些总结 本文参考: KVC官方文档 KVC原理剖析 iOS KVC详解 KVC 简介 ...

  • iOS 关于KVC的一些总结

    本文参考: KVC官方文档 KVC原理剖析 iOS KVC详解 KVC 简介 KVC全称是Key Value Co...

  • iOS日记15-KVC

    1.iOS开发技巧系列---详解KVC 2.漫谈 KVC 与 KVO 3.KVC/KVO原理详解及编程指南 关键点...

  • iOS Objective-C KVC 详解

    iOS Objective-C KVC 详解 1. KVC简介 KVC 全称Key Value Coding,是苹...

  • OC--KVC

    参考:iOS开发技巧系列---详解KVC(我告诉你KVC的一切)KVC原理剖析 NSObject(NSKeyVal...

  • KVC

    iOS 如何使用KVC iOS开发UI篇—Kvc简单介绍 iOS开发系列--Objective-C之KVC、KVO

  • IOS进阶之WKWebView

    IOS进阶之WKWebView IOS进阶之WKWebView

  • iOS - KVO

    [toc] 参考 KVO KVC 【 iOS--KVO的实现原理与具体应用 】 【 IOS-详解KVO底层实现 】...

网友评论

      本文标题:iOS进阶之详解KVC流程

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