iOS之你不知道的KVC技巧&KVC原理

前言

相关代码:

源码objc779      KVCCode(博客中的代码都在这里)

iOS开发中很多人都知道KVC ,聊到KVC大家也都知道,就是字典转模型,但是再具体聊的话,就说不太清楚了,这篇文章,准备把KVC再来了解一下;

        XZPerson *person = [XZPerson alloc];
        person.name      = @"Alan";

在调用person.name  会有LLVM编译过程中统一分发给底层的

void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}

void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}


void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}

void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

然后统一调用reallySetProperty方法 ,进入后会根据偏移,和设置的属性,修饰词等进行赋值

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
        if (offset == 0) {
        //设置对象isa
        object_setClass(self, newValue);
        return;
    }
    //声明临时变量oldValue
    id oldValue;
    //将 self 先强转为字符串指针,然后进行内存平移得到要设置的属性的内存偏移值
    id *slot = (id*) ((char*)self + offset);
    //判断属性是否需要copy操作
    if (copy) {
        //这一步的目的是拿到 newValue 的副本,然后覆写 newValue,使得传入的 newValue 之后再发生了改变都不会影响到属性值
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {//标识符是否需要进行 mutableCopy 操作
        //newValue 也就是要设置的属性值发送 mutableCopyWithZone 消息
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        //如果不等,则对新值发送 objc_retain 消息进行 retain 操作,然后将返回值覆写到 newValue 上
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        //如果不是原子操作,则将属性赋值给临时变量 oldValue,然后将新值赋上去
        oldValue = *slot;
        *slot = newValue;
    } else {
        //如果是原子操作,则对赋值操作进行加锁操作保证数据完整性,防止赋值过程中数据发生变化,这也就印证了 atomic 是保证属性的读写操作线程安全
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
//最后对 oldValue 也就是旧值进行内存的释放
    objc_release(oldValue);

}

KVC定义

Key Value Coding 也即 KVC 是 iOS 开发中一个很重要的概念,中文翻译过来是 键值编码 ,关于这个概念的具体定义可以在 Apple 的官方文档处找到:

Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties.
【译】KVC 是通过 NSKeyValueCoding 这个非正式协议启用的一种机制,而遵循了这个协议的对象就提供了对其属性的间接访问

KVC的使用技巧

  1. KVC-直接使用setValue:forKey 进行赋值

  2. KVC-xib 中使用keyPath 进行赋值

  3. KVC-集合类型进行操作(不可变数组进行修改值)

  4. KVC-集合操作符(一些不常用,但很使用的技巧)

  5. KVC-访问非对象属性(结构体类型访问与修改)

  6. KVC - 层层访问

 下面会使用的的2个类XZPerson类,和XZStudent类

#import 

NS_ASSUME_NONNULL_BEGIN

@interface XZStudent : NSObject
@property (nonatomic, copy)   NSString          *name;
@property (nonatomic, copy)   NSString          *subject;
@property (nonatomic, copy)   NSString          *nick;
@property (nonatomic, assign) int               age;
@property (nonatomic, assign) int               length;
@property (nonatomic, strong) NSMutableArray    *penArr;
@end

NS_ASSUME_NONNULL_END


#import 
#import "XZStudent.h"

NS_ASSUME_NONNULL_BEGIN
typedef struct {
    float x, y, z;
} ThreeFloats;


@interface XZPerson : NSObject{
   @public
   NSString *myName;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSArray *array;
@property (nonatomic, strong) NSMutableArray *mArray;
@property (nonatomic, assign) int age;
@property (nonatomic) ThreeFloats threeFloats;
@property (nonatomic, strong) XZStudent *student;

@end

NS_ASSUME_NONNULL_END

1.直接使用setValue:forKey 进行赋值

  XZPerson *person = [[XZPerson alloc]init];
    //2.key- Value  Codeing (KVC) 基本数据类型
    [person setValue:@"XZ" forKey:@"name"];
    [person setValue:@19 forKey:@"age"];
    [person setValue:@"星" forKey:@"myName"];

2.xib 中使用keyPath 进行赋值

iOS之你不知道的KVC技巧&KVC原理_第1张图片 在Xib 上直接使用key Path 进行添加属性;查看执行结果:

iOS之你不知道的KVC技巧&KVC原理_第2张图片

 

可以看到根据key Path 修改了View的属性:

3、集合类型进行操作

     3.1不可变数组进行赋值修改;

    //3: KVC 集合类型
    person.array = @[@"1",@"2",@"3"];
    // 由于不是可变数组 - 无法做到
    // person.array[0] = @"100";
    NSArray *array = [person valueForKey:@"array"];
    // 用 array 的值创建一个新的数组
    array = @[@"100",@"2",@"3"];
    [person setValue:array forKey:@"array"];
    NSLog(@"%@",[person valueForKey:@"array"]);

    3.2不可变数组直接深拷贝,进行赋值修改

  // KVC 的方式,使用深拷贝地址,直接改变数组
    NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
    ma[0] = @"101";
    ma[3] = @"103";
    //可以顺序插入,如果跳过插入会导致崩溃
    //ma[5] = @"105";
    NSLog(@"%@",[person valueForKey:@"array"]);
    

4:KVC - 集合操作符

4.1:array取值

#pragma mark - array取值
- (void)arrayDemo{
    XZStudent *p = [XZStudent new];
    p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
    NSArray *arr = [p valueForKey:@"penArr"]; // 动态成员变量
    NSLog(@"pens = %@", arr);
    //NSLog(@"%@",arr[0]);
    NSLog(@"%d",[arr containsObject:@"pen9"]);
    // 遍历
    NSEnumerator *enumerator = [arr objectEnumerator];
    NSString* str = nil;
    while (str = [enumerator nextObject]) {
        NSLog(@"%@", str);
    }
}

4.2:字典操作

- (void)dictionaryTest{
    
    NSDictionary* dict = @{
                           @"name":@"Alan",
                           @"nick":@"XZ",
                           @"subject":@"iOS",
                           @"age":@18,
                           @"length":@180
                           };
    XZStudent *p = [[XZStudent alloc] init];
    // 字典转模型
    [p setValuesForKeysWithDictionary:dict];
    NSLog(@"%@",p);
    // 键数组转模型到字典
    NSArray *array = @[@"name",@"age"];
    NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
    NSLog(@"%@",dic);
}

输出dic为:

4.3 KVC消息传递

#pragma mark - KVC消息传递
- (void)arrayMessagePass{
    NSArray *array = @[@"Alan",@"Xing",@"XZ",@"ZhaiAlan"];
    NSArray *lenStr= [array valueForKeyPath:@"length"];
    NSLog(@"%@",lenStr);// 消息从array传递给了string
    NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
    NSLog(@"%@",lowStr);
}

输出结果为:

iOS之你不知道的KVC技巧&KVC原理_第3张图片

4.4 聚合操作符(@avg、@count、@max、@min、@sum)

#pragma mark - 聚合操作符
// @avg、@count、@max、@min、@sum
- (void)aggregationOperator{
    NSMutableArray *personArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        XZStudent *p = [XZStudent new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [p setValuesForKeysWithDictionary:dict];
        [personArray addObject:p];
    }
    NSLog(@"length-->%@", [personArray valueForKey:@"length"]);
    
    /// 平均身高
    float avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];
    NSLog(@"avg-->%f", avg);
    
    int count = [[personArray valueForKeyPath:@"@count.length"] intValue];
    NSLog(@"count-->%d", count);
    
    int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];
    NSLog(@"sum--->%d", sum);
    
    int max = [[personArray valueForKeyPath:@"@max.length"] intValue];
    NSLog(@"max.lenght-->%d", max);
    
    int min = [[personArray valueForKeyPath:@"@min.length"] intValue];
    NSLog(@"min.lenght-->%d", min);
}

输出结果为 

    4.5:数组操作符 @distinctUnionOfObjects @unionOfObjects 

// 数组操作符 @distinctUnionOfObjects @unionOfObjects
- (void)arrayOperator{
    NSMutableArray *personArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        XZStudent *student = [XZStudent new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [personArray addObject:student];
    }
    NSLog(@"%@", [personArray valueForKey:@"age"]);
    // 返回操作对象指定属性的集合
    NSArray* arr1 = [personArray valueForKeyPath:@"@unionOfObjects.age"];
    NSLog(@"@unionOfObjects.age--> %@", arr1);
    // 返回操作对象指定属性的集合 -- 去重
    NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.age"];
    NSLog(@"@distinctUnionOfObjects.age--> %@", arr2);
    NSArray* arr3 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.nick"];
    NSLog(@"@distinctUnionOfObjects.nick--> %@", arr3);

}

输出结果为:

iOS之你不知道的KVC技巧&KVC原理_第4张图片

4.6:嵌套集合(array)操作 @distinctUnionOfArrays @unionOfArrays

// 嵌套集合(array&set)操作 @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets
- (void)arrayNesting{
    
    NSMutableArray *studentArray1 = [NSMutableArray array];
    for (int i = 0; i < 5; i++) {
        XZStudent *student = [XZStudent new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(15+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [studentArray1 addObject:student];
    }
    
    NSMutableArray *studentArray2 = [NSMutableArray array];
    for (int i = 0; i < 5; i++) {
        XZStudent *student = [XZStudent new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [studentArray2 addObject:student];
    }
    
    // 嵌套数组
    NSArray* nestArr = @[studentArray1, studentArray2];
    
    NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.age"];
    NSLog(@"@distinctUnionOfArrays.age--> %@", arr);
    
    NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.age"];
    NSLog(@"@unionOfArrays.age--> %@", arr1);
}

输出结果为:

iOS之你不知道的KVC技巧&KVC原理_第5张图片

4.7 嵌套组合(set )@distinctUnionOfSets 

- (void)setNesting{
    
    NSMutableSet *studentSet1 = [NSMutableSet set];
    for (int i = 0; i < 5; i++) {
        XZStudent *student = [XZStudent new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(15+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [studentSet1 addObject:student];
    }
    NSLog(@"studentSet1 = %@", [studentSet1 valueForKey:@"length"]);
    
    NSMutableSet *studentSet2 = [NSMutableSet set];
    for (int i = 0; i < 5; i++) {
        XZStudent *student = [XZStudent new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [studentSet2 addObject:student];
    }
    NSLog(@"studentSet2 = %@", [studentSet2 valueForKey:@"length"]);

    // 嵌套set
    NSSet* nestSet = [NSSet setWithObjects:studentSet1, studentSet2, nil];
    // 并集
    NSSet* set1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.age"];
    NSLog(@"@distinctUnionOfSets.age--> %@", set1);
    
}

输出结果为:

iOS之你不知道的KVC技巧&KVC原理_第6张图片

5:KVC - 访问非对象属性

给结构体中赋值:需要转换成NSValue类型才能进行赋值

结构体为:
typedef struct {
    float x, y, z;
} ThreeFloats;

   

 ThreeFloats floats = {1., 2., 3.};
    //结构体需要转换成相应的NSValue
    NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
    [person setValue:value forKey:@"threeFloats"];
    NSValue *reslut = [person valueForKey:@"threeFloats"];
    NSLog(@"%@",reslut);

6:KVC -层层访问

person类中有个student类,其中的属性进行访问

  // 5:KVC - 层层访问
    XZPerson *person = [[XZPerson alloc]init];

    
    XZStudent *student = [[XZStudent alloc] init];
    student.subject    = @"iOS";
    person.student     = student;
    [person setValue:@"iOS学习" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);

KVC底层原理探索

我们通常使用访问器方法来访问对象的属性,即使用 getter 来获取属性值,使用 setter 来设置属性值。在 Objective-C 中,我们还可以直接通过实例变量的方式来获取属性值和设置属性值。如下面的代码所示:

这种方式我们再熟悉不过了,关于属性会由编译器自动生成 getter 和 setter 以及对应的实例变量前面我们已经探索过了,我们可以在 ro 中来找到它们的踪影,感兴趣的读者可以翻阅前面的文章(类的分析)。

注:这里再明确下实例变量、成员变量、属性之间的区别: 在 @interface 括号里面声明的变量统称为 成员变量 而成员变量实际上由两部分组成:实例变量 + 基本数据类型变量 而属性 = 成员变量 + getter方法 + setter方法

KVC赋值过程

底层是根据setValue:forkey方式对键值进行赋值的,官方文中是这么描述的:

官方文档:

Search Pattern for the Basic Setter
The default implementation of setValue:forKey:, given key and value parameters as input, attempts to set a property named key to value (or, for non-object properties, the unwrapped version of value, as described in Representing Non-Object Values) inside the object receiving the call, using the following procedure:

1.Look for the first accessor named set: or _set, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.

2.If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like _, _is, or is, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.

3.Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.

译文:

基本Setter的搜索模式

setValue:for key:的默认实现是,给定键和值参数作为输入,尝试在接收调用的对象内将名为key的属性设置为value(或者,对于非对象属性,使用以下过程将值的未包装版本设置为value,如表示非对象值中所述):

1.按此顺序查找第一个名为set或_set的访问器。如果找到,使用输入值(或者根据需要使用unwrapped值)调用它并完成。

2.如果找不到简单的访问器,并且类方法accessInstanceVariablesDirectly返回YES,则按该顺序查找名为_,  _is,  or  is,的实例变量。如果找到,直接用输入值(或未包装值)设置变量并完成。

3.在找不到访问器或实例变量时,调用setValue:forUndefinedKey:。默认情况下,这会引发异常,但NSObject的子类可能提供特定于键的行为。

由此可以看出,如果set方法和成员变量都会造成影响,所以这里进行研究的时候不能使用属性,需要使用成员变量进行研究:

这里新建一个XZPerson类:

#import 

NS_ASSUME_NONNULL_BEGIN

@interface XZPerson : NSObject{
    @public
     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}

@end

NS_ASSUME_NONNULL_END
//MARK: - setKey. 的流程分析
- (void)setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

- (void)_setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

给XZPerson类属性赋值:

    XZPerson *person = [XZPerson new];
//    1: KVC - 设置值的过程
    [person setValue:@"XZ_Alan" forKey:@"name"];
    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
    NSLog(@"%@-%@",person->name,person->isName);
    NSLog(@"%@",person->isName);

执行,观察打印结果:(这里需要说明一下下面打印纸都为Null的原因是因为实现了set方法但是并没有赋值)

可以看到运行了setName方法,将这个方法注释:

继续注释_setName:方法:

根据官方文档我们看到,如果这3个方法都没有实现(将上面实现的3个方法都注释掉),则需要实现类方法accessInstanceVariablesDirectly并返回YES,我们先尝试一下返回NO

在Person类中添加方法:

//#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
    return NO;
}

这样就会报一个常见的错误:[ setValue:forUndefinedKey:] 未找到当前key值

iOS之你不知道的KVC技巧&KVC原理_第7张图片

将 accessInstanceVariablesDirectly返回为YES;再进行尝试;

可以看到person->_name 有值了,注释掉_name,因为注释_name,第一行打印也需要去掉,再来查看结果:

可以看到使用person->_isName有值了,继续注释掉_isName,进行查看;

可以看到使用person->name有值了,继续注释掉name,进行查看;

可以看到使用,person->isName有值,继续注释掉isName就会看到就位的错误,赋值过程完毕;

iOS之你不知道的KVC技巧&KVC原理_第8张图片

小结:

1.赋值是会首先查看setName ,_setName,setIsName,方法

2.如果找访问器这需要看类中是否实现accessInstanceVariablesDirectly 并返回YES ,然后按照顺序:_name,isName,name,isName实例变量查找,找到直接复制,设置完成

3.如果还是找不到,会调用setValue:forUndefinedKey方法,这个会报异常,

KVC取值过程

底层是根据valueForKey:方式对键值进行赋值的,官方文中是这么描述的:

Search Pattern for the Basic Getter

The default implementation of valueForKey:, given a key parameter as input, carries out the following procedure, operating from within the class instance receiving the valueForKey: call.

  1. Search the instance for the first accessor method found with a name like getis, or _, in that order. If found, invoke it and proceed to step 5 with the result. Otherwise proceed to the next step.

  2. If no simple accessor method is found, search the instance for methods whose names match the patterns countOf and objectInAtIndex: (corresponding to the primitive methods defined by the NSArray class) and AtIndexes: (corresponding to the NSArray method objectsAtIndexes:).

    If the first of these and at least one of the other two is found, create a collection proxy object that responds to all NSArray methods and return that. Otherwise, proceed to step 3.

    The proxy object subsequently converts any NSArray messages it receives to some combination of countOfobjectInAtIndex:, and AtIndexes: messages to the key-value coding compliant object that created it. If the original object also implements an optional method with a name like get:range:, the proxy object uses that as well, when appropriate. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSArray, even if it is not.

  3. If no simple accessor method or group of array access methods is found, look for a triple of methods named countOfenumeratorOf, and memberOf: (corresponding to the primitive methods defined by the NSSet class).

    If all three methods are found, create a collection proxy object that responds to all NSSet methods and return that. Otherwise, proceed to step 4.

    This proxy object subsequently converts any NSSet message it receives into some combination of countOfenumeratorOf, and memberOf: messages to the object that created it. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSSet, even if it is not.

  4. If no simple accessor method or group of collection access methods is found, and if the receiver's class method accessInstanceVariablesDirectly returns YES, search for an instance variable named __is, or is, in that order. If found, directly obtain the value of the instance variable and proceed to step 5. Otherwise, proceed to step 6.

  5. If the retrieved property value is an object pointer, simply return the result.

    If the value is a scalar type supported by NSNumber, store it in an NSNumber instance and return that.

    If the result is a scalar type not supported by NSNumber, convert to an NSValue object and return that.

  6. If all else fails, invoke valueForUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior

译文

基本Getter的搜索模式

给定一个键参数作为输入,valueForKey:的默认实现从接收valueForKey:调用的类实例中执行以下过程。

      1.在实例中搜索找到的第一个访问器方法,该方法的名称如get、is、或者 _。如果找到,则调用它并继续执行步骤5并返回结果。否则继续下一步。

       2.如果找不到简单的访问器方法,请在实例中搜索名称与模式countOf和objectInAtIndex:(对应于NSArray类定义的基元方法)和AtIndex:(对应于NSArray方法objectsAtIndexes:)匹配的方法。

       如果找到其中的第一个和其他两个方法中的至少一个,则创建一个响应所有NSArray方法的集合代理对象并返回该对象。否则,继续执行步骤3。

       3.代理对象随后将其接收到的任何NSArray消息转换为countOf、objectInAtIndex:、和AtIndexes:消息的组合,并将其转换为创建该对象的键值编码兼容对象。如果原始对象还实现了一个名为get:range:(get:range:)的可选方法,则代理对象也会在适当时使用该方法。实际上,代理对象与密钥值编码兼容对象一起工作,允许底层属性的行为如同它是NSArray,即使它不是NSArray。

        如果找不到简单的访问器方法或数组访问方法组,请查找名为countOf、enumeratorOf和memberOf的三个方法(对应于NSSet类定义的基本方法)。

      如果找到这三个方法,则创建一个集合代理对象,该对象响应所有NSSet方法并返回该对象。否则,继续执行步骤4。

此代理对象随后将接收到的任何NSSet消息转换为countOf、enumeratorOf和memberOf:消息的组合,并将其转换为创建它的对象。实际上,代理对象与键值编码兼容对象一起工作,允许底层属性的行为如同它是NSSet,即使它不是NSSet。

        4. 如果找不到简单的访问器方法或集合访问方法组,并且如果接收方的类方法accessInstanceVariablesDirectly返回YES,则按该顺序搜索名为_、_is或is的实例变量。如果找到,直接获取实例变量的值并继续执行步骤5。否则,继续执行步骤6。

       5.如果检索到的属性值是对象指针,则只需返回结果。

          如果该值是NSNumber支持的标量类型,请将其存储在NSNumber实例中并返回该实例。

          如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回该对象。

        6.如果所有其他操作都失败,请调用valueForUnd定义键:。默认情况下,这会引发异常,但NSObject的子类可能提供特定于键的行为

根据文档来探索流程:

首先我们将XZPerson类中进行修改,添加对应的get方法(返回值直接返回方法编号),并把对应的成员变量都进行打开

NS_ASSUME_NONNULL_BEGIN

@interface XZPerson : NSObject{
    @public
     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}
@end

NS_ASSUME_NONNULL_END

@implementation XZPerson
//#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
    return YES;
}
//MARK: - valueForKey 流程分析 - get, , is, or _,

- (NSString *)getName{
    return NSStringFromSelector(_cmd);
}

- (NSString *)name{
    return NSStringFromSelector(_cmd);
}

- (NSString *)isName{
    return NSStringFromSelector(_cmd);
}

- (NSString *)_name{
    return NSStringFromSelector(_cmd);
}

@end

在Viewdidload中添加代码:对应的方式,附上对应的值,这样就可以看出到底是那种方式进行赋值的了

     XZPerson *person = [XZPerson new];

    // 2: KVC - 取值的过程
     person->_name = @"赋值_name";
     person->_isName = @"赋值_isName";
     person->name = @"赋值name";
     person->isName = @"赋值isName";
     NSLog(@"取值:%@",[person valueForKey:@"name"]);

运行结果为:

注释掉getName方法,再次执行:

继续注释掉name方法,继续执行:

注释isName方法,继续执行:

继续注释掉_name方法执行:

执行结果为上面赋值结果了,这里将person->_name = @"赋值_name"和person类中_name成员变量注释掉,继续执行:

继续注释对应的_isName,执行

注释name,执行

注释isName,执行

报错,未找到当前属性

这里再插一条,如果有_name也有_isName,但是_name没有赋值,_isName有赋值会是什么情况呢?

iOS之你不知道的KVC技巧&KVC原理_第9张图片

iOS之你不知道的KVC技巧&KVC原理_第10张图片

执行结果为:

可以看出,只要有_name,无论赋值没有,就只会获取_name的值; 

小结:

这里以name调用valueForKey后具体步骤分为3步;

1. 系统会顺序调用get、is、 _,找到方法后,就不会调用下一个方法了

2.-3就不会走了,因为不是数组集合类型(数组集合探索可下载代码KVCCode)

直接执行到第4步,顺序访问_name,_isName,name,isName成员变量值;如果访问到任何一个值就不会访问下一个值了;

如果上面都没有实现,则会报错valueForUndefinedKey 未找到当前属性

 

总结:

这篇文章主要总结了一些KVC的一些用法,和KVC 取值和赋值过程中的分析,主要是大家可以根据一些文档来进行学习,iOS底层的实现逻辑,后续会写一篇自定义KVC。

希望对大家有用处,欢迎大家点赞+评论,关注我的CSDN,我会定期做一些技术分享!未完待续。。。

你可能感兴趣的:(iOS底层,iOS面试题,KVC)