I am trying to subclass NSNotification
.
Apple's docs for NSNotification
state the following:
NSNotification
is a class cluster with no instance variables. As such,
you must subclass NSNotification
and override the primitive methods
name
, object
, and userInfo
. You can choose any designated initializer
you like, but be sure that your initializer does not call
NSNotification
’s implementation of init
(via [super init]
).
NSNotification
is not meant to be instantiated directly, and its init
method raises an exception.
But this isn't clear to me. Should I create an initializer like this?
-(id)initWithObject:(id)object
{
return self;
}
Subclassing NSNotification
is an atypical operation. I think I've only seen it done once or twice in the past few years.
If you're looking to pass things along with the notification, that's what the userInfo
property is for. If you don't like accessing things through the userInfo
directly, you could use a category to simplify access:
@interface NSNotification (EasyAccess)
@property (nonatomic, readonly) NSString *foo;
@property (nonatomic, readonly) NSNumber *bar;
@end
@implementation NSNotification (EasyAccess)
- (NSString *)foo {
return [[self userInfo] objectForKey:@"foo"];
}
- (NSNumber *)bar {
return [[self userInfo] objectForKey:@"bar"];
}
@end
You can also use this approach to simplify NSNotification
creation. For example, your category could also include:
+ (id)myNotificationWithFoo:(NSString *)foo bar:(NSString *)bar object:(id)object {
NSDictionary *d = [NSDictionary dictionaryWithObjectsForKeys:foo, @"foo", bar, @"bar", nil];
return [self notificationWithName:@"MyNotification" object:object userInfo:d];
}
If, for some strange reason, you'd need the properties to be mutable, then you'd need to use associative references to accomplish that:
#import <objc/runtime.h>
static const char FooKey;
static const char BarKey;
...
- (NSString *)foo {
return (NSString *)objc_getAssociatedObject(self, &FooKey);
}
- (void)setFoo:(NSString *)foo {
objc_setAssociatedObject(self, &FooKey, foo, OBJC_ASSOCIATION_RETAIN);
}
- (NSNumber *)bar {
return (NSNumber *)objc_getAssociatedObject(self, &BarKey);
}
- (void)setBar:(NSNumber *)bar {
objc_setAssociatedObject(self, &BarKey, bar, OBJC_ASSOCIATION_RETAIN);
}
...
It seems this does work. For example:
#import "TestNotification.h"
NSString *const TEST_NOTIFICATION_NAME = @"TestNotification";
@implementation TestNotification
-(id)initWithObject:(id)object
{
object_ = object;
return self;
}
-(NSString *)name
{
return TEST_NOTIFICATION_NAME;
}
-(id)object
{
return object_;
}
- (NSDictionary *)userInfo
{
return nil;
}
@end
also beware a massive Gotcha related to NSNotifications. The type of NSNotifications greated using NSNotification notificationWithName:object: is NSConcreteNotification, not NSNotification. And to make it a little more awkward, if you are checking for class, NSConcreteNotification is private so you have nothing to compare to.
You don’t set it, exactly—you just override the implementation of the name
method so it returns what you want. In other words:
- (NSString *)name
{
return @"Something";
}
Your initializer looks fine—I haven’t seen an example of an init
that doesn’t call its superclass’s implementation before, but if that’s what the doc’s saying you should do, it’s probably worth a try.
You can pass a userInfo
argument when delivering a notification. Why not create a payload and send that.
// New file:
@interface NotificationPayload : NSObject
@property (copy, nonatomic) NSString *thing;
@end
@implementation NotificationPayload
@end
// Somewhere posting:
NotificationPayload *obj = [NotificationPayload new];
obj.thing = @"LOL";
[[NSNotificationCenter defaultCenter] postNotificationName:@"Hi" object:whatever userInfo:@{ @"payload": obj }];
// In some observer:
- (void)somethingHappened:(NSNotification *)notification
{
NotificationPayload *obj = notification.userInfo[@"payload"];
NSLog(@"%@", obj.thing);
}
Done.
As a side note: I've found over the years that making a conscious effort to avoid subclassing has made my code more clean, maintainable, changeable, testable and extensible. If you can solve the problem using protocols or categories then you wont lock yourself into the first shoddy design you come up with. With Swift 2.0 protocol extensions in the mix we're really laughing too.