iOS KVC底层原理

2020-02-11  本文已影响0人  Joker_King

什么是KVC?

KVC的全称叫Key-Value Coding,也叫做键值编码,在apple官方文档中是这么解释的。

键值编码是NSKeyValueCoding非正式协议支持的一种机制,对象采用这种机制来提供对其属性的间接访问。当对象符合键值编码时,可通过简洁,统一的消息传递接口通过字符串参数访问其属性,这种间接访问机制补充了实例变量及其关联的访问器方法提供的直接访问。

通常,您使用访问器方法来访问对象的属性。一个get访问器(或getter)从一个属性返回值,一个set访问器(或setter)给一个属性设置值。在Objective-C中,您还可以直接访问属性的基础实例变量。以任何一种方式访问​​对象属性都很简单,但是需要调用特定于属性的方法或变量名。随着属性列表的增加或更改,访问这些属性的代码也必须如此。相反,与键值编码兼容的对象提供了一个简单的消息传递接口,该接口在其所有属性之间都是一致的。

键值编码是许多其他Cocoa技术的基础概念,例如键值观察,Cocoa绑定,Core Data和AppleScript-ability。在某些情况下,键值编码还可以帮助简化代码。

1、访问对象的属性

@interface BankAccount : NSObject
 
@property (nonatomic) NSNumber* currentBalance;              // 一个属性
@property (nonatomic) Person* owner;                         // 一对一的关系
@property (nonatomic) NSArray< Transaction* >* transactions; // 一对多的关系
 
@end

为了维护封装,对象通常为其接口上的属性提供访问器方法。 对象的作者可以显式地编写这些方法,也可以依靠编译器自动合成它们。 无论哪种方式,使用这些访问器之一的代码作者都必须在编译属性名称之前将其写入代码。 访问器方法的名称成为使用它的代码的静态部分。 例如,上述代码中银行行帐户对象,编译器将合成一个可以为myAccount实例调用的设置器:

[myAccount setCurrentBalance:@(100.0)];

这是最直接的,但缺乏灵活性。 另一方面,符合键值编码的对象提供了一种更通用的机制,可以使用字符串标识符访问对象的属性。

1.1、使用Key和KeyPaths径识别对象的属性

key是标识特定属性的字符串。 通常,按照约定,代表属性的键是该属性本身在代码中出现的名称。 key必须使用ASCII编码,不能包含空格,并且通常以小写字母开头(尽管有例外,例如在许多类中找到的URL属性)。

由于上面代码中的BankAccount类符合键值编码,因此它可以识别这些key键的ownercurrentBalancetransactions,这是其属性的名称。 您可以通过其键设置值,而不是调用setCurrentBalance:方法:

[myAccount setValue:@(100.0) forKey:@"currentBalance"];

1.2、使用key获取属性的值

当对象采用NSKeyValueCoding协议时,它符合键值编码。继承自NSObject的对象(提供了该协议的基本方法的默认实现)会自动采用具有某些默认行为的该协议。这样的对象至少实现以下基于键的基本getter:

集合对象(例如NSArrayNSSetNSDictionary)不能包含nil作为值。 而是使用NSNull对象表示nil值。 NSNull提供了单个实例,表示对象属性的nil值。 dictionaryWithValuesForKeys:和相关的setValuesForKeysWithDictionary:的默认实现会自动在NSNull(在dictionary参数中)和nil(在存储的属性中)之间转换。

1.3、使用key设置属性的值

getter一样,与键值编码兼容的对象还根据NSObject中提供的NSKeyValueCoding协议的实现,为一小组具有默认行为的定义setter:

在默认实现中,当您尝试将非对象属性设置为nil值时,符合键值编码的对象会向自身发送setNilValueForKey:消息。 setNilValueForKey:的默认实现抛出NSInvalidArgumentException,但是对象重写这个方法,以设置默认值或标记值。

2、访问集合属性

就像访问和设置其他属性一样,您也可以使用valueForKey:setValue:forKey:来访问和设置集合属性的值。但是,当您要操纵这些集合的内容时,通常使用协议定义的可变代理方法最有效。

该协议定义了三种不同的代理对象访问代理方法,每种方法都有一个键和一个键路径变量:

3、集合运算符

当您发送与键值编码兼容的对象valueForKeyPath:消息时,可以将集合运算符嵌入到键路径中。集合运算符是一小部分关键字之一,其后带有一个at符号(@),该符号指定getter在返回数据之前应执行的操作以某种方式处理数据。由NSObject提供的valueForKeyPath:的默认实现会实现此行为。

4、类型转换

当您调用协议的一种getters,例如valueForKey:时,默认实现将根据访问者搜索模式中描述的规则来确定为指定键提供值的特定访问器方法或实例变量。 如果返回值不是对象,则getter使用此值初始化NSNumber对象(用于标量)或NSValue对象(用于结构体),并返回该值。

类似地,默认情况下,使用setValue:forKey之类的setter:在给定特定键的情况下,确定属性的访问器或实例变量所需的数据类型。 如果数据类型不是对象,则设置器首先将适当的<type> Value消息发送到传入值对象以提取基础数据,然后存储该数据。

Data type Creation method Accessor method
BOOL numberWithBool: boolValue (in iOS) charValue (in macOS)*
char numberWithChar: charValue
double numberWithDouble: doubleValue
float numberWithFloat: floatValue
int numberWithInt: intValue
long numberWithLong: longValue
long long numberWithLongLong: longLongValue
short numberWithShort: shortValue
unsigned char numberWithUnsignedChar: unsignedChar
unsigned int numberWithUnsignedInt: unsignedInt
unsigned long numberWithUnsignedLong: unsignedLong
unsigned long long numberWithUnsignedLongLong: unsignedLongLong
unsigned short numberWithUnsignedShort: unsignedShort
Data type Creation method Accessor method
NSPoint valueWithPoint: pointValue
NSRange valueWithRange: rangeValue
NSRect valueWithRect: (macOS only). rectValue
NSSize valueWithSize: sizeValue

4.1、自定义结构体类型的转换

typedef struct {
    float x, y, z;
} ThreeFloats;
 
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end

取值

NSValue* result = [myClass valueForKey:@"threeFloats"];

设置值

ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];

5、KVC的底层原理

5.1、基本getter的搜索模式

当一个对象调用valueForKey:方法取值的时候,他的内部执行以下过程。

5.1、基本setter的搜索模式

setValue:forKey:的默认实现(给定键和值参数作为输入),尝试将名为key的属性设置为value,在使用这个方法设置值时,对象的内部会经历以下流程。

上一篇下一篇

猜你喜欢

热点阅读