initial commit

This commit is contained in:
Caleb
2026-03-01 15:59:27 +08:00
commit a9e97d56cb
1426 changed files with 172367 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
//
// MKAnnotationView+RACSignalSupport.h
// ReactiveObjC
//
// Created by Zak Remer on 3/31/15.
// Copyright (c) 2015 GitHub. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@class RACSignal<__covariant ValueType>;
@class RACUnit;
NS_ASSUME_NONNULL_BEGIN
@interface MKAnnotationView (RACSignalSupport)
/// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon
/// the receiver.
///
/// Examples
///
/// [[[self.cancelButton
/// rac_signalForControlEvents:UIControlEventTouchUpInside]
/// takeUntil:self.rac_prepareForReuseSignal]
/// subscribeNext:^(UIButton *x) {
/// // do other things
/// }];
@property (nonatomic, strong, readonly) RACSignal<RACUnit *> *rac_prepareForReuseSignal;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,31 @@
//
// MKAnnotationView+RACSignalSupport.m
// ReactiveObjC
//
// Created by Zak Remer on 3/31/15.
// Copyright (c) 2015 GitHub. All rights reserved.
//
#import "MKAnnotationView+RACSignalSupport.h"
#import "NSObject+RACDescription.h"
#import "NSObject+RACSelectorSignal.h"
#import "RACSignal+Operations.h"
#import "RACUnit.h"
#import <objc/runtime.h>
@implementation MKAnnotationView (RACSignalSupport)
- (RACSignal *)rac_prepareForReuseSignal {
RACSignal *signal = objc_getAssociatedObject(self, _cmd);
if (signal != nil) return signal;
signal = [[[self
rac_signalForSelector:@selector(prepareForReuse)]
mapReplace:RACUnit.defaultUnit]
setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)];
objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return signal;
}
@end

View File

@@ -0,0 +1,24 @@
//
// NSArray+RACSequenceAdditions.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSArray<__covariant ObjectType> (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<ObjectType> *rac_sequence;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,18 @@
//
// NSArray+RACSequenceAdditions.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSArray+RACSequenceAdditions.h"
#import "RACArraySequence.h"
@implementation NSArray (RACSequenceAdditions)
- (RACSequence *)rac_sequence {
return [RACArraySequence sequenceWithArray:self offset:0];
}
@end

View File

@@ -0,0 +1,26 @@
//
// NSData+RACSupport.h
// ReactiveObjC
//
// Created by Josh Abernathy on 5/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACScheduler;
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSData (RACSupport)
// Read the data at the URL using -[NSData initWithContentsOfURL:options:error:].
// Sends the data or the error.
//
// scheduler - cannot be nil.
+ (RACSignal<NSData *> *)rac_readContentsOfURL:(nullable NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,35 @@
//
// NSData+RACSupport.m
// ReactiveObjC
//
// Created by Josh Abernathy on 5/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "NSData+RACSupport.h"
#import "RACReplaySubject.h"
#import "RACScheduler.h"
@implementation NSData (RACSupport)
+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler {
NSCParameterAssert(scheduler != nil);
RACReplaySubject *subject = [RACReplaySubject subject];
[subject setNameWithFormat:@"+rac_readContentsOfURL: %@ options: %lu scheduler: %@", URL, (unsigned long)options, scheduler];
[scheduler schedule:^{
NSError *error = nil;
NSData *data = [[NSData alloc] initWithContentsOfURL:URL options:options error:&error];
if (data == nil) {
[subject sendError:error];
} else {
[subject sendNext:data];
[subject sendCompleted];
}
}];
return subject;
}
@end

View File

@@ -0,0 +1,35 @@
//
// NSDictionary+RACSequenceAdditions.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence<__covariant ValueType>;
@class RACTwoTuple<__covariant First, __covariant Second>;
NS_ASSUME_NONNULL_BEGIN
@interface NSDictionary<__covariant KeyType, __covariant ObjectType> (RACSequenceAdditions)
/// Creates and returns a sequence of key/value tuples.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<RACTwoTuple<KeyType, ObjectType> *> *rac_sequence;
/// Creates and returns a sequence corresponding to the keys in the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<KeyType> *rac_keySequence;
/// Creates and returns a sequence corresponding to the values in the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<ObjectType> *rac_valueSequence;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,34 @@
//
// NSDictionary+RACSequenceAdditions.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSDictionary+RACSequenceAdditions.h"
#import "NSArray+RACSequenceAdditions.h"
#import "RACSequence.h"
#import "RACTuple.h"
@implementation NSDictionary (RACSequenceAdditions)
- (RACSequence *)rac_sequence {
NSDictionary *immutableDict = [self copy];
// TODO: First class support for dictionary sequences.
return [immutableDict.allKeys.rac_sequence map:^(id key) {
id value = immutableDict[key];
return RACTuplePack(key, value);
}];
}
- (RACSequence *)rac_keySequence {
return self.allKeys.rac_sequence;
}
- (RACSequence *)rac_valueSequence {
return self.allValues.rac_sequence;
}
@end

View File

@@ -0,0 +1,24 @@
//
// NSEnumerator+RACSequenceAdditions.h
// ReactiveObjC
//
// Created by Uri Baghin on 07/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSEnumerator<ObjectType> (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// The receiver is exhausted lazily as the sequence is enumerated.
@property (nonatomic, copy, readonly) RACSequence<ObjectType> *rac_sequence;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,22 @@
//
// NSEnumerator+RACSequenceAdditions.m
// ReactiveObjC
//
// Created by Uri Baghin on 07/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSEnumerator+RACSequenceAdditions.h"
#import "RACSequence.h"
@implementation NSEnumerator (RACSequenceAdditions)
- (RACSequence *)rac_sequence {
return [RACSequence sequenceWithHeadBlock:^{
return [self nextObject];
} tailBlock:^{
return self.rac_sequence;
}];
}
@end

View File

@@ -0,0 +1,23 @@
//
// NSFileHandle+RACSupport.h
// ReactiveObjC
//
// Created by Josh Abernathy on 5/10/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSFileHandle (RACSupport)
// Read any available data in the background and send it. Completes when data
// length is <= 0.
- (RACSignal<NSData *> *)rac_readInBackground;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,40 @@
//
// NSFileHandle+RACSupport.m
// ReactiveObjC
//
// Created by Josh Abernathy on 5/10/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSFileHandle+RACSupport.h"
#import "NSNotificationCenter+RACSupport.h"
#import "NSObject+RACDescription.h"
#import "RACReplaySubject.h"
#import "RACDisposable.h"
@implementation NSFileHandle (RACSupport)
- (RACSignal *)rac_readInBackground {
RACReplaySubject *subject = [RACReplaySubject subject];
[subject setNameWithFormat:@"%@ -rac_readInBackground", RACDescription(self)];
RACSignal *dataNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:NSFileHandleReadCompletionNotification object:self] map:^(NSNotification *note) {
return note.userInfo[NSFileHandleNotificationDataItem];
}];
__block RACDisposable *subscription = [dataNotification subscribeNext:^(NSData *data) {
if (data.length > 0) {
[subject sendNext:data];
[self readInBackgroundAndNotify];
} else {
[subject sendCompleted];
[subscription dispose];
}
}];
[self readInBackgroundAndNotify];
return subject;
}
@end

View File

@@ -0,0 +1,25 @@
//
// NSIndexSet+RACSequenceAdditions.h
// ReactiveObjC
//
// Created by Sergey Gavrilyuk on 12/17/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSIndexSet (RACSequenceAdditions)
/// Creates and returns a sequence of indexes (as `NSNumber`s) corresponding to
/// the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<NSNumber *> *rac_sequence;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,18 @@
//
// NSIndexSet+RACSequenceAdditions.m
// ReactiveObjC
//
// Created by Sergey Gavrilyuk on 12/17/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSIndexSet+RACSequenceAdditions.h"
#import "RACIndexSetSequence.h"
@implementation NSIndexSet (RACSequenceAdditions)
- (RACSequence *)rac_sequence {
return [RACIndexSetSequence sequenceWithIndexSet:self];
}
@end

View File

@@ -0,0 +1,56 @@
//
// NSInvocation+RACTypeParsing.h
// ReactiveObjC
//
// Created by Josh Abernathy on 11/17/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACTuple;
// A private category of methods to handle wrapping and unwrapping of values.
@interface NSInvocation (RACTypeParsing)
// Sets the argument for the invocation at the given index by unboxing the given
// object based on the type signature of the argument.
//
// This does not support C arrays or unions.
//
// Note that calling this on a char * or const char * argument can cause all
// arguments to be retained.
//
// object - The object to unbox and set as the argument.
// index - The index of the argument to set.
- (void)rac_setArgument:(id)object atIndex:(NSUInteger)index;
// Gets the argument for the invocation at the given index based on the
// invocation's method signature. The value is then wrapped in the appropriate
// object type.
//
// This does not support C arrays or unions.
//
// index - The index of the argument to get.
//
// Returns the argument of the invocation, wrapped in an object.
- (id)rac_argumentAtIndex:(NSUInteger)index;
// Arguments tuple for the invocation.
//
// The arguments tuple excludes implicit variables `self` and `_cmd`.
//
// See -rac_argumentAtIndex: and -rac_setArgumentAtIndex: for further
// description of the underlying behavior.
@property (nonatomic, copy) RACTuple *rac_argumentsTuple;
// Gets the return value from the invocation based on the invocation's method
// signature. The value is then wrapped in the appropriate object type.
//
// This does not support C arrays or unions.
//
// Returns the return value of the invocation, wrapped in an object. Voids are
// returned as `RACUnit.defaultUnit`.
- (id)rac_returnValue;
@end

View File

@@ -0,0 +1,232 @@
//
// NSInvocation+RACTypeParsing.m
// ReactiveObjC
//
// Created by Josh Abernathy on 11/17/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "NSInvocation+RACTypeParsing.h"
#import "RACTuple.h"
#import "RACUnit.h"
#import <CoreGraphics/CoreGraphics.h>
@implementation NSInvocation (RACTypeParsing)
- (void)rac_setArgument:(id)object atIndex:(NSUInteger)index {
#define PULL_AND_SET(type, selector) \
do { \
type val = [object selector]; \
[self setArgument:&val atIndex:(NSInteger)index]; \
} while (0)
const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
// Skip const type qualifier.
if (argType[0] == 'r') {
argType++;
}
if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
[self setArgument:&object atIndex:(NSInteger)index];
} else if (strcmp(argType, @encode(char)) == 0) {
PULL_AND_SET(char, charValue);
} else if (strcmp(argType, @encode(int)) == 0) {
PULL_AND_SET(int, intValue);
} else if (strcmp(argType, @encode(short)) == 0) {
PULL_AND_SET(short, shortValue);
} else if (strcmp(argType, @encode(long)) == 0) {
PULL_AND_SET(long, longValue);
} else if (strcmp(argType, @encode(long long)) == 0) {
PULL_AND_SET(long long, longLongValue);
} else if (strcmp(argType, @encode(unsigned char)) == 0) {
PULL_AND_SET(unsigned char, unsignedCharValue);
} else if (strcmp(argType, @encode(unsigned int)) == 0) {
PULL_AND_SET(unsigned int, unsignedIntValue);
} else if (strcmp(argType, @encode(unsigned short)) == 0) {
PULL_AND_SET(unsigned short, unsignedShortValue);
} else if (strcmp(argType, @encode(unsigned long)) == 0) {
PULL_AND_SET(unsigned long, unsignedLongValue);
} else if (strcmp(argType, @encode(unsigned long long)) == 0) {
PULL_AND_SET(unsigned long long, unsignedLongLongValue);
} else if (strcmp(argType, @encode(float)) == 0) {
PULL_AND_SET(float, floatValue);
} else if (strcmp(argType, @encode(double)) == 0) {
PULL_AND_SET(double, doubleValue);
} else if (strcmp(argType, @encode(BOOL)) == 0) {
PULL_AND_SET(BOOL, boolValue);
} else if (strcmp(argType, @encode(char *)) == 0) {
const char *cString = [object UTF8String];
[self setArgument:&cString atIndex:(NSInteger)index];
[self retainArguments];
} else if (strcmp(argType, @encode(void (^)(void))) == 0) {
[self setArgument:&object atIndex:(NSInteger)index];
} else {
NSCParameterAssert([object isKindOfClass:NSValue.class]);
NSUInteger valueSize = 0;
NSGetSizeAndAlignment([object objCType], &valueSize, NULL);
#if DEBUG
NSUInteger argSize = 0;
NSGetSizeAndAlignment(argType, &argSize, NULL);
NSCAssert(valueSize == argSize, @"Value size does not match argument size in -rac_setArgument: %@ atIndex: %lu", object, (unsigned long)index);
#endif
unsigned char valueBytes[valueSize];
[object getValue:valueBytes];
[self setArgument:valueBytes atIndex:(NSInteger)index];
}
#undef PULL_AND_SET
}
- (id)rac_argumentAtIndex:(NSUInteger)index {
#define WRAP_AND_RETURN(type) \
do { \
type val = 0; \
[self getArgument:&val atIndex:(NSInteger)index]; \
return @(val); \
} while (0)
const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
// Skip const type qualifier.
if (argType[0] == 'r') {
argType++;
}
if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
__autoreleasing id returnObj;
[self getArgument:&returnObj atIndex:(NSInteger)index];
return returnObj;
} else if (strcmp(argType, @encode(char)) == 0) {
WRAP_AND_RETURN(char);
} else if (strcmp(argType, @encode(int)) == 0) {
WRAP_AND_RETURN(int);
} else if (strcmp(argType, @encode(short)) == 0) {
WRAP_AND_RETURN(short);
} else if (strcmp(argType, @encode(long)) == 0) {
WRAP_AND_RETURN(long);
} else if (strcmp(argType, @encode(long long)) == 0) {
WRAP_AND_RETURN(long long);
} else if (strcmp(argType, @encode(unsigned char)) == 0) {
WRAP_AND_RETURN(unsigned char);
} else if (strcmp(argType, @encode(unsigned int)) == 0) {
WRAP_AND_RETURN(unsigned int);
} else if (strcmp(argType, @encode(unsigned short)) == 0) {
WRAP_AND_RETURN(unsigned short);
} else if (strcmp(argType, @encode(unsigned long)) == 0) {
WRAP_AND_RETURN(unsigned long);
} else if (strcmp(argType, @encode(unsigned long long)) == 0) {
WRAP_AND_RETURN(unsigned long long);
} else if (strcmp(argType, @encode(float)) == 0) {
WRAP_AND_RETURN(float);
} else if (strcmp(argType, @encode(double)) == 0) {
WRAP_AND_RETURN(double);
} else if (strcmp(argType, @encode(BOOL)) == 0) {
WRAP_AND_RETURN(BOOL);
} else if (strcmp(argType, @encode(char *)) == 0) {
WRAP_AND_RETURN(const char *);
} else if (strcmp(argType, @encode(void (^)(void))) == 0) {
__unsafe_unretained id block = nil;
[self getArgument:&block atIndex:(NSInteger)index];
return [block copy];
} else {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(argType, &valueSize, NULL);
unsigned char valueBytes[valueSize];
[self getArgument:valueBytes atIndex:(NSInteger)index];
return [NSValue valueWithBytes:valueBytes objCType:argType];
}
return nil;
#undef WRAP_AND_RETURN
}
- (RACTuple *)rac_argumentsTuple {
NSUInteger numberOfArguments = self.methodSignature.numberOfArguments;
NSMutableArray *argumentsArray = [NSMutableArray arrayWithCapacity:numberOfArguments - 2];
for (NSUInteger index = 2; index < numberOfArguments; index++) {
[argumentsArray addObject:[self rac_argumentAtIndex:index] ?: RACTupleNil.tupleNil];
}
return [RACTuple tupleWithObjectsFromArray:argumentsArray];
}
- (void)setRac_argumentsTuple:(RACTuple *)arguments {
NSCAssert(arguments.count == self.methodSignature.numberOfArguments - 2, @"Number of supplied arguments (%lu), does not match the number expected by the signature (%lu)", (unsigned long)arguments.count, (unsigned long)self.methodSignature.numberOfArguments - 2);
NSUInteger index = 2;
for (id arg in arguments) {
[self rac_setArgument:(arg == RACTupleNil.tupleNil ? nil : arg) atIndex:index];
index++;
}
}
- (id)rac_returnValue {
#define WRAP_AND_RETURN(type) \
do { \
type val = 0; \
[self getReturnValue:&val]; \
return @(val); \
} while (0)
const char *returnType = self.methodSignature.methodReturnType;
// Skip const type qualifier.
if (returnType[0] == 'r') {
returnType++;
}
if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0 || strcmp(returnType, @encode(void (^)(void))) == 0) {
__autoreleasing id returnObj;
[self getReturnValue:&returnObj];
return returnObj;
} else if (strcmp(returnType, @encode(char)) == 0) {
WRAP_AND_RETURN(char);
} else if (strcmp(returnType, @encode(int)) == 0) {
WRAP_AND_RETURN(int);
} else if (strcmp(returnType, @encode(short)) == 0) {
WRAP_AND_RETURN(short);
} else if (strcmp(returnType, @encode(long)) == 0) {
WRAP_AND_RETURN(long);
} else if (strcmp(returnType, @encode(long long)) == 0) {
WRAP_AND_RETURN(long long);
} else if (strcmp(returnType, @encode(unsigned char)) == 0) {
WRAP_AND_RETURN(unsigned char);
} else if (strcmp(returnType, @encode(unsigned int)) == 0) {
WRAP_AND_RETURN(unsigned int);
} else if (strcmp(returnType, @encode(unsigned short)) == 0) {
WRAP_AND_RETURN(unsigned short);
} else if (strcmp(returnType, @encode(unsigned long)) == 0) {
WRAP_AND_RETURN(unsigned long);
} else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
WRAP_AND_RETURN(unsigned long long);
} else if (strcmp(returnType, @encode(float)) == 0) {
WRAP_AND_RETURN(float);
} else if (strcmp(returnType, @encode(double)) == 0) {
WRAP_AND_RETURN(double);
} else if (strcmp(returnType, @encode(BOOL)) == 0) {
WRAP_AND_RETURN(BOOL);
} else if (strcmp(returnType, @encode(char *)) == 0) {
WRAP_AND_RETURN(const char *);
} else if (strcmp(returnType, @encode(void)) == 0) {
return RACUnit.defaultUnit;
} else {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(returnType, &valueSize, NULL);
unsigned char valueBytes[valueSize];
[self getReturnValue:valueBytes];
return [NSValue valueWithBytes:valueBytes objCType:returnType];
}
return nil;
#undef WRAP_AND_RETURN
}
@end

View File

@@ -0,0 +1,22 @@
//
// NSNotificationCenter+RACSupport.h
// ReactiveObjC
//
// Created by Josh Abernathy on 5/10/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSNotificationCenter (RACSupport)
// Sends the NSNotification every time the notification is posted.
- (RACSignal<NSNotification *> *)rac_addObserverForName:(nullable NSString *)notificationName object:(nullable id)object;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,31 @@
//
// NSNotificationCenter+RACSupport.m
// ReactiveObjC
//
// Created by Josh Abernathy on 5/10/12.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSNotificationCenter+RACSupport.h"
#import <ReactiveObjC/RACEXTScope.h>
#import "RACSignal.h"
#import "RACSubscriber.h"
#import "RACDisposable.h"
@implementation NSNotificationCenter (RACSupport)
- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object {
@unsafeify(object);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
@strongify(object);
id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {
[subscriber sendNext:note];
}];
return [RACDisposable disposableWithBlock:^{
[self removeObserver:observer];
}];
}] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object];
}
@end

View File

@@ -0,0 +1,30 @@
//
// NSObject+RACDeallocating.h
// ReactiveObjC
//
// Created by Kazuo Koga on 2013/03/15.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACCompoundDisposable;
@class RACDisposable;
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (RACDeallocating)
/// The compound disposable which will be disposed of when the receiver is
/// deallocated.
@property (atomic, readonly, strong) RACCompoundDisposable *rac_deallocDisposable;
/// Returns a signal that will complete immediately before the receiver is fully
/// deallocated. If already deallocated when the signal is subscribed to,
/// a `completed` event will be sent immediately.
- (RACSignal *)rac_willDeallocSignal;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,103 @@
//
// NSObject+RACDeallocating.m
// ReactiveObjC
//
// Created by Kazuo Koga on 2013/03/15.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSObject+RACDeallocating.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACReplaySubject.h"
#import <objc/message.h>
#import <objc/runtime.h>
static const void *RACObjectCompoundDisposable = &RACObjectCompoundDisposable;
static NSMutableSet *swizzledClasses() {
static dispatch_once_t onceToken;
static NSMutableSet *swizzledClasses = nil;
dispatch_once(&onceToken, ^{
swizzledClasses = [[NSMutableSet alloc] init];
});
return swizzledClasses;
}
static void swizzleDeallocIfNeeded(Class classToSwizzle) {
@synchronized (swizzledClasses()) {
NSString *className = NSStringFromClass(classToSwizzle);
if ([swizzledClasses() containsObject:className]) return;
SEL deallocSelector = sel_registerName("dealloc");
__block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL;
id newDealloc = ^(__unsafe_unretained id self) {
RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable);
[compoundDisposable dispose];
if (originalDealloc == NULL) {
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(classToSwizzle)
};
void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper;
msgSend(&superInfo, deallocSelector);
} else {
originalDealloc(self, deallocSelector);
}
};
IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);
if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) {
// The class already contains a method implementation.
Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector);
// We need to store original implementation before setting new implementation
// in case method is called at the time of setting.
originalDealloc = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
// We need to store original implementation again, in case it just changed.
originalDealloc = (__typeof__(originalDealloc))method_setImplementation(deallocMethod, newDeallocIMP);
}
[swizzledClasses() addObject:className];
}
}
@implementation NSObject (RACDeallocating)
- (RACSignal *)rac_willDeallocSignal {
RACSignal *signal = objc_getAssociatedObject(self, _cmd);
if (signal != nil) return signal;
RACReplaySubject *subject = [RACReplaySubject subject];
[self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
[subject sendCompleted];
}]];
objc_setAssociatedObject(self, _cmd, subject, OBJC_ASSOCIATION_RETAIN);
return subject;
}
- (RACCompoundDisposable *)rac_deallocDisposable {
@synchronized (self) {
RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable);
if (compoundDisposable != nil) return compoundDisposable;
swizzleDeallocIfNeeded(self.class);
compoundDisposable = [RACCompoundDisposable compoundDisposable];
objc_setAssociatedObject(self, RACObjectCompoundDisposable, compoundDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return compoundDisposable;
}
}
@end

View File

@@ -0,0 +1,16 @@
//
// NSObject+RACDescription.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-05-13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
// A simplified description of the object, which does not invoke -description
// (and thus should be much faster in many cases).
//
// This is for debugging purposes only, and will return a constant string
// unless the RAC_DEBUG_SIGNAL_NAMES environment variable is set.
NSString *RACDescription(id object);

View File

@@ -0,0 +1,50 @@
//
// NSObject+RACDescription.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-05-13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSObject+RACDescription.h"
#import "RACTuple.h"
@implementation NSValue (RACDescription)
- (NSString *)rac_description {
return self.description;
}
@end
@implementation NSString (RACDescription)
- (NSString *)rac_description {
return self.description;
}
@end
@implementation RACTuple (RACDescription)
- (NSString *)rac_description {
if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) {
return self.allObjects.description;
} else {
return @"(description skipped)";
}
}
@end
NSString *RACDescription(id object) {
if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) {
if ([object respondsToSelector:@selector(rac_description)]) {
return [object rac_description];
} else {
return [[NSString alloc] initWithFormat:@"<%@: %p>", [object class], object];
}
} else {
return @"(description skipped)";
}
}

View File

@@ -0,0 +1,46 @@
//
// NSObject+RACKVOWrapper.h
// ReactiveObjC
//
// Created by Josh Abernathy on 10/11/11.
// Copyright (c) 2011 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACDisposable;
@class RACKVOTrampoline;
// A private category providing a block based interface to KVO.
@interface NSObject (RACKVOWrapper)
// Adds the given block as the callbacks for when the key path changes.
//
// Unlike direct KVO observation, this handles deallocation of `weak` properties
// by generating an appropriate notification. This will only occur if there is
// an `@property` declaration visible in the observed class, with the `weak`
// memory management attribute.
//
// The observation does not need to be explicitly removed. It will be removed
// when the observer or the receiver deallocate.
//
// keyPath - The key path to observe. Must not be nil.
// options - The KVO observation options.
// observer - The object that requested the observation. May be nil.
// block - The block called when the value at the key path changes. It is
// passed the current value of the key path and the extended KVO
// change dictionary including RAC-specific keys and values. Must not
// be nil.
//
// Returns a disposable that can be used to stop the observation.
- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer block:(void (^)(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent))block;
@end
typedef void (^RACKVOBlock)(id target, id observer, NSDictionary *change);
@interface NSObject (RACUnavailableKVOWrapper)
- (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block __attribute((unavailable("Use rac_observeKeyPath:options:observer:block: instead.")));
@end

View File

@@ -0,0 +1,200 @@
//
// NSObject+RACKVOWrapper.m
// ReactiveObjC
//
// Created by Josh Abernathy on 10/11/11.
// Copyright (c) 2011 GitHub. All rights reserved.
//
#import "NSObject+RACKVOWrapper.h"
#import <ReactiveObjC/RACEXTRuntimeExtensions.h>
#import <ReactiveObjC/RACEXTScope.h>
#import "NSObject+RACDeallocating.h"
#import "NSString+RACKeyPathUtilities.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACKVOTrampoline.h"
#import "RACSerialDisposable.h"
@implementation NSObject (RACKVOWrapper)
- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block {
NSCParameterAssert(block != nil);
NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0);
keyPath = [keyPath copy];
NSObject *strongObserver = weakObserver;
NSArray *keyPathComponents = keyPath.rac_keyPathComponents;
BOOL keyPathHasOneComponent = (keyPathComponents.count == 1);
NSString *keyPathHead = keyPathComponents[0];
NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent;
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
// The disposable that groups all disposal necessary to clean up the callbacks
// added to the value of the first key path component.
RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]];
RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{
return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable;
};
[disposable addDisposable:firstComponentSerialDisposable];
BOOL shouldAddDeallocObserver = NO;
objc_property_t property = class_getProperty(object_getClass(self), keyPathHead.UTF8String);
if (property != NULL) {
rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property);
if (attributes != NULL) {
@onExit {
free(attributes);
};
BOOL isObject = attributes->objectClass != nil || strstr(attributes->type, @encode(id)) == attributes->type;
BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol");
BOOL isBlock = strcmp(attributes->type, @encode(void(^)(void))) == 0;
BOOL isWeak = attributes->weak;
// If this property isn't actually an object (or is a Class object),
// no point in observing the deallocation of the wrapper returned by
// KVC.
//
// If this property is an object, but not declared `weak`, we
// don't need to watch for it spontaneously being set to nil.
//
// Attempting to observe non-weak properties will result in
// broken behavior for dynamic getters, so don't even try.
shouldAddDeallocObserver = isObject && isWeak && !isBlock && !isProtocol;
}
}
// Adds the callback block to the value's deallocation. Also adds the logic to
// clean up the callback to the firstComponentDisposable.
void (^addDeallocObserverToPropertyValue)(NSObject *) = ^(NSObject *value) {
if (!shouldAddDeallocObserver) return;
// If a key path value is the observer, commonly when a key path begins
// with "self", we prevent deallocation triggered callbacks for any such key
// path components. Thus, the observer's deallocation is not considered a
// change to the key path.
if (value == weakObserver) return;
NSDictionary *change = @{
NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting),
NSKeyValueChangeNewKey: NSNull.null,
};
RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable;
RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{
block(nil, change, YES, keyPathHasOneComponent);
}];
[valueDisposable addDisposable:deallocDisposable];
[firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{
[valueDisposable removeDisposable:deallocDisposable];
}]];
};
// Adds the callback block to the remaining path components on the value. Also
// adds the logic to clean up the callbacks to the firstComponentDisposable.
void (^addObserverToValue)(NSObject *) = ^(NSObject *value) {
RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block];
[firstComponentDisposable() addDisposable:observerDisposable];
};
// Observe only the first key path component, when the value changes clean up
// the callbacks on the old value, add callbacks to the new value and call the
// callback block as needed.
//
// Note this does not use NSKeyValueObservingOptionInitial so this only
// handles changes to the value, callbacks to the initial value must be added
// separately.
NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial;
RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) {
// If this is a prior notification, clean up all the callbacks added to the
// previous value and call the callback block. Everything else is deferred
// until after we get the notification after the change.
if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
[firstComponentDisposable() dispose];
if ((options & NSKeyValueObservingOptionPrior) != 0) {
block([trampolineTarget valueForKeyPath:keyPath], change, NO, keyPathHasOneComponent);
}
return;
}
// From here the notification is not prior.
NSObject *value = [trampolineTarget valueForKey:keyPathHead];
// If the value has changed but is nil, there is no need to add callbacks to
// it, just call the callback block.
if (value == nil) {
block(nil, change, NO, keyPathHasOneComponent);
return;
}
// From here the notification is not prior and the value is not nil.
// Create a new firstComponentDisposable while getting rid of the old one at
// the same time, in case this is being called concurrently.
RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]];
[oldFirstComponentDisposable dispose];
addDeallocObserverToPropertyValue(value);
// If there are no further key path components, there is no need to add the
// other callbacks, just call the callback block with the value itself.
if (keyPathHasOneComponent) {
block(value, change, NO, keyPathHasOneComponent);
return;
}
// The value has changed, is not nil, and there are more key path components
// to consider. Add the callbacks to the value for the remaining key path
// components and call the callback block with the current value of the full
// key path.
addObserverToValue(value);
block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent);
}];
// Stop the KVO observation when this one is disposed of.
[disposable addDisposable:trampoline];
// Add the callbacks to the initial value if needed.
NSObject *value = [self valueForKey:keyPathHead];
if (value != nil) {
addDeallocObserverToPropertyValue(value);
if (!keyPathHasOneComponent) {
addObserverToValue(value);
}
}
// Call the block with the initial value if needed.
if ((options & NSKeyValueObservingOptionInitial) != 0) {
id initialValue = [self valueForKeyPath:keyPath];
NSDictionary *initialChange = @{
NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting),
NSKeyValueChangeNewKey: initialValue ?: NSNull.null,
};
block(initialValue, initialChange, NO, keyPathHasOneComponent);
}
RACCompoundDisposable *observerDisposable = strongObserver.rac_deallocDisposable;
RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable;
// Dispose of this observation if the receiver or the observer deallocate.
[observerDisposable addDisposable:disposable];
[selfDisposable addDisposable:disposable];
return [RACDisposable disposableWithBlock:^{
[disposable dispose];
[observerDisposable removeDisposable:disposable];
[selfDisposable removeDisposable:disposable];
}];
}
@end

View File

@@ -0,0 +1,51 @@
//
// NSObject+RACLifting.h
// ReactiveObjC
//
// Created by Josh Abernathy on 10/13/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal<__covariant ValueType>;
@class RACTuple;
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (RACLifting)
/// Lifts the selector on the receiver into the reactive world. The selector will
/// be invoked whenever any signal argument sends a value, but only after each
/// signal has sent an initial value.
///
/// It will replay the most recently sent value to new subscribers.
///
/// This does not support C arrays or unions.
///
/// selector - The selector on self to invoke.
/// firstSignal - The signal corresponding to the first method argument. This
/// must not be nil.
/// ... - A list of RACSignals corresponding to the remaining arguments.
/// There must be a non-nil signal for each method argument.
///
/// Examples
///
/// [button rac_liftSelector:@selector(setTitleColor:forState:) withSignals:textColorSignal, [RACSignal return:@(UIControlStateNormal)], nil];
///
/// Returns a signal which sends the return value from each invocation of the
/// selector. If the selector returns void, it instead sends RACUnit.defaultUnit.
/// It completes only after all the signal arguments complete.
- (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... NS_REQUIRES_NIL_TERMINATION;
/// Like -rac_liftSelector:withSignals:, but accepts an array instead of
/// a variadic list of arguments.
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray<RACSignal *> *)signals;
/// Like -rac_liftSelector:withSignals:, but accepts a signal sending tuples of
/// arguments instead of a variadic list of arguments.
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal<RACTuple *> *)arguments;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,78 @@
//
// NSObject+RACLifting.m
// ReactiveObjC
//
// Created by Josh Abernathy on 10/13/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "NSObject+RACLifting.h"
#import <ReactiveObjC/RACEXTScope.h>
#import "NSInvocation+RACTypeParsing.h"
#import "NSObject+RACDeallocating.h"
#import "NSObject+RACDescription.h"
#import "RACSignal+Operations.h"
#import "RACTuple.h"
@implementation NSObject (RACLifting)
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments {
NSCParameterAssert(selector != NULL);
NSCParameterAssert(arguments != nil);
@unsafeify(self);
NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector];
NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector));
return [[[[arguments
takeUntil:self.rac_willDeallocSignal]
map:^(RACTuple *arguments) {
@strongify(self);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = selector;
invocation.rac_argumentsTuple = arguments;
[invocation invokeWithTarget:self];
return invocation.rac_returnValue;
}]
replayLast]
setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsOfArguments: %@", RACDescription(self), sel_getName(selector), arguments];
}
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals {
NSCParameterAssert(signals != nil);
NSCParameterAssert(signals.count > 0);
NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector];
NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector));
NSUInteger numberOfArguments __attribute__((unused)) = methodSignature.numberOfArguments - 2;
NSCAssert(numberOfArguments == signals.count, @"Wrong number of signals for %@ (expected %lu, got %lu)", NSStringFromSelector(selector), (unsigned long)numberOfArguments, (unsigned long)signals.count);
return [[self
rac_liftSelector:selector withSignalOfArguments:[RACSignal combineLatest:signals]]
setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsFromArray: %@", RACDescription(self), sel_getName(selector), signals];
}
- (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... {
NSCParameterAssert(firstSignal != nil);
NSMutableArray *signals = [NSMutableArray array];
va_list args;
va_start(args, firstSignal);
for (id currentSignal = firstSignal; currentSignal != nil; currentSignal = va_arg(args, id)) {
NSCAssert([currentSignal isKindOfClass:RACSignal.class], @"Argument %@ is not a RACSignal", currentSignal);
[signals addObject:currentSignal];
}
va_end(args);
return [[self
rac_liftSelector:selector withSignalsFromArray:signals]
setNameWithFormat:@"%@ -rac_liftSelector: %s withSignals: %@", RACDescription(self), sel_getName(selector), signals];
}
@end

View File

@@ -0,0 +1,120 @@
//
// NSObject+RACPropertySubscribing.h
// ReactiveObjC
//
// Created by Josh Abernathy on 3/2/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <ReactiveObjC/RACEXTKeyPathCoding.h>
#import "RACmetamacros.h"
/// Creates a signal which observes `KEYPATH` on `TARGET` for changes.
///
/// In either case, the observation continues until `TARGET` _or self_ is
/// deallocated. If any intermediate object is deallocated instead, it will be
/// assumed to have been set to nil.
///
/// Make sure to `@strongify(self)` when using this macro within a block! The
/// macro will _always_ reference `self`, which can silently introduce a retain
/// cycle within a block. As a result, you should make sure that `self` is a weak
/// reference (e.g., created by `@weakify` and `@strongify`) before the
/// expression that uses `RACObserve`.
///
/// Examples
///
/// // Observes self, and doesn't stop until self is deallocated.
/// RACSignal *selfSignal = RACObserve(self, arrayController.items);
///
/// // Observes the array controller, and stops when self _or_ the array
/// // controller is deallocated.
/// RACSignal *arrayControllerSignal = RACObserve(self.arrayController, items);
///
/// // Observes obj.arrayController, and stops when self _or_ the array
/// // controller is deallocated.
/// RACSignal *signal2 = RACObserve(obj.arrayController, items);
///
/// @weakify(self);
/// RACSignal *signal3 = [anotherSignal flattenMap:^(NSArrayController *arrayController) {
/// // Avoids a retain cycle because of RACObserve implicitly referencing
/// // self.
/// @strongify(self);
/// return RACObserve(arrayController, items);
/// }];
///
/// Returns a signal which sends the current value of the key path on
/// subscription, then sends the new value every time it changes, and sends
/// completed if self or observer is deallocated.
#define _RACObserve(TARGET, KEYPATH) \
({ \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})
#if __clang__ && (__clang_major__ >= 8)
#define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
#else
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
_RACObserve(TARGET, KEYPATH) \
_Pragma("clang diagnostic pop") \
})
#endif
@class RACDisposable;
@class RACTwoTuple<__covariant First, __covariant Second>;
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (RACPropertySubscribing)
/// Creates a signal to observe the value at the given key path.
///
/// The initial value is sent on subscription, the subsequent values are sent
/// from whichever thread the change occured on, even if it doesn't have a valid
/// scheduler.
///
/// Returns a signal that immediately sends the receiver's current value at the
/// given keypath, then any changes thereafter.
#if OS_OBJECT_HAVE_OBJC_SUPPORT
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer;
#else
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer;
#endif
/// Creates a signal to observe the changes of the given key path.
///
/// The initial value is sent on subscription if `NSKeyValueObservingOptionInitial` is set.
/// The subsequent values are sent from whichever thread the change occured on,
/// even if it doesn't have a valid scheduler.
///
/// Returns a signal that sends tuples containing the current value at the key
/// path and the change dictionary for each KVO callback.
#if OS_OBJECT_HAVE_OBJC_SUPPORT
- (RACSignal<RACTwoTuple<id, NSDictionary *> *> *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer;
#else
- (RACSignal<RACTwoTuple<id, NSDictionary *> *> *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer;
#endif
@end
NS_ASSUME_NONNULL_END
#define RACAble(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(_RACAbleObject(self, __VA_ARGS__)) \
(_RACAbleObject(__VA_ARGS__))
#define _RACAbleObject(object, property) [object rac_signalForKeyPath:@keypath(object, property) observer:self]
#define RACAbleWithStart(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(_RACAbleWithStartObject(self, __VA_ARGS__)) \
(_RACAbleWithStartObject(__VA_ARGS__))
#define _RACAbleWithStartObject(object, property) [object rac_signalWithStartingValueForKeyPath:@keypath(object, property) observer:self]

View File

@@ -0,0 +1,84 @@
//
// NSObject+RACPropertySubscribing.m
// ReactiveObjC
//
// Created by Josh Abernathy on 3/2/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "NSObject+RACPropertySubscribing.h"
#import <ReactiveObjC/RACEXTScope.h>
#import "NSObject+RACDeallocating.h"
#import "NSObject+RACDescription.h"
#import "NSObject+RACKVOWrapper.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACKVOTrampoline.h"
#import "RACSubscriber.h"
#import "RACSignal+Operations.h"
#import "RACTuple.h"
#import <libkern/OSAtomic.h>
@implementation NSObject (RACPropertySubscribing)
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer {
return [[[self
rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer]
map:^(RACTuple *value) {
// -map: because it doesn't require the block trampoline that -reduceEach: uses
return value[0];
}]
setNameWithFormat:@"RACObserve(%@, %@)", RACDescription(self), keyPath];
}
- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver {
NSObject *strongObserver = weakObserver;
keyPath = [keyPath copy];
NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init];
objectLock.name = @"org.reactivecocoa.ReactiveObjC.NSObjectRACPropertySubscribing";
__weak NSObject *weakSelf = self;
RACSignal *deallocSignal = [[RACSignal
zip:@[
self.rac_willDeallocSignal,
strongObserver.rac_willDeallocSignal ?: [RACSignal never]
]]
doCompleted:^{
// Forces deallocation to wait if the object variables are currently
// being read on another thread.
[objectLock lock];
@onExit {
[objectLock unlock];
};
}];
return [[[RACSignal
createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
// Hold onto the lock the whole time we're setting up the KVO
// observation, because any resurrection that might be caused by our
// retaining below must be balanced out by the time -dealloc returns
// (if another thread is waiting on the lock above).
[objectLock lock];
@onExit {
[objectLock unlock];
};
__strong NSObject *observer __attribute__((objc_precise_lifetime)) = weakObserver;
__strong NSObject *self __attribute__((objc_precise_lifetime)) = weakSelf;
if (self == nil) {
[subscriber sendCompleted];
return nil;
}
return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
[subscriber sendNext:RACTuplePack(value, change)];
}];
}]
takeUntil:deallocSignal]
setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", RACDescription(self), keyPath, (unsigned long)options, RACDescription(strongObserver)];
}
@end

View File

@@ -0,0 +1,86 @@
//
// NSObject+RACSelectorSignal.h
// ReactiveObjC
//
// Created by Josh Abernathy on 3/18/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACTuple;
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// The domain for any errors originating from -rac_signalForSelector:.
extern NSErrorDomain const RACSelectorSignalErrorDomain;
typedef NS_ERROR_ENUM(RACSelectorSignalErrorDomain, RACSelectorSignalError) {
/// -rac_signalForSelector: was going to add a new method implementation for
/// `selector`, but another thread added an implementation before it was able to.
///
/// This will _not_ occur for cases where a method implementation exists before
/// -rac_signalForSelector: is invoked.
RACSelectorSignalErrorMethodSwizzlingRace = 1,
};
@interface NSObject (RACSelectorSignal)
/// Creates a signal associated with the receiver, which will send a tuple of the
/// method's arguments each time the given selector is invoked.
///
/// If the selector is already implemented on the receiver, the existing
/// implementation will be invoked _before_ the signal fires.
///
/// If the selector is not yet implemented on the receiver, the injected
/// implementation will have a `void` return type and accept only object
/// arguments. Invoking the added implementation with non-object values, or
/// expecting a return value, will result in undefined behavior.
///
/// This is useful for changing an event or delegate callback into a signal. For
/// example, on an NSView:
///
/// [[view rac_signalForSelector:@selector(mouseDown:)] subscribeNext:^(RACTuple *args) {
/// NSEvent *event = args.first;
/// NSLog(@"mouse button pressed: %@", event);
/// }];
///
/// selector - The selector for whose invocations are to be observed. If it
/// doesn't exist, it will be implemented to accept object arguments
/// and return void. This cannot have C arrays or unions as arguments
/// or C arrays, unions, structs, complex or vector types as return
/// type.
///
/// Returns a signal which will send a tuple of arguments upon each invocation of
/// the selector, then completes when the receiver is deallocated. `next` events
/// will be sent synchronously from the thread that invoked the method. If
/// a runtime call fails, the signal will send an error in the
/// RACSelectorSignalErrorDomain.
- (RACSignal<RACTuple *> *)rac_signalForSelector:(SEL)selector;
/// Behaves like -rac_signalForSelector:, but if the selector is not yet
/// implemented on the receiver, its method signature is looked up within
/// `protocol`, and may accept non-object arguments.
///
/// If the selector is not yet implemented and has a return value, the injected
/// method will return all zero bits (equal to `nil`, `NULL`, 0, 0.0f, etc.).
///
/// selector - The selector for whose invocations are to be observed. If it
/// doesn't exist, it will be implemented using information from
/// `protocol`, and may accept non-object arguments and return
/// a value. This cannot have C arrays or unions as arguments or
/// return type.
/// protocol - The protocol in which `selector` is declared. This will be used
/// for type information if the selector is not already implemented on
/// the receiver. This must not be `NULL`, and `selector` must exist
/// in this protocol.
///
/// Returns a signal which will send a tuple of arguments on each invocation of
/// the selector, or an error in RACSelectorSignalErrorDomain if a runtime
/// call fails.
- (RACSignal<RACTuple *> *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,328 @@
//
// NSObject+RACSelectorSignal.m
// ReactiveObjC
//
// Created by Josh Abernathy on 3/18/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSObject+RACSelectorSignal.h"
#import <ReactiveObjC/RACEXTRuntimeExtensions.h>
#import "NSInvocation+RACTypeParsing.h"
#import "NSObject+RACDeallocating.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACSubject.h"
#import "RACTuple.h"
#import "NSObject+RACDescription.h"
#import <objc/message.h>
#import <objc/runtime.h>
NSErrorDomain const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain";
static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_";
static NSString * const RACSubclassSuffix = @"_RACSelectorSignal";
static void *RACSubclassAssociationKey = &RACSubclassAssociationKey;
static NSMutableSet *swizzledClasses() {
static NSMutableSet *set;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
set = [[NSMutableSet alloc] init];
});
return set;
}
@implementation NSObject (RACSelectorSignal)
static BOOL RACForwardInvocation(id self, NSInvocation *invocation) {
SEL aliasSelector = RACAliasForSelector(invocation.selector);
RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
Class class = object_getClass(invocation.target);
BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector];
if (respondsToAlias) {
invocation.selector = aliasSelector;
[invocation invoke];
}
if (subject == nil) return respondsToAlias;
[subject sendNext:invocation.rac_argumentsTuple];
return YES;
}
static void RACSwizzleForwardInvocation(Class class) {
SEL forwardInvocationSEL = @selector(forwardInvocation:);
Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL);
// Preserve any existing implementation of -forwardInvocation:.
void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL;
if (forwardInvocationMethod != NULL) {
originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod);
}
// Set up a new version of -forwardInvocation:.
//
// If the selector has been passed to -rac_signalForSelector:, invoke
// the aliased method, and forward the arguments to any attached signals.
//
// If the selector has not been passed to -rac_signalForSelector:,
// invoke any existing implementation of -forwardInvocation:. If there
// was no existing implementation, throw an unrecognized selector
// exception.
id newForwardInvocation = ^(id self, NSInvocation *invocation) {
BOOL matched = RACForwardInvocation(self, invocation);
if (matched) return;
if (originalForwardInvocation == NULL) {
[self doesNotRecognizeSelector:invocation.selector];
} else {
originalForwardInvocation(self, forwardInvocationSEL, invocation);
}
};
class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
}
static void RACSwizzleRespondsToSelector(Class class) {
SEL respondsToSelectorSEL = @selector(respondsToSelector:);
// Preserve existing implementation of -respondsToSelector:.
Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL);
BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod);
// Set up a new version of -respondsToSelector: that returns YES for methods
// added by -rac_signalForSelector:.
//
// If the selector has a method defined on the receiver's actual class, and
// if that method's implementation is _objc_msgForward, then returns whether
// the instance has a signal for the selector.
// Otherwise, call the original -respondsToSelector:.
id newRespondsToSelector = ^ BOOL (id self, SEL selector) {
Method method = rac_getImmediateInstanceMethod(class, selector);
if (method != NULL && method_getImplementation(method) == _objc_msgForward) {
SEL aliasSelector = RACAliasForSelector(selector);
if (objc_getAssociatedObject(self, aliasSelector) != nil) return YES;
}
return originalRespondsToSelector(self, respondsToSelectorSEL, selector);
};
class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod));
}
static void RACSwizzleGetClass(Class class, Class statedClass) {
SEL selector = @selector(class);
Method method = class_getInstanceMethod(class, selector);
IMP newIMP = imp_implementationWithBlock(^(id self) {
return statedClass;
});
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method));
}
static void RACSwizzleMethodSignatureForSelector(Class class) {
IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) {
// Don't send the -class message to the receiver because we've changed
// that to return the original class.
Class actualClass = object_getClass(self);
Method method = class_getInstanceMethod(actualClass, selector);
if (method == NULL) {
// Messages that the original class dynamically implements fall
// here.
//
// Call the original class' -methodSignatureForSelector:.
struct objc_super target = {
.super_class = class_getSuperclass(class),
.receiver = self,
};
NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper;
return messageSend(&target, @selector(methodSignatureForSelector:), selector);
}
char const *encoding = method_getTypeEncoding(method);
return [NSMethodSignature signatureWithObjCTypes:encoding];
});
SEL selector = @selector(methodSignatureForSelector:);
Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector);
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod));
}
// It's hard to tell which struct return types use _objc_msgForward, and
// which use _objc_msgForward_stret instead, so just exclude all struct, array,
// union, complex and vector return types.
static void RACCheckTypeEncoding(const char *typeEncoding) {
#if !NS_BLOCK_ASSERTIONS
// Some types, including vector types, are not encoded. In these cases the
// signature starts with the size of the argument frame.
NSCAssert(*typeEncoding < '1' || *typeEncoding > '9', @"unknown method return type not supported in type encoding: %s", typeEncoding);
NSCAssert(strstr(typeEncoding, "(") != typeEncoding, @"union method return type not supported");
NSCAssert(strstr(typeEncoding, "{") != typeEncoding, @"struct method return type not supported");
NSCAssert(strstr(typeEncoding, "[") != typeEncoding, @"array method return type not supported");
NSCAssert(strstr(typeEncoding, @encode(_Complex float)) != typeEncoding, @"complex float method return type not supported");
NSCAssert(strstr(typeEncoding, @encode(_Complex double)) != typeEncoding, @"complex double method return type not supported");
NSCAssert(strstr(typeEncoding, @encode(_Complex long double)) != typeEncoding, @"complex long double method return type not supported");
#endif // !NS_BLOCK_ASSERTIONS
}
static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) {
SEL aliasSelector = RACAliasForSelector(selector);
@synchronized (self) {
RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
if (subject != nil) return subject;
Class class = RACSwizzleClass(self);
NSCAssert(class != nil, @"Could not swizzle class of %@", self);
subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", RACDescription(self), sel_getName(selector)];
objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN);
[self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
[subject sendCompleted];
}]];
Method targetMethod = class_getInstanceMethod(class, selector);
if (targetMethod == NULL) {
const char *typeEncoding;
if (protocol == NULL) {
typeEncoding = RACSignatureForUndefinedSelector(selector);
} else {
// Look for the selector as an optional instance method.
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
if (methodDescription.name == NULL) {
// Then fall back to looking for a required instance
// method.
methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES);
NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol));
}
typeEncoding = methodDescription.types;
}
RACCheckTypeEncoding(typeEncoding);
// Define the selector to call -forwardInvocation:.
if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class],
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil)
};
return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]];
}
} else if (method_getImplementation(targetMethod) != _objc_msgForward) {
// Make a method alias for the existing method implementation.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
RACCheckTypeEncoding(typeEncoding);
BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class);
// Redefine the selector to call -forwardInvocation:.
class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
}
return subject;
}
}
static SEL RACAliasForSelector(SEL originalSelector) {
NSString *selectorName = NSStringFromSelector(originalSelector);
return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]);
}
static const char *RACSignatureForUndefinedSelector(SEL selector) {
const char *name = sel_getName(selector);
NSMutableString *signature = [NSMutableString stringWithString:@"v@:"];
while ((name = strchr(name, ':')) != NULL) {
[signature appendString:@"@"];
name++;
}
return signature.UTF8String;
}
static Class RACSwizzleClass(NSObject *self) {
Class statedClass = self.class;
Class baseClass = object_getClass(self);
// The "known dynamic subclass" is the subclass generated by RAC.
// It's stored as an associated object on every instance that's already
// been swizzled, so that even if something else swizzles the class of
// this instance, we can still access the RAC generated subclass.
Class knownDynamicSubclass = objc_getAssociatedObject(self, RACSubclassAssociationKey);
if (knownDynamicSubclass != Nil) return knownDynamicSubclass;
NSString *className = NSStringFromClass(baseClass);
if (statedClass != baseClass) {
// If the class is already lying about what it is, it's probably a KVO
// dynamic subclass or something else that we shouldn't subclass
// ourselves.
//
// Just swizzle -forwardInvocation: in-place. Since the object's class
// was almost certainly dynamically changed, we shouldn't see another of
// these classes in the hierarchy.
//
// Additionally, swizzle -respondsToSelector: because the default
// implementation may be ignorant of methods added to this class.
@synchronized (swizzledClasses()) {
if (![swizzledClasses() containsObject:className]) {
RACSwizzleForwardInvocation(baseClass);
RACSwizzleRespondsToSelector(baseClass);
RACSwizzleGetClass(baseClass, statedClass);
RACSwizzleGetClass(object_getClass(baseClass), statedClass);
RACSwizzleMethodSignatureForSelector(baseClass);
[swizzledClasses() addObject:className];
}
}
return baseClass;
}
const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) return nil;
RACSwizzleForwardInvocation(subclass);
RACSwizzleRespondsToSelector(subclass);
RACSwizzleGetClass(subclass, statedClass);
RACSwizzleGetClass(object_getClass(subclass), statedClass);
RACSwizzleMethodSignatureForSelector(subclass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, OBJC_ASSOCIATION_ASSIGN);
return subclass;
}
- (RACSignal *)rac_signalForSelector:(SEL)selector {
NSCParameterAssert(selector != NULL);
return NSObjectRACSignalForSelector(self, selector, NULL);
}
- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol {
NSCParameterAssert(selector != NULL);
NSCParameterAssert(protocol != NULL);
return NSObjectRACSignalForSelector(self, selector, protocol);
}
@end

View File

@@ -0,0 +1,24 @@
//
// NSOrderedSet+RACSequenceAdditions.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSOrderedSet<__covariant ObjectType> (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<ObjectType> *rac_sequence;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,19 @@
//
// NSOrderedSet+RACSequenceAdditions.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSOrderedSet+RACSequenceAdditions.h"
#import "NSArray+RACSequenceAdditions.h"
@implementation NSOrderedSet (RACSequenceAdditions)
- (RACSequence *)rac_sequence {
// TODO: First class support for ordered set sequences.
return self.array.rac_sequence;
}
@end

View File

@@ -0,0 +1,24 @@
//
// NSSet+RACSequenceAdditions.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSSet<__covariant ObjectType> (RACSequenceAdditions)
/// Creates and returns a sequence corresponding to the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<ObjectType> *rac_sequence;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,19 @@
//
// NSSet+RACSequenceAdditions.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSSet+RACSequenceAdditions.h"
#import "NSArray+RACSequenceAdditions.h"
@implementation NSSet (RACSequenceAdditions)
- (RACSequence *)rac_sequence {
// TODO: First class support for set sequences.
return self.allObjects.rac_sequence;
}
@end

View File

@@ -0,0 +1,34 @@
//
// NSString+RACKeyPathUtilities.h
// ReactiveObjC
//
// Created by Uri Baghin on 05/05/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
// A private category of methods to extract parts of a key path.
@interface NSString (RACKeyPathUtilities)
// Returns an array of the components of the receiver.
//
// Calling this method on a string that isn't a key path is considered undefined
// behavior.
- (NSArray *)rac_keyPathComponents;
// Returns a key path with all the components of the receiver except for the
// last one.
//
// Calling this method on a string that isn't a key path is considered undefined
// behavior.
- (NSString *)rac_keyPathByDeletingLastKeyPathComponent;
// Returns a key path with all the components of the receiver expect for the
// first one.
//
// Calling this method on a string that isn't a key path is considered undefined
// behavior.
- (NSString *)rac_keyPathByDeletingFirstKeyPathComponent;
@end

View File

@@ -0,0 +1,36 @@
//
// NSString+RACKeyPathUtilities.m
// ReactiveObjC
//
// Created by Uri Baghin on 05/05/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSString+RACKeyPathUtilities.h"
@implementation NSString (RACKeyPathUtilities)
- (NSArray *)rac_keyPathComponents {
if (self.length == 0) {
return nil;
}
return [self componentsSeparatedByString:@"."];
}
- (NSString *)rac_keyPathByDeletingLastKeyPathComponent {
NSUInteger lastDotIndex = [self rangeOfString:@"." options:NSBackwardsSearch].location;
if (lastDotIndex == NSNotFound) {
return nil;
}
return [self substringToIndex:lastDotIndex];
}
- (NSString *)rac_keyPathByDeletingFirstKeyPathComponent {
NSUInteger firstDotIndex = [self rangeOfString:@"."].location;
if (firstDotIndex == NSNotFound) {
return nil;
}
return [self substringFromIndex:firstDotIndex + 1];
}
@end

View File

@@ -0,0 +1,25 @@
//
// NSString+RACSequenceAdditions.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSequence<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSString (RACSequenceAdditions)
/// Creates and returns a sequence containing strings corresponding to each
/// composed character sequence in the receiver.
///
/// Mutating the receiver will not affect the sequence after it's been created.
@property (nonatomic, copy, readonly) RACSequence<NSString *> *rac_sequence;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,18 @@
//
// NSString+RACSequenceAdditions.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "NSString+RACSequenceAdditions.h"
#import "RACStringSequence.h"
@implementation NSString (RACSequenceAdditions)
- (RACSequence *)rac_sequence {
return [RACStringSequence sequenceWithString:self offset:0];
}
@end

View File

@@ -0,0 +1,26 @@
//
// NSString+RACSupport.h
// ReactiveObjC
//
// Created by Josh Abernathy on 5/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACScheduler;
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSString (RACSupport)
// Reads in the contents of the file using +[NSString stringWithContentsOfURL:usedEncoding:error:].
// Note that encoding won't be valid until the signal completes successfully.
//
// scheduler - cannot be nil.
+ (RACSignal<NSString *> *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,37 @@
//
// NSString+RACSupport.m
// ReactiveObjC
//
// Created by Josh Abernathy on 5/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "NSString+RACSupport.h"
#import "RACReplaySubject.h"
#import "RACScheduler.h"
@implementation NSString (RACSupport)
+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler {
NSCParameterAssert(URL != nil);
NSCParameterAssert(encoding != nil);
NSCParameterAssert(scheduler != nil);
RACReplaySubject *subject = [RACReplaySubject subject];
[subject setNameWithFormat:@"+rac_readContentsOfURL: %@ usedEncoding:scheduler: %@", URL, scheduler];
[scheduler schedule:^{
NSError *error = nil;
NSString *string = [NSString stringWithContentsOfURL:URL usedEncoding:encoding error:&error];
if (string == nil) {
[subject sendError:error];
} else {
[subject sendNext:string];
[subject sendCompleted];
}
}];
return subject;
}
@end

View File

@@ -0,0 +1,30 @@
//
// NSURLConnection+RACSupport.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-01.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACTwoTuple<__covariant First, __covariant Second>;
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
@interface NSURLConnection (RACSupport)
/// Lazily loads data for the given request in the background.
///
/// request - The URL request to load. This must not be nil.
///
/// Returns a signal which will begin loading the request upon each subscription,
/// then send a tuple of the received response and downloaded data, and complete
/// on a background thread. If any errors occur, the returned signal will error
/// out.
+ (RACSignal<RACTwoTuple<NSURLResponse *, NSData *> *> *)rac_sendAsynchronousRequest:(NSURLRequest *)request;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,54 @@
//
// NSURLConnection+RACSupport.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-01.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSURLConnection+RACSupport.h"
#import "RACDisposable.h"
#import "RACSignal.h"
#import "RACSubscriber.h"
#import "RACTuple.h"
@implementation NSURLConnection (RACSupport)
+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request {
NSCParameterAssert(request != nil);
return [[RACSignal
createSignal:^(id<RACSubscriber> subscriber) {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"org.reactivecocoa.ReactiveObjC.NSURLConnectionRACSupport";
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// The docs say that `nil` data means an error occurred, but
// `nil` responses can also occur in practice (circumstances
// unknown). Consider either to be an error.
//
// Note that _empty_ data is not necessarily erroneous, as there
// may be headers but no HTTP body.
if (response == nil || data == nil) {
[subscriber sendError:error];
} else {
[subscriber sendNext:RACTuplePack(response, data)];
[subscriber sendCompleted];
}
}];
#pragma clang diagnostic pop
return [RACDisposable disposableWithBlock:^{
// It's not clear if this will actually cancel the connection,
// but we can at least prevent _some_ unnecessary work --
// without writing all the code for a proper delegate, which
// doesn't really belong in RAC.
queue.suspended = YES;
[queue cancelAllOperations];
}];
}]
setNameWithFormat:@"+rac_sendAsynchronousRequest: %@", request];
}
@end

View File

@@ -0,0 +1,31 @@
//
// NSUserDefaults+RACSupport.h
// ReactiveObjC
//
// Created by Matt Diephouse on 12/19/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACChannelTerminal;
NS_ASSUME_NONNULL_BEGIN
@interface NSUserDefaults (RACSupport)
/// Creates and returns a terminal for binding the user defaults key.
///
/// **Note:** The value in the user defaults is *asynchronously* updated with
/// values sent to the channel.
///
/// key - The user defaults key to create the channel terminal for.
///
/// Returns a channel terminal that sends the value of the user defaults key
/// upon subscription, sends an updated value whenever the default changes, and
/// updates the default asynchronously with values it receives.
- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,58 @@
//
// NSUserDefaults+RACSupport.m
// ReactiveObjC
//
// Created by Matt Diephouse on 12/19/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSUserDefaults+RACSupport.h"
#import <ReactiveObjC/RACEXTScope.h>
#import "NSNotificationCenter+RACSupport.h"
#import "NSObject+RACDeallocating.h"
#import "RACChannel.h"
#import "RACScheduler.h"
#import "RACSignal+Operations.h"
@implementation NSUserDefaults (RACSupport)
- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key {
NSParameterAssert(key != nil);
RACChannel *channel = [RACChannel new];
RACScheduler *scheduler = [RACScheduler scheduler];
__block BOOL ignoreNextValue = NO;
@weakify(self);
[[[[[[[NSNotificationCenter.defaultCenter
rac_addObserverForName:NSUserDefaultsDidChangeNotification object:self]
map:^(id _) {
@strongify(self);
return [self objectForKey:key];
}]
startWith:[self objectForKey:key]]
// Don't send values that were set on the other side of the terminal.
filter:^ BOOL (id _) {
if (RACScheduler.currentScheduler == scheduler && ignoreNextValue) {
ignoreNextValue = NO;
return NO;
}
return YES;
}]
distinctUntilChanged]
takeUntil:self.rac_willDeallocSignal]
subscribe:channel.leadingTerminal];
[[channel.leadingTerminal
deliverOn:scheduler]
subscribeNext:^(id value) {
@strongify(self);
ignoreNextValue = YES;
[self setObject:value forKey:key];
}];
return channel.followingTerminal;
}
@end

View File

@@ -0,0 +1,11 @@
//
// RACAnnotations.h
// ReactiveObjC
//
// Created by Eric Horacek on 3/31/17.
// Copyright © 2017 GitHub. All rights reserved.
//
#ifndef RAC_WARN_UNUSED_RESULT
#define RAC_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#endif

View File

@@ -0,0 +1,18 @@
//
// RACArraySequence.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "RACSequence.h"
// Private class that adapts an array to the RACSequence interface.
@interface RACArraySequence : RACSequence
// Returns a sequence for enumerating over the given array, starting from the
// given offset. The array will be copied to prevent mutation.
+ (RACSequence *)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset;
@end

View File

@@ -0,0 +1,125 @@
//
// RACArraySequence.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "RACArraySequence.h"
@interface RACArraySequence ()
// Redeclared from the superclass and marked deprecated to prevent using `array`
// where `backingArray` is intended.
@property (nonatomic, copy, readonly) NSArray *array __attribute__((deprecated));
// The array being sequenced.
@property (nonatomic, copy, readonly) NSArray *backingArray;
// The index in the array from which the sequence starts.
@property (nonatomic, assign, readonly) NSUInteger offset;
@end
@implementation RACArraySequence
#pragma mark Lifecycle
+ (RACSequence *)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset {
NSCParameterAssert(offset <= array.count);
if (offset == array.count) return self.empty;
RACArraySequence *seq = [[self alloc] init];
seq->_backingArray = [array copy];
seq->_offset = offset;
return seq;
}
#pragma mark RACSequence
- (id)head {
return self.backingArray[self.offset];
}
- (RACSequence *)tail {
RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1];
sequence.name = self.name;
return sequence;
}
#pragma mark NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len {
NSCParameterAssert(len > 0);
if (state->state >= self.backingArray.count) {
// Enumeration has completed.
return 0;
}
if (state->state == 0) {
state->state = self.offset;
// Since a sequence doesn't mutate, this just needs to be set to
// something non-NULL.
state->mutationsPtr = state->extra;
}
state->itemsPtr = stackbuf;
NSUInteger startIndex = state->state;
NSUInteger index = 0;
for (id value in self.backingArray) {
// Constructing an index set for -enumerateObjectsAtIndexes: can actually be
// slower than just skipping the items we don't care about.
if (index < startIndex) {
++index;
continue;
}
stackbuf[index - startIndex] = value;
++index;
if (index - startIndex >= len) break;
}
NSCAssert(index > startIndex, @"Final index (%lu) should be greater than start index (%lu)", (unsigned long)index, (unsigned long)startIndex);
state->state = index;
return index - startIndex;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
- (NSArray *)array {
return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)];
}
#pragma clang diagnostic pop
#pragma mark NSCoding
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self == nil) return nil;
_backingArray = [coder decodeObjectForKey:@"array"];
_offset = 0;
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
// Encoding is handled in RACSequence.
[super encodeWithCoder:coder];
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p>{ name = %@, array = %@ }", self.class, self, self.name, self.backingArray];
}
@end

View File

@@ -0,0 +1,22 @@
//
// RACBehaviorSubject.h
// ReactiveObjC
//
// Created by Josh Abernathy on 3/16/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACSubject.h"
NS_ASSUME_NONNULL_BEGIN
/// A behavior subject sends the last value it received when it is subscribed to.
@interface RACBehaviorSubject<ValueType> : RACSubject<ValueType>
/// Creates a new behavior subject with a default value. If it hasn't received
/// any values when it gets subscribed to, it sends the default value.
+ (instancetype)behaviorSubjectWithDefaultValue:(nullable ValueType)value;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,56 @@
//
// RACBehaviorSubject.m
// ReactiveObjC
//
// Created by Josh Abernathy on 3/16/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACBehaviorSubject.h"
#import "RACDisposable.h"
#import "RACScheduler+Private.h"
@interface RACBehaviorSubject<ValueType> ()
// This property should only be used while synchronized on self.
@property (nonatomic, strong) ValueType currentValue;
@end
@implementation RACBehaviorSubject
#pragma mark Lifecycle
+ (instancetype)behaviorSubjectWithDefaultValue:(id)value {
RACBehaviorSubject *subject = [self subject];
subject.currentValue = value;
return subject;
}
#pragma mark RACSignal
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
[subscriber sendNext:self.currentValue];
}
}];
return [RACDisposable disposableWithBlock:^{
[subscriptionDisposable dispose];
[schedulingDisposable dispose];
}];
}
#pragma mark RACSubscriber
- (void)sendNext:(id)value {
@synchronized (self) {
self.currentValue = value;
[super sendNext:value];
}
}
@end

View File

@@ -0,0 +1,30 @@
//
// RACBlockTrampoline.h
// ReactiveObjC
//
// Created by Josh Abernathy on 10/21/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACTuple;
// A private class that allows a limited type of dynamic block invocation.
@interface RACBlockTrampoline : NSObject
// Invokes the given block with the given arguments. All of the block's
// argument types must be objects and it must be typed to return an object.
//
// At this time, it only supports blocks that take up to 15 arguments. Any more
// is just cray.
//
// block - The block to invoke. Must accept as many arguments as are given in
// the arguments array. Cannot be nil.
// arguments - The arguments with which to invoke the block. `RACTupleNil`s will
// be passed as nils.
//
// Returns the return value of invoking the block.
+ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments;
@end

View File

@@ -0,0 +1,155 @@
//
// RACBlockTrampoline.m
// ReactiveObjC
//
// Created by Josh Abernathy on 10/21/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACBlockTrampoline.h"
#import "RACTuple.h"
@interface RACBlockTrampoline ()
@property (nonatomic, readonly, copy) id block;
@end
@implementation RACBlockTrampoline
#pragma mark API
- (instancetype)initWithBlock:(id)block {
self = [super init];
_block = [block copy];
return self;
}
+ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments {
NSCParameterAssert(block != NULL);
RACBlockTrampoline *trampoline = [(RACBlockTrampoline *)[self alloc] initWithBlock:block];
return [trampoline invokeWithArguments:arguments];
}
- (id)invokeWithArguments:(RACTuple *)arguments {
SEL selector = [self selectorForArgumentCount:arguments.count];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
invocation.selector = selector;
invocation.target = self;
for (NSUInteger i = 0; i < arguments.count; i++) {
id arg = arguments[i];
NSInteger argIndex = (NSInteger)(i + 2);
[invocation setArgument:&arg atIndex:argIndex];
}
[invocation invoke];
__unsafe_unretained id returnVal;
[invocation getReturnValue:&returnVal];
return returnVal;
}
- (SEL)selectorForArgumentCount:(NSUInteger)count {
NSCParameterAssert(count > 0);
switch (count) {
case 0: return NULL;
case 1: return @selector(performWith:);
case 2: return @selector(performWith::);
case 3: return @selector(performWith:::);
case 4: return @selector(performWith::::);
case 5: return @selector(performWith:::::);
case 6: return @selector(performWith::::::);
case 7: return @selector(performWith:::::::);
case 8: return @selector(performWith::::::::);
case 9: return @selector(performWith:::::::::);
case 10: return @selector(performWith::::::::::);
case 11: return @selector(performWith:::::::::::);
case 12: return @selector(performWith::::::::::::);
case 13: return @selector(performWith:::::::::::::);
case 14: return @selector(performWith::::::::::::::);
case 15: return @selector(performWith:::::::::::::::);
}
NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 15 arguments are currently supported.");
return NULL;
}
- (id)performWith:(id)obj1 {
id (^block)(id) = self.block;
return block(obj1);
}
- (id)performWith:(id)obj1 :(id)obj2 {
id (^block)(id, id) = self.block;
return block(obj1, obj2);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 {
id (^block)(id, id, id) = self.block;
return block(obj1, obj2, obj3);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 {
id (^block)(id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 {
id (^block)(id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 {
id (^block)(id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 {
id (^block)(id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 {
id (^block)(id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 {
id (^block)(id, id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 {
id (^block)(id, id, id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 {
id (^block)(id, id, id, id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 {
id (^block)(id, id, id, id, id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 {
id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 :(id)obj14 {
id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13, obj14);
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 :(id)obj14 :(id)obj15 {
id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block;
return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13, obj14, obj15);
}
@end

View File

@@ -0,0 +1,77 @@
//
// RACChannel.h
// ReactiveObjC
//
// Created by Uri Baghin on 01/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSignal.h"
#import "RACSubscriber.h"
@class RACChannelTerminal<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// A two-way channel.
///
/// Conceptually, RACChannel can be thought of as a bidirectional connection,
/// composed of two controllable signals that work in parallel.
///
/// For example, when connecting between a view and a model:
///
/// Model View
/// `leadingTerminal` ------> `followingTerminal`
/// `leadingTerminal` <------ `followingTerminal`
///
/// The initial value of the model and all future changes to it are _sent on_ the
/// `leadingTerminal`, and _received by_ subscribers of the `followingTerminal`.
///
/// Likewise, whenever the user changes the value of the view, that value is sent
/// on the `followingTerminal`, and received in the model from the
/// `leadingTerminal`. However, the initial value of the view is not received
/// from the `leadingTerminal` (only future changes).
@interface RACChannel<ValueType> : NSObject
/// The terminal which "leads" the channel, by sending its latest value
/// immediately to new subscribers of the `followingTerminal`.
///
/// New subscribers to this terminal will not receive a starting value, but will
/// receive all future values that are sent to the `followingTerminal`.
@property (nonatomic, strong, readonly) RACChannelTerminal<ValueType> *leadingTerminal;
/// The terminal which "follows" the lead of the other terminal, only sending
/// _future_ values to the subscribers of the `leadingTerminal`.
///
/// The latest value sent to the `leadingTerminal` (if any) will be sent
/// immediately to new subscribers of this terminal, and then all future values
/// as well.
@property (nonatomic, strong, readonly) RACChannelTerminal<ValueType> *followingTerminal;
@end
/// Represents one end of a RACChannel.
///
/// An terminal is similar to a socket or pipe -- it represents one end of
/// a connection (the RACChannel, in this case). Values sent to this terminal
/// will _not_ be received by its subscribers. Instead, the values will be sent
/// to the subscribers of the RACChannel's _other_ terminal.
///
/// For example, when using the `followingTerminal`, _sent_ values can only be
/// _received_ from the `leadingTerminal`, and vice versa.
///
/// To make it easy to terminate a RACChannel, `error` and `completed` events
/// sent to either terminal will be received by the subscribers of _both_
/// terminals.
///
/// Do not instantiate this class directly. Create a RACChannel instead.
@interface RACChannelTerminal<ValueType> : RACSignal<ValueType> <RACSubscriber>
- (instancetype)init __attribute__((unavailable("Instantiate a RACChannel instead")));
// Redeclaration of the RACSubscriber method. Made in order to specify a generic type.
- (void)sendNext:(nullable ValueType)value;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,88 @@
//
// RACChannel.m
// ReactiveObjC
//
// Created by Uri Baghin on 01/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACChannel.h"
#import "RACDisposable.h"
#import "RACReplaySubject.h"
#import "RACSignal+Operations.h"
@interface RACChannelTerminal<ValueType> ()
/// The values for this terminal.
@property (nonatomic, strong, readonly) RACSignal<ValueType> *values;
/// A subscriber will will send values to the other terminal.
@property (nonatomic, strong, readonly) id<RACSubscriber> otherTerminal;
- (instancetype)initWithValues:(RACSignal<ValueType> *)values otherTerminal:(id<RACSubscriber>)otherTerminal;
@end
@implementation RACChannel
- (instancetype)init {
self = [super init];
// We don't want any starting value from the leadingSubject, but we do want
// error and completion to be replayed.
RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"];
RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"];
// Propagate errors and completion to everything.
[[leadingSubject ignoreValues] subscribe:followingSubject];
[[followingSubject ignoreValues] subscribe:leadingSubject];
_leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"];
_followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"];
return self;
}
@end
@implementation RACChannelTerminal
#pragma mark Lifecycle
- (instancetype)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal {
NSCParameterAssert(values != nil);
NSCParameterAssert(otherTerminal != nil);
self = [super init];
_values = values;
_otherTerminal = otherTerminal;
return self;
}
#pragma mark RACSignal
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
return [self.values subscribe:subscriber];
}
#pragma mark <RACSubscriber>
- (void)sendNext:(id)value {
[self.otherTerminal sendNext:value];
}
- (void)sendError:(NSError *)error {
[self.otherTerminal sendError:error];
}
- (void)sendCompleted {
[self.otherTerminal sendCompleted];
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
[self.otherTerminal didSubscribeWithDisposable:disposable];
}
@end

View File

@@ -0,0 +1,118 @@
//
// RACCommand.h
// ReactiveObjC
//
// Created by Josh Abernathy on 3/3/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// The domain for errors originating within `RACCommand`.
extern NSErrorDomain const RACCommandErrorDomain;
typedef NS_ERROR_ENUM(RACCommandErrorDomain, RACCommandError) {
/// -execute: was invoked while the command was disabled.
RACCommandErrorNotEnabled = 1,
};
/// A `userInfo` key for an error, associated with the `RACCommand` that the
/// error originated from.
///
/// This is included only when the error code is `RACCommandErrorNotEnabled`.
extern NSString * const RACUnderlyingCommandErrorKey;
/// A command is a signal triggered in response to some action, typically
/// UI-related.
@interface RACCommand<__contravariant InputType, __covariant ValueType> : NSObject
/// A signal of the signals returned by successful invocations of -execute:
/// (i.e., while the receiver is `enabled`).
///
/// Errors will be automatically caught upon the inner signals, and sent upon
/// `errors` instead. If you _want_ to receive inner errors, use -execute: or
/// -[RACSignal materialize].
///
/// Only executions that begin _after_ subscription will be sent upon this
/// signal. All inner signals will arrive upon the main thread.
@property (nonatomic, strong, readonly) RACSignal<RACSignal<ValueType> *> *executionSignals;
/// A signal of whether this command is currently executing.
///
/// This will send YES whenever -execute: is invoked and the created signal has
/// not yet terminated. Once all executions have terminated, `executing` will
/// send NO.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *executing;
/// A signal of whether this command is able to execute.
///
/// This will send NO if:
///
/// - The command was created with an `enabledSignal`, and NO is sent upon that
/// signal, or
/// - `allowsConcurrentExecution` is NO and the command has started executing.
///
/// Once the above conditions are no longer met, the signal will send YES.
///
/// This signal will send its current value upon subscription, and then all
/// future values on the main thread.
@property (nonatomic, strong, readonly) RACSignal<NSNumber *> *enabled;
/// Forwards any errors that occur within signals returned by -execute:.
///
/// When an error occurs on a signal returned from -execute:, this signal will
/// send the associated NSError value as a `next` event (since an `error` event
/// would terminate the stream).
///
/// After subscription, this signal will send all future errors on the main
/// thread.
@property (nonatomic, strong, readonly) RACSignal<NSError *> *errors;
/// Whether the command allows multiple executions to proceed concurrently.
///
/// The default value for this property is NO.
@property (atomic, assign) BOOL allowsConcurrentExecution;
/// Invokes -initWithEnabled:signalBlock: with a nil `enabledSignal`.
- (instancetype)initWithSignalBlock:(RACSignal<ValueType> * (^)(InputType _Nullable input))signalBlock;
/// Initializes a command that is conditionally enabled.
///
/// This is the designated initializer for this class.
///
/// enabledSignal - A signal of BOOLs which indicate whether the command should
/// be enabled. `enabled` will be based on the latest value sent
/// from this signal. Before any values are sent, `enabled` will
/// default to YES. This argument may be nil.
/// signalBlock - A block which will map each input value (passed to -execute:)
/// to a signal of work. The returned signal will be multicasted
/// to a replay subject, sent on `executionSignals`, then
/// subscribed to synchronously. Neither the block nor the
/// returned signal may be nil.
- (instancetype)initWithEnabled:(nullable RACSignal<NSNumber *> *)enabledSignal signalBlock:(RACSignal<ValueType> * (^)(InputType _Nullable input))signalBlock;
/// If the receiver is enabled, this method will:
///
/// 1. Invoke the `signalBlock` given at the time of initialization.
/// 2. Multicast the returned signal to a RACReplaySubject.
/// 3. Send the multicasted signal on `executionSignals`.
/// 4. Subscribe (connect) to the original signal on the main thread.
///
/// input - The input value to pass to the receiver's `signalBlock`. This may be
/// nil.
///
/// Returns the multicasted signal, after subscription. If the receiver is not
/// enabled, returns a signal that will send an error with code
/// RACCommandErrorNotEnabled.
- (RACSignal<ValueType> *)execute:(nullable InputType)input;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,200 @@
//
// RACCommand.m
// ReactiveObjC
//
// Created by Josh Abernathy on 3/3/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACCommand.h"
#import <ReactiveObjC/RACEXTScope.h>
#import "NSArray+RACSequenceAdditions.h"
#import "NSObject+RACDeallocating.h"
#import "NSObject+RACDescription.h"
#import "NSObject+RACPropertySubscribing.h"
#import "RACMulticastConnection.h"
#import "RACReplaySubject.h"
#import "RACScheduler.h"
#import "RACSequence.h"
#import "RACSignal+Operations.h"
#import <libkern/OSAtomic.h>
NSErrorDomain const RACCommandErrorDomain = @"RACCommandErrorDomain";
NSString * const RACUnderlyingCommandErrorKey = @"RACUnderlyingCommandErrorKey";
@interface RACCommand () {
// Atomic backing variable for `allowsConcurrentExecution`.
volatile uint32_t _allowsConcurrentExecution;
}
/// A subject that sends added execution signals.
@property (nonatomic, strong, readonly) RACSubject *addedExecutionSignalsSubject;
/// A subject that sends the new value of `allowsConcurrentExecution` whenever it changes.
@property (nonatomic, strong, readonly) RACSubject *allowsConcurrentExecutionSubject;
// `enabled`, but without a hop to the main thread.
//
// Values from this signal may arrive on any thread.
@property (nonatomic, strong, readonly) RACSignal *immediateEnabled;
// The signal block that the receiver was initialized with.
@property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input);
@end
@implementation RACCommand
#pragma mark Properties
- (BOOL)allowsConcurrentExecution {
return _allowsConcurrentExecution != 0;
}
- (void)setAllowsConcurrentExecution:(BOOL)allowed {
if (allowed) {
OSAtomicOr32Barrier(1, &_allowsConcurrentExecution);
} else {
OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution);
}
[self.allowsConcurrentExecutionSubject sendNext:@(_allowsConcurrentExecution)];
}
#pragma mark Lifecycle
- (instancetype)init {
NSCAssert(NO, @"Use -initWithSignalBlock: instead");
return nil;
}
- (instancetype)initWithSignalBlock:(RACSignal<id> * (^)(id input))signalBlock {
return [self initWithEnabled:nil signalBlock:signalBlock];
}
- (void)dealloc {
[_addedExecutionSignalsSubject sendCompleted];
[_allowsConcurrentExecutionSubject sendCompleted];
}
- (instancetype)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal<id> * (^)(id input))signalBlock {
NSCParameterAssert(signalBlock != nil);
self = [super init];
_addedExecutionSignalsSubject = [RACSubject new];
_allowsConcurrentExecutionSubject = [RACSubject new];
_signalBlock = [signalBlock copy];
_executionSignals = [[[self.addedExecutionSignalsSubject
map:^(RACSignal *signal) {
return [signal catchTo:[RACSignal empty]];
}]
deliverOn:RACScheduler.mainThreadScheduler]
setNameWithFormat:@"%@ -executionSignals", self];
// `errors` needs to be multicasted so that it picks up all
// `activeExecutionSignals` that are added.
//
// In other words, if someone subscribes to `errors` _after_ an execution
// has started, it should still receive any error from that execution.
RACMulticastConnection *errorsConnection = [[[self.addedExecutionSignalsSubject
flattenMap:^(RACSignal *signal) {
return [[signal
ignoreValues]
catch:^(NSError *error) {
return [RACSignal return:error];
}];
}]
deliverOn:RACScheduler.mainThreadScheduler]
publish];
_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
[errorsConnection connect];
RACSignal *immediateExecuting = [[[[self.addedExecutionSignalsSubject
flattenMap:^(RACSignal *signal) {
return [[[signal
catchTo:[RACSignal empty]]
then:^{
return [RACSignal return:@-1];
}]
startWith:@1];
}]
scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) {
return @(running.integerValue + next.integerValue);
}]
map:^(NSNumber *count) {
return @(count.integerValue > 0);
}]
startWith:@NO];
_executing = [[[[[immediateExecuting
deliverOn:RACScheduler.mainThreadScheduler]
// This is useful before the first value arrives on the main thread.
startWith:@NO]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -executing", self];
RACSignal *moreExecutionsAllowed = [RACSignal
if:[self.allowsConcurrentExecutionSubject startWith:@NO]
then:[RACSignal return:@YES]
else:[immediateExecuting not]];
if (enabledSignal == nil) {
enabledSignal = [RACSignal return:@YES];
} else {
enabledSignal = [enabledSignal startWith:@YES];
}
_immediateEnabled = [[[[RACSignal
combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
and]
takeUntil:self.rac_willDeallocSignal]
replayLast];
_enabled = [[[[[self.immediateEnabled
take:1]
concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -enabled", self];
return self;
}
#pragma mark Execution
- (RACSignal *)execute:(id)input {
// `immediateEnabled` is guaranteed to send a value upon subscription, so
// -first is acceptable here.
BOOL enabled = [[self.immediateEnabled first] boolValue];
if (!enabled) {
NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
RACUnderlyingCommandErrorKey: self
}];
return [RACSignal error:error];
}
RACSignal *signal = self.signalBlock(input);
NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
// We subscribe to the signal on the main thread so that it occurs _after_
// -addActiveExecutionSignal: completes below.
//
// This means that `executing` and `enabled` will send updated values before
// the signal actually starts performing work.
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
[self.addedExecutionSignalsSubject sendNext:connection.signal];
[connection connect];
return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(input)];
}
@end

View File

@@ -0,0 +1,52 @@
//
// RACCompoundDisposable.h
// ReactiveObjC
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACDisposable.h"
NS_ASSUME_NONNULL_BEGIN
/// A disposable of disposables. When it is disposed, it disposes of all its
/// contained disposables.
///
/// If -addDisposable: is called after the compound disposable has been disposed
/// of, the given disposable is immediately disposed. This allows a compound
/// disposable to act as a stand-in for a disposable that will be delivered
/// asynchronously.
@interface RACCompoundDisposable : RACDisposable
/// Creates and returns a new compound disposable.
+ (instancetype)compoundDisposable;
/// Creates and returns a new compound disposable containing the given
/// disposables.
+ (instancetype)compoundDisposableWithDisposables:(nullable NSArray *)disposables;
/// Adds the given disposable. If the receiving disposable has already been
/// disposed of, the given disposable is disposed immediately.
///
/// This method is thread-safe.
///
/// disposable - The disposable to add. This may be nil, in which case nothing
/// happens.
- (void)addDisposable:(nullable RACDisposable *)disposable;
/// Removes the specified disposable from the compound disposable (regardless of
/// its disposed status), or does nothing if it's not in the compound disposable.
///
/// This is mainly useful for limiting the memory usage of the compound
/// disposable for long-running operations.
///
/// This method is thread-safe.
///
/// disposable - The disposable to remove. This argument may be nil (to make the
/// use of weak references easier).
- (void)removeDisposable:(nullable RACDisposable *)disposable;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,250 @@
//
// RACCompoundDisposable.m
// ReactiveObjC
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACCompoundDisposable.h"
#import "RACCompoundDisposableProvider.h"
#import <pthread/pthread.h>
// The number of child disposables for which space will be reserved directly in
// `RACCompoundDisposable`.
//
// This number has been empirically determined to provide a good tradeoff
// between performance, memory usage, and `RACCompoundDisposable` instance size
// in a moderately complex GUI application.
//
// Profile any change!
#define RACCompoundDisposableInlineCount 2
static CFMutableArrayRef RACCreateDisposablesArray(void) {
// Compare values using only pointer equality.
CFArrayCallBacks callbacks = kCFTypeArrayCallBacks;
callbacks.equal = NULL;
return CFArrayCreateMutable(NULL, 0, &callbacks);
}
@interface RACCompoundDisposable () {
// Used for synchronization.
pthread_mutex_t _mutex;
#if RACCompoundDisposableInlineCount
// A fast array to the first N of the receiver's disposables.
//
// Once this is full, `_disposables` will be created and used for additional
// disposables.
//
// This array should only be manipulated while _mutex is held.
RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount];
#endif
// Contains the receiver's disposables.
//
// This array should only be manipulated while _mutex is held. If
// `_disposed` is YES, this may be NULL.
CFMutableArrayRef _disposables;
// Whether the receiver has already been disposed.
//
// This ivar should only be accessed while _mutex is held.
BOOL _disposed;
}
@end
@implementation RACCompoundDisposable
#pragma mark Properties
- (BOOL)isDisposed {
pthread_mutex_lock(&_mutex);
BOOL disposed = _disposed;
pthread_mutex_unlock(&_mutex);
return disposed;
}
#pragma mark Lifecycle
+ (instancetype)compoundDisposable {
return [[self alloc] initWithDisposables:nil];
}
+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables {
return [[self alloc] initWithDisposables:disposables];
}
- (instancetype)init {
self = [super init];
const int result __attribute__((unused)) = pthread_mutex_init(&_mutex, NULL);
NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result);
return self;
}
- (instancetype)initWithDisposables:(NSArray *)otherDisposables {
self = [self init];
#if RACCompoundDisposableInlineCount
[otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) {
self->_inlineDisposables[index] = disposable;
// Stop after this iteration if we've reached the end of the inlined
// array.
if (index == RACCompoundDisposableInlineCount - 1) *stop = YES;
}];
#endif
if (otherDisposables.count > RACCompoundDisposableInlineCount) {
_disposables = RACCreateDisposablesArray();
CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount);
CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range);
}
return self;
}
- (instancetype)initWithBlock:(void (^)(void))block {
RACDisposable *disposable = [RACDisposable disposableWithBlock:block];
return [self initWithDisposables:@[ disposable ]];
}
- (void)dealloc {
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
_inlineDisposables[i] = nil;
}
#endif
if (_disposables != NULL) {
CFRelease(_disposables);
_disposables = NULL;
}
const int result __attribute__((unused)) = pthread_mutex_destroy(&_mutex);
NSCAssert(0 == result, @"Failed to destroy mutex with error %d.", result);
}
#pragma mark Addition and Removal
- (void)addDisposable:(RACDisposable *)disposable {
NSCParameterAssert(disposable != self);
if (disposable == nil || disposable.disposed) return;
BOOL shouldDispose = NO;
pthread_mutex_lock(&_mutex);
{
if (_disposed) {
shouldDispose = YES;
} else {
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
if (_inlineDisposables[i] == nil) {
_inlineDisposables[i] = disposable;
goto foundSlot;
}
}
#endif
if (_disposables == NULL) _disposables = RACCreateDisposablesArray();
CFArrayAppendValue(_disposables, (__bridge void *)disposable);
if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) {
RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
}
#if RACCompoundDisposableInlineCount
foundSlot:;
#endif
}
}
pthread_mutex_unlock(&_mutex);
// Performed outside of the lock in case the compound disposable is used
// recursively.
if (shouldDispose) [disposable dispose];
}
- (void)removeDisposable:(RACDisposable *)disposable {
if (disposable == nil) return;
pthread_mutex_lock(&_mutex);
{
if (!_disposed) {
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil;
}
#endif
if (_disposables != NULL) {
CFIndex count = CFArrayGetCount(_disposables);
for (CFIndex i = count - 1; i >= 0; i--) {
const void *item = CFArrayGetValueAtIndex(_disposables, i);
if (item == (__bridge void *)disposable) {
CFArrayRemoveValueAtIndex(_disposables, i);
}
}
if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) {
RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
}
}
}
}
pthread_mutex_unlock(&_mutex);
}
#pragma mark RACDisposable
static void disposeEach(const void *value, void *context) {
RACDisposable *disposable = (__bridge id)value;
[disposable dispose];
}
- (void)dispose {
#if RACCompoundDisposableInlineCount
RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
#endif
CFArrayRef remainingDisposables = NULL;
pthread_mutex_lock(&_mutex);
{
_disposed = YES;
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
inlineCopy[i] = _inlineDisposables[i];
_inlineDisposables[i] = nil;
}
#endif
remainingDisposables = _disposables;
_disposables = NULL;
}
pthread_mutex_unlock(&_mutex);
#if RACCompoundDisposableInlineCount
// Dispose outside of the lock in case the compound disposable is used
// recursively.
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
[inlineCopy[i] dispose];
}
#endif
if (remainingDisposables == NULL) return;
CFIndex count = CFArrayGetCount(remainingDisposables);
CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
CFRelease(remainingDisposables);
}
@end

View File

@@ -0,0 +1,4 @@
provider RACCompoundDisposable {
probe added(char *compoundDisposable, char *disposable, long newTotal);
probe removed(char *compoundDisposable, char *disposable, long newTotal);
};

View File

@@ -0,0 +1,32 @@
//
// RACDelegateProxy.h
// ReactiveObjC
//
// Created by Cody Krieger on 5/19/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
// A private delegate object suitable for using
// -rac_signalForSelector:fromProtocol: upon.
@interface RACDelegateProxy : NSObject
// The delegate to which messages should be forwarded if not handled by
// any -signalForSelector: applications.
@property (nonatomic, unsafe_unretained) id rac_proxiedDelegate;
// Creates a delegate proxy capable of responding to selectors from `protocol`.
- (instancetype)initWithProtocol:(Protocol *)protocol;
// Calls -rac_signalForSelector:fromProtocol: using the `protocol` specified
// during initialization.
- (RACSignal *)signalForSelector:(SEL)selector;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,75 @@
//
// RACDelegateProxy.m
// ReactiveObjC
//
// Created by Cody Krieger on 5/19/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACDelegateProxy.h"
#import "NSObject+RACSelectorSignal.h"
#import <objc/runtime.h>
@interface RACDelegateProxy () {
// Declared as an ivar to avoid method naming conflicts.
Protocol *_protocol;
}
@end
@implementation RACDelegateProxy
#pragma mark Lifecycle
- (instancetype)initWithProtocol:(Protocol *)protocol {
NSCParameterAssert(protocol != NULL);
self = [super init];
class_addProtocol(self.class, protocol);
_protocol = protocol;
return self;
}
#pragma mark API
- (RACSignal *)signalForSelector:(SEL)selector {
return [self rac_signalForSelector:selector fromProtocol:_protocol];
}
#pragma mark NSObject
- (BOOL)isProxy {
return YES;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.rac_proxiedDelegate];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
// Look for the selector as an optional instance method.
struct objc_method_description methodDescription = protocol_getMethodDescription(_protocol, selector, NO, YES);
if (methodDescription.name == NULL) {
// Then fall back to looking for a required instance
// method.
methodDescription = protocol_getMethodDescription(_protocol, selector, YES, YES);
if (methodDescription.name == NULL) return [super methodSignatureForSelector:selector];
}
return [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
}
- (BOOL)respondsToSelector:(SEL)selector {
// Add the delegate to the autorelease pool, so it doesn't get deallocated
// between this method call and -forwardInvocation:.
__autoreleasing id delegate = self.rac_proxiedDelegate;
if ([delegate respondsToSelector:selector]) return YES;
return [super respondsToSelector:selector];
}
@end

View File

@@ -0,0 +1,39 @@
//
// RACDisposable.h
// ReactiveObjC
//
// Created by Josh Abernathy on 3/16/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class RACScopedDisposable;
NS_ASSUME_NONNULL_BEGIN
/// A disposable encapsulates the work necessary to tear down and cleanup a
/// subscription.
@interface RACDisposable : NSObject
/// Whether the receiver has been disposed.
///
/// Use of this property is discouraged, since it may be set to `YES`
/// concurrently at any time.
///
/// This property is not KVO-compliant.
@property (atomic, assign, getter = isDisposed, readonly) BOOL disposed;
+ (instancetype)disposableWithBlock:(void (^)(void))block;
/// Performs the disposal work. Can be called multiple times, though subsequent
/// calls won't do anything.
- (void)dispose;
/// Returns a new disposable which will dispose of this disposable when it gets
/// dealloc'd.
- (RACScopedDisposable *)asScopedDisposable;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,90 @@
//
// RACDisposable.m
// ReactiveObjC
//
// Created by Josh Abernathy on 3/16/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACDisposable.h"
#import "RACScopedDisposable.h"
#import <libkern/OSAtomic.h>
@interface RACDisposable () {
// A copied block of type void (^)(void) containing the logic for disposal,
// a pointer to `self` if no logic should be performed upon disposal, or
// NULL if the receiver is already disposed.
//
// This should only be used atomically.
void * volatile _disposeBlock;
}
@end
@implementation RACDisposable
#pragma mark Properties
- (BOOL)isDisposed {
return _disposeBlock == NULL;
}
#pragma mark Lifecycle
- (instancetype)init {
self = [super init];
_disposeBlock = (__bridge void *)self;
OSMemoryBarrier();
return self;
}
- (instancetype)initWithBlock:(void (^)(void))block {
NSCParameterAssert(block != nil);
self = [super init];
_disposeBlock = (void *)CFBridgingRetain([block copy]);
OSMemoryBarrier();
return self;
}
+ (instancetype)disposableWithBlock:(void (^)(void))block {
return [(RACDisposable *)[self alloc] initWithBlock:block];
}
- (void)dealloc {
if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return;
CFRelease(_disposeBlock);
_disposeBlock = NULL;
}
#pragma mark Disposal
- (void)dispose {
void (^disposeBlock)(void) = NULL;
while (YES) {
void *blockPtr = _disposeBlock;
if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
if (blockPtr != (__bridge void *)self) {
disposeBlock = CFBridgingRelease(blockPtr);
}
break;
}
}
if (disposeBlock != nil) disposeBlock();
}
#pragma mark Scoped Disposables
- (RACScopedDisposable *)asScopedDisposable {
return [RACScopedDisposable scopedDisposableWithDisposable:self];
}
@end

View File

@@ -0,0 +1,20 @@
//
// RACDynamicSequence.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "RACSequence.h"
// Private class that implements a sequence dynamically using blocks.
@interface RACDynamicSequence : RACSequence
// Returns a sequence which evaluates `dependencyBlock` only once, the first
// time either `headBlock` or `tailBlock` is evaluated. The result of
// `dependencyBlock` will be passed into `headBlock` and `tailBlock` when
// invoked.
+ (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock;
@end

View File

@@ -0,0 +1,197 @@
//
// 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

View File

@@ -0,0 +1,17 @@
//
// RACDynamicSignal.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSignal.h"
// A private `RACSignal` subclasses that implements its subscription behavior
// using a block.
@interface RACDynamicSignal : RACSignal
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;
@end

View File

@@ -0,0 +1,54 @@
//
// RACDynamicSignal.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACDynamicSignal.h"
#import <ReactiveObjC/RACEXTScope.h>
#import "RACCompoundDisposable.h"
#import "RACPassthroughSubscriber.h"
#import "RACScheduler+Private.h"
#import "RACSubscriber.h"
#import <libkern/OSAtomic.h>
@interface RACDynamicSignal ()
// The block to invoke for each subscriber.
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);
@end
@implementation RACDynamicSignal
#pragma mark Lifecycle
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
#pragma mark Managing Subscribers
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
@end

View File

@@ -0,0 +1,14 @@
//
// RACEagerSequence.h
// ReactiveObjC
//
// Created by Uri Baghin on 02/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACArraySequence.h"
// Private class that implements an eager sequence.
@interface RACEagerSequence : RACArraySequence
@end

View File

@@ -0,0 +1,66 @@
//
// RACEagerSequence.m
// ReactiveObjC
//
// Created by Uri Baghin on 02/01/2013.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACEagerSequence.h"
#import "NSObject+RACDescription.h"
#import "RACArraySequence.h"
@implementation RACEagerSequence
#pragma mark RACStream
+ (RACSequence *)return:(id)value {
return [[self sequenceWithArray:@[ value ] offset:0] setNameWithFormat:@"+return: %@", RACDescription(value)];
}
- (RACSequence *)bind:(RACSequenceBindBlock (^)(void))block {
NSCParameterAssert(block != nil);
RACStreamBindBlock bindBlock = block();
NSArray *currentArray = self.array;
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count];
for (id value in currentArray) {
BOOL stop = NO;
RACSequence *boundValue = (id)bindBlock(value, &stop);
if (boundValue == nil) break;
for (id x in boundValue) {
[resultArray addObject:x];
}
if (stop) break;
}
return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name];
}
- (RACSequence *)concat:(RACSequence *)sequence {
NSCParameterAssert(sequence != nil);
NSCParameterAssert([sequence isKindOfClass:RACSequence.class]);
NSArray *array = [self.array arrayByAddingObjectsFromArray:sequence.array];
return [[self.class sequenceWithArray:array offset:0] setNameWithFormat:@"[%@] -concat: %@", self.name, sequence];
}
#pragma mark Extended methods
- (RACSequence *)eagerSequence {
return self;
}
- (RACSequence *)lazySequence {
return [RACArraySequence sequenceWithArray:self.array offset:0];
}
- (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *rest))reduce {
return [super foldRightWithStart:start reduce:^(id first, RACSequence *rest) {
return reduce(first, rest.eagerSequence);
}];
}
@end

View File

@@ -0,0 +1,16 @@
//
// RACEmptySequence.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "RACSequence.h"
// Private class representing an empty sequence.
@interface RACEmptySequence : RACSequence
+ (RACEmptySequence *)empty;
@end

View File

@@ -0,0 +1,71 @@
//
// RACEmptySequence.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import "RACEmptySequence.h"
@implementation RACEmptySequence
#pragma mark Lifecycle
+ (instancetype)empty {
static id singleton;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
singleton = [[self alloc] init];
});
return singleton;
}
#pragma mark RACSequence
- (id)head {
return nil;
}
- (RACSequence *)tail {
return nil;
}
- (RACSequence *)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence {
return passthroughSequence ?: self;
}
#pragma mark NSCoding
- (Class)classForCoder {
// Empty sequences should be encoded as themselves, not array sequences.
return self.class;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
// Return the singleton.
return self.class.empty;
}
- (void)encodeWithCoder:(NSCoder *)coder {
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p>{ name = %@ }", self.class, self, self.name];
}
- (NSUInteger)hash {
// This hash isn't ideal, but it's better than -[RACSequence hash], which
// would just be zero because we have no head.
return (NSUInteger)(__bridge void *)self;
}
- (BOOL)isEqual:(RACSequence *)seq {
return (self == seq);
}
@end

View File

@@ -0,0 +1,17 @@
//
// RACEmptySignal.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSignal.h"
// A private `RACSignal` subclasses that synchronously sends completed to any
// subscribers.
@interface RACEmptySignal<__covariant ValueType> : RACSignal<ValueType>
+ (RACSignal<ValueType> *)empty;
@end

View File

@@ -0,0 +1,62 @@
//
// RACEmptySignal.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACEmptySignal.h"
#import "RACScheduler+Private.h"
#import "RACSubscriber.h"
@implementation RACEmptySignal
#pragma mark Properties
// Only allow this signal's name to be customized in DEBUG, since it's
// a singleton in release builds (see +empty).
- (void)setName:(NSString *)name {
#ifdef DEBUG
[super setName:name];
#endif
}
- (NSString *)name {
#ifdef DEBUG
return super.name;
#else
return @"+empty";
#endif
}
#pragma mark Lifecycle
+ (RACSignal *)empty {
#ifdef DEBUG
// Create multiple instances of this class in DEBUG so users can set custom
// names on each.
return [[[self alloc] init] setNameWithFormat:@"+empty"];
#else
static id singleton;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
singleton = [[self alloc] init];
});
return singleton;
#endif
}
#pragma mark Subscription
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
return [RACScheduler.subscriptionScheduler schedule:^{
[subscriber sendCompleted];
}];
}
@end

View File

@@ -0,0 +1,17 @@
//
// RACErrorSignal.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSignal.h"
// A private `RACSignal` subclass that synchronously sends an error to any
// subscriber.
@interface RACErrorSignal : RACSignal
+ (RACSignal *)error:(NSError *)error;
@end

View File

@@ -0,0 +1,47 @@
//
// RACErrorSignal.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACErrorSignal.h"
#import "RACScheduler+Private.h"
#import "RACSubscriber.h"
@interface RACErrorSignal ()
// The error to send upon subscription.
@property (nonatomic, strong, readonly) NSError *error;
@end
@implementation RACErrorSignal
#pragma mark Lifecycle
+ (RACSignal *)error:(NSError *)error {
RACErrorSignal *signal = [[self alloc] init];
signal->_error = error;
#ifdef DEBUG
[signal setNameWithFormat:@"+error: %@", error];
#else
signal.name = @"+error:";
#endif
return signal;
}
#pragma mark Subscription
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
return [RACScheduler.subscriptionScheduler schedule:^{
[subscriber sendError:self.error];
}];
}
@end

View File

@@ -0,0 +1,55 @@
//
// RACEvent.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-01-07.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Describes the type of a RACEvent.
///
/// RACEventTypeCompleted - A `completed` event.
/// RACEventTypeError - An `error` event.
/// RACEventTypeNext - A `next` event.
typedef NS_ENUM(NSUInteger, RACEventType) {
RACEventTypeCompleted,
RACEventTypeError,
RACEventTypeNext
};
/// Represents an event sent by a RACSignal.
///
/// This corresponds to the `Notification` class in Rx.
@interface RACEvent<__covariant ValueType> : NSObject <NSCopying>
/// Returns a singleton RACEvent representing the `completed` event.
+ (RACEvent<ValueType> *)completedEvent;
/// Returns a new event of type RACEventTypeError, containing the given error.
+ (RACEvent<ValueType> *)eventWithError:(nullable NSError *)error;
/// Returns a new event of type RACEventTypeNext, containing the given value.
+ (RACEvent<ValueType> *)eventWithValue:(nullable ValueType)value;
/// The type of event represented by the receiver.
@property (nonatomic, assign, readonly) RACEventType eventType;
/// Returns whether the receiver is of type RACEventTypeCompleted or
/// RACEventTypeError.
@property (nonatomic, getter = isFinished, assign, readonly) BOOL finished;
/// The error associated with an event of type RACEventTypeError. This will be
/// nil for all other event types.
@property (nonatomic, strong, readonly, nullable) NSError *error;
/// The value associated with an event of type RACEventTypeNext. This will be
/// nil for all other event types.
@property (nonatomic, strong, readonly, nullable) ValueType value;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,112 @@
//
// RACEvent.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-01-07.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACEvent.h"
@interface RACEvent ()
// An object associated with this event. This will be used for the error and
// value properties.
@property (nonatomic, strong, readonly) id object;
// Initializes the receiver with the given type and object.
- (instancetype)initWithEventType:(RACEventType)type object:(id)object;
@end
@implementation RACEvent
#pragma mark Properties
- (BOOL)isFinished {
return self.eventType == RACEventTypeCompleted || self.eventType == RACEventTypeError;
}
- (NSError *)error {
return (self.eventType == RACEventTypeError ? self.object : nil);
}
- (id)value {
return (self.eventType == RACEventTypeNext ? self.object : nil);
}
#pragma mark Lifecycle
+ (instancetype)completedEvent {
static dispatch_once_t pred;
static id singleton;
dispatch_once(&pred, ^{
singleton = [[self alloc] initWithEventType:RACEventTypeCompleted object:nil];
});
return singleton;
}
+ (instancetype)eventWithError:(NSError *)error {
return [[self alloc] initWithEventType:RACEventTypeError object:error];
}
+ (instancetype)eventWithValue:(id)value {
return [[self alloc] initWithEventType:RACEventTypeNext object:value];
}
- (instancetype)initWithEventType:(RACEventType)type object:(id)object {
self = [super init];
_eventType = type;
_object = object;
return self;
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#pragma mark NSObject
- (NSString *)description {
NSString *eventDescription = nil;
switch (self.eventType) {
case RACEventTypeCompleted:
eventDescription = @"completed";
break;
case RACEventTypeError:
eventDescription = [NSString stringWithFormat:@"error = %@", self.object];
break;
case RACEventTypeNext:
eventDescription = [NSString stringWithFormat:@"next = %@", self.object];
break;
default:
NSCAssert(NO, @"Unrecognized event type: %i", (int)self.eventType);
}
return [NSString stringWithFormat:@"<%@: %p>{ %@ }", self.class, self, eventDescription];
}
- (NSUInteger)hash {
return self.eventType ^ [self.object hash];
}
- (BOOL)isEqual:(id)event {
if (event == self) return YES;
if (![event isKindOfClass:RACEvent.class]) return NO;
if (self.eventType != [event eventType]) return NO;
// Catches the nil case too.
return self.object == [event object] || [self.object isEqual:[event object]];
}
@end

View File

@@ -0,0 +1,23 @@
//
// RACGroupedSignal.h
// ReactiveObjC
//
// Created by Josh Abernathy on 5/2/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACSubject.h"
NS_ASSUME_NONNULL_BEGIN
/// A grouped signal is used by -[RACSignal groupBy:transform:].
@interface RACGroupedSignal : RACSubject
/// The key shared by the group.
@property (nonatomic, readonly, copy) id<NSCopying> key;
+ (instancetype)signalWithKey:(id<NSCopying>)key;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,25 @@
//
// RACGroupedSignal.m
// ReactiveObjC
//
// Created by Josh Abernathy on 5/2/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACGroupedSignal.h"
@interface RACGroupedSignal ()
@property (nonatomic, copy) id<NSCopying> key;
@end
@implementation RACGroupedSignal
#pragma mark API
+ (instancetype)signalWithKey:(id<NSCopying>)key {
RACGroupedSignal *subject = [self subject];
subject.key = key;
return subject;
}
@end

View File

@@ -0,0 +1,14 @@
//
// RACImmediateScheduler.h
// ReactiveObjC
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACScheduler.h"
// A private scheduler which immediately executes its scheduled blocks.
@interface RACImmediateScheduler : RACScheduler
@end

View File

@@ -0,0 +1,54 @@
//
// RACImmediateScheduler.m
// ReactiveObjC
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACImmediateScheduler.h"
#import "RACScheduler+Private.h"
@implementation RACImmediateScheduler
#pragma mark Lifecycle
- (instancetype)init {
return [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.immediateScheduler"];
}
#pragma mark RACScheduler
- (RACDisposable *)schedule:(void (^)(void))block {
NSCParameterAssert(block != NULL);
block();
return nil;
}
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
NSCParameterAssert(date != nil);
NSCParameterAssert(block != NULL);
[NSThread sleepUntilDate:date];
block();
return nil;
}
- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
NSCAssert(NO, @"+[RACScheduler immediateScheduler] does not support %@.", NSStringFromSelector(_cmd));
return nil;
}
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock {
for (__block NSUInteger remaining = 1; remaining > 0; remaining--) {
recursiveBlock(^{
remaining++;
});
}
return nil;
}
@end

View File

@@ -0,0 +1,16 @@
//
// RACIndexSetSequence.h
// ReactiveObjC
//
// Created by Sergey Gavrilyuk on 12/18/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSequence.h"
// Private class that adapts an array to the RACSequence interface.
@interface RACIndexSetSequence : RACSequence
+ (RACSequence *)sequenceWithIndexSet:(NSIndexSet *)indexSet;
@end

View File

@@ -0,0 +1,111 @@
//
// RACIndexSetSequence.m
// ReactiveObjC
//
// Created by Sergey Gavrilyuk on 12/18/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACIndexSetSequence.h"
@interface RACIndexSetSequence ()
// A buffer holding the `NSUInteger` values to enumerate over.
//
// This is mostly used for memory management. Most access should go through
// `indexes` instead.
@property (nonatomic, strong, readonly) NSData *data;
// The indexes that this sequence should enumerate.
@property (nonatomic, readonly) const NSUInteger *indexes;
// The number of indexes to enumerate.
@property (nonatomic, readonly) NSUInteger count;
@end
@implementation RACIndexSetSequence
#pragma mark Lifecycle
+ (RACSequence *)sequenceWithIndexSet:(NSIndexSet *)indexSet {
NSUInteger count = indexSet.count;
if (count == 0) return self.empty;
NSUInteger sizeInBytes = sizeof(NSUInteger) * count;
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:sizeInBytes];
[indexSet getIndexes:data.mutableBytes maxCount:count inIndexRange:NULL];
RACIndexSetSequence *seq = [[self alloc] init];
seq->_data = data;
seq->_indexes = data.bytes;
seq->_count = count;
return seq;
}
+ (instancetype)sequenceWithIndexSetSequence:(RACIndexSetSequence *)indexSetSequence offset:(NSUInteger)offset {
NSCParameterAssert(offset < indexSetSequence.count);
RACIndexSetSequence *seq = [[self alloc] init];
seq->_data = indexSetSequence.data;
seq->_indexes = indexSetSequence.indexes + offset;
seq->_count = indexSetSequence.count - offset;
return seq;
}
#pragma mark RACSequence
- (id)head {
return @(self.indexes[0]);
}
- (RACSequence *)tail {
if (self.count <= 1) return [RACSequence empty];
return [self.class sequenceWithIndexSetSequence:self offset:1];
}
#pragma mark NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len {
NSCParameterAssert(len > 0);
if (state->state >= self.count) {
// Enumeration has completed.
return 0;
}
if (state->state == 0) {
// Enumeration begun, mark the mutation flag.
state->mutationsPtr = state->extra;
}
state->itemsPtr = stackbuf;
unsigned long index = 0;
while (index < MIN(self.count - state->state, len)) {
stackbuf[index] = @(self.indexes[index + state->state]);
++index;
}
state->state += index;
return index;
}
#pragma mark NSObject
- (NSString *)description {
NSMutableString *indexesStr = [NSMutableString string];
for (unsigned int i = 0; i < self.count; ++i) {
if (i > 0) [indexesStr appendString:@", "];
[indexesStr appendFormat:@"%lu", (unsigned long)self.indexes[i]];
}
return [NSString stringWithFormat:@"<%@: %p>{ name = %@, indexes = %@ }", self.class, self, self.name, indexesStr];
}
@end

View File

@@ -0,0 +1,107 @@
//
// RACKVOChannel.h
// ReactiveObjC
//
// Created by Uri Baghin on 27/12/2012.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACChannel.h"
#import <ReactiveObjC/RACEXTKeyPathCoding.h>
#import "RACmetamacros.h"
/// Creates a RACKVOChannel to the given key path. When the targeted object
/// deallocates, the channel will complete.
///
/// If RACChannelTo() is used as an expression, it returns a RACChannelTerminal that
/// can be used to watch the specified property for changes, and set new values
/// for it. The terminal will start with the property's current value upon
/// subscription.
///
/// If RACChannelTo() is used on the left-hand side of an assignment, there must a
/// RACChannelTerminal on the right-hand side of the assignment. The two will be
/// subscribed to one another: the property's value is immediately set to the
/// value of the channel terminal on the right-hand side, and subsequent changes
/// to either terminal will be reflected on the other.
///
/// There are two different versions of this macro:
///
/// - RACChannelTo(TARGET, KEYPATH, NILVALUE) will create a channel to the `KEYPATH`
/// of `TARGET`. If the terminal is ever sent a `nil` value, the property will
/// be set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object
/// properties, but an NSValue should be used for primitive properties, to
/// avoid an exception if `nil` is sent (which might occur if an intermediate
/// object is set to `nil`).
/// - RACChannelTo(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to
/// `nil`.
///
/// Examples
///
/// RACChannelTerminal *integerChannel = RACChannelTo(self, integerProperty, @42);
///
/// // Sets self.integerProperty to 5.
/// [integerChannel sendNext:@5];
///
/// // Logs the current value of self.integerProperty, and all future changes.
/// [integerChannel subscribeNext:^(id value) {
/// NSLog(@"value: %@", value);
/// }];
///
/// // Binds properties to each other, taking the initial value from the right
/// side.
/// RACChannelTo(view, objectProperty) = RACChannelTo(model, objectProperty);
/// RACChannelTo(view, integerProperty, @2) = RACChannelTo(model, integerProperty, @10);
#define RACChannelTo(TARGET, ...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(RACChannelTo_(TARGET, __VA_ARGS__, nil)) \
(RACChannelTo_(TARGET, __VA_ARGS__))
/// Do not use this directly. Use the RACChannelTo macro above.
#define RACChannelTo_(TARGET, KEYPATH, NILVALUE) \
[[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)]
NS_ASSUME_NONNULL_BEGIN
/// A RACChannel that observes a KVO-compliant key path for changes.
@interface RACKVOChannel<ValueType> : RACChannel<ValueType>
/// Initializes a channel that will observe the given object and key path.
///
/// The current value of the key path, and future KVO notifications for the given
/// key path, will be sent to subscribers of the channel's `followingTerminal`.
/// Values sent to the `followingTerminal` will be set at the given key path using
/// key-value coding.
///
/// When the target object deallocates, the channel will complete. Signal errors
/// are considered undefined behavior.
///
/// This is the designated initializer for this class.
///
/// target - The object to bind to.
/// keyPath - The key path to observe and set the value of.
/// nilValue - The value to set at the key path whenever a `nil` value is
/// received. This may be nil when connecting to object properties, but
/// an NSValue should be used for primitive properties, to avoid an
/// exception if `nil` is received (which might occur if an intermediate
/// object is set to `nil`).
#if OS_OBJECT_HAVE_OBJC_SUPPORT
- (instancetype)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(nullable ValueType)nilValue;
#else
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
- (instancetype)initWithTarget:(NSObject *)target keyPath:(NSString *)keyPath nilValue:(nullable ValueType)nilValue;
#endif
- (instancetype)init __attribute__((unavailable("Use -initWithTarget:keyPath:nilValue: instead")));
@end
/// Methods needed for the convenience macro. Do not call explicitly.
@interface RACKVOChannel (RACChannelTo)
- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key;
- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,207 @@
//
// RACKVOChannel.m
// ReactiveObjC
//
// Created by Uri Baghin on 27/12/2012.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACKVOChannel.h"
#import <ReactiveObjC/RACEXTScope.h>
#import "NSObject+RACDeallocating.h"
#import "NSObject+RACKVOWrapper.h"
#import "NSString+RACKeyPathUtilities.h"
#import "RACChannel.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACSignal+Operations.h"
// Key for the array of RACKVOChannel's additional thread local
// data in the thread dictionary.
static NSString * const RACKVOChannelDataDictionaryKey = @"RACKVOChannelKey";
// Wrapper class for additional thread local data.
@interface RACKVOChannelData : NSObject
// The flag used to ignore updates the channel itself has triggered.
@property (nonatomic, assign) BOOL ignoreNextUpdate;
// A pointer to the owner of the data. Only use this for pointer comparison,
// never as an object reference.
@property (nonatomic, assign) void *owner;
+ (instancetype)dataForChannel:(RACKVOChannel *)channel;
@end
@interface RACKVOChannel ()
// The object whose key path the channel is wrapping.
@property (atomic, weak) NSObject *target;
// The key path the channel is wrapping.
@property (nonatomic, copy, readonly) NSString *keyPath;
// Returns the existing thread local data container or nil if none exists.
@property (nonatomic, strong, readonly) RACKVOChannelData *currentThreadData;
// Creates the thread local data container for the channel.
- (void)createCurrentThreadData;
// Destroy the thread local data container for the channel.
- (void)destroyCurrentThreadData;
@end
@implementation RACKVOChannel
#pragma mark Properties
- (RACKVOChannelData *)currentThreadData {
NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey];
for (RACKVOChannelData *data in dataArray) {
if (data.owner == (__bridge void *)self) return data;
}
return nil;
}
#pragma mark Lifecycle
- (instancetype)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue {
NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0);
NSObject *strongTarget = target;
self = [super init];
_target = target;
_keyPath = [keyPath copy];
[self.leadingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -leadingTerminal", target, keyPath, nilValue];
[self.followingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -followingTerminal", target, keyPath, nilValue];
if (strongTarget == nil) {
[self.leadingTerminal sendCompleted];
return self;
}
// Observe the key path on target for changes and forward the changes to the
// terminal.
//
// Intentionally capturing `self` strongly in the blocks below, so the
// channel object stays alive while observing.
RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
// If the change wasn't triggered by deallocation, only affects the last
// path component, and ignoreNextUpdate is set, then it was triggered by
// this channel and should not be forwarded.
if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) {
[self destroyCurrentThreadData];
return;
}
[self.leadingTerminal sendNext:value];
}];
NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent;
NSArray *keyPathComponents = keyPath.rac_keyPathComponents;
NSUInteger keyPathComponentsCount = keyPathComponents.count;
NSString *lastKeyPathComponent = keyPathComponents.lastObject;
// Update the value of the property with the values received.
[[self.leadingTerminal
finally:^{
[observationDisposable dispose];
}]
subscribeNext:^(id x) {
// Check the value of the second to last key path component. Since the
// channel can only update the value of a property on an object, and not
// update intermediate objects, it can only update the value of the whole
// key path if this object is not nil.
NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target);
if (object == nil) return;
// Set the ignoreNextUpdate flag before setting the value so this channel
// ignores the value in the subsequent -didChangeValueForKey: callback.
[self createCurrentThreadData];
self.currentThreadData.ignoreNextUpdate = YES;
[object setValue:x ?: nilValue forKey:lastKeyPathComponent];
} error:^(NSError *error) {
NSCAssert(NO, @"Received error in %@: %@", self, error);
// Log the error if we're running with assertions disabled.
NSLog(@"Received error in %@: %@", self, error);
}];
// Capture `self` weakly for the target's deallocation disposable, so we can
// freely deallocate if we complete before then.
@weakify(self);
[strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
@strongify(self);
[self.leadingTerminal sendCompleted];
self.target = nil;
}]];
return self;
}
- (void)createCurrentThreadData {
NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey];
if (dataArray == nil) {
dataArray = [NSMutableArray array];
NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey] = dataArray;
[dataArray addObject:[RACKVOChannelData dataForChannel:self]];
return;
}
for (RACKVOChannelData *data in dataArray) {
if (data.owner == (__bridge void *)self) return;
}
[dataArray addObject:[RACKVOChannelData dataForChannel:self]];
}
- (void)destroyCurrentThreadData {
NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey];
NSUInteger index = [dataArray indexOfObjectPassingTest:^ BOOL (RACKVOChannelData *data, NSUInteger idx, BOOL *stop) {
return data.owner == (__bridge void *)self;
}];
if (index != NSNotFound) [dataArray removeObjectAtIndex:index];
}
@end
@implementation RACKVOChannel (RACChannelTo)
- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key {
NSCParameterAssert(key != nil);
RACChannelTerminal *terminal = [self valueForKey:key];
NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key);
return terminal;
}
- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key {
NSCParameterAssert(otherTerminal != nil);
RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key];
[otherTerminal subscribe:selfTerminal];
[[selfTerminal skip:1] subscribe:otherTerminal];
}
@end
@implementation RACKVOChannelData
+ (instancetype)dataForChannel:(RACKVOChannel *)channel {
RACKVOChannelData *data = [[self alloc] init];
data->_owner = (__bridge void *)channel;
return data;
}
@end

View File

@@ -0,0 +1,34 @@
//
// RACKVOProxy.h
// ReactiveObjC
//
// Created by Richard Speyer on 4/10/14.
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/// A singleton that can act as a proxy between a KVO observation and a RAC
/// subscriber, in order to protect against KVO lifetime issues.
@interface RACKVOProxy : NSObject
/// Returns the singleton KVO proxy object.
+ (instancetype)sharedProxy;
/// Registers an observer with the proxy, such that when the proxy receives a
/// KVO change with the given context, it forwards it to the observer.
///
/// observer - True observer of the KVO change. Must not be nil.
/// context - Arbitrary context object used to differentiate multiple
/// observations of the same keypath. Must be unique, cannot be nil.
- (void)addObserver:(__weak NSObject *)observer forContext:(void *)context;
/// Removes an observer from the proxy. Parameters must match those passed to
/// addObserver:forContext:.
///
/// observer - True observer of the KVO change. Must not be nil.
/// context - Arbitrary context object used to differentiate multiple
/// observations of the same keypath. Must be unique, cannot be nil.
- (void)removeObserver:(NSObject *)observer forContext:(void *)context;
@end

View File

@@ -0,0 +1,69 @@
//
// RACKVOProxy.m
// ReactiveObjC
//
// Created by Richard Speyer on 4/10/14.
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
//
#import "RACKVOProxy.h"
@interface RACKVOProxy()
@property (strong, nonatomic, readonly) NSMapTable *trampolines;
@property (strong, nonatomic, readonly) dispatch_queue_t queue;
@end
@implementation RACKVOProxy
+ (instancetype)sharedProxy {
static RACKVOProxy *proxy;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
proxy = [[self alloc] init];
});
return proxy;
}
- (instancetype)init {
self = [super init];
_queue = dispatch_queue_create("org.reactivecocoa.ReactiveObjC.RACKVOProxy", DISPATCH_QUEUE_SERIAL);
_trampolines = [NSMapTable strongToWeakObjectsMapTable];
return self;
}
- (void)addObserver:(__weak NSObject *)observer forContext:(void *)context {
NSValue *valueContext = [NSValue valueWithPointer:context];
dispatch_sync(self.queue, ^{
[self.trampolines setObject:observer forKey:valueContext];
});
}
- (void)removeObserver:(NSObject *)observer forContext:(void *)context {
NSValue *valueContext = [NSValue valueWithPointer:context];
dispatch_sync(self.queue, ^{
[self.trampolines removeObjectForKey:valueContext];
});
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSValue *valueContext = [NSValue valueWithPointer:context];
__block NSObject *trueObserver;
dispatch_sync(self.queue, ^{
trueObserver = [self.trampolines objectForKey:valueContext];
});
if (trueObserver != nil) {
[trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
@end

View File

@@ -0,0 +1,31 @@
//
// RACKVOTrampoline.h
// ReactiveObjC
//
// Created by Josh Abernathy on 1/15/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "NSObject+RACKVOWrapper.h"
#import "RACDisposable.h"
// A private trampoline object that represents a KVO observation.
//
// Disposing of the trampoline will stop observation.
@interface RACKVOTrampoline : RACDisposable
// Initializes the receiver with the given parameters.
//
// target - The object whose key path should be observed. Cannot be nil.
// observer - The object that gets notified when the value at the key path
// changes. Can be nil.
// keyPath - The key path on `target` to observe. Cannot be nil.
// options - Any key value observing options to use in the observation.
// block - The block to call when the value at the observed key path changes.
// Cannot be nil.
//
// Returns the initialized object.
- (instancetype)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block;
@end

View File

@@ -0,0 +1,109 @@
//
// RACKVOTrampoline.m
// ReactiveObjC
//
// Created by Josh Abernathy on 1/15/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACKVOTrampoline.h"
#import "NSObject+RACDeallocating.h"
#import "RACCompoundDisposable.h"
#import "RACKVOProxy.h"
@interface RACKVOTrampoline ()
// The keypath which the trampoline is observing.
@property (nonatomic, readonly, copy) NSString *keyPath;
// These properties should only be manipulated while synchronized on the
// receiver.
@property (nonatomic, readonly, copy) RACKVOBlock block;
@property (nonatomic, readonly, unsafe_unretained) NSObject *unsafeTarget;
@property (nonatomic, readonly, weak) NSObject *weakTarget;
@property (nonatomic, readonly, weak) NSObject *observer;
@end
@implementation RACKVOTrampoline
#pragma mark Lifecycle
- (instancetype)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block {
NSCParameterAssert(keyPath != nil);
NSCParameterAssert(block != nil);
NSObject *strongTarget = target;
if (strongTarget == nil) return nil;
self = [super init];
_keyPath = [keyPath copy];
_block = [block copy];
_weakTarget = target;
_unsafeTarget = strongTarget;
_observer = observer;
[RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self];
[strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self];
[strongTarget.rac_deallocDisposable addDisposable:self];
[self.observer.rac_deallocDisposable addDisposable:self];
return self;
}
- (void)dealloc {
[self dispose];
}
#pragma mark Observation
- (void)dispose {
NSObject *target;
NSObject *observer;
@synchronized (self) {
_block = nil;
// The target should still exist at this point, because we still need to
// tear down its KVO observation. Therefore, we can use the unsafe
// reference (and need to, because the weak one will have been zeroed by
// now).
target = self.unsafeTarget;
observer = self.observer;
_unsafeTarget = nil;
_observer = nil;
}
[target.rac_deallocDisposable removeDisposable:self];
[observer.rac_deallocDisposable removeDisposable:self];
[target removeObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath context:(__bridge void *)self];
[RACKVOProxy.sharedProxy removeObserver:self forContext:(__bridge void *)self];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context != (__bridge void *)self) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
RACKVOBlock block;
id observer;
id target;
@synchronized (self) {
block = self.block;
observer = self.observer;
target = self.weakTarget;
}
if (block == nil || target == nil) return;
block(target, observer, change);
}
@end

View File

@@ -0,0 +1,17 @@
//
// RACMulticastConnection+Private.h
// ReactiveObjC
//
// Created by Josh Abernathy on 4/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACMulticastConnection.h"
@class RACSubject;
@interface RACMulticastConnection<__covariant ValueType> ()
- (instancetype)initWithSourceSignal:(RACSignal<ValueType> *)source subject:(RACSubject *)subject;
@end

View File

@@ -0,0 +1,53 @@
//
// RACMulticastConnection.h
// ReactiveObjC
//
// Created by Josh Abernathy on 4/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RACAnnotations.h"
@class RACDisposable;
@class RACSignal<__covariant ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// A multicast connection encapsulates the idea of sharing one subscription to a
/// signal to many subscribers. This is most often needed if the subscription to
/// the underlying signal involves side-effects or shouldn't be called more than
/// once.
///
/// The multicasted signal is only subscribed to when
/// -[RACMulticastConnection connect] is called. Until that happens, no values
/// will be sent on `signal`. See -[RACMulticastConnection autoconnect] for how
/// -[RACMulticastConnection connect] can be called automatically.
///
/// Note that you shouldn't create RACMulticastConnection manually. Instead use
/// -[RACSignal publish] or -[RACSignal multicast:].
@interface RACMulticastConnection<__covariant ValueType> : NSObject
/// The multicasted signal.
@property (nonatomic, strong, readonly) RACSignal<ValueType> *signal;
/// Connect to the underlying signal by subscribing to it. Calling this multiple
/// times does nothing but return the existing connection's disposable.
///
/// Returns the disposable for the subscription to the multicasted signal.
- (RACDisposable *)connect;
/// Connects to the underlying signal when the returned signal is first
/// subscribed to, and disposes of the subscription to the multicasted signal
/// when the returned signal has no subscribers.
///
/// If new subscribers show up after being disposed, they'll subscribe and then
/// be immediately disposed of. The returned signal will never re-connect to the
/// multicasted signal.
///
/// Returns the autoconnecting signal.
- (RACSignal<ValueType> *)autoconnect RAC_WARN_UNUSED_RESULT;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,84 @@
//
// RACMulticastConnection.m
// ReactiveObjC
//
// Created by Josh Abernathy on 4/11/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACMulticastConnection.h"
#import "RACMulticastConnection+Private.h"
#import "RACDisposable.h"
#import "RACSerialDisposable.h"
#import "RACSubject.h"
#import <libkern/OSAtomic.h>
@interface RACMulticastConnection () {
RACSubject *_signal;
// When connecting, a caller should attempt to atomically swap the value of this
// from `0` to `1`.
//
// If the swap is successful the caller is resposible for subscribing `_signal`
// to `sourceSignal` and storing the returned disposable in `serialDisposable`.
//
// If the swap is unsuccessful it means that `_sourceSignal` has already been
// connected and the caller has no action to take.
int32_t volatile _hasConnected;
}
@property (nonatomic, readonly, strong) RACSignal *sourceSignal;
@property (strong) RACSerialDisposable *serialDisposable;
@end
@implementation RACMulticastConnection
#pragma mark Lifecycle
- (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
NSCParameterAssert(source != nil);
NSCParameterAssert(subject != nil);
self = [super init];
_sourceSignal = source;
_serialDisposable = [[RACSerialDisposable alloc] init];
_signal = subject;
return self;
}
#pragma mark Connecting
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
- (RACSignal *)autoconnect {
__block volatile int32_t subscriberCount = 0;
return [[RACSignal
createSignal:^(id<RACSubscriber> subscriber) {
OSAtomicIncrement32Barrier(&subscriberCount);
RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
RACDisposable *connectionDisposable = [self connect];
return [RACDisposable disposableWithBlock:^{
[subscriptionDisposable dispose];
if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
[connectionDisposable dispose];
}
}];
}]
setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}
@end

View File

@@ -0,0 +1,29 @@
//
// RACPassthroughSubscriber.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-06-13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RACSubscriber.h"
@class RACCompoundDisposable;
@class RACSignal<__covariant ValueType>;
// A private subscriber that passes through all events to another subscriber
// while not disposed.
@interface RACPassthroughSubscriber : NSObject <RACSubscriber>
// Initializes the receiver to pass through events until disposed.
//
// subscriber - The subscriber to forward events to. This must not be nil.
// signal - The signal that will be sending events to the receiver.
// disposable - When this disposable is disposed, no more events will be
// forwarded. This must not be nil.
//
// Returns an initialized passthrough subscriber.
- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;
@end

View File

@@ -0,0 +1,106 @@
//
// RACPassthroughSubscriber.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-06-13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACPassthroughSubscriber.h"
#import "RACCompoundDisposable.h"
#import "RACSignal.h"
#import "RACSignalProvider.h"
#if !defined(DTRACE_PROBES_DISABLED) || !DTRACE_PROBES_DISABLED
static const char *cleanedDTraceString(NSString *original) {
return [original stringByReplacingOccurrencesOfString:@"\\s+" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, original.length)].UTF8String;
}
static const char *cleanedSignalDescription(RACSignal *signal) {
NSString *desc = signal.description;
NSRange range = [desc rangeOfString:@" name:"];
if (range.location != NSNotFound) {
desc = [desc stringByReplacingCharactersInRange:range withString:@""];
}
return cleanedDTraceString(desc);
}
#endif
@interface RACPassthroughSubscriber ()
// The subscriber to which events should be forwarded.
@property (nonatomic, strong, readonly) id<RACSubscriber> innerSubscriber;
// The signal sending events to this subscriber.
//
// This property isn't `weak` because it's only used for DTrace probes, so
// a zeroing weak reference would incur an unnecessary performance penalty in
// normal usage.
@property (nonatomic, unsafe_unretained, readonly) RACSignal *signal;
// A disposable representing the subscription. When disposed, no further events
// should be sent to the `innerSubscriber`.
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
@end
@implementation RACPassthroughSubscriber
#pragma mark Lifecycle
- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
NSCParameterAssert(subscriber != nil);
self = [super init];
_innerSubscriber = subscriber;
_signal = signal;
_disposable = disposable;
[self.innerSubscriber didSubscribeWithDisposable:self.disposable];
return self;
}
#pragma mark RACSubscriber
- (void)sendNext:(id)value {
if (self.disposable.disposed) return;
if (RACSIGNAL_NEXT_ENABLED()) {
RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
}
[self.innerSubscriber sendNext:value];
}
- (void)sendError:(NSError *)error {
if (self.disposable.disposed) return;
if (RACSIGNAL_ERROR_ENABLED()) {
RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description));
}
[self.innerSubscriber sendError:error];
}
- (void)sendCompleted {
if (self.disposable.disposed) return;
if (RACSIGNAL_COMPLETED_ENABLED()) {
RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description));
}
[self.innerSubscriber sendCompleted];
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
if (disposable != self.disposable) {
[self.disposable addDisposable:disposable];
}
}
@end

View File

@@ -0,0 +1,44 @@
//
// RACQueueScheduler+Subclass.h
// ReactiveObjC
//
// Created by Josh Abernathy on 6/6/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACQueueScheduler.h"
#import "RACScheduler+Subclass.h"
NS_ASSUME_NONNULL_BEGIN
/// An interface for use by GCD queue-based subclasses.
///
/// See RACScheduler+Subclass.h for subclassing notes.
@interface RACQueueScheduler ()
/// The queue on which blocks are enqueued.
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
#else
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
@property (nonatomic, assign, readonly) dispatch_queue_t queue;
#endif
/// Initializes the receiver with the name of the scheduler and the queue which
/// the scheduler should use.
///
/// name - The name of the scheduler. If nil, a default name will be used.
/// queue - The queue upon which the receiver should enqueue scheduled blocks.
/// This argument must not be NULL.
///
/// Returns the initialized object.
- (instancetype)initWithName:(nullable NSString *)name queue:(dispatch_queue_t)queue;
/// Converts a date into a GCD time using dispatch_walltime().
///
/// date - The date to convert. This must not be nil.
+ (dispatch_time_t)wallTimeWithDate:(NSDate *)date;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,22 @@
//
// RACQueueScheduler.h
// ReactiveObjC
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACScheduler.h"
NS_ASSUME_NONNULL_BEGIN
/// An abstract scheduler which asynchronously enqueues all its work to a Grand
/// Central Dispatch queue.
///
/// Because RACQueueScheduler is abstract, it should not be instantiated
/// directly. Create a subclass using the `RACQueueScheduler+Subclass.h`
/// interface and use that instead.
@interface RACQueueScheduler : RACScheduler
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,106 @@
//
// RACQueueScheduler.m
// ReactiveObjC
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACQueueScheduler.h"
#import "RACDisposable.h"
#import "RACQueueScheduler+Subclass.h"
#import "RACScheduler+Private.h"
@implementation RACQueueScheduler
#pragma mark Lifecycle
- (instancetype)initWithName:(NSString *)name queue:(dispatch_queue_t)queue {
NSCParameterAssert(queue != NULL);
self = [super initWithName:name];
_queue = queue;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(_queue);
#endif
return self;
}
#if !OS_OBJECT_USE_OBJC
- (void)dealloc {
if (_queue != NULL) {
dispatch_release(_queue);
_queue = NULL;
}
}
#endif
#pragma mark Date Conversions
+ (dispatch_time_t)wallTimeWithDate:(NSDate *)date {
NSCParameterAssert(date != nil);
double seconds = 0;
double frac = modf(date.timeIntervalSince1970, &seconds);
struct timespec walltime = {
.tv_sec = (time_t)fmin(fmax(seconds, LONG_MIN), LONG_MAX),
.tv_nsec = (long)fmin(fmax(frac * NSEC_PER_SEC, LONG_MIN), LONG_MAX)
};
return dispatch_walltime(&walltime, 0);
}
#pragma mark RACScheduler
- (RACDisposable *)schedule:(void (^)(void))block {
NSCParameterAssert(block != NULL);
RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_async(self.queue, ^{
if (disposable.disposed) return;
[self performAsCurrentScheduler:block];
});
return disposable;
}
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block {
NSCParameterAssert(date != nil);
NSCParameterAssert(block != NULL);
RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{
if (disposable.disposed) return;
[self performAsCurrentScheduler:block];
});
return disposable;
}
- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block {
NSCParameterAssert(date != nil);
NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC);
NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC);
NSCParameterAssert(block != NULL);
uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC);
uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
return [RACDisposable disposableWithBlock:^{
dispatch_source_cancel(timer);
}];
}
@end

View File

@@ -0,0 +1,26 @@
//
// RACReplaySubject.h
// ReactiveObjC
//
// Created by Josh Abernathy on 3/14/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACSubject.h"
NS_ASSUME_NONNULL_BEGIN
extern const NSUInteger RACReplaySubjectUnlimitedCapacity;
/// A replay subject saves the values it is sent (up to its defined capacity)
/// and resends those to new subscribers. It will also replay an error or
/// completion.
@interface RACReplaySubject<ValueType> : RACSubject<ValueType>
/// Creates a new replay subject with the given capacity. A capacity of
/// RACReplaySubjectUnlimitedCapacity means values are never trimmed.
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,112 @@
//
// RACReplaySubject.m
// ReactiveObjC
//
// Created by Josh Abernathy on 3/14/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACReplaySubject.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACScheduler+Private.h"
#import "RACSubscriber.h"
#import "RACTuple.h"
const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax;
@interface RACReplaySubject ()
@property (nonatomic, assign, readonly) NSUInteger capacity;
// These properties should only be modified while synchronized on self.
@property (nonatomic, strong, readonly) NSMutableArray *valuesReceived;
@property (nonatomic, assign) BOOL hasCompleted;
@property (nonatomic, assign) BOOL hasError;
@property (nonatomic, strong) NSError *error;
@end
@implementation RACReplaySubject
#pragma mark Lifecycle
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {
return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];
}
- (instancetype)init {
return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity {
self = [super init];
_capacity = capacity;
_valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);
return self;
}
#pragma mark RACSignal
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
for (id value in self.valuesReceived) {
if (compoundDisposable.disposed) return;
[subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
}
if (compoundDisposable.disposed) return;
if (self.hasCompleted) {
[subscriber sendCompleted];
} else if (self.hasError) {
[subscriber sendError:self.error];
} else {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable];
}
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
#pragma mark RACSubscriber
- (void)sendNext:(id)value {
@synchronized (self) {
[self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
[super sendNext:value];
}
}
- (void)sendCompleted {
@synchronized (self) {
self.hasCompleted = YES;
[super sendCompleted];
}
}
- (void)sendError:(NSError *)e {
@synchronized (self) {
self.hasError = YES;
self.error = e;
[super sendError:e];
}
}
@end

View File

@@ -0,0 +1,17 @@
//
// RACReturnSignal.h
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSignal.h"
// A private `RACSignal` subclasses that synchronously sends a value to any
// subscribers, then completes.
@interface RACReturnSignal<__covariant ValueType> : RACSignal<ValueType>
+ (RACSignal<ValueType> *)return:(ValueType)value;
@end

View File

@@ -0,0 +1,90 @@
//
// RACReturnSignal.m
// ReactiveObjC
//
// Created by Justin Spahr-Summers on 2013-10-10.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACReturnSignal.h"
#import "RACScheduler+Private.h"
#import "RACSubscriber.h"
#import "RACUnit.h"
@interface RACReturnSignal ()
// The value to send upon subscription.
@property (nonatomic, strong, readonly) id value;
@end
@implementation RACReturnSignal
#pragma mark Properties
// Only allow this signal's name to be customized in DEBUG, since it's
// potentially a singleton in release builds (see +return:).
- (void)setName:(NSString *)name {
#ifdef DEBUG
[super setName:name];
#endif
}
- (NSString *)name {
#ifdef DEBUG
return super.name;
#else
return @"+return:";
#endif
}
#pragma mark Lifecycle
+ (RACSignal *)return:(id)value {
#ifndef DEBUG
// In release builds, use singletons for two very common cases.
if (value == RACUnit.defaultUnit) {
static RACReturnSignal *unitSingleton;
static dispatch_once_t unitPred;
dispatch_once(&unitPred, ^{
unitSingleton = [[self alloc] init];
unitSingleton->_value = RACUnit.defaultUnit;
});
return unitSingleton;
} else if (value == nil) {
static RACReturnSignal *nilSingleton;
static dispatch_once_t nilPred;
dispatch_once(&nilPred, ^{
nilSingleton = [[self alloc] init];
nilSingleton->_value = nil;
});
return nilSingleton;
}
#endif
RACReturnSignal *signal = [[self alloc] init];
signal->_value = value;
#ifdef DEBUG
[signal setNameWithFormat:@"+return: %@", value];
#endif
return signal;
}
#pragma mark Subscription
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
return [RACScheduler.subscriptionScheduler schedule:^{
[subscriber sendNext:self.value];
[subscriber sendCompleted];
}];
}
@end

Some files were not shown because too many files have changed in this diff Show More