You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
5.6 KiB
197 lines
5.6 KiB
// |
|
// RACDynamicSequence.m |
|
// ReactiveObjC |
|
// |
|
// Created by Justin Spahr-Summers on 2012-10-29. |
|
// Copyright (c) 2012 GitHub. All rights reserved. |
|
// |
|
|
|
#import "RACDynamicSequence.h" |
|
#import <libkern/OSAtomic.h> |
|
|
|
// Determines how RACDynamicSequences will be deallocated before the next one is |
|
// shifted onto the autorelease pool. |
|
// |
|
// This avoids stack overflows when deallocating long chains of dynamic |
|
// sequences. |
|
#define DEALLOC_OVERFLOW_GUARD 100 |
|
|
|
@interface RACDynamicSequence () { |
|
// The value for the "head" property, if it's been evaluated already. |
|
// |
|
// Because it's legal for head to be nil, this ivar is valid any time |
|
// headBlock is nil. |
|
// |
|
// This ivar should only be accessed while synchronized on self. |
|
id _head; |
|
|
|
// The value for the "tail" property, if it's been evaluated already. |
|
// |
|
// Because it's legal for tail to be nil, this ivar is valid any time |
|
// tailBlock is nil. |
|
// |
|
// This ivar should only be accessed while synchronized on self. |
|
RACSequence *_tail; |
|
|
|
// The result of an evaluated `dependencyBlock`. |
|
// |
|
// This ivar is valid any time `hasDependency` is YES and `dependencyBlock` |
|
// is nil. |
|
// |
|
// This ivar should only be accessed while synchronized on self. |
|
id _dependency; |
|
} |
|
|
|
// A block used to evaluate head. This should be set to nil after `_head` has been |
|
// initialized. |
|
// |
|
// This is marked `strong` instead of `copy` because of some bizarre block |
|
// copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. |
|
// |
|
// The signature of this block varies based on the value of `hasDependency`: |
|
// |
|
// - If YES, this block is of type `id (^)(id)`. |
|
// - If NO, this block is of type `id (^)(void)`. |
|
// |
|
// This property should only be accessed while synchronized on self. |
|
@property (nonatomic, strong) id headBlock; |
|
|
|
// A block used to evaluate tail. This should be set to nil after `_tail` has been |
|
// initialized. |
|
// |
|
// This is marked `strong` instead of `copy` because of some bizarre block |
|
// copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. |
|
// |
|
// The signature of this block varies based on the value of `hasDependency`: |
|
// |
|
// - If YES, this block is of type `RACSequence * (^)(id)`. |
|
// - If NO, this block is of type `RACSequence * (^)(void)`. |
|
// |
|
// This property should only be accessed while synchronized on self. |
|
@property (nonatomic, strong) id tailBlock; |
|
|
|
// Whether the receiver was initialized with a `dependencyBlock`. |
|
// |
|
// This property should only be accessed while synchronized on self. |
|
@property (nonatomic, assign) BOOL hasDependency; |
|
|
|
// A dependency which must be evaluated before `headBlock` and `tailBlock`. This |
|
// should be set to nil after `_dependency` and `dependencyBlockExecuted` have |
|
// been set. |
|
// |
|
// This is marked `strong` instead of `copy` because of some bizarre block |
|
// copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. |
|
// |
|
// This property should only be accessed while synchronized on self. |
|
@property (nonatomic, strong) id (^dependencyBlock)(void); |
|
|
|
@end |
|
|
|
@implementation RACDynamicSequence |
|
|
|
#pragma mark Lifecycle |
|
|
|
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence<id> *(^)(void))tailBlock { |
|
NSCParameterAssert(headBlock != nil); |
|
|
|
RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; |
|
seq.headBlock = [headBlock copy]; |
|
seq.tailBlock = [tailBlock copy]; |
|
seq.hasDependency = NO; |
|
return seq; |
|
} |
|
|
|
+ (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock { |
|
NSCParameterAssert(dependencyBlock != nil); |
|
NSCParameterAssert(headBlock != nil); |
|
|
|
RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; |
|
seq.headBlock = [headBlock copy]; |
|
seq.tailBlock = [tailBlock copy]; |
|
seq.dependencyBlock = [dependencyBlock copy]; |
|
seq.hasDependency = YES; |
|
return seq; |
|
} |
|
|
|
- (void)dealloc { |
|
static volatile int32_t directDeallocCount = 0; |
|
|
|
if (OSAtomicIncrement32(&directDeallocCount) >= DEALLOC_OVERFLOW_GUARD) { |
|
OSAtomicAdd32(-DEALLOC_OVERFLOW_GUARD, &directDeallocCount); |
|
|
|
// Put this sequence's tail onto the autorelease pool so we stop |
|
// recursing. |
|
__autoreleasing RACSequence *tail __attribute__((unused)) = _tail; |
|
} |
|
|
|
_tail = nil; |
|
} |
|
|
|
#pragma mark RACSequence |
|
|
|
- (id)head { |
|
@synchronized (self) { |
|
id untypedHeadBlock = self.headBlock; |
|
if (untypedHeadBlock == nil) return _head; |
|
|
|
if (self.hasDependency) { |
|
if (self.dependencyBlock != nil) { |
|
_dependency = self.dependencyBlock(); |
|
self.dependencyBlock = nil; |
|
} |
|
|
|
id (^headBlock)(id) = untypedHeadBlock; |
|
_head = headBlock(_dependency); |
|
} else { |
|
id (^headBlock)(void) = untypedHeadBlock; |
|
_head = headBlock(); |
|
} |
|
|
|
self.headBlock = nil; |
|
return _head; |
|
} |
|
} |
|
|
|
- (RACSequence *)tail { |
|
@synchronized (self) { |
|
id untypedTailBlock = self.tailBlock; |
|
if (untypedTailBlock == nil) return _tail; |
|
|
|
if (self.hasDependency) { |
|
if (self.dependencyBlock != nil) { |
|
_dependency = self.dependencyBlock(); |
|
self.dependencyBlock = nil; |
|
} |
|
|
|
RACSequence * (^tailBlock)(id) = untypedTailBlock; |
|
_tail = tailBlock(_dependency); |
|
} else { |
|
RACSequence * (^tailBlock)(void) = untypedTailBlock; |
|
_tail = tailBlock(); |
|
} |
|
|
|
if (_tail.name == nil) _tail.name = self.name; |
|
|
|
self.tailBlock = nil; |
|
return _tail; |
|
} |
|
} |
|
|
|
#pragma mark NSObject |
|
|
|
- (NSString *)description { |
|
id head = @"(unresolved)"; |
|
id tail = @"(unresolved)"; |
|
|
|
@synchronized (self) { |
|
if (self.headBlock == nil) head = _head; |
|
if (self.tailBlock == nil) { |
|
tail = _tail; |
|
if (tail == self) tail = @"(self)"; |
|
} |
|
} |
|
|
|
return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@, tail = %@ }", self.class, self, self.name, head, tail]; |
|
} |
|
|
|
@end
|
|
|