重学OC第十五篇:KVC

  • 时间:
  • 来源:互联网
  • 文章标签:

KVC

  • KVC简介
  • 一、键值编码基础
    • 1.1 访问对象属性
    • 1.2 访问集合属性
    • 1.3 使用集合运算符
    • 1.4 表示非对象值。
    • 1.5 属性验证(仅OC)
  • 二、KVC访问器搜索模式
    • 2.1 基础Getter(valueForKey:)
    • 2.2 基础Setter(setValue:forKey:)
    • 2.3 可变数组( mutableArrayValueForKey:)
    • 2.4 可变有序集合(mutableOrderedSetValueForKey:)
    • 2.5 可变集合(mutableSetValueForKey:)
  • 总结

KVC简介

键值编码是NSKeyValueCoding非正式协议支持的一种机制,对象采用这种机制来提供对其属性的间接访问。当对象符合键值编码时,可以通过简洁、统一的消息传递接口使用字符串参数来访问其属性。这种间接访问机制补充了实例变量及其关联的访问器方法提供的直接访问。当对象从NSObject继承(直接或间接)时,它们通常采用键值编码,该对象既采用NSKeyValueCoding协议,又为基本方法提供默认实现。

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

一、键值编码基础

1.1 访问对象属性

通过key或keyPath访问对象属性。key必须使用ASCII编码,不能包含空格,并且通常以小写字母开头。在Swift中,可以使用#keyPath表达式来代替使用字符串来表示key或keyPath,这提供了编译时检查的优势。

@interface BankAccount : NSObject
 
@property (nonatomic) NSNumber* currentBalance;              // An attribute
@property (nonatomic) Person* owner;                         // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
 
@end

[myAccount setValue:@(100.0) forKey:@"currentBalance"];
//返回相对于接收者的键数组的值。该方法为数组中的每个键调用valueForKey:。返回的NSDictionary包含数组中所有键的值。
[myAccount dictionaryWithValuesForKeys:@"transactions"];

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

1.2 访问集合属性

可以使用valueForKey:和setValue:forKey :(或它们的等效键路径)一样,获取或设置集合对象,但是,当您要操纵这些集合的内容时,通常使用协议定义的可变代理方法最有效。

  • mutableArrayValueForKey:和mutableArrayValueForKeyPath:
    它们返回行为类似于NSMutableArray对象的代理对象。
  • mutableSetValueForKey:和mutableSetValueForKeyPath:
    它们返回行为类似于NSMutableSet对象的代理对象。
  • mutableOrderedSetValueForKey:和 mutableOrderedSetValueForKeyPath:
    它们返回行为类似于NSMutableOrderedSet对象的代理对象。

1.3 使用集合运算符

在这里插入图片描述

  • 聚合运算符:@avg、@count、@max、@min、@sum
    聚合运算符处理数组或一组属性,产生一个反映集合某些方面的值。
NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];
NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];
  • 数组运算符:@distinctUnionOfObjects、@unionOfObjects
    数组运算符使valueForKeyPath:返回一个对象数组,该对象数组与右键路径指示的一组特定对象相对应。如果使用数组运算符时任何叶子对象为nil,则valueForKeyPath:方法将引发异常。
NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];
  • 嵌套运算符: @distinctUnionOfArrays、@unionOfArrays、@distinctUnionOfSets
    嵌套运算符对嵌套集合进行操作,其中集合本身的每个条目都包含一个集合。
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];
NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];

1.4 表示非对象值。

NSObject提供的键-值编码协议方法的默认实现可同时用于对象和非对象属性。默认实现自动在对象参数或返回值与非对象属性之间转换。由于Swift中的所有属性都是对象,因此本节仅介绍Objective-C属性。
在这里插入图片描述
上图是基本类型包装为NSNumber对象中的部分内容,主要注意BOOL在macOS下由于历史原因会作为charValue来访问,所以在macOS下仅传递一个NSNumber对象,例如@(1)或@(YES)。
在这里插入图片描述
iOS可自动转换NSPoint、NSRange、NSSize,对于自定义的结构体可通过包装到NSValue来访问。

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"];

1.5 属性验证(仅OC)

由于特定于属性的验证方法通过引用接收值和错误参数,因此验证具有三种可能的结果:

  1. 验证方法认为值对象有效,并且在不更改值或错误的情况下返回YES。
  2. 验证方法认为值对象无效,但选择不对其进行更改。在这种情况下,该方法返回NO并将错误引用(如果由调用者提供)设置为指示错误原因的NSError对象。在尝试设置错误引用之前,请始终对其进行测试。
  3. 验证方法认为值对象无效,但创建了一个新的有效对象作为替换。在这种情况下,该方法将返回YES,同时使错误对象保持不变。在返回之前,该方法将值引用修改为指向新值对象。进行修改时,即使值对象是可变的,该方法也总是创建一个新对象,而不是修改旧对象。
- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
    if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2)) {
        if (outError != NULL) {
            *outError = [NSError errorWithDomain:PersonErrorDomain
                                            code:PersonInvalidNameCode
                                        userInfo:@{ NSLocalizedDescriptionKey
                                                    : @"Name too short" }];
        }
        return NO;
    }
    return YES;
}

仅在Objective-C中使用此处描述的验证。在Swift中,属性验证更多地使用依赖于编译器对可选选项和强类型检查的支持,同时使用内置的willSet和didSet属性观察器来测试任何运行时API合约。

通常,键值编码协议及其默认实现均未定义任何机制来自动执行验证,而是在适合您的应用程序时使用验证方法。某些Cocoa技术在某些情况下会自动执行验证, 比如CoreData保存托管对象上下文时,自动执行验证。

二、KVC访问器搜索模式

2.1 基础Getter(valueForKey:)

  1. get<Key><key>is<Key>_<key>的顺序搜索访问器方法,找到第一个就调用它进入第5步,没找到就接着走第2步。
  2. 如果找不到简单的访问器方法,在实例中搜索名称与模式countOf <Key>objectIn <Key> AtIndex :<key> AtIndexes :的方法。 如果找到其中的第一个以及其他两个中的至少一个,请创建一个响应所有NSArray方法的集合代理对象,并返回该对象。否则,请继续执行步骤3。
  3. 如果未找到简单的访问器方法或数组访问方法组,请查找名为countOf <Key>,enumeratorOf <Key>和memberOf <Key>的三类方法。
    如果找到所有三个方法,请创建一个响应所有NSSet方法的集合代理对象,并返回该对象。否则,请继续执行步骤4。
  4. 如果没有找到简单的访问器方法或集合访问方法组,并且接收方的类方法accessInstanceVariablesDirectly返回YES,则搜索名为_ <key>,_ is <Key>,<key>或is <Key>的实例变量,以该顺序。如果找到,请直接获取实例变量的值,然后继续执行步骤5。否则,请继续执行步骤6
  5. 如果检索到的属性值是对象指针,则只需返回结果。如果该值是NSNumber支持的标量类型,则将其存储在NSNumber实例中并返回它。如果结果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象。
  6. 如果其他所有方法均失败,则调用valueForUndefinedKey:。默认情况下,这会引发一个异常,但是NSObject的子类可以提供特定于key的行为。
    在这里插入图片描述

2.2 基础Setter(setValue:forKey:)

  1. 按此顺序查找第一个名为set <Key>:或_set <Key>的访问器。如果找到,请使用输入值(或根据需要解包的值)调用它并完成。
  2. 如果找不到简单的访问器,并且类方法accessInstanceVariablesDirectly返回YES,则按该顺序查找名称类似于_ <key>,_ is <Key>,<key>或is <Key>的实例变量。如果找到,则直接使用输入值(或展开值)设置变量并完成操作。
  3. 在找不到访问器或实例变量后,调用setValue:forUndefinedKey:。默认情况下,这会引发一个异常,但是NSObject的子类可以提供特定于key的行为。
    在这里插入图片描述

2.3 可变数组( mutableArrayValueForKey:)

  1. 查找insertObject:in<Key>AtIndex: 和removeObjectFrom<Key>AtIndex:,或者查找insert<Key>:atIndexes: 和remove<Key>AtIndexes:。至少找到一个插入和删除方法,通过向mutableArrayValueForKey:原始接收方发送四个方法的某种组合来返回一个响应NSMutableArray消息的代理对象。当接收到mutableArrayValueForKey:消息的对象还实现了一个可选的replace对象方法,其名称类似于replaceObjectIn <Key> AtIndex:withObject:或replace <Key> AtIndexes:with <Key>:时,代理对象也将在适当的时候使用它们提升性能。
  2. 如果对象没有可变数组方法,请查找名称与模式set <Key>:相匹配的访问器方法。在这种情况下,返回一个代理对象,该对象通过向mutableArrayValueForKey:的原始接收者发出set <Key>:消息来响应NSMutableArray消息。此步骤中描述的机制比上一步的机制效率低得多,因为它可能涉及重复创建新的集合对象而不是修改现有的集合对象。因此,在设计自己的键值编码兼容对象时,通常应避免使用它。
  3. 如果既没有找到可变数组方法,也没有找到访问器,并且接收方的类对accessInstanceVariablesDirectly响应为YES,则按该顺序搜索名称类似于_ <key>或<key>的实例变量。如果找到了这样的实例变量,则返回一个代理对象,该代理对象将接收到的每个NSMutableArray消息转发到该实例变量的值,该值通常是NSMutableArray或其子类之一的实例。
  4. 如果其他所有方法均失败,则每当接收到NSMutableArray消息时,就向mutableArrayValueForKey:消息的原始接收者返回发出setValue:forUndefinedKey:消息的可变集合代理对象。setValue:forUndefinedKey:的默认实现会引发NSUndefinedKeyException,但是子类可能会覆盖此行为。

2.4 可变有序集合(mutableOrderedSetValueForKey:)

基本与可变数组( mutableArrayValueForKey:)相同,只是对应的变为mutableOrderedSetValueForKey和NSMutableOrderedSet。

2.5 可变集合(mutableSetValueForKey:)

  1. 搜索add<Key>Object: 和 remove<Key>Object:以及add <Key>:和remove <Key> :,如果找到至少一种添加方法和至少一种移除方法,则返回一个代理对象,对于收到的每个NSMutableSet消息该对象发送四种方法的某种组合消息给mutableSetValueForKey的原始接收者。
    代理对象还使用名称为intersect <Key>:或set <Key>:之类的方法(如果有)来获得最佳性能
  2. 如果mutableSetValueForKey:调用的接收者是托管对象,则搜索模式不会像非托管对象那样继续。有关更多信息,请参见Core Data Programming Guide中的Managed Object Accessor Methods。
  3. 如果找不到可变的set方法,并且该对象不是托管对象,则搜索名称类似于set <Key>:的访问器方法。如果找到了这样的方法,则返回的代理对象针对接收到的每个NSMutableSet消息,向mutableSetValueForKey:的原始接收者发送set <Key>:消息。(尽量避免,效率低)
  4. 如果未找到可变的set方法和accessor方法,并且accessInstanceVariablesDirectly类方法返回YES,则按该顺序搜索名称如_ <key>或<key>的实例变量。如果找到了这样的实例变量,则代理对象会将收到的每个NSMutableSet消息转发到该实例变量的值,该值通常是NSMutableSet的实例或其子类之一。
  5. 如果所有其他方法均失败,则返回的代理对象通过将setValue:forUndefinedKey:消息发送到mutableSetValueForKey:的原始接收者来响应它收到的任何NSMutableSet消息。

总结

  • 键值编码是NSKeyValueCoding非正式协议支持的一种机制,对象采用这种机制来提供对其属性的间接访问。
  • 在OC中通过使用字符串表示的key或keyPath访问对象属性。在Swift中,使用#keyPath表达式。
  • NSObject提供的键-值编码协议方法的默认实现可同时用于对象和非对象属性。默认实现自动在对象参数或返回值与非对象属性之间转换。(OC)
  • 对访问器搜索模式的解释。

参考文档:Key-Value Coding Programming Guide
扩展知识:DIS_KVC_KVO根据IOS Foundation框架汇编反写的KVC、KVO实现,加深对KVC、KVO机制的理解,理解oc runtime在这些机制实现中的重要作用。

本文链接http://www.taodudu.cc/news/show-1781920.html