如何为只读派生的 NSArray 属性 实现 KVO?
How to implement KVO for readonly derived NSArray property?
我想为声明为 readonly
的 NSArray
属性 实现 KVO。 getter 为这个 readonly
属性 returns 备份了 public readonly
一个私人 NSMutableArray
的副本:
在我的 .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:
时抛出异常。这是执行此操作的正确方法吗?
根据 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;
}
我没有测试过这段代码,如果我走错了路,请见谅。
我在这方面没有太多经验,但我 post 这个答案希望它能解决您的问题或引导您找到解决方案。过去我用过这个:
-(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";
并且我在此 class 中使用了同名的 属性,因此键值名称为:
@property (strong, nonatomic) NSMutableSet * youtubeLinksSet;
我会为您的密钥添加一个观察者,并指定您有兴趣观察的变化。此外,我会让您的密钥命名保持一致,这意味着如果您要更新私钥,请观察该私钥,而不是 public 私钥。当观察者检测到私钥发生变化时,您的 public 密钥将因此更新。例如:
-(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...
}
}
}
这很脆弱,- (NSArray *)myArray
不断为 KVO 不喜欢的同一个数组返回不同的值。
您最好定义一个私有可变数组和一个public 只读数组。当您对可变数组进行更改时:
self.myPublicReadOnlyArray=self.myMutableArray.copy;
这样您就可以避免所有 will/has 更改的通知,因为 self.myPublicReadOnlyArray
是 KVC/KVO 兼容的。
我建议您不要对可变数组使用单独的 属性。相反,让数组 属性 由可变数组变量支持。然后,实现 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
我想为声明为 readonly
的 NSArray
属性 实现 KVO。 getter 为这个 readonly
属性 returns 备份了 public readonly
一个私人 NSMutableArray
的副本:
在我的 .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:
时抛出异常。这是执行此操作的正确方法吗?
根据 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;
}
我没有测试过这段代码,如果我走错了路,请见谅。
我在这方面没有太多经验,但我 post 这个答案希望它能解决您的问题或引导您找到解决方案。过去我用过这个:
-(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";
并且我在此 class 中使用了同名的 属性,因此键值名称为:
@property (strong, nonatomic) NSMutableSet * youtubeLinksSet;
我会为您的密钥添加一个观察者,并指定您有兴趣观察的变化。此外,我会让您的密钥命名保持一致,这意味着如果您要更新私钥,请观察该私钥,而不是 public 私钥。当观察者检测到私钥发生变化时,您的 public 密钥将因此更新。例如:
-(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...
}
}
}
这很脆弱,- (NSArray *)myArray
不断为 KVO 不喜欢的同一个数组返回不同的值。
您最好定义一个私有可变数组和一个public 只读数组。当您对可变数组进行更改时:
self.myPublicReadOnlyArray=self.myMutableArray.copy;
这样您就可以避免所有 will/has 更改的通知,因为 self.myPublicReadOnlyArray
是 KVC/KVO 兼容的。
我建议您不要对可变数组使用单独的 属性。相反,让数组 属性 由可变数组变量支持。然后,实现 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