Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite ARTEventEmitter per the spec and adjust Connection. #179

Merged
merged 10 commits into from
Feb 9, 2016
4 changes: 3 additions & 1 deletion ably-ios/ARTConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
#import "CompatibilityMacros.h"
#import "ARTTypes.h"
#import "ARTEventEmitter.h"

@class ARTRealtime;
@class ARTEventEmitter;
Expand All @@ -21,14 +22,15 @@ ART_ASSUME_NONNULL_BEGIN
@property (art_nullable, readonly, getter=getKey) NSString *key;
@property (readonly, getter=getSerial) int64_t serial;
@property (readonly, getter=getState) ARTRealtimeConnectionState state;
@property (readonly, getter=getEventEmitter) ARTEventEmitter *eventEmitter;

- (instancetype)initWithRealtime:(ARTRealtime *)realtime;

- (void)connect;
- (void)close;
- (void)ping:(ARTRealtimePingCb)cb;

ART_EMBED_INTERFACE_EVENT_EMITTER(NSNumber *, ARTConnectionStateChange *)

@end

ART_ASSUME_NONNULL_END
8 changes: 4 additions & 4 deletions ably-ios/ARTConnection.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
@interface ARTConnection () {
// FIXME: temporary
__weak ARTRealtime* _realtime;
__weak ARTEventEmitter* _eventEmitter;
}

@end
Expand All @@ -23,6 +24,7 @@ @implementation ARTConnection
- (instancetype)initWithRealtime:(ARTRealtime *)realtime {
if (self == [super init]) {
_realtime = realtime;
_eventEmitter = realtime.eventEmitter;
}
return self;
}
Expand All @@ -43,10 +45,6 @@ - (ARTRealtimeConnectionState)getState {
return _realtime.state;
}

- (ARTEventEmitter *)getEventEmitter {
return _realtime.eventEmitter;
}

- (void)connect {
[_realtime connect];
}
Expand All @@ -59,4 +57,6 @@ - (void)ping:(ARTRealtimePingCb)cb {
[_realtime ping:cb];
}

ART_EMBED_IMPLEMENTATION_EVENT_EMITTER(NSNumber *, ARTConnectionStateChange *)

@end
30 changes: 30 additions & 0 deletions ably-ios/ARTEventEmitter+Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// ARTEventEmitter+Private.h
// ably
//
// Created by Toni Cárdenas on 29/1/16.
// Copyright © 2016 Ably. All rights reserved.
//

#include "ARTEventEmitter.h"
#include "CompatibilityMacros.h"

ART_ASSUME_NONNULL_BEGIN

@interface __GENERIC(ARTEventEmitterEntry, ItemType) : NSObject

@property (readwrite, strong, nonatomic) __GENERIC(ARTEventListener, ItemType) *listener;
@property (readwrite, nonatomic) BOOL once;

- (instancetype)initWithListener:(__GENERIC(ARTEventListener, ItemType) *)listener once:(BOOL)once;

@end

@interface __GENERIC(ARTEventEmitter, EventType, ItemType) ()

@property (readwrite, nonatomic) __GENERIC(NSMutableDictionary, EventType, __GENERIC(NSMutableArray, __GENERIC(ARTEventEmitterEntry, ItemType) *) *) *listeners;
@property (readwrite, nonatomic) __GENERIC(NSMutableArray, __GENERIC(ARTEventEmitterEntry, ItemType) *) *anyListeners;

@end

ART_ASSUME_NONNULL_END
77 changes: 71 additions & 6 deletions ably-ios/ARTEventEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,79 @@
#import <Foundation/Foundation.h>
#import "ARTTypes.h"

@protocol ARTSubscription;

@class ARTRealtime;

@interface ARTEventEmitter : NSObject
ART_ASSUME_NONNULL_BEGIN

@interface __GENERIC(ARTEventListener, ItemType) : NSObject

- (void)call:(ItemType)argument;

@end

@interface __GENERIC(ARTEventEmitter, EventType, ItemType) : NSObject

- (instancetype)initWithRealtime:(ARTRealtime *)realtime;
- (id<ARTSubscription>)on:(ARTRealtimeConnectionStateCb)cb;
- (void)removeEvents;
- (__GENERIC(ARTEventListener, ItemType) *)on:(EventType)event call:(void (^)(ItemType __art_nullable))cb;
- (__GENERIC(ARTEventListener, ItemType) *)on:(void (^)(ItemType __art_nullable))cb;

- (__GENERIC(ARTEventListener, ItemType) *)once:(EventType)event call:(void (^)(ItemType __art_nullable))cb;
- (__GENERIC(ARTEventListener, ItemType) *)once:(void (^)(ItemType __art_nullable))cb;

- (void)off:(EventType)event listener:(__GENERIC(ARTEventListener, ItemType) *)listener;
- (void)off:(__GENERIC(ARTEventListener, ItemType) *)listener;
- (void)off;

- (void)emit:(EventType)event with:(ItemType)data;

@end

// This macro adds methods to a class header file that mimic the API of an event emitter.
// This way you can automatically "implement the EventEmitter pattern" for a class
// as the spec say. It's supposed to be used together with ART_EMBED_IMPLEMENTATION_EVENT_EMITTER
// in the implementation of the class.
#define ART_EMBED_INTERFACE_EVENT_EMITTER(EventType, ItemType) - (__GENERIC(ARTEventListener, ItemType) *)on:(EventType)event call:(void (^)(ItemType __art_nullable))cb;\
- (__GENERIC(ARTEventListener, ItemType) *)on:(void (^)(ItemType __art_nullable))cb;\
\
- (__GENERIC(ARTEventListener, ItemType) *)once:(EventType)event call:(void (^)(ItemType __art_nullable))cb;\
- (__GENERIC(ARTEventListener, ItemType) *)once:(void (^)(ItemType __art_nullable))cb;\
\
- (void)off:(EventType)event listener:(__GENERIC(ARTEventListener, ItemType) *)listener;\
- (void)off:(__GENERIC(ARTEventListener, ItemType) *)listener;\
- (void)off;

// This macro adds methods to a class implementation that just bridge calls to an internal
// instance variable, which must be called _eventEmitter, of type ARTEventEmitter *.
// It's supposed to be used together with ART_EMBED_IMPLEMENTATION_EVENT_EMITTER in the
// header file of the class.
#define ART_EMBED_IMPLEMENTATION_EVENT_EMITTER(EventType, ItemType) - (__GENERIC(ARTEventListener, ItemType) *)on:(EventType)event call:(void (^)(ItemType __art_nullable))cb {\
return [_eventEmitter on:event call:cb];\
}\
\
- (__GENERIC(ARTEventListener, ItemType) *)on:(void (^)(ItemType __art_nullable))cb {\
return [_eventEmitter on:cb];\
}\
\
- (__GENERIC(ARTEventListener, ItemType) *)once:(EventType)event call:(void (^)(ItemType __art_nullable))cb {\
return [_eventEmitter once:event call:cb];\
}\
\
- (__GENERIC(ARTEventListener, ItemType) *)once:(void (^)(ItemType __art_nullable))cb {\
return [_eventEmitter once:cb];\
}\
\
- (void)off:(EventType)event listener:listener {\
[_eventEmitter off:event listener:listener];\
}\
\
- (void)off:(__GENERIC(ARTEventListener, ItemType) *)listener {\
[_eventEmitter off:listener];\
}\
- (void)off {\
[_eventEmitter off];\
}\
\
- (void)emit:(EventType)event with:(ItemType)data {\
[_eventEmitter emit:event with:data];\
}

ART_ASSUME_NONNULL_END
191 changes: 176 additions & 15 deletions ably-ios/ARTEventEmitter.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,200 @@
// Copyright (c) 2015 Ably. All rights reserved.
//

#import "ARTEventEmitter.h"
#import "ARTEventEmitter+Private.h"

#import "ARTRealtime.h"
#import "ARTRealtime+Private.h"
#import "ARTRealtimeChannel.h"
#import "ARTRealtimeChannelSubscription.h"

@interface ARTEventEmitter ()
@implementation NSMutableArray (AsSet)

@property (readonly, weak, nonatomic) ARTRealtime *realtime;
- (void)artRemoveWhere:(BOOL (^)(id))cond {
NSUInteger l = [self count];
for (NSInteger i = 0; i < l; i++) {
if (cond([self objectAtIndex:i])) {
[self removeObjectAtIndex:i];
i--;
l--;
}
}
}

@end

@implementation ARTEventEmitter
@interface ARTEventListener ()

- (instancetype)initWithBlock:(void (^)(id __art_nonnull))block;

@end

@implementation ARTEventListener {
void (^_block)(id __art_nonnull);
}

- (instancetype)initWithBlock:(void (^)(id __art_nonnull))block {
self = [self init];
if (self) {
_block = block;
}
return self;
}

- (void)call:(id)argument {
_block(argument);
}

@end

@implementation ARTEventEmitterEntry

-(instancetype)initWithListener:(ARTEventListener *)listener once:(BOOL)once {
self = [self init];
if (self) {
_listener = listener;
_once = once;
}
return self;
}

- (instancetype)initWithRealtime:(ARTRealtime *)realtime {
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[self class]]) {
return self == object || self.listener == ((ARTEventEmitterEntry *)object).listener;
}
return self.listener == object;
}

@end

@implementation ARTEventEmitter
- (instancetype)init {
self = [super init];
if(self) {
_realtime = realtime;
if (self) {
[self resetListeners];
}
return self;
}

- (id<ARTSubscription>)on:(ARTRealtimeConnectionStateCb)cb {
// TODO: more protection, callback can be nil!
ARTRealtimeConnectionStateSubscription *subscription = [[ARTRealtimeConnectionStateSubscription alloc] initWithRealtime:self.realtime cb:cb];
[self.realtime.stateSubscriptions addObject:subscription];
cb(self.realtime.state, nil);
return subscription;
- (ARTEventListener *)on:(id)event call:(void (^)(id __art_nonnull))cb {
ARTEventListener *listener = [[ARTEventListener alloc] initWithBlock:cb];
[self addOnEntry:[[ARTEventEmitterEntry alloc] initWithListener:listener once:false] event:event];
return listener;
}

- (void)removeEvents {
[self.realtime.stateSubscriptions removeAllObjects];
- (ARTEventListener *)once:(id)event call:(void (^)(id __art_nonnull))cb {
ARTEventListener *listener = [[ARTEventListener alloc] initWithBlock:cb];
[self addOnEntry:[[ARTEventEmitterEntry alloc] initWithListener:listener once:true] event:event];
return listener;
}

- (void)addOnEntry:(ARTEventEmitterEntry *)entry event:(id)event {
[self addObject:entry toArrayWithKey:event inDictionary:self.listeners];
}

- (ARTEventListener *)on:(void (^)(id __art_nonnull))cb {
ARTEventListener *listener = [[ARTEventListener alloc] initWithBlock:cb];
[self addOnAllEntry:[[ARTEventEmitterEntry alloc] initWithListener:listener once:false]];
return listener;
}

- (ARTEventListener *)once:(void (^)(id __art_nonnull))cb {
ARTEventListener *listener = [[ARTEventListener alloc] initWithBlock:cb];
[self addOnAllEntry:[[ARTEventEmitterEntry alloc] initWithListener:listener once:true]];
return listener;
}

- (void)addOnAllEntry:(ARTEventEmitterEntry *)entry {
[self.anyListeners addObject:entry];
}

- (void)off:(id)event listener:(ARTEventListener *)listener {
[self removeObject:listener fromArrayWithKey:event inDictionary:self.listeners where:^BOOL(id entry) {
return ((ARTEventEmitterEntry *)entry).listener == listener;
}];
}

- (void)off:(ARTEventListener *)listener {
BOOL (^cond)(id) = ^BOOL(id entry) {
return ((ARTEventEmitterEntry *)entry).listener == listener;
};
[self.anyListeners artRemoveWhere:cond];
for (id event in [self.listeners allKeys]) {
[self removeObject:listener fromArrayWithKey:event inDictionary:self.listeners where:cond];
}
}

- (void)off {
[self resetListeners];
}

- (void)resetListeners {
_listeners = [[NSMutableDictionary alloc] init];
_anyListeners = [[NSMutableArray alloc] init];
}

- (void)emit:(id)event with:(id)data {
NSMutableArray *toCall = [[NSMutableArray alloc] init];
NSMutableArray *toRemoveFromListeners = [[NSMutableArray alloc] init];
NSMutableArray *toRemoveFromTotalListeners = [[NSMutableArray alloc] init];
@try {
for (ARTEventEmitterEntry *entry in [self.listeners objectForKey:event]) {
if (entry.once) {
[toRemoveFromListeners addObject:entry];
}

[toCall addObject:entry];
}

for (ARTEventEmitterEntry *entry in self.anyListeners) {
if (entry.once) {
[toRemoveFromTotalListeners addObject:entry];
}
[toCall addObject:entry];
}
}
@finally {
for (ARTEventEmitterEntry *entry in toRemoveFromListeners) {
[self removeObject:entry fromArrayWithKey:event inDictionary:self.listeners];
}
for (ARTEventEmitterEntry *entry in toRemoveFromTotalListeners) {
[self.anyListeners removeObject:entry];
}
for (ARTEventEmitterEntry *entry in toCall) {
[entry.listener call:data];
}
}
}

- (void)addObject:(id)obj toArrayWithKey:(id)key inDictionary:(NSMutableDictionary *)dict {
NSMutableArray *array = [dict objectForKey:key];
if (array == nil) {
array = [[NSMutableArray alloc] init];
[dict setObject:array forKey:key];
}
[array addObject:obj];
}

- (void)removeObject:(id)obj fromArrayWithKey:(id)key inDictionary:(NSMutableDictionary *)dict {
NSMutableArray *array = [dict objectForKey:key];
if (array == nil) {
return;
}
[array removeObject:obj];
if ([array count] == 0) {
[dict removeObjectForKey:key];
}
}

- (void)removeObject:(id)obj fromArrayWithKey:(id)key inDictionary:(NSMutableDictionary *)dict where:(BOOL(^)(id))cond {
NSMutableArray *array = [dict objectForKey:key];
if (array == nil) {
return;
}
[array artRemoveWhere:cond];
if ([array count] == 0) {
[dict removeObjectForKey:key];
}
}

@end
Loading