parent
f0ee545221
commit
30697c9be2
21 changed files with 710 additions and 0 deletions
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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. |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
Loading…
Reference in new issue