Objective-C KVC 底层原理探究

1,127 阅读15分钟

我们都知道苹果对于KVC(Key-Value-Coding)部分的源码是不开源的,因此我们不能从源码中直接了解KVC的底层实现原理,但我们可以从苹果的官方文档中去学习并探索其底层实现原理,记住当没有开源代码的情况下,官方文档就是最好的学习资料

Key-Value-Coding的苹果官方文档地址: developer.apple.com/library/arc… )

下面我们从解析官方文档入手开始KVC的原理探究: (一)Accessing Object Properties An object typically specifies properties in its interface declaration, and these properties belong to one of several categories:

Attributes. These are simple values, such as a scalars, strings, or Boolean values. Value objects such as NSNumber and other immutable types such as as NSColor are also considered attributes. To-one relationships. These are mutable objects with properties of their own. An object’s properties can change without the object itself changing. For example, a bank account object might have an owner property that is an instance of a Person object, which itself has an address property. The owner’s address may change without changing the owner reference held by the bank account. The bank account’s owner did not change. Only their address did. To-many relationships. These are collection objects. You commonly use an instance of NSArray or NSSet to hold such a collection, although custom collection classes are also possible. The BankAccount object declared in Listing 2-1 demonstrates one of each type of property.

Listing 2-1Properties of the BankAccount object @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 In order to maintain encapsulation, an object typically provides accessor methods for the properties on its interface. The object’s author may write these methods explicitly or may rely on the compiler to synthesize them automatically. Either way, the author of code using one of these accessors must write the property name into the code before compiling it. The name of the accessor method becomes a static part of the code that is using it. For example, given the bank account object declared in Listing 2-1, the compiler synthesizes a setter that you can invoke for the myAccount instance:

[myAccount setCurrentBalance:@(100.0)]; This is direct, but lacks flexibility. A key-value coding compliant object, on the other hand, offers a more general mechanism to access an object’s properties using string identifiers.

Identifying an Object’s Properties with Keys and Key Paths

A key is a string that identifies a specific property. Typically, by convention, the key representing a property is the name of the property itself as it appears in code. Keys must use ASCII encoding, may not contain whitespace, and usually begin with a lowercase letter (though there are exceptions, such as the URL property found in many classes).

Because the BankAccount class in Listing 2-1 is key-value coding compliant, it recognizes the keys owner, currentBalance, and transactions, which are the names of its properties. Instead of calling the setCurrentBalance: method, you can set the value by its key:

[myAccount setValue:@(100.0) forKey:@"currentBalance"]; In fact, you can set all the properties of the myAccount object with the same method, using different key parameters. Because the parameter is a string, it can be a variable that is manipulated at run-time.

A key path is a string of dot-separated keys used to specify a sequence of object properties to traverse. The property of the first key in the sequence is relative to the receiver, and each subsequent key is evaluated relative to the value of the previous property. Key paths are useful for drilling down into a hierarchy of objects with a single method call.

For example, the key path owner.address.street applied to a bank account instance refers to the value of the street string that is stored in the address of the bank account’s owner, assuming the Person and Address classes are also key-value coding compliant.

NOTE

In Swift, instead of using a string to indicate a key or key path, you can use a #keyPath expression. This offers the advantage of a compile time check, as described in the Keys and Key Paths section of the Using Swift with Cocoa and Objective-C (Swift 3) guide.

Getting Attribute Values Using Keys

An object is key-value coding compliant when it adopts the NSKeyValueCoding protocol. An object that inherits from NSObject, which provides a default implementation of the protocol’s essential methods, automatically adopts this protocol with certain default behaviors. Such an object implements at least the following basic key-based getters:

valueForKey: - Returns the value of a property named by the key parameter. If the property named by the key cannot be found according to the rules described in Accessor Search Patterns, then the object sends itself a valueForUndefinedKey: message. The default implementation of valueForUndefinedKey: raises an NSUndefinedKeyException, but subclasses may override this behavior and handle the situation more gracefully. valueForKeyPath: - Returns the value for the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key—that is, for which the default implementation of valueForKey: cannot find an accessor method—receives a valueForUndefinedKey: message. dictionaryWithValuesForKeys: - Returns the values for an array of keys relative to the receiver. The method calls valueForKey: for each key in the array. The returned NSDictionary contains values for all the keys in the array. NOTE

Collection objects, such as NSArray, NSSet, and NSDictionary, can’t contain nil as a value. Instead, you represent nil values using the NSNull object. NSNull provides a single instance that represents the nil value for object properties. The default implementations of dictionaryWithValuesForKeys: and the related setValuesForKeysWithDictionary: translate between NSNull (in the dictionary parameter) and nil (in the stored property) automatically.

When you use a key path to address a property, if any but the final key in the key path is a to-many relationship (that is, it references a collection), the returned value is a collection containing all the values for the keys to the right of the to-many key. For example, requesting the value of the key path transactions.payee returns an array containing all the payee objects for all the transactions. This also works for multiple arrays in the key path. The key path accounts.transactions.payee returns an array with all the payee objects for all the transactions in all the accounts.

Setting Attribute Values Using Keys

As with getters, key-value coding compliant objects also provide a small group of generalized setters with default behavior based upon the implementation of the NSKeyValueCoding protocol found in NSObject:

setValue:forKey: - Sets the value of the specified key relative to the object receiving the message to the given value. The default implementation of setValue:forKey: automatically unwraps NSNumber and NSValue objects that represent scalars and structs and assigns them to the property. See Representing Non-Object Values for details on the wrapping and unwrapping semantics. If the specified key corresponds to a property that the object receiving the setter call does not have, the object sends itself a setValue:forUndefinedKey: message. The default implementation of setValue:forUndefinedKey: raises an NSUndefinedKeyException. However, subclasses may override this method to handle the request in a custom manner. setValue:forKeyPath: - Sets the given value at the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key receives a setValue:forUndefinedKey: message. setValuesForKeysWithDictionary: - Sets the properties of the receiver with the values in the specified dictionary, using the dictionary keys to identify the properties. The default implementation invokes setValue:forKey: for each key-value pair, substituting nil for NSNull objects as required. In the default implementation, when you attempt to set a non-object property to a nil value, the key-value coding compliant object sends itself a setNilValueForKey: message. The default implementation of setNilValueForKey: raises an NSInvalidArgumentException, but an object may override this behavior to substitute a default value or a marker value instead, as described in Handling Non-Object Values.

Using Keys to Simplify Object Access

To see how key-based getters and setters can simplify your code, consider the following example. In macOS, NSTableView and NSOutlineView objects associate an identifier string with each of their columns. If the model object backing the table is not key-value coding compliant, the table’s data source method is forced to examine each column identifier in turn to find the correct property to return, as shown in Listing 2-2. Further, in the future, when you add another property to your model, in this case the Person object, you must also revisit the data source method, adding another condition to test for the new property and return the relevant value.

Listing 2-2Implementation of data source method without key-value coding

  • (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row { id result = nil; Person *person = [self.people objectAtIndex:row];

    if ([[column identifier] isEqualToString:@"name"]) { result = [person name]; } else if ([[column identifier] isEqualToString:@"age"]) { result = @([person age]); // Wrap age, a scalar, as an NSNumber } else if ([[column identifier] isEqualToString:@"favoriteColor"]) { result = [person favoriteColor]; } // And so on...

    return result; } On the other hand, Listing 2-3 shows a much more compact implementation of the same data source method that takes advantage of a key-value coding compliant Person object. Using only the valueForKey: getter, the data source method returns the appropriate value using the column identifier as a key. In addition to being shorter, it is also more general, because it continues to work unchanged when new columns are added later, as long as the column identifiers always match the model object’s property names.

Listing 2-3Implementation of data source method with key-value coding

  • (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row { return [[self.people objectAtIndex:row] valueForKey:[column identifier]]; }

上面文档中主要说明了:

1.访问对象属性类别说明:

  • (attribute)属性:列如简单的值,如标量、字符串或布尔值。
  • (A to-one relation)一对一的关系:如对象,官方文档中列举了 BankAccount类下的一个对象属性Person。
  • (A to-many relation)一对多的关系,如数组、集合 KVC的探究也是在这几种类型上进行探究。
  1. Getting Attribute Values Using Keys部分主要介绍了使用秘钥访问属性的方法:
  • 使用key值访问:valueForKey
  • 使用键值路径访问:valueForKeyPath
  • 使用字典访问:dictionaryWithValuesForKeys:方法,传入一个数组,数组的元素为代表对应属性Key值的字符串,方法返回键值对字典,如下代码:
//JSBankAccount.h 文件
@interface JSBankAccount : NSObject
@property (nonatomic, copy) NSString* bankName;         // An attribute
@property (nonatomic, strong) JSPerson* owner;          // A to-one relation
@property (nonatomic, strong) NSArray* dayArray;        // A to-many relation

@end

//JSPerson.h文件
@interface JSPerson : NSObject
@property (nonatomic, copy) NSString* name;
@end

//注意因为文档中明确说明dictionaryWithValuesForKeys内部会对每一个array元素即Key值进行调用方法valueForKey,所以array里面的元素不能为键值路径;
//测试:
    JSBankAccount *methodTest = [[JSBankAccount alloc] init];
    //属性直接赋值,调用setter方法
    methodTest.bankName = @"工商银行";
    methodTest.owner = [[JSPerson alloc] init];
    methodTest.owner.name = @"Julian";
   
    //valueForKey 方法访问
    NSLog(@"name =%@", [methodTest valueForKey:@"bankName"]);
    //valueForKeyPath 访问
    NSLog(@"%@",[methodTest valueForKeyPath:@"owner.name"]);
    //dictionaryWithValuesForKeys 方法访问,owner.name不能作为arr数组里面元素值,否则调用dictionaryWithValuesForKeys方法会报错valueForUndefinedKey;
    NSArray * arr = @[@"bankName",@"owner"];
    NSLog(@"%@",[methodTest dictionaryWithValuesForKeys:arr]);
  1. Setting Attribute Values Using Keys 使用密钥设置属性值:
  • setValue:forKey:根据指定的键值把接收消息的对象相对应的值设置为给定值
  • setValue:forKeyPath:根据键值路径把接收消息的对象相对应的值设置为给定值
  • setValuesForKeysWithDictionary:根据字典键值对一一把接收消息的对象相对应的值设置为给定值,此方法会遍历字典,默认实现会调用setValue:forKey方法 ·注意:没有找到对应的key值是会执行setValue:forUndefinedKey:方法,并报错

(二)Accessing Collection Properties (访问集合属性)

  • mutableArrayValueForKey: and mutableArrayValueForKeyPath:
  • mutableSetValueForKey: and mutableSetValueForKeyPath:
  • mutableOrderedSetValueForKey: and mutableOrderedSetValueForKeyPath:

(三)Aggregation Operators(聚合运算符):

  • @avg +<.key>:返回算术平均值
  • @count:返回集合元素个数,如果后面指定了正确的.key路径,.key路径将被忽略
  • @max +<.key>:指定@max操作符时,valueForKeyPath:在按正确的键路径命名的集合条目中搜索,并返回最大的条目。搜索使用compare:方法进行比较,如许多Foundation类(如NSNumber类)所定义的那样。因此,由正确的密钥路径指示的属性必须包含对此消息作出有意义的响应的对象。搜索将忽略空值集合项
  • @min +<.key>: 指定@min操作符时,valueForKeyPath:在按正确的密钥路径命名的集合条目中搜索,并返回最小的条目。搜索使用compare:方法进行比较,如许多Foundation类(如NSNumber类)所定义的那样。因此,由正确的密钥路径指示的属性必须包含对此消息作出有意义的响应的对象。搜索将忽略空值集合项。
  • @sum +<.key>: 指定@sum操作符时,valueForKeyPath:读取由集合中每个元素的正确键路径指定的属性,将其转换为双精度值(用0代替nil值),并计算这些值的和。然后返回存储在NSNumber实例中的结果。

(四)Array Operators(数组运算符:数组操作符导致valueForKeyPath:返回一个对象数组,该对象对应于由正确的键路径指示的特定对象集):

  • @distinctUnionOfObjects:指定@distinctUnionOfObjects操作符时,valueForKeyPath:创建并返回一个数组,其中包含与正确密钥路径指定的属性相对应的集合的不同对象。
  • @unionOfObjects:指定@unionOfObjects操作符时,valueForKeyPath:创建并返回一个数组,其中包含由正确的键路径指定的属性对应的集合的所有对象。与@distinctUnionOfObjects不同,重复的对象不会被删除

(五)Nesting Operators 嵌套操作符(嵌套操作符对嵌套集合进行操作,其中集合本身的每个条目都包含一个集合。)

  • @distinctUnionOfArrays:指定@distinctUnionOfArrays操作符时,valueForKeyPath:创建并返回一个数组,其中包含由正确的键路径指定的属性对应的所有集合的不同对象组合。
  • @unionOfArrays:指定@unionOfArrays操作符时,valueForKeyPath:创建并返回一个数组,其中包含与正确密钥路径指定的属性对应的所有集合的所有对象的组合,而不删除重复项。
  • @distinctUnionOfSets:指定@distinctUnionOfSets操作符时,valueForKeyPath:创建并返回一个NSSet对象,该对象包含由正确密钥路径指定的属性对应的所有集合的不同组合对象。
//JSTeather.h
typedef struct {
    float x, y, z;
} ThreeFloats;

@interface JSTeather : NSObject
@property (nonatomic, copy)   NSString          *name;
@property (nonatomic, copy)   NSString          *subject;
@property (nonatomic, copy)   NSString          *nick;
@property (nonatomic, assign) int               age;
@property (nonatomic, assign) int               height;
@property (nonatomic, strong) NSMutableArray    *penArr;
@property (nonatomic)         ThreeFloats       threeFloats;
@end


//ViewController.m
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    JSBankAccount *methodTest = [[JSBankAccount alloc] init];
    //属性直接赋值,调用setter方法
    methodTest.bankName = @"工商银行";
    methodTest.owner = [[JSPerson alloc] init];
    methodTest.owner.name = @"Julian";
    /*
     使用秘钥访问属性的方法
     */
    //1.valueForKey访问
    NSLog(@"name =%@", [methodTest valueForKey:@"bankName"]);
    //2.valueForKeyPath 访问
    NSLog(@"%@",[methodTest valueForKeyPath:@"owner.name"]);
    //3.dictionaryWithValuesForKeys 方法访问
    NSArray * arr = @[@"bankName",@"owner"];
    NSLog(@"%@",[methodTest dictionaryWithValuesForKeys:arr]);
    
    /*
     使用密钥设置属性值方法探究
     */
    //1.setValue:forKey:方法设置
    [methodTest setValue:@"中国银行" forKey:@"bankName"];
    NSLog(@"bankName@setValue:%@",methodTest.bankName);
    //2.setValue:forKeyPath:方法设置
    [methodTest setValue:@"xiaoming" forKeyPath:@"owner.name"];
    NSLog(@"owner.name@setValue:forKeyPath:===%@",methodTest.owner.name);
    //2.setValuesForKeysWithDictionary:方法设置
    NSDictionary *keyValuesDic = @{@"bankName":@"广发银行"};
    [methodTest setValuesForKeysWithDictionary:keyValuesDic];
    NSLog(@"setValuesForKeysWithDictionary:===%@",methodTest.bankName);
    
    //访问数组
    [methodTest setValue:@[@1,@2,@3,@4,@5] forKey:@"dayArray"];
    NSLog(@"dayArray:===%@",[methodTest valueForKey:@"dayArray"]);
    NSLog(@"dayArray@mutableArrayValueForKeyPath:===%@",[methodTest mutableArrayValueForKey:@"dayArray"]);
    
   
    // 聚合运算符测试
//    [self aggregation_operators_test];
    //数组运算符测试
//    [self arrayOperator];
    //嵌套数组
//    [self arrayNesting];
    //嵌套set
    [self setNesting];
    // Do any additional setup after loading the view.
}

//聚合运算符 @avg、@count、@max、@min、@sum
- (void)aggregation_operators_test{
    NSMutableArray *testArray = [NSMutableArray array];
    for (int i = 0; i<10; i++) {
        JSTeather *teather = [[JSTeather alloc]init];
        NSDictionary *dic = @{
            @"name":[NSString stringWithFormat:@"酷走天涯_%d",i],
            @"age":@(18+i),
            @"nick":[NSString stringWithFormat:@"Julian_%d",i],
            @"height":@(170 + i),
        };
        [teather setValuesForKeysWithDictionary:dic];
        [testArray addObject:teather];
    }
    NSLog(@"testArray====%@", testArray);
    //返回一个数组,包含对每个接收方的元素调用-valueForKey:的结果
    NSLog(@"height===%@", [testArray valueForKey:@"height"]);
    
    //身高平均值
    float avg =[[testArray valueForKeyPath:@"@avg.height"] floatValue];
    NSLog(@"avg height===%f",avg);
    //元素个数
    int count = [[testArray valueForKeyPath:@"@count"] intValue];
    NSLog(@"@count.height===%d", count);
    //求和敏
    int sum = [[testArray valueForKeyPath:@"@sum.height"] intValue];
    NSLog(@"%d", sum);
    //最大值
    int max = [[testArray valueForKeyPath:@"@max.height"] intValue];
    NSLog(@"@max.height===%d", max);
    //最小值
    int min = [[testArray valueForKeyPath:@"@min.height"] intValue];
    NSLog(@"@max.height===%d", min );
}

// 数组操作符 @distinctUnionOfObjects @unionOfObjects
- (void)arrayOperator{
    NSMutableArray *teatherArray = [NSMutableArray array];
    for (int i = 0; i < 10; i++) {
        JSTeather *teather = [[JSTeather alloc] init];
            NSDictionary* dic = @{
                @"name":[NSString stringWithFormat:@"酷走天涯_%d",i],
                @"age":@(18+i),
                @"nick":[NSString stringWithFormat:@"Julian_%d",i],
                @"height":@(170 + i%3),
            };
        [teather setValuesForKeysWithDictionary:dic];
        [teatherArray addObject:teather];
    }
    NSLog(@"testArray====%@", teatherArray);
    NSLog(@"height====%@", [teatherArray valueForKey:@"height"]);
    // 返回操作对象指定属性的集合, 不去重
    NSArray* arr1 = [teatherArray valueForKeyPath:@"@unionOfObjects.height"];
    NSLog(@"@unionOfObjects.height== %@", arr1);
    // 返回操作对象指定属性的集合 -- 去重
    NSArray* arr2 = [teatherArray valueForKeyPath:@"@distinctUnionOfObjects.height"];
    NSLog(@"@distinctUnionOfObjects.height== %@", arr2);
    
}

// 嵌套集合(array&set)操作 @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets
- (void)arrayNesting{
    NSMutableArray *teatherArray1 = [NSMutableArray array];
    for (int i = 0; i < 10; i++) {
        JSTeather *teather = [[JSTeather alloc] init];
            NSDictionary* dic = @{
                @"name":[NSString stringWithFormat:@"酷走天涯_%d",i],
                @"age":@(18+i),
                @"nick":[NSString stringWithFormat:@"Julian_%d",i],
                @"height":@(170 + i),
            };
        [teather setValuesForKeysWithDictionary:dic];
        [teatherArray1 addObject:teather];
    }
    
    NSMutableArray *teatherArray2 = [NSMutableArray array];
    for (int i = 0; i < 10; i++) {
        JSTeather *teather = [[JSTeather alloc] init];
            NSDictionary* dic = @{
                @"name":[NSString stringWithFormat:@"酷酷_%d",i],
                @"age":@(18+i),
                @"nick":[NSString stringWithFormat:@"Son_%d",i],
                @"height":@(180 + i%3),
            };
        [teather setValuesForKeysWithDictionary:dic];
        [teatherArray2 addObject:teather];
    }
    
    // 嵌套数组
    NSArray* nestArr = @[teatherArray1, teatherArray2];
    //嵌套数组执行valueForKey方法,返回结果也是嵌套数组,调用的是NSArray的分类方法
    NSArray *test = [nestArr valueForKey:@"height"];
    NSLog(@"嵌套数组height===%@",test);
    //不去重
    NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.height"];
    NSLog(@"@distinctUnionOfArrays.height== %@", arr);
    // 去重
    NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.height"];
    NSLog(@"@unionOfArrays.height== %@", arr1);
}

- (void)setNesting{
    NSMutableSet *teatherSet1 = [NSMutableSet set];
    for (int i = 0; i < 10; i++) {
        JSTeather *teather = [[JSTeather alloc]init];
        NSDictionary* dic = @{
            @"name":[NSString stringWithFormat:@"酷走天涯_%d",i],
            @"age":@(18+i),
            @"nick":[NSString stringWithFormat:@"Julian_%d",i],
            @"height":@(170 + i),
        };
        [teather setValuesForKeysWithDictionary:dic];
        [teatherSet1 addObject:teather];
    }
    NSLog(@"personSet1== %@", [teatherSet1 valueForKey:@"height"]);
    
    NSMutableSet *teatherSet2 = [NSMutableSet set];
    for (int i = 0; i < 10; i++) {
        JSTeather *teather = [[JSTeather alloc]init];
        NSDictionary* dic = @{
            @"name":[NSString stringWithFormat:@"酷走天涯_%d",i],
            @"age":@(18+i),
            @"nick":[NSString stringWithFormat:@"Julian_%d",i],
            @"height":@(180 + i%3),
        };
        [teather setValuesForKeysWithDictionary:dic];
        [teatherSet2 addObject:teather];
    }
    //NSSet会自动合并相同项,此处调用的是NSSet的分类方法
    NSSet *test =[teatherSet2 valueForKey:@"height"];
    NSLog(@"personSet2=== %@", test);
    // 嵌套set
    NSSet* nestSet = [NSSet setWithObjects:teatherSet1, teatherSet2, nil];
    
    NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.height"];
    NSLog(@"@distinctUnionOfSets.height== %@", arr1);
}

@end

(六)Representing Non-Object Values 非对象类型,如结构体

    //结构体
    JSTeather *myT = [[JSTeather alloc] init];
    ThreeFloats floats = {1.,2.,3.};
    NSValue *value     = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
    [myT setValue:value forKey:@"threeFloats"];
    NSValue *value1    = [myT valueForKey:@"threeFloats"];
    NSLog(@"结构体@valueForKey===%@",value1);
    
    ThreeFloats th;
    [value1 getValue:&th];
    NSLog(@"结构体@getValue===%f-%f-%f",th.x,th.y,th.z);

(七)Accessor Search Patterns (访问器搜索模式)

苹果官方文档中明确说明: NSObject提供的NSKeyValueCoding协议的默认实现使用一组明确定义的规则将基于密钥的访问器调用映射到对象的基础属性。这些协议方法使用一个关键参数来搜索它们自己的对象实例,以查找遵循特定命名约定的访问器、实例变量和相关方法。

· Search Pattern for the Basic Getter Getter的搜索模式:

  • valueForKey:的默认实现执行以下流程:
  1. 在实例中搜索第一个找到的访问器方法,该方法的名称为 get, , is,或 _。如果找到,调用它,然后使用结果进行第5步。否则,执行下一步。

  2. 如果没有找到简单的访问方法,搜索实例中的名称匹配为countOf和objectInAtIndex:(对应于NSArray类定义的原始方法)和 AtIndexes:(对应于NSArray方法objectsAtIndexes:)的方法。如果找到了其中的第一个和另外两个中的至少一个,创建一个集合代理对象来响应所有的NSArray方法并返回它。否则,请执行步骤3。代理对象随后将它接收到的任何NSArray消息转换为countOf, objectInAtInd的组合

  3. 如果没有找到简单的访问方法或数组访问方法组,则查找名为countOf、enumeratorOf和memberOf的三个方法:(对应于NSSet类定义的基本方法)。如果找到了所有这三个方法,创建一个响应所有NSSet方法的集合代理对象并返回它。否则,请执行步骤4。这个代理对象随后将它接收到的任何NSSet消息转换为countOf、enumeratorOf和memberOf:消息的组合,这些消息传递给创建它的对象。

  4. 如果没有找到简单的访问方法或集合访问方法组,并且接收方的类方法accessinstancevariablesdirect返回YES,则按此顺序搜索名为 _、_is、或 is的实例变量。如果找到,直接获取实例变量的值,继续执行步骤5。否则,请执行步骤6。

  5. 如果检索到的属性值是一个对象指针,只需返回结果。 如果该值是NSNumber支持的标量类型,则将其存储在NSNumber实例中并返回该值。 如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回该对象。

  6. 如果所有以上方法都失败了,则调用valueForUndefinedKey:。默认情况下这会引发一个异常,但NSObject的子类可能会提供指定键的反应。

· Search Pattern for the Basic Setter (Setter的搜索模式):

  • setValue:forKey:的默认实现,执行以下流程:
  1. 查找第一个命名为set:或 _set的访问器。如果找到,使用输入值为传入参数调用它,然后完成。

  2. 如果没有找到简单的访问器,并且类方法accessinstancevariablesdirect返回YES,则查找名称为_, _is, , 或 is,的实例变量。如果找到,直接使用输入值(或未包装值)设置变量,然后完成。

  3. 如果没有找到访问器或实例变量,调用setValue:forUndefinedKey:。默认情况下这会引发一个异常,但NSObject的子类可能会提供指定键的反应。

· Search Pattern for Mutable Arrays 可变数组的搜索模式

  • mutableArrayValueForKey:的默认实现,执行下流程:
  1. 首先查找方法名为insertObject:inAtIndex:和 removeObjectFromAtIndex:(对应NSMutableArray的insertObject:atIndex: 和 removeObjectAtIndex:方法),或方法名称如insert:atIndexes: 和 removeAtIndexes:(对应于NSMutableArray的insertObjects:atIndexes:和removeObjectsAtIndexes:方法)。

    如果对象至少有一个插入法和至少一个删除方法,返回一个代理对象,响应NSMutableArray消息发送的一些组合insertObject:inAtIndex:, removeObjectFromAtIndex:, insert:atIndexes:, 和 removeAtIndexes: 原始mutableArrayValueForKey接收者:消息。 当接收到mutableArrayValueForKey:消息的对象也实现了一个可选的替换对象方法,名称类似replaceObjectInAtIndex:withObject:或replaceAtIndexes:with:代理对象也会在合适的时候利用这些特性以获得最佳性能。

  2. 如果对象没有可变数组方法,则查找名称与模式匹配的访问器方法 set:

  3. 如果既没有可变数组方法,也没有找到访问器,并且接收方的类对accessinstancevariablesdirect响应YES,则按此顺序搜索名称为_或的实例变量。

    如果找到这样一个实例变量,返回一个代理对象,该代理对象将它接收到的每个NSMutableArray消息转发给该实例变量的值,该值通常是NSMutableArray的一个实例或它的一个子类。

  4. 如果以上方法都失败了,则返回一个可变的集合代理对象,当它接收到一个NSMutableArray消息时,它会发出一个setValue:forUndefinedKey:消息给接收者。

    setValue:forUndefinedKey的默认实现会引发NSUndefinedKeyException,但子类可以重新此方法

· Search Pattern for Mutable Ordered Sets NSMutableOrderedSet的搜索模式:

  • mutableOrderedSetValueForKey:的默认实现,执行下流程: