「iOS」——KVC

iOS学习

前言

对KVC模式的简单学习和总结。


KVC模式

KVC(Key-Value Coding,键值编码)是一种通过字符串来访问对象属性的机制,允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。可以在运行时动态地访问和修改对象的属性。而不是在编译时确定。

以下是KVC的常用方法:

objectivec 复制代码
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath设置值
- (void)setValue:(id)value forKey:(NSString *)key;//通过key设置值
- (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath获取值
- (id)valueForKey:(NSString *)key;//通过key获取值

KVC设值

我们通过- (void)setValue:(id)value forKey:(NSString *)key;方法来为KVC设值,下面给出代码演示:

objectivec 复制代码
#import <Foundation/Foundation.h>

@interface AUser : NSObject

@property (nonatomic, copy) NSString *str1;
@property (nonatomic, copy) NSString *str2;

@end

#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *user = [[AUser alloc] init];
        [user setValue:@"Astr1" forKey:@"str1"];
        [user setValue:@"Astr2" forKey:@"str2"];
        
        NSLog(@"str1:%@",[user valueForKey:@"str1"]);
        NSLog(@"str2:%@",[user valueForKey:@"str2"]);
         
    }
    return 0;
}

运行结果为:

那么KVC设置的逻辑原理是什么呢?

如上图所示:

  1. 首先会按照setKey、_setKey的顺序查找方法,如找到方法,则直接调用方法并赋值;
  2. 未找到方法,则调用+ (BOOL)accessInstanceVariablesDirectly(是否可以直接访问成员变量,默认返回YES);
  3. 若accessInstanceVariablesDirectly方法返回YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到直接赋值,找不到则抛出NSUnknowKeyExpection异常;
  4. 若accessInstanceVariablesDirectly方法返回NO,那么就会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常;

我们来验证一些处理逻辑:

objectivec 复制代码
#import <Foundation/Foundation.h>

@interface AUser1 : NSObject{
    @package
    NSString *name;
    NSString *_name;
}
@end


#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser1 *aUser = [[AUser1 alloc] init];
        
        [aUser setValue:@"strName1" forKey:@"_name"];
        [aUser setValue:@"strName2" forKey:@"name"];
        
        NSLog(@"name = %@", aUser->name);
        NSLog(@"_name = %@", aUser->_name);
        
    }
    return 0;
}

KVC取值

我们通过- (id)valueForKey:(NSString *)key; 方法来获取值。

下面来探究取值顺序:

objectivec 复制代码
#import "AUser.h"

@implementation AUser

-(int) getAge{
    return 9999;
}

-(int) age
{
    return 999;
}

-(int) isAge
{
    return 99;
}
@end


#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *user = [[AUser alloc] init];

        [user setValue:@"9" forKey:@"age"];
        
        NSLog(@"age:%@",[user valueForKey:@"age"]);
        
    }
    return 0;
}

运行后:

注释掉getAge后:

注释掉age方法后:

再注释掉isAge方法后:

原理:

  1. 首先会按照getKey、key、isKey、_key的顺序查找方法,找到直接调用取值
  2. 若未找到,则查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出NSUnknowKeyExpection异常;
  3. 若返回的YES,则按照_ key、_isKey、key、isKey的顺序查找成员变量,找到则取值;
  4. 找不到则调用valueForUndefinedKey:抛出NSUnknowKeyExpection异常;

KVC使用keyPath

面对复杂的嵌套属性进行初始化赋值时,如果使用key一层层赋值十分麻烦。我们可以采用keyPath来访问对象的嵌套属性。

keyPath的两个方法:

objectivec 复制代码
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath设置值
- (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath获取值

下面我们来以代码举例:

我们先创建一个BUser:

objectivec 复制代码
#import <Foundation/Foundation.h>

@interface BUser : NSObject
@property (nonatomic, copy) NSString *strb1;
@property (nonatomic, copy) NSString *strb2;
@end

我们再创建一个AUser嵌套一个BUser:

objectivec 复制代码
#import <Foundation/Foundation.h>
#import "BUser.h"

@interface AUser : NSObject
{
    BUser *bUser;
}
@end

此时,AUser对象中,含有一个嵌套属性,我们可以使用keyPath进行为嵌套对象赋值并且取值:

objectivec 复制代码
#import <Foundation/Foundation.h>
#import "AUser.h"
#import "BUser.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *aUser = [[AUser alloc] init];
        BUser *bUser = [[BUser alloc] init];
        [aUser setValue:bUser forKey:@"bUser"];
        [aUser setValue:@"b1" forKeyPath:@"bUser.strb1"];
        [aUser setValue:@"b2" forKeyPath:@"bUser.strb2"];
        NSLog(@"b1:%@",[aUser valueForKeyPath:@"bUser.strb1"]);
        NSLog(@"b1:%@",[aUser valueForKeyPath:@"bUser.strb2"]);

    }
    return 0;
}

打印结果:

KVC处理异常

处理不存在的key

在上面我们说过KVC设置的顺序。如果最后没有找到相应的成员变量,则会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常来结束程序。

我们只需要重写- (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key 这个方法,则不会产生Crash。

objectivec 复制代码
#import "AUser1.h"
@implementation AUser1{
    int age;
}

- (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key {
    NSLog(@"重写了setValue:value forUndefinedKey方法");
}
@end

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        AUser1 *aUser = [[AUser1 alloc] init];
        //处理不存在的key
        [aUser setValue:@"strName1" forKey:@"1"];
        
    }
    return 0;
}

运行结果:

处理nil异常

我们可以将nil赋值给字符串类型,但是不能复制给int或NSInteger类型。如果需要为对象赋nil时,则需要自己处理一下nil异常的部分,例如给int类型赋nil的情况。

当我们给int类型赋值nil时,就会出现异常,会执行-(void)setNilValueForKey:(NSString *)key这个方法,使程序崩溃,所以我们通常需要重写这个方法来处理nil异常。

objectivec 复制代码
#import "AUser1.h"

@implementation AUser1{
    int age;
}

-(void) setNilValueForKey:(NSString *)key
{
    if ([key isEqualToString:@"age"]) {
        age = 0;
    } else {
        [super setNilValueForKey:key];
    }
}
@end


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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser1 *aUser = [[AUser1 alloc] init];
        
        [aUser setValue:nil forKey:@"age"];
        
        NSLog(@"age = %@", [aUser valueForKey:@"age"]);
    }
    return 0;
}

运行结果:

KVC处理字典

我们可以通过字典进行批量的设值取值操作。

  • setValuesForKeysWithDictionary: 方法用于将字典中的值赋给对象的属性,
  • dictionaryWithValuesForKeys: 方法则用于根据属性键数组获取对象的属性值并返回对应的字典。
objectivec 复制代码
#import <Foundation/Foundation.h>

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

@end

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person = [[Person alloc] init];
        //使用KVC批量存值
        [person setValue:@"Kobe" forKey:@"name"];
        [person setValue:@"man" forKey:@"sex"];
        [person setValue:@"24" forKey:@"age"];
        NSDictionary* firstDictionary = [person dictionaryWithValuesForKeys:@[@"name", @"sex", @"age"]];
        NSLog(@"dictonary = %@", firstDictionary);
        
        //使用KVC批量赋值
        NSDictionary* secondDictionary = @{@"name":@"瑞娜", @"age":@2, @"sex": @"woman"};
        Person* secondPerson = [[Person alloc] init];
        [secondPerson setValuesForKeysWithDictionary:secondDictionary];
        NSLog(@"name  = %@, age = %ld, sex = %@", secondPerson.name, secondPerson.age, secondPerson.sex);
        
    }
    return 0;
}

KVC高阶消息传递

通俗来讲就是让数组中的每一个元素都执行某个方法,并把结果返回到新的数组中。这里我们实现将数组中的每个首字母大写,并且返回长度。

objectivec 复制代码
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSArray* arrStr = @[@"reyna",@"jett",@"Neon"];
        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;
}

运行结果:


总结

KVC相比于setter和getter方法,虽然在性能上差一点,但是在编码上更加的灵活,简洁,可以批量操作并且可以在运行时动态地访问和操作对象的属性。

相关推荐
1telescope18 小时前
MacBook 安装 nvm 管理 Node.js 多版本教程
macos·node.js
1telescope18 小时前
MacBook 安装 Oh My Zsh 完整教程
macos·mac
蜜汁小强19 小时前
macOS 上的git代理配置在哪里
git·macos·代理模式·proxy模式
蜜汁小强20 小时前
macOS 上升级到 python 3.12
开发语言·python·macos
上天_去_做颗惺星 EVE_BLUE20 小时前
Android设备与Mac/Docker全连接指南:有线到无线的完整方案
android·linux·macos·adb·docker·容器·安卓
2501_9160088920 小时前
iOS开发APP上架全流程解析:从开发到App Store的完整指南
android·ios·小程序·https·uni-app·iphone·webview
goodmao21 小时前
【macOS】【磁盘空间整理】查看大文件夹
macos
七夜zippoe21 小时前
Cython终极性能优化指南:从Python到C++的混合编程实战
c++·python·macos·cython·类型系统·内存视图
2501_915909062 天前
Charles 抓不到包怎么办?iOS 调试过程中如何判断请求路径
android·ios·小程序·https·uni-app·iphone·webview
2501_916007472 天前
iOS和iPadOS文件管理系统全面解析与使用指南
android·ios·小程序·https·uni-app·iphone·webview