【iOS】KVC的学习

devtools/2024/9/25 16:07:05/

【iOS】KVC的学习

文章目录

  • 【iOS】KVC的学习
    • 前言
    • KVC
      • 定义
      • KVC设值
      • KVC取值
      • KVC使用keyPath
      • KVC处理异常
        • 处理nil异常
      • KVC的一些应用
        • 修改动态的设置值
        • 实现高阶的消息传递
    • 小结

前言

笔者简单学习了有关与KVC的相关内容,这里写一篇博客简单介绍一下相关内容。

KVC

定义

KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。很多高级的iOS开发技巧都是基于KVC实现的。

KVC的定义是通过对于NSObject的扩展来实现的,下面是几个有关于KVC最重要的四个方法:

- (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来设值

这里我们从KVC设置值开始说起:

KVC设值

首先我们要清楚KVC是怎么实现一个设置值的效果的,这里我们举一个例子:[test setValue:@"1223" forKey:@"str"];这条语句在执行的时候,KVC到底执行了那些代码。

  • 程序优先调用set:属性值方法,代码通过setter方法完成设置。注意,这里的是指成员变量名,首字母大小写要符合KVC的命名规则,下同
  • 如果没有找到setName:方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为**_str** 的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以_命名的变量,KVC都可以对该成员变量赋值。
  • 如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索isStr的成员变量。

所以简而言之就是这个设置值的函数查找成员变量的顺序就是顺序查找名称类似于 _<key>_is<Key><key>is<Key> 的实例变量。只要能找到一个满足条件的成员变量就会给这个成员变量设置我们的一个值,下面给一段代码来讲解一下。

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface TestObject : NSObject {@publicNSString* isAge;//NSString* _age;
}
@property NSString* str;
@property NSArray* ary;
@endNS_ASSUME_NONNULL_END
#import "TestObject.h"@implementation TestObject
@end#import <Foundation/Foundation.h>
#import "TestObject.h"
int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...TestObject* test = [[TestObject alloc] init];[test setValue:@[] forKey:@"ary"];[test setValue:@"1223" forKey:@"str"];[test setValue:@"12" forKey:@"age"];NSLog(@"%@", test->isAge);//NSLog(@"%@", [test valueForKey:@"_age"]);}return 0;
}

打印结果:

12
Type: Notice | Timestamp: 2024-09-17 16:31:02.784471+08:00 | Process: KVC | Library: KVC | TID: 0xda0dbb

可以看到上面这段代码虽然我们不是一个名字为age的标题,但是还是能打印出一个结果。

然后我们修改一下成员变量部分

@interface TestObject : NSObject {@publicNSString* isAge;NSString* _age;
}

主函数

int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...TestObject* test = [[TestObject alloc] init];[test setValue:@[] forKey:@"ary"];[test setValue:@"1223" forKey:@"str"];[test setValue:@"12" forKey:@"age"];NSLog(@"%@", test->isAge);NSLog(@"%@", test->_age);//NSLog(@"%@", [test valueForKey:@"_age"]);NSLog(@"%@", test.str);NSLog(@"%@", test.ary);}return 0;
}

(null)
Type: Notice | Timestamp: 2024-09-17 17:14:26.416628+08:00 | Process: KVC | Library: KVC | TID: 0xda5d69

12
Type: Notice | Timestamp: 2024-09-17 17:14:26.416670+08:00 | Process: KVC | Library: KVC | TID: 0xda5d69

可以看到我们的isAge这个变量打印出了一个(null)而我们下面的age这个变量则打印出来一个12也就是我们想要的值,这里就说明KVC方法的取值是有一个固定的顺序的。也就是我们上面提到的 _<key>_is<Key><key>is<Key>

同时我们这里如果不想让我们的一个代码实现一个KVC的话,我们可以通过设置+ (BOOL)accessInstanceVariablesDirectly这种方法让他永远返回NO,这样就可以让这个类无法使用KVC。我们先重写下面几个方法,主函数仍旧是上面那一个主函数。

+ (BOOL)accessInstanceVariablesDirectly {return NO;
}
- (id)valueForUndefinedKey:(NSString *)key {NSLog(@"出现异常");return nil;
}
- (void)setValue:(id)value forKey:(NSString *)key {NSLog(@"出现异常,无法设置");
}

打印结果:

出现异常,无法设置。
Type: Notice | Timestamp: 2024-09-17 18:47:05.235218+08:00 | Process: KVC | Library: KVC | TID: 0xdaa7bd

出现异常,无法设置
Type: Notice | Timestamp: 2024-09-17 18:47:05.235285+08:00 | Process: KVC | Library: KVC | TID: 0xdaa7bd

出现异常,无法设置
Type: Notice | Timestamp: 2024-09-17 18:47:05.235285+08:00 | Process: KVC | Library: KVC | TID: 0xdaa7bd

(null)
Type: Notice | Timestamp: 2024-09-17 18:47:05.235326+08:00 | Process: KVC | Library: KVC | TID: 0xdaa7bd

(null)
Type: Notice | Timestamp: 2024-09-17 18:47:05.235326+08:00 | Process: KVC | Library: KVC | TID: 0xdaa7bd

(null)
Type: Notice | Timestamp: 2024-09-17 18:47:05.235326+08:00 | Process: KVC | Library: KVC | TID: 0xdaa7bd

(null)
Type: Notice | Timestamp: 2024-09-17 18:47:05.235340+08:00 | Process: KVC | Library: KVC | TID: 0xdaa7bd

重写这个方法后,如果我们的KVC找不到set< Key >这个方法之后就不会继续向下寻找了。

在这里插入图片描述

正如上图所示,KVC的设值大概就是上图的过程。

KVC取值

有关于KVC取值,一般采用

  • 首先按get< Key >,< key>,is < Key>的顺序方法查找getter方法,找到的话会直接调用。如果是BOOL或者Int等值类型, 会将其包装成一个NSNumber对象。
  • 如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_,_is,is的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。

一般情况按照这三个顺序进行一个查找,笔者这里给出函数。

#import "TestObject.h"@implementation TestObject
-(int)getAge {return 1222;
}
-(int)age {return 120;
}
-(int)isAge {return 10;
}
@end
#import <Foundation/Foundation.h>
#import "TestObject.h"
int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...TestObject* test = [[TestObject alloc] init];[test setValue:@[] forKey:@"ary"];[test setValue:@"1223" forKey:@"str"];[test setValue:@"12" forKey:@"age"];NSLog(@"%@",[test valueForKey:@"age"]);//NSLog(@"%@", test->_age);//NSLog(@"%@", [test valueForKey:@"_age"]);NSLog(@"%@", test.str);//NSLog(@"%@", test.ary);}return 0;
}

打印结果:

1222
Type: Notice | Timestamp: 2024-09-18 22:58:51.536732+08:00 | Process: KVC | Library: KVC | TID: 0xdf2afb

返回的是我们getAge的方法返回的函数。

这时候我们注释getAge,然后重新运行一下

120
Type: Notice | Timestamp: 2024-09-18 23:00:38.285681+08:00 | Process: KVC | Library: KVC | TID: 0xdf33e6

最后我们在注释age,在运行一下得到

10
Type: Notice | Timestamp: 2024-09-18 23:02:06.668767+08:00 | Process: KVC | Library: KVC | TID: 0xdf3d01

最后我们注释isAge,运行一下可以得到。

12
Type: Notice | Timestamp: 2024-09-18 23:05:09.897443+08:00 | Process: KVC | Library: KVC | TID: 0xdf4c04

KVC使用keyPath

有时候我们的要改变的对象可能是比较复杂的,比如说自定义类或者是其他复杂的数据类型,我们如果使用key来一层一层的监控的话,会非常复杂,这时候就出现了keyPath这个方法来简化代码。

如下面的代码,我们先定义一个新的类。

#import <Foundation/Foundation.h>
#import "TestObject.h"
NS_ASSUME_NONNULL_BEGIN@interface NextText : NSObject
@property TestObject* test;
@endNS_ASSUME_NONNULL_END

上面这个自定义类存储了一个属性是我们之前创建的对象。

int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...TestObject* test = [[TestObject alloc] init];NextText* test1 = [[NextText alloc] init];[test1 setValue:test forKey:@"test"];[test1 setValue:@"1222" forKeyPath:@"test.str"];NSLog(@"%@", [test1 valueForKeyPath:@"test.str"]);}return 0;
}

打印结果:

1222
Type: Notice | Timestamp: 2024-09-19 18:24:49.998580+08:00 | Process: KVC | Library: KVC | TID: 0xe07e3c

这里可以看到我们在这里也是成功设置了有关keyPath的内容,然后也通过keyPath来实现了获取对应的数值。所以说我们仅仅需要用一个点语法的形式就可以实现一个修改对应路径的一个值。

KVC处理异常

在一般情况下,我们是不允许KVC来给一个对象赋值为nil的,这时候,我们可能需要自己去处理一下nil异常的部分。比方说,我们传入一个nil给NSInteger的对象的时候,就会出现一个异常的问题,会执行-(void)setNilValueForKey:(NSString *)key这个方法,这个方法会产生一个Crash,所以我们通常需要重写这个方法。

处理nil异常
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface TestObject : NSObject {@publicNSInteger _age;
}
@property NSString* str;
@property NSArray* ary;
@endNS_ASSUME_NONNULL_END
#import "TestObject.h"@implementation TestObject
- (void)setNilValueForKey:(NSString *)key {if ([key isEqualToString:@"age"]) {NSLog(@"不能设置成nil状态%@", key);_age = 11;} else {[super setNilValueForKey:key];}
}
@end
#import <Foundation/Foundation.h>
#import "TestObject.h"
#import "NextText.h"
int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...TestObject* test = [[TestObject alloc] init];[test setValue:nil forKey:@"age"];NSLog(@"%@", [test valueForKey:@"age"]);//NSLog(@"%@", test.ary);}return 0;
}

然后执行代码的打印结果是:

不能设置成nil状态age
Type: Notice | Timestamp: 2024-09-19 19:31:52.434134+08:00 | Process: KVC | Library: KVC | TID: 0xe16087

11
Type: Notice | Timestamp: 2024-09-19 19:31:52.434305+08:00 | Process: KVC | Library: KVC | TID: 0xe16087

Program ended with exit code: 0
Type: stdio

KVC的一些应用

修改动态的设置值

这是KVC最简单的应用。

实现高阶的消息传递

首先,我们要明白对于容器使用一个KVC,并没有对于我们的容器进行一个操作,实际上是将这个方法传递给容器中的每一个元素,然后再重新返回一个容器,所以这里我们实现一些特殊的效果,通过操作一个容器来返回一个符合我们需求的容器。

int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...NSArray* arrStr = @[@"english",@"franch",@"chinese"];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);}//NSLog(@"%@", test.ary);}return 0;
}

打印结果:

English
Type: Notice | Timestamp: 2024-09-19 19:52:26.638075+08:00 | Process: KVC | Library: KVC | TID: 0xe198e4
Franch
Type: Notice | Timestamp: 2024-09-19 19:52:26.638130+08:00 | Process: KVC | Library: KVC | TID: 0xe198e4
Chinese
Type: Notice | Timestamp: 2024-09-19 19:52:26.638150+08:00 | Process: KVC | Library: KVC | TID: 0xe198e4
7
Type: Notice | Timestamp: 2024-09-19 19:52:26.638865+08:00 | Process: KVC | Library: KVC | TID: 0xe198e4
6
Type: Notice | Timestamp: 2024-09-19 19:52:26.638895+08:00 | Process: KVC | Library: KVC | TID: 0xe198e4
7
Type: Notice | Timestamp: 2024-09-19 19:52:26.638912+08:00 | Process: KVC | Library: KVC | TID: 0xe198e4
Program ended with exit code: 0
Type: stdio

可以看到这里我们通过KVC来实现了一个让容器中所有字符串第一个字符大写的容器。

小结

笔者在这里简单介绍了一下有关于KVC的使用,之后还会继续学习相关的内容。


http://www.ppmy.cn/devtools/117044.html

相关文章

爬虫技术初步自学

目的 本篇文章实际上自学爬虫技术的学习一份学习笔记&#xff0c;希望可以对后学的小白起到帮助&#xff0c;也希望得到大佬的指点&#xff0c;若有错漏希望大佬指出。 初步认知 爬虫实际上是一个计算机程序。开发爬虫程序的常用语言是Python。&#xff08;Python我已经在五…

Spring IoC 配置类 总结

1. 简介 Spring中可以使用配置类完全代替xml来配置IoC容器。 2. 代码 以下代码实现了定义配置并引用外部类&#xff0c;并从外部文件赋值。 ComponentScan("com.jojo.ioc")//确定扫描范围 PropertySource(value "classpath:jdbc.properties")//指定外…

初识elasticsearch

初识elasticsearch 1.什么是elasticsearch 一个开源的分布式搜索引擎&#xff0c;可以用来实现搜索、日志统计、分析、系统监控等功能&#xff1b;elasticsearch 是结合kibana、Logstash、Beats,也就是elastic stach(ELK)。被广泛应用在日志数据分析、实时监控等领域。 elastic…

【计算机网络】详解TCP/IP分层模型局域网和跨网络通信的原理

一、网络协议 两个概念&#xff1a;交换机&#xff1a;实现位于同一个子网中的主机数据交换。路由器&#xff1a;实现数据包的跨网络转发。 两台主机的距离变远了&#xff0c;会引发出一系列问题&#xff1a; 1、如何使用数据的问题 2、数据的可靠性问题 3、主机定位问题 4、…

IOS 25 实现歌单详情(UITableView)列表 ②

歌单详情完整效果 歌单详情歌单列表头部图片背景效果 本节是在文章 IOS 24 实现歌单详情&#xff08;UITableView&#xff09;列表 的基础上&#xff0c;实现歌单详情里面的歌单列表头部Cell和图片背景效果。 歌单列表头部Cell实现 实现流程&#xff1a; 1.创建Cell&#xff…

ssm协同办公系统-计算机毕业设计源码42133

摘要 随着信息技术的迅猛发展&#xff0c;协同办公系统在企业和组织中扮演着越来越重要的角色。本研究旨在基于SSM框架、Java编程语言和MySQL数据库&#xff0c;设计和开发一个协同办公系统。首先&#xff0c;介绍了协同办公系统的背景和意义&#xff0c;详细阐述了系统的功能模…

基于AI+多技术融合在流域生态系统服务评价、水文水生态分析、碳收支、气候变化影响、制图等领域中的实践应用

流域生态系统服务在环境保护与资源管理中具有不可替代的重要性。随着全球气候变化和人类活动对自然环境的压力日益增大&#xff0c;流域生态系统的稳定性和健康状况面临严峻挑战。水资源短缺、洪水频发、水质污染、生物多样性减少等问题&#xff0c;正在威胁流域内及其下游区域…

如何使用Postman搞定带有token认证的接口实战!

现在许多项目都使用jwt来实现用户登录和数据权限&#xff0c;校验过用户的用户名和密码后&#xff0c;会向用户响应一段经过加密的token&#xff0c;在这段token中可能储存了数据权限等&#xff0c;在后期的访问中&#xff0c;需要携带这段token&#xff0c;后台解析这段token才…