iOS -- KVC

一、KVC赋值过程分析

1.1. 先找相关方法 set<Key>: _set<Key>: setIs<Key>:
1.2. 若没有相关方法+ (BOOL)accessInstanceVariablesDirectly判断是否可以直接访问成员变量(默认为YES
1.3. 如果是判断是NO,直接执行KVCsetValue:forUndefinedKey:(系统抛出一个异常,未定义key)
1.4. 如果是YES,继续找相关变量_<key> _is<Key> <key> is<Key>
1.5. 方法或成员都不存在,setValue: forUndefinedKey:方法,默认是抛出异常
示例1

// ZYPerson.h
@interface ZYPerson : NSObject{

     @public
    NSString* _name; ④
    NSString* _isName; ⑤
    NSString* name; ⑥
    NSString* isName; ⑦
}

#import "ZYPerson.h"

// ZYPerson.m
@implementation ZYPerson

- (void) setName:(NSString*) name {   ①
    NSLog(@"%s", __func__);
}

- (void) _setName:(NSString*) name {  ②
    NSLog(@"%s", __func__);
}

- (void) setIsName:(NSString*) name {  ③
    NSLog(@"%s", __func__);
}

- (NSString*) getName { ⑧
    NSLog(@"%s", __func__);
    return @"getName";
}

- (NSString*) name { ⑨
    NSLog(@"%s", __func__);
    return @"name";
}

@end

// ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    ZYPerson *p = [ZYPerson new];
    
    [p setValue:@"tom" forKey:@"name"];
    NSLog(@"name = %@", [p valueForKey:@"name"]);
    NSLog(@"name = %@", p->name);
    NSLog(@"_name = %@", p->_name);
    NSLog(@"isName = %@", p->isName);
    NSLog(@"_isName = %@", p->_isName);
}

@end

说明 ① ② ③ 等代码中标注

如果①存在,② ③ 可无,结果如下

2019-01-16 15:46:56.798680+0800 KVC001[20175:1536368] -[ZYPerson setName:]
2019-01-16 15:46:56.798798+0800 KVC001[20175:1536368] name = (null)
2019-01-16 15:46:56.798881+0800 KVC001[20175:1536368] _name = (null)
2019-01-16 15:46:56.798955+0800 KVC001[20175:1536368] isName = (null)
2019-01-16 15:46:56.799023+0800 KVC001[20175:1536368] _isName = (null)

如果①不存在,② 存在,③ 可无,结果如下

2019-01-16 15:49:26.650198+0800 KVC001[20220:1539317] -[ZYPerson _setName:]
2019-01-16 15:49:26.650333+0800 KVC001[20220:1539317] name = (null)
2019-01-16 15:49:26.650412+0800 KVC001[20220:1539317] _name = (null)
2019-01-16 15:49:26.650482+0800 KVC001[20220:1539317] isName = (null)
2019-01-16 15:49:26.650548+0800 KVC001[20220:1539317] _isName = (null)

如果① ②不存在,③ 存在,结果如下

2019-01-16 15:50:19.981200+0800 KVC001[20246:1540579] -[ZYPerson setIsName:]
2019-01-16 15:50:19.981313+0800 KVC001[20246:1540579] name = (null)
2019-01-16 15:50:19.981399+0800 KVC001[20246:1540579] _name = (null)
2019-01-16 15:50:19.981475+0800 KVC001[20246:1540579] isName = (null)
2019-01-16 15:50:19.981541+0800 KVC001[20246:1540579] _isName = (null)

如果① ② ③ 不存在,结果如下

2019-01-16 15:57:59.461807+0800 KVC001[20377:1549518] name = (null)
2019-01-16 15:57:59.461938+0800 KVC001[20377:1549518] _name = tom
2019-01-16 15:57:59.462019+0800 KVC001[20377:1549518] isName = (null)
2019-01-16 15:57:59.462112+0800 KVC001[20377:1549518] _isName = (null)

如果① ② ③ ④不存在,结果如下

2019-01-16 15:59:34.276391+0800 KVC001[20413:1551470] name = (null)
2019-01-16 15:59:34.276510+0800 KVC001[20413:1551470] isName = (null)
2019-01-16 15:59:34.276587+0800 KVC001[20413:1551470] _isName = tom

以此类推...
结论: 先找相关方法 set<Key>: _set<Key>: setIs<Key>: 再找相关变量_<key> _is<Key> <key> is<Key>

二、KVC取值过程分析

2.1. 先找相关方法get<Key> key
2.2. 若没有相关方法 + (BOOL)accessInstanceVariablesDirectly,判断是否可以直接方法成员变量(默认是YES
2.3. 如果是判断是NO,直接执行KVCvalueForUndefinedKey:(系统抛出一个异常,未定义key)
2.4. 如果是YES,继续找相关变量_<key> _is<Key> <key> is<Key>
2.5. 方法或成员都不存在,valueForUndefinedKey:方法,默认是抛出异常

如果⑧存在,⑨可无,结果如下

2019-01-16 16:18:45.537583+0800 KVC001[20710:1573310] -[ZYPerson getName]
2019-01-16 16:18:45.537709+0800 KVC001[20710:1573310] name = getName
2019-01-16 16:18:45.537797+0800 KVC001[20710:1573310] name = (null)
2019-01-16 16:18:45.537869+0800 KVC001[20710:1573310] isName = (null)
2019-01-16 16:18:45.537941+0800 KVC001[20710:1573310] _isName = tom

如果⑧ 不存在,⑨存在,结果如下

2019-01-16 16:20:15.113493+0800 KVC001[20744:1575289] -[ZYPerson name]
2019-01-16 16:20:15.113627+0800 KVC001[20744:1575289] name = name
2019-01-16 16:20:15.113708+0800 KVC001[20744:1575289] name = (null)
2019-01-16 16:20:15.113778+0800 KVC001[20744:1575289] isName = (null)
2019-01-16 16:20:15.114321+0800 KVC001[20744:1575289] _isName = tom

如果⑧ ⑨不存在,结果如下

2019-01-16 16:22:04.841822+0800 KVC001[20792:1578120] name = tom
2019-01-16 16:22:04.841938+0800 KVC001[20792:1578120] name = (null)
2019-01-16 16:22:04.842009+0800 KVC001[20792:1578120] _name = tom
2019-01-16 16:22:04.842078+0800 KVC001[20792:1578120] isName = (null)
2019-01-16 16:22:04.842144+0800 KVC001[20792:1578120] _isName = (null)

以此类推...
结论: 先找相关方法get<Key> key 再找相关变量_<key> _is<Key> <key> is<Key>

三、KVC异常处理

示例2

// ZYPerson.h
@interface ZYPerson : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

@end

// ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    ZYPerson *p = [ZYPerson new];
    
    [p setValue:nil forKey:@"age"];
    [p setValue:@"tom" forKey:@"name1"];
    NSLog(@"name = %@", [p valueForKey:@"name1"]);
}

@end
3.1、值为空
2019-01-16 16:46:44.065665+0800 KVC001[21151:1602368] *** Terminating app due to uncaught 
exception 'NSInvalidArgumentException',
 reason: '[<ZYPerson 0x600000dfe360> setNilValueForKey]: 
could not set nil as the value for the key age.'
*** First throw call stack:
3.2、key值不存在
3.2.1、赋值key值不存在
2019-01-16 17:05:38.681366+0800 KVC001[21451:1622468] *** Terminating app due to uncaught 
exception 'NSUnknownKeyException', r
eason: '[<ZYPerson 0x6000001664e0> setValue:forUndefinedKey:]: 
this class is not key value coding-compliant for the key name1.'
*** First throw call stack:
3.2.2、取值key值不存在
2019-01-16 17:16:26.100903+0800 KVC001[21604:1634947] *** Terminating app due to uncaught
 exception 'NSUnknownKeyException', 
reason: '[<ZYPerson 0x600002d09300> valueForUndefinedKey:]: this class is not
 key value coding-compliant for the key name1.'
*** First throw call stack:

为了程序的健壮性,重写上面报错方法,如下:

// ZYPerson.m
@implementation ZYPerson

// 对非对象类型,值不能为空
- (void) setNilValueForKey:(NSString *)key {
    NSLog(@"%@ 值不能为空", key);
}

// 赋值key值不存在
- (void) setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"key = %@值不存在 ", key);
}

// 取值key值不存在
- (id) valueForUndefinedKey:(NSString *)key {
    NSLog(@"key=%@不存在", key);
    return nil;
}

@end

结果如下:

2019-01-16 17:38:24.760843+0800 KVC001[21893:1654601] age 值不能为空
2019-01-16 17:38:24.761428+0800 KVC001[21893:1654601] key = name1值不存在 
2019-01-16 17:38:24.761934+0800 KVC001[21893:1654601] key=name1不存在
2019-01-16 17:38:24.762044+0800 KVC001[21893:1654601] name = (null)

四、KVC正确性验证

场景:假设人的年纪不能小于0或者不能大于200

// ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    ZYPerson *p = [ZYPerson new];
    
    NSNumber* value = @200;
    if ([p validateValue:&value forKey:@"age" error:NULL]) {
        [p setValue:value forKey:@"age"];
    }
}

// ZYPerson.m
@implementation ZYPerson

- (BOOL) validateAge:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError {
    
    NSNumber* value = (NSNumber*)*ioValue;
    if ([value integerValue] <= 0 || [value integerValue] >= 200) {
        
        return NO;
    }
    return YES;
}

@end

validateValue方法的工作原理:
4.1. 先找一下你的类中是否实现了方法-(BOOL)validate<Key>:error:
4.2. 如果实现了就会根据实现方法里面的自定义逻辑返回NO或者YES如果没有实现这个方法,则系统默认返回就是YES

五、KVC与字典

利用这两个API

/* Given an array of keys, return a dictionary containing the keyed attribute values, to-one-related objects, and/or collections of to-many-related objects. Entries for which -valueForKey: returns nil have NSNull as their value in the returned dictionary.
*/
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

/* Given a dictionary containing keyed attribute values, to-one-related objects, and/or collections of to-many-related objects, set the keyed values. Dictionary entries whose values are NSNull result in -setValue:nil forKey:key messages being sent to the receiver.
*/
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

示例3

// KVC字典操作
- (void) dictionaryTest {
    ZYPerson* p = [ZYPerson new];
    
    NSDictionary* dict = @{
                           @"name":@"Tom",
                           @"age":@18,
                           @"nick":@"Cat",
                           @"height":@180
                           };
    
    [p setValuesForKeysWithDictionary:dict];
    NSLog(@"p.name = %@, p.age = %d, p.nick =%@, p.height = %f", p.name, p.age, p.nick, p.height);
    
    NSArray* keys = @[@"name", @"age"];
    NSDictionary* dict1 = [p dictionaryWithValuesForKeys:keys];
    
    NSLog(@"%@", dict1);
}

@end

输出如下:

2019-01-16 21:09:31.140841+0800 KVC001[24763:1926331] p.name = Tom, p.age = 18, p.nick =Cat, p.height = 180.000000
2019-01-16 21:09:31.141081+0800 KVC001[24763:1926331] {
    age = 18;
    name = Tom;
}

六、KVC的消息传递

1.length 字符串长度
2.lowercaseString 全部小写
3.uppercaseString 全部大写
4.capitalizedString 首字母大写

示例4

// KVC消息传递  array
- (void) arrayKVCTest {
   
    NSArray* arr = @[@"monDay", @"tuesDay", @"wednesDay"];
    NSArray* lengthArr = [arr valueForKey:@"length"];
    NSLog(@"%@", lengthArr);
    
    NSArray* lowercaseArr = [arr valueForKey:@"lowercaseString"]; 
    NSLog(@"%@", lowercaseArr);
    
    NSArray* uppercaseArr = [arr valueForKey:@"uppercaseString"]; 
    NSLog(@"%@", uppercaseArr);
    
    NSArray* capitalizedArr = [arr valueForKey:@"capitalizedString"];
    NSLog(@"%@", capitalizedArr);
}

@end

结果如下:

2019-01-16 21:44:56.079415+0800 KVC001[25267:2056897] (
    6,
    7,
    9
)
2019-01-16 21:44:56.079665+0800 KVC001[25267:2056897] (
    monday,
    tuesday,
    wednesday
)
2019-01-16 21:44:56.079798+0800 KVC001[25267:2056897] (
    MONDAY,
    TUESDAY,
    WEDNESDAY
)
2019-01-16 21:44:56.079937+0800 KVC001[25267:2056897] (
    Monday,
    Tuesday,
    Wednesday
)

七、KVC容器操作

7.1聚合操作符

1.@avg 平均值
2.@count 数量
3.@max 最大值
4.@min 最小值
5.@sum 总和

- (void) contrainerTest {
    
    NSMutableArray* students = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        TZPerson* student = [TZPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"height":@(1.65 + 0.02*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [students addObject:student];
    }
    
    NSLog(@"%@", [students valueForKey:@"height"]);
    
    // 平均身高
    float avg = [[students valueForKeyPath:@"@avg.height"] floatValue];
    NSLog(@"%f", avg);
}

结果如下:

2019-01-16 22:05:20.043606+0800 KVC001[25551:2194029] (
    "1.75",
    "1.65",
    "1.75",
    "1.65",
    "1.65",
    "1.65"
)
2019-01-16 22:05:20.045527+0800 KVC001[25551:2194029] 1.683333

操作符取值格式如下:

3212473-d4fe48ae88443e90.png
官网文档

7.2 数组操作符

1.@distinctUnionOfObjects 去重
2.@unionOfObjects 不去重

- (void) contrainerArrayTest {
    
    NSMutableArray* students = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        ZYPerson* student = [ZYPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"height":@(1.65 + 0.02*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [students addObject:student];
    }
    
    NSLog(@"%@", [students valueForKey:@"height"]);
    
    NSArray* arr = [students valueForKeyPath:@"@distinctUnionOfObjects.height"];
    NSLog(@"arr = %@", arr);
    
    NSArray* arr1 = [students valueForKeyPath:@"@unionOfObjects.height"];
    NSLog(@"arr1 = %@", arr1);
}

结果如下:

2019-01-16 22:12:13.543600+0800 KVC001[25671:2242372] (
    "1.73",
    "1.71",
    "1.67",
    "1.65",
    "1.65",
    "1.73"
)
2019-01-16 22:12:13.543815+0800 KVC001[25671:2242372] arr = (
    "1.65",
    "1.73",
    "1.71",
    "1.67"
)
2019-01-16 22:12:13.543964+0800 KVC001[25671:2242372] arr1 = (
    "1.73",
    "1.71",
    "1.67",
    "1.65",
    "1.65",
    "1.73"
)

7.3嵌套集合(array&set)操作

1.@distinctUnionOfArrays 数组去重
2.@unionOfArrays 数组不去重
3.@distinctUnionOfSets 集合去重

- (void) containerNestingTest {
    
    NSMutableArray* students = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        TZPerson* student = [TZPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"height":@(1.65 + 0.02*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [students addObject:student];
    }
    
    NSMutableArray* students1 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        TZPerson* student = [TZPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"height":@(1.65 + 0.02*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [students1 addObject:student];
    }
    
    // 嵌套数组
    NSArray* nestArr = @[students, students1];
    
    
    NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.height"];
    NSLog(@"arr = %@", arr);
    
    NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.height"];
    NSLog(@"arr1 = %@", arr1);
    
}

- (void) containerNestingTest1 {
    
    NSMutableSet* students = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        TZPerson* student = [TZPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"height":@(1.65 + 0.02*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [students addObject:student];
    }
    
    NSLog(@"students = %@", [students valueForKey:@"height"]);
    
    NSMutableSet* students1 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        TZPerson* student = [TZPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"height":@(1.65 + 0.02*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [students1 addObject:student];
    }
    
     NSLog(@"students1 = %@", [students1 valueForKey:@"height"]);
    
    NSSet* nestSet = [NSSet setWithObjects:students, students1, nil];
    
    NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.height"];
    NSLog(@"arr1 = %@", arr1);
}

八、KVC集合代理对象

通过一个key映射多个方法

// TZPerson.h
@interface TZPerson : NSObject

@property (nonatomic, assign) NSUInteger count;

@property (nonatomic, strong) NSMutableArray *penArr;

@end

// TZPerson.m
@implementation TZPerson

- (NSUInteger) countOfBooks {
    return self.count;
}

- (id) objectInBooksAtIndex:(NSUInteger)index {
    return [NSString stringWithFormat:@"book %lu", index];
}

// 个数
- (NSUInteger) countOfPens {
    return [self.penArr count];
}

// 是否包含这个成员对象
- (id) memberOfPens:(id)object {
    return [self.penArr containsObject:object] ? object : nil;
}

// 迭代器
- (id) enumeratorOfPens {
    return [self.penArr objectEnumerator];
}

@end

// ViewController.h
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    TZPerson* p = [TZPerson new];
    p.count = 5;
    
    NSLog(@"books = %@", [p valueForKey:@"books"]);
    
    p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
    NSSet* set = [p valueForKey:@"pens"];
    NSLog(@"pens = %@", set);
}

结果如下

2019-01-17 15:04:27.768932+0800 KVC001[4017:619765] books = (
    "book 0",
    "book 1",
    "book 2",
    "book 3",
    "book 4"
)
2019-01-17 15:04:27.769168+0800 KVC001[4017:619765] pens = {(
    pen0,
    pen1,
    pen2,
    pen3
)}

猜你喜欢

转载自blog.csdn.net/weixin_34138056/article/details/87501864
KVC