当前位置: 首页 > news >正文

「iOS」——KVC

iOS学习

  • 前言
  • KVC模式
    • KVC设值
    • KVC取值
    • KVC使用keyPath
    • KVC处理异常
      • 处理不存在的key
      • 处理nil异常
    • KVC处理字典
    • KVC高阶消息传递
  • 总结

前言

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


KVC模式

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

以下是KVC的常用方法:

- (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设值,下面给出代码演示:

#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异常;

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

#import <Foundation/Foundation.h>@interface AUser1 : NSObject{@packageNSString *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; 方法来获取值。
下面来探究取值顺序:

#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的两个方法:

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

下面我们来以代码举例:
我们先创建一个BUser:

#import <Foundation/Foundation.h>@interface BUser : NSObject
@property (nonatomic, copy) NSString *strb1;
@property (nonatomic, copy) NSString *strb2;
@end

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

#import <Foundation/Foundation.h>
#import "BUser.h"@interface AUser : NSObject
{BUser *bUser;
}
@end

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

#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。


#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异常。

#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: 方法则用于根据属性键数组获取对象的属性值并返回对应的字典。
#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高阶消息传递

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

#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方法,虽然在性能上差一点,但是在编码上更加的灵活,简洁,可以批量操作并且可以在运行时动态地访问和操作对象的属性。

相关文章:

  • 使用 pypdf 给 PDF 添加目录书签
  • 搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(四)-搜索
  • 基于Hive和Hadoop的图书分析系统
  • nodejs逐字读取文件示例
  • 防火墙详解(三)华为防火墙基础安全策略配置(命令行配置)
  • 如何恢复被删除的 GitLab 项目?
  • 前端Vue.js与后端Flask/Django协同开发指南
  • 修改DNS地址有什么影响
  • 选择更轻松:山海鲸可视化与PowerBI的深度对比
  • RP2040 C SDK GPIO和IRQ 唤醒功能使用
  • Angular与Vue的全方位对比分析
  • uni-app 封装websocket 心跳检测,开箱即用
  • 原码反码补码移码
  • 快速创建第一个Spring Boot 项目
  • 【Python】Flask-Admin:构建强大、灵活的后台管理界面
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • input的行数自动增减
  • JavaScript 基本功--面试宝典
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • PHP变量
  • Python_网络编程
  • Python连接Oracle
  • Python中eval与exec的使用及区别
  • spring + angular 实现导出excel
  • ⭐ Unity + OpenCV 实现实时图像识别与叠加效果
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 构造函数(constructor)与原型链(prototype)关系
  • 关于List、List?、ListObject的区别
  • 机器学习学习笔记一
  • 前端工程化(Gulp、Webpack)-webpack
  • 通信类
  • 一份游戏开发学习路线
  • puppet连载22:define用法
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • ​​​​​​​​​​​​​​Γ函数
  • # dbt source dbt source freshness命令详解
  • #if #elif #endif
  • #vue3 实现前端下载excel文件模板功能
  • #控制台大学课堂点名问题_课堂随机点名
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (NSDate) 时间 (time )比较
  • (Qt) 默认QtWidget应用包含什么?
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (九)c52学习之旅-定时器
  • (南京观海微电子)——示波器使用介绍
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)
  • (一)UDP基本编程步骤
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • .NET 2.0中新增的一些TryGet,TryParse等方法
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .Net OpenCVSharp生成灰度图和二值图