【问题标题】:How to implement KVO for readonly derived NSArray property?如何为只读派生的 NSArray 属性实现 KVO?
【发布时间】:2015-05-01 23:32:58
【问题描述】:

我想为声明为readonlyNSArray 属性实现KVO。这个readonly 属性的getter 返回一个私有NSMutableArray 的副本,它支持公共readonly 一个:

在我的.h:

@interface MyClass : NSObject
@property (readonly, nonatomic) NSArray *myArray;
- (void)addObjectToMyArray:(NSObject *)obj;
- (void)removeObjectFromMyArray:(NSObject *)obj;
@end

在我的.m:

@interface MyClass()
@property (strong, nonatomic) NSMutableArray *myPrivateArray;
@end

@implementation MyClass

- (NSArray *)myArray {
    return (NSArray *)[self.myPrivateArray copy];
}

- (void) addObjectToMyArray:(NSObject *)obj {
    [self willChangeValueForKey:@"myArray"];
    [self.myPrivateArray addObject:obj];
    [self didChangeValueForKey:@"myArray"];
}

- (void) removeObjectToMyArray:(NSObject *)obj {
    [self willChangeValueForKey:@"myArray"];
    [self.myPrivateArray removeObject:obj];
    [self didChangeValueForKey:@"myArray"];
}
@end

在我的测试中,当我调用 didChangeValueForKey: 时,我看到一个异常抛出。这是正确的做法吗?

【问题讨论】:

  • 异常说明了什么?
  • 为什么 myArray 是原子的而 myPrivateArray 是非原子的?如果 myPrivateArray 是非原子的并且您不同步任何内容,则 myArray 将不是线程安全的,因此它将是违反其原子契约的非原子属性。不过,这不是异常的原因..您是从不同的线程访问 MyClass 吗?您是否因此而收到 EXC_BAD_ACCESS 错误?
  • 抱歉,两者都声明为nonatomic
  • 是的......在运行时,在这种情况下无论如何这不会改变任何东西。它还告诉我你没有在这里发布真正的代码。你能用示例代码重现你的问题吗?不?如果示例代码没有问题,你怎么能指望别人在示例代码中找到问题呢?如果您不想发布真实代码,而只发布示例代码,那没关系,但您必须确保示例代码显示出与您尝试解决的问题相同的问题。

标签: ios objective-c key-value-observing


【解决方案1】:

我建议您不要为可变数组使用单独的属性。相反,让数组属性由可变数组变量支持。然后,实现indexed collection mutating accessors 并通过它们对数组进行所有更改。 KVO 知道挂钩这些访问器并发出更改通知。事实上,它可以发出更好、更具体的变更通知,让观察者更有效地做出响应。

@interface MyClass : NSObject
@property (readonly, copy, nonatomic) NSArray *myArray;
- (void)addObjectToMyArray:(NSObject *)obj;
- (void)removeObjectFromMyArray:(NSObject *)obj;
@end

@interface MyClass()
// Optional, if you want to be able to do self.myArray = <whatever> in your implementation
@property (readwrite, copy, nonatomic) NSArray *myArray;
@end

@implementation MyClass
{
    NSMutableArray *_myArray;
}

@synthesize myArray = _myArray;

// If you optionally re-declared the property read-write internally, above
- (void) setMyArray:(NSArray*)array {
    if (array != _myArray) {
        _myArray = [array mutableCopy];
    }
}

- (void) insertObject:(id)anObject inMyArrayAtIndex:(NSUInteger)index {
    [_myArray insertObject:anObject atIndex:index];
}

- (void) removeObjectFromMyArrayAtIndex:(NSUInteger)index {
    [_myArray removeObjectAtIndex:index];
}

- (void) addObjectToMyArray:(NSObject *)obj {
    [self insertObject:obj inMyArrayAtIndex:_myArray.count];
}

- (void) removeObjectToMyArray:(NSObject *)obj {
    NSUInteger index = [_myArray indexOfObject:obj];
    if (index != NSNotFound)
        [self removeObjectFromMyArrayAtIndex:index];
}
@end

【讨论】:

  • 这是完美的解决方案
  • 谢谢肯。如果你不介意,你能解释一下这里到底发生了什么吗?
  • @KenThomases,当我运行测试时,我得到了 EXC_BAD_ACCESS 方法 addObjectToMyArray 的异常。知道是什么原因造成的吗?
  • 属性声明建立接口(例如,客户端可以期望哪些访问器方法)。它不会强制执行特定的实现。所以,仅仅因为属性被声明为NSArray* 类型,并不意味着它的后备存储必须是NSArray*。在这种情况下,因为NSMutableArray 是一个NSArray,所以使用NSMutableArray 作为属性的后备存储很方便,不需要两个相互关联的属性。对于一个属性,您只需要确保它与 KVO 兼容,这很容易通过始终通过访问器对其进行变异来完成。
  • 关于您的异常,您可能未能在观察者被释放之前取消注册它。所以,KVO 试图在一个不再存在的对象上调用-observeValueForKeyPath:...。或者它可能是一个不同但相似的错误。与僵尸仪器一起运行。如果您需要更多帮助,请按照其他人的要求显示崩溃的详细信息。
【解决方案2】:

根据 KVO 文档 https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-BAJEAIEE,您需要实现 automaticallyNotifiesObserversForKey,类似

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {

BOOL automatic = NO;
if ([theKey isEqualToString:@"myArray"]) {
    automatic = NO;
}
else {
    automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;

}

我没有测试过这段代码,如果我走错了路,请见谅。

【讨论】:

    【解决方案3】:

    这很脆弱,- (NSArray *)myArray 不断为同一个数组返回不同的值,这是 KVO 不喜欢的。

    你最好定义一个私有的可变数组和一个公共的只读数组。当您对可变数组进行更改时:

    self.myPublicReadOnlyArray=self.myMutableArray.copy;
    

    这样您就可以避免所有将/已更改通知,因为self.myPublicReadOnlyArray 符合 KVC/KVO。

    【讨论】:

      【解决方案4】:

      我对此没有太多经验,但我发布此答案是希望它能够解决您的问题或引导您找到解决方案。过去我用过这个:

      -(void)viewDidLoad{
          [self addObserver:self forKeyPath:kYoutubeObserverKey options:NSKeyValueObservingOptionNew context:nil];
      }
      
      -(void) addLocatedYoutubeURLToList:(NSString *)youtubeURL{
      
          // -- KVO Update of Youtube Links -- //
          [self willChangeValueForKey:kYoutubeObserverKey
                      withSetMutation:NSKeyValueUnionSetMutation
                         usingObjects:[NSSet setWithObject:youtubeURL]];
      
          [self.youtubeLinksSet addObject:youtubeURL];
      
          [self didChangeValueForKey:kYoutubeObserverKey
                     withSetMutation:NSKeyValueUnionSetMutation
                        usingObjects:[NSSet setWithObject:youtubeURL]];
      }
      

      kYoutubeObserverKey对应:

      static NSString * const kYoutubeObserverKey = @"youtubeLinksSet";
      

      我在这个类中使用了同名的属性,因此键值名称:

      @property (strong, nonatomic) NSMutableSet * youtubeLinksSet;
      

      我会为您的密钥添加一个观察者,并指定您有兴趣观察的变化。此外,我会保持您的密钥命名一致,这意味着如果您要更新私钥,请注意该私钥,而不是公共密钥。当观察者检测到私钥发生变化时,您的公钥会因此而更新。例如:

      -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
      
          NSNumber * keyValueChangeType = change[@"kind"];
          if ([keyValueChangeType integerValue] == NSKeyValueChangeInsertion) {
      
              if ([keyPath isEqualToString:kYoutubeObserverKey] ) {
                  //code and such...
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-24
        • 2011-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-20
        相关资源
        最近更新 更多