【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 {
    @private
    int _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 {
    @private
    int _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 3
   
NSMutableArray* 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];  //  不会触发KVO

Observer* observer = [[Observer alloc] init];
[person addObserver: observer forKeyPath: @"array" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context: NULL];

NSMutableArray* mutableArray = [person mutableArrayValueForKey: @"array"];
    mutableArray[0] = @-1;  //  触发KVO
    mutableArray[2] = @-2;  //  触发KVO
    NSLog(@"%@", [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(苹果官方文档)

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

maven引入依赖时莫名报错

一般跟依赖的版本无关&#xff0c;会报出 Cannot resolve xxx 的错误。 这种情况下去IDEA的setting中找maven的仓库位置 在仓库中顺着包路径下寻找&#xff0c;可能会找到.lastUpdated 的文件&#xff0c;这样的文件一般是下载失败了&#xff0c;而且在一段时间内不再下载&…

docker 部署nginx多级子域名(三级四级...)映射不同web项目,访问不同路径地址

一、背景 只有一台服务器&#xff0c;一个顶级域名&#xff0c;现在需要根据不同子域名访问不同web项目&#xff0c;比如 # 管理后台 cms.biacu.com# 客户端h5 h5.biacu.com# 四级域名 h5.s.biacu.com同时&#xff0c;不同web项目放在不同位置 二、 1、在云服务器上&#x…

组织创新|AI赋能敏捷实践,助力企业敏捷转型

在工业5.0时代&#xff0c;随着项目变得越来越复杂&#xff0c;对效率的需求也在增长&#xff0c;致力于敏捷转型的组织正在寻求创新的解决方案来应对常见的挑战&#xff1a;工作量不平衡、低效的任务分配和知识孤岛等等。对此&#xff0c;AI等尖端技术的潜力可以帮助实现更高效…

2024 年十大关键渗透测试发现:您需要了解的内容

编辑信息技术 (IT) 专业人员在坏人之前发现公司弱点的最有效方法之一就是渗透测试。通过模拟现实世界的网络攻击&#xff0c;渗透测试&#xff08;有时称为 pentests&#xff09;可以提供有关组织安全状况的宝贵见解&#xff0c;揭示可能导致数据泄露或其他安全事件的弱点。 自…

通信设备的网卡

一、网卡的作用 将计算机或者路由器连接到传输介质上的接口&#xff0c;传输介质可以是有线也可以是无线的。 &#xff08;1&#xff09;计算机的网卡 现在的计算机大多有两个网卡&#xff0c;一个是有线网卡一个无线网卡&#xff0c;比如以我们的台式电脑为例 台式电脑千兆网…

【智能算法应用】基于混合粒子群-蚁群算法的多机器人多点送餐路径规划问题

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】粒子群算法&#xff08;PSO&#xff09;原理及实现 配餐顺序&#xff1a; 采用混合粒子群算法 || 路径规划&#xff1a; 采用蚁群算法 2.数学模型 餐厅送餐多机器人多点配送路径规划&…

基于注意力的MIL

多实例学习是监督学习的一种变体&#xff0c;其中单个类标签被分配给一袋实例。在本文中&#xff0c;作者将MIL问题描述为学习bag标签的伯努利分布&#xff0c;其中bag标签概率通过神经网络完全参数化。此外&#xff0c;提出了一种基于神经网络的置换不变聚合算子&#xff0c;该…

最实用的AI软件开发工具CodeFlying测评

就在上个月&#xff0c;OpenAI宣布GPT-4o支持免费试用&#xff0c;调用API价格降到5美元/百万token。 谷歌在得到消息后立马将Gemini 1.5 的价格下降到0.35美元/百万token。 Anthropic的API价格&#xff0c;直接干到了0.25美元/百万token。 国外尚且如此&#xff0c;那么国内…

6.13长难句打卡

Hard times may hold you down at what usually seems like the most inopportune time, but you should remember that they won’t last forever. 艰难时刻可能会在你最不顺心的时刻让你低迷&#xff0c;但请相信&#xff0c;它们不会永远持续下去。

数据结构逻辑

一&#xff1a;逻辑关系 1、线性关系 2&#xff1a;树型关系 3&#xff1a;图像关系 二&#xff1a;存储关系 1&#xff1a;顺序存储、数据在存储中会开辟一块连续的空间进行存储。一般使用数组来存储数据 2&#xff1a;链式存储、数据在内存中不需要开辟连续的空间进行存储 3…

冯喜运:6.13美盘外汇黄金原油趋势分析及操作策略

【黄金消息面分析】&#xff1a;美国5月生产者价格指数&#xff08;PPI&#xff09;的意外下降&#xff0c;为市场带来了通胀可能见顶的积极信号。与此同时&#xff0c;初请失业金人数的上升&#xff0c;为劳动力市场的现状增添了一层不确定性。美国劳工统计局公布的数据显示&a…

供应链与直播的“低价”探戈

文丨郭梦仪 10个月前&#xff0c;梅姐&#xff08;化名&#xff09;开启了人生中第一次直播带货&#xff0c;10年的工作经验在镜头前完全“失灵”&#xff0c;个位数观看量更让她一度怀疑人生。 而今年4月&#xff0c;梅姐面朝西沙群岛的湛蓝海域&#xff0c;对着400万人侃侃而…

Elasticsearch 第二期:倒排索引,分析,映射

前言 正像前面所说&#xff0c;ES真正强大之处在于可以从无规律的数据中找出有意义的信息——从“大数据”到“大信息”。这也是Elasticsearch一开始就将自己定位为搜索引擎&#xff0c;而不是数据存储的一个原因。因此用这一篇文字记录ES搜索的过程。 关于ES搜索计划分两篇或…

细说MCU串口函数及使用printf函数实现串口发送数据的方法

目录 1、硬件及工程 2、串口相关的库函数 &#xff08;1&#xff09;串口中断服务函数&#xff1a; &#xff08;2&#xff09;串口接收回调函数&#xff1a; &#xff08;3&#xff09;串口接收中断配置函数&#xff1a; &#xff08;4&#xff09;非中断发送&#xff…

python:faces swap

# encoding: utf-8 # 版权所有 2024 ©涂聚文有限公司 # 许可信息查看&#xff1a; 两个头像图片之间换脸 # 描述&#xff1a; https://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image?answertabvotes # Author : geovindu,Geovin Du 涂聚文. #…

低成本,高性能:10 万美元实现Llama2-7B级性能

高性能的语言模型如Llama2-7B已经成为推动自然语言处理技术进步的重要力量。然而&#xff0c;这些模型往往需要昂贵的计算资源和庞大的研发投入&#xff0c;使得许多研究团队和小型企业望而却步。现在&#xff0c;JetMoE架构以其创新的设计和优化策略&#xff0c;不仅成功地在只…

PC微信逆向) 定位微信浏览器打开链接的call

首发地址: https://mp.weixin.qq.com/s/Nik8fBF3hxH5FPMGNx3JFw 前言 最近想写一个免费的微信公众号自动采集的工具&#xff0c;我看公众号文章下载需求还挺多的。搜了下github&#xff0c;免费的工具思路大多都是使用浏览器打开公众号主页获取到需要的请求参数&#xff0c;例…

云化XR什么意思,Cloud XR是否有前景?

云化XR&#xff08;CloudXR&#xff09;什么意思&#xff1f; 云化XR&#xff08;CloudXR&#xff09;是一种基于云计算技术的扩展现实&#xff08;XR&#xff09;应用方式&#xff0c;将XR体验从本地设备转移到云端进行处理和交付。它通过将计算和渲染任务放置在云端服务器上…

Linux基础IO【II】真的很详细

目录 一.文件描述符 1.重新理解文件 1.推论 2.证明 2.理解文件描述符 1.文件描述符的分配规则 3.如何理解文件操作的本质&#xff1f; 4.输入重定向和输出重定向 1.原理 2.代码实现重定向 3.dup函数 ​编辑 4.命令行中实现重定向 二.关于缓冲区 1.现象 …

Modbus转Profinet网关的IP地址怎么设置

在工业自动化领域&#xff0c;Modbus和Profinet是两种常用的通信协议&#xff0c;而网关可以实现不同协议之间的转换&#xff0c;使得不同设备能够互相通信。本文将详细介绍如何设置Modbus转Profinet网关&#xff08;XD-MDPN100&#xff09;的IP地址&#xff0c;让您轻松实现设…