• Objective-C:KVC


    1 概述

    1.1 访问方法

          Key-value coding(KVC)是一种间接访问对象属性的机制,类似键值对,通过名字(或键)可以直接获得对象的属性值。事实上,key-value coding定义了一些模式和规则方法来实现属性的访问,这些基本方法在NSKeyValueCoding协议中声明,同时NSObject默认实现了该协议。

    KVC存在两种形式的访问方法:

    • getter:获取对象的属性值
    • setter:设置对象的属性值

    如下使用KVC来简化代码的编写:

     1 - (id)tableView:(NSTableView *)tableview    //未使用KVC的访问方法
     2 objectValueForTableColumn:(id)column
     3 row:(int)row
     4 {
     5     ChildObject *child = [childrenArray objectAtIndex:row];
     6     if ( [[column identifier] isEqualToString:@"name"] ) 
     7     {
     8        return [child name];
     9     }
    10     if ( [[column identifier] isEqualToString:@"age"] )
    11    {
    12       return [child age];
    13    }
    14    if ( [[column identifier] isEqualToString:@"favoriteColor"] )
    15   {
    16      // etc...
    17   }
    18   // etc...
    19 }
    20 - (id)tableView:(NSTableView *)tableview   //使用了KVC的访问方法
    21      objectValueForTableColumn:(id)column
    22                            row:(int)row
    23 {
    24       ChildObject *child = [childrenArray objectAtIndex:row];
    25       return [child valueForKey:[column identifier]];
    26 }

    1.2 访问属性

           Key-value coding技术其实就是间接访问对象的属性,支持KVC的属性有三种类型:attributes、to-one relationships和to-many relationships。

            1) attributes:该类型属性是普通类型,有scalar、string和Boolean 等简单类型,以及一些值对象和不可变类型,如NSNumber和NSColor。

            2) to-one relationship:该类型属性是对象类型,即该类型属性本身也是一个对象(拥有自己的属性)。

            3) to-many relationships:该类型属性是一种容器,主要是NSArray 和NSSet容器。

    2 KVC基础

    2.1 键与键路径

    1) 键(keys)

          键是字符串类型,用于标识对象的属性,建的命名必须遵循如下的规则:

          a) 必须是 ASCII编码的。

          b) 必须以小写字母打头。第一个字母可以是下划线,但不能是数字,也不能是大写字母。

          c) 不能包含空格。

    2) 键路径(Key Paths)

          键路径也是一个键,其通过点操作符隔开不同对象。访问一个属性的属性,就需要使用点标记指定一个更复杂的键路径。

    比如:

    1 [foo valueForKeyPath:@”someMember.someAttributeonMember”];
    2 键路径访问 foo 对象的 someMember 特性,在 someMember 所属的类上查找someAttributeOnMember属性,并返回存储在这里的值。

    2.2 Getter方法

          KVC提供如下方法来获取对象的属性值:

              1) valueForKey:方法

         通过向该方法传递一个key(标识符),能够获得对象的属性值。若没有相应的访问方法或没有健变量,则所属对象将调用自身的valueForUndefinedKey方法,该方法默认抛出NSUndefinedKeyException异常

             2) valueForKeyPath:方法

          通过向该方法传递一个key path,能够获得对象中属性的属性值。若没有键路径,同样会调用自身的valueForUndefinedKey方法

             3) dictionaryWithValuesForKeys:方法

          用一个标识符数组(字符串类型)返回一个NSDictionary对象。

    2.3 Setter方法

    KVC提供一系列方法设置对象的属性:

             1) setValue:forKey:方法

         通过向该方法传递key和value,从而能够设置对象的属性值,并且该方法自动实现wrapping和unwrapping。若对象中不存在key标识符或变量,则会调用该对象的setValue:forUndefinedKey:方法,该方法的默认实现是抛出NSUndefinedKeyException类型的异常。

            2) setValue:forKeyPath:方法

         同样是设置对象的属性,不同的是该方法能够操纵key path。

            3) setValuesForKeysWithDictionary:方法

         通过向该方法传递一个NSDictionary对象,从而设置对象的属性。 

    2.4 访问实例

           如下简单创建Myclass类,并在该类中定义了两个属性,接着通过KVC进行访问对象的属性。

    1) 类定义

    @interface MyClass : NSObject
    @property NSString *string;
    @property NSInteger integer;
    @property MyClass *instance;
    @end

    2) 键访问

     1 int main(int argc, const char * argv[])
     2  {
     3     MyClass *myInstance = [[MyClass alloc] init];
     4     
     5     [myInstance setValue:@"hello world" forKey:@"string"];
     6     [myInstance setValue:[NSNumber numberWithInt:100] forKey:@"integer"];
     7     
     8     NSLog(@"%@",[myInstance valueForKey:@"string"]);
     9     NSLog(@"%@",[myInstance valueForKey:@"integer"]);
    10     return 0;
    11 }
    12 输出:
    13       2016-04-06 21:04:32.533 KVC[1134:94328] hello world
    14       2016-04-06 21:04:32.534 KVC[1134:94328100

    3) 键路径访问

     1 int main(int argc, const char * argv[]) 
     2 {
     3     MyClass *myInstance = [[MyClass alloc] init];
     4     MyClass *anotherInstance = [[MyClass alloc] init];
     5     anotherInstance.instance = myInstance;
     6     
     7     [anotherInstance setValue: [NSNumber numberWithInt:222]  forKeyPath:@"instance.integer"];
     8     NSLog(@"%@",[anotherInstance valueForKeyPath:@"instance.integer"]);
     9     
    10     return 0;
    11 }
    12 输出:
    13      2016-04-06 21:13:46.432 KVC[1253:99388222

    3 存取方法

          虽然可以使用KVC中的访问方法valueForKey:和setValue:forKey:来操作对象的属性,但需要实现属性的存取方法来供KVC访问方法调用

    3.1 普通模式

    3.1.1 取方法(getter)

          对于属性的取方法有两种形式,对于不同类型的属性可以实现如下两种之一:

    • -<key>此种形式的取方法用于返回一个对象、标量或结构体等类型。
    • -is<Key>此种形式的取方法用于返回一个布尔类型的值。

         如某类中的hidden属性为bool类型,可以如下两种方式实现其取方法:

     1 - (BOOL)hidden
     2 {
     3      // Implementation specific code.
     4      return ...;
     5 }
     6 - (BOOL)isHidden
     7 {
     8     // Implementation specific code.
     9     return ...;
    10 }

    3.1.2 存方法(setter)

           为了使用KVC的setValue:forKey:方法来设置属性值,需要实现对象的set<Key>:存方法。如下所示hidden属性的存方法实现。

    1 - (void)setHidden:(BOOL)flag
    2 {
    3 // Implementation specific code.
    4    return;
    5 }

           若设置的属性值为non-object类型(即为标量),那么还需考虑传递给存方法的参数为nil的情况。其中在Objective-C中,若给标量属性的存方法传递nil参数,那么将调用该对象的setNilValueForKey:方法。从而工程师可以重载该方法来重新设置新的值,否则将抛出异常。

          如在给setHidden方法传递nil参数时,需要将其重定向为YES值,那么可以进行如下重载setNilValueForKey:方法:

    1 - (void)setNilValueForKey:(NSString *)theKey
    2 {
    3      if ([theKey isEqualToString:@"hidden"]) 
    4     {
    5         [self setValue:[NSNumber numberWithBool:YES] forKey:@"hidden"];
    6     } else
    7     [super setNilValueForKey:theKey];
    8 }

    3.2 集合模式

        若对象的属性是一个集合,也可以简单就实现-<key>和-set<Key>:两种存取方法来访问集合对象。若需要访问集合中的元素,那么需要实现更多的方法。

        可以简单将Objective-C的集合类型分为两种:有序无序。从而集合元素的存取方法也相应有两种形式:索引存取器方法和无序存取器方法。

    3.2.1 有序集合

          索引方法定义了集合属性的计数、查询、添加和替换等机制,其中有序集合有种两种类型:NSArray和NSMutableArray

    1) 取元素方法(不可变集合)

          不可变集合只能获取集合中的元素,从而只需实现如下的方法:

    • -countOf<Key>必须实现,此方法的功能类似NSArray类的count方法。
    • -objectIn<Key>AtIndex:-<key>AtIndexes:必须实现之一,同样类似于NSArray类的objectAtIndex: and objectsAtIndexes:方法。
    • -get<Key>:range:可选,其功能类似于NSArray类的getObjects:range方法。

          如在某类中有个employees集合,从而可实现上述四个方法:

     1 - (NSUInteger)countOfEmployees
     2 {
     3       return [employees count];
     4 }
     5 - (id)objectInEmployeesAtIndex:(NSUInteger)index
     6 {
     7       return [employees objectAtIndex:index];
     8 } -
     9 (NSArray *)employeesAtIndexes:(NSIndexSet *)indexes
    10 {
    11       return [employees objectsAtIndexes:indexes];
    12 }
    13 - (void)getEmployees:(Employee **)buffer range:(NSRange)inRange
    14 {
    15      // Return the objects in the specified range in the provided
    16      // buffer. For example, if the employees were stored in an
    17      // underlying NSArray
    18      [employees getObjects:buffer range:inRange];
    19 }

    2) 存元素方法(可变集合)

           对于可变集合(mutable)本身是可以修改集合的元素,因此除了需要实现上述的取元素方法,同时还需要实现如下的存元素方法:

              a) -insertObject:in<Key>AtIndex: -insert<Key>:atIndexes:

             至少实现之一,其功能类似于NSMutableArray类的insertObject:atIndex和insertObjects:atIndexes:。

              b) -removeObjectFrom<Key>AtIndex: -remove<Key>AtIndexes:

            至少实现之一,其功能类似于NSMutableArray类的removeObjectAtIndex:和removeObjectsAtIndexes:。

              c)-replaceObjectIn<Key>AtIndex:withObject: -replace<Key>AtIndexes:with<Key>:

            可选的,若有性能上的要求可以实现其。

         如在某类中有个employees集合,从而可实现上述6个方法:

     1 - (void)insertObject:(Employee *)employee  inEmployeesAtIndex:(NSUInteger)index
     2 {
     3      [employees insertObject:employee atIndex:index];
     4      return;
     5 } 
     6 -(void)insertEmployees:(NSArray *)employeeArray atIndexes:(NSIndexSet *)indexes
     7 {
     8      [employees insertObjects:employeeArray atIndexes:indexes];
     9      return;
    10 }
    11 - (void)removeObjectFromEmployeesAtIndex:(NSUInteger)index
    12 {
    13     [employees removeObjectAtIndex:index];
    14 }
    15 - (void)removeEmployeesAtIndexes:(NSIndexSet *)indexes
    16 {
    17     [employees removeObjectsAtIndexes:indexes];
    18 }
    19 - (void)replaceObjectInEmployeesAtIndex:(NSUInteger)index withObject:(id)anObject
    20 {
    21     [employees replaceObjectAtIndex:index withObject:anObject];
    22 } 
    23 -(void)replaceEmployeesAtIndexes:(NSIndexSet *)indexes withEmployees:(NSArray *)employeeArray
    24 {
    25     [employees replaceObjectsAtIndexes:indexes withObjects:employeeArray];
    26 }

    3.2.2 无序集合

    1) 取元素方法(不可变集合)

           对于无序的集合来说,一般的取元素方法是获取迭代器和判断是否为成员对象。为了满足只读需要,必须实现如下所有的方法。

    • -countOf<Key>类似NSSet类的count方法;
    • -enumeratorOf<Key>类似NSSet类的objectEnumerator方法;
    • -memberOf<Key>类似NSSet类的member方法;

    如下是实现例子:

     1 - (NSUInteger)countOfTransactions
     2 {
     3     return [transactions count];
     4 } 
     5 -(NSEnumerator *)enumeratorOfTransactions
     6 {
     7     return [transactions objectEnumerator];
     8 }
     9 -(Transaction *)memberOfTransactions:(Transaction *)anObject
    10 {
    11     return [transactions member:anObject];
    12 }

    2) 存元素方法(可变集合)

          为了容易且高效的利用mutableSetValueForKey:方法来访问无序集合的元素,需要实现如下方法:

             a) -add<Key>Object: -add<Key>:

             至少实现两者之一,功能类似于NSMutableSet类的addObject:方法。

             b) -remove<Key>Object: -remove<Key>:

             至少实现两者之一,功能类似于NSMutableSet类的removeObject:方法。

             c) -intersect<Key>:

            可选类型,功能类似于NSSet 类的intersectSet: 方法。

    如下的测试例子:

     1 - (void)addTransactionsObject:(Transaction *)anObject
     2 {
     3     [transactions addObject:anObject];
     4 }
     5 -(void)addTransactions:(NSSet *)manyObjects
     6 {
     7     [transactions unionSet:manyObjects];
     8 }
     9 - (void)removeTransactionsObject:(Transaction *)anObject
    10 {
    11     [transactions removeObject:anObject];
    12 } 
    13 -(void)removeTransactions:(NSSet *)manyObjects
    14 {
    15     [transactions minusSet:manyObjects];
    16 }
    17 - (void)intersectTransactions:(NSSet *)otherObjects
    18 { 
    19     return [transactions intersectSet:otherObjects];
    20 }

    4 搜索模式

    4.1 简单属性

    4.1.1 默认setValue:forKey

    当为了设置对象的某个属性,而调用setValue:forKey:方法时,将按如下顺序执行搜素:

         1) 首先会搜索接收对象的set<Key>:方法,即setValue:forKey:方法所属的对象。

        2) 若未找到匹配的方法,且接收对象的accessInstanceVariablesDirectly方法会返回YES,那么将在接收对象中按序搜素以这样命名的实例变量:_<key>_is<Key><key>、和is<Key>

         3) 若找到匹配的方法,那么会调用匹配的方法;若需要可以设置Non-Object值。

         4) 若没有匹配方法或实例变量被发现,那么会调用接收对象的setValue:forUndefinedKey:方法

    4.1.2 默认valueForKey

    当为了访问对象的某个属性,而调用valueForKey:方法时,将按如下的顺序执行搜索:

         a) 首先会在接收对象中按序搜索get<Key><key>、和is<Key>方法,若找到其中之一的方法,并且该方法返回的是Objective-C类型的指针,则直接返回;若是找到的方法是返回支持NSNumber的标量类型,那么将执行转换。

         b) 若未找到匹配的方法,则在接收对象中按序搜素countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:方法

         c) 若仍未找到匹配的方法,则在接收对象中按序搜索countOf<Key>enumeratorOf<Key>、和memberOf<Key>:方法

        d) 若未找到匹配的方法,且接收对象的accessInstanceVariablesDirectly方法会返回YES,那么将在接收对象中按序搜素以这样命名的实例变量:_<key>_is<Key><key>、和is<Key>

         e) 若上述所有情况都未发现,那么将调用接收对象的valueForUndefinedKey:方法

  • 相关阅读:
    加载声音的过程
    onkeyup,onkeydown和onkeypress
    加载着色器的异常

    3
    1
    1
    java总结
    环路
    own address as source address
  • 原文地址:https://www.cnblogs.com/huliangwen/p/5462953.html
Copyright © 2020-2023  润新知