KVC简述

作者: Miss_QL | 来源:发表于2018-03-15 17:34 被阅读20次

最近在看KVC的一些知识,在此记录分享。

1、 valueForKey
valueForKey是一个取值的操作。关于valueForKey的调用顺序,如下:

(1)valueForKey这个方法被调用的时候,首先会调用其相关方法
(a)去找getter方法。
实际上getter方法是一个系列方法,总共包括三个方法(按调用优先级顺序排列如下):
getKey > key > isKey
具体逻辑在代码中看起来更明显:

#pragma mark - getter
// 优先级:第一(最高)
- (NSString *)getName {
    return @"getName";
}

// 优先级:第二
- (NSString *)name {
    return @"name";
}

// 优先级:第三(几乎不用)
- (NSString *)isName {
    return @"isName";
}

(b)如果没有实现上述的三个getter方法,则会调用NSarray(和NSSet)的两个方法。
- (NSInteger)countOfKey- (id)objectInKeyAtIndex:(NSInteger)index

- (NSInteger)countOfName {
    return 5;
}

- (id)objectInNameAtIndex:(NSInteger)index {
    return @"这里的name是key";
}

此时,valueForKey结果返回的是一个数组。

(2)如果相关方法不存在,会查看+ (BOOL)accessInstanceVariablesDirectly方法
方法+ (BOOL)accessInstanceVariablesDirectly的作用为是否允许调用成员变量,结果默认返回YES。
(a)如果这个方法返回值为YES,则找成员变量。
查找的成员变量总共有四个,调用的先后顺序分别为:
_key > _isKey > key > isKey

{
    NSString * _name;       //优先级最高
    NSString * _isName;     //优先级第二
    NSString * name;        //优先级第三
    NSString * isName;      //优先级最低
}

(b)如果这个方法返回值为NO,则抛出异常。
+ (BOOL)accessInstanceVariablesDirectly返回NO则无法调用成员变量,运行会崩溃,报错为找不到key。要想防止运行报错,需要重写- (id)valueForUndefinedKey:(NSString *)key方法。

- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(@"找不到key:%@",key);
    return nil;
}

PS:valueForKeyPath
使用场景:现在有一个Dog类,Dog类中有一个@property (nonatomic, assign) int age;属性,然后还有一个People类,People类中有一个@property (nonatomic, strong) Dog * dog;属性。当我们想要在外部类中获取People类的一个对象的dog的age值,可以使用valueForKeyPath替代valueForKey来简化代码。
Dog类:

#import <Foundation/Foundation.h>

@interface Dog : NSObject

@property (nonatomic, assign) int age;

@end



#import "Dog.h"

@implementation Dog

- (instancetype)init {
    if (self = [super init]) {
        _age = 10;
    }
    return self;
}

@end

People类:

#import <Foundation/Foundation.h>
#import "Dog.h"

@interface People : NSObject

@property (nonatomic, strong) Dog * dog;

@end



#import "People.h"

@implementation People

- (instancetype)init {
    if (self = [super init]) {
        _dog = [[Dog alloc] init];
    }
    return self;
}

@end

调用类:

People * p = [[People alloc] init];
/**
  *   [p valueForKeyPath:@"dog.age"]等价于下面两句
  *   Dog * dog = [p valueForKey:@"dog"];
  *   NSString * age = [dog valueForKey:@"age"];
*/
NSString * age = [p valueForKeyPath:@"dog.age"];

2、 setValue: forKey:
setValue: forKey:是一个设值的操作。关于setValue: forKey:的调用顺序(可类比取值的操作),如下:

(1)setValue: forKey:这个方法被调用的时候,首先会调用setter方法
实际上setter方法是一个系列方法,总共包括两个方法(按调用优先级顺序排列如下):
setKey: > setIsKey:
具体逻辑在代码中看起来更明显:

// 优先级高
- (void)setName:(NSString *)name {
    NSLog(@"setName:%@", name);
}

// 优先级低
- (void)setIsName:(NSString *)name {
    NSLog(@"setIsName:%@", name);
}

(2)如果setter方法不存在,会查看+ (BOOL)accessInstanceVariablesDirectly方法
在没有实现setter方法的时候,系统会默认去修改成员变量(当然前提是+ (BOOL)accessInstanceVariablesDirectly返回YES)。虽然成员变量是私有的,但是外界是可以通过KVC修改的。
具体流程同1中(2),详细步骤与讲解请翻上面内容~~
只有一个不同点,就是当+ (BOOL)accessInstanceVariablesDirectly方法返回NO的时候,要想程序不报错,需要实现- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法。

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"没有找到key:%@", key);
}

PS:setNilValueForKey
基本数据类型设置为nil的问题也会导致项目崩溃。
具体描述:People类有一个@property (nonatomic, assign) int age;属性,外部创建一个People类的对象p,当给p的age 属性赋值nil的时候,即[p setValue:nil forKey:@"age"];,运行会报错,报错为找不到setNilValueForKey。要想防止运行报错,需要重写- (void)setNilValueForKey:(NSString *)key方法。

- (void)setNilValueForKey:(NSString *)key {
    NSLog(@"不能设置nil给key:%@", key);
}

3、自定义KVC
自定义setValue:forKey:方法,完成系统的功能。

- (void)CustomSetValue:(id)value forKey:(NSString *)key {
    if (key == nil || key.length == 0) {
        return;
    }
    
    // 调用相关setter方法
    NSString * setKey = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
        [self performSelector:NSSelectorFromString(setKey) withObject:value];
        return;
    }
    
    NSString * setIsKey = [NSString stringWithFormat:@"setIs%@:", key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
        [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
        return;
    }
    
    // 异常处理方法
    if (![self.class accessInstanceVariablesDirectly]) {
        NSException * except = [NSException exceptionWithName:@"CustomKVC Exception" reason:@"accessInstanceVariablesDirectly返回NO,无法访问成员变量了" userInfo:nil];
        @throw except;
        return;
    }
    
    unsigned int count = 0;
    Ivar * ivarList = class_copyIvarList([self class], &count);
    // _key
    for (int i = 0; i < count; i++) {
        Ivar var = ivarList[i];
        NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
            object_setIvar(self, var, value);
            free(ivarList);
            return;
        }
    }
    // _isKey
    for (int i = 0; i < count; i++) {
        Ivar var = ivarList[i];
        NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
            object_setIvar(self, var, value);
            free(ivarList);
            return;
        }
    }
    //key
    for (int i = 0; i < count; i++) {
        Ivar var = ivarList[i];
        NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
        if ([keyName isEqualToString:key]) {
            object_setIvar(self, var, value);
            free(ivarList);
            return;
        }
    }
    //isKey
    for (int i = 0; i < count; i++) {
        Ivar var = ivarList[i];
        NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
            object_setIvar(self, var, value);
            free(ivarList);
            return;
        }
    }
    
    //异常处理方法
    [self setValue:value forUndefinedKey:key];
    free(ivarList);
}

4、KVC容器方法
在用KVO监听p对象的array属性时,直接调用[p.array addObject:@"1"];是无法监听到array值的变化的,因为没有修改setter方法。如果需要监听到array的值变化,需要使用[[p mutableArrayValueForKey:@"array"] addObject:@"1"];

5、KVC集合运算符
KVC里面共有五个集合运算符:@count @max @min @sum @avg(求平均值)

    NSMutableArray * array = NSMutableArray.array;
    People * p1 = [[People alloc] init];
    p1.age = 10;
    [array addObject:p1];
    People * p2 = [[People alloc] init];
    p2.age = 20;
    [array addObject:p2];
    People * p3 = [[People alloc] init];
    p3.age = 30;
    [array addObject:p3];
    NSLog(@"count = %@", [array valueForKey:@"@count"]);
    NSLog(@"max = %@", [array valueForKeyPath:@"@max.age"]);

输出结果如下:
count = 3
max = 30

相关文章

网友评论

    本文标题:KVC简述

    本文链接:https://www.haomeiwen.com/subject/meocqftx.html