【问题标题】:Observing an NSMutableArray for insertion/removal观察 NSMutableArray 以进行插入/删除
【发布时间】:2010-09-23 01:47:49
【问题描述】:

一个类有一个 NSMutableArray 类型的属性(和实例 var),带有合成访问器(通过@property)。如果您使用以下方法观察此数组:

[myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL];

然后像这样在数组中插入一个对象:

[myObj.theArray addObject:NSString.string];

发送了一个 observeValueForKeyPath... 通知。但是,以下确实会发送正确的通知:

[[myObj mutableArrayValueForKey:@"theArray"] addObject:NSString.string];

这是因为mutableArrayValueForKey 返回一个负责通知观察者的代理对象。

但是合成的访问器不应该自动返回这样的代理对象吗?解决这个问题的正确方法是什么——我应该编写一个只调用 [super mutableArrayValueForKey...] 的自定义访问器吗?

【问题讨论】:

    标签: cocoa key-value-observing key-value-coding


    【解决方案1】:

    但是合成的访问器不应该自动返回这样的代理对象吗?

    没有。

    解决这个问题的正确方法是什么——我应该编写一个只调用[super mutableArrayValueForKey...] 的自定义访问器吗?

    没有。实现array accessors。当您调用这些时,KVO 将自动发布适当的通知。所以你所要做的就是:

    [myObject insertObject:newObject inTheArrayAtIndex:[myObject countOfTheArray]];
    

    正确的事情会自动发生。

    为方便起见,您可以编写一个addTheArrayObject: 访问器。此访问器将调用上述真正的数组访问器之一:

    - (void) addTheArrayObject:(NSObject *) newObject {
        [self insertObject:newObject inTheArrayAtIndex:[self countOfTheArray]];
    }
    

    (您可以并且应该为数组中的对象填写正确的类,以代替NSObject。)

    然后,你写[myObject addTheArrayObject:newObject],而不是[myObject insertObject:…]

    可悲的是,add<Key>Object: 和它的对应物 remove<Key>Object:,上次我检查过,KVO 只能识别集合(如在 NSSet 中)属性,而不是数组属性,所以你不会得到免费的 KVO 通知,除非你在它确实识别的访问器之上实现它们。我为此提交了一个错误:x-radar://problem/6407437

    我的博客上有a list of all the accessor selector formats

    【讨论】:

    • #1 问题是当您添加观察者时,您正在观察某个对象的属性。该数组是该属性的,而不是属性本身。这就是为什么您需要使用访问器或 -mutableArrayValueForKey: 来修改数组。
    • 你的最后一点似乎已经过时了——只要我实现了添加和删除访问器,我就可以获得关于 NSArray 属性的免费 KVO 通知。
    【解决方案2】:

    在这种情况下,我不会使用 willChangeValueForKeydidChangeValueForKey。一方面,它们旨在表明该路径上的值已更改,而不是一对多关系中的值正在更改。如果你这样做,你会想要使用willChange:valuesAtIndexes:forKey:。即便如此,使用像这样的手动 KVO 通知是不好的封装。更好的方法是在实际拥有该数组的类中定义一个方法addSomeObject:,其中包括手动 KVO 通知。这样,将对象添加到数组的外部方法也不需要担心处理数组所有者的 KVO,这不是很直观,如果您开始向数组添加对象,可能会导致不需要的代码和可能的错误来自多个地方的数组。

    在这个例子中,我实际上会继续使用mutableArrayValueForKey:。我不赞成可变数组,但我相信通过阅读文档,这种方法实际上用一个新对象替换了整个数组,所以如果性能是一个问题,你还需要实现insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:拥有数组的类。

    【讨论】:

      【解决方案3】:

      当你只想观察计数变化时,你可以使用聚合键路径:

      [myObj addObserver:self forKeyPath:@"theArray.@count" options:0 context:NULL];
      

      但请注意,在 theArray 中的任何重新排序都不会触发。

      【讨论】:

      • 或替代品,例如当使用 [-replaceObjectAtIndex:withObject:] 时。
      【解决方案4】:

      您自己对自己问题的回答几乎是正确的。不要在外部出售theArray。相反,声明一个不同的属性,theMutableArray,对应于没有实例变量,并编写此访问器:

      - (NSMutableArray*) theMutableArray {
          return [self mutableArrayValueForKey:@"theArray"];
      }
      

      结果是其他对象可以使用thisObject.theMutableArray对数组进行更改,这些更改会触发KVO。

      其他答案指出,如果您还实现insertObject:inTheArrayAtIndex:removeObjectFromTheArrayAtIndex:,效率会提高仍然正确。但是没有必要让其他对象知道这些或直接调用它们。

      【讨论】:

      • 使用此快捷方式时,我只能观察到关键路径“theArray”的变化,而不是“theMutableArray”。
      • 除此之外,一切都运行良好,我可以像操作 theMutableArray 一样操作 NSMutableArray 属性。
      • 当我尝试延迟初始化此代理对象时,不会发出任何 KVO 通知。你知道为什么吗? - (NSMutableArray*) theMutableArray { if (_theMutableArray) return _theMutableArray; _theMutableArray = [self mutableArrayValueForKey:@"theArray"];返回_theMutableArray; }
      【解决方案5】:

      如果您不需要 setter,您也可以使用下面更简单的形式,它具有相似的性能(在我的测试中与 growth rate 相同)并且样板更少。

      // Interface
      @property (nonatomic, strong, readonly) NSMutableArray *items;
      
      // Implementation
      @synthesize items = _items;
      
      - (NSMutableArray *)items
      {
          return [self mutableArrayValueForKey:@"items"];
      }
      
      // Somewhere else
      [myObject.items insertObject:@"test"]; // Will result in KVO notifications for key "items"
      

      这是有效的,因为如果数组访问器没有实现并且没有设置键,mutableArrayValueForKey: 将查找名称为_<key><key> 的实例变量。如果找到,代理会将所有消息转发到该对象。

      参见these Apple docs,“有序集合的访问器搜索模式”部分,#3。

      【讨论】:

      • 出于某种原因,这对我不起作用。不过在这种情况下我可能真的是瞎子。
      • 使用此方案时,请务必将您的属性标记为readonly,否则会陷入死循环...
      【解决方案6】:

      您需要将您的addObject: 调用封装在willChangeValueForKey:didChangeValueForKey: 调用中。据我所知,您正在修改的 NSMutableArray 无法知道任何观察者正在监视其所有者。

      【讨论】:

        【解决方案7】:

        一种解决方案是使用 NSArray 并通过插入和删除从头开始创建它,例如

        - (void)addSomeObject:(id)object {
            self.myArray = [self.myArray arrayByAddingObject:object];
        }
        
        - (void)removeSomeObject:(id)object {
            NSMutableArray * ma = [self.myArray mutableCopy];
            [ma removeObject:object];
            self.myArray = ma;
        }
        

        比你得到 KVO 并且可以比较新旧数组

        注意:self.myArray 不应该为 nil,否则 arrayByAddingObject: 结果也为 nil

        视情况而定,这可能是解决方案,并且由于 NSArray 仅存储指针,因此这不是太大的开销,除非您使用大型数组和频繁的操作

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-02-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多