I would like to log any NSNotifications posted by a single NSNotificationCenter shared accross my application. I have tried subclassing NSNotificationCenter with the intention of adding logging code to the three post methods, but it returns an instance of CFNotification center instead of my subclass.
Surely there is a way of monitoring NSNotification posting?
EDIT/UPDATE
As two answers below correctly point out I could listen to all notifications and log them in a handler, but the sequence the handler would receive these notifications is far from guaranteed to be the same as the sequence in which they were dispatched. If I could be sure the handler would always be the first hander to be notified this would work, but I cannot: 'The order in which observers receive notifications is undefined' From NSNotification Docs
By using - addObserver:selector:name:object: and passing nil for both the name and the object, you will get notified about any notification.
- (id)init
{
self = [super init];
if (self != nil)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(log:) name:nil object:nil];
}
return self;
}
- (void)log:(NSNotification *)notification
{
NSLog(#"%#", notification);
}
Edit: if you want to get the real order of the notifications being send, try subclassing NSNotificationCenter and overriding the following methods:
– postNotification:
– postNotificationName:object:
– postNotificationName:object:userInfo:
If subclassing is no option for you, you might consider defining a category on NSNotificationCenter where you override these methods with calling the super implementation. (you will need to swizzle methods to call super within a category). Tell me if you need help doing so.
You should be able to use [addObserver:self selector:#selector(whatever:) name:nil object:nil] and just put your logging code in the whatever: method. This observer should get all notifications posted by your app (at least all those posted by the default center).
Related
I have an iPad app that uses a proprietary library object which registers for a "UIScreenDidConnectNotification". Occasionally this object is deallocated and reallocated behind the scenes. As it is in a library, I cannot ensure that it is properly removing this observer.
Is there a way for me to manually remove all/any observers for a specific notification (i.e. UIScreenDidConnectNotification) without having any access to the object that has registered. This would keep the application from sending the message to a deallocated object.
Update: Here is the easiest way to fix my problem. I wish I could do a better job, but life is too short.
#import
#import
#interface NSNotificationCenter (AllObservers)
#end
#implementation NSNotificationCenter (AllObservers)
// This function runs before main to swap in our special version of addObserver
+ (void) load
{
Method original, swizzled;
original = class_getInstanceMethod(self, #selector(addObserver:selector:name:object:));
swizzled = class_getInstanceMethod(self, #selector(swizzled_addObserver:selector:name:object:));
method_exchangeImplementations(original, swizzled);
// This function runs before main to swap in our special version of addObserver
+ (void) load
{
Method original, swizzled;
original = class_getInstanceMethod(self, #selector(addObserver:selector:name:object:));
swizzled = class_getInstanceMethod(self, #selector(swizzled_addObserver:selector:name:object:));
method_exchangeImplementations(original, swizzled);
}
/*
Use this function to remove any unwieldy behavior for adding observers
*/
- (void) swizzled_addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
{
NSString *notification = [[NSString alloc] initWithUTF8String: "UIScreenDidConnectNotification" ];
// It's a hack, but I just won't allow my app to add this type of notificiation
if([notificationName isEqualToString: notification])
{
printf("### screen notifcation added for an observer: %s\n", [notificationSender UTF8String] );
}
else
{
// Calls the original addObserver function
[self swizzled_addObserver:notificationObserver selector:notificationSelector name:notificationName object:notificationSender];
}
}
As it is in a library, I cannot ensure that it is properly removing this observer.
If the object is created in a library, it's not your responsibility to remove the object. If the library is deallocating the object without removing it from the notification center, that's a clear bug in the library.
Is there a way for me to manually remove all/any observers for a specific notification... without having any access to the object that has registered.
There's nothing in the API for NSNotificationCenter that lets you do that. Just the opposite, in fact -- the methods that let you remove the observer all require a pointer to a specific object.
I agree with both of Caleb's points: it is not your responsibility to perform this task and there is nothing in the API to support it.
However... if you feel like hacking something in to perform this task for whatever reason, refer to this thread: How to retrieve all NSNotificationCenter observers?
The selected answer of that thread has a category for NSNotificationCenter that allows you to retrieve all observers for a given notification name. Again, this is not recommended though.
I am building a program that utilises NSNotification as I want to be able to pass information through from another class that is going to affect the value of variables in a different class.
So, I have set up the following:
categories.m class:
In viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateTheScore:)name:#"TheScore" object:nil];
in the same class, with my updateTheScore function:
- (void)updateTheScore:(NSNotification *)notification
{
NSLog(#"Notification Received. The value of the score is currently %d", self.mainScreen.currentScore);
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
In mainScreen.m:
self.currentScore++;
[[NSNotificationCenter defaultCenter]postNotificationName:#"TheScore" object:self];
The score in a usual instance would update from 0 to 1.
The program will call the notification correctly, as I can see my NSLog being performed. However, the value of the variable is not passing through, and this is where I am stuck.
Can anyone please think of a solution as to why my variable value is not passing through?
To clarify, if I do an NSLog right before the postNotificationName line to show me the value of self.currentScore; this returns 1, as expected. In the updateTheScore function, it returns 0.
Thanks in advance to everyone.
I do not know why you get another value then expected. Maybe, because your not on main thread? you can check it with [NSThread isMainThread]
Actually if you want to pass an object with notification, you can use userInfo property of NSNotification object. It is the proper way of doing this. One of the best advantage of NSNotificationCenter is, you can post, receive notifications without knowing poster and receiver eachother.
You can post notification like that
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:self
userInfo:#{key:[NSNumber numberWithInt:value]}];
And receive like that
- (void)updateTheScore:(NSNotification *)notification
{
NSInteger value = [[notification.userInfo objectForKey:key] intValue];
}
You are logging self.mainScreen.currentScore. The obvious question is: Is self.mainScreen the same object that posts the notification? Maybe you have several instances of MainScreen (assuming that this is the name of your class).
Since you are attaching self to the notification when you post it, have you tried this?
int currentScore = (int)[[notification object] currentScore];
NSLog(#"Notification Received. The value of the score is currently %d", currentScore);
lets say i have classA which is a class of audio,that sample the audio input many times.
each time class A get a new data (can happen many times in second), he needs to inform another class, which is classB.
Now, i could just make an instance of class B in classA and call B when there is a new data arrived, but this is not a modular software.
i want classA to be "blind" to the outside, and just to add him to every project, and to have another classB that will register him some how, so when A has something new, B will know about it,(without A calling B ! )
how its done right in objective c ?
thanks a lot .
Sounds like you want to implement the Observer Pattern
You can post a notification in ClassA, and register for that notification in other classes (namely ClassB).
This is how you can do it:
(in ClassA):
[[NSNotificationCenter defaultCenter]
postNotificationName:#"noteName" object:self];
(in ClassB):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"noteName" object:nil];
Whenever an instance of ClassA posts a new notification, other instances that have registered to that notification will be informed (instantaneously). In this case, ClassB will perform doSomething:(NSNotification *)note.
[Edit]
You can post that notification your setter method (setVar:(NSString*)newVar).
If you want to pass something along, use the postNotificationName:object:userInfo: variant. userInfo is a NSDictionary and you can pass anything you want in it. for example:
NSDictionary* dic = [NSDictionary dictionaryWithObjectsAndKeys:var, #"variable", nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"noteName" object:self userInfo:dic];
now, edit your doSomething: method:
-(void)doSomething:(NSNotification*)note {
if ([[note name] isEqualToString:#"noteName"]) {
NSLog(#"%#", [note userInfo]);
}
}
More info:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html
https://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/Notification.html
https://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/MacOSXNotifcationOv/Introduction/Introduction.html
As ennuikiller suggested, an easy way to implement an observer pattern in obj-c is to use NSNotificationCenter class. For further info see its class reference.
Edit
An other way is using KVO (Key Value Observing). This is more complicated but has better performances respect to the first one. For a simple explanation see Jeff Lamarche blog and KVO Reference.
Hope it helps.
Here are my objective-c classes:
AppDelegate
SomeScript
How might I call the function loggedIn on the SomeScript class from the app-delegate or any other class?
Thanks,
Christian Stewart
(I'll assume loggedIn is an instance method taking no parameters.) First, several terminology issues:
They're not functions, they're methods (same idea, though).
You don't call methods, you send messages (usually same idea, though).
Most importantly, we usually send messages not to classes, but to instances of those classes. (If you can't visualize the difference, imagine placing a letter in the idea of mailboxes vs. placing a letter in your mailbox. Only one makes sense!)
So, our new plan is to first instantiate SomeScript, then send a message to the instance.
SomeScript* myScript = [[SomeScript alloc] init]; //First, we create an instance of SomeScript
[myScript loggedIn]; //Next, we send the loggedIn message to our new instance
This is good. However! I bet you want your script to stick around for later use. Thus, we should really make it an instance variable of your app delegate. So, instead, in AppDelegate.h, add this inside the braces:
SomeScript* myScript;
Now our variable will stick around, and our first line from before becomes simply:
myScript = [[SomeScript alloc] init];
Last complication: we don't want to create a new script every time we call loggedIn (I assume)! So, you should place the instantiation somewhere it will only be run once (for example, application:DidFinishLaunchingWithOptions:). Ta-da!
You shall have an initialized reference of a SomeScript object in your AppDelegate class (supposing you do not need SomeScript to be a Singleton class like your AppDelegate). Something like:
SomeScript * myScript;
as an ivar in your AppDelegate interface, while in its application:DidFinishLaunchingWithOptions:
you have inited it (let's suppose with the default alloc/init combo calling):
myScript = [[SomeScript alloc] init]
Done all of this, when you need to call a method of myScript you can simply do:
[myScript myMethod:myParameter]
Here you can find a nice guide for beginners from Apple
If you don't want to use instances of SomeScript ... you can follow a different approach. Use NSNotificationCenter for sending a notification to your SomeScript object and make it run a selector after that.
In your -(void)awakeFromNib{} method, from SomeScript place the following code :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mySelector:)
name:#"aUniqueNameForTheNotification"
object:nil];
Create the method "mySelector:" and place the the call to your loggedIn method. (Or if you prefer, you could replace "mySelector:" with loggedIn directly)
-(void) mySelector:(id)elem
{
[self loggedIn];
}
Then don't forget to remove the observer on dealloc, so place the following piece of code in your SomeScript class also :
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Then you can send a notification from any other like so :
[[NSNotificationCenter defaultCenter] postNotificationName:#"aUniqueNameForTheNotification" object:self];
That last piece of code sends a notification to SomeScript and your selector is executed.
Hope it helps you guys!
We can call it like [self loggedIn] When loggedIn method is in SomeScript class, using simple syntaxes in latest xcode.
[[SomeScript new] loggedIn];
I have an NSView subclass which is bound to the arrangedObjects of an NSArrayController. When the array has an item inserted or removed the view is notified. How do I get it to be notified if a model stored in the array has an attribute changed?
Do I need to add my view as an observer to every (relevant) attribute of every item added to the array?
When an item is added to or removed from the array I am notified via observeValueForKeyPath:ofObject:change:context: in my NSView subclass. I am not notified of changes to the models stored in the array but I could, every time I am notified of an insertion, add the view as an observer to the new item's attributes. Is this the best way to do this?
I overrode addObserver for the model class so that I could see what happens and noticed that NSTableView columns bound to the arrangedObjects add themselves as observers to the appropriate attributes. Can this be made to happen automagically or do I set up the observations manually?
A big thank you to dreamlax but I think I didn't do a good enough job explaining my problem. My model class was observable and produced the right notifications but I couldn't work out how to observe them without observing every item in the array directly.
I think the documentation for key paths could be improved because I couldn't find anything that explained the very simple change I needed to make. There's some good info the array magic keypaths but no simple "these are the common things" documentation.
Anyway. Previously in my NSView subclass I had the following:
- (void) bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
if ([binding isEqualToString:#"observedObjects"]) {
[observable addObserver:self forKeyPath:#"arrangedObjects" options:0 context:nil];
} else {
[super bind:binding toObject:observable withKeyPath:keyPath options:options];
}
}
To get notification of changes to the models within the NSArrayController's arrangedObjects all I needed to add was observation of arrangedObjects.name (for the name property of my model). So the above code became:
- (void) bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
if ([binding isEqualToString:#"observedObjects"]) {
[observable addObserver:self forKeyPath:#"arrangedObjects" options:0 context:nil];
[observable addObserver:self forKeyPath:#"arrangedObjects.name" options:0 context:nil];
} else {
[super bind:binding toObject:observable withKeyPath:keyPath options:options];
}
}
That's it! Now if any object in arrangedObjects gets its name changed I am notified.
Maybe rather than observing potentially many key value paths, why not have each object in the array post a notification when something has changed, then only one object needs to observe one notification instead of one object observing many key value paths.
EDIT:
Also, your arrayed objects could also respond to a class method called +keyPathsForValuesAffecting<key> where <key> is your key name. Here's an example: paymentDue is a key, which is affected when the values invoiceItems.count or paymentsMade have changed. When invoiceItems.count or paymentsMade has changed, anything bound to paymentDue is sent a notification.
+ (NSSet *) keyPathsForValuesAffectingPaymentDue:
{
return [NSSet setWithObjects:#"invoiceItems.count", #"paymentMade", nil];
}
If you are running on 10.4, or are targeting 10.4 or earlier, you'll need to use this method instead, but it essentially boils down to the same thing.
EDIT 2:
To clarify your other comment; you can still have each object in the array manually call
[[NSNotificationCenter defaultCenter] postNotificationName:#"ModelDidChange" object:self];
Then, with some controller code you can register for notification updates from your objects. If you choose a unique notification name then you won't need to manually listen from specific objects, you can tell the NSNotificationCenter that you want to receive notifications from any object. Your controller can work out which object has changed quite easily.
Register with the notification center (these methods should be in a controller object):
// This string could really be just about anything you want, but make it conspicuous.
static NSString * const ModelDidChangeName = #"ModelDidChange";
- (void) awakeFromNib
{
// using nil for the object parameter will make the notification center
// invoke modelDidChange: regardless of who the sender is.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(modelDidChange:) name:ModelDidChangeName object:nil];
}
Implement a method to handle the notification.
- (void) modelDidChange:(NSNotification *) notification
{
MyModelClass *myObject = [notification object];
// do stuff with your model if necessary.
// do stuff with your view too
}
Get your model objects to post notifications when parts of them change:
#implementation MyModelClass
- (void) setSomething:(NSString *) newThing
{
[something autorelease];
something = [newThing copy];
if (something == nil)
{
// special case scenario for when something is nil
// do stuff, modify MyModelClass instance's attributes
[[NSNotificationCenter defaultCenter] postNotificationName:ModelDidChange object:self];
// the controller's modelDidChange: method is automatically invoked.
}
}
#end
But
If your model is properly KVC compliant and made with the appropriate KVO callbacks, manual notifications won't be necessary.