【iOS】KVC

打印 上一主题 下一主题

主题 809|帖子 809|积分 2427

目录
KVC的界说
容器类中KVC的实现
KVC设值
KVC取值
KVC使用KeyPath
KVC处理异常
KVC处理设值nil异常
KVC处理UndefinedKey异常
KVC处理数值和布局体范例属性
KVC键值验证
KVC处理集合
简单集合运算符
对象运算符
KVC处理字典
KVC应用
动态地取值和设值
用KVC来访问和修改私有变量
Model和字典转换
修改一些控件的内下属性
操作集合
用KVC实现高阶消息传递
实现KVO


KVC的界说

界说:KVC(Key-value coding)键值编码,是指iOS开发中,答应开发者通过key名直接访问对象的属性,或者给对象的属性赋值,而不必要调用明白的存取方法。使用KVC键值编码的好处就是可以在运行时动态地访问和修改对象的属性,而不是在编译时确定。
留意:在实现了访问器方法的类中,使用点语法和KVC访问对象差别不大,二者可以混用。但是在没有访问器方法的类中,无法使用点语法,这时KVC就有上风了。
KVC的界说都是对NSObject的扩展来实现的,OC语言中有一个显示的NSKeyValueCoding类别名,所以所有继承了NSObject的范例,都能使用KVC(一些没有继承NSObject的纯Swift类和布局体不支持KVC),下面是KVC最重要的四个方法:
  1. - (nullable id)valueForKey:(NSString *)key;//直接通过Key来取值
  2. - (void)setValue:(nullable id)value forKey:(NSString *)key;//通过Key来取值
  3. - (nullable id)valueForKeyPath:(NSString *)keyPath;//通过KeyPath来取值
  4. - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;//通过KeyPath来设值
复制代码
此外,在NSKeyValueCoding这个类别中,另有一些其他的方法:
  1. + (BOOL)accessInstanceVariablesDirectly;
  2. //默认返回YES,表示如果没有找到Set<Key>这个存取方法的话,会按照_key, _iskey, key, iskey的顺序搜索成员,重写方法设置成返回NO就不会这样搜索,也就是说如果没找到Set<Key>这个方法,就不会再继续搜索
  3. - (BOOL)validateValue:(inout id __nullable * __)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
  4. //KVC提供属性值正确性验证的API,可以用来检查set的值是否正确,为不正确的值做一个替换值或者拒绝设置新值并返回错误原因(通过outError参数来返回错误原因)
  5. - (NSMutableArray *)mutableArrayValueForKey:(NSString*)key;
  6. //集合操作的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回这个NSMutableArray,在类别里面还有类似的API来对set操作
  7. - (nullable id)valueForUndefinedKey:(NSString *)key;
  8. //如果key不存在,且KVC无法搜索到任何和key有关的字段或属性,则会调用这个方法,默认抛出异常
  9. - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
  10. //这个方法也是针对key不存在的情况进行的操作,不过这个方法是在设值时操作
  11. - (void)setNilValueForKey:(NSString *)key;
  12. //如果在SetValue时,为方法传入的参数是nil(也就是说给Value传一个nil),就会调用这个方法
  13. - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
  14. //输入一组key,返回该组key对应的value,再转回字典返回
复制代码
关于validateValue:ioValue forKey:in**Key errorutError 这个方法,这里给出一个使用的示例,可以使用这个方法来对设置的属性值举行验证,这里以验证是否为空为例
首先在自界说的类中实现这个方法:
  1. - (BOOL)validateValue:(inout id __nullable *)ioValue forKey:(NSString *)inKey error:(out NSError **)outError {
  2.    // 检查键名
  3.    if ([inKey isEqualToString:@"name"]) {
  4.        NSString *name = (NSString *)*ioValue;
  5.        
  6.        // 验证值是否为空
  7.        if (name.length == 0) {
  8.            if (outError) {
  9.                *outError = [NSError errorWithDomain:@"MyDomain"
  10.                                                  code:1001
  11.                                              userInfo:@{NSLocalizedDescriptionKey: @"Name cannot be empty."}];
  12.            }
  13.            return NO; // 验证失败
  14.        }
  15.    }
  16.    
  17.    return YES; // 验证成功
  18. }
复制代码
然后在设置属性值时,调用此方法举行验证:
  1. NSError *error = nil;
  2. NSString *name = @"";
  3. if (![self validateValue:&name forKey:@"name" error:&error]) {
  4.    NSLog(@"Validation failed: %@", error.localizedDescription);
  5. } else {
  6.    self.name = name; // 设置有效的值
  7. }
复制代码
容器类中KVC的实现

在自界说容器类时,KVC有特别的实现
苹果原生的容器类比如NSArray或者NSSet等,都已经实现了对应的方法。
对于有序集合:
  1. -countOf<Key> //必须实现,对应NSArray的方法count:
  2. -objectIn<Key>AtIndex: -<Key>AtIndexes: //这两个方法必须实现一个,对应NSArray的方法objectAtIndex: 和objectsAtIndexes:
  3. -get<Key>:range: //不是必须实现,对应NSArray方法getObjects:range:
  4. -insertObject:in<Key>AtIndex: -insert<Key>:atIndexes: //两个必须实现一个,类似NSMutableArray的方法insertObject: atIndex: 和insertObjects: atIndexes:
  5. -replaceObejctIn<Key>AtIndex: withObject:
  6. -replace<Key>AtIndexes: with<Key>://可选的,是一种替换元素的方法
复制代码
对于无序集合:
  1. -countOf<Key> //必须实现,对应NSSet
  2. -objectIn<Key>AtIndex: //由于NSSet是无序的,此方法可以选择性实现
  3. -<Key>AtIndexes: //和上一个方法一样,选择性实现
  4. -get<Key>: range: //不是必须实现,对应NSArray的getObjects: range:方法,无序一般不适用
  5. -insertObject:in<Key>AtIndex: //无序集合一般不支持索引插入
  6. -insert<Key>:atIndexes: //和上一个方法一样
  7. -removeObjectFrom<Key>AtIndex: //和前两个一样
  8. -remove<Key>AtIndexes: //和之前一样
  9. -replaceObejctIn<Key>AtIndex: withObject:
  10. -replace<Key>AtIndexes: with<Key>: //都和之前一样
复制代码
一上就是KVC的界说和KVC有关的一些方法
KVC设值

通过KVC设值时,会在对象中寻找对应的key,当调用setValue: forKey:时,有一个寻找key的次序:


  • 优先调用setKey: ,通过setter方法完成设置。
  • 如果未找到setKey:方法,会先查抄+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认返回YES,如果重写该方法返回NO的话,那么这一步KVC会执行setValue: forUndefinedKey: 方法。如果返回YES,会在该类内里搜刮有没有名为_key的成员变量,无论该变量是在类接口界说,照旧在实现处界说,也无论使用什么修饰关键字。
  • 如果没有setKey:方法,也没有_key成员变量,就会搜刮 _isKey成员的变量。
  • 如果以上都没有,就会继承搜刮Key和isKey的成员变量
  • 如果方法和相应成员变量全都不存在,就会执行setValue: forUndefinedKey: 方法,默认抛出异常
简单来说:如果没有找到SetKey方法,会按照_Key, _isKey,Key,isKey的次序搜刮成员并举行赋值操作。
来尝试一下寻找_Key的成员变量
  1. @interface KVCMethod : NSObject {
  2.    NSString* _FirstName;
  3. }
  4. @end
  5.  
  6.  int main(int argc, char * argv[]) {
  7.    NSString * appDelegateClassName;
  8.    @autoreleasepool {
  9.        KVCMethod* kvcMethod = [[KVCMethod alloc] init];
  10.        [kvcMethod setValue:@"李" forKey:@"FirstName"];
  11.        NSLog(@"%@", [kvcMethod valueForKey:@"FirstName"]);
  12.        }
  13.        appDelegateClassName = NSStringFromClass([AppDelegate class]);
  14.    }
  15.    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
  16. }
复制代码
这里将后续的代码删去了,所以只用看第一行输出的效果,可以看到运行的效果是乐成找到了_key成员变量并且乐成设置和取出了相应的成员变量

再将accessInstanceVariablesDirectly设置为NO运行:
我们重写处理异常的方法来查抄修改accessInstanceVariablesDirectly的返回值之后程序是怎么执行的
  1. @implementation KVCMethod
  2. - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError {
  3.    if ([inKey isEqualToString:@"name"]) {
  4.        NSString* name = (NSString*)*ioValue;
  5.        if (name.length == 0) {
  6.            if (outError) {
  7.                *outError = [NSError errorWithDomain:@"MyDoain" code:1001 userInfo:@{NSLocalizedDescriptionKey: @"Name cannot be empty"}];
  8.            }
  9.            return NO;
  10.        }
  11.    }
  12.    return YES;
  13. }
  14. + (BOOL)accessInstanceVariablesDirectly {
  15.    return NO;
  16. }
  17. - (id)valueForUndefinedKey:(NSString *)key {
  18.    NSLog(@"出现异常,不存在该key:%@", key);
  19.    return nil;
  20. }
  21. - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
  22.    NSLog(@"出现异常,不存在该key:%@", key);
  23. }
  24. @end
复制代码
运行效果如下:

可以发现触发了处理异常的方法,也就是说程序没有按照之前的次序寻找成员变量,也就没有乐成赋值,程序输出NULL
KVC取值

当调用valueForKey: @"name"的代码时,KVC对key的都搜刮方式不同于setValue: forKey: ,其搜刮方式如下:


  • 首先按照getKey,Key,isKey的次序查找getter方法,找到的话会直接调用。如果是BOOL或者int等值范例,会将其包装成一个NSNumber对象
  • 如果没有找到getter方法,就会查找countOf,objectIn AtIndex 或者AtIndexes 格式的方法,如果其中一个被找到,就会返回一个可以相应NSArray所有方法的代理集合(NSKeyValueArray,属于NSArray的子类),调用这个代理集合的方法或者说给它发送属于NSArray的方法,就会以countOf,objectIn Atindex或AtIndexes这几个方法组合的形式调用。
  • 如果上面方法都没有找到,就会同时查照countOf,enumeratorOf,merberOf格式的方法,这三个方法都找到了的话,那么就返回一个可以相应NSSet所有方法的代理集合,和上面所说一样,给这个代理集合发送NSSet的方法,就会以countOf,enumeratorOf,merberOf组合的形式调用。
  • 如果还没有找到,这时候就会查抄方法accessInstanceVariablesDirectly的返回值,如果是YES,就会按照之前设值一样的次序,搜刮_Key, _isKey,Key,isKey。
KVC使用KeyPath

当自界说类的成员变量是自界说类或者其他的复杂数据范例时,可以先使用KVC获取该属性,然后再用一次KVC获得这个属性的属性。但这种方法比力繁琐,KVC有一种更简洁的方法,那就是键路径KeyPath,按照路径寻找Key
  1. - (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
  2. - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值
  3. int main(int argc, char * argv[]) {
  4.    NSString * appDelegateClassName;
  5.    @autoreleasepool {
  6.        KVCMethod* kvcMethod = [[KVCMethod alloc] init];
  7.        SubKVCMethod* subKVCMethod = [[SubKVCMethod alloc] init];
  8.        [kvcMethod setValue:subKVCMethod forKey:@"subKVCMethod"];
  9.        [kvcMethod setValue:@"南崩" forKeyPath:@"subKVCMethod.name"];
  10.        NSLog(@"KVCMethod的属性SubKVCMethod的属性name是%@", [kvcMethod valueForKeyPath:@"subKVCMethod.name"]);
  11.        }
  12.        appDelegateClassName = NSStringFromClass([AppDelegate class]);
  13.    }
  14.    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
  15. }
复制代码
运行的效果是:

发现乐成实现了赋值和取值操作,并且这里KeyPath的格式实在就类似于我们点语法的格式
KVC处理异常

之前实在已经提到过KVC处理异常的方法了,这里重要是两种异常,一种是使用了错误的Key,另一种是设值时传递了nil的值,分别可以使用两类方法来处理
KVC处理设值nil异常

KVC一样寻常不答应在调用setValue: forKey: 时传递一个nil的值。如果传入一个nil的值,KVC会调用setNilValueForKey:方法,这个方法默认抛出异常。
  1. #import "KVCMethod.h"
  2. @implementation KVCMethod
  3. - (void)setNilValueForKey:(NSString *)key {
  4.    NSLog(@"不能将%@设成nil", key);
  5. }
  6. @end
复制代码
KVC处理UndefinedKey异常

当调用setValue: forKey或者valueForKey时,一样寻常不答应使用不存在的Key,否则会报错forUndefinedKey发生瓦解,重写forUndefinedKey可以克制瓦解
  1. @implementation KVCMethod
  2. - (id)valueForUndefinedKey:(NSString *)key {
  3.    NSLog(@"出现异常,不存在该key:%@", key);
  4.    return nil;
  5. }
  6. - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
  7.    NSLog(@"出现异常,不存在该key:%@", key);
  8. }
  9. @end
复制代码
  1. [/code] [size=5]KVC处理数值和布局体范例属性[/size]
  2. valueForKey: 方法总是会返回一个id对象,如果本来的变量范例是值范例或者布局体,返回值会封装成NSNumber或者NSValue对象,这两个类会处理数字、布尔值、指针和布局体。
  3. 在使用setValue: forKey时,必须手动将值范例转换成NSNumber或者NSValue范例,才能传递过去,由于传递进去和取出来的都是id范例,所以必要本身确保范例正确性,运行时如果范例错误会抛出异常
  4. [code]@interface KVCMethod : NSObject
  5. @property(nonatomic, readwrite, assign)int age;
  6. @end
  7. int main(int argc, char * argv[]) {
  8.    NSString * appDelegateClassName;
  9.    @autoreleasepool {
  10.        KVCMethod* kvcMethod = [[KVCMethod alloc] init];
  11.        [kvcMethod setValue:[NSNumber numberWithInt:23] forKey:@"age"];
  12.        NSLog(@"age = %@", [kvcMethod valueForKey:@"age"]);
  13.        appDelegateClassName = NSStringFromClass([AppDelegate class]);
  14.    }
  15.    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
  16. }
复制代码
这里就是为类添加了一个int范例的属性age,我们赋给age的是一个NSNumber对象,KVC会自动的将NSNumber对象转换成int对象,然后再调用相应的访问器方法设置age的值。
使用valueForKey:方法时,返回的值是一个NSNumber。
必要留意的是,我们不能直接将数值通过KVC赋值,必要先转换为NSNumber和NSValue范例,那么哪些范例用NSNumber封装,哪些用NSValue呢?
可以使用NSNumber的数据范例:
  1. + (NSNumber*)numberWithChar:(char)value;
  2. + (NSNumber*)numberWithUnsignedChar:(unsignedchar)value;
  3. + (NSNumber*)numberWithShort:(short)value;
  4. + (NSNumber*)numberWithUnsignedShort:(unsignedshort)value;
  5. + (NSNumber*)numberWithInt:(int)value;
  6. + (NSNumber*)numberWithUnsignedInt:(unsignedint)value;
  7. + (NSNumber*)numberWithLong:(long)value;
  8. + (NSNumber*)numberWithUnsignedLong:(unsignedlong)value;
  9. + (NSNumber*)numberWithLongLong:(longlong)value;
  10. + (NSNumber*)numberWithUnsignedLongLong:(unsignedlonglong)value;
  11. + (NSNumber*)numberWithFloat:(float)value;
  12. + (NSNumber*)numberWithDouble:(double)value;
  13. + (NSNumber*)numberWithBool:(BOOL)value;
  14. + (NSNumber*)numberWithInteger:(NSInteger)valueNS_AVAILABLE(10_5,2_0);
  15. + (NSNumber*)numberWithUnsignedInteger:(NSUInteger)valueNS_AVAILABLE(10_5,2_0);
复制代码
可以使用NSValue的数据范例:
  1. + (NSValue*)valueWithCGPoint:(CGPoint)point;
  2. + (NSValue*)valueWithCGSize:(CGSize)size;
  3. + (NSValue*)valueWithCGRect:(CGRect)rect;
  4. + (NSValue*)valueWithCGAffineTransform:(CGAffineTransform)transform;
  5. + (NSValue*)valueWithUIEdgeInsets:(UIEdgeInsets)insets;
  6. + (NSValue*)valueWithUIOffset:(UIOffset)insetsNS_AVAILABLE_IOS(5_0);
复制代码
NSValue重要用于处理布局体范例的数据,任何布局体都可以转化成NSValue对象,包括自界说的布局体
KVC键值验证

KVC有提供验证Key对应的Value是否正当的方法:
  1. - (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;
复制代码
这个方法在KVC的界说那个部分已经有过讲解,这里就不多赘述了。
KVC处理集合

简单集合运算符

简单集合运算符有@avg, @count,@max,@min,@sum5种,这五种运算符分别表示均匀值,总个数,最大值,最小值和总量
  1. int main(int argc, char * argv[]) {
  2.    NSString * appDelegateClassName;
  3.    @autoreleasepool {
  4.        KVCMethod* kvcMethod1 = [[KVCMethod alloc] init];
  5.        kvcMethod1.name = @"灰灰";
  6.        kvcMethod1.age = 12;
  7.        KVCMethod* kvcMethod2 = [[KVCMethod alloc] init];
  8.        kvcMethod2.name = @"黑黑";
  9.        kvcMethod2.age = 22;
  10.        KVCMethod* kvcMethod3 = [[KVCMethod alloc] init];
  11.        kvcMethod3.name = @"白白";
  12.        kvcMethod3.age = 32;
  13.        
  14.        NSArray* array = [NSArray arrayWithObjects:kvcMethod1, kvcMethod2, kvcMethod3, nil];
  15.        NSNumber* sum = [array valueForKeyPath:@"@sum.age"];
  16.        NSLog(@"sum = %@", sum);
  17.        NSNumber* avg = [array valueForKeyPath:@"@avg.age"];
  18.        NSLog(@"avg = %@", avg);
  19.        NSNumber* count = [array valueForKeyPath:@"@count.age"];
  20.        NSLog(@"count = %@", count);
  21.        NSNumber* min = [array valueForKeyPath:@"@min.age"];
  22.        NSLog(@"min = %@", min);
  23.        NSNumber* max = [array valueForKeyPath:@"@max.age"];
  24.        NSLog(@"max = %@", max);
  25.        appDelegateClassName = NSStringFromClass([AppDelegate class]);
  26.    }
  27.    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
  28. }
复制代码
复制代码
对象运算符

以数组的形式返回指定内容,有两种:
@distinctUnionOfObjects
@unionOfObjects
他们的返回值都是NSArray,区别是前者返回元素唯一,是去重后的效果,后者返回全集。
KVC处理字典

有两个关于NSDictionary的方法:
  1. - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
  2. - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
复制代码
第一个方法是指输入一组key,返回这组key对应的属性,再构成一个字典
第二个方法是用来修改Model中对应Key的属性
  1. @interface Address : NSObject
  2. @property (nonatomic, copy)NSString* country;
  3. @property (nonatomic, copy)NSString* province;
  4. @property (nonatomic, copy)NSString* city;
  5. @property (nonatomic, copy)NSString* district;
  6. @end
  7.  
  8. int main(int argc, char * argv[]) {
  9.    NSString * appDelegateClassName;
  10.    @autoreleasepool {
  11.        //模型转字典
  12.                Address* add = [Address new];
  13.                add.country = @"China";
  14.                add.province = @"Guang Dong";
  15.                add.city = @"Shen Zhen";
  16.                add.district = @"Nan Shan";
  17.                NSArray* arr = @[@"country",@"province",@"city",@"district"];
  18.                NSDictionary* dict = [add dictionaryWithValuesForKeys:arr]; //把对应key所有的属性全部取出来
  19.                NSLog(@"%@",dict);
  20.                
  21.                //字典转模型
  22.                NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};
  23.                [add setValuesForKeysWithDictionary:modifyDict];            //用key Value来修改Model的属性
  24.                NSLog(@"country:%@  province:%@ city:%@",add.country,add.province,add.city);
  25.        appDelegateClassName = NSStringFromClass([AppDelegate class]);
  26.    }
  27.    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
  28. }
复制代码
[code][/code] KVC应用

KVC这种基于运行时的编程方式具有很高的灵活性,因此在iOS开发中有着广泛的用途
   动态地取值和设值

  利用KVC动态的取值和设值是最基本的用途了。
  用KVC来访问和修改私有变量

  对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的。
  Model和字典转换

  这是KVC强大作用的又一次体现,KVC和Objc的runtime组合可以很容易的实现Model和字典的转换。
  修改一些控件的内下属性

  这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些控件的API,这样我们就无法正常地访问和修改这些控件的样式。 而KVC在大多数情况可下可以办理这个问题。最常用的就是个性化UITextField中的placeHolderText了。
  操作集合

  Apple对KVC的valueForKey:方法作了一些特别的实现,比如说NSArray和NSSet这样的容器类就实现了这些方法。所以可以用KVC很方便地操作集合。
  用KVC实现高阶消息传递

  当对容器类使用KVC时,valueForKey:将会被传递给容器中的每一个对象,而不是容器本身举行操作。效果会被添加进返回的容器中,这样,开发者可以很方便的操作集合来返回另一个集合。
  实现KVO

  KVO是基于KVC实现的,下面讲一下KVO的概念和实现。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

尚未崩坏

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表