KVC定义

KVC的全称是Key-Value Coding,中文就是我们所熟知的键值编码,键值编码是NSKeyValueCoding非正式协议启用的一种机制。对象采用该协议来间接访问该对象属性(既:可以通过一个key值来访问)。这种间接访问机制补充了实例变量及其相关的访问器方法所提供的直接访问.

KVC常用API

常用方法

  • 1.通过key设值/取值
  • 2.通过keyPath来设置/取值

其它相关方法

KVC设值及底层原理

在日常开中,我们经常用的赋值就是setter方法或者用KVC进行赋值,如下图所示:

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发公众号:编程大鑫,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!

下面我们就看看最常使用的setValue:forKey:底层是如何进行赋值的。

1.首先我们去查找这个方法实现

我们发现这个方法在NSObject的分类上,接着我们看到实现在Foundation框架下,所以这个方法的实现并未开源。

这个时候我们应该怎么办?因为研究的内容没有开源,有没有别的办法进行研究。之前的文章用到了很多方法,看源码只是其中的一种。我们可以通过下面的方法:

  • 通过clang转成.cpp看下在C++下的编译情况
  • 通过LLVM查找相关方法
  • 通过Hopper进行反汇编,看看相关代码
  • 通过GNU代码查看。下载链接:gnu这个是辅助理解,它的代码最贴近官方源码)。
  • 通过苹果官方文档
  • 通过GitHub下载相关代码,进行分析

我们在这里通过官方文档:About Key-Value Coding以及GNU代码辅助去研究设值流程

2.研究setValue:forKey:赋值的底层实现

调用setValue:forKey:方法的时候,底层调用又分为一下几个步骤

  • 1.首先查找是否有setKey,_setKey,_setIsKey(这三个set方法是一次查找,有一个实现就进行赋值,不再向下进行查找)。其中key是指成员变量。如果都没有则进行第2步。
  • 2.如果第1步没找到写的3种set方法,则去查找accessInstanceVariablesDirectly是否返回YES(默认返回YES)。如果返回的是YES,表示如果没有找到SetKey方法的话,会查找直接访问的实例变量进行赋值,会按照_key,_iskey,key,iskey的顺序搜索成员(Key指成员变量),设置成NO就不这样搜索。如果返回的是NO或者没有找到上面的方法,则进行第3步。
  • 3.来到这一步说明setter方法和实例变量都没有找到,系统就会执行该对象的setValue:forUndefinedKey: 方法,如果该方法没有实现,则会抛出NSUndefinedKeyException类型异常(方法setValue:forUndefinedKey:是需要我们自己实现)。
综上所述,KVC通过setValue:forKey:方法设置值的流程如下图所示(已设置Person类的name为例):

KVC取值的底层原理

同样赋值原理的探索一样,我们同样可以通过官方文档和GNU代码去研究。 下面我们研究一下执行valueForKey:时的底层变化。

  • 1.首先查找getter方法,按照getKey,Key,isKey,_Key的方法顺序去查找。如果找到就执行第5步,如果找不到则执行第2步。

  • 2.来到这里说明没有查找到getter方法,KVC就会查找countOfKey、objectInKey AtIndex: 以及KeyAtIndexes: 。

    • 如果找到countOfKey和另外两个中的任意一个方法,则会创建一个响应所有NSArray的方法集合代理对象,并返回该对象,即NSKeyValueArray,是NSArray的子类
    • 代理对象随后将接收到的所有NSArray消息转换为countOfKey,objectInKey AtIndex: 和KeyAtIndexes:消息的某种组合,用来创建键值编码对象
    • 如果原始对象还实现了一个名为getKey:range:之类的可选方法,则代理对象也将在适当时使用该方法(注意:方法名的命名规则要符合KVC的标准命名方法,包括方法签名。)
    • 如果没有找到,就进入第3步。
  • 3.来到第3步,说明上面的方法都没有找到

    • 此时就会同时查找countOfKey、enumeratorOfKey和memberOfKey这三个方法
    • 如果这三个方法都找到,则会创建一个响应所有NSSet方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet消息转换为countOfKey,enumeratorOfKey和memberOfKey:消息的某种组合,用于创建它的对象。
    • 如果没有找到则进入第4步。
  • 4.进入这一步说明上面的都没有找到

    • 此时会检查类方法accessInstanceVariablesDirectly是否为YES。
    • 如果为YES,则开始查找成员变量,顺序:_Key, _isKey, Key, isKey。如果找到所需要的Key时,则进入第5步。
    • 未找所需要的Key到则进入第6步,如果accessInstanceVariablesDirectly为NO也进入第6步。
  • 5.根据搜索到的属性值类型,返回不同的结果。

    • 如果是指针,则直接返回结果。
    • 如果是NSNumber支持的标准类型,则将其存储在NSNumber实例中,并返回该值。
    • 如果上面的类型都不符合,则转为NSValue类型,并返回。
  • 6.进入这一步说明未找到对应Key,此时就会执行valueForUndefinedKey方法,如果该方法未实现就会抛出NSUndefinedKeyException类型的异常。

综上所述,通过valueForKey:方法取值的流程如下图所示(已取Person类的name为例)

KVC几种赋值方式

Key-Value Coding (KVC) : 基本类型

一般类型就是我们所说的字符串,基本数据类型,它的用法如下:

    LJPerson *person = [[LJPerson alloc] init];
    person.name      = @"LJ_A";
    person.age       = 18;
    person->myName   = @"A";
    // KVC
    [person setValue:@"LJ" forKey:@"name"];

这种事比较简单的,这里不做过多解释

KVC - 集合类型

集合类型的用法如下:

    person.array = @[@"1",@"2",@"3"];
    // 修改数组
    // person.array[0] = @"100";
    // 第一种:搞一个新的数组 - KVC 赋值就OK
    NSArray *array = [person valueForKey:@"array"];
    array = @[@"100",@"2",@"3"];
    [person setValue:array forKey:@"array"];
    NSLog(@"%@",[person valueForKey:@"array"]);
    // 第二种
    NSMutableArray *mArray = [person mutableArrayValueForKey:@"array"];
    mArray[0] = @"200";
    NSLog(@"%@",[person valueForKey:@"array"]);
我们运行代码

发现赋值成功

集合操作符

集合操作符包含:array取值以及遍历,字典的操作遍历,KVC的消息传递,聚合操作符,数组操作符,嵌套集合(array&set)操作

访问非对象属性

结构体

    ThreeFloats floats = {1.,2.,3.};
    NSValue *value     = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
    [person setValue:value forKey:@"threeFloats"];
    NSValue *value1    = [person valueForKey:@"threeFloats"];
    NSLog(@"%@",value1);
    ThreeFloats th;
    [value1 getValue:&th];
    NSLog(@"%f-%f-%f",th.x,th.y,th.z);
我们运行下,进行打印
image

层层访问 - keyPath

    LGStudent *student = [LGStudent alloc];
    student.subject    = @"好好学习";
    person.student     = student;

    [person setValue:@"Swift" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
keyPath又称为路由,我们运行下代码

说明我们通过keyPath将swift赋值给了student.subject。 之所以对KVC分为上面的几条是从官方文档上的解释

自定义KVC

上面我们讲了KVC的set赋值以及取值过程,所以自定义KVC其实就是实现上面说的那些方法。下面我们创建一个NSObject分类LJKVC

自定义KVC赋值

大致思想

  • 1.先判断key值是否存在,不存在就没必要继续,直接返回。
  • 2.查找setter方法,顺序为setKey、_setKey、setIsKey。
  • 3.判断accessInstanceVariablesDirectly是否为YES,为YES继续下面的流程,为NO就直接抛出异常。
  • 4.间接访问变量赋值(只赋值一次),赋值顺序:_Key、_isKey、Key、isKey。
    • 定义一个收集实例变量的可变数组
    • 通过class_getInstanceVariable获取相应的ivar
    • 通过object_setIvar,对相对应的ivar进行赋值
  • 5.如果找不到相关实例变量,则抛出异常
整体方法如下:

自定义KVC取值

大致思想:

  • 1.判断key,必须非空。
  • 2.查找相应的方法,顺序:getKey、Key、countOfKey、objectInKeyAtIndex: (key都为传入的key值)
  • 3.判断accessInstanceVariablesDirectly是否为YES(是否能够直接赋值实例变量,YES是可以)。为NO则抛出异样。(抛出异常就为崩溃)。
  • 4.间接访问实例变量,顺序:_Key、_isKey、Key、isKey
    • 定义一个收集实例变量的可变数组
    • 通过class_getInstanceVariable,获取相应的ivar
    • 通过object_getIvar,返回相对应ivar值
整体方法如下:

上面就将自定义KVC讲完了。完整代码:自定义KVC

总结

我们上面讲了KVC的赋值和取值的流程,也写了自定义KVC,下篇文章就讲跟KVC关心密切的KVO