kvc实践一:核心方法和进阶

  • Post author:
  • Post category:其他




kvc概念和核心方法

NSObject类中,有一个显示的类别名NSKeyValueCoding(缩写kvc),所以继承自NSObject的类都可以使用kvc,下面是kvc的4个主要方法:

- (nullable id)valueForKey:(NSString *)key;// 取类属性的值
- (void)setValue:(nullable id)value forKey:(NSString *)key;//给类属性设值
- (nullable id)valueForKeyPath:(NSString *)keyPath;//取类属性的属性的值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;// 给类属性的属性设值

对这4个方法的主要使用如下,可以分别通过key和keyPath来对类实例取值和设值;

// 家庭成员类
@interface Member : NSObject
@property(nonatomic, strong)NSString* role;
@end
@implementation Member
-(NSString*)desc {
    return [NSString stringWithFormat:@"member role:%@", _role];
}
@end
// 家族
@interface TomFamily : NSObject
@property(nonatomic,strong) NSString* familyName;
@property(nonatomic,strong) Member* member;
@end
@implementation TomFamily
-(void)show {
    NSLog(@"kvc存入成功");
    NSLog(@"familyName is: %@", _familyName);
    NSLog(@"member‘s role is: %@", [_member desc]);
}
@endint main(int argc, const char * argv[]) {
    @autoreleasepool {
        TomFamily* tomFamily = [[TomFamily alloc]init];
        Member* member = [[Member alloc]init];
        tomFamily.member = member;
        // kvc存入数据
        [tomFamily setValue:@"Tom家族" forKey:@"familyName"];
        [tomFamily setValue:@"father" forKeyPath:@"member.role"];
        // kvc取出数据
        [tomFamily show];
        NSLog(@"取出存入的数据: 家庭名称--%@, 家庭成员角色--%@", [tomFamily valueForKey:@"familyName"], [tomFamily valueForKeyPath:@"member.role"]);
    }
    return 0;
}



kvc进阶方法



一、 accessInstanceVariablesDirectly:是否链式查找_key,_iskey,key,iskey

+ (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索

实验如下,把familyName改成isFamilyName,看下能不能正常设置familyName值和取familyName值:

// 家族
@interface TomFamily : NSObject
{
    NSString* isFamilyName; // familyName => isFamilyName,也可以找到
}
@end
@implementation TomFamily
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TomFamily* tomFamily = [[TomFamily alloc]init];
        [tomFamily setValue:@"Tom家族" forKey:@"familyName"];
        
        NSLog(@"取出存入的数据: 家庭名称--%@",
              [tomFamily valueForKey:@"familyName"]
        );
    }
    return 0;
}

输出如下:

取出存入的数据: 家庭名称--Tom家族

如果+ (BOOL)accessInstanceVariablesDirectly的返回值设置成NO,则找不到familyName就不会去尝试找isFamilyName,测试如下:

@interface TomFamily : NSObject
{
    NSString* isFamilyName;
}
@end
@implementation TomFamily
+ (BOOL)accessInstanceVariablesDirectly
{
    return NO; // 改成NO则编译报错,找不到familyName
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TomFamily* tomFamily = [[TomFamily alloc]init];
        [tomFamily setValue:@"Tom家族" forKey:@"familyName"];
        
        NSLog(@"取出存入的数据: 家庭名称--%@",
              [tomFamily valueForKey:@"familyName"]
        );
    }
    return 0;
}

运行结果,报错

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<TomFamily 0x1004387c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key familyName.' 



二、validateValue:检验setter是否合法;

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因

代码演示:验证familyName是否大于5个字符,否则不设值

@interface TomFamily : NSObject
{
    NSString* familyName;
}
@end
@implementation TomFamily
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError{
    NSString* str = *ioValue;
    if (str.length > 5) {
        return YES;
    } else {
        NSLog(@"输入字符数:%d,不大于5", str.length);
        return NO;
    }
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSError* error;
        TomFamily* tomFamily = [[TomFamily alloc]init];
        NSString* value = @"Tom家族";
        if ([tomFamily validateValue:&value forKey:@"familyName" error:&error]) {
            [tomFamily setValue:value forKey:@"familyName"]; // 存值
        }
        NSLog(@"取出存入的数据: 家庭名称--%@",
              [tomFamily valueForKey:@"familyName"] // 取值并输出到控制台
        );
    }
    return 0;
}

输出结果

2020-07-11 12:48:24.830761+0800 kvcAndKvo[2489:3405389] 输入字符数:5,不大于5



三、mutableArrayValueForKey,用于取mutableArray类型的值

示例代码如下

@interface TomFamily : NSObject
{
    NSString* _familyName;
    NSMutableArray* _membersArr;
}
@end
@implementation TomFamily
-(id)init{
    if (self == [super init]){
        _membersArr = [[NSMutableArray alloc]initWithArray:@[@"father",@"mother",@"daughter"]];
    }
    return self;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSError* error;
        TomFamily* tomFamily = [[TomFamily alloc]init];
        NSLog(@"取出存入的数据: 家庭成员--%@",
              [tomFamily mutableArrayValueForKey:@"membersArr"] // 取值并输出到控制台
        );
    }
    return 0;
}

运行结果:

2020-07-11 17:31:26.427154+0800 kvcAndKvo[4997:3444350] 取出存入的数据: 家庭成员--(
    father,
    mother,
    daughter
)



四、 – (nullable id)valueForUndefinedKey:(NSString *)key,如果没有找到对应的key,则会调用这个方法

下面代码演示读取一个不存在的属性

testName

@interface TomFamily : NSObject
{
    NSString* familyName;
    NSMutableArray* membersArr;
}
@end
@implementation TomFamily
- (id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"你正在读取一个不存在的属性:%@", key);
    [super valueForUndefinedKey:key];
    return nil;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSError* error;
        TomFamily* tomFamily = [[TomFamily alloc]init];
        [tomFamily valueForKey:@"testName"];
    }
    return 0;
}

输出结果

2020-07-11 17:41:18.756170+0800 kvcAndKvo[5769:3455381] 你正在读取一个不存在的属性:testName
2020-07-11 17:41:18.761467+0800 kvcAndKvo[5769:3455381] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:



五 – (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

//和上一个方法一样,只不过是设值,不赘述



– (void)setNilValueForKey:(NSString *)key;

//如果你在SetValue方法时面给Value传nil,则会调用这个方法,不赘述



六、 – (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典;

以下是一个示例,把family中的familyName、address、tel三个属性取出,并返回一个字典对象。类似于lodash库中的pick函数:

@interface TomFamily : NSObject
{
    NSString* familyName;
    NSString* address;
    NSString* tel;
}
@end
@implementation TomFamily
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSError* error;
        TomFamily* tomFamily = [[TomFamily alloc]init];
        [tomFamily setValuesForKeysWithDictionary:@{@"familyName":@"tom家族",@"address":@"北京",@"tel":@"15201923378"}];
        NSLog(@"批量取值:%@", [tomFamily dictionaryWithValuesForKeys:@[@"familyName",@"address",@"tel"]]);
    
    }
    return 0;
}



版权声明:本文为u013161241原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。