iOS--kvc学习

什么是kvc

iOS 中的键值编码(Key-Value Coding,KVC)是一种机制,它允许开发者通过字符串键来访问对象的属性和值,而不是直接使用访问器方法或实例变量。KVC 提供了一种简单而灵活的方式来操作对象的属性,减少了样板代码和提供了动态性。

但就目前我学到的看来,kvc机制的调用更像是系统自动寻找对应的方法来实现对对象属性和值的访问 ;

以下是 KVC 的一些重要概念和用法:

  • 访问属性:使用 valueForKey: 方法可以获取对象的属性值,而使用 setValue:forKey:
    方法可以设置对象的属性值。KVC 会自动搜索适当的访问器方法或实例变量来完成操作。
  • 键路径:键路径允许通过多级属性访问对象的嵌套属性。例如,可以使用 valueForKeyPath: 方法来获取一个对象的嵌套属性的值。
  • 集合操作:KVC 提供了一些集合操作符,例如 @avg、@sum、@max、@min、@distinctUnionOfObjects 和
    @unionOfObjects,用于对集合进行聚合操作。
  • 容器类属性:KVC 支持操作容器类属性,如数组和字典。可以使用 mutableArrayValueForKey: 和
    mutableDictionaryValueForKey: 方法来获取可变的容器对象,以便进行添加、删除和替换等操作。
  • 异常处理:在使用 KVC 时,如果访问或设置的属性不存在,会引发 UndefinedKey 异常。可以通过实现
    setValue:forUndefinedKey: 和 valueForUndefinedKey: 方法来自定义处理该异常。

KVC 会自动搜索适当的访问器方法或实例变量来完成操作。,通过这个我们其实也可以的出一个猜想:在理论上,使用键值编码(Key-Value Coding,KVC)可以访问对象的私有属性。KVC是一种通过字符串键来访问对象的属性和值的机制,它不依赖于属性的可见性修饰符(如public、private等)。

  • 然而,需要注意的是,直接访问私有属性可能违反了封装原则,破坏了对象的设计意图。私有属性是为了实现对象内部的逻辑和隐藏实现细节而存在的,直接访问私有属性可能导致意外的行为或不可预测的结果。
  • 另外,使用KVC访问私有属性也会增加代码的脆弱性,因为私有属性的名称和实现可能会在未来的版本中发生变化,这会导致KVC操作失败或引发运行时错误。

kvc的一些概念和用法

KVC的定义都是对NSObject的扩展来实现的,Objective-C中有个显式的NSKeyValueCoding类别名,所以对于所有继承了NSObject的类型,都能使用KVC(一些纯Swift类和结构体是不支持KVC的,因为没有继承NSObject)

NSKeyValueCoding类别中的一些方法

- (nullable id)valueForKey:(NSString *)key;                          //直接通过Key来取值

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

- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值

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

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

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

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。

- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。

- (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转到字典。

kvc设置值(set)

KVC要设值,那么就要对象中对应的key,KVC在内部是按什么样的顺序来寻找key的。当调用setValue:属性值 forKey:@”name“的代码时,底层的执行机制如下:

1.程序优先调用set:属性值方法,代码通过setter方法完成设置。注意,这里的是指成员变量名,首字母大小写要符合KVC的命名规则,下同

2.如果没有找到setName:方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为_的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以_命名的变量,KVC都可以对该成员变量赋值。

3.如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量。

4.和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。

5.如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。

简单来说就是如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作。
如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUndefinedKey:方法。

#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;
}

在这里插入图片描述

accessInstanceVariablesDirectly为NO的时候KVC只会查询setter和getter这一层,下面寻找key的相关变量执行就会停止,直接报错。

kvc取值(get)

当调用valueForKey:@”name“的代码时,KVC对key的搜索方式不同于setValue:属性值 forKey:@”name“,其搜索方式如下:

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

如果上面的getter没有找到,KVC则会查找countOf,objectInAtIndex或AtIndexes格式的方法。如果countOf方法和另外两个方法中的一个被找到,那么就会返回一个可以响应NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子类),调用这个代理集合的方法,或者说给这个代理集合发送属于NSArray的方法,就会以countOf,objectInAtIndex或AtIndexes这几个方法组合的形式调用。还有一个可选的get:range:方法。所以你想重新定义KVC的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合KVC的标准命名方法,包括方法签名。

如果上面的方法没有找到,那么会同时查找countOf,enumeratorOf,memberOf格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合,和上面一样,给这个代理集合发NSSet的消息,就会以countOf,enumeratorOf,memberOf组合的形式调用。

如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_,_is,is的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+ (BOOL)accessInstanceVariablesDirectly返回NO的话,那么会直接调用valueForUndefinedKey:方法,默认是抛出异常。

#import <Foundation/Foundation.h>

@interface Test: NSObject {
}

@end

@implementation Test

- (NSUInteger)isAge {
    return 10;
}

@end

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

在这里插入图片描述

KVC的KeyPath方法

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

KVC 提供了一系列的 Key Path 相关方法,用于在对象的属性之间进行导航和操作。Key Path 是一种用于描述对象属性层级关系的字符串表示形式,它允许通过多级属性路径来访问嵌套属性。

valueForKeyPath::通过指定的 Key Path 获取对象的属性值。可以使用点语法来描述多级属性路径,例如 “address.street”。
setValue:forKeyPath::通过指定的 Key Path 设置对象的属性值。可以使用点语法来描述多级属性路径,例如 “address.street”。
mutableArrayValueForKeyPath::通过指定的 Key Path 获取可变数组的引用。如果指定的属性是一个数组,该方法返回一个可变数组,可以直接对其进行添加、删除和替换操作。
mutableDictionaryValueForKeyPath::通过指定的 Key Path 获取可变字典的引用。如果指定的属性是一个字典,该方法返回一个可变字典,可以直接对其进行添加、删除和替换操作。
validateValue:forKeyPath:error::通过指定的 Key Path 验证给定的属性值是否有效。可以使用此方法来执行属性值的验证,并在验证失败时获取错误信息。

#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;
}

在这里插入图片描述

KVC处理nil异常

通常情况下,KVC不允许你要在调用setValue:属性值 forKey:(或者keyPath)时对非对象传递一个nil的值。很简单,因为值类型是不能为nil的。如果你不小心传了,KVC会调用setNilValueForKey:方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

Person *person = [[Person alloc] init];

// 设置属性值为 nil
[person setValue:nil forKey:@"name"];

// 获取属性值
NSString *name = [person valueForKey:@"name"];
NSLog(@"Name: %@", name); // 输出:Name: (null)

// 设置非对象类型属性为 nil
[person setValue:nil forKey:@"age"];

// 获取属性值
NSInteger age = [[person valueForKey:@"age"] integerValue];
NSLog(@"Age: %ld", (long)age); 

在这里插入图片描述

KVC处理UndefinedKey异常

在键值编码(Key-Value Coding,KVC)中,如果使用 setValue:forKey: 或 valueForKey: 方法时,对象不存在指定的键(Key),KVC 会引发 Undefined Key 异常。
当你使用 setValue:forKey: 方法给对象设置属性值时,如果对象的类没有对应的属性或访问器方法(Accessor Method)来处理指定的键,KVC 会调用对象的 setValue:forUndefinedKey: 方法,并传递相应的参数,包括未定义的键(Undefined Key)和要设置的属性值。你可以在该方法中自定义处理未定义的键情况,例如抛出异常或执行其他逻辑。
同样地,当使用 valueForKey: 方法从对象中获取属性值时,如果对象的类没有对应的属性或访问器方法来处理指定的键,KVC 会调用对象的 valueForUndefinedKey: 方法,并传递相应的参数,包括未定义的键。你可以在该方法中自定义处理未定义的键情况,例如返回默认值或执行其他逻辑。

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end

@implementation Person

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"Undefined Key: %@, Value: %@", key, value);
    // 这里可以自定义处理未定义的键情况
}

- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(@"Undefined Key: %@", key);
    // 这里可以自定义处理未定义的键情况
    return nil; // 返回默认值或其他逻辑
}

@end

Person *person = [[Person alloc] init];

// 设置未定义的键
[person setValue:@"John" forKey:@"firstName"]; // 调用 setValue:forUndefinedKey:

// 获取未定义的键
NSString *name = [person valueForKey:@"lastName"]; // 调用 valueForUndefinedKey:

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

valueForKey:总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。
这两个类会处理从数字,布尔值到指针和结构体任何类型。然后开以者需要手动转换成原来的类型。
尽管valueForKey:会自动将值类型封装成对象,但是setValue:forKey:却不行。你必须手动将值类型转换成NSNumber或者NSValue类型,才能传递过去。
因为传递进去和取出来的都是id类型,所以需要开发者自己担保类型的正确性,运行时Objective-C在发送消息的会检查类型,如果错误会直接抛出异常。

//  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

KVC键值验证

- (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;//验证Key对应的Value是否可用
#import <Foundation/Foundation.h>

@interface Test: NSObject {
    NSUInteger _age;
}

@end

@implementation Test

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
    NSNumber *age = *ioValue;
    if (age.integerValue == 10) {
        return NO;
    }
    
    return YES;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //Test生成对象
        Test *test = [[Test alloc] init];
        //通过KVC设值test的age
        NSNumber *age = @10;
        NSError* error;
        NSString *key = @"age";
        BOOL isValid = [test validateValue:&age forKey:key error:&error];
        if (isValid) {
            NSLog(@"键值匹配");
            [test setValue:age forKey:key];
        }
        else {
            NSLog(@"键值不匹配");
        }
        //通过KVC取值age打印
        NSLog(@"test的年龄是%@", [test valueForKey:@"age"]);
        
    }
    return 0;
}
  • KVC是不会自动调用键值验证方法的,就是说我们如果想要键值验证则需要手动验证。但是有些技术,比如CoreData会自动调用。

KVC处理集合和字典

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中提到了在严格模式下使用map的function声明时,this指向undefined。这可能导致一些问题。解决方法是将map里面的function声明修改为箭头函数,因为箭头函数没有自己的this,它会继承父级作用域的this。这样就可以避免this指向undefined的问题。 引用中提到了setValue:ForKey:是KVC的主要方法,而setObject:ForKey:是NSMutableDictionary特有的方法。两者之间有一些区别。如果你是在使用NSMutableDictionary,应该使用setObject:ForKey:来设置对象的值。而如果你是在使用KVC,应该使用setValue:ForKey:来设置对象的值。 引用中提到了在GetNav方法中使用map遍历数组,并为每个元素动态生成一列数据,并为每一列数据添加了onClick事件。但在运行过程中报错说updateRoute为undefined。根据错误信息来看,可能是updateRoute这个方法未定义。需要检查代码中是否有定义这个方法,并且确保被正确引用。 综上所述,问题的解决方法包括: 1. 将map里面的function声明修改为箭头函数,以解决严格模式下this指向undefined的问题。 2. 根据具体情况选择使用setValue:ForKey:或setObject:ForKey:方法来设置对象的值。 3. 检查代码中是否正确定义了updateRoute方法,并确保正确引用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [TypeError:Cannot read properties of undefined(reading XXX)](https://blog.csdn.net/qq_57558631/article/details/124961465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [iOS中setValue和setObject的区别详解](https://download.csdn.net/download/weixin_38701407/12786722)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值