zoukankan      html  css  js  c++  java
  • iOS

    1、KVC

    • KVC 是 Key-Value Coding 的简写,是键值编码的意思,属于 runtime 方法。Key Value Coding 是 cocoa 的一个标准组成部分,是间接给对象属性设置数值的方法,它能让我们可以通过 name(key) 的方式访问属性变量, 不必调用明确的属性访问方法, 如我们有个属性变量叫做 foo, 我们可以 foo 直接访问它,同样我们也可以用 KVC 来完成 [Object valueForKey:@“foo”], 这样做主要的好处就是来减少我们的代码量。

    • 程序执行过程中,KVC 动态给对象属性设置数值,不关心属性在 .h 和 .m 中是如何定义的,只要对象有属性,就能够读取和设置。这种方式,有点违背程序的开发原则。

    • 在 iOS 中,用 KVC 用的最多是核心动画,核心动画是通过 KVC 对涂层的可动画属性设置数值来实现的。

    2、数据模型

    • 模型是专门用来存放数据的对象,一般都是一些直接继承自 NSObject 的纯对象,内部会提供一些属性来存放数据,控制器可以直接传递模型给视图控件以显示空间的内容。

    • 1)用模型取代字典的好处

      • 使用字典的坏处:

        • 一般情况下,设置数据和取出数据都使用 “字符串类型的 key”,编写这些 key 时,编辑器没有智能提示,需要手敲,手敲字符串 key,key 容易写错,Key 如果写错了,编译器不会有任何警告和报错,造成设错数据或者取错数据。如:

          	dict[@"name"] = @"Jack";
          	NSString *name = dict[@"name"];
          
      • 使用模型的好处:

        • 所谓模型,其实就是数据模型,专门用来存放数据的对象,用它来表示数据会更加专业。模型设置数据和取出数据都是通过它的属性,属性名如果写错了,编译器会马上报错,因此,保证了数据的正确性
          使用模型访问属性时,编译器会提供一系列的提示,提高编码效率。

          	app.name = @"Jack";
          	NSString *name = app.name;
          
    • 2)字典转模型

      • 字典转模型的过程最好封装在模型内部。

      • 模型应该提供一个可以传入字典参数的构造方法。

        	- (instancetype)initWithDict:(NSDictionary *)dict;
        	+ (instancetype)xxxWithDict:(NSDictionary *)dict;
        
    • 3)字典转模型 KVC 方法

      • 字典转模型:setValuesForKeysWithDictionary
        • 字典中的 key 值需与要赋值的对象的属性变量名相同,并且都为字符串类型。
      • 模型转字典:dictionaryWithValuesForKeys
        • 参数是要被转换到字典中的属性名称
    • 4)KVC 数据模型的设置

      • 为了避免服务端返回的数值型数据是 null,可以把数值型的数据设置成 NSNumber 类型,否则会报错 could not set nil as the value for the key messageId 。

      • id 是服务端最喜欢用的属性,id 在 iOS 中是关键字,但在模型中可以正常使用的。

      • copy 属性,在设置数值的时候,如果有一方是可变的,会默认做一次 copy 操作,会建立新的副本,在模型中对象全都是用 copy 属性会比较安全。

      • 定义为 copy 的属性,重写了 setter 方法之后,定义属性的 copy 就是摆设了,不会默认进行 copy 操作,必须要自己 copy 一下,否则设置数值的时候,不会 copy。

    • 5)字典转模型的过程

      KVC1

    3、KVC 赋值与取值

    • KVC 是一种操作全局变量的方法,无论是公有的,私有的,还是受保护的全都可以操作。

      • 1、找对象的 setter 方法,找到就执行;
      • 2、找不到 setter 方法就找 _name 变量,找到就赋值;
      • 3、如果找不到 _name 变量,就找 name;
      • 4、如果 name 也找不到就会让对象调用 -(void)setValue:forUnderfinedKey; 方法处理异常。
    • Objective-C

      • KvcClass.h

        	@property(nonatomic, assign) NSInteger ID;
        
        	@property(nonatomic, copy) NSString *name;
        	@property(nonatomic, assign) NSInteger age;
        
        	@property(nonatomic, retain) SubKvcClass *subKVC;
        
      • SubKvcClass.h

        	@property(nonatomic, copy) NSString *subName;
        	@property(nonatomic, assign) NSInteger subAge;
        
      • ViewController.m

        	KvcClass *kvcObject = [[KvcClass alloc] init];
            
        	SubKvcClass *subKVCObject = [[SubKvcClass alloc] init];
        	kvcObject.subKVC = subKVCObject;
        
    • Swift

      • KvcClass.swift

        	var ID:NSInteger!
        
        	var name:String!
        	var age:NSInteger = 0
        
        	var subKVC:SubKvcClass!
        
      • SubKvcClass.swift

        	var subName:String!
        	var subAge:NSInteger = 0
        
      • ViewController.swift

        	var kvcObject = KvcClass()
        
        	var subKVCObject = SubKvcClass()
        	kvcObject.subKVC = subKVCObject
        

    3.1 通过 键值编码 给对象的属性动态赋值

    • 必须得有标准的 getter 和 setter 方法,或者用 @property 声明。

    • 调用 setValue: forKey: 方法以字符串的方式向对象发送消息,设置实例变量的值。第一个参数是要设置的值,第二个参数是实例变量的名称。

    • 调用 valueForKey: 来获取实例变量的值。

    • Objective-C

      	// 动态设置属性的值
      	[kvcObject setValue:@"xiao bai" forKey:@"name"];
      	[kvcObject setValue:@"8" forKey:@"age"];
      
      	// 获取实例变量的值
      	NSString *name = [kvcObject valueForKey:@"name"];
      	NSInteger age = [[kvcObject valueForKey:@"age"] integerValue];
      
    • Swift

      	// 动态设置属性的值
      	kvcObject.setValue("xiao bai", forKey: "name")
      	kvcObject.setValue("8", forKey: "age")
          
       	// 获取实例变量的值
      	let name1 = kvcObject.valueForKey("name") as! String
      	let age1 = kvcObject.valueForKey("age") as! NSInteger
      

    3.2 通过 键路径 给实例变量是其他类的对象赋值

    • 如果实例变量中有其他类的对象,那么可以使用 setValue: forKeyPath: 给其他类的对象的属性变量赋值。

    • Objective-C

      	// 通过键路径给 KVCClass 中的对象的属性赋值
      	[kvcObject setValue:@"sub xiao bai" forKeyPath:@"subKVC.subName"];
      	[kvcObject setValue:@"5" forKeyPath:@"subKVC.subAge"];
      
      	// 获取 KVCClass 中的对象的属性值
      	NSString *subName = [kvcObject valueForKeyPath:@"subKVC.subName"];
      	NSInteger subAage = [[kvcObject valueForKeyPath:@"subKVC.subAge"] integerValue];
      
    • Swift

      	// 通过键路径给 KvcClass 中的对象的属性赋值
      	kvcObject.setValue("sub xiao bai", forKeyPath: "subKVC.subName")
      	kvcObject.setValue("5", forKeyPath: "subKVC.subAge")
          
      	// 获取 KvcClass 中的对象的属性值
      	let subName1 = kvcObject.valueForKeyPath("subKVC.subName") as! String
      	let subAage1 = kvcObject.valueForKeyPath("subKVC.subAge") as! NSInteger
      

    3.3 通过 字典 给对象的属性动态赋值

    • 字典中的 key 值需与要赋值的对象的属性变量名相同。并且都为字符串类型。

      	- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
      	- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
      
    • Objective-C

      	NSDictionary *kvcDic = @{@"name":@"xiaobai", @"age":@"6"};
      
      	// 以字典的 key 和 value 值分别作为 kvc 的 key 和 value 存储
      	[kvcObject setValuesForKeysWithDictionary:kvcDic];
      
      	// 取值,获取指定 keys 值对应的 values
      	NSDictionary *dicValue = [kvcObject dictionaryWithValuesForKeys:@[@"name", @"age"]];
      
    • Swift

      	let kvcDic = ["name":"xiaobai", "age":"6"]
      
      	// 以字典的 key 和 value 值分别作为 kvc 的 key 和 value 存储
      	kvcObject.setValuesForKeysWithDictionary(kvcDic)
      
       	// 取值,获取指定 keys 值对应的 values
      	let dicValue = kvcObject.dictionaryWithValuesForKeys(["name", "age"])
      

    4、KVC 异常处理

    4.1 数据冗余处理

    • 在键值编码的类中使用以下两个方法处理 key 值不存在的异常。如果不做处理,编译时系统会报错。

      	- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
      	- (id)valueForUndefinedKey:(NSString *)key;
      
    • Objective-C

      • KvcClass.m

        	// 设置不存在 key 的值
        	- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
            	
            	NSLog(@"key 值 %@ 不存在,无法设置值 !", key);
        	}
        
        	// 获取不存在的 key 的值
        	- (id)valueForUndefinedKey:(NSString *)key {
            	
            	NSLog(@"key 值 %@ 不存在,无法获取值 !", key);
            	
            	return nil;
        	}
        
      • ViewController.m

        	// 对象 kvcObject 没有 score 属性,出现数据异常
        	[kvcObject setValue:@"99" forKey:@"score"];                                             
        	[kvcObject valueForKey:@"score"];
        
        	NSDictionary *kvcDic1 = @{@"name":@"xiaobai", @"age":@"6", @"score":@"100"};
        
        	// 对象 kvcObject 没有 score 属性,出现数据异常                         
        	[kvcObject setValuesForKeysWithDictionary:kvcDic1];                                     
        
    • Swift

      • KvcClass.swift

        	// 设置不存在 key 的值
        	override func setValue(value: AnyObject?, forUndefinedKey key: String) {
            
            	print("key 值 (key) 不存在,无法设置值 !")   
        	}
        
        	// 获取不存在的 key 的值
        	override func valueForUndefinedKey(key: String) -> AnyObject? {
            
            	print("key 值 (key) 不存在,无法获取值 !")
            
            	return nil
        	}
        
      • ViewController.swift

        	// 对象 kvcObject 没有 score 属性,出现数据异常
        	kvcObject.setValue("99", forKey: "score")
        	kvcObject .valueForKey("score")
        
        	// 对象 kvcObject 没有 score 属性,出现数据异常
        	let kvcDic1 = ["name":"xiaobai", "age":"6", "score":"99"]
        	kvcObject.setValuesForKeysWithDictionary(kvcDic1)
        

    4.2 key 为系统关键字处理

    • 在需要处理的数据源中有系统关键字时,在键值编码处理的类中使用以下两个方法处理 key 值为系统关键字的情况。

      	- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
      	- (id)valueForUndefinedKey:(NSString *)key;
      
    • id 是服务端最喜欢用的属性,id 在 iOS 中是关键字,但在模型中可以正常使用的。

    • Objective-C

      • KvcClass.m

        	// 设置不存在 key 的值
        	- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        
            	// id 为系统关键字,用 ID 代替与系统冲突的 id
            	if ([key isEqualToString:@"id"]) {
        
        			// 设置自定义的 key 的值
        			self.ID = [(NSString *)value integerValue];
            	}
        	}
        
        	// 获取不存在的 key 的值
        	- (id)valueForUndefinedKey:(NSString *)key {
        
        		// id 为系统关键字,用 ID 代替与系统冲突的 id
            	if ([key isEqualToString:@"id"]) {
        
        			// 获取自定义的 key 的值
        			return [NSString stringWithFormat:@"%li", self.ID];
            	}
            
            	return nil;
        	}
        
      • ViewController.m

        	// id 为系统关键字
        	[kvcObject setValue:@"3" forKey:@"id"];
        	NSInteger ID1 = [[kvcObject valueForKey:@"id"] integerValue];
        
        	NSDictionary *kvcDic2 = @{@"name":@"xiaobai", @"age":@"6", @"id":@"5"};
        	[kvcObject setValuesForKeysWithDictionary:kvcDic2];
        	NSInteger ID2 = kvcObject.ID;
        
    • Swift

      • KvcClass.swift

        	// 设置不存在 key 的值
        	override func setValue(value: AnyObject?, forUndefinedKey key: String) {
        
        		// id 为系统关键字,用 ID 代替与系统冲突的 id
            	if key == "id" {
        
        			// 设置自定义的 key 的值
        			self.ID = (value as! NSString).integerValue
            	}
        	}
        
        	// 获取不存在的 key 的值
        	override func valueForUndefinedKey(key: String) -> AnyObject? {
        
            	// id 为系统关键字,用 ID 代替与系统冲突的 id
            	if key == "id" {
            	
        			// 获取自定义的 key 的值
        			return NSString(format: "%li", self.ID)
            	}
            
            	return nil
        	}
        
      • ViewController.swift

        	// id 为系统关键字
        	kvcObject.setValue("3", forKey: "id")
        
        	let ID1 = (kvcObject.valueForKey("id") as! NSString).integerValue
        	
        	// id 为系统关键字
        	let kvcDic2 = ["name":"xiaobai", "age":"6", "id":"5"]
        
        	kvcObject.setValuesForKeysWithDictionary(kvcDic2)
        	let ID2 = kvcObject.ID
        

    5、KVC 消息传递

    • valueForKey: 的使用并不仅仅用来取值那么简单,还有很多特殊的用法,集合类也覆盖了这个方法,通过调用 valueForKey: 给容器中每一个对象发送操作消息,并且结果会被保存在一个新的容器中返回,这样我们能很方便地利用一个容器对象创建另一个容器对象。另外,valueForKeyPath: 还能实现多个消息的传递。

    • Objective-C

      	NSArray *array = @[@"10.11", @"20.22"];
          
      	// 结果为 (10, 20)
      	NSArray *resultArray = [array valueForKeyPath:@"doubleValue.intValue"];
      
    • Swift

      	let array:NSArray = ["10.11", "20.22"]
          
      	// 结果为 (10, 20)
      	let resultArray:AnyObject? = array.valueForKeyPath("doubleValue.intValue")
      

    6、KVC 字典转模型 数据冗余处理

    • 字典中元素与模型中的属性数量不想等的情况处理。处理 key 值不存在的异常。如果不做处理,编译时系统会报错。

    6.1 系统方式

    • Objective-C

      	+ (instancetype)newsModelWithDict:(NSDictionary *)dict {
          	id obj = [[self alloc] init];
          
          	[obj setValuesForKeysWithDictionary:dict];
          
          	return obj;
      	}
      
      	/// 重写系统方法
      
      	- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
          
      	}
      

    6.2 列举属性数组方式

    • Objective-C

      	+ (instancetype)newsModelWithDict:(NSDictionary *)dict {
          	id obj = [[self alloc] init];
          	
          	[obj setValueWithDict:dict];
          	
          	return obj;
      	}
      
      	- (instancetype)setValueWithDict:(NSDictionary *)dict {
          
          	// 列出所有使用的属性
          	NSArray *properties = @[@"title", @"digest", @"imgsrc", @"replyCount"];
          
          	for (NSString *key in properties) {
              
              	// 判断字典中是否包含 key
              	if (dict[key] != nil) {
      
              		// 每一个属性使用 kvc 设置数值
               		[self setValue:dict[key] forKey:key];
              	}
          	}
          	return self;
      	}
      

    6.3 运行时动态获取对象属性方式

    • Objective-C

      	+ (instancetype)newsModelWithDict:(NSDictionary *)dict {
          	id obj = [[self alloc] init];
          
          	[obj setValueWithDict:dict];
          
          	return obj;
      	}
      
      	/// 使用运行时动态获取对象属性
      
      	- (instancetype)setValueWithDict:(NSDictionary *)dict {
          
          	unsigned int count = 0;
          
          	// 拷贝对象属性数组(数组名就是指向数组第一个元素的地址)
          	objc_property_t *properties = class_copyPropertyList(self.class, &count);
      
          	// 遍历数组
          	for (unsigned int i = 0; i < count; ++i) {
      
              	// 从数组中获取属性
              	objc_property_t pty = properties[i];
      
              	// 获取属性名称
              	const char *cname = property_getName(pty);
              
      			NSString *key = [NSString stringWithUTF8String:cname];
      
      			if (dict[key] != nil) {
          			[self setValue:dict[key] forKey:key];
      			}
          	}
          
          	// 释放数组
          	free(properties);
          
          		return self;
      		}
      
  • 相关阅读:
    .NET 多线程 Task async await
    .NET5 MVC 文件目录
    Html 问题记录
    vue学习笔记(记录知识点)
    vue调试工具vue-devtools安装及使用
    node.js入坑记录
    vue从0开始笔记
    前端样式css问题记录
    谷歌浏览器chrome console 发送POST/GET请求
    jQuery的请求数据方式
  • 原文地址:https://www.cnblogs.com/QianChia/p/5771049.html
Copyright © 2011-2022 走看看