How to implement KVO for readonly derived NSArray property?

我的未来我决定 提交于 2020-01-06 16:59:07

问题


I'd like to implement KVO for an NSArray property that is declared as readonly. The getter for this readonly property returns a copy of the private NSMutableArray that backs the backs the public readonly one:

In my .h:

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

And in my .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

In my tests, I am seeing an exception thrown when I call didChangeValueForKey:. Is this the correct way to do this?


回答1:


I recommend that you don't use a separate property for the mutable array. Instead, have the array property backed by a mutable array variable. Then, implement the indexed collection mutating accessors and make all changes to the array through those. KVO knows to hook into those accessors and emit change notifications. In fact, it can emit better, more specific change notifications that can allow observers to be more efficient in how they respond.

@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



回答2:


According to the KVO docs, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOCompliance.html#//apple_ref/doc/uid/20002178-BAJEAIEE, you need to implement automaticallyNotifiesObserversForKey, something like

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

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

}

I have not tested this code, so apologies if I am on the wrong track.




回答3:


This is fragile, and - (NSArray *)myArray keeps returning different values for the same array which KVO doe not like.

You'd be better of to define a private mutable array and a public read-only array. The when you make changes to the mutable array:

self.myPublicReadOnlyArray=self.myMutableArray.copy;

That way you can avoid all the will/has changed notifications because self.myPublicReadOnlyArray is KVC/KVO compliant.




回答4:


I don't have terribly much experience with this, but I post this answer in hopes that it will either solve your issue or lead you to a solution. In the past I've used this:

-(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 corresponds to:

static NSString * const kYoutubeObserverKey = @"youtubeLinksSet";

and I use a property of the same name in this class, hence the keyvalue name:

@property (strong, nonatomic) NSMutableSet * youtubeLinksSet;

I would add an observer for your key and specify what change your interested in observing. Additionally, I'd keep your key naming consistent, meaning that if you're updating the private key, then observe that private key, not the public one. When the observer detects a change in the private key, then have your public key update as a result of that. For example:

-(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...
        }
    }
}


来源:https://stackoverflow.com/questions/29989249/how-to-implement-kvo-for-readonly-derived-nsarray-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!