iOS进阶笔记(五)KVC

📣 iOS进阶笔记目录


一、KVC定义

KVC(Key-Value Coding)是利用NSKeyValueCoding 非正式协议实现的一种机制,对象采用这种机制来提供对其属性的间接访问.

  • KVC是通过对NSObject的扩展来实现的 —— 只要继承了NSObject的类都可以使用KVC

  • NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet等也遵守KVC协议


二、应用领域

  • KVO

    1)监听属性值的改变。注册观察者 和 监听观察者都是通过Key(Path)来实现。

    2)监听MutableArray改变:当我们要使用KVO监听集合对象变化时,需要通过KVC的可变代理方法(如mutableArrayValueForKey)替代getter方法来获取集合代理对象(即runtime动态生成的NSKeyValueArray类型对象,继承自NSArray),然后对代理对象进行操作。当代理对象的内部对象发生改变时,会触发KVO的监听方法。


  • CoreData

    1)增操作 -insertObject:in<Key>AtIndex: 或者 -insert<Key>:atIndexes:

    2)删操作 -removeObjectFrom<Key>AtIndex: 或者 -remove<Key>AtIndexes:

    3)改操作 -replaceObjectIn<Key>AtIndex:withObject: 或者 -replace<Key>AtIndexes:with<Key>:


  • Cocoa bings

    Xib控件会随内容动态变化


三、API

  • 常用基本API

      - setValue:forKey:
      - valueForKey:
      - setValue:forKeyPath:
      - valueForKeyPath:
    

  • 其他方法

    1、是否启动实例变量搜索: 默认为YES。 如果返回为YES,如果没有找到 set 方法的话, 会按照_key, _isKey, key, isKey的顺序搜索成员变量, 返回NO则不会搜索

      + (BOOL)accessInstanceVariablesDirectly;
    

    2、键值验证: 可以通过该方法检验键值的正确性, 然后做出相应的处理

      - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
    

    3、异常处理: 如果key不存在, 并且没有搜索到和key有关的字段, 会调用此方法, 默认抛出异常。两个方法分别对应 getset 的情况

        - (nullable id)valueForUndefinedKey:(NSString *)key;
        - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    

    setValue方法传 nil 时调用的方法;当且仅当 NSNumber 和 NSValue 类型时才会调用此方法

      - (void)setNilValueForKey:(NSString *)key;
    

    4、字典与模型的之间转换: 一组 key对应的value, 将其转成字典返回, 可用于将 Model 转成字典

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

四、KVC适用类型

  • NSNumber(int、float、double)

  • NSString(char)

  • 集合类型(NSArray、NSDictionary、NSSet、NSOrderedSet)

  • NSValue(可以包装自定义结构体)


1、对象属性类型

  • setValue:forKey:

  • valueForKey:

  • setValue:forKeyPath:

  • valueForKeyPath:


2、集合类型


1)操作集合对象

  • setValue:forKey:

  • valueForKey:


2)操作集合对象内容的获取

通过KVC的可变代理方法获取集合代理对象


  • NSMutableArray
- mutableArrayValueForKey:
- mutableArrayValueForKeyPath:

  • NSMutableSet
- mutableSetValueForKey:
- mutableSetValueForKeyPath:

  • NSMutableOrderedSet
- mutableOrderedSetValueForKey:
- mutableOrderedSetValueForKeyPath:

当代理对象的内部对象发生变化时,会触发KVO的监听方法,可以使用KVO就可以对代理对象进行操作


五、非对象值处理

  • setValue:forKey:
  • 对与value

    • 包装成NSNumber或NSValue
    • 若value传入nil,则会调用 setNilValueForKey:方法
  • 对于key

    • 若key为非对象类型,会发送一条Value消息给value对象以提取数据
  • valueForKey:

若返回值非对象,包装成NSNumber或NSValue返回


六、属性验证

  • KVC提供了属性验证的方法,可以在使用KVC赋值前验证能否为这个key赋值指定value
- (BOOL)validateValue:(id  _Nullable *)value 
               forKey:(NSString *)key 
                error:(NSError * _Nullable *)error;

- (BOOL)validateValue:(inout id  _Nullable *)ioValue 
           forKeyPath:(NSString *)inKeyPath 
                error:(out NSError * _Nullable *)outError;

七、搜索模式

参考Apple官方文档:《Key-Value Coding Programming Guide》


1、基本Getter搜索模式


Getter搜索流程

方法查找:按照 get<Key><key>is<Key>_<key>顺序查找方法。如果找到就调用取值并执行⑤,否则执行②;


为包装NSKeyValueArray提供方法查找:查找 countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:命名的方法。

如果找到第一个和后面两个中的至少一个,则创建一个能够响应所有NSArray的方法的集合代理对象(类型为NSKeyValueArray,继承自NSArray),并返回该对象。否则执行③;


  • 代理对象随后将其接收到的任何NSArray消息转换为 countOf<Key>objectIn<Key>AtIndex:<Key>AtIndexes:消息的组合,并将其发送给KVC调用方。如果原始对象还实现了一个名为get<Key>:range:的可选方法,则代理对象也会在适当时使用该方法。

  • 当KVC调用方与代理对象一起工作时,允许底层属性的行为如同NSArray一样,即使它不是NSArray


为包装成NSKeyValueSet提供方法查找:查找countOf、enumeratorOf、memberOf:命名的方法。

如果三个方法都找到,则创建一个能够响应所有NSSet的方法的集合代理对象(类型为NSKeyValueSet,继承自NSSet),并返回该对象。否则执行④

  • 代理对象随后将其接收到的任何NSSet消息转换为 countOf、enumeratorOf、memberOf: 消息的组合,并将其发送给KVC调用方
  • 当KVC调用方与代理对象一起工作时,允许底层属性的行为如同NSSet一样,即使它不是NSSet

直接查找成员变量:查看消息接收者类的+accessInstanceVariablesDirectly方法的返回值(默认返回YES)。

  • 如果返回YES,就按照_、_is、is顺序直接查找成员变量。
  • 如果找到就直接取值并执行⑤,否则执行⑥。
  • 如果+accessInstanceVariablesDirectly方法返回NO也执行⑥

将查找到的成员变量包装成NSNumber或NSValue:如果取到的值是一个对象指针,即获取的是对象,则直接将对象返回。

  • 如果取到的值是一个NSNumber支持的数据类型,则将其存储在NSNumber实例并返回。
  • 如果取到的值不是一个NSNumber支持的数据类型,则转换为NSValue对象, 然后返回

异常处理:调用valueForUndefinedKey:方法,该方法抛出异常NSUnknownKeyException,并导致程序Crash。这是默认实现,我们可以重写该方法根据特定key做一些特殊处理

  • 流程图

2 基本Setter搜索模式

Setter搜索流程


方法查找:按照 set<Key>:_set<Key>:顺序查找方法

如果找到就调用并将value传进去(根据需要进行数据类型转换),否则执行②;


查看消息接收者类的 +accessInstanceVariablesDirectly方法的返回值(默认返回YES)。

如果返回YES,就按照 _<key>_is<Key><key>is<Key> 顺序查找成员变量(同基本的Getter搜索模式)。

如果找到就将value赋值给它(根据需要进行数据类型转换),否则执行③。

如果+accessInstanceVariablesDirectly方法返回NO也执行③


调用setValue:forUndefinedKey:方法,该方法抛出异常NSUnknownKeyException,并导致程序Crash。这是默认实现,我们可以重写该方法根据特定key做一些特殊处理



3 NSMutableArray 搜索模式


对于mutableArrayValueForKey:方法,给定一个key作为输入参数,返回属性名为key的集合的代理对象(这里指NSMutableArray对象),在消息接收者类中操作,执行以下过程


① 查找一对方法

insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:(相当于NSMutableArray的原始方法insertObject:atIndex:removeObjectAtIndex:)

或者insert<Key>:atIndexes:remove<Key>AtIndexes:(相当于NSMutableArray的原始方法insertObjects:atIndexes:removeObjectsAtIndexes:)


  • 如果我们至少实现了一个insertion方法和一个removal方法,则返回一个代理对象,来响应发送给NSMutableArray的消息,通过发送insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:insert<Key>:atIndexes:remove<Key>AtIndexes:组合消息给KVC调用方。否则执行②。

  • 该代理对象类型为NSKeyValueFastMutableArray2,继承链为NSKeyValueFastMutableArray2->NSKeyValueFastMutableArray->NSKeyValueMutableArray->NSMutableArray

  • 如果我们也实现了一个可选的replace object方法,如replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:,代理对象在适当的情况下也会使用它们,以获得最佳性能。


② 查找 set: 方法

如果找到,就会向KVC调用方发送一个set<Key>:消息,来返回一个响应NSMutableArray消息的代理对象。否则执行③

  • 该代理对象类型为NSKeyValueSlowMutableArray,继承链为NSKeyValueSlowMutableArray->NSKeyValueMutableArray->NSMutableArray

  • 注意:
    此步骤中描述的机制比上一步的效率低得多,因为它可能重复创建新的集合对象,而不是修改现有的集合对象。因此,在设计自己的键值编码兼容对象时,通常应该避免使用它。

    给代理对象发送NSMutableArray消息都会调用set<Key>:方法。即,对代理对象进行修改,都是调用set<Key>:来重新赋值,所以效率会低很多。


③ 查看消息接收者类的+accessInstanceVariablesDirectly方法的返回值(默认返回YES)

如果返回YES,就按照_<key><key>顺序查找成员变量。如果找到就返回一个代理对象,该代理对象将接收所有NSMutableArray消息,通常是NSMutableArray或其子类。否则执行④。

如果+accessInstanceVariablesDirectly方法返回NO也执行④


④ 返回一个可变的集合代理对象

当它接收到NSMutableArray消息时,发送一个valueForUndefinedKey:消息给KVC调用方,该方法抛出异常NSUnknownKeyException,并导致程序Crash。

这是默认实现,我们可以重写该方法根据特定key做一些特殊处理



八、集合操作符


1、格式


  • Left key path:左键路径,要操作的集合对象,如果消息接收者就是集合对象,则可以省略 Left 部分;
  • Collection operator:集合运算符;
  • Right key path:右键路径,要进行运算的集合中的属性

2、数组操作符

  • 去重操作符:@distinctUnionOfObjects:,返回操作对象指定属性的集合

    示例:

    // 对象运算符:返回的数组元素是唯一的
    NSArray *arrDistinact = [bookArray valueForKeyPath:@"@distinctUnionOfObjects.price"];
    

  • @unionOfObjects: 返回操作对象指定属性的集合

    示例:

    NSArray *arrUnion = [bookArray valueForKeyPath:@"@unionOfObjects.price"];
    

    Tips: 在使用时,若数组任何元素含有nil,则valueForKeyPath时报异常


  • 聚合操作符

    @avg: 返回操作对象指定属性的平均值

    示例:

    // 集合运算符
    Book *b1 = [[Book alloc] init];
    b1.name = @"A";
    [b1 setValue:@30.5 forKey:@"price"];
    
    Book *b2 = [[Book alloc] init];
    b2.name = @"B";
    [b2 setValue:@50.5 forKey:@"price"];
    
    Book *b3 = [[Book alloc] init];
    b3.name = @"C";
    [b3 setValue:@30.5 forKey:@"price"];
    
    Book *b4 = [[Book alloc] init];
    b4.name = @"D";
    [b4 setValue:@35.5 forKey:@"price"];
    
    NSArray *bookArray = @[b1,b2,b3];
    // @avg.price @count @min.price @max.price
    NSNumber *sum = [bookArray valueForKeyPath:@"@sum.price"];
    

    其它操作符:

    @count: 返回操作对象指定属性的个数;

    @max: 返回操作对象指定属性的最大值;

    @min: 返回操作对象指定属性的最小值;

    @sum: 返回操作对象指定属性值之和;


  • 嵌套操作符

    1)@distinctUnionOfArrays:返回操作对象(嵌套集合)指定属性的集合--去重,返回的是 NSArray

    示例:

    NSArray *arr1 = @[b1,b2];
    NSArray *arr2 = @[b3,b4];
        
    NSArray *arrayOfArrays = @[arr1,arr2];
        
    NSArray *distArr = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.price"];
    

    2)@unionOfArrays: 返回操作对象(集合)指定属性的集合

    3)@distinctUnionOfSets: 返回操作对象(嵌套集合)指定属性的集合--去重,返回的是 NSSet;注意因为NSSet集合没有重复元素,因此不存在-----@unionSets

    示例:

    NSSet *set1 = [NSSet setWithArray:@[b1,b2]];
    NSSet *set2 = [NSSet setWithArray:@[b3,b4]];
    
    NSArray *arrayOfSets = @[set1,set2];
    NSSet *distSet = [arrayOfSets valueForKeyPath:@"@distinctUnionOfSets.price"];
    
    

    Tips:嵌套操作符中若含有非集合(NSArray、NSDictionary、NSSet)时会导致Crash


  • 自定义集合操作符

    为NSArray添加一个分类,并定义一个 _<operatorKey>ForKeyPath: 方法即可使用@operatorKey自定义的运算符。

    Tips:

    • NSSet类不支持@unionOfObjects 和 @unionOfArrays操作符;
    • NSArray类支持的@distinctUnionOfSets操作符的类型必须是arrayOfSets类型;
    • 如果集合中的对象都是NSNumber,右键路径可以用self。


九、高阶消息传递


示例:

NSArray *strArray = @[@"english",@"chinese",@"japenese"];
  • 集合中的字符串首字母改为大写
NSArray *strCapArray = [strArray valueForKey:@"capitalizedString"];
  • 统计集合中每个字符串长度
NSArray *lengthArray = [strArray valueForKeyPath:@"capitalizedString.length"];

以上

----------End------------

posted @ 2021-08-04 20:57  ITRyan  阅读(81)  评论(0编辑  收藏  举报