KVC & KVO使用

标签(空格分隔): iOS学习


KVC 基础

KVC是KeyValue Coding的简称,它是一种可以直接通过字符串的名字(key)来间接访问对象的属性的机制。而不是通过调用Setter、Getter方法访问。 KVO是基于KVC实现的关键技术之一。

keys和key Paths

key,是用来识别一个对象的特定属性的字符串。必须用ASCII编码,以小写字母开头,不能有空格

key path是用.来分隔的keys的字符串,指定要遍历的对象属性序列。

用KVC获取属性值

  • valueForKey:返回指定key的值,如果没有与指定key的方法或实例变量,接收者会给自己发送
  • valueForUndefinedKey:消息,默认的valueForUndefinedKey:实现触发NSUndefinedKeyException异常,子类可以重写这个行为。
  • valueForKeyPath:返回指定的key path的值,如果没有对应的key对象就会收到valueForUndefinedKey:消息。
  • dictionaryWithValuesForKeys: 返回一个数组keys的值。返回的NSDictionary包含对应的keyvalue.

用KVC设置属性值

  • setValue:forKey:设置指定key的值,setValue:forKey:的默认实现会自动展开表示scalar和结构体的NSValue对象,并给属性赋值。如果指定的key不存在,接收者会收到setValue:forUndefinedKey:消息,默认的实现也会触发NSUndefinedKeyException异常,子类可以重写该方法。
  • setValue:forKeyPath:跟单个key差不多的做法。
  • setValuesForKeysWithDictionary:用NSDictionary的keys来指定相应属性的值,默认实现会调用setValue:forKey:,如果需要用nil来替换NSNull对象。

如果尝试给一个非对象属性设置nil值,接收者会给自己发送setNilValueForKey:消息,默认实现会触发NSInvalidArgumentException异常。应用可以重写该方法替换一个默认值或标记值,然后调用setValue:forKey:来设置新值。

简单属性的访问方法搜索模式

  • 搜索接收者的类是否有方法的名字匹配set:。如果成员使用了@property ,@synthsize 处理,@synthsize告诉编译器自动生成set:格式的setter方法,所以这种情况下会直接搜索到。
  • 如果没有找到相应的方法,并且接收者的类方法accessInstanceVariablesDirectly返回YES,搜索接收者的实例变量的名字是否有按下面顺序匹配的,_<key>, _is<Key>, <key>, or is<Key>(包括私有变量)
  • 如果找到了访问方法或实例变量,就对它进行赋值
  • 如果没有找到对应的访问方法或实例变量,调用接收者的setValue:forUndefinedKey:

valueForKey:的默认搜索模式

  • 搜索接收者类的访问方法名称是否有按get<Key>, <key>, or is<Key>这个顺序匹配的。如果有这样的方法就调用。如果方法的返回值类型是对象指针就直接返回。如果返回值类型是scalar(简单)类型,并且可以转换为NSNumber,就返回NSNumber。其他的就转化为NSValue并返回。
  • 上面的getter没有找到,查找countOf<Key>objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。如果countOf和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf、objectInAtIndex:、AtIndexes这几个方法组合的形式调用。还有一个可选的get:range:方法。
  • 还没查到,那么查找countOf、enumeratorOf、memberOf:格式的方法。
    如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf、enumeratorOf、memberOf:组合的形式调用。
  • 还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_,_is,,is的顺序直接搜索实例变量,但是这种访问操作破坏了封装性,应该尽量避免这样操作,可以通过重写accessInstanceVariablesDirectly方法,返回为NO来避免这种行为。

KVC设定值的正确性检查

KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。

实现检查方法的格式: validate+属性名字:error:
如果有个属性name,则设置检查方法如下代码所示:

//如果不想让类的name中不存在前后的空白字符,则实现KVV
- (BOOL)validateName:(NSString **)ioValue error:( NSError * __autoreleasing *)outError
{
    //如果要设置的name的value值为nil或者长度要大于等于2
    if (*ioValue == nil) {
        *ioValue = @"";
        return YES;
    }
    else
    {
        NSString *str = [*ioValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
        NSArray *compont = [ str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
        compont = [compont filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id  _Nonnull evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
            return (0 < [evaluatedObject length]);
        }]];
        *ioValue = [compont componentsJoinedByString:@" "];
    }
    return YES;
}

为什么要使用KVC

KVC可以用来获取3类不同类型的对象值:attributes, to-one relationships, 和 to-many relationships 。术语property就是指这些类型的任意一种。

  • 列表项attribute, 简单的值,如scalar(char, NSTimeInterval, int, float, or double等), string, 布尔值, Value objects也是。
  • A property that specifies a to-one relationship is an object that has properties of its own. These underlying properties can change without the object itself changing. For example, an NSView instance’s superview is a to-one relationship.
  • a property that specifies a to-many relationship consists of a collection of related objects

简化你的代码

当我们需要统计很多People的时候,每一行是一个人的实例,并且有2列属性,name, age, 这时候我们可以会这样做

- (id)tableView:(NSTableView *)tableview
      objectValueForTableColumn:(id)column row:(NSInteger)row {

    People *people = [peoleArray objectAtIndex:row];
    if ([[column identifier] isEqualToString:@"name"]) {
        return [people name];
    }
    if ([[column identifier] isEqualToString:@"age"]) {
        return [people age];
    }
    // And so on.
}

使用了KVC之后,可以帮助简化这些if else,因为name age 都是property,可以直接通过key访问。整理之后

- (id)tableView:(NSTableView *)tableview
    objectValueForTableColumn:(id)column row:(NSInteger)row {
    ChildObject *child = [childrenArray objectAtIndex:row];
    return [child valueForKey:[column identifier]];
}

如果属性很多的话,可以极大的简化代码。

可以获取以及设置任何对象的任何属性的值.(包括私有变量)

验证代码:

@interface Author : NSObject
{
    NSString *privateVar;
}
@property (nonatomic, strong) NSString *name;
@end
@implementation Author
@end

如果下面这样用dot+属性访问是编译不过去的,因为没有getter和setter方法。

Author *au = [Author new];
au.privateVar = @"private";

那么使用KVC就可以做到访问实例变量,并使得程序正常运行。

Author *au = [Author new];
[au setValue:@"private" forKey:@"privateVar"];
NSLog(@"%@",[au valueForKey:@"privateVar"]);

如果我们把这个变量用@private声明,那么也可以做到访问。

@interface Author : NSObject
{
@private NSString *_privateVar;
}
@property (nonatomic, copy) NSString *privateVar;
@end
@implementation Author
@synthesize privateVar = _privateVar;
@end

使用的时候依然可以用KVC访问。

Author *au = [Author new];
[au setValue:@"private" forKey:@"privateVar"];
NSLog(@"%@",[au valueForKey:@"privateVar"]);

所以obj-C实际上并不存在真正的私有变量,因为只要知道变量名称就可以访问且操作这个变量。

虽然我们可以通过KVC给私有变量赋值,但是就不意味着可以肆无忌惮的修改苹果系统规定过的私有变量或者只读权限。在app 上架前深刻的过程中,苹果官方会对这类做法严格检查的。我们可以修改自定义类里面的私有属性和权限。

UITextField的placeholder其实就是个UILabel,我们拿到它的真实属性名,就可以通过KVC赋值了。

UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 100, 100, 20)];
       textField.placeholder = @"i am placeholder.";
       [textField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
       [textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
       // 如果你愿意的话,placehloder本身也可以通过KVC赋值
       [textField setValue:@"abc" forKeyPath:@"_placeholderLabel.text"];
       [self.view addSubview:textField];  

KVC容器操作

valueForKey:的使用并不仅仅用来取值那么简单,还有很多特殊的用法,集合类也覆盖了这个方法,通过调用valueForKey:给容器中每一个对象发送操作消息,并且结果会被保存在一个新的容器中返回,这样我们能很方便地利用一个容器对象创建另一个容器对象。另外,valueForKeyPath:还能实现多个消息的传递。一个例子:

//1.KVC 容器使用valueforkeypath
   NSArray *array = [NSArray arrayWithObjects:@"10.11", @"20.22",nil];
   CGFloat a = 10.11;
   CGFloat b = 20.22;
   NSArray *array2 = [NSArray arrayWithObjects:[NSNumber numberWithFloat:a],[NSNumber numberWithFloat:b],nil];
   NSArray *resultArray = [array valueForKeyPath:@"doubleValue.intValue"];
   NSArray *resultArray2 = [array2 valueForKeyPath:@"doubleValue.intValue"];
   NSLog(@"%@", resultArray);
   NSLog(@"%@", resultArray2);
   //打印结果 (     10,     20 

容器不仅仅能使用KVC方法实现对容器成员传递普通的操作消息,KVC还定义了特殊的一些常用操作,使用valueForKeyPath:结合操作符来使用,所定义的keyPath格式入下图所示:

KVC 的集合操作符可使用键路径和操作运算作用于集合中的所有元素,实际上就是一些特殊的键路径,以参数的形式传递给 valueForKeyPath: 方法。集合操作是以 @ 开始的字符串, 也可理解为: KVC集合操作符允许在 valueForKeyPath: 方法中使用 key path 符号在一个集合中执行方法。无论什么时候你在 key path 中看见了@,它都代表了一个特定的集合方法,其结果可以被返回或者链接,就像其他的 key path 一样。下图就是集合操作符的格式:

集合操作符路径

其中左边的键路径(keypathToCollection)指定了相对消息接收者的 NSArray 或者 NSSet,右边的键路径(keypathToProperty)指定了相对于集合内对象的键路径,集合操作作用于该键路径。

集合运算符会根据其返回值的不同分为以下三种类型:

  • 简单的集合运算符 返回的是strings, number, 或者 dates
  • 对象运算符 返回的是一个数组
  • 数组和集合运算符 返回的是一个数组或者集合

简单集合运算符:(操作对象必须是集合)

  • @count: 返回一个值为集合中对象总数的NSNumber对象。
  • @sum: 首先把集合中的每个对象都转换为double类型,然后计算其总,最后返回一个值为这个总和的NSNumber对象。
  • @avg: 操作符将集合中属性键路径所指对象转换为 double, 计算其平均值,返回该平均值的 NSNumber 对象。当均值为 nil 的时候,返回 0.
  • @max: 使用compare:方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较。
  • @min: 和@max一样,但是返回的是集合中的最小值。

Pro提示:你可以简单的通过把self作为操作符后面的key path来获取一个由NSNumber组成的数组或者集合的总值,例如[@[@(1), @(2), @(3)] valueForKeyPath:@”@max.self”] (/感谢 @davandermobile, 来自 Objective Sea)

对象操作符

对象操作符包括 @distinctUnionOfObjects@unionOfObjects, 返回一个由操作符右边的 key path 所指定的对象属性组成的数组。其中 @distinctUnionOfObjects 会对数组去重,而 @unionOfObjects 不会。

数组和集合操作符

数则和集合操作符跟对象操作符很相似,只不过它是在NSArrayNSSet所组成的集合中工作的。

  • @distinctUnionOfArrays / @unionOfArrays: 返回了一个数组,其中包含这个集合中每个数组对于这个操作符右面指定的key path进行操作之后的值。正如你期望的,distinct版本会移除重复的值。
  • @distinctUnionOfSets: 和@distinctUnionOfArrays差不多, 但是它期望的是一个包含着NSSet对象的NSSet,并且会返回一个NSSet对象。因为集合不能包含重复的值,所以它只有distinct操作

批处理函数 dictionaryWithValuesForKeys && setValuesForKeysWithDictionnary:

dictionaryWithValuesForKeys:方法,通过传入key数组,返回一个成员变量名(不加下划线)和变量值的键值对组成的字典。

People *pDic = [People new];
    [pDic setValue:@"0.12" forKey:@"price"];
    [pDic setValue:@"chenhao" forKey:@"name"];
    NSArray *peopleProperties = [NSArray arrayWithObjects:@"name",@"price", nil];
    NSDictionary *peopleDictionary = [pDic dictionaryWithValuesForKeys:peopleProperties];

    NSLog(@"people Values :%@",peopleDictionary);  

生成一个NSDictionary,打印输出为:

people Values :{
    name = chenhao;
    price = "0.12";
}

setValuesForKeysWithDictionnary:
方法,通过传入字典,初始化相应的键对应的成员变量(同前所述,非常智能的查找)的值为相应的的键值,在MVC设计模式中,模型初始化常用这个方法,一般是写在初始化方法中,通过字典初始化模型数据。

例子:

People *pDic = [People new];
    [pDic setValue:@"0.12" forKey:@"price"];
    [pDic setValue:@"chenhao" forKey:@"name"];
    NSArray *peopleProperties = [NSArray arrayWithObjects:@"name",@"price", nil];
    NSDictionary *peopleDictionary = [pDic dictionaryWithValuesForKeys:peopleProperties];

    NSLog(@"people Values :%@",peopleDictionary);

    NSDictionary *values = [NSDictionary dictionaryWithObjectsAndKeys:@"chenhao",@"name",@"18.0",@"price", nil];
    [pDic setValuesForKeysWithDictionary:values];

###KVC的缺点

可以看到KVC确实有很多优点,但是需要清醒一下,滥用KVC并不是什么好的事情。

缺点:

  • KVC需要解析字符串来获取所需要的内容,因此速度较慢。
  • 编辑器无法对KVC进行字符串错误检查,所以当key错误的时候,会造成运行时错误。

KVO的使用

NSKeyValueObserving非正式协议定义了一种机制,它允许对象去监听其它对象的某个属性的修改。

KVO可以监听一个对象的属性,包括简单属性,一对一的关系,和一对多的关系。一对多关系的监听者会被告知集合变更的类型,以及哪些对象参与了变化。(监听数组会和监听普通的对象有区别)

KVO的使用步骤:

注册观察者

要让一个对象监听另一个对象的属性的变化,首先需要将这个对象注册为相关属性的观察者,使用以下方法来实现:

- (void)addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context
  • anObserver:观察者对象,这个对象必须实现observeValueForKeyPath:ofObject:change:context:方法,以响应属性的修改通知。
  • keyPath:被监听的属性。这个值不能为nil。
  • options:监听选项,这个值可以是NSKeyValueObservingOptions选项的组合。
  • context:任意的额外数据,我们可以将这些数据作为上下文数据,它会传递给观察者对象的observeValueForKeyPath:ofObject:change:context:方法。这个参数的意义在于用于区分同一对象监听同一属性(从属于同一对象)的多个不同的监听。

NSKeyValueObservingOptions是什么呢?

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {

    // 提供属性的新值
    NSKeyValueObservingOptionNew = 0x01,
    // 提供属性的旧值
    NSKeyValueObservingOptionOld = 0x02,

    // 如果指定,则在每次修改属性时,会在修改通知被发送之前预先发送一条通知给观察者,
  // 这与-willChangeValueForKey:被触发的时间是相对应的。
  // 这样,在每次修改属性时,实际上是会发送两条通知。
    NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,

    NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08

};

需要注意的是:

  • 调用这个方法时,两个对象(即观察者对象及属性所属的对象)都不会被retain
  • 可以多次调用这个方法,将同一个对象注册为同一属性的观察者(所有参数可以完全相同)。这时,即便在所有参数一致的情况下,新注册的观察者并不会替换原来观察者,而是会并存。这样,当属性被修改时,两次监听都会响应。

移除观察者

当观察者不再需要监听属性变化时,必须调用removeObserver:forKeyPath:removeObserver:forKeyPath:context:方法来移除观察者

- (void)removeObserver:(NSObject *)anObserver
            forKeyPath:(NSString *)keyPath

- (void)removeObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath
               context:(void *)context

未移除观察者会崩溃,在添加观察者的时候,观察者对象与被观察属性所属的对象都不会被retain,然而在这些对象被释放后,相关的监听信息却还存在,KVO做的处理是直接让程序崩溃。

处理属性修改通知

当被监听的属性修改时,KVO会发出一个通知以告知所有监听这个属性的观察者对象。而观察者对象必须实现-observeValueForKeyPath:ofObject:change:context:方法,来对属性修改通知做相应的处理。这个方法的声明如下:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
  • keyPath:即被观察的属性,与参数object相关。
  • object:keyPath所属的对象。
  • change:这是一个字典,它包含了属性被修改的一些信息。这个字典中包含的值会根据我们在添加观察者时设置的options参数的不同而有所不同。
  • context:这个值即是添加观察者时提供的上下文信息

第三个参数叫做 变化字典(Change Dictionary),它记录了被监听属性的变化情况。可以通过下面的key来获取想要的value 值。

FOUNDATION_EXPORT NSString *const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNotificationIsPriorKey NS_AVAILABLE(10_5, 2_0);

使用方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{

    int changeKind = [[change objectForKey:NSKeyValueChangeKindKey]intValue];
    NSIndexSet *indexs = [change objectForKey:NSKeyValueChangeIndexesKey];

    if ([keyPath isEqualToString:@"cousins.array"]) {
        [indexs enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
            if (changeKind == NSKeyValueChangeInsertion) {
                NSLog(@"Insert Action");
            }
            else if(changeKind == NSKeyValueChangeRemoval)
            {
                NSLog(@"Remove Action");
            }

        }];
        NSLog(@"The name of the FIRST child was changed.");
//        NSLog(@"%@", change);

    }
}

其中,NSKeyValueChangeKindKey的值取自于NSKeyValueChange,它的值是由以下枚举定义的:

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};

属性发生变化,通知观察者

默认情况下是自动发送通知,在这种模式下,当我们修改属性的值时,KVO会自动调用以下两个方法:

- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key

这两个方法的任务是告诉接收者给定的属性将要或已经被修改。需要注意的是不应该在子类中去重写这两个方法。但如果我们希望自己控制通知发送的一些细节,则可以启用手动控制模式。手动控制通知提供了对KVO更精确控制,它可以控制通知如何以及何时被发送给观察者。采用这种方式可以减少不必要的通知,或者可以将多个修改组合到一个修改中。

实现手动通知的类必须重写NSObject中对automaticallyNotifiesObserversForKey:方法的实现。这个方法是在NSKeyValueObserving协议中声明的,其声明如下:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

这个方法返回一个布尔值(默认是返回YES),以标识参数key指定的属性是否支持自动KVO。如果我们希望手动去发送通知,则针对指定的属性返回NO。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    BOOL automatic = YES;
    if ([key isEqualToString:@"key"]) {
        return automatic;
    }
    //其他属性,使用父类的处理方式
    return [super automaticallyNotifiesObserversForKey:key];
}

手动处理相关属性的kvo通知:

- (void)setName:(NSString *)name
{
    if (_name != name) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}

如果我们在setter方法之外改变了实例变量(如_bankCodeEn),且希望这种修改被观察者监听到,则需要像在setter方法里面做一样的处理。这也涉及到我们通常会遇到的一个问题,在类的内部,对于一个属性值,何时用属性(self.bankCodeEn)访问而何时用实例变量(_bankCodeEn)访问。一般的建议是,在获取属性值时,可以用实例变量,在设置属性值时,尽量用setter方法,以保证属性的KVO特性。当然,性能也是一个考量,在设置值时,使用实例变量比使用属性设置值的性能高不少。

这里可以看下唐巧的文章: iOS 开发中的争议(一)(属性相关和变量)

计算属性(属性的变化依赖其他属性)

有时候,我们的监听的某个属性可能会依赖于其它多个属性的变化(可以称之为计算属性),不管所依赖的哪个属性发生了变化,都会导致计算属性的变化。

Foundation 框架提供的表示属性依赖的机制如下:

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

或者更为详细的用法如下:

+ (NSSet *)keyPathsForValuesAffecting<键名>

使用方法:

+ (NSSet *)keyPathsForValuesAffectingValuesForKey:(NSString *)key
{
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"fullName"]) {
        keyPaths = [keyPaths setByAddingObjectsFromArray:[NSArray arrayWithObjects:@"firstName", @"lastName",nil]];
    }
    return keyPaths;
}

//建议使用下面的语法,可以让依赖关系更加的明确

+ (NSSet *)keyPathsForValuesAffectingFullName
{
    NSSet *keyPaths = [NSSet setWithArray:[NSArray arrayWithObjects:@"firstName", @"lastName",nil]];
    return keyPaths;
}

需要注意的就是当我们重写+keyPathsForValuesAffectingValueForKey:时,需要去调用super的对应方法,并返回一个包含父类中可能会对key指定属性产生影响的属性集合。

集合属性的监听

方法1:(实现集合代理对象的相关操作方法)

对于集合的KVO,需要了解的一点是,KVO旨在观察关系(relationship)而不是集合。对于不可变集合属性,我们更多的是把它当成一个整体来监听,而无法去监听集合中的某个元素的变化;对于可变集合属性,实际上也是当成一个整体,去监听它整体的变化,如添加、删除和替换元素。

KVC中,我们可以使用集合代理对象(collection proxy object)来处理集合相关的操作。可变数组需要实现以下几个方法:(siblings为相关的数组名称,也就是key)

// 至少实现一个插入方法和一个删除方法
-insertObject:in<Key>AtIndex:
-removeObjectFrom<Key>AtIndex:
-insert<Key>:atIndexes:
-remove<Key>AtIndexes:

// 可选(增强性能)以下方法二选一
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>:

这些方法分别对应插入、删除和替换,有批量操作的,也有只改变一个对象的方法。可以根据实际需要来实现。

实现以上方法后,对于可变数组,当调用kvc访问这个数组的时候,就会返回一个由以上方法调用数组相关操作方法的代理数组对象。这个代理数组对象支持所有正常的NSMutableArray调用。换句话说,调用者并不知道返回的是一个真正的NSMutableArray,还是一个代理的数组。

方法2:使用mutableArrayValueForKey方法

对于可变集合,通常不使用valueForKey:来获取代理对象,而是使用以下方法:

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

通过这个方法,可以将可变数组与强大的KVO结合在一起。KVO机制能在集合改变的时候把详细的变化放进change字典中。

相关文章: