很多对象都可以触发事件,也有对象可能是关注其他对象上的事件。这就是构成了被观察者和观察者(或称为Publisher/Subscriber)一个对子。最近学习了一篇题为《Five approaches to listening, observing and notifying in Cocoa.》的文章,了解了Cocoa中五种常用的监听观察机制:
1 手动广播者和监听者(Broadcaster and listeners)
2 键-值观察(Key Value Observing)
3 通知中心(Notification center)
4 上下文通知(Context notification)
5 用于观察的委托(Delegate)
这五种机制给Cocoa的开发者带来了很多的选择和实现的可能的同时,也让之前从事Java和.Net相关开发的程序员来感觉缺点什么,多少是有点怀念如下代码:
- public class MouseEventDemo extends JPanel implements MouseListener {
- public MouseEventDemo() {
- super(new GridBagLayout());
- addMouseListener(this);
- }
- public void mousePressed(MouseEvent e) {
- }
- public void mouseReleased(MouseEvent e) {
- }
- public void mouseEntered(MouseEvent e) {
- }
- public void mouseExited(MouseEvent e) {
- }
- public void mouseClicked(MouseEvent e) {
- }
- }
为此本文将对Cocoa平台上Event系统的实现方式进行一个探讨。
Event系统的类结构图如下:
其中,Event类是对事件的描述,包括了发送者、事件类型等信息。在事件被触发的时候,由EventDispatcher将Event的实例会被推送给(Push Mode)事件的监听者Listener。同时EventDispatcher还具备了注册和取消注册监听者的功能。
各个类的具体实现
Event基类的声明如下:
- @class EventDispatcher;
- @interface Event : NSObject
- {
- EventDispatcher *mSender;
- NSString *mType;
- }
- @property (nonatomic, readonly) NSString* type;
- @property (nonatomic, retain) EventDispatcher* sender;
- - (id)initWithType:(NSString*)type;
- + (Event*)eventWithType:(NSString*)type;
- @end
Event基类的实现如下:
- #import "Event.h"
- @implementation Event
- @synthesize sender = mSender;
- @synthesize type = mType;
- - (id)initWithType:(NSString*)aType
- {
- if (self = [super init])
- {
- mType = [[NSString alloc] initWithString:aType];
- }
- return self;
- }
- + (Event*)eventWithType:(NSString*)type
- {
- return [[[Event alloc] initWithType:type] autorelease];
- }
- @end
有了事件后我们就可以定义事件的发布者了。事件发布者将不同的事件发布给关注该事件的监听者。这里通过NSMutableDictionary进行管理,键值为事件的类型,每个键值对应的就是关注该事件的监听者队列。同时为了便于监听者注册和取消注册准备了
addEventListener:和removeEventListenersAtObject:两个方法。真正发布事件则是通过dispatchEvent:实现。EventDispatcher类的声明如下:
- @class Event;
- @interface EventDispatcher : NSObject
- {
- @private
- NSMutableDictionary *mEventListeners;
- }
- - (void)addEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType;
- - (void)removeEventListenersAtObject:(id)object forType:(NSString*)eventType;
- - (void)dispatchEvent:(Event*)event;
- @end
其中,addEventListener:方法首先通过NSInvocation的invocationWithTarget方法将监听者及其回调函数封装成一个NSInvocation对象,然后将其放入事件类型eventType所对应的队列中:
- - (void)addEventListener:(SEL)listener atObject:(id)object forType:(NSString*)eventType
- {
- if (mEventListeners == nil)
- mEventListeners = [[NSMutableDictionary alloc] init];
- NSInvocation *invocation = [NSInvocation invocationWithTarget:object selector:listener];
- NSArray *listeners = [mEventListeners objectForKey:eventType];
- if (listeners != nil)
- {
- listeners = [listeners arrayByAddingObject:invocation];
- [mEventListeners setObject:listeners forKey:eventType];
- }
- else
- {
- listeners = [[NSArray alloc] initWithObjects:invocation, nil];
- [mEventListeners setObject:listeners forKey:eventType];
- [listeners release];
- }
- }
NSInvocation的invocationWithTarget方法是对NSInvocation的一个扩展,通过Cocoa的Category实现扩展代码如下:
- @implementation NSInvocation (Extensions)
- + (NSInvocation*)invocationWithTarget:(id)target selector:(SEL)selector
- {
- NSMethodSignature *signature = [target methodSignatureForSelector:selector];
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
- invocation.selector = selector;
- invocation.target = target;
- return invocation;
- }
- @end
removeEventListenersAtObject:方法会取消指定对象object对指定事件eventType的监听,实现如下:
- - (void)removeEventListenersAtObject:(id)object forType:(NSString*)eventType
- {
- NSArray *listeners = [mEventListeners objectForKey:eventType];
- if (listeners != nil)
- {
- NSMutableArray *remainingListeners = [[NSMutableArray alloc] init];
- for (NSInvocation *inv in listeners)
- {
- if (inv.target != object){
- [remainingListeners addObject:inv];
- }
- }
- if (remainingListeners.count == 0)
- [mEventListeners removeObjectForKey:eventType];
- else
- [mEventListeners setObject:remainingListeners forKey:eventType];
- [remainingListeners release];
- }
- }
dispatchEvent:就是在事件发生的时候通知对该事件关注的所有监听者,实现如下:
- - (void)dispatchEvent:(Event*)event
- {
- NSMutableArray *listeners = [mEventListeners objectForKey:event.type];
- if (!listeners) return;
- event.sender = self;
- [self retain];
- if (listeners.count != 0)
- {
- [listeners retain];
- for (NSInvocation *inv in listeners)
- {
- [inv setArgument:&event atIndex:2];
- [inv invoke];
- }
- [listeners release];
- }
- [self autorelease];
- }
- @end
实际应用
至此一个Event系统的雏形就完成了。实际使用的时候可以使用Event基类,也可以使用从Event类派生的自定义类CustomizedEvent。实例代码如下:
- #define EVENT_TYPE @"Standard Event"
- - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
- // Insert code here to initialize your application
- EventDispatcher *dispatcher = [[EventDispatcher alloc] init];
- [dispatcher addEventListener:@selector(onEvent:) atObject:self forType:EVENT_TYPE];
- Event *event = [[Event alloc] initWithType:EVENT_TYPE];
- [dispatcher dispatchEvent:event];
- [event release];
- CustomizedEvent *customizedEvent = [[CustomizedEvent alloc] initWithType:EVENT_TYPE];
- [dispatcher dispatchEvent:customizedEvent];
- [customizedEvent release];
- }
- - (void)onEvent:(Event*)event{
- NSLog(@"%@", event);
- }
这样的代码是不是和.Net或Java的实现有几分相似呢?监听者可以关注不同的事件,不同的事件对应不同的Event类型。通过扩展Event类可以引入更多需要关注的信息。比如触摸事件可以加入触摸点信息,定时器事件可以加入前后时间信息等等。
小结
通过这样的一次尝试不仅解了更多Cocoa的监听观察机制,也丰富了Cocoa的监听观察机制的实现。实现后的Event系统让.Net和Java的开发者有了一种熟悉的感觉。利用这样的雏形根据实际需求进行完善和扩展后还可能应用到诸如图形界面开发等需要利用监听观察机制的各种应用中。