aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Jorge Canizales <jcanizales@google.com>2015-02-17 17:09:14 -0800
committerGravatar Jorge Canizales <jcanizales@google.com>2015-02-17 17:09:14 -0800
commit30697c9be2ff01e9f33e0934b58877fc3d11f516 (patch)
treeacf985330b52d27cfcc17e442a995096281aace0
parentf0ee545221414ed28bf3e0fcec7b285762177eeb (diff)
Imports code of the RX library.
-rw-r--r--src/objective-c/RxLibrary/GRXImmediateWriter.h40
-rw-r--r--src/objective-c/RxLibrary/GRXImmediateWriter.m132
-rw-r--r--src/objective-c/RxLibrary/GRXWriteable.h27
-rw-r--r--src/objective-c/RxLibrary/GRXWriteable.m33
-rw-r--r--src/objective-c/RxLibrary/GRXWriter+Immediate.h33
-rw-r--r--src/objective-c/RxLibrary/GRXWriter+Immediate.m31
-rw-r--r--src/objective-c/RxLibrary/GRXWriter+Transformations.h9
-rw-r--r--src/objective-c/RxLibrary/GRXWriter+Transformations.m14
-rw-r--r--src/objective-c/RxLibrary/GRXWriter.h94
-rw-r--r--src/objective-c/RxLibrary/GRXWriter.m79
-rw-r--r--src/objective-c/RxLibrary/NSEnumerator+GRXUtil.h18
-rw-r--r--src/objective-c/RxLibrary/NSEnumerator+GRXUtil.m21
-rw-r--r--src/objective-c/RxLibrary/README.md8
-rw-r--r--src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.h9
-rw-r--r--src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.m28
-rw-r--r--src/objective-c/RxLibrary/private/GRXNSFastEnumerator.h10
-rw-r--r--src/objective-c/RxLibrary/private/GRXNSFastEnumerator.m55
-rw-r--r--src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.h8
-rw-r--r--src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.m24
-rw-r--r--src/objective-c/RxLibrary/transformations/GRXMappingWriter.h7
-rw-r--r--src/objective-c/RxLibrary/transformations/GRXMappingWriter.m30
21 files changed, 710 insertions, 0 deletions
diff --git a/src/objective-c/RxLibrary/GRXImmediateWriter.h b/src/objective-c/RxLibrary/GRXImmediateWriter.h
new file mode 100644
index 0000000000..568dbe6576
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXImmediateWriter.h
@@ -0,0 +1,40 @@
+#import <Foundation/Foundation.h>
+
+#import "GRXWriter.h"
+
+// Utility to construct GRXWriter instances from values that are immediately available when
+// required. The returned writers all support pausing and early termination.
+//
+// Unless the writeable callback pauses them or stops them early, these writers will do all their
+// interactions with the writeable before the start method returns.
+@interface GRXImmediateWriter : NSObject<GRXWriter>
+
+// Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
+// its writeable. The NSEnumerator is released when it finishes.
++ (id<GRXWriter>)writerWithEnumerator:(NSEnumerator *)enumerator;
+
+// Returns a writer that pushes to its writeable the successive values returned by the passed
+// block. When the block first returns nil, it is released.
++ (id<GRXWriter>)writerWithValueSupplier:(id (^)())block;
+
+// Returns a writer that iterates over the values of the passed container and pushes them to
+// its writeable. The container is released when the iteration is over.
+//
+// Note that the usual speed gain of NSFastEnumeration over NSEnumerator results from not having to
+// call one method per element. Because GRXWriteable instances accept values one by one, that speed
+// gain doesn't happen here.
++ (id<GRXWriter>)writerWithContainer:(id<NSFastEnumeration>)container;
+
+// Returns a writer that sends the passed value to its writeable and then finishes (releasing the
+// value).
++ (id<GRXWriter>)writerWithValue:(id)value;
+
+// Returns a writer that, as part of its start method, sends the passed error to the writeable
+// (then releasing the error).
++ (id<GRXWriter>)writerWithError:(NSError *)error;
+
+// Returns a writer that, as part of its start method, finishes immediately without sending any
+// values to its writeable.
++ (id<GRXWriter>)emptyWriter;
+
+@end
diff --git a/src/objective-c/RxLibrary/GRXImmediateWriter.m b/src/objective-c/RxLibrary/GRXImmediateWriter.m
new file mode 100644
index 0000000000..ebeb3f458a
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXImmediateWriter.m
@@ -0,0 +1,132 @@
+#import "GRXImmediateWriter.h"
+
+#import "NSEnumerator+GRXUtil.h"
+
+@implementation GRXImmediateWriter {
+ NSEnumerator *_enumerator;
+ NSError *_errorOrNil;
+ id<GRXWriteable> _writeable;
+}
+
+@synthesize state = _state;
+
+- (instancetype) init {
+ return [self initWithEnumerator:nil error:nil]; // results in an empty writer.
+}
+
+// Designated initializer
+- (instancetype)initWithEnumerator:(NSEnumerator *)enumerator error:(NSError *)errorOrNil {
+ if (((self = [super init]))) {
+ _enumerator = enumerator;
+ _errorOrNil = errorOrNil;
+ _state = GRXWriterStateNotStarted;
+ }
+ return self;
+}
+
+#pragma mark Convenience constructors
+
++ (instancetype)writerWithEnumerator:(NSEnumerator *)enumerator error:(NSError *)errorOrNil {
+ return [[self alloc] initWithEnumerator:enumerator error:errorOrNil];
+}
+
++ (id<GRXWriter>)writerWithEnumerator:(NSEnumerator *)enumerator {
+ return [self writerWithEnumerator:enumerator error:nil];
+}
+
++ (id<GRXWriter>)writerWithValueSupplier:(id (^)())block {
+ return [self writerWithEnumerator:[NSEnumerator grx_enumeratorWithValueSupplier:block]];
+}
+
++ (id<GRXWriter>)writerWithContainer:(id<NSFastEnumeration>)container {
+ return [self writerWithEnumerator:[NSEnumerator grx_enumeratorWithContainer:container]];;
+}
+
++ (id<GRXWriter>)writerWithValue:(id)value {
+ if (value) {
+ return [self writerWithEnumerator:[NSEnumerator grx_enumeratorWithSingleValue:value]];
+ } else {
+ return [self emptyWriter];
+ }
+}
+
++ (id<GRXWriter>)writerWithError:(NSError *)error {
+ if (error) {
+ return [self writerWithEnumerator:nil error:error];
+ } else {
+ return [self emptyWriter];
+ }
+}
+
++ (id<GRXWriter>)emptyWriter {
+ static GRXImmediateWriter *emptyWriter;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ emptyWriter = [self writerWithEnumerator:nil error:nil];
+ });
+ return emptyWriter;
+}
+
+#pragma mark Conformance with GRXWriter
+
+// Most of the complexity in this implementation is the result of supporting pause and resumption of
+// the GRXWriter. It's an important feature for instances of GRXWriter that are backed by a
+// container (which may be huge), or by a NSEnumerator (which may even be infinite).
+
+- (void)writeUntilPausedOrStopped {
+ id value;
+ while (value = [_enumerator nextObject]) {
+ [_writeable didReceiveValue:value];
+ // If the writeable has a reference to us, it might change our state to paused or finished.
+ if (_state == GRXWriterStatePaused || _state == GRXWriterStateFinished) {
+ return;
+ }
+ }
+ [self finishWithError:_errorOrNil];
+}
+
+- (void)startWithWriteable:(id<GRXWriteable>)writeable {
+ _state = GRXWriterStateStarted;
+ _writeable = writeable;
+ [self writeUntilPausedOrStopped];
+}
+
+- (void)finishWithError:(NSError *)errorOrNil {
+ _state = GRXWriterStateFinished;
+ _enumerator = nil;
+ _errorOrNil = nil;
+ id<GRXWriteable> writeable = _writeable;
+ _writeable = nil;
+ [writeable didFinishWithError:errorOrNil];
+}
+
+- (void)setState:(GRXWriterState)newState {
+ // Manual transitions are only allowed from the started or paused states.
+ if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
+ return;
+ }
+
+ switch (newState) {
+ case GRXWriterStateFinished:
+ _state = newState;
+ _enumerator = nil;
+ _errorOrNil = nil;
+ // Per GRXWriter's contract, setting the state to Finished manually
+ // means one doesn't wish the writeable to be messaged anymore.
+ _writeable = nil;
+ return;
+ case GRXWriterStatePaused:
+ _state = newState;
+ return;
+ case GRXWriterStateStarted:
+ if (_state == GRXWriterStatePaused) {
+ _state = newState;
+ [self writeUntilPausedOrStopped];
+ }
+ return;
+ case GRXWriterStateNotStarted:
+ return;
+ }
+}
+
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriteable.h b/src/objective-c/RxLibrary/GRXWriteable.h
new file mode 100644
index 0000000000..bbcdb6a2ba
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriteable.h
@@ -0,0 +1,27 @@
+#import <Foundation/Foundation.h>
+
+// A GRXWriteable is an object to which a sequence of values can be sent. The
+// sequence finishes with an optional error.
+@protocol GRXWriteable <NSObject>
+
+// Push the next value of the sequence to the receiving object.
+// TODO(jcanizales): Name it enumerator:(id<GRXEnumerator>) didProduceValue:(id)?
+- (void)didReceiveValue:(id)value;
+
+// Signal that the sequence is completed, or that an error ocurred. After this
+// message is sent to the instance, neither it nor didReceiveValue: may be
+// called again.
+// TODO(jcanizales): enumerator:(id<GRXEnumerator>) didFinishWithError:(NSError*)?
+- (void)didFinishWithError:(NSError *)errorOrNil;
+@end
+
+typedef void (^GRXValueHandler)(id value);
+typedef void (^GRXCompletionHandler)(NSError *errorOrNil);
+
+// Utility to create objects that conform to the GRXWriteable protocol, from
+// blocks that handle each of the two methods of the protocol.
+@interface GRXWriteable : NSObject<GRXWriteable>
+- (instancetype)initWithValueHandler:(GRXValueHandler)valueHandler
+ completionHandler:(GRXCompletionHandler)completionHandler
+ NS_DESIGNATED_INITIALIZER;
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriteable.m b/src/objective-c/RxLibrary/GRXWriteable.m
new file mode 100644
index 0000000000..3b4f0811aa
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriteable.m
@@ -0,0 +1,33 @@
+#import "GRXWriteable.h"
+
+@implementation GRXWriteable {
+ GRXValueHandler _valueHandler;
+ GRXCompletionHandler _completionHandler;
+}
+
+- (instancetype)init {
+ return [self initWithValueHandler:nil completionHandler:nil];
+}
+
+// Designated initializer
+- (instancetype)initWithValueHandler:(GRXValueHandler)valueHandler
+ completionHandler:(GRXCompletionHandler)completionHandler {
+ if ((self = [super init])) {
+ _valueHandler = valueHandler;
+ _completionHandler = completionHandler;
+ }
+ return self;
+}
+
+- (void)didReceiveValue:(id)value {
+ if (_valueHandler) {
+ _valueHandler(value);
+ }
+}
+
+- (void)didFinishWithError:(NSError *)errorOrNil {
+ if (_completionHandler) {
+ _completionHandler(errorOrNil);
+ }
+}
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriter+Immediate.h b/src/objective-c/RxLibrary/GRXWriter+Immediate.h
new file mode 100644
index 0000000000..2d397d7655
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriter+Immediate.h
@@ -0,0 +1,33 @@
+#import "GRXWriter.h"
+
+@interface GRXWriter (Immediate)
+
+// Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
+// its writeable. The NSEnumerator is released when it finishes.
++ (instancetype)writerWithEnumerator:(NSEnumerator *)enumerator;
+
+// Returns a writer that pushes to its writeable the successive values returned by the passed
+// block. When the block first returns nil, it is released.
++ (instancetype)writerWithValueSupplier:(id (^)())block;
+
+// Returns a writer that iterates over the values of the passed container and pushes them to
+// its writeable. The container is released when the iteration is over.
+//
+// Note that the usual speed gain of NSFastEnumeration over NSEnumerator results from not having to
+// call one method per element. Because GRXWriteable instances accept values one by one, that speed
+// gain doesn't happen here.
++ (instancetype)writerWithContainer:(id<NSFastEnumeration>)container;
+
+// Returns a writer that sends the passed value to its writeable and then finishes (releasing the
+// value).
++ (instancetype)writerWithValue:(id)value;
+
+// Returns a writer that, as part of its start method, sends the passed error to the writeable
+// (then releasing the error).
++ (instancetype)writerWithError:(NSError *)error;
+
+// Returns a writer that, as part of its start method, finishes immediately without sending any
+// values to its writeable.
++ (instancetype)emptyWriter;
+
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriter+Immediate.m b/src/objective-c/RxLibrary/GRXWriter+Immediate.m
new file mode 100644
index 0000000000..841ea8a30f
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriter+Immediate.m
@@ -0,0 +1,31 @@
+#import "GRXWriter+Immediate.h"
+
+#import "GRXImmediateWriter.h"
+
+@implementation GRXWriter (Immediate)
+
++ (instancetype)writerWithEnumerator:(NSEnumerator *)enumerator {
+ return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithEnumerator:enumerator]];
+}
+
++ (instancetype)writerWithValueSupplier:(id (^)())block {
+ return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithValueSupplier:block]];
+}
+
++ (instancetype)writerWithContainer:(id<NSFastEnumeration>)container {
+ return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithContainer:container]];
+}
+
++ (instancetype)writerWithValue:(id)value {
+ return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithValue:value]];
+}
+
++ (instancetype)writerWithError:(NSError *)error {
+ return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithError:error]];
+}
+
++ (instancetype)emptyWriter {
+ return [[self alloc] initWithWriter:[GRXImmediateWriter emptyWriter]];
+}
+
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriter+Transformations.h b/src/objective-c/RxLibrary/GRXWriter+Transformations.h
new file mode 100644
index 0000000000..4c9335b675
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriter+Transformations.h
@@ -0,0 +1,9 @@
+#import "GRXWriter.h"
+
+@interface GRXWriter (Transformations)
+
+// Returns a writer that wraps the receiver, and has all the values the receiver would write
+// transformed by the provided mapping function.
+- (GRXWriter *)map:(id (^)(id value))map;
+
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriter+Transformations.m b/src/objective-c/RxLibrary/GRXWriter+Transformations.m
new file mode 100644
index 0000000000..30e5000afd
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriter+Transformations.m
@@ -0,0 +1,14 @@
+#import "GRXWriter+Transformations.h"
+
+#import "transformations/GRXMappingWriter.h"
+
+@implementation GRXWriter (Transformations)
+
+- (GRXWriter *)map:(id (^)(id))map {
+ if (!map) {
+ return self;
+ }
+ return [[GRXMappingWriter alloc] initWithWriter:self map:map];
+}
+
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriter.h b/src/objective-c/RxLibrary/GRXWriter.h
new file mode 100644
index 0000000000..03b3ee18cd
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriter.h
@@ -0,0 +1,94 @@
+#import <Foundation/Foundation.h>
+
+#import "GRXWriteable.h"
+
+typedef NS_ENUM(NSInteger, GRXWriterState) {
+
+ // The writer has not yet been given a writeable to which it can push its
+ // values. To have an writer transition to the Started state, send it a
+ // startWithWriteable: message.
+ //
+ // An writer's state cannot be manually set to this value.
+ GRXWriterStateNotStarted,
+
+ // The writer might push values to the writeable at any moment.
+ GRXWriterStateStarted,
+
+ // The writer is temporarily paused, and won't send any more values to the
+ // writeable unless its state is set back to Started. The writer might still
+ // transition to the Finished state at any moment, and is allowed to send
+ // didFinishWithError: to its writeable.
+ //
+ // Not all implementations of writer have to support pausing, and thus
+ // trying to set an writer's state to this value might have no effect.
+ GRXWriterStatePaused,
+
+ // The writer has released its writeable and won't interact with it anymore.
+ //
+ // One seldomly wants to set an writer's state to this value, as its
+ // writeable isn't notified with a didFinishWithError: message. Instead, sending
+ // finishWithError: to the writer will make it notify the writeable and then
+ // transition to this state.
+ GRXWriterStateFinished
+};
+
+// An object that conforms to this protocol can produce, on demand, a sequence
+// of values. The sequence may be produced asynchronously, and it may consist of
+// any number of elements, including none or an infinite number.
+//
+// GRXWriter is the active dual of NSEnumerator. The difference between them
+// is thus whether the object plays an active or passive role during usage: A
+// user of NSEnumerator pulls values off it, and passes the values to a writeable.
+// A user of GRXWriter, though, just gives it a writeable, and the
+// GRXWriter instance pushes values to the writeable. This makes this protocol
+// suitable to represent a sequence of future values, as well as collections
+// with internal iteration.
+//
+// An instance of GRXWriter can start producing values after a writeable is
+// passed to it. It can also be commanded to finish the sequence immediately
+// (with an optional error). Finally, it can be asked to pause, but the
+// conforming instance is not required to oblige.
+//
+// Unless otherwise indicated by a conforming class, no messages should be sent
+// concurrently to a GRXWriter. I.e., conforming classes aren't required to
+// be thread-safe.
+@protocol GRXWriter <NSObject>
+
+// This property can be used to query the current state of the writer, which
+// determines how it might currently use its writeable. Some state transitions can
+// be triggered by setting this property to the corresponding value, and that's
+// useful for advanced use cases like pausing an writer. For more details,
+// see the documentation of the enum.
+@property(nonatomic) GRXWriterState state;
+
+// Start sending messages to the writeable. Messages may be sent before the method
+// returns, or they may be sent later in the future. See GRXWriteable.h for the
+// different messages a writeable can receive.
+//
+// If this writer draws its values from an external source (e.g. from the
+// filesystem or from a server), calling this method will commonly trigger side
+// effects (like network connections).
+//
+// This method might only be called on writers in the NotStarted state.
+- (void)startWithWriteable:(id<GRXWriteable>)writeable;
+
+// Send didFinishWithError:errorOrNil immediately to the writeable, and don't send
+// any more messages to it.
+//
+// This method might only be called on writers in the Started or Paused
+// state.
+//
+// TODO(jcanizales): Consider adding some guarantee about the immediacy of that
+// stopping. I know I've relied on it in part of the code that uses this, but
+// can't remember the details in the presence of concurrency.
+- (void)finishWithError:(NSError *)errorOrNil;
+@end
+
+// A "proxy" class that simply forwards values, completion, and errors from its
+// input writer to its writeable.
+// It is useful as a superclass for pipes that act as a transformation of their
+// input writer, and for classes that represent objects with input and
+// output sequences of values, like an RPC.
+@interface GRXWriter : NSObject<GRXWriter>
+- (instancetype)initWithWriter:(id<GRXWriter>)writer NS_DESIGNATED_INITIALIZER;
+@end
diff --git a/src/objective-c/RxLibrary/GRXWriter.m b/src/objective-c/RxLibrary/GRXWriter.m
new file mode 100644
index 0000000000..67d928fed5
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXWriter.m
@@ -0,0 +1,79 @@
+#import "GRXWriter.h"
+
+@interface GRXWriter () <GRXWriteable>
+@end
+
+@implementation GRXWriter {
+ id<GRXWriter> _writer;
+ id<GRXWriteable> _writeable;
+}
+
+- (instancetype)init {
+ return [self initWithWriter:nil];
+}
+
+// Designated initializer
+- (instancetype)initWithWriter:(id<GRXWriter>)writer {
+ if (!writer) {
+ [NSException raise:NSInvalidArgumentException format:@"writer can't be nil."];
+ }
+ if ((self = [super init])) {
+ _writer = writer;
+ }
+ return self;
+}
+
+// This is used to send a completion or an error to the writeable. It nillifies
+// our reference to it in order to guarantee no more messages are sent to it,
+// and to release it.
+- (void)finishOutputWithError:(NSError *)errorOrNil {
+ id<GRXWriteable> writeable = _writeable;
+ _writeable = nil;
+ [writeable didFinishWithError:errorOrNil];
+}
+
+// This is used to stop the input writer. It nillifies our reference to it
+// to release it.
+- (void)finishInput {
+ id<GRXWriter> writer = _writer;
+ _writer = nil;
+ writer.state = GRXWriterStateFinished;
+}
+
+#pragma mark GRXWriteable implementation
+
+- (void)didReceiveValue:(id)value {
+ [_writeable didReceiveValue:value];
+}
+
+- (void)didFinishWithError:(NSError *)errorOrNil {
+ _writer = nil;
+ [self finishOutputWithError:errorOrNil];
+}
+
+#pragma mark GRXWriter implementation
+
+- (GRXWriterState)state {
+ return _writer ? _writer.state : GRXWriterStateFinished;
+}
+
+- (void)setState:(GRXWriterState)state {
+ if (state == GRXWriterStateFinished) {
+ _writeable = nil;
+ [self finishInput];
+ } else {
+ _writer.state = state;
+ }
+}
+
+- (void)startWithWriteable:(id<GRXWriteable>)writeable {
+ _writeable = writeable;
+ [_writer startWithWriteable:self];
+}
+
+- (void)finishWithError:(NSError *)errorOrNil {
+ [self finishOutputWithError:errorOrNil];
+ [self finishInput];
+}
+
+@end
diff --git a/src/objective-c/RxLibrary/NSEnumerator+GRXUtil.h b/src/objective-c/RxLibrary/NSEnumerator+GRXUtil.h
new file mode 100644
index 0000000000..ecd8f2de79
--- /dev/null
+++ b/src/objective-c/RxLibrary/NSEnumerator+GRXUtil.h
@@ -0,0 +1,18 @@
+#import <Foundation/Foundation.h>
+
+@interface NSEnumerator (GRXUtil)
+
+// Returns a NSEnumerator instance that iterates through the elements of the passed container that
+// supports fast enumeration. Note that this negates the speed benefits of fast enumeration over
+// NSEnumerator. It's only intended for the rare cases when one needs the latter and only has the
+// former, e.g. for iteration that needs to be paused and resumed later.
++ (NSEnumerator *)grx_enumeratorWithContainer:(id<NSFastEnumeration>)container;
+
+// Returns a NSEnumerator instance that provides a single object before finishing. The value is then
+// released.
++ (NSEnumerator *)grx_enumeratorWithSingleValue:(id)value;
+
+// Returns a NSEnumerator instance that delegates the invocations of nextObject to the passed block.
+// When the block first returns nil, it is released.
++ (NSEnumerator *)grx_enumeratorWithValueSupplier:(id (^)())block;
+@end
diff --git a/src/objective-c/RxLibrary/NSEnumerator+GRXUtil.m b/src/objective-c/RxLibrary/NSEnumerator+GRXUtil.m
new file mode 100644
index 0000000000..7da05d13c4
--- /dev/null
+++ b/src/objective-c/RxLibrary/NSEnumerator+GRXUtil.m
@@ -0,0 +1,21 @@
+#import "NSEnumerator+GRXUtil.h"
+
+#import "private/GRXNSBlockEnumerator.h"
+#import "private/GRXNSFastEnumerator.h"
+#import "private/GRXNSScalarEnumerator.h"
+
+@implementation NSEnumerator (GRXUtil)
+
++ (NSEnumerator *)grx_enumeratorWithContainer:(id<NSFastEnumeration>)container {
+ // TODO(jcanizales): Consider checking if container responds to objectEnumerator and return that?
+ return [[GRXNSFastEnumerator alloc] initWithContainer:container];
+}
+
++ (NSEnumerator *)grx_enumeratorWithSingleValue:(id)value {
+ return [[GRXNSScalarEnumerator alloc] initWithValue:value];
+}
+
++ (NSEnumerator *)grx_enumeratorWithValueSupplier:(id (^)())block {
+ return [[GRXNSBlockEnumerator alloc] initWithValueSupplier:block];
+}
+@end
diff --git a/src/objective-c/RxLibrary/README.md b/src/objective-c/RxLibrary/README.md
new file mode 100644
index 0000000000..88e90723b9
--- /dev/null
+++ b/src/objective-c/RxLibrary/README.md
@@ -0,0 +1,8 @@
+This is a generic Reactive Extensions library for Objective-C, created to ease
+the implementation of the gRPC Objective-C runtime.
+
+It has no dependencies on gRPC nor other libraries, and should eventually be
+moved under its own GitHub project.
+
+If you're trying to get started on the library, you might want to first read
+GRXWriter.h and then GRXWriteable.h.
diff --git a/src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.h b/src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.h
new file mode 100644
index 0000000000..0bb1f47764
--- /dev/null
+++ b/src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.h
@@ -0,0 +1,9 @@
+#import <Foundation/Foundation.h>
+
+// Concrete subclass of NSEnumerator that delegates the invocations of nextObject to a block passed
+// on initialization.
+@interface GRXNSBlockEnumerator : NSEnumerator
+// The first time the passed block returns nil, the enumeration will end and the block will be
+// released.
+- (instancetype)initWithValueSupplier:(id (^)())block;
+@end
diff --git a/src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.m b/src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.m
new file mode 100644
index 0000000000..9a53531b12
--- /dev/null
+++ b/src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.m
@@ -0,0 +1,28 @@
+#import "GRXNSBlockEnumerator.h"
+
+@implementation GRXNSBlockEnumerator {
+ id (^_block)();
+}
+
+- (instancetype)init {
+ return [self initWithValueSupplier:nil];
+}
+
+- (instancetype)initWithValueSupplier:(id (^)())block {
+ if ((self = [super init])) {
+ _block = block;
+ }
+ return self;
+}
+
+- (id)nextObject {
+ if (!_block) {
+ return nil;
+ }
+ id value = _block();
+ if (!value) {
+ _block = nil;
+ }
+ return value;
+}
+@end
diff --git a/src/objective-c/RxLibrary/private/GRXNSFastEnumerator.h b/src/objective-c/RxLibrary/private/GRXNSFastEnumerator.h
new file mode 100644
index 0000000000..e5f27b1cc7
--- /dev/null
+++ b/src/objective-c/RxLibrary/private/GRXNSFastEnumerator.h
@@ -0,0 +1,10 @@
+#import <Foundation/Foundation.h>
+
+// This is a bridge to interact through NSEnumerator's interface with objects that only conform to
+// NSFastEnumeration. (There's nothing specifically fast about it - you certainly don't win any
+// speed by using this instead of a NSEnumerator provided by your container).
+@interface GRXNSFastEnumerator : NSEnumerator
+// After the iteration of the container (via the NSFastEnumeration protocol) is over, the container
+// is released. If the container is modified during enumeration, an exception is thrown.
+- (instancetype)initWithContainer:(id<NSFastEnumeration>)container;
+@end
diff --git a/src/objective-c/RxLibrary/private/GRXNSFastEnumerator.m b/src/objective-c/RxLibrary/private/GRXNSFastEnumerator.m
new file mode 100644
index 0000000000..817ff34d95
--- /dev/null
+++ b/src/objective-c/RxLibrary/private/GRXNSFastEnumerator.m
@@ -0,0 +1,55 @@
+#import "GRXNSFastEnumerator.h"
+
+@implementation GRXNSFastEnumerator {
+ id<NSFastEnumeration> _container;
+ NSFastEnumerationState _state;
+ // Number of elements of the container currently in the _state.itemsPtr array.
+ NSUInteger _count;
+ // The index of the next object to return from the _state.itemsPtr array.
+ NSUInteger _index;
+ // A "buffer of one element," for the containers that enumerate their elements one by one. Those
+ // will set _state.itemsPtr to point to this.
+ // The NSFastEnumeration protocol requires it to be __unsafe_unretained, but that's alright
+ // because the only use we'd make of its value is to return it immediately as the result of
+ // nextObject.
+ __unsafe_unretained id _bufferValue;
+ // Neither NSEnumerator nor NSFastEnumeration instances are required to work correctly when the
+ // underlying container is mutated during iteration. The expectation is that an exception is
+ // thrown when that happens. So we check for mutations.
+ unsigned long _mutationFlag;
+ BOOL _mutationFlagIsSet;
+}
+
+- (instancetype)init {
+ return [self initWithContainer:nil];
+}
+
+// Designated initializer.
+- (instancetype)initWithContainer:(id<NSFastEnumeration>)container {
+ NSAssert(container, @"container can't be nil");
+ if ((self = [super init])) {
+ _container = container;
+ }
+ return self;
+}
+
+- (id)nextObject {
+ if (_index == _count) {
+ _index = 0;
+ _count = [_container countByEnumeratingWithState:&_state objects:&_bufferValue count:1];
+ if (_count == 0) {
+ // Enumeration is over.
+ _container = nil;
+ return nil;
+ }
+ if (_mutationFlagIsSet) {
+ NSAssert(_mutationFlag == *(_state.mutationsPtr),
+ @"container was mutated while being enumerated");
+ } else {
+ _mutationFlag = *(_state.mutationsPtr);
+ _mutationFlagIsSet = YES;
+ }
+ }
+ return _state.itemsPtr[_index++];
+}
+@end
diff --git a/src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.h b/src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.h
new file mode 100644
index 0000000000..1130f52897
--- /dev/null
+++ b/src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.h
@@ -0,0 +1,8 @@
+#import <Foundation/Foundation.h>
+
+// Concrete subclass of NSEnumerator whose instances return a single object before finishing.
+@interface GRXNSScalarEnumerator : NSEnumerator
+// Param value: the single object this instance will produce. After the first invocation of
+// nextObject, the value is released.
+- (instancetype)initWithValue:(id)value;
+@end
diff --git a/src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.m b/src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.m
new file mode 100644
index 0000000000..b2a1afd00e
--- /dev/null
+++ b/src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.m
@@ -0,0 +1,24 @@
+#import "GRXNSScalarEnumerator.h"
+
+@implementation GRXNSScalarEnumerator {
+ id _value;
+}
+
+- (instancetype)init {
+ return [self initWithValue:nil];
+}
+
+// Designated initializer.
+- (instancetype)initWithValue:(id)value {
+ if ((self = [super init])) {
+ _value = value;
+ }
+ return self;
+}
+
+- (id)nextObject {
+ id value = _value;
+ _value = nil;
+ return value;
+}
+@end
diff --git a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.h b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.h
new file mode 100644
index 0000000000..13640c5bd6
--- /dev/null
+++ b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.h
@@ -0,0 +1,7 @@
+#import "GRXWriter.h"
+
+// A "proxy" writer that transforms all the values of its input writer by using a mapping function.
+@interface GRXMappingWriter : GRXWriter
+- (instancetype)initWithWriter:(id<GRXWriter>)writer map:(id (^)(id value))map
+ NS_DESIGNATED_INITIALIZER;
+@end
diff --git a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m
new file mode 100644
index 0000000000..3aa2a2503a
--- /dev/null
+++ b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m
@@ -0,0 +1,30 @@
+#import "GRXMappingWriter.h"
+
+static id (^kIdentity)(id value) = ^id(id value) {
+ return value;
+};
+
+@interface GRXWriter () <GRXWriteable>
+@end
+
+@implementation GRXMappingWriter {
+ id (^_map)(id value);
+}
+
+- (instancetype)initWithWriter:(id<GRXWriter>)writer {
+ return [self initWithWriter:writer map:nil];
+}
+
+// Designated initializer
+- (instancetype)initWithWriter:(id<GRXWriter>)writer map:(id (^)(id value))map {
+ if ((self = [super initWithWriter:writer])) {
+ _map = map ?: kIdentity;
+ }
+ return self;
+}
+
+// Override
+- (void)didReceiveValue:(id)value {
+ [super didReceiveValue:_map(value)];
+}
+@end