How to add observer on NSMutableArray? - objective-c

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 i.e when values in array changes then it should call a particular method for e.g
-(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.

Related

NSTreeController KVO notifications unexpectedly not firing

I've encountered a bit of a poser involving NSTreeController and KVO. NSTreeController's selectionIndexPaths property is documented as being KVO-observable—and when I observe it directly, it works perfectly. However, if I list NSTreeController's selectionIndexPath as a dependency of some other property, and then try to observe that, the notifications are not fired when one would expect.
Here's the shortest sample code I could come up with to demonstrate what I mean:
import Cocoa
class ViewController: NSViewController {
// Our tree controller
#IBOutlet dynamic var treeController: NSTreeController!
// Some random property on my object; you'll see why it's here later
#objc dynamic var foo: String = "Foo"
// A quick-and-dirty class to give us something to populate our tree with
class Thingy: NSObject {
#objc let name: String
init(_ name: String) { self.name = name }
#objc var children: [Thingy] { return [] }
}
// The property that the tree controller's `Content Array` is bound to
#objc dynamic var thingies: [Thingy] = [Thingy("Foo"), Thingy("Bar")]
// Dependencies for selectionIndexPaths
#objc private static let keyPathsForValuesAffectingSelectionIndexPaths: Set<String> = [
#keyPath(treeController.selectionIndexPaths),
#keyPath(foo)
]
// This property should be dependent on the tree controller's selectionIndexPaths
// (and also on foo)
#objc dynamic var selectionIndexPaths: [IndexPath] {
return self.treeController.selectionIndexPaths
}
// Some properties to store our KVO observations
var observer1: NSKeyValueObservation? = nil
var observer2: NSKeyValueObservation? = nil
// And set up the observations
override func viewDidLoad() {
super.viewDidLoad()
self.observer1 = self.observe(\.selectionIndexPaths) { _, _ in
print("This is only logged when foo changes")
}
self.observer2 = self.observe(\.treeController.selectionIndexPaths) { _, _ in
print("This, however, is logged when the tree controller's selection changes")
}
}
// A button is wired to this; its purpose is to set off the
// KVO notifications for foo
#IBAction func changeFoo(_: Any?) {
self.foo = "Bar"
}
}
In addition, the following setup is done in the storyboard:
Add a tree controller, and connect the view controller's treeController outlet to it.
Bind the tree controller's "Content Array" binding to thingies on the view controller.
Set the tree controller's "Children Key Path" to children.
Create an outline view, and bind its "Content" and "Selection Index Paths" bindings to arrangedObjects and selectionIndexPaths respectively on the tree controller.
Create a button, and point it at the view controller's changeFoo: method.
If you'd like to try it yourself, I've uploaded a sample project here.
The behavior is as follows:
The notification for observer2 is always fired whenever the outline view's (and thus the tree controller's) selection changes, as one would expect.
However, the notification for observer1 is not fired when the outline view's selection changes.
However, observer1's notification is fired when the button is clicked, and foo is changed. This suggests that the property's dependencies are being considered, but just not for this one particular key path.
Using the old-school method with an observeValue(forKeyPath:bla:bla:bla:) override instead of the swank Swift 4 closure-based system seems to behave the same way.
EDIT: Well, it's not Swift's fault! Same thing happens when I write this program in Objective-C:
#interface Thingy: NSObject
#property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
#end
#implementation Thingy
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self == nil) {
return nil;
}
self->_name = name;
return self;
}
- (NSArray *)children { return #[]; }
#end
void *ctxt1 = &ctxt1;
void *ctxt2 = &ctxt2;
#interface ViewController()
#property (nonatomic, strong) IBOutlet NSTreeController *treeController;
#property (nonatomic, copy) NSString *foo;
#property (nonatomic, copy) NSArray *thingies;
#end
#implementation ViewController
+ (NSSet *)keyPathsForValuesAffectingSelectionIndexPaths {
return [NSSet setWithObjects:#"treeController.selectionIndexPaths", #"foo", nil];
}
- (NSArray *)selectionIndexPaths {
return self.treeController.selectionIndexPaths;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.thingies = #[[[Thingy alloc] initWithName:#"Foo"], [[Thingy alloc] initWithName:#"Bar"]];
[self addObserver:self forKeyPath:#"selectionIndexPaths" options:0 context:ctxt1];
[self addObserver:self forKeyPath:#"treeController.selectionIndexPaths" options:0 context:ctxt2];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (context == ctxt1) {
NSLog(#"This only gets logged when I click the button");
} else if (context == ctxt2) {
NSLog(#"This gets logged whenever the selection changes");
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (IBAction)changeFoo:(__unused id)sender {
self.foo = #"Bar";
}
#end
I've been staring at this for a while, and I cannot figure out why directly observing treeController.selectionIndexPaths works, but observing a property that depends on treeController.selectionIndexPaths does not. And since I've generally felt like I had a pretty good handle on KVO and its workings, it is really bugging me that I can't explain this.
Does anyone know the reason for this discrepancy?
Thanks!

Why does OCMock partialMock break KVO?

If I have an object that uses KVO to observe a property on some object and then create a partial mock for that observer I no longer receive any notifications. Why is this?
Here's a minimal example:
#interface TestPartialMockAndKVO : SenTestCase
#end
#implementation TestPartialMockAndKVO
- (void)test {
// Should print "Changed!" when foo property is changed
MyObserver* myObserver = [[[MyObserver alloc] init] autorelease];
// But with this line, there is no print out
[OCMockObject partialMockForObject:myObserver];
[myObserver setFoo:#"change"];
}
#end
-
#interface MyObserver : NSObject
#property (copy) NSString* foo;
#end
#implementation MyObserver
- (id)init {
self = [super init];
[self addObserver:self forKeyPath:#"foo" options:0 context:NULL];
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
NSLog(#"Changed!");
}
- (void)dealloc { ... }
#end
Both KVO and OCMock are doing some little runtime tricks whereby they create a private subclass of your actual class in order to perform their magic. KVO is doing a thing called "isa-swizzling", and OCMock is creating an object to be the forwarding target of your original object.
Each system is sort of off in its own little world, with its own class that has nothing to do with the other. Mocking KVO with OCMock looks similar to your problem. I think you should be able to make this work just by telling your mock to
[[myMock expect] observeValueForKeyPath:#"foo"
ofObject:myObserver
change:[OCMArg any]
context:[OCMArg any]];

KVO for one-to-many but NSNull object passed into observeValueForKeyPath

I have one managed object with a one-to-many relationship to member class. When I add the observers for members, it worked. When one new member is added to the relationship, the observeValueForKeyPath will be invoked with the new object and change dictionary contains the new member object. However, observeValueForKeyPath will be triggered second time with all values nil and change dictionary new="NULL". What is the second trigger? I set a breakpoint, but not sure who made the trigger.
#interface FooObject : NSManagedObject {}
#property (nonatomic, strong) NSString *fooId;
#property (nonatomic, strong) NSSet* members;
#end
#implementation FooObject
#dynamic fooId;
#dynamic members;
- (NSMutableSet*)membersSet {
[self willAccessValueForKey:#"members"];
NSMutableSet *result = (NSMutableSet*)[self mutableSetValueForKey:#"members"];
[self didAccessValueForKey:#"members"];
return result;
}
- (void)registerObservers {
[self addObserver:self
forKeyPath:#"members"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)unregisterObservers {
#try{
[self removeObserver:self forKeyPath:#"members"];
}#catch(id anException){
//do nothing, obviously it wasn't attached because an exception was thrown
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
id valueNewSet = [change objectForKey:NSKeyValueChangeNewKey];
if (![valueNewSet isKindOfClass:[NSSet class]]) {
// not a NSSet, it contains <null> value
NSLog(#"%#", change);
NSLog(#"%#", object);
}
if ([[change objectForKey:NSKeyValueChangeKindKey] intValue] == NSKeyValueChangeInsertion) {
// insert change is valid, process the changes
}
}
#end
Log output:
{
kind = 1;
new = "<null>";
}
<FooObject: 0xfa9cc60> (entity: FooObject; id: 0xfa9be00 <x-coredata://39DB31FD-6795-4FDE-B700-819AB22E5170/SHInterest/p6> ; data: {
fooId = nil;
members = nil;
})
EDIT 1
I set a breakpoint at NSLog(#"%#", change);
This is the stack trace but not really helpful to figure who makes this call.
main -> UIApplicationMain -> NSKeyValueNotifyObserver -> observeValueForKeyPath:ofObject:change:context
EDIT 2
Maybe this is still a bug?
http://www.cocoabuilder.com/archive/cocoa/182567-kvo-observevalueforkeypath-not-reflecting-changes.html
I am running into the same issue where the "implicit" assignment (and hence the notification of the KVO observer) apparently occurs only as part of deallocating MOs: I save a child MOC, then release it, then iOS releases its MOs. I assume that it then sets one-to-many relationships temporarily to NSNull in the process of deallocating related MOs (where delete rule cascade applies).
So next to change kinds for insertion and deletion my KVO observer now also accepts NSKeyValueChangeSetting and asserts change[NSKeyValueChangeNewKey] == NSNull.null. Call it a pragmatic solution.

KVO with Run-to-Completion semantics - Is it possible?

I recently ran into reentrancy issues with KVO. To visualize the problem, I would like to show a minimal example. Consider the interface of an AppDelegate class
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic) int x;
#end
as well as its implementation
#implementation AppDelegate
- (BOOL) application:(__unused UIApplication *)application
didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions
{
__unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self];
self.x = 42;
NSLog(#"%d", self.x);
return YES;
}
#end
Unexpectedly, this program prints 43 to the console.
Here's why:
#interface BigBugSource : NSObject {
AppDelegate *appDelegate;
}
#end
#implementation BigBugSource
- (id)initWithAppDelegate:(AppDelegate *)anAppDelegate
{
self = [super init];
if (self) {
appDelegate = anAppDelegate;
[anAppDelegate addObserver:self
forKeyPath:#"x"
options:NSKeyValueObservingOptionNew
context:nil];
}
return self;
}
- (void)dealloc
{
[appDelegate removeObserver:self forKeyPath:#"x"];
}
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(__unused void *)context
{
if (appDelegate.x == 42) {
appDelegate.x++;
}
}
#end
As you see, some different class (that may be in third-party code you do not have access to) may register an invisible observer to a property. This observer is then called synchronously, whenever the property's value has changed.
Because the call happens during the execution of another function, this introduces all sort of concurrency / multithreading bugs although the program runs on a single thread. Worse, the change happens without an explicit notice in the client-code (OK, you could expect that concurrency issues arise whenever you set a property...).
What is the best practice to solve this problem in Objective-C?
Is there some common solution to regain run-to-completion semantics automatically, meaning that KVO-Observation messages go through an event-queue, AFTER the current method finishes executing and invariants / postconditions are restored?
Not exposing any properties?
Guarding every critical function of an object with a boolean variable to ensure that reentrancy is not possible?
For example: assert(!opInProgress); opInProgress = YES; at the beginning of the methods, and opInProgress = NO; at the end of the methods. This would at least reveal those kind of bugs directly during runtime.
Or is it possible to opt out of KVO somehow?
Update
Based on the answer by CRD, here is the updated code:
BigBugSource
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(__unused void *)context
{
if (appDelegate.x == 42) {
[appDelegate willChangeValueForKey:#"x"]; // << Easily forgotten
appDelegate.x++; // Also requires knowledge of
[appDelegate didChangeValueForKey:#"x"]; // whether or not appDelegate
} // has automatic notifications
}
AppDelegate
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"x"]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
- (BOOL) application:(__unused UIApplication *)application
didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions
{
__unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self];
[self willChangeValueForKey:#"x"];
self.x = 42;
NSLog(#"%d", self.x); // now prints 42 correctly
[self didChangeValueForKey:#"x"];
NSLog(#"%d", self.x); // prints 43, that's ok because one can assume that
// state changes after a "didChangeValueForKey"
return YES;
}
What you are asking for is manual change notification and is supported by KVO. It is a three stage process:
Your class overrides + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey returning NO for any property you wish to defer notifications for and deferring to super otherwise;
Before changing a property you call [self willChangeValueForKey:key]; and
When you are ready for the notification to occur you call [self didChangeValueForKey:key]
You can build on this protocol quite easily, e.g. it is easy to keep a record of keys you have changed and trigger them all before you exit.
You can also use willChangeValueForKey: and didChangeValueForKey with automatic notifications turned on if you directly alter the backing variable of a property and need to trigger KVO.
The process along with an examples is described in Apple's documentation.

How to change all my application texts (UILabel, UITableCell.....) color?

I would like to do something similar in CSS than "BODY {color:red;}" but in objectif C. I mean if I have 10 differents UIView, I would like to change all the UIView texts color in one time.
Cheers
Simple case - you somehow gathered them together
for (UIView *v in styledViews) {
// apply current style here
}
I doubt that this is your case
Complex case - there are tons of styled views everywhere.
Disclaimer: I can't guarantee anything about following code, it works on my simulator, which doesn't mean it will not blow up in user's hands. I wrote it because it was fun and may help Thomas to solve his problem. I didn't check documentation thoroughly because it's already 5 a.m. here
1) Encapsulate style stuff in some StyleManager class (in this example applyCurrentStyle: will apply current style to any view passed to it). It should post notification each time style is changed (e.g. kStyleManagerNotificationStyleChanged)
2) Make UIView category (like UIView+Style) with public setStyleManager: method.
3) Implement it:
#import "UIView+Style.h"
#import <objc/runtime.h>
#interface StyleSubscription : NSObject {
StyleManager *styleManager;
NSObject *subscriber;
}
#property (readonly) StyleManager *styleManager;
- (id)initWithStyleManager:(NSObject*)p subscriber:(NSObject*)s;
#end
#implementation StyleSubscription
#synthesize styleManager;
- (id)initWithStyleManager:(StyleManager*)sManager subscriber:(NSObject*)s {
if (self = [super init]) {
styleManager = [sManager retain];
subscriber = s;
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:subscriber
name:kStyleManagerNotificationStyleChanged
object:styleManager];
[styleManager release];
[super dealloc];
}
#end
#implementation UIView (Style)
static char styleSubsriptionKey;
- (StyleManager*)styleManager {
StyleSubscription *s = objc_getAssociatedObject(self, &styleSubsriptionKey);
return s.styleManager;
}
- (void)styleChanged:(NSNotification*)n {
[[self styleManager] applyCurrentStyle:self];
}
- (void)setStyleManager:(StyleManager*)sManager {
if ([self styleManager] == sManager) {
return;
}
StyleSubscription *subscr = nil;
if (sManager != nil) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(styleChanged:)
name:kStyleManagerNotificationStyleChanged
object:sManager];
subscr = [[[StyleSubscription alloc] initWithStyleManager:sManager
subscriber:self] autorelease];
}
objc_setAssociatedObject(self, &styleSubsriptionKey, subscr, OBJC_ASSOCIATION_RETAIN);
[sManager applyCurrentStyle:self];
}
#end
Each time style manager posts notification correspondent views will be updated with a new style. View will unsubscribe from style notifications automatically upon deallocation. Style manager can be removed explicitly [view setStyleManager:nil].
you can fix this in a nice way.here is a tutorial
http://dot-ios.blogspot.com/2013/02/design-uilabel-in-optimize-way-for.html