iOS KVC(Key-Value Coding, NSKeyValueCoding)

返回上级目录:iOS KVC-KVO-Runtime

1.定义

KVC(Key-Value Coding)键值编码,就是指iOS的开发中,一种可以通过键名间接访问和赋值对象属性的机制。

KVC是通过NSObject的一个分类NSKeyValueCoding来实现的,所以所有继承自NSObject的类都可以使用KVC,不是继承自NSObject就不具备KVC的功能(如结构体或是一些纯swift的类)

苹果文档的定义是:A mechanism by which you can access the properties of an object indirectly by name or key. 如下图:

苹果官网指导链接:Key-Value Coding Programming Guide

kvc的实现有赖于runtime,本质是在运行时动态的给对象发送setValue:forKey 消息,设置数值
在这里插入图片描述
鼠标放在NSObject类名上,点击option会弹出文档

在这里插入图片描述
在这里插入图片描述

2.设值和取值

方法:

//设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

设值的时候会按照set,_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作

//取值
- (nullable id)valueForKey:(NSString *)key;

取值的时候,首先按get,,is的顺序方法查找getter方法,找到的话会直接调用。如果是BOOL或者Int等值类型, 会将其包装成一个NSNumber对象。

如果上面的getter没有找到,KVC则会查找countOf,objectInAtIndex或AtIndexes格式的方法。

如果上面的方法没有找到,那么会同时查找countOf,enumeratorOf,memberOf格式的方法。

实例:

#import <Foundation/Foundation.h>

@interface Test: NSObject {
    NSString *_name;
}

@end

@implementation Test

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //生成对象
        Test *obj = [[Test alloc] init];
        //通过KVC赋值name
        [obj setValue:@"xiaoming" forKey:@"name"];
        //通过KVC取值name打印
        NSLog(@"obj的名字是%@", [obj valueForKey:@"name"]);
        
    }
    return 0;
}

打印结果:
2018-05-05 15:36:52.354405+0800 KVCKVO[35231:6116188] obj的名字是xiaoming

3.KVC处理字典

方法:

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

代码实现:

#import <Foundation/Foundation.h>

@interface Address : NSObject

@end

@interface Address()

@property (nonatomic, copy)NSString* country;
@property (nonatomic, copy)NSString* province;
@property (nonatomic, copy)NSString* city;
@property (nonatomic, copy)NSString* district;

@end

@implementation Address

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //模型转字典
        Address* add = [Address new];
        add.country = @"China";
        add.province = @"Guang Dong";
        add.city = @"Shen Zhen";
        add.district = @"Nan Shan";
        NSArray* arr = @[@"country",@"province",@"city",@"district"];
        NSDictionary* dict = [add dictionaryWithValuesForKeys:arr]; //把对应key所有的属性全部取出来
        NSLog(@"%@",dict);
        
        //字典转模型
        NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};
        [add setValuesForKeysWithDictionary:modifyDict];            //用key Value来修改Model的属性
        NSLog(@"country:%@  province:%@ city:%@",add.country,add.province,add.city);
        
        
    }
    return 0;
}

打印结果:
2018-05-05 17:08:48.824653+0800 KVCKVO[35807:6368235] {
city = “Shen Zhen”;
country = China;
district = “Nan Shan”;
province = “Guang Dong”;
}
2018-05-05 17:08:48.825075+0800 KVCKVO[35807:6368235] country:USA province:california city:Los angle

4.关闭KVC,UndefinedKey方法

#import <Foundation/Foundation.h>

@interface Test: NSObject {
    NSString *_name;
}

@end

@implementation Test

//返回NO的时候KVC只会查询setter和getter这一层,下面寻找key的相关变量执行就会停止,直接报错。默认返回YES
+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}

- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(@"出现异常,该key不存在%@",key);
    return nil;
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"出现异常,该key不存在%@", key);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //生成对象
        Test *obj = [[Test alloc] init];
        //通过KVC赋值name
        [obj setValue:@"xiaoming" forKey:@"name"];
        //通过KVC取值name打印
        NSLog(@"obj的名字是%@", [obj valueForKey:@"name"]);
        
    }
    return 0;
}

打印结果:
2018-05-05 15:45:22.399021+0800 KVCKVO[35290:6145826] 出现异常,该key不存在name
2018-05-05 15:45:22.399546+0800 KVCKVO[35290:6145826] 出现异常,该key不存在name

5.keyPath

- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值

代码实现:

#import <Foundation/Foundation.h>

@interface Test1: NSObject {
    NSString *_name;
}
@end

@implementation Test1
@end

@interface Test: NSObject {
    Test1 *_test1;
}

@end

@implementation Test
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //Test生成对象
        Test *test = [[Test alloc] init];
        //Test1生成对象
        Test1 *test1 = [[Test1 alloc] init];
        //通过KVC设值test的"test1"
        [test setValue:test1 forKey:@"test1"];
        //通过KVC设值test的"test1的name"
        [test setValue:@"xiaoming" forKeyPath:@"test1.name"];
        //通过KVC取值age打印
        NSLog(@"test的\"test1的name\"是%@", [test valueForKeyPath:@"test1.name"]);
        
    }
    return 0;
}

打印结果:
2018-05-05 16:19:02.613394+0800 KVCKVO[35436:6239788] test的"test1的name"是xiaoming

6.KVC处理数值和结构体类型属性

valueForKey:总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。

但是setValue:forKey:却不行。你必须手动将值类型转换成NSNumber或者NSValue类型,才能传递过去。

代码实现:

//  ViewController.m
#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [[Person alloc]init];
    [person setValue:[NSNumber numberWithInteger:5] forKey:@"age"];
    NSLog(@"age=%@",[person valueForKey:@"age"]);
    
}


@end

打印结果:
2017-01-16 16:31:55.709 Test[28586:2294177] age=5

7.KVC处理集合

7.1 简单集合运算符 @avg(平均数), @count(元素个数) , @max(最大值) , @min(最小值) ,@sum(和)

#import <Foundation/Foundation.h>

@interface Book : NSObject
@property (nonatomic, copy)  NSString* name;
@property (nonatomic, assign)  CGFloat price;
@end

@implementation Book
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Book *book1 = [Book new];
        book1.name = @"The Great Gastby";
        book1.price = 10;
        Book *book2 = [Book new];
        book2.name = @"Time History";
        book2.price = 20;
        Book *book3 = [Book new];
        book3.name = @"Wrong Hole";
        book3.price = 30;
        
        Book *book4 = [Book new];
        book4.name = @"Wrong Hole";
        book4.price = 40;
        
        NSArray* arrBooks = @[book1,book2,book3,book4];
        NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"];
        NSLog(@"sum:%f",sum.floatValue);
        NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"];
        NSLog(@"avg:%f",avg.floatValue);
        NSNumber* count = [arrBooks valueForKeyPath:@"@count"];
        NSLog(@"count:%f",count.floatValue);
        NSNumber* min = [arrBooks valueForKeyPath:@"@min.price"];
        NSLog(@"min:%f",min.floatValue);
        NSNumber* max = [arrBooks valueForKeyPath:@"@max.price"];
        NSLog(@"max:%f",max.floatValue);
        
    }
    return 0;
}

打印结果:
2018-05-05 17:04:50.674243+0800 KVCKVO[35785:6351239] sum:100.000000
2018-05-05 17:04:50.675007+0800 KVCKVO[35785:6351239] avg:25.000000
2018-05-05 17:04:50.675081+0800 KVCKVO[35785:6351239] count:4.000000
2018-05-05 17:04:50.675146+0800 KVCKVO[35785:6351239] min:10.000000
2018-05-05 17:04:50.675204+0800 KVCKVO[35785:6351239] max:40.000000

7.2 对象运算符 @distinctUnionOfObjects(去重后的元素),@unionOfObjects(所有元素)

#import <Foundation/Foundation.h>

@interface Book : NSObject
@property (nonatomic, copy)  NSString* name;
@property (nonatomic, assign)  CGFloat price;
@end

@implementation Book
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Book *book1 = [Book new];
        book1.name = @"The Great Gastby";
        book1.price = 40;
        Book *book2 = [Book new];
        book2.name = @"Time History";
        book2.price = 20;
        Book *book3 = [Book new];
        book3.name = @"Wrong Hole";
        book3.price = 30;
      
        
        Book *book4 = [Book new];
        book4.name = @"Wrong Hole";
        book4.price = 10;
        
        Book *book5 = [Book new];
        book5.name = @"Wrong Hole";
        book5.price = 1;
        
        Book *book6 = [Book new];
        book6.name = @"Wrong Hole";
        book6.price = 10;
        
        
        Book *book7 = [Book new];
        book7.name = @"Wrong Hole";
        book7.price = 100;
        
        NSArray* arrBooks = @[book1,book2,book3,book4,book5,book6,book7];
        
        NSLog(@"distinctUnionOfObjects");
        NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];
        for (NSNumber *price in arrDistinct) {
            NSLog(@"%f",price.floatValue);
        }
        NSLog(@"unionOfObjects");
        NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];
        for (NSNumber *price in arrUnion) {
            NSLog(@"%f",price.floatValue);
        }
        
    }
    return 0;
}

打印结果:
2020-07-15 13:54:30.135902+0800 kvc_main[2084:48512] distinctUnionOfObjects
2020-07-15 13:54:30.136668+0800 kvc_main[2084:48512] 100.000000
2020-07-15 13:54:30.136733+0800 kvc_main[2084:48512] 40.000000
2020-07-15 13:54:30.136831+0800 kvc_main[2084:48512] 10.000000
2020-07-15 13:54:30.136894+0800 kvc_main[2084:48512] 1.000000
2020-07-15 13:54:30.136932+0800 kvc_main[2084:48512] 20.000000
2020-07-15 13:54:30.136965+0800 kvc_main[2084:48512] 30.000000
2020-07-15 13:54:30.136997+0800 kvc_main[2084:48512] unionOfObjects
2020-07-15 13:54:30.137063+0800 kvc_main[2084:48512] 40.000000
2020-07-15 13:54:30.137099+0800 kvc_main[2084:48512] 20.000000
2020-07-15 13:54:30.137130+0800 kvc_main[2084:48512] 30.000000
2020-07-15 13:54:30.137160+0800 kvc_main[2084:48512] 10.000000
2020-07-15 13:54:30.137189+0800 kvc_main[2084:48512] 1.000000
2020-07-15 13:54:30.137233+0800 kvc_main[2084:48512] 10.000000
2020-07-15 13:54:30.137281+0800 kvc_main[2084:48512] 100.000000
Program ended with exit code: 0

8.KVC的使用

1.用KVC来访问和修改私有变量

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

2.Model和字典转换

这是KVC强大作用的又一次体现,KVC和Objc的runtime组合可以很容易的实现Model和字典的转换。

3.修改一些控件的内部属性

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

4.操作集合

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

实例代码:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSArray* arrStr = @[@"english",@"franch",@"chinese"];
        NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];
        for (NSString* str  in arrCapStr) {
            NSLog(@"%@",str);
        }
        NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
        for (NSNumber* length  in arrCapStrLength) {
            NSLog(@"%ld",(long)length.integerValue);
        }
        
    }
    return 0;
}

打印结果:
2018-05-05 17:16:21.975983+0800 KVCKVO[35824:6395514] English
2018-05-05 17:16:21.976296+0800 KVCKVO[35824:6395514] Franch
2018-05-05 17:16:21.976312+0800 KVCKVO[35824:6395514] Chinese
2018-05-05 17:16:21.976508+0800 KVCKVO[35824:6395514] 7
2018-05-05 17:16:21.976533+0800 KVCKVO[35824:6395514] 6
2018-05-05 17:16:21.976550+0800 KVCKVO[35824:6395514] 7

方法capitalizedString被传递到NSArray中的每一项,这样,NSArray的每一员都会执行capitalizedString并返回一个包含结果的新的NSArray。
从打印结果可以看出,所有String都成功以转成了大写。
同样如果要执行多个方法也可以用valueForKeyPath:方法。它先会对每一个成员调用 capitalizedString方法,然后再调用length,因为lenth方法返回是一个数字,所以返回结果以NSNumber的形式保存在新数组里。

参考博客

iOS KVC和KVO详解

还原iOS对KVO,KVC的真实实现

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页