Objective-CでEventEmitterを実装した話


は、こういうのがあったのだけど、

https://github.com/jerolimov/EventEmitter

中を見るとイベント発生のところとかでなぜか独自のオブジェクトを使っていたりしてよく分からなかったので、NSNotificationCenterラッパーとして実装した。

インターフェイスはこんな感じ。

```objectivec
typedef void (^KXEventEmitterHandler)(NSNotification* n);

@interface NSObject (KXEventEmitter)

- (void)kx_on:(NSString*)event handler:(KXEventEmitterHandler)handler;
- (void)kx_on:(NSString*)event handler:(KXEventEmitterHandler)handler from:(id)from;
- (void)kx_on:(NSString*)event selector:(SEL)selector;
- (void)kx_on:(NSString*)event selector:(SEL)selector from:(id)from;
- (void)kx_once:(NSString*)event handler:(KXEventEmitterHandler)handler;
- (void)kx_once:(NSString*)event handler:(KXEventEmitterHandler)handler from:(id)from;
- (void)kx_once:(NSString*)event selector:(SEL)selector;
- (void)kx_once:(NSString*)event selector:(SEL)selector from:(id)from;

- (void)kx_off:(NSString*)event;
- (void)kx_off;

- (void)kx_emit:(NSString*)event;
- (void)kx_emit:(NSString*)event userInfo:(NSDictionary*)userInfo;

@end 
```

こんな感じで使えます。
あまり知られてないですけどNSNotificationCenter-addObserver:selector:name:object:のobjectは、senderを指定できるので、それをwrapしてfromという形で送信元を限定できます。nilを設定すると何処からきても処理します。

```objc
- (void)testEventEmitter
{
    [self kx_once:@"hoge" selector:@selector(handler:)];
    [self kx_once:@"fuga" handler:^(NSNotification *n) {
        XCTAssert([n.name isEqualToString:@"fuga"], );
    }];
    NSObject *em = [NSObject new];
    [em kx_emit:@"hoge"];
    [em kx_emit:@"fuga"];
    NSObject *em2 = [NSObject new];
    [self kx_on:@"var" handler:^(NSNotification *n) {
        XCTAssert(n.object == em2, );
    } from:em2];
    [em kx_emit:@"var"];
    [em2 kx_emit:@"var"];
}
```

# Implementation?

```objectivec
static const char * oncesKey = "me.keroxp.app:EventEmitterOncesKey";
static const char * handlersKey = "me.keroxp.app:EventEmitterHandlersKey";


@implementation NSObject (KXEventEmitter)

- (NSMutableDictionary*)onceDictionary
{
    NSMutableDictionary *onced = objc_getAssociatedObject(self, &oncesKey);
    if (!onced) {
        onced = [NSMutableDictionary new];
        objc_setAssociatedObject(self, &oncesKey, onced, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return onced;
}

- (NSMutableDictionary*)handlerDictionary
{
    NSMutableDictionary *handlers = objc_getAssociatedObject(self, &handlersKey);
    if (!handlers) {
        handlers = [NSMutableDictionary new];
        objc_setAssociatedObject(self, &handlersKey, handlers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return handlers;
}

- (void)kx_on:(NSString *)event handler:(KXEventEmitterHandler)handler
{
    [self kx_on:event handler:handler from:nil];
}

- (void)kx_on:(NSString *)event handler:(KXEventEmitterHandler)handler from:(id)from
{
    [[self handlerDictionary] setObject:[handler copy] forKey:event];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_notificationProxy:) name:event object:from];
}

- (void)kx_on:(NSString *)event selector:(SEL)selector
{
    [self kx_on:event selector:selector from:nil];
}

- (void)kx_on:(NSString *)event selector:(SEL)selector from:(id)from;
{
    [[self handlerDictionary] setObject:NSStringFromSelector(selector) forKey:event];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_notificationProxy:) name:event object:from];
}

- (void)kx_once:(NSString *)event handler:(KXEventEmitterHandler)handler
{
    [self kx_once:event handler:handler from:nil];
}

- (void)kx_once:(NSString *)event handler:(KXEventEmitterHandler)handler from:(id)from;
{
    [[self onceDictionary] setObject:@YES forKey:event];
    [self kx_on:event handler:handler from:from];
}

- (void)kx_once:(NSString *)event selector:(SEL)selector
{
    [self kx_once:event selector:selector from:nil];
}

- (void)kx_once:(NSString *)event selector:(SEL)selector from:(id)from
{
    [[self onceDictionary] setObject:@YES forKey:event];
    [self kx_on:event selector:selector from:from];
}

- (void)_notificationProxy:(NSNotification*)not
{
    NSString *name = [not name];
    BOOL once = [[[self onceDictionary] objectForKey:name] boolValue];
    id handler = [[self handlerDictionary] objectForKey:name];
    if ([handler isKindOfClass:[NSString class]]) {
        // sel
        SEL sel = NSSelectorFromString(handler);
        objc_msgSend(self, sel, not);
    }else if ([handler isKindOfClass:NSClassFromString(@"NSBlock")]){
        // block
        KXEventEmitterHandler _handler = (KXEventEmitterHandler)handler;
        _handler(not);
    }
    if (once) {
        [[self onceDictionary] removeObjectForKey:name];
        [[self handlerDictionary] removeObjectForKey:name];
        [self kx_off:name];
    }
}

- (void)kx_off
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)kx_off:(NSString *)event
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:event object:nil];
}

- (void)kx_emit:(NSString *)event
{
    [self kx_emit:event userInfo:nil];
}

- (void)kx_emit:(NSString *)event userInfo:(NSDictionary *)userInfo
{
    [[NSNotificationCenter defaultCenter] postNotificationName:event object:self userInfo:userInfo];
}

@end
```

リアクティブですね。
podでも使えます。

```ruby
pod "KXEventEmitter", :git => "https://github.com/keroxp/KXEventEmitter"
```