开发者

Is subclassing NSNotification the right route if I want to add typed properties?

开发者 https://www.devze.com 2023-04-08 14:25 出处:网络
I am trying to subclass NSNotification. Apple\'s docs for NSNotificationstate the followi开发者_开发问答ng:

I am trying to subclass NSNotification.

Apple's docs for NSNotificationstate the followi开发者_开发问答ng:

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消