epologee
epologee

Reputation: 11319

Your opinion of this alternative to notifications and delegates: Signals?

SO is telling me this question is subjective and likely to be closed. It is indeed subjective, because I'm asking for the opinion of experienced Objective-C developers. Should I post this somewhere else? Please advise.


Fairly new to Objective-C, though fairly confident in the concept of writing OOP code, I've been struggling with the NSNotification vs Delegate dilemma from the start. I've posted a few questions about that subject alone. I do get the gist, I think. Notifications are broadcasted globally, so shouldn't be used for notifying closely related objects. Delegates exist to hand over tasks to other object, that act on behalf of the delegated object. While this can be used for closely related objects, I find the workflow to be verbose (new class, new protocol, etc), and the word "delegation" alone makes me think of armies and bosses and in general makes me feel uneasy.

Where I come from (AS3) there are things called Events. They're halfway between delegates and NSNotifications and pretty much ruled the world of flash notifying, until fairly recently, a Mr. Robert Penner came along and expressed his dissatisfaction with events. He therefore wrote a library that is now widely used in the AS3 community, called Signals. Inspired by C# events and Signals/Slots in Qt, these signals are actually properties of objects, that you access from the outside and add listeners to. There's much more you can do with a signal, but at it's core, that's it.

Because the concept is so humble, I gave it a go and wrote my own signal class in Objective-C. I've gisted Signal.h/.m here.

A way to use this for notifying class A of an event in class B could look like this:

// In class b, assign a Signal instance to a retained property:
self.awesomeThingHappened = [[[Signal alloc] init] autorelease];

// In class a, owner of class b, listen to the signal:
[b.awesomeThingHappened add:self withSelector:@selector(reactToAwesomeThing)];

// And when something actually happens, you dispatch the signal in class b:
[self.awesomeThingHappened dispatch];

// You might even pass along a userInfo dictionary, your selector should match:
[self.awesomeThingHappened dispatchWithUserInfo:userInfo];

I hope it adheres to the right memory management rules, but when the signal deallocs, it should automatically remove all listeners and pass away silently. A signal like this isn't supposed to be a generic replacement of notification and delegation, but there are lot's of close counter situations where I feel a Signal is cleaner than the other two.

My question for stackoverflow is what do you think of a solution like this? Would you instantly erase this from your project if one of your interns puts it in? Would you fire your employee if he already finished his internship? Or is there maybe already something similar yet much grander out there that you'd use instead?

Thanks for your time, EP.


EDIT: Let me give a concrete example of how I used this in an iOS project.

Consider this scenario of four object types with nested ownership. There's a view controller owning a window manager, owning several windows, each owning a view with controls, among which a close button. There's probably a design flaw in here, but that's not the point of the example :P

Now when the close button is tapped, a gesture recognizer fires the first selector in the window object. This needs to notify the window manager that it's closing. The window manager may then decide whether another window appears, or whether the windows stay hidden alltogether, at which point the view controller needs to get a bump to enable scrolling on the main view.

The notifications from window to window manager, and from window manager to view controller are the ones I've now implemented with Signals. This might have been a case of delegation, but for just a 'close' action, it seemed so verbose to create two delegate protocols. On the other hand, because the coupling of these objects is very well defined, it also didn't seem like a case for NSNotifications. There's also not really a value change that I could observe with KVO, because it's just a button tap. Listening to some kind of 'hidden' state would only make me have to reset that flag when reopening a window, which makes it harder to understand and a little error prone.

Upvotes: 4

Views: 1686

Answers (4)

Alejandro
Alejandro

Reputation: 3746

I think that there is a gap between NSNotifications and Delegates. And KVO has the worst API in all of Cocoa.

As for NSNotificationCenter here are some of its problems:

  • it's prone to typos
  • it's hard to track observers of a given object, therefore hard to debug
  • it's very verbose
  • you can only pass notification data in a dictionary, which means you can't use weak references, or structs unless you wrap them. (see: very verbose)
  • doesn't play nice with GCD: limited support for queues (only blocks)

So there is definitely a need for something better.

I created my own observable class which I use on every project. Another alternative is to use ReactiveCocoa's RACSignal.

Upvotes: 0

epologee
epologee

Reputation: 11319

Alright, after marinating the answers and comments for a bit, I think I have come to a conclusion that the Signal class I borrowed from AS3, has very little reason for existence in Objective-C/Cocoa. There are several patterns in Cocoa that cover the ranges of use that I was thinking of covering with the Signal class. This might seem very trivial to more experienced Cocoa developers, but it for me it was hard to get the spectrum complete.

I've tried to put it down fairly concisely, but please correct me if I have them wrong.

Target-Action

Used only for notifying your application of user interaction (touches, mostly). From what I've seen and read, there's no way to 'borrow' the target-action system for your own use

KVO (key value observing)

Very useful for receiving notifications when values change in accessible objects. Not so useful for notifying specific events that have no value attached to them, like timer events or interface followup events.

NSNotification

Very useful for receiving notifications when values change or other events happen in less-accessible objects. Due to the broadcast nature of the notification center, this is less suitable for cases where objects have a direct reference to another.

Delegation

Takes the most lines of code compared to the other three, but is also most suitable when the other three are not. Use this one when one object should be notified of specific events in the other. Delegates should not be abused for just accessing methods of the owner object. Stick to methods like 'should', 'will' and 'did'.

Signal

It was a fun experiment, but I mostly used this for classic delegation situations. I also used it to circumvent linked delegates (c delegate of b, b delegate of a, where a starts the event that should make it to c) without wanting to resort to NSNotification.

I still think there should be a more elegant solution for this edge case, but for now I'll just stick to the existing frameworks. If anyone has a correction or another notification concept, please let me know. Thanks for your help!

Upvotes: 2

mipadi
mipadi

Reputation: 410762

It's an interesting idea, but I guess I don't see what makes it dramatically different from Cocoa's notification center. Compare and contrast:

self.awesomeThingHappened = [[[Signal alloc] init] autorelease];                  // Your signals library
                                                                                  // Cocoa notifications (no equivalent code)

[b.awesomeThingHappened add:self withSelector:@selector(reactToAwesomeThing)];    // Your signals library
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(reactToAwesomeThing:)
                                             name:@"AwesomeThingHappened"
                                           object:n];                             // Cocoa notifications

[self.awesomeThingHappened dispatch];                                             // Your signals library
[[NSNotificationCenter defaultCenter] postNotificationName:@"AwesomeThingHappened"
                                                    object:self];                 // Cocoa notifications

[self.awesomeThingHappened dispatchWithUserInfo:userInfo];                        // Your signals library
[[NSNotificationCenter defaultCenter] postNotificationName:@"AwesomeThingHappened"
                                                    object:self
                                                  userInfo:userInfo];             // Cocoa notifications

So, okay. I don't think you're trying to say that, line-for-line, a Signals library for Cocoa is different; rather, the argument goes that it terms of coupling, it isn't as tight as delegates, but not as loose as notifications. To that end, I guess I wonder how necessary it is? I guess I can see somewhat of a need to say "this object 'A' relies heavily on 'B', but doesn't need to be coupled all that closely", but to be honest, that seems like somewhat rare situation.

At any rate, NSNotificationCenter and its ilk, as well as delegates, are pretty standard in Cocoa apps. I always use the rule of thumb that if you deviate from a standard, even a de facto standard, you should have a good reason. If you have a good reason for using neither NSNotificationCenter nor delegates, then you might have a good reason to use this Signals setup. (And as an aside, I'm hesitant to associate notifications and delegates -- they each have a role and exist for different reasons.)

It's hard to say more without a specific use case. I'm inclined to say, "Hey, it looks cool in a geeky way, but it looks like it fills a role already served by notifications." Do you have any specific use cases you could cite?

Upvotes: 1

Dave DeLong
Dave DeLong

Reputation: 243156

What do you think of a solution like this?

I don't really see what the benefit is. To me, it seems like a combination of target/action+notifications (you can have multiple target/actions for a single notification event, but the t/a pair is registered with the object itself as opposed to a global notification center). In fact, it's more like key-value-observing that way, except that KVO is limited to observable properties.

Would you instantly erase this from your project if one of your interns puts it in?

No. It's not bad code. In fact, it seems kinda neat. But I just don't see an obvious benefit to it.

Would you fire your employee if he already finished his internship?

Of course not. You don't fire people for writing good code.

Is there maybe already something similar yet much grander out there that you'd use instead?

If you really wanted to make this neat, change the API to use blocks instead. Then you could do:

[object dispatchOnAwesomeThingHappened:^{
  NSLog(@"holy cow, something awesome just happened!");
}];

Again, however, you'd be limited to reacting to stuff that the objects explicitly "dispatch". It would be much neater if you could attach stuff to immediately before and/or after any arbitrary method call. If you're interested in that, then I'd check out Aspect Objective-C on github.

Upvotes: 0

Related Questions