简体   繁体   中英

How to add observer on NSMutableArray?

I have searched a lot but didn't find useful code or tutorial.

In my application, I have an mutable array which update in every 60 seconds.

The objects in array is being displayed by table view in multiple view controllers.

I want to reload table view automatically when only when values in array changes or updated.

For this, I want to add observer on mutable array ie when values in array changes then it should call a particular method for eg

-(void)ArrayUpdatedNotification:(NSMutableArray*)array
{
    //Reload table or do something
} 

Thanks in advance.

You can abstract the array into a data container class with accessor methods, and then use key-value observing to observe when the array that backs the container object is changed (you cannot use KVO on an NSArray directly).

A simple example of a class used as an abstraction on top of an array follows. You use its insertObject:inDataAtIndex: and removeObjectFromDataAtIndex: methods instead of directly accessing the with addObject: and removeObject: .

// DataContainer.h
@interface DataContainer : NSObject

// Convenience accessor
- (NSArray *)currentData;

// For KVC compliance, publicly declared for readability
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index;
- (void)removeObjectFromDataAtIndex:(NSUInteger)index;
- (id)objectInDataAtIndex:(NSUInteger)index;
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes;
- (NSUInteger)countOfData;

@end

// DataContainer.m

@interface DataContainer ()

@property (nonatomic, strong) NSMutableArray *data;

@end

@implementation DataContainer

//  We'll use automatic notifications for this example
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    if ([key isEqualToString:@"data"]) {
        return YES;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

- (id)init
{
    self = [super init];
    if (self) {
        // This is the ivar which provides storage
        _data = [NSMutableArray array];
    }
    return self;
}

//  Just a convenience method
- (NSArray *)currentData
{
    return [self dataAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self countOfData])]];
}

//  These methods enable KVC compliance
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index
{
    self.data[index] = object;
}

- (void)removeObjectFromDataAtIndex:(NSUInteger)index
{
    [self.data removeObjectAtIndex:index];
}

- (id)objectInDataAtIndex:(NSUInteger)index
{
    return self.data[index];
}

- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes
{
    return [self.data objectsAtIndexes:indexes];
}

- (NSUInteger)countOfData
{
    return [self.data count];
}

@end

The reason that we do this is so we can now observe changes made to the underlying array. This is done through Key Value Observing . A simple view controller that instantiates and observes a data controller is shown:

// ViewController.h
@interface ViewController : UIViewController

@end

// ViewController.m

@interface ViewController ()

@property (nonatomic,strong) DataContainer *dataContainer;

@end

@implementation ViewController

static char MyObservationContext;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        //  Instantiate a DataContainer and store it in our property
        _dataContainer = [[DataContainer alloc] init];
        //  Add self as an observer. The context is used to verify that code from this class (and not its superclass) started observing.
        [_dataContainer addObserver:self
                         forKeyPath:@"data"
                            options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
                            context:&MyObservationContext];
    }

    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    //  Check if our class, rather than superclass or someone else, added as observer
    if (context == &MyObservationContext) {
        //  Check that the key path is what we want
        if ([keyPath isEqualToString:@"data"]) {
            //  Verify we're observing the correct object
            if (object == self.dataContainer) {
                NSLog(@"KVO for our container property, change dictionary is %@", change);
            }
        }
    }
    else {
        //  Otherwise, call up to superclass implementation
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    //  Insert and remove some objects. Console messages should be logged.
    [self.dataContainer insertObject:[NSObject new] inDataAtIndex:0];
    [self.dataContainer insertObject:[NSObject new] inDataAtIndex:1];
    [self.dataContainer removeObjectFromDataAtIndex:0];
}

- (void)dealloc
{
    [_dataContainer removeObserver:self forKeyPath:@"data" context:&MyObservationContext];
}

@end

When this code runs, three changes to the data are observed by the view controller and logged to the console:

KVO for our container property, change dictionary is {
        indexes = "<NSIndexSet: 0x8557d40>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
        kind = 2;
        new =     (
            "<NSObject: 0x8557d10>"
        );
    }
KVO for our container property, change dictionary is {
        indexes = "<NSIndexSet: 0x715d2b0>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
        kind = 2;
        new =     (
            "<NSObject: 0x71900c0>"
        );
    }
KVO for our container property, change dictionary is {
        indexes = "<NSIndexSet: 0x8557d40>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
        kind = 3;
        old =     (
            "<NSObject: 0x8557d10>"
        );
    }

While this is somewhat complex (and can get much more involved), this is the only way to be notified automatically that a mutable array's contents were changed.

What is can do is - After updating your Array send a Notification (NSNotificationCenter) and this notification will be received by all the controllers. On receiving the notificaiton the controller should do [tableview reloaddata].

Code example :

// Adding an observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateTable:) name:@"arrayUpdated" object:nil];

// Post a notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"arrayUpdated" object:nil]; 

// the void function, specified in the same class where the Notification addObserver method has defined
- (void)updateTable:(NSNotification *)note { 
    [tableView reloadData]; 
}

If you want to use shiny blocks you can do this

// Create an instance variable for your block holder in your interface extension
@property (strong) id notificationHolder;

// Listen for notification events (In your TableView class.
self.notificationHolder = [[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationName"
                             object:nil
                              queue:[NSOperationQueue mainQueue]
                         usingBlock:^(NSNotification *note) {

        NSLog(@"Received notification");
}];

Then in dealloc (or when you don't use it anymore)

- (void)dealloc {
     [[NSNotificationCenter defaultCenter] removeObserver:self.notificationHolder];
}

Then in some other class

// Send a notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];

Ask if something is not clear! Hope it helps!

EDIT DUE TO COMMENT

The " YourEvent " is the name of the notification, this means that you can name it to whatever you want. (Perhaps " UpdateArrayNotification could be a good name?)

Something to think about: Note that you can have several observers for the same notification. This means that one 'post' will be snapped up by all observers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM