blob: 9567ea8d98e47ad737598add5b2027efd40eb1e1 [file] [log] [blame]
//
// RACSequence.m
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "RACSequence.h"
#import "RACArraySequence.h"
#import "RACDynamicSequence.h"
#import "RACEagerSequence.h"
#import "RACEmptySequence.h"
#import "RACScheduler.h"
#import "RACSignal.h"
#import "RACSubscriber.h"
#import "RACTuple.h"
#import "RACUnarySequence.h"
// An enumerator over sequences.
@interface RACSequenceEnumerator : NSEnumerator
// The sequence the enumerator is enumerating.
//
// This will change as the enumerator is exhausted. This property should only be
// accessed while synchronized on self.
@property (nonatomic, strong) RACSequence *sequence;
@end
@interface RACSequence ()
// Performs one iteration of lazy binding, passing through values from `current`
// until the sequence is exhausted, then recursively binding the remaining
// values in the receiver.
//
// Returns a new sequence which contains `current`, followed by the combined
// result of all applications of `block` to the remaining values in the receiver.
- (instancetype)bind:(RACStreamBindBlock)block passingThroughValuesFromSequence:(RACSequence *)current;
@end
@implementation RACSequenceEnumerator
- (id)nextObject {
id object = nil;
@synchronized (self) {
object = self.sequence.head;
self.sequence = self.sequence.tail;
}
return object;
}
@end
@implementation RACSequence
#pragma mark Lifecycle
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock {
return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"];
}
#pragma mark Class cluster primitives
- (id)head {
NSCAssert(NO, @"%s must be overridden by subclasses", __func__);
return nil;
}
- (RACSequence *)tail {
NSCAssert(NO, @"%s must be overridden by subclasses", __func__);
return nil;
}
#pragma mark RACStream
+ (instancetype)empty {
return RACEmptySequence.empty;
}
+ (instancetype)return:(id)value {
return [RACUnarySequence return:value];
}
- (instancetype)bind:(RACStreamBindBlock (^)(void))block {
RACStreamBindBlock bindBlock = block();
return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name];
}
- (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence {
// Store values calculated in the dependency here instead, avoiding any kind
// of temporary collection and boxing.
//
// This relies on the implementation of RACDynamicSequence synchronizing
// access to its head, tail, and dependency, and we're only doing it because
// we really need the performance.
__block RACSequence *valuesSeq = self;
__block RACSequence *current = passthroughSequence;
__block BOOL stop = NO;
RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id {
while (current.head == nil) {
if (stop) return nil;
// We've exhausted the current sequence, create a sequence from the
// next value.
id value = valuesSeq.head;
if (value == nil) {
// We've exhausted all the sequences.
stop = YES;
return nil;
}
current = (id)bindBlock(value, &stop);
if (current == nil) {
stop = YES;
return nil;
}
valuesSeq = valuesSeq.tail;
}
NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current);
return nil;
} headBlock:^(id _) {
return current.head;
} tailBlock:^ id (id _) {
if (stop) return nil;
return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail];
}];
sequence.name = self.name;
return sequence;
}
- (instancetype)concat:(RACStream *)stream {
NSCParameterAssert(stream != nil);
return [[[RACArraySequence sequenceWithArray:@[ self, stream ] offset:0]
flatten]
setNameWithFormat:@"[%@] -concat: %@", self.name, stream];
}
- (instancetype)zipWith:(RACSequence *)sequence {
NSCParameterAssert(sequence != nil);
return [[RACSequence
sequenceWithHeadBlock:^ id {
if (self.head == nil || sequence.head == nil) return nil;
return RACTuplePack(self.head, sequence.head);
} tailBlock:^ id {
if (self.tail == nil || [[RACSequence empty] isEqual:self.tail]) return nil;
if (sequence.tail == nil || [[RACSequence empty] isEqual:sequence.tail]) return nil;
return [self.tail zipWith:sequence.tail];
}]
setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence];
}
#pragma mark Extended methods
- (NSArray *)array {
NSMutableArray *array = [NSMutableArray array];
for (id obj in self) {
[array addObject:obj];
}
return [array copy];
}
- (NSEnumerator *)objectEnumerator {
RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init];
enumerator.sequence = self;
return enumerator;
}
- (RACSignal *)signal {
return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name];
}
- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
__block RACSequence *sequence = self;
return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) {
if (sequence.head == nil) {
[subscriber sendCompleted];
return;
}
[subscriber sendNext:sequence.head];
sequence = sequence.tail;
reschedule();
}];
}] setNameWithFormat:@"[%@] -signalWithScheduler:", self.name];
}
- (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce {
NSCParameterAssert(reduce != NULL);
if (self.head == nil) return start;
for (id value in self) {
start = reduce(start, value);
}
return start;
}
- (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *))reduce {
NSCParameterAssert(reduce != NULL);
if (self.head == nil) return start;
RACSequence *rest = [RACSequence sequenceWithHeadBlock:^{
return [self.tail foldRightWithStart:start reduce:reduce];
} tailBlock:nil];
return reduce(self.head, rest);
}
- (BOOL)any:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
return [self objectPassingTest:block] != nil;
}
- (BOOL)all:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) {
return @(accumulator.boolValue && block(value));
}];
return result.boolValue;
}
- (id)objectPassingTest:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
return [self filter:block].head;
}
- (RACSequence *)eagerSequence {
return [RACEagerSequence sequenceWithArray:self.array offset:0];
}
- (RACSequence *)lazySequence {
return self;
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#pragma mark NSCoding
- (Class)classForCoder {
// Most sequences should be archived as RACArraySequences.
return RACArraySequence.class;
}
- (id)initWithCoder:(NSCoder *)coder {
if (![self isKindOfClass:RACArraySequence.class]) return [[RACArraySequence alloc] initWithCoder:coder];
// Decoding is handled in RACArraySequence.
return [super init];
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.array forKey:@"array"];
}
#pragma mark NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len {
if (state->state == ULONG_MAX) {
// Enumeration has completed.
return 0;
}
// We need to traverse the sequence itself on repeated calls to this
// method, so use the 'state' field to track the current head.
RACSequence *(^getSequence)(void) = ^{
return (__bridge RACSequence *)(void *)state->state;
};
void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) {
// Release the old sequence and retain the new one.
CFBridgingRelease((void *)state->state);
state->state = (unsigned long)CFBridgingRetain(sequence);
};
void (^complete)(void) = ^{
// Release any stored sequence.
setSequence(nil);
state->state = ULONG_MAX;
};
if (state->state == 0) {
// Since a sequence doesn't mutate, this just needs to be set to
// something non-NULL.
state->mutationsPtr = state->extra;
setSequence(self);
}
state->itemsPtr = stackbuf;
NSUInteger enumeratedCount = 0;
while (enumeratedCount < len) {
RACSequence *seq = getSequence();
// Because the objects in a sequence may be generated lazily, we want to
// prevent them from being released until the enumerator's used them.
__autoreleasing id obj = seq.head;
if (obj == nil) {
complete();
break;
}
stackbuf[enumeratedCount++] = obj;
if (seq.tail == nil) {
complete();
break;
}
setSequence(seq.tail);
}
return enumeratedCount;
}
#pragma mark NSObject
- (NSUInteger)hash {
return [self.head hash];
}
- (BOOL)isEqual:(RACSequence *)seq {
if (self == seq) return YES;
if (![seq isKindOfClass:RACSequence.class]) return NO;
for (id<NSObject> selfObj in self) {
id<NSObject> seqObj = seq.head;
// Handles the nil case too.
if (![seqObj isEqual:selfObj]) return NO;
seq = seq.tail;
}
// self is now depleted -- the argument should be too.
return (seq.head == nil);
}
@end
@implementation RACSequence (Deprecated)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
- (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine {
return [self foldLeftWithStart:start reduce:combine];
}
- (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine {
return [self foldRightWithStart:start reduce:combine];
}
#pragma clang diagnostic pop
@end