【iOS】KVC相关总结

目录

    • 1. 什么是KVC?
    • 2. 访问对象属性
      • 常用方法声明
      • 基础使用
      • KeyPath路径
      • 多值操作
    • 3. 访问集合属性
    • 4. 集合运算符
      • 自定义集合运算符
    • 5. 非对象值处理
      • 访问基本数据类型
      • 访问结构体
    • 6. 属性验证
    • 7. 设值和取值原理
      • 基本的Getter搜索模式
      • 基本的Setter搜索模式
      • NSMutableArray搜索模式
      • 其他搜索模式
    • 8. 异常处理
    • 9. KVC 经典问题
      • 通过KVC修改属性会触发KVO吗?
      • 通过KVC键值编码技术是否会破坏面向对象的编程方法,或者说违背面向对象的编程思想呢?
    • 参考文章


1. 什么是KVC?

KVC的全称是Key-Value Coding,即键值编码,可通过一个key来访问某个属性

KVC是由@interface NSObject(NSKeyValueCoding)非正式协议启用的一种机制,遵循了这个协议的对象除了直接通过存取方法和点语法来访问属性,也可用KVC来间接访问属性,通过字符串来访问一个对象的成员变量或其关联的存取方法

某些情况下,KVC还可以帮助简化代码

2. 访问对象属性

常用方法声明

- (nullable id)valueForKey:(NSString *)key;         // 通过 key 来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath; // 通过 keyPath 来取值- (void)setValue:(nullable id)value forKey:(NSString *)key;         // 通过 key 来赋值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // 通过 keyPath 来赋值

基础使用

以下是Person类的声明:

@interface Person : NSObject {@privateint _age;
}//@property (nonatomic, copy, readonly) NSString* name;
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign)int age;
@property (nonatomic, strong)Dog* dog;@end

对于Person类的实例对象person,可用setter/getter方法对属性进行访问:

//  person.name = @"Jaxon";
//  NSLog(@"%@", person.name);
[person setName: @"Jaxon"];
NSLog(@"%@", [person name]);

也可通过KVC间接访问,通过key设置或访问其值:

[person setValue: @"Jakey" forKey: @"name"];
NSLog(@"%@", [person valueForKey: @"name"]);

甚至将属性设置为 readonly只读或私有变量后,也可以通过KVC进行访问:

@interface Person : NSObject {@privateint _age;
}@property (nonatomic, copy, readonly) NSString* name;
//@property (nonatomic, assign)int age;@end// main函数
[person setValue: @"Jakey" forKey: @"name"];
NSLog(@"%@", [person valueForKey: @"name"]);//私有变量也可通过KVC进行访问
[person setValue: @11 forKey: @"age"];
NSLog(@"%@", [person valueForKey: @"age"]);

请添加图片描述

设置变量时,会自动将设置的字符串转换成我们设置的类型@(11)

[person setValue: @"11" forKey: @"age"];

KeyPath路径

KVC还支持多级访问,比如我们想访问persondog属性的name属性的lastName属性:

[person valueForKeyPath: @"dog.name.lastName"];

keyPath是一个以点分隔开来的字符串,表示了要遍历的对象属性序列。序列中第一个key相对于接收者,而后续的每个key都与前一级key相关联。keyPath对于单个方法调用来深入对象内部结构来说很有用

多值操作

给定一组相对于调用者的key存入数组中,该方法会为数组中的每个key调用valueForKay:方法,将数组中所有key的value以字典的形式返回:

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
/*NSMutableDictionary <NSString *, id>* dict = [NSMutableDictionary dictionary];for (NSString* key in keys) {id value = [self valueForKey: key];if (value) {dict[key] = value;} else {dict[key] = [NSNull null];}}*/

将指定字典中的value设置到调用者的属性中,默认实现是对每一个键值对调用setValue:ForKey:方法,设置时需要将nil替换成NSNull

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
/*NSArray* keyArray = dict.allKeys;for (NSString* key in keyArray) {id value = dict[key];//使用KVC时,key值与属性名一致if (value) {[self setValue: value forKey: key];} else {[self setValue: [NSNull null] forKey: key];}}*/

3. 访问集合属性

获取或设置集合对象时(主要指NSArrayNSSet)仍可通过上述方法对对象进行访问,但是对于操作集合对象内部的元素来说,比如添加或者删除元素,更高效的方式是使用KVC的可变代理方法获取集合代理对象

//  比如我们为person添加一个不可变array属性
NSArray* array = @[@1, @2, @3];
NSArray* tempArray = @[@0, @1, @3];
person.array = array;
[person setValue: tempArray forKey: @"array"];
NSLog(@"%@", [person valueForKey: @"array"]); //  0 1 3NSMutableArray* mutableArray = [person mutableArrayValueForKey: @"array"];
mutableArray[0] = @-1;
mutableArray[2] = @-2;
NSLog(@"%@", [person valueForKey: @"array"]); //  -1 1 -2

这里用到的mutableArrayValueForKey:实例方法会通过传入的key返回对应的属性的一个可变数组的代理对象,KVC提供了三种不同的代理对象访问方法,每种都有Key和KeyPath方法:

  • mutableArrayValueForKey:mutableArrayValueForKeyPath:
  • mutableSetValueForKey:mutableSetValueForKeyPath:
  • mutableOrderedSetValueForKey:mutableOrderedSetValueForKeyPath:

由于KVO的实现原理是在Runtime运行时生成子类并重写setter方法来达到可以通知所有观察者对象的目的,所以对集合对象进行操作不会触发KVO方法
要使用KVO监听集合对象的变化时,需通过KVC的可变代理方法获取集合代理对象,然后对代理对象进行操作,当代理对象的内部发生改变时,就会触发KVO方法:

person.array = @[@3, @7, @9];  //  不会触发KVOObserver* observer = [[Observer alloc] init];
[person addObserver: observer forKeyPath: @"array" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context: NULL];NSMutableArray* mutableArray = [person mutableArrayValueForKey: @"array"];mutableArray[0] = @-1;  //  触发KVOmutableArray[2] = @-2;  //  触发KVONSLog(@"%@", [person valueForKey: @"array"]);

监听方法中打印change参数,可以看到还将改变的索引值展示了出来:
请添加图片描述

4. 集合运算符

KVC的valueForKeyPath:除了可以多级访问外,还可以使用集合运算符来实现一些高效的运算操作

以下是集合运算符的结构:

请添加图片描述

  • Left Key Path:左键路径,指向要进行运算的集合,如果调用者就是集合对象,则Left部分可以省略
  • Collection Operator:集合运算符,是一小部分关键字其后带有一个@符号,该符号指定getter在返回数据之前以某种方式处理数据应执行的操作
  • Right Key Path:右键路径,要进行运算操作的集合中的属性,除了@count运算符外,所有的集合运算符的Right部分不能省略

集合运算符主要分为三类:

  • 聚合运算符:以某种方式合并集合中的对象,并返回右键路径中指定属性的数据类型匹配的一个对象,一般返回NSNumber对象

    • @count:返回操作对象指定属性的个数
    • @max:返回操作对象指定属性的最大值
    • @min:返回操作对象指定属性的最小值
    • @sum:返回操作对象指定属性值之和
    • @avg:返回操作对象指定属性的平均值

    比如给Person类添加一数组属性,数组元素为Book类,而Book类有一price属性:

    @interface Person : NSObject
    // 	...
    @property (nonatomic, strong)NSArray <Book *>* books;  //每个人都有几本书,且每本书的价格不同
    @end//  main函数
    NSMutableArray* bookArray = [NSMutableArray array];
    for (int i = 1; i < 100; ++i) {Book* book = [[Book alloc] init];book.price = i;[bookArray addObject: book];
    }
    personWithBooks.books = bookArray;NSNumber* countOfBooks = [personWithBooks valueForKeyPath: @"books.@count"];
    NSLog(@"%@", countOfBooks);
    NSNumber* maxPrice = [personWithBooks valueForKeyPath: @"books.@max.price"];
    NSNumber* minPrice = [personWithBooks valueForKeyPath: @"books.@min.price"];
    NSNumber* averPrice = [personWithBooks valueForKeyPath: @"books.@avg.price"];
    NSNumber* sumPrice = [personWithBooks valueForKeyPath: @"books.@sum.price"];
    NSLog(@"maxPrice: %@  minPrice: %@  averPrive: %@  sumPrice: %@ = %@", maxPrice, minPrice, averPrice,sumPrice, @(99 * 100 / 2));
    

    来看打印结果:

    请添加图片描述

    备注: @max@min根据右键路径指定的属性在集合中搜索,搜索使用compare:方法进行比较,许多基础类 (如NSNumber类) 中都有定义。因此,右键路径指定的属性必须能响应compare:消息。搜索忽略值为nil的集合项。可以通过重写compare:方法对搜索过程进行控制。

  • 数组操作符:根据运算符的条件,将符合条件的对象以一个NSArray实例返回

    • @unionOfObjects:返回操作对象指定属性的集合
    • @distinctUnionOfObjects: 返回操作对象指定属性的集合–去重
    NSArray* priceArr = @[@5, @4, @6, @4, @4, @75, @245, @35, @6];
    NSMutableArray* bookArr = [NSMutableArray array];
    for (int i = 0; i < priceArr.count; ++i) {Book* book = [[Book alloc] init];book.price = [priceArr[i] doubleValue];[bookArr addObject: book];
    }//  获取集合中所有元素的price
    NSArray* returnArr = [bookArr valueForKeyPath: @"@unionOfObjects.price"];
    NSLog(@"%@", returnArr);
    //  获取集合中所有元素不同price
    NSArray* returnDistinctArr = [bookArr valueForKeyPath: @"@distinctUnionOfObjects.price"];
    NSLog(@"%@", returnDistinctArr);
    

    运行结果:

    请添加图片描述

    注意: 在使用数组运算符时,如果有任何操作的对象为nil,则valueFoeKeyPath:方法将引发异常

  • 嵌套运算符:处理集合对象中嵌套其他集合对象的情况,并根据运算符返回一个NSArrayNSSet实例

    • @unionOfArrays:读取集合中的每个集合中的每个元素的右键路径指定的属性,放在一个NSArray实例中并返回

    • @distinctUnionOfArrays:读取集合中的每个集合中的每个元素的右键路径指定的属性,放在一个NSArray实例中,将数组进行去重后返回

    • @distinctUnionOfSets:读取集合中的每个集合中的每个元素的右键路径指定的属性,放在一个NSSet实例中,去重后返回

      • 在使用嵌套运算符时,valueForKeyPath:内部会根据运算符创建一个NSMutableArray或NSMutableSet对象,将集合中的array和set添加进去再进行操作。如果集合中有非集合元素,会导致Crash
      • 使用unionOfArrays或distinctUnionOfArrays运算符,消息接收者应该是arrayOfArrays类型,即NSArray< NSArray* >* arrayOfArrays;;使用distinctUnionOfSets运算符,消息接收者应该是setOfSets或者arrayOfSets类型,否则会发生异常
      • 在使用嵌套运算符时,如果有任何操作的对象为nil, 则valueForKeyPath:方法将引发异常
  • 如果集合中的对象都是NSNumber,右键路径可以用self

    NSArray* priceArr = @[@5, @4, @6, @4, @4, @75, @245, @35, @6];
    NSNumber* sum = [priceArr valueForKeyPath: @"@sum.self"];
    NSLog(@"%@", sum);
    

自定义集合运算符

上面介绍了KVC为我们提供的集合运算符,我们能不能自定义呢?

我们使用Runtime中的函数打印NSArray类的方法列表:

u_int count;  //  unsigned int
Method* methods = class_copyMethodList([NSArray class], &count);
for (int i = 0; i < count; ++i) {Method method = methods[i];SEL sel = method_getName(method);NSLog(@"%d --- %@", i, NSStringFromSelector(sel));
}free(methods);

方法很多,其中也发现了KVC提供的集合运算符都有对应的方法_<operatorKey>ForKeyPath:

请添加图片描述
请添加图片描述

再来看一下NSSet类支持哪些集合运算符:
请添加图片描述
可见NSSet类不支持@unionOfObjects@unionOfArrays运算符,如果使用了就会抛出异常NSInvalidArgumentException并导致程序崩溃,reason: [<__NSSetI 0x6000017a12f0> valueForKeyPath:]: this class does not implement the unionOfArrays operation.不支持该运算符


NSArray类虽然支持@distinctUnionOfSets运算符,但其必须是arrayOfSets类型,即NSArray< NSSet* >* arrayOfSets;。因为_distinctUnionOfSetsForKeyPath方法中会创建一个NSMutableSet实例,并调用unionSet:方法将集合中的set的元素添加进去再进行操作。如果是arrayOfArrays类型就会抛出异常NSInvalidArgumentException并导致程序崩溃,reason: '*** -[NSMutableSet unionSet:]: set argument is not an NSSet’即集合中有非NSSet元素。

我们模仿以上方法使用分类给NSArray动态添加一个实现集合运算符@medium的方法:

//  4 4 5 35 75 245
NSArray* array = @[@5, @4, @4, @75, @245, @35];
NSNumber* num = [array valueForKeyPath: @"@medium.self"];
NSLog(@"%@", num);

此方法用于获取数组中的中位数,打印结果为:20

5. 非对象值处理

KVC还支持对非对象属性进行访问

非对象类型分为两类:基本数据类型(标量)、结构体

  • 当非对象类型作为参数传入valueForKey:时,会使用该值初始化一个NSNumber(用于基础数据类型)或NSValue(用于结构体)实例返回
  • 当非对象类型作为参数传入setValueForKey:时,会发送一条<type>Value消息给value对象以提取基础数据,而后赋值给key

注意:

  • 因为Swift中的所有属性都是对象,所以这里仅适用于Objective-C属性。
  • 当进行赋值如setValue:forKey:时,如果key的数据类型是非对象类型,则value就禁止传nil"。否则会调用setNilValueForKey:方法,该方法的默认实现抛出异常NSInvalidArgumentException,并导致程序Crash。

访问基本数据类型

请添加图片描述

访问结构体

请添加图片描述

除上述结构体外,对于自定义的结构体,也需要进行包装成NSValue

typedef struct {float x, y, z;
} ThreeFloats;@interface Man : NSObject
@property (nonatomic)ThreeFloats threeFloats;
@end//  取值
NSValue* result = [myClass valueForKey: @"threeFloats"];//  赋值
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes: &floats objCType: @encode(ThreeFloats)];
[man setValue: value forKeyPath: @"threeFloats"];

6. 属性验证

KVC支持属性验证,可以在使用KVC赋值前验证能否为这个key赋值指定value

实现方法:

- (BOOL)validateValue:(id  _Nullable *)value forKey:(NSString *)key error:(NSError * _Nullable *)error;- (BOOL)validateValue:(inout id  _Nullable *)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError * _Nullable *)outError;

这个验证方法的默认实现是查看 消息接收者(或keyPath中最后的对象) 中是否实现了遵循命名规则为validate<Key>:error:的方法,如果没有,验证默认成功,返回YES。而由于validate<Key>:error:通过引用接收值和错误参数,所以会有以下三种结果:

  • 验证成功,返回YES,对属性值不做任何改动
  • 验证失败,返回NO,但对属性值不做改动,如果调用者提供了NSError的话,就把错误引用设置为指示错误原因的NSError对象
  • 验证失败,返回 YES,创建一个新的,有效的属性值作为替代。在返回之前,该方法将值引用修改为指向新值对象。 进行修改时,即使值对象是可变的,该方法也总是创建一个新对象,而不是修改旧对象

可以在消息接收者类中实现validate<Key>:error:的方法来自定义逻辑返回YESNO,在Person类中实现validateName:error:方法,验证给name赋的值是不是Jaxon

Person *person = [[Person alloc] init];
NSString *value = @"Rose";
NSString *key = @"name";
NSError  *error;
BOOL result = [person validateValue: &value forKey: key error: &error];if (error) {NSLog(@"error = %@", error);return;
}
NSLog(@"%d",result);// Person.m
// 按照上面的逻辑,会根据key,即@"name",调用一下方法
- (BOOL)validateName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError {NSString *name = *value;BOOL result = NO;if ([name isEqualToString:@"Jaxon"]) {result = YES;}return result;
}
// 打印:0

KVC或其默认实现均未定义任何机制来自动的执行属性验证,也就是说需要在适合你的应用的时候自己提供属性验证方法
某些其他 Cocoa 技术在某些情况下会自动执行验证。 例如,保存 managed object context 时,Core Data会自动执行验证。另外,在 macOS 中,Cocoa Binding允许你指定验证应自动进行

7. 设值和取值原理

基本的Getter搜索模式

valueForKey:方法会在调用者传入key之后在对象中按下列步骤进行模式搜索:

  1. 按照get<Key><key>is<Key>_<key>的顺序查找对象中是否有对应的方法
    如果找到就调用取值✅并执行5.,否则执行2.
  2. 按照countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:顺序查找方法(对应于NSArray类定义的原始方法)
    • 如果找到上述第一个方法,再找到其他两个中的至少一个,则创建一个响应所有NSArray方法的代理集合对象,并返回该对象(即要么countOf<Key> + objectIn<Key>AtIndex:、要么countOf<Key> + <key>AtIndexes:或者countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:三种组合)✅
      • 代理对象随后将其接收到的任何NSArray消息转换为countOf<Key>objectIn<Key>AtIndex:<Key>AtIndexes:消息的组合,并将其发送给KVC调用方。如果原始对象还实现了一个名为get<Key>:range:的可选方法,则代理对象也会在适当时使用该方法
      • 当KVC调用方与代理对象一起工作时,允许底层属性的行为如同NSArray一样,即使它不是NSArray
    • 如果没有找到,跳转至3.
  3. 按照countOf<Key>enumeratorOf<Key>memberOf<Key>:顺序查找方法(对应于NSSet类定义的原始方法)
    • 如果三个方法都找到,则创建一个响应所有NSSet方法的代理集合对象,并返回该对象✅
      • 代理对象随后将其接收到的任何NSSet消息转换为countOf<Key>enumeratorOf<Key>memberOf<Key>:消息的组合,并将其发送给KVC调用方
      • 当KVC调用方与代理对象一起工作时,允许底层属性的行为如同NSSet一样,即使它不是NSSet
    • 如果没有找到,跳转至4.
  4. 查看消息接收者类的+ (BOOL)accessInstanceVariablesDirectly;方法返回值
    • 如果返回YES,就按照_<key>_is<Key><key>is<Key>顺序查找成员变量,如果找到就直接取值✅并执行5.,否则执行6.
    • 如果返回NO,跳转到6.
  5. 判断取出的属性值✅:
    • 如果属性值是对象,直接返回
    • 如果属性值不是对象,但是可以转化为NSNumber类型,则将属性值转化为NSNumber类型返回
    • 如果属性值不是对象,也不能转化为NSNumber类型,则将属性值转化为NSValue类型返回
  6. 调用valueForUndefinedKey:方法,该方法抛出异常NSUnknownKeyException,并导致程序Crash,这是默认实现,我们可以重写该方法根据特定key做一些特殊处理

请添加图片描述

基本的Setter搜索模式

setValue:forKey: 方法默认实现会在调用者传入keyvalue(如果是非对象类型,则指的是解包之后的值) 之后会在对象中按下列的步骤进行模式搜索:

  1. 按照set<Key>:_set<Key>:顺序查找方法
    如果找到就将value传进(根据需要进行转换)方法里并调用✅,否则执行2.
  2. 查看消息接收者类的+ (BOOL)accessInstanceVariablesDirectly;方法返回值(默认返回YES)
    • 如果返回YES,就按照_<key>_is<Key><key>is<Key>顺序查找成员变量(同Getter搜索模式),如果找到就将value传进方法✅并执行,否则执行3.
    • 如果返回NO,跳转到3.
  3. 调用setValueForUndefinedKey:方法,该方法抛出异常NSUnknownKeyException,并导致程序Crash,这是默认实现,我们可以重写该方法根据特定key做一些特殊处理

请添加图片描述

NSMutableArray搜索模式

mutableArrayValueForKey:方法的默认实现,给定一个key作为输入参数,返回属性名为key的集合的代理对象(NSMutableArray),在消息接收者中操作,执行以下搜索步骤:

  1. 查找一对方法insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:(相当于NSMutableArray的原始方法insertObject:atIndex:removeObjectAtIndex:或者insert<Key>:atIndexes:remove<Key>AtIndexes:(相当于NSMutableArray的原始方法insertObjects:atIndexes:removeObjectsAtIndexes:

    • 如果至少实现了一个insert方法和一个remove方法,则返回一个代理对象✅,来响应发送给NSMutableArray的消息,通过发送insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:insert<Key>:atIndexes:remove<Key>AtIndexes:组合消息给KVC调用方,否则执行2.

      该代理对象类型为NSKeyValueFastMutableArray2,继承链为NSKeyValueFastMutableArray2->NSKeyValueFastMutableArray->NSKeyValueMutableArray->NSMutableArray

    • 如果我们也实现了一个可选的replace object方法,如replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:,代理对象在适当的情况下也会使用它们,以获得最佳性能

  2. 查找set<Key>:方法
    如果找到,就会向KVC调用方发送一个set<Key>:消息,来返回一个响应NSMutableArray消息的代理对象✅,否则执行3.

    该代理对象类型为NSKeyValueSlowMutableArray,继承链为NSKeyValueSlowMutableArray->NSKeyValueMutableArray->NSMutableArray

    注意:此步骤中描述的机制比上一步的效率低得多,因为它可能重复创建新的集合对象,而不是修改现有的集合对象。因此,在设计自己的键值编码兼容对象时,通常应该避免使用它。给代理对象发送NSMutableArray消息都会调用set<Key>:方法。即对代理对象进行修改,都是调用set<Key>:重新赋值,所以效率会低很多

  3. 查看消息接收者类的+ (BOOL)accessInstanceVariablesDirectly;方法返回值(默认返回YES)

    • 如果返回YES,就按照_<key><key>顺序查找成员变量。如果找到就返回一个代理对象✅,该代理对象将接收所有NSMutableArray消息,通常是NSMutableArray或其子类,否则执行4.
    • 如果返回NO,执行4.
  4. 返回一个可变的集合代理对象。当它接收到NSMutableArray消息时,发送一个valueForUndefinedKey:消息给KVC调用方,该方法抛出异常NSUnknownKeyException,并导致程序Crash。这是默认实现,我们可以重写该方法根据特定key做一些特殊处理

其他搜索模式

除以上三种搜索模式,KVC还有NSMutableSetNSMutableOrderedSet两种搜索模式,它们的搜索规则和NSMutableArray相同,只是搜索和调用的方法不同

8. 异常处理

根据上述KVC搜索规则,当没有搜索到对应的key或者keyPath相关方法或者变量时,会调用以下方法:

- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

这两个方法的默认实现是抛出异常NSUnknownKeyException,并导致程序Crash:

在这里插入图片描述

我们可以通过重写这两个方法来处理异常:

  1. 当我们用setValue:ForKey:进行赋值时,如key(属性)的数据类型是非对象类型,则value就禁止传入nil。否则就会调用setNilValueForKey:方法,该方法的默认实现是抛出异常NSInvalidArgumentException,并导致程序Crash。我们可以重写这个方法来处理异常:

    - (void)setNilValueForKey:(NSString *)key {if ([key isEqualToString: @"hidden"]) {[self setValue: @(NO) forKey: @”hidden”];} else {[super setNilValueForKey: key];}
    }
    
  2. 比如从服务器上给我回传了一个人Person,而这个人往往都是有编号的id,得到的字段往往是小写的“id”。意味着我们Model中的属性名也需要是小写的id,但在OC中id是关键字,不能用作属性名,那我们无法接收了吗。解决方案是利用KVC将id转换成大写的ID

    //  由于实际是没有名为id的属性的,所以会调用以下方法
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {if ([key isEqualToString: @"id"]) {self.ID = value;}
    }- (id)valueForUndefinedKey:(NSString *)key {return self.ID;
    }
    

    这样重写可帮助我们躲避一些关键字(如id):

    [person setValue: @"1001" forKey: @"id"];
    NSLog(@"%@", [person valueForKey: @"id"]);
    

    一般服务器给我们返回的是一个字典(JSON数据),需将其转化成Person模型:

    NSDictionary* dict = @{@"name" : @"Jaxon",@"age" : @"20",@"id" : @"1001"};
    //dict -> model 字典转换模型
    Person* p = [[Person alloc] initWithDictionary3: dict];
    NSLog(@"name = %@ age = %d id = %@", p.name, p.age, p.ID);//model -> dict 模型转换字典
    NSArray* array = @[@"name", @"age", @"id"];
    NSDictionary* dict1 = [p dictionaryWithValuesForKeys: array];
    NSLog(@"%@", dict1);
    

以下是转换模型方法的实现:

- (instancetype)initWithDictionary: (NSDictionary *)dict {self = [super init];if (self) {_age = [dict[@"age"] intValue];_name = dict[@"name"];_ID = dict[@"id"];}return self;
}//Person的信息太多的话,一一赋值就显得冗杂
- (instancetype)initWithDictionary2: (NSDictionary *)dict {self = [super init];if (self) {NSArray* keyArray = dict.allKeys;for (NSString* key in keyArray) {id value = dict[key];//使用KVC时,key值与属性名一致if (value) {[self setValue: value forKey: key];} else {[self setValue: [NSNull null] forKey: key];}}}return self;
}- (instancetype)initWithDictionary3: (NSDictionary *)dict {self = [super init];if (self) {[self setValuesForKeysWithDictionary: dict];}return self;
}

9. KVC 经典问题

通过KVC修改属性会触发KVO吗?

按照KVC的搜索模式,会调用到属性的setter方法,所以若是该属性被监听,值被更改后一定会出发监听方法
值得注意的是,通过 KVC,实际就算直接修改成员变量,也会触发 KVC 监听方法

通过KVC键值编码技术是否会破坏面向对象的编程方法,或者说违背面向对象的编程思想呢?

valueForKey:和setValue:forKey:这里面的key是没有任何限制的,当我们知道一个类或实例它内部的私有变量名称的情况下,我们在外界可以通过已知的key来对它的私有变量进行访问或者赋值的操作,从这个角度来讲KVC键值编码技术会违背面向对象的编程思想

参考文章

KVC - Accessor Search Patterns
Key-Value Coding Programming Guide(苹果官方文档)

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/26529.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows本地使用SSH连接VM虚拟机

WIN10 VM17.5 Ubuntu:20.04 1.网路设置 1)选择编辑->更改设置 配置完成 2.修改了服务器文件&#xff0c;修改sshd配置&#xff0c;在此文件下/etc/ssh/sshd_config&#xff0c;以下为比较重要的配置 PasswordAuthentication yes PermitRootLogin yes PubkeyAuthenticat…

[ue5]建模场景学习笔记(6)——必修内容可交互的地形,交互沙(4)

1.需求分析&#xff1a; 现在我们已经有了可以在世界内近于无限的跑动痕迹&#xff0c;现在需要对痕迹进行细化&#xff0c;包括例如当人物跳起时便不再绘制痕迹&#xff0c;以及痕迹应该存在深浅&#xff0c;应该由两只脚分别绘制&#xff0c;同时也应该对地面材质进行进一步处…

Vue基本使用-02

上节我们讲了什么是mvvm模型&#xff0c;以及我们vue的一些常用指令&#xff0c;今天给大家讲一下vue的基本使用&#xff0c;在将之前我们需要重点讲解我们的一个指令&#xff0c;v-model指令 v-model v-model 可以在组件上使用以实现双向绑定,什么是双向绑定呢?意思就是当我们…

景芯SoC A72的时钟树分析

innovus的ctslog中的Clock DAG信息可以报出来CTS主要运行步骤的关键信息&#xff0c;比如clustering&#xff0c;balancing做完后的clock tree的长度&#xff0c;clock tree上所用的buffer、inverter&#xff0c;icg cell数量&#xff0c;clock skew等信息。我们以景芯SoC A72 …

Centos离线安装Python3

目录 1.准备工作 2.解压python压缩包 3.编译 4.安装、更改环境变量 5.建立pip连接 使用的是Centos7服务器&#xff0c;Py版本是py3.9.0 1.准备工作 首先确保服务器中存在相关的编译器&#xff0c;例如GCC&#xff1b;这里不做过多叙述&#xff0c;需要者前往&#xff1a…

空间搜索geohash概述;redis的geo命令

概述 通常在一些2C业务场景中会根据用户的位置来搜索一些内容。通常提供位置搜索的都是直接通过redis/mongodb/es等中间件实现的。 但是这些中间件又是怎么实现位置搜索的呢&#xff1b; 查了一番资料&#xff0c;发现背后一个公共的算法Geohash。 搜索的时候可以根据距离对…

Vitis HLS 学习笔记--移除内存分配malloc

目录 1. 简介 2. 示例解析 2.1 源码解释 2.2 malloc 分析 2.3 替代方案分析 3. 总结 1. 简介 Vitis HLS 也不支持动态创建或删除 C/C 对象&#xff08;用于综合&#xff09;。 本文探究如何在C/C代码中避免使用显式的malloc函数来分配内存。在硬件设计和FPGA开发中&…

Xcode无法使用设备:Failed to prepare the device for development

问题&#xff1a; Xcode无法使用设备开发&#xff0c;失败报错如下&#xff1a; Failed to prepare the device for development. This operation can fail if the version of the OS on the device is incompatible with the installed version of Xcode. You may also need…

致 粉丝de信

致 粉丝 -本文呢看不下去别看&#xff0c;但是学业是真的重要&#xff08;平常有信奥&#x1f62b;&#xff09;&#xff0c;电脑没收……更新可能得到暑假&#xff0c; 同学&#xff1a;小没苯agoe &#xff08;aaa&#xff0c;学霸&#xff01;&#xff01;&#xff01;&…

GGML简单介绍

GGML是一个用于机器学习的张量库&#xff0c;可以在商用硬件上实现大型模型和高性能。它被llama.cpp和whisper.cpp使用 C语言编写 16位浮点支撑 整数量化支持(如4位、5位、8位) 自动分化 内置优化算法(如ADAM, L-BFGS) 针对苹果芯片进行优化 在x86架构上利用AVX / AVX2的内在特…

A股上市公司MSCI ESG评级面板数据(2017-2023)

数据简介&#xff1a;MSCI ESG&#xff08;Environmental, Social, and Governance&#xff09;评级是由 MSCI Inc. 提供的一项服务&#xff0c;旨在评估公司在环境、社会和治理方面的表现。MSCI 是一家全球领先的投资研究和指数提供商&#xff0c;其 ESG 评级被广泛用于评估企…

C++ Primer 第五版 第16章 模板与泛型编程

模板是C中泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者说公式。当使用一个vector这样的泛型类型&#xff0c;或者find这样的泛型函数时&#xff0c;我们提供足够的信息&#xff0c;将蓝图转换为特定的类或函数。这种转换发生在编译时。 一、定义模板 1. 函数模板…

windows11 建立批处理bat文件来删除指定目录下的所有隐藏的文件。

今天在导入项目的时候发现之前项目中的文件夹中有很多隐藏的临时文件&#xff0c;这个文件应该是版本控制产生的&#xff0c;导致导入后文件夹上有X&#xff0c;然后里面文件是一个没有错。 我们来建立一个bat来&#xff0c;进行批量删除隐藏文件就可以了&#xff1a; echo o…

纯C实现的ymodem库,无额外依赖

本文目录 1、引言2、理论2.1 YMODEM协议的主要特点2.2 YMODEM的工作原理 3、代码3.1 main.cpp3.2 ymodem.c 3.3 ymodem.h 4、验证4.1 ymodem发送4.2 ymodem接收 5、移植说明 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我…

源码解析:从零解读SAM(Segment Anything Model)大模型!

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

TF-IDF算法教程

前言 TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种常用的文本分析技术&#xff0c;广泛应用于信息检索和文本挖掘领域。它是一种统计方法&#xff0c;用于评估一个词语在一个文档中的重要程度。TF-IDF的核心思想是&#xff1a;如果一个词语…

VS2019+QT5.15调用动态库dll带有命名空间

VS2019QT5.15调用动态库dll带有命名空间 vs创建动态库 参考&#xff1a; QT调用vs2019生成的c动态库-CSDN博客 demo的dll头文件&#xff1a; // 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 DLL3_EXPORTS // 符号…

四十一、openlayers官网示例Flight Animation解析——在地图上绘制飞机航线、牵引线效果、动态动画

官网demo地址&#xff1a; Flight Animation 这篇介绍了如何实现飞机航线动画。 首先加载一张底图&#xff0c;定义一个样式。 const tileLayer new TileLayer({source: new StadiaMaps({layer: "outdoors",}),});const map new Map({layers: [tileLayer],target…

【实例分享】访问后端服务超时,银河麒麟服务器操作系统分析及处理建议

1.服务器环境以及配置 【机型】 处理器&#xff1a; Intel 32核 内存&#xff1a; 128G 整机类型/架构&#xff1a; x86_64虚拟机 【内核版本】 4.19.90-25.22.v2101.kylin.x86_64 【OS镜像版本】 kylin server V10 SP2 【第三方软件】 开阳k8s 2.问题现象描述 …

API工具--Apifox和Postman对比(区别)

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…