14、iOS底层分析 - KVC

2023-11-29 06:48
文章标签 分析 底层 14 ios kvc

本文主要是介绍14、iOS底层分析 - KVC,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

KVC

apple文档

KVC定义

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

在实现了访问器方法的类中,使用点语法和KVC访问对象其实差别不大,二者可以任意混用。但是没有访问起方法的类中,点语法无法使用,这时KVC就有优势了。

KVC的定义都是对NSObject的扩展来实现的,Objective-C中有个显式的NSKeyValueCoding类别名,所以对于所有继承了NSObject的类型,都能使用KVC(一些纯Swift类和结构体是不支持KVC的,因为没有继承NSObject),下面是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转到字典。

扩展:

常用的点语法赋值,在底层是怎么实现赋值的。

点语法的赋值在底层实际是setter

在编译器通过

typedef struct {float x, y, z;
} ThreeFloats;@interface LGPerson : NSObject{@publicNSString *myName;
}@property (nonatomic, copy)   NSString          *name;
@property (nonatomic, strong) NSArray           *array;
@property (nonatomic, strong) NSMutableArray    *mArray;
@property (nonatomic, assign) int age;
@property (nonatomic)         ThreeFloats       threeFloats;
@property (nonatomic, strong) LGStudent         *student;
    LGPerson *person = [[LGPerson alloc] init];// 一般setter 方法person.name      = @"LG_Cooci";person.age       = 18;person->myName   = @"cooci";NSLog(@"%@ - %d - %@",person.name,person.age,person->myName);

1、KVC设置过程

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

基本的KVC设置,int要转成NSNumber类型。其他类型见文章最后。

    [person setValue:@"KC" forKey:@"name"];[person setValue:@19 forKey:@"age"];[person setValue:@"酷C" forKey:@"myName"];NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);

2、 KVC - 集合类型 -

修改person的array,两种方式

  1. 直接取出array,然后重新赋值
  2. 用KVC将array转成 mutableArray,然后修改对应的值。
  3. 通常使用协议定义的三种代理对象访问代理方法,每种方法都有一个键和一个键路径变量:
    1. mutableArrayValueForKey:和 mutableArrayValueForKeyPath:返回行为类似于NSMutableArray对象的代理对象。
    2. mutableSetValueForKey:和 mutableSetValueForKeyPath:     返回行为类似于NSMutableSet对象的代理对象。
    3. mutableOrderedSetValueForKey:和 mutableOrderedSetValueForKeyPath:返回行为类似于NSMutableOrderedSet对象的代理象。
    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"]);// KVC 的方式NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];ma[0] = @"100";NSLog(@"%@",[person valueForKey:@"array"]);

3、 KVC - 集合操作符

[self dictionaryTest];
[self arrayMessagePass];//[self aggregationOperator];//[self arrayOperator];//[self arrayNesting];//[self setNesting];//[self arrayDemo];#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传递给了stringNSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];NSLog(@"%@",lowStr);
}

4、KVC - 访问非对象属性 (比较重要)

访问非对象的属性,不能直接访问,需要使用中间类型。也就是先转换成 NSValue 类型然后再去存。读取的时候也需要先用NSValue接收然后再转换一下。

    ThreeFloats floats = {1., 2., 3.};NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];[person setValue:value forKey:@"threeFloats"];NSValue *reslut = [person valueForKey:@"threeFloats"];NSLog(@"%@",reslut);ThreeFloats th;[reslut getValue:&th] ;NSLog(@"%f - %f - %f",th.x,th.y,th.z);

5、KVC - 层层访问

修改 person 的 student 属性,修改stubent的属性之后,然后将 student 赋值给 person的 student 属性。

@interface LGStudent : NSObject
@property (nonatomic, copy)   NSString          *name;
@property (nonatomic, copy)   NSString          *subject;
    LGStudent *student = [[LGStudent alloc] init];student.subject    = @"iOS";person.student     = student;[person setValue:@"测试" forKeyPath:@"student.subject"];NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);

setValue:forKey: 

通过成员变量进行分析。用成员变量分析不用属性分析的原因是因为通过查看官方文档,可以知道set 和 成员变量都是影响KVC访问的因素,成员变量具有单一变量原则,属性会自动生成set方法。所以用成员变量来进行分析。

LGPerson.h
@interface LGPerson : NSObject{@public// NSString *_name;// NSString *_isName;// NSString *name;// NSString *isName;
}
LGPerson.m
//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);
//}
// 1: KVC - 设置值的过程[person setValue:@"LG_Cooci" 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);
  1. 去掉成员变量的注释,去掉set方法的注释,然后运行查看。然后逐个方法的查看注释,看看调用顺序
  2. 同理通过逐个注释成员变量,来看看 [person setValue:@"LG_Cooci" forKey:@"name"]; 赋值给成员变量的顺序
    • 判断是否存在 1、set<key> 或者 2、_set<key> (掉下划线的属性) 3、setIs<key>,有的话执行。没有的话继续
    • 如果没有条件1(简单访问方式)
      • 判断accessInstanceVariablesDirectly(关闭或开启实例变量赋值) 是否存在 返回 YES。
        • 如果返回的是NO,又不存在set方法的时候就会直接报错,报没有这个方法。
      • 直接给这些实例变量 设值
      • 判断 1、“_<key>” 2、“_is<key>” 3、“<key>” or 4、“is<key>”等实例变量

2、取值的过程

// 2: KVC - 取值的过程person->_name = @"_name";
// person->_isName = @"_isName";
// person->name = @"name";
// person->isName = @"isName";NSLog(@"取值:%@",[person valueForKey:@"name"]);
LGPerson.m
//MARK: - valueForKey 流程分析 - get<Key>, <key>, is<Key>, or _<key>,//- (NSString *)getName{
//    return NSStringFromSelector(_cmd);
//}//- (NSString *)name{
//    return NSStringFromSelector(_cmd);
//}//- (NSString *)isName{
//    return NSStringFromSelector(_cmd);
//}//- (NSString *)_name{
//    return NSStringFromSelector(_cmd);
//}

同设置值一样的验证方式。验证结果:

  1. get<key>”, “<key>”, “is<key>” or “_<key>”  如果有就跳到第五步
  2. 如果没有条件1开始  是否是NSArray 判断
  3. 是否是NSSet判断
  4. 非集合类型
    1. 如果找到,直接获取实例变量的值,然后继续执行步骤5
    2. 判断 “_<key>” “_is<key>” “<key>” or “is<key>”等实例变量
    3. 判断accessInstanceVariablesDirectly 是否存在 返回 YES
  5. 细节处理
    1. 如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回
    2. 如果该值是NSNumber支持的标量类型,则将其存储在NSNumber实例中返回它。
    3. 如果检索到的属性值是对象指针,则只需返回结果。
  6. vlueForUnderfinedKey 报错
  7. 集合类型的还需要操作

3、集合运算符

当您发送与键值编码兼容的对象valueForKeyPath:消息时,可以将集合运算符嵌入到键路径中。集合运算符是一小部分关键字之一,其后带有一个at符号(@),该符号指定getter在返回数据之前应执行的操作以某种方式处理数据。由NSObject提供的valueForKeyPath:的默认实现会实现此行为。

  1. @avg.属性名  求集合中对象某个属性的平均值。
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];
  1. @count      求集合中对象个数。
  2. @max.属性名  求集合中对象某个属性的最大值。
  3. @min.属性名  求集合中对象某个属性的最小值。
  4. @sum.属性名  求集合中对象某个属性的和。
  5. @distinctUnionOfObjects.属性名  取出集合中所有对象某个属性的值,并将这些值存入一个新的数组并返回。这个操作去重。
  6. @unionOfObjects.属性名          取出集合中所有对象某个属性的值,并将这些值存入一个新的数组并返回。这个操作不去重。
  7. @distinctUnionOfArrays.属性名   取出嵌套集合(集合嵌套集合)中所有对象某个属性的值,并返回一个新的数组。这个操作去重。
  8. @unionOfArrays.属性名           取出嵌套集合(集合嵌套集合)中所有对象某个属性的值,并返回一个新的数组。这个操作不去重。
  9. @distinctUnionOfSets.属性名     返回值是个一个NSSet效果和distinctUnionOfArrays一样。
#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);
}

 

4、类型转换

当调用getters如果返回值不是对象,则getter使用此值初始化NSNumber对象(用于标量)或NSValue对象(用于结构体),并返回该值。

类似地,默认情况下,使用setValue:forKey之类的setter:在给定特定键的情况下,确定属性的访问器或实例变量所需的数据类型。 如果数据类型不是对象,则设置器首先将适当的<type> Value消息发送到传入值对象以提取基础数据,然后存储该数据。

Data typeCreation methodAccessor method
BOOLnumberWithBool:boolValue (in iOS) charValue (in macOS)*
charnumberWithChar:charValue
doublenumberWithDouble:doubleValue
floatnumberWithFloat:floatValue
intnumberWithInt:intValue
longnumberWithLong:longValue
long longnumberWithLongLong:longLongValue
shortnumberWithShort:shortValue
unsigned charnumberWithUnsignedChar:unsignedChar
unsigned intnumberWithUnsignedInt:unsignedInt
unsigned longnumberWithUnsignedLong:unsignedLong
unsigned long longnumberWithUnsignedLongLong:unsignedLongLong
unsigned shortnumberWithUnsignedShort:unsignedShort
Data typeCreation methodAccessor method
NSPointvalueWithPoint:pointValue
NSRangevalueWithRange:rangeValue
NSRectvalueWithRect: (macOS only).rectValue
NSSizevalueWithSize:sizeValue

5、自定义KVC

使用NSObject 的分类的形式,可以发现NSKeyValueCoding 也是写的是NSObject的一个分类,这是一种设计模式目的是为了解耦合。将KVC的功能写到NSObject的这个分类里面。

这里不做详细书写,自定义KVC是根据官方文档进行一步一步的去判断,实现。

6、KVC小技巧

typedef struct {float x, y, z;
} ThreeFloats;@interface LGPerson : NSObject{@publicNSString *name;NSString *_name;NSString *_isName;NSString *isName;}@property (nonatomic, copy) NSString *subject;
@property (nonatomic, assign) int  age;
@property (nonatomic, assign) BOOL sex;
@property (nonatomic) ThreeFloats  threeFloats;

1、

// 1: KVC 自动转换类型NSLog(@"******1: KVC - int -> NSNumber - 结构体******");//age是int类型,需要转成NSNumber[person setValue:@18 forKey:@"age"];// 上面那个表达 大家应该都会! 但是下面这样操作可以?//由于age是int类型,即便是存的是用的字符串@“20”,KVC仍然能自动转换成NSCFNumber[person setValue:@"20" forKey:@"age"]; // int - stringNSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);//__NSCFNumber//由于sex是Bool类型,即便是存的是用的字符串@“20”,KVC仍然能自动转换成NSCFNumber[person setValue:@"20" forKey:@"sex"];NSLog(@"%@-%@",[person valueForKey:@"sex"],[[person valueForKey:@"sex"] class]);//__NSCFNumber//threeFloats式结构体类型,存的时候需要存NSValue类型ThreeFloats floats = {1., 2., 3.};NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];[person setValue:value forKey:@"threeFloats"];NSLog(@"%@-%@",[person valueForKey:@"threeFloats"],[[person valueForKey:@"threeFloats"] class]);//NSConcreteValue

 

 

// 2: 设置空值NSLog(@"******2: 设置空值******");
//设置age 会打印出“你傻不傻 。。。”,但是 subject 不会。是因为 setNilValueForKey 方法只能只对 NSNumber - NSValue的结构体进行处理,对NSString 不处理[person setValue:nil forKey:@"age"]; // subject不会走 - 官方注释里面说只对 NSNumber - NSValue[person setValue:nil forKey:@"subject"];// 3: 找不到的 keyNSLog(@"******3: 找不到的 key******");[person setValue:nil forKey:@"KC"]; // 4: 取值时 - 找不到 keyNSLog(@"******4: 取值时 - 找不到 key******");NSLog(@"%@",[person valueForKey:@"KC"]);// 5: 键值验证NSLog(@"******5: 键值验证******");NSError *error;NSString *name = @"LG_Cooci";if (![person validateValue:&name forKey:@"names" error:&error]) {NSLog(@"%@",error);}else{NSLog(@"%@",[person valueForKey:@"name"]);}
person.m
//设置age 会打印出“你傻不傻 。。。”,但是 subject 不会。是因为 setNilValueForKey 方法只能只对 NSNumber - NSValue的结构体进行处理,对NSString 不处理
- (void)setNilValueForKey:(NSString *)key{NSLog(@"你傻不傻: 设置 %@ 是空值",key);
}- (void)setValue:(id)value forUndefinedKey:(NSString *)key{NSLog(@"你瞎啊: %@ 没有这个key",key);
}- (id)valueForUndefinedKey:(NSString *)key{NSLog(@"你瞎啊: %@ 没有这个key - 给你一个其他的吧,别奔溃了!",key);return @"Master 牛逼";
}//MARK: - 键值验证 - 容错 - 派发 - 消息转发
//这里可以进行重定向,运行时进行容错,派发等
- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError{if([inKey isEqualToString:@"name"]){[self setValue:[NSString stringWithFormat:@"里面修改一下: %@",*ioValue] forKey:inKey];return YES;}*outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的属性",inKey,self] code:10088 userInfo:nil];return NO;
}

 

 

 

 

 

 

 

这篇关于14、iOS底层分析 - KVC的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/431854

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in

三相直流无刷电机(BLDC)控制算法实现:BLDC有感启动算法思路分析

一枚从事路径规划算法、运动控制算法、BLDC/FOC电机控制算法、工控、物联网工程师,爱吃土豆。如有需要技术交流或者需要方案帮助、需求:以下为联系方式—V 方案1:通过霍尔传感器IO中断触发换相 1.1 整体执行思路 霍尔传感器U、V、W三相通过IO+EXIT中断的方式进行霍尔传感器数据的读取。将IO口配置为上升沿+下降沿中断触发的方式。当霍尔传感器信号发生发生信号的变化就会触发中断在中断