OC底层原理19-KVC

4,827 阅读4分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

前言

KVC又称键值编码 (Key-Value-Coding),在iOS开发中是一个比较常见的技术点,相信很多开发人员都使用过KVC,其主要的两个方法就是如下两个,分别对应设置值和取值:

  • (void)setValue:(nullable id)value forKey:(NSString *)key;
  • (nullable id)valueForKey:(NSString *)key;

一.KVC简单应用

    LGPerson *person = [[LGPerson alloc] init];

    // 一般setter 方法

    person.name      = @"LG_Cooci"; // setter -- llvm

    person.age       = 18;

    person->myName   = @"cooci";

    NSLog(@"%@ - %d - %@",person.name,person.age,person->myName);

   

    // 1:Key-Value Coding (KVC) : 基本类型 - 看底层原理

    // 非正式协议 - 间接访问

    [person setValue:@"KC" forKey:@"name"];

    

    // 2: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"]);
    // 3:KVC - 集合操作符
    // 4:KVC - 访问非对象属性 - 面试可能问到
    // 结构体

    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);

    // 5:KVC - 层层访问 - keyPath

    LGStudent *student = [LGStudent alloc];

    student.subject    = @"大师班";

    person.student     = student;

    [person setValue:@"Swift" forKeyPath:@"student.subject"];

    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
#pragma mark **- array取值**

- (**void**)arrayDemo{

    LGStudent *p = [LGStudent new];

    p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", **nil**];

    NSArray *arr = [p valueForKey:@"pens"]; // 动态成员变量

    NSLog(@"pens = %@", arr);

    //NSLog(@"%@",arr[0]);

    NSLog(@"%d",[arr containsObject:@"pen9"]);

    // 遍历

    NSEnumerator *enumerator = [arr objectEnumerator];

    NSString* str = **nil**;

    **while** (str = [enumerator nextObject]) {

        NSLog(@"%@", str);

    }

}

#pragma mark **- 字典操作**
- (**void**)dictionaryTest{

    

    NSDictionary* dict = @{

                           @"name":@"Cooci",

                           @"nick":@"KC",

                           @"subject":@"iOS",

                           @"age":@18,

                           @"length":@180

                           };

    LGStudent *p = [[LGStudent alloc] init];

    // 字典转模型

    [p setValuesForKeysWithDictionary:dict];

    NSLog(@"%@",p);

    // 键数组转模型到字典

    NSArray *array = @[@"name",@"age"];

    NSDictionary *dic = [p dictionaryWithValuesForKeys:array];

    NSLog(@"%@",dic);

}
#pragma mark **- KVC消息传递**

- (**void**)arrayMessagePass{

    NSArray *array = @[@"Hank",@"Cooci",@"Kody",@"CC"];

    NSArray *lenStr= [array valueForKeyPath:@"length"];

    NSLog(@"%@",lenStr);// 消息从array传递给了string

    NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];

    NSLog(@"%@",lowStr);

}
#pragma mark **- 聚合操作符**

// @avg、@count、@max、@min、@sum

- (**void**)aggregationOperator{

    NSMutableArray *personArray = [NSMutableArray array];

    **for** (**int** i = 0; i < 6; i++) {

        LGStudent *p = [LGStudent new];

        NSDictionary* dict = @{

                               @"name":@"Tom",

                               @"age":@(18+i),

                               @"nick":@"Cat",

                               @"length":@(175 + 2*arc4random_uniform(6)),

                               };

        [p setValuesForKeysWithDictionary:dict];

        [personArray addObject:p];

    }

    NSLog(@"%@", [personArray valueForKey:@"length"]);

    

    /// 平均身高

    **float** avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];

    NSLog(@"%f", avg);

    

    **int** count = [[personArray valueForKeyPath:@"@count.length"] intValue];

    NSLog(@"%d", count);

    

    **int** sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];

    NSLog(@"%d", sum);

    

    **int** max = [[personArray valueForKeyPath:@"@max.length"] intValue];

    NSLog(@"%d", max);

    

    **int** min = [[personArray valueForKeyPath:@"@min.length"] intValue];

    NSLog(@"%d", min);

}

// 数组操作符 @distinctUnionOfObjects @unionOfObjects

- (**void**)arrayOperator{

    NSMutableArray *personArray = [NSMutableArray array];

    **for** (**int** i = 0; i < 6; i++) {

        LGStudent *p = [LGStudent new];

        NSDictionary* dict = @{

                               @"name":@"Tom",

                               @"age":@(18+i),

                               @"nick":@"Cat",

                               @"length":@(175 + 2*arc4random_uniform(6)),

                               };

        [p setValuesForKeysWithDictionary:dict];

        [personArray addObject:p];

    }

    NSLog(@"%@", [personArray valueForKey:@"length"]);

    // 返回操作对象指定属性的集合

    NSArray* arr1 = [personArray valueForKeyPath:@"@unionOfObjects.length"];

    NSLog(@"arr1 = %@", arr1);

    // 返回操作对象指定属性的集合 -- 去重

    NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];

    NSLog(@"arr2 = %@", arr2);

    

}
// 嵌套集合(array&set)操作 @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets

- (**void**)arrayNesting{

    

    NSMutableArray *personArray1 = [NSMutableArray array];

    **for** (**int** i = 0; i < 6; i++) {

        LGStudent *student = [LGStudent new];

        NSDictionary* dict = @{

                               @"name":@"Tom",

                               @"age":@(18+i),

                               @"nick":@"Cat",

                               @"length":@(175 + 2*arc4random_uniform(6)),

                               };

        [student setValuesForKeysWithDictionary:dict];

        [personArray1 addObject:student];

    }

    

    NSMutableArray *personArray2 = [NSMutableArray array];

    **for** (**int** i = 0; i < 6; i++) {

        LGPerson *person = [LGPerson new];

        NSDictionary* dict = @{

                               @"name":@"Tom",

                               @"age":@(18+i),

                               @"nick":@"Cat",

                               @"length":@(175 + 2*arc4random_uniform(6)),

                               };

        [person setValuesForKeysWithDictionary:dict];

        [personArray2 addObject:person];

    }

    

    // 嵌套数组

    NSArray* nestArr = @[personArray1, personArray2];

    

    NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.length"];

    NSLog(@"arr = %@", arr);

    

    NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.length"];

    NSLog(@"arr1 = %@", arr1);

}
- (**void**)setNesting{

    

    NSMutableSet *personSet1 = [NSMutableSet set];

    **for** (**int** i = 0; i < 6; i++) {

        LGStudent *person = [LGStudent new];

        NSDictionary* dict = @{

                               @"name":@"Tom",

                               @"age":@(18+i),

                               @"nick":@"Cat",

                               @"length":@(175 + 2*arc4random_uniform(6)),

                               };

        [person setValuesForKeysWithDictionary:dict];

        [personSet1 addObject:person];

    }

    NSLog(@"personSet1 = %@", [personSet1 valueForKey:@"length"]);

    

    NSMutableSet *personSet2 = [NSMutableSet set];

    **for** (**int** i = 0; i < 6; i++) {

        LGPerson *person = [LGPerson new];

        NSDictionary* dict = @{

                               @"name":@"Tom",

                               @"age":@(18+i),

                               @"nick":@"Cat",

                               @"length":@(175 + 2*arc4random_uniform(6)),

                               };

        [person setValuesForKeysWithDictionary:dict];

        [personSet2 addObject:person];

    }

    NSLog(@"personSet2 = %@", [personSet2 valueForKey:@"length"]);
    // 嵌套set
    NSSet* nestSet = [NSSet setWithObjects:personSet1, personSet2, **nil**];
    // 交集
    NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.length"];
    NSLog(@"arr1 = %@", arr1);
}

二.KVC简介-苹果官方文档重要

苹果开发iOS地址

三.苹果官方文档解释KVC

KVC地址

四.KVC设值和取值过程

KVC设置

Xnip2021-08-08_15-00-28.jpg

KVC设置值的过程

Xnip2021-08-08_15-06-56.jpg

执行的优先顺序按照苹果官方文档所示set<Key>:-> _set<key>:

Xnip2021-08-08_15-10-03.jpg

accessInstanceVariablesDirectly 设置为YES时 获取实例变量的顺序为顺序查找名称为_<key>->_is<Key>-><key>->is<Key>

Xnip2021-08-08_15-15-32.jpg

Xnip2021-08-08_15-15-58.jpg

_<key> Xnip2021-08-08_15-16-45.jpg _is<Key>

Xnip2021-08-08_15-17-37.jpg <key>

Xnip2021-08-08_15-18-03.jpg

is<Key>

Xnip2021-08-08_15-18-29.jpg

按照官方文档说 如果没有以上都没有 就会进入下面这个方法 Xnip2021-08-08_15-24-00.jpg

KVC取值的过程

Xnip2021-08-08_15-26-47.jpg

执行顺序按照官方文档 get<Key>-><key>->is<Key>->_<key>

Xnip2021-08-08_15-31-10.jpg

如果以上都没有 按照_<key>->_is<Key>-><key>->is<Key>

Xnip2021-08-08_15-40-37.jpg

细节当我们注释掉_name 打印name 显示的是_isName 顺序问题

Xnip2021-08-08_15-42-40.jpg

如果都没有的话 就会调用 valueForUndefinedKey:这个方法

五.KVC自定义实现

- (**void**)lg_setValue:(**nullable** **id**)value forKey:(NSString *)key{

    // KVC 自定义

    // 1: 判断什么 key

    **if** (key == **nil** || key.length == 0) {

        **return**;

    }
    // 2: setter set<Key>: or _set<Key>,

    // key 要大写

    NSString *Key = key.capitalizedString;

    // 拼接方法

    NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];

    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];

    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];

    

    **if** ([**self** lg_performSelectorWithMethodName:setKey value:value]) {

        NSLog(@"*********%@**********",setKey);

        **return**;

    }**else** **if** ([**self** lg_performSelectorWithMethodName:_setKey value:value]) {

        NSLog(@"*********%@**********",_setKey);

        **return**;

    }**else** **if** ([**self** lg_performSelectorWithMethodName:setIsKey value:value]) {

        NSLog(@"*********%@**********",setIsKey);

        **return**;

    }

    

    // 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃

    // 3:判断是否能够直接赋值实例变量

    **if** (![**self**.class accessInstanceVariablesDirectly] ) {

        **@throw** [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",**self**] userInfo:**nil**];

    }
    // 4: 间接变量

    // 获取 ivar -> 遍历 containsObjct -

    // 4.1 定义一个收集实例变量的可变数组

    NSMutableArray *mArray = [**self** getIvarListName];

    // _<key> _is<Key> <key> is<Key>

    NSString *_key = [NSString stringWithFormat:@"_%@",key];

    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];

    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];

    **if** ([mArray containsObject:_key]) {

        // 4.2 获取相应的 ivar

       Ivar ivar = class_getInstanceVariable([**self** class], _key.UTF8String);

        // 4.3 对相应的 ivar 设置值

       object_setIvar(**self** , ivar, value);

       **return**;

    }**else** **if** ([mArray containsObject:_isKey]) {

       Ivar ivar = class_getInstanceVariable([**self** class], _isKey.UTF8String);

       object_setIvar(**self** , ivar, value);

       **return**;

    }**else** **if** ([mArray containsObject:key]) {

       Ivar ivar = class_getInstanceVariable([**self** class], key.UTF8String);

       object_setIvar(**self** , ivar, value);

       **return**;

    }**else** **if** ([mArray containsObject:isKey]) {

       Ivar ivar = class_getInstanceVariable([**self** class], isKey.UTF8String);

       object_setIvar(**self** , ivar, value);

       **return**;

    }
    // 5:如果找不到相关实例

    **@throw** [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",**self**,NSStringFromSelector( **_cmd**)] userInfo:**nil**];

    

}
- (**nullable** **id**)lg_valueForKey:(NSString *)key{

    

    // 1:刷选key 判断非空

    **if** (key == **nil**  || key.length == 0) {

        **return** **nil**;

    }

    // 2:找到相关方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex

    // key 要大写

    NSString *Key = key.capitalizedString;

    // 拼接方法

    NSString *getKey = [NSString stringWithFormat:@"get%@",Key];

    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];

    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];

        

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

    **if** ([**self** respondsToSelector:NSSelectorFromString(getKey)]) {

        **return** [**self** performSelector:NSSelectorFromString(getKey)];

    }**else** **if** ([**self** respondsToSelector:NSSelectorFromString(key)]){

        **return** [**self** performSelector:NSSelectorFromString(key)];

    }**else** **if** ([**self** respondsToSelector:NSSelectorFromString(countOfKey)]){

        **if** ([**self** respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {

            **int** num = (**int**)[**self** performSelector:NSSelectorFromString(countOfKey)];

            NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];

            **for** (**int** i = 0; i<num-1; i++) {

                num = (**int**)[**self** performSelector:NSSelectorFromString(countOfKey)];

            }

            **for** (**int** j = 0; j<num; j++) {

                **id** objc = [**self** performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];

                [mArray addObject:objc];

            }

            **return** mArray;

        }

    }

#pragma clang diagnostic pop

    

    // 3:判断是否能够直接赋值实例变量

    **if** (![**self**.class accessInstanceVariablesDirectly] ) {

        **@throw** [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",**self**] userInfo:**nil**];

    }

    

    // 4.找相关实例变量进行赋值

    // 4.1 定义一个收集实例变量的可变数组

    NSMutableArray *mArray = [**self** getIvarListName];

    // _<key> _is<Key> <key> is<Key>

    // _name -> _isName -> name -> isName

    NSString *_key = [NSString stringWithFormat:@"_%@",key];

    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];

    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];

    **if** ([mArray containsObject:_key]) {

        Ivar ivar = class_getInstanceVariable([**self** class], _key.UTF8String);

        **return** object_getIvar(**self**, ivar);;

    }**else** **if** ([mArray containsObject:_isKey]) {

        Ivar ivar = class_getInstanceVariable([**self** class], _isKey.UTF8String);

        **return** object_getIvar(**self**, ivar);;

    }**else** **if** ([mArray containsObject:key]) {

        Ivar ivar = class_getInstanceVariable([**self** class], key.UTF8String);

        **return** object_getIvar(**self**, ivar);;

    }**else** **if** ([mArray containsObject:isKey]) {

        Ivar ivar = class_getInstanceVariable([**self** class], isKey.UTF8String);

        **return** object_getIvar(**self**, ivar);;

    }
    **return** @"";

}
#pragma mark **- 相关方法**

- (**BOOL**)lg_performSelectorWithMethodName:(NSString *)methodName value:(**id**)value{

 

    **if** ([**self** respondsToSelector:NSSelectorFromString(methodName)]) {

        

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

        [**self** performSelector:NSSelectorFromString(methodName) withObject:value];

#pragma clang diagnostic pop

        **return** **YES**;

    }

    **return** **NO**;

}
- (**id**)performSelectorWithMethodName:(NSString *)methodName{

    **if** ([**self** respondsToSelector:NSSelectorFromString(methodName)]) {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

        **return** [**self** performSelector:NSSelectorFromString(methodName) ];

#pragma clang diagnostic pop

    }

    **return** **nil**;

}
- (NSMutableArray *)getIvarListName{

    

    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];

    **unsigned** **int** count = 0;

    Ivar *ivars = class_copyIvarList([**self** class], &count);

    **for** (**int** i = 0; i<count; i++) {

        Ivar ivar = ivars[i];

        **const** **char** *ivarNameChar = ivar_getName(ivar);

        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];

        NSLog(@"ivarName == %@",ivarName);

        [mArray addObject:ivarName];

    }

    free(ivars);

    **return** mArray;

}

六.KVC拓展和总结

Xnip2021-08-08_16-11-31.jpg