Project import
diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..56cda91 --- /dev/null +++ b/LICENSE.md
@@ -0,0 +1,19 @@ +**Copyright (c) 2012 - 2014, GitHub, Inc.** +**All rights reserved.** + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md new file mode 100644 index 0000000..c919c15 --- /dev/null +++ b/README.md
@@ -0,0 +1,562 @@ +# ReactiveCocoa + +ReactiveCocoa (RAC) is an Objective-C framework for [Functional Reactive +Programming][]. It provides APIs for **composing and transforming streams of +values**. + +If you're already familiar with functional reactive programming or know the basic +premise of ReactiveCocoa, check out the [Documentation][] folder for a framework +overview and more in-depth information about how it all works in practice. + +## New to ReactiveCocoa? + +ReactiveCocoa is documented like crazy, and there's a wealth of introductory +material available to explain what RAC is and how you can use it. + +If you want to learn more, we recommend these resources, roughly in order: + + 1. [Introduction](#introduction) + 1. [When to use ReactiveCocoa](#when-to-use-reactivecocoa) + 1. [Framework Overview][] + 1. [Basic Operators][] + 1. [Header documentation](ReactiveCocoaFramework/ReactiveCocoa) + 1. Previously answered [Stack Overflow](https://github.com/ReactiveCocoa/ReactiveCocoa/wiki) + questions and [GitHub issues](https://github.com/ReactiveCocoa/ReactiveCocoa/issues?labels=question&state=closed) + 1. The rest of the [Documentation][] folder + 1. [Functional Reactive Programming on iOS](https://leanpub.com/iosfrp/) + (eBook) + +If you have any further questions, please feel free to [file an issue](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/new). + +## Introduction + +ReactiveCocoa is an implementation of [functional reactive +programming](http://blog.maybeapps.com/post/42894317939/input-and-output). +Rather than using mutable variables which are replaced and modified in-place, +RAC provides signals (represented by `RACSignal`) that capture present and +future values. + +By chaining, combining, and reacting to signals, software can be written +declaratively, without the need for code that continually observes and updates +values. + +For example, a text field can be bound to the latest time, even as it changes, +instead of using additional code that watches the clock and updates the +text field every second. It works much like KVO, but with blocks instead of +overriding `-observeValueForKeyPath:ofObject:change:context:`. + +Signals can also represent asynchronous operations, much like [futures and +promises][]. This greatly simplifies asynchronous software, including networking +code. + +One of the major advantages of RAC is that it provides a single, unified +approach to dealing with asynchronous behaviors, including delegate methods, +callback blocks, target-action mechanisms, notifications, and KVO. + +Here's a simple example: + +```objc +// When self.username changes, log the new name to the console. +// +// RACObserve(self, username) creates a new RACSignal that sends the current +// value of self.username, then the new value whenever it changes. +// -subscribeNext: will execute the block whenever the signal sends a value. +[RACObserve(self, username) subscribeNext:^(NSString *newName) { + NSLog(@"%@", newName); +}]; +``` + +But unlike KVO notifications, signals can be chained together and operated on: + +```objc +// Only log names that start with "j". +// +// -filter returns a new RACSignal that only sends a new value when its block +// returns YES. +[[RACObserve(self, username) + filter:^(NSString *newName) { + return [newName hasPrefix:@"j"]; + }] + subscribeNext:^(NSString *newName) { + NSLog(@"%@", newName); + }]; +``` + +Signals can also be used to derive state. Instead of observing properties and +setting other properties in response to the new values, RAC makes it possible to +express properties in terms of signals and operations: + +```objc +// Create a one-way binding so that self.createEnabled will be +// true whenever self.password and self.passwordConfirmation +// are equal. +// +// RAC() is a macro that makes the binding look nicer. +// +// +combineLatest:reduce: takes an array of signals, executes the block with the +// latest value from each signal whenever any of them changes, and returns a new +// RACSignal that sends the return value of that block as values. +RAC(self, createEnabled) = [RACSignal + combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] + reduce:^(NSString *password, NSString *passwordConfirm) { + return @([passwordConfirm isEqualToString:password]); + }]; +``` + +Signals can be built on any stream of values over time, not just KVO. For +example, they can also represent button presses: + +```objc +// Log a message whenever the button is pressed. +// +// RACCommand creates signals to represent UI actions. Each signal can +// represent a button press, for example, and have additional work associated +// with it. +// +// -rac_command is an addition to NSButton. The button will send itself on that +// command whenever it's pressed. +self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + NSLog(@"button was pressed!"); + return [RACSignal empty]; +}] +``` + +Or asynchronous network operations: + +```objc +// Hook up a "Log in" button to log in over the network. +// +// This block will be run whenever the login command is executed, starting +// the login process. +self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) { + // The hypothetical -logIn method returns a signal that sends a value when + // the network request finishes. + return [client logIn]; +}]; + +// -executionSignals returns a signal that includes the signals returned from +// the above block, one for each time the command is executed. +[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) { + // Log a message whenever we log in successfully. + [loginSignal subscribeCompleted:^ { + NSLog(@"Logged in successfully!"); + }]; +}]; + +// Execute the login command when the button is pressed. +self.loginButton.rac_command = self.loginCommand; +``` + +Signals can also represent timers, other UI events, or anything else that +changes over time. + +Using signals for asynchronous operations makes it possible to build up more +complex behavior by chaining and transforming those signals. Work can easily be +trigged after a group of operations completes: + +```objc +// Perform 2 network operations and log a message to the console when they are +// both completed. +// +// +merge: takes an array of signals and returns a new RACSignal that passes +// through the values of all of the signals and completes when all of the +// signals complete. +// +// -subscribeCompleted: will execute the block when the signal completes. +[[RACSignal + merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] + subscribeCompleted:^{ + NSLog(@"They're both done!"); + }]; +``` + +Signals can be chained to sequentially execute asynchronous operations, instead +of nesting callbacks with blocks. This is similar to how [futures and promises][] +are usually used: + +```objc +// Log in the user, then load any cached messages, then fetch the remaining +// messages from the server. After that's all done, log a message to the +// console. +// +// The hypothetical -logInUser methods returns a signal that completes after +// logging in. +// +// -flattenMap: will execute its block whenever the signal sends a value, and +// return a new RACSignal that merges all of the signals returned from the block +// into a single signal. +[[[[client + logInUser] + flattenMap:^(User *user) { + // Return a signal that loads cached messages for the user. + return [client loadCachedMessagesForUser:user]; + }] + flattenMap:^(NSArray *messages) { + // Return a signal that fetches any remaining messages. + return [client fetchMessagesAfterMessage:messages.lastObject]; + }] + subscribeNext:^(NSArray *newMessages) { + NSLog(@"New messages: %@", newMessages); + } completed:^{ + NSLog(@"Fetched all messages."); + }]; +``` + +RAC even makes it easy to bind to the result of an asynchronous operation: + +```objc +// Create a one-way binding so that self.imageView.image will be set the user's +// avatar as soon as it's downloaded. +// +// The hypothetical -fetchUserWithUsername: method returns a signal which sends +// the user. +// +// -deliverOn: creates new signals that will do their work on other queues. In +// this example, it's used to move work to a background queue and then back to the main thread. +// +// -map: calls its block with each user that's fetched and returns a new +// RACSignal that sends values returned from the block. +RAC(self.imageView, image) = [[[[client + fetchUserWithUsername:@"joshaber"] + deliverOn:[RACScheduler scheduler]] + map:^(User *user) { + // Download the avatar (this is done on a background queue). + return [[NSImage alloc] initWithContentsOfURL:user.avatarURL]; + }] + // Now the assignment will be done on the main thread. + deliverOn:RACScheduler.mainThreadScheduler]; +``` + +That demonstrates some of what RAC can do, but it doesn't demonstrate why RAC is +so powerful. It's hard to appreciate RAC from README-sized examples, but it +makes it possible to write code with less state, less boilerplate, better code +locality, and better expression of intent. + +For more sample code, check out [C-41][] or [GroceryList][], which are real iOS +apps written using ReactiveCocoa. Additional information about RAC can be found +in the [Documentation][] folder. + +## When to use ReactiveCocoa + +Upon first glance, ReactiveCocoa is very abstract, and it can be difficult to +understand how to apply it to concrete problems. + +Here are some of the use cases that RAC excels at. + +### Handling asynchronous or event-driven data sources + +Much of Cocoa programming is focused on reacting to user events or changes in +application state. Code that deals with such events can quickly become very +complex and spaghetti-like, with lots of callbacks and state variables to handle +ordering issues. + +Patterns that seem superficially different, like UI callbacks, network +responses, and KVO notifications, actually have a lot in common. [RACSignal][] +unifies all these different APIs so that they can be composed together and +manipulated in the same way. + +For example, the following code: + +```objc + +static void *ObservationContext = &ObservationContext; + +- (void)viewDidLoad { + [super viewDidLoad]; + + [LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext]; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager]; + + [self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; + [self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; + [self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside]; +} + +- (void)dealloc { + [LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext]; + [NSNotificationCenter.defaultCenter removeObserver:self]; +} + +- (void)updateLogInButton { + BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0; + BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn; + self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn; +} + +- (IBAction)logInPressed:(UIButton *)sender { + [[LoginManager sharedManager] + logInWithUsername:self.usernameTextField.text + password:self.passwordTextField.text + success:^{ + self.loggedIn = YES; + } failure:^(NSError *error) { + [self presentError:error]; + }]; +} + +- (void)loggedOut:(NSNotification *)notification { + self.loggedIn = NO; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context == ObservationContext) { + [self updateLogInButton]; + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} +``` + +… could be expressed in RAC like so: + +```objc +- (void)viewDidLoad { + [super viewDidLoad]; + + @weakify(self); + + RAC(self.logInButton, enabled) = [RACSignal + combineLatest:@[ + self.usernameTextField.rac_textSignal, + self.passwordTextField.rac_textSignal, + RACObserve(LoginManager.sharedManager, loggingIn), + RACObserve(self, loggedIn) + ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) { + return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue); + }]; + + [[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) { + @strongify(self); + + RACSignal *loginSignal = [LoginManager.sharedManager + logInWithUsername:self.usernameTextField.text + password:self.passwordTextField.text]; + + [loginSignal subscribeError:^(NSError *error) { + @strongify(self); + [self presentError:error]; + } completed:^{ + @strongify(self); + self.loggedIn = YES; + }]; + }]; + + RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter + rac_addObserverForName:UserDidLogOutNotification object:nil] + mapReplace:@NO]; +} +``` + +### Chaining dependent operations + +Dependencies are most often found in network requests, where a previous request +to the server needs to complete before the next one can be constructed, and so +on: + +```objc +[client logInWithSuccess:^{ + [client loadCachedMessagesWithSuccess:^(NSArray *messages) { + [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) { + NSLog(@"Fetched all messages."); + } failure:^(NSError *error) { + [self presentError:error]; + }]; + } failure:^(NSError *error) { + [self presentError:error]; + }]; +} failure:^(NSError *error) { + [self presentError:error]; +}]; +``` + +ReactiveCocoa makes this pattern particularly easy: + +```objc +[[[[client logIn] + then:^{ + return [client loadCachedMessages]; + }] + flattenMap:^(NSArray *messages) { + return [client fetchMessagesAfterMessage:messages.lastObject]; + }] + subscribeError:^(NSError *error) { + [self presentError:error]; + } completed:^{ + NSLog(@"Fetched all messages."); + }]; +``` + +### Parallelizing independent work + +Working with independent data sets in parallel and then combining them into +a final result is non-trivial in Cocoa, and often involves a lot of +synchronization: + +```objc +__block NSArray *databaseObjects; +__block NSArray *fileContents; + +NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init]; +NSBlockOperation *databaseOperation = [NSBlockOperation blockOperationWithBlock:^{ + databaseObjects = [databaseClient fetchObjectsMatchingPredicate:predicate]; +}]; + +NSBlockOperation *filesOperation = [NSBlockOperation blockOperationWithBlock:^{ + NSMutableArray *filesInProgress = [NSMutableArray array]; + for (NSString *path in files) { + [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; + } + + fileContents = [filesInProgress copy]; +}]; + +NSBlockOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{ + [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; + NSLog(@"Done processing"); +}]; + +[finishOperation addDependency:databaseOperation]; +[finishOperation addDependency:filesOperation]; +[backgroundQueue addOperation:databaseOperation]; +[backgroundQueue addOperation:filesOperation]; +[backgroundQueue addOperation:finishOperation]; +``` + +The above code can be cleaned up and optimized by simply composing signals: + +```objc +RACSignal *databaseSignal = [[databaseClient + fetchObjectsMatchingPredicate:predicate] + subscribeOn:[RACScheduler scheduler]]; + +RACSignal *fileSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) { + NSMutableArray *filesInProgress = [NSMutableArray array]; + for (NSString *path in files) { + [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; + } + + [subscriber sendNext:[filesInProgress copy]]; + [subscriber sendCompleted]; +}]; + +[[RACSignal + combineLatest:@[ databaseSignal, fileSignal ] + reduce:^ id (NSArray *databaseObjects, NSArray *fileContents) { + [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; + return nil; + }] + subscribeCompleted:^{ + NSLog(@"Done processing"); + }]; +``` + +### Simplifying collection transformations + +Higher-order functions like `map`, `filter`, `fold`/`reduce` are sorely missing +from Foundation, leading to loop-focused code like this: + +```objc +NSMutableArray *results = [NSMutableArray array]; +for (NSString *str in strings) { + if (str.length < 2) { + continue; + } + + NSString *newString = [str stringByAppendingString:@"foobar"]; + [results addObject:newString]; +} +``` + +[RACSequence][] allows any Cocoa collection to be manipulated in a uniform and +declarative way: + +```objc +RACSequence *results = [[strings.rac_sequence + filter:^ BOOL (NSString *str) { + return str.length >= 2; + }] + map:^(NSString *str) { + return [str stringByAppendingString:@"foobar"]; + }]; +``` + +## System Requirements + +ReactiveCocoa supports OS X 10.7+ and iOS 5.0+. + +## Importing ReactiveCocoa + +To add RAC to your application: + + 1. Add the ReactiveCocoa repository as a submodule of your application's + repository. + 1. Run `script/bootstrap` from within the ReactiveCocoa folder. + 1. Drag and drop `ReactiveCocoaFramework/ReactiveCocoa.xcodeproj` into your + application's Xcode project or workspace. + 1. On the "Build Phases" tab of your application target, add RAC to the "Link + Binary With Libraries" phase. + * **On iOS**, add `libReactiveCocoa-iOS.a`. + * **On OS X**, add `ReactiveCocoa.framework`. RAC must also be added to any + "Copy Frameworks" build phase. If you don't already have one, simply add + a "Copy Files" build phase and target the "Frameworks" destination. + 1. Add `"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/include" + $(inherited)` to the "Header Search Paths" build setting (this is only + necessary for archive builds, but it has no negative effect otherwise). + 1. **For iOS targets**, add `-ObjC` to the "Other Linker Flags" build setting. + 1. **If you added RAC to a project (not a workspace)**, you will also need to + add the appropriate RAC target to the "Target Dependencies" of your + application. + +If you would prefer to use [CocoaPods](http://cocoapods.org), there are some +[ReactiveCocoa +podspecs](https://github.com/CocoaPods/Specs/tree/master/ReactiveCocoa) that +have been generously contributed by third parties. + +To see a project already set up with RAC, check out [C-41][] or [GroceryList][], +which are real iOS apps written using ReactiveCocoa. + +## Standalone Development + +If you’re working on RAC in isolation instead of integrating it into another project, you’ll want to open `ReactiveCocoaFramework/ReactiveCocoa.xcworkspace` and not the `.xcodeproj`. + +## More Info + +ReactiveCocoa is based on .NET's [Reactive +Extensions](http://msdn.microsoft.com/en-us/data/gg577609) (Rx). Most of the +principles of Rx apply to RAC as well. There are some really good Rx resources +out there: + +* [Reactive Extensions MSDN entry](http://msdn.microsoft.com/en-us/library/hh242985.aspx) +* [Reactive Extensions for .NET Introduction](http://leecampbell.blogspot.com/2010/08/reactive-extensions-for-net.html) +* [Rx - Channel 9 videos](http://channel9.msdn.com/tags/Rx/) +* [Reactive Extensions wiki](http://rxwiki.wikidot.com/) +* [101 Rx Samples](http://rxwiki.wikidot.com/101samples) +* [Programming Reactive Extensions and LINQ](http://www.amazon.com/Programming-Reactive-Extensions-Jesse-Liberty/dp/1430237473) + +RAC and Rx are both implementations of functional reactive programming. Here are +some more resources for learning about FRP: + +* [What is FRP? - Elm Language](http://elm-lang.org/learn/What-is-FRP.elm) +* [What is Functional Reactive Programming - Stack Overflow](http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming/1030631#1030631) +* [Escape from Callback Hell](http://elm-lang.org/learn/Escape-from-Callback-Hell.elm) +* [Principles of Reactive Programming on Coursera](https://www.coursera.org/course/reactive) + +[Basic Operators]: Documentation/BasicOperators.md +[Documentation]: Documentation +[Framework Overview]: Documentation/FrameworkOverview.md +[Functional Reactive Programming]: http://en.wikipedia.org/wiki/Functional_reactive_programming +[GroceryList]: https://github.com/jspahrsummers/GroceryList +[Memory Management]: Documentation/MemoryManagement.md +[NSObject+RACLifting]: ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h +[RACDisposable]: ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h +[RACEvent]: ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h +[RACMulticastConnection]: ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h +[RACScheduler]: ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h +[RACSequence]: ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h +[RACSignal+Operations]: ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h +[RACSignal]: ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h +[RACStream]: ReactiveCocoaFramework/ReactiveCocoa/RACStream.h +[RACSubscriber]: ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h +[RAC]: ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h +[futures and promises]: http://en.wikipedia.org/wiki/Futures_and_promises +[C-41]: https://github.com/AshFurrow/C-41
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.h new file mode 100644 index 0000000..d2d0b3f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSArray+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSArray (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 *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.m new file mode 100644 index 0000000..ca2b951 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.m
@@ -0,0 +1,18 @@ +// +// NSArray+RACSequenceAdditions.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.h new file mode 100644 index 0000000..42198a6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.h
@@ -0,0 +1,22 @@ +// +// NSData+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; +@class RACScheduler; + +@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 *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.m new file mode 100644 index 0000000..227eb4d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.m
@@ -0,0 +1,35 @@ +// +// NSData+RACSupport.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.h new file mode 100644 index 0000000..4871fe7 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.h
@@ -0,0 +1,31 @@ +// +// NSDictionary+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSDictionary (RACSequenceAdditions) + +/// Creates and returns a sequence of RACTuple key/value pairs. The key will be +/// the first element in the tuple, and the value will be the second. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *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 *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 *rac_valueSequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.m new file mode 100644 index 0000000..192e6fd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.m
@@ -0,0 +1,34 @@ +// +// NSDictionary+RACSequenceAdditions.m +// ReactiveCocoa +// +// 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 [RACTuple tupleWithObjects:key, value, nil]; + }]; +} + +- (RACSequence *)rac_keySequence { + return self.allKeys.rac_sequence; +} + +- (RACSequence *)rac_valueSequence { + return self.allValues.rac_sequence; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.h new file mode 100644 index 0000000..1d8fc84 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSEnumerator+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Uri Baghin on 07/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSEnumerator (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 *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.m new file mode 100644 index 0000000..aa56eaa --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.m
@@ -0,0 +1,22 @@ +// +// NSEnumerator+RACSequenceAdditions.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.h new file mode 100644 index 0000000..985398d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.h
@@ -0,0 +1,19 @@ +// +// NSFileHandle+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +@interface NSFileHandle (RACSupport) + +// Read any available data in the background and send it. Completes when data +// length is <= 0. +- (RACSignal *)rac_readInBackground; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.m new file mode 100644 index 0000000..a258789 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.m
@@ -0,0 +1,39 @@ +// +// NSFileHandle+RACSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSFileHandle+RACSupport.h" +#import "NSNotificationCenter+RACSupport.h" +#import "RACReplaySubject.h" +#import "RACDisposable.h" + +@implementation NSFileHandle (RACSupport) + +- (RACSignal *)rac_readInBackground { + RACReplaySubject *subject = [RACReplaySubject subject]; + [subject setNameWithFormat:@"%@ -rac_readInBackground", self]; + + RACSignal *dataNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:NSFileHandleReadCompletionNotification object:self] map:^(NSNotification *note) { + return [note.userInfo objectForKey: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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.h new file mode 100644 index 0000000..b2066e4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSIndexSet+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Sergey Gavrilyuk on 12/17/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACSequence.h" + +@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 *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.m new file mode 100644 index 0000000..b3557e6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.m
@@ -0,0 +1,18 @@ +// +// NSIndexSet+RACSequenceAdditions.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.h b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.h new file mode 100644 index 0000000..d1e72bf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.h
@@ -0,0 +1,56 @@ +// +// NSInvocation+RACTypeParsing.h +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.m b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.m new file mode 100644 index 0000000..a5318e7 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.m
@@ -0,0 +1,232 @@ +// +// NSInvocation+RACTypeParsing.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.h new file mode 100644 index 0000000..ddb3954 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.h
@@ -0,0 +1,18 @@ +// +// NSNotificationCenter+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +@interface NSNotificationCenter (RACSupport) + +// Sends the NSNotification every time the notification is posted. +- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.m new file mode 100644 index 0000000..4219ecd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.m
@@ -0,0 +1,31 @@ +// +// NSNotificationCenter+RACSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSNotificationCenter+RACSupport.h" +#import "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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.h new file mode 100644 index 0000000..1530eb4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.h
@@ -0,0 +1,34 @@ +// +// NSObject+RACDeallocating.h +// ReactiveCocoa +// +// 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; + +@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 + +@interface NSObject (RACDeallocatingDeprecated) + +- (RACSignal *)rac_didDeallocSignal __attribute__((deprecated("Use -rac_willDeallocSignal"))); + +- (void)rac_addDeallocDisposable:(RACDisposable *)disposable __attribute__((deprecated("Add disposables to -rac_deallocDisposable instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.m new file mode 100644 index 0000000..a81d89c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.m
@@ -0,0 +1,129 @@ +// +// NSObject+RACDeallocating.m +// ReactiveCocoa +// +// 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 + +@implementation NSObject (RACDeallocatingDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (RACSignal *)rac_didDeallocSignal { + RACSubject *subject = [RACSubject subject]; + + RACScopedDisposable *disposable = [[RACDisposable + disposableWithBlock:^{ + [subject sendCompleted]; + }] + asScopedDisposable]; + + objc_setAssociatedObject(self, (__bridge void *)disposable, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return subject; +} + +- (void)rac_addDeallocDisposable:(RACDisposable *)disposable { + [self.rac_deallocDisposable addDisposable:disposable]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.h new file mode 100644 index 0000000..41e3d8a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.h
@@ -0,0 +1,21 @@ +// +// NSObject+RACDescription.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +// A private category providing a terser but faster alternative to -description. +@interface NSObject (RACDescription) + +// A simplified description of the receiver, 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 *)rac_description; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.m new file mode 100644 index 0000000..0088880 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.m
@@ -0,0 +1,46 @@ +// +// NSObject+RACDescription.m +// ReactiveCocoa +// +// 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 NSObject (RACDescription) + +- (NSString *)rac_description { + if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { + return [[NSString alloc] initWithFormat:@"<%@: %p>", self.class, self]; + } else { + return @"(description skipped)"; + } +} + +@end + +@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 { + return self.allObjects.description; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.h new file mode 100644 index 0000000..3b9ff2f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.h
@@ -0,0 +1,56 @@ +// +// NSObject+RACKVOWrapper.h +// GitHub +// +// Created by Josh Abernathy on 10/11/11. +// Copyright (c) 2011 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + + +// RAC-specific KVO change dictionary key: Will be @YES if the change was caused +// by the value at the key path or an intermediate value deallocating, @NO +// otherwise. +extern NSString * const RACKeyValueChangeCausedByDeallocationKey; + +// RAC-specific KVO change dictionary key: Will be @YES if the change only +// affected the value of the last key path component leaving the values of the +// intermediate key path components unaltered, @NO otherwise. +extern NSString * const RACKeyValueChangeAffectedOnlyLastComponentKey; + +@class RACDisposable, 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:(NSObject *)observer block:(void (^)(id value, NSDictionary *change))block; + +@end + +typedef void (^RACKVOBlock)(id target, id observer, NSDictionary *change); + +@interface NSObject (RACKVOWrapperDeprecated) + +- (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block __attribute((deprecated("Use rac_observeKeyPath:options:observer:block: instead."))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.m new file mode 100644 index 0000000..03f5375 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.m
@@ -0,0 +1,237 @@ +// +// NSObject+RACKVOWrapper.m +// GitHub +// +// Created by Josh Abernathy on 10/11/11. +// Copyright (c) 2011 GitHub. All rights reserved. +// + +#import "NSObject+RACKVOWrapper.h" +#import "RACEXTRuntimeExtensions.h" +#import "RACEXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSString+RACKeyPathUtilities.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACKVOTrampoline.h" +#import "RACSerialDisposable.h" + +NSString * const RACKeyValueChangeCausedByDeallocationKey = @"RACKeyValueChangeCausedByDeallocationKey"; +NSString * const RACKeyValueChangeAffectedOnlyLastComponentKey = @"RACKeyValueChangeAffectedOnlyLastComponentKey"; + +@implementation NSObject (RACKVOWrapper) + +- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer block:(void (^)(id, NSDictionary *))block { + NSCParameterAssert(block != nil); + NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); + + keyPath = [keyPath copy]; + + @unsafeify(observer); + + 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]; + + // Adds the callback block to the value's deallocation. Also adds the logic to + // clean up the callback to the firstComponentDisposable. + void (^addDeallocObserverToPropertyValue)(NSObject *, NSString *, NSObject *) = ^(NSObject *parent, NSString *propertyKey, NSObject *value) { + // 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. + @strongify(observer); + if (value == observer) return; + + objc_property_t property = class_getProperty(object_getClass(parent), propertyKey.UTF8String); + if (property == NULL) { + // If we can't find an Objective-C property for this key, we assume + // that we don't need to observe its deallocation (thus matching + // vanilla KVO behavior). + // + // Even if we wanted to, there's not enough type information on + // ivars to figure out its memory management. + return; + } + + rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property); + if (attributes == NULL) return; + + @onExit { + free(attributes); + }; + + BOOL isNonObject = attributes->objectClass == nil && strstr(attributes->type, @encode(id)) != attributes->type; + BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol"); + BOOL isBlock = strcmp(attributes->type, @encode(void(^)())) == 0; + if (isNonObject || isProtocol || isBlock) { + // 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. + return; + } + + if (!attributes->weak) { + // 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. + return; + } + + NSDictionary *change = @{ + NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), + NSKeyValueChangeNewKey: NSNull.null, + RACKeyValueChangeCausedByDeallocationKey: @YES, + RACKeyValueChangeAffectedOnlyLastComponentKey: @(keyPathHasOneComponent) + }; + + RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable; + RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ + block(nil, change); + }]; + + [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) { + @strongify(observer); + RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:observer 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:observer keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) { + // Prepare the change dictionary by adding the RAC specific keys + { + NSMutableDictionary *newChange = [change mutableCopy]; + newChange[RACKeyValueChangeCausedByDeallocationKey] = @NO; + newChange[RACKeyValueChangeAffectedOnlyLastComponentKey] = @(keyPathHasOneComponent); + change = newChange.copy; + } + + // 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); + } + + 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); + 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(trampolineTarget, keyPathHead, 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); + 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); + }]; + + // 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(self, keyPathHead, 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, + RACKeyValueChangeCausedByDeallocationKey: @NO, + RACKeyValueChangeAffectedOnlyLastComponentKey: @(keyPathHasOneComponent) + }; + block(initialValue, initialChange); + } + + + RACCompoundDisposable *observerDisposable = observer.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 + +@implementation NSObject (RACKVOWrapperDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { + return [[RACKVOTrampoline alloc] initWithTarget:self observer:observer keyPath:keyPath options:options block:block]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h new file mode 100644 index 0000000..0feb272 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h
@@ -0,0 +1,55 @@ +// +// NSObject+RACLifting.h +// iOSDemo +// +// Created by Josh Abernathy on 10/13/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +@class RACSignal; + +@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 *)signals; + +@end + +@interface NSObject (RACLiftingDeprecated) + +- (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... __attribute__((deprecated("Use -rac_liftSelector:withSignals: instead"))); +- (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args __attribute__((deprecated("Use -rac_liftSelector:withSignalsFromArray: instead"))); +- (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... NS_REQUIRES_NIL_TERMINATION __attribute__((deprecated("Use +combineLatest:reduce: instead"))); +- (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args __attribute__((deprecated("Use +combineLatest:reduce: instead"))); + +@end + +@interface NSObject (RACLiftingUnavailable) + +- (instancetype)rac_lift __attribute__((unavailable("Use -rac_liftSelector:withSignals: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.m new file mode 100644 index 0000000..5fe9a00 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.m
@@ -0,0 +1,132 @@ +// +// NSObject+RACLifting.m +// iOSDemo +// +// Created by Josh Abernathy on 10/13/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACLifting.h" +#import "RACEXTScope.h" +#import "NSInvocation+RACTypeParsing.h" +#import "NSObject+RACDeallocating.h" +#import "RACSignal+Operations.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" + +@implementation NSObject (RACLifting) + +- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals { + NSCParameterAssert(selector != NULL); + 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); + + @unsafeify(self); + + return [[[[[RACSignal + combineLatest:signals] + 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 withSignalsFromArray: %@", [self rac_description], 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: %@", [self rac_description], sel_getName(selector), signals]; +} + +@end + +@implementation NSObject (RACLiftingDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +static NSArray *RACMapArgumentsToSignals(NSArray *args) { + NSMutableArray *mappedArgs = [NSMutableArray array]; + for (id arg in args) { + if ([arg isEqual:RACTupleNil.tupleNil]) { + [mappedArgs addObject:[RACSignal return:nil]]; + } else if ([arg isKindOfClass:RACSignal.class]) { + [mappedArgs addObject:arg]; + } else { + [mappedArgs addObject:[RACSignal return:arg]]; + } + } + + return mappedArgs; +} + +- (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... { + NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; + NSMutableArray *arguments = [NSMutableArray array]; + + va_list args; + va_start(args, arg); + for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { + id currentObject = (i == 2 ? arg : va_arg(args, id)); + [arguments addObject:currentObject ?: RACTupleNil.tupleNil]; + } + + va_end(args); + return [self rac_liftSelector:selector withObjectsFromArray:arguments]; +} + +- (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args { + return [self rac_liftSelector:selector withSignalsFromArray:RACMapArgumentsToSignals(args)]; +} + +- (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... { + NSMutableArray *arguments = [NSMutableArray array]; + + va_list args; + va_start(args, arg); + for (id currentObject = arg; currentObject != nil; currentObject = va_arg(args, id)) { + [arguments addObject:currentObject]; + } + + va_end(args); + return [self rac_liftBlock:block withArgumentsFromArray:arguments]; +} + +- (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args { + return [[[[RACSignal + combineLatest:RACMapArgumentsToSignals(args)] + reduceEach:block] + takeUntil:self.rac_willDeallocSignal] + replayLast]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.h new file mode 100644 index 0000000..10bb68d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.h
@@ -0,0 +1,102 @@ +// +// NSObject+RACPropertySubscribing.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "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) \ + [(id)(TARGET) rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self] + +@class RACDisposable; +@class RACSignal; + +@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. +- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer; + +/// Creates a signal to observe the changes of 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 sends tuples containing the current value at the key +/// path and the change dictionary for each KVO callback. +- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer; + +@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] + +@interface NSObject (RACPropertySubscribingDeprecated) + ++ (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); ++ (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); ++ (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesAndChangesForKeyPath:options:observer: instead."))); +- (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); +- (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); +- (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal __attribute__((deprecated("Use -[RACSignal setKeyPath:onObject:] instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.m new file mode 100644 index 0000000..88d4683 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.m
@@ -0,0 +1,161 @@ +// +// NSObject+RACPropertySubscribing.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACPropertySubscribing.h" +#import "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:(NSObject *)observer { + return [[[self + rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] + reduceEach:^(id value, NSDictionary *change) { + return value; + }] + setNameWithFormat:@"RACObserve(%@, %@)", self.rac_description, keyPath]; +} + +- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer { + keyPath = [keyPath copy]; + + NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; + objectLock.name = @"com.github.ReactiveCocoa.NSObjectRACPropertySubscribing"; + + __block __unsafe_unretained NSObject *unsafeSelf = self; + __block __unsafe_unretained NSObject *unsafeObserver = observer; + + RACSignal *deallocSignal = [[RACSignal + zip:@[ + self.rac_willDeallocSignal, + observer.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]; + }; + + unsafeSelf = nil; + unsafeObserver = nil; + }]; + + 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)) = unsafeObserver; + __strong NSObject *self __attribute__((objc_precise_lifetime)) = unsafeSelf; + + if (self == nil) { + [subscriber sendCompleted]; + return nil; + } + + return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change) { + [subscriber sendNext:RACTuplePack(value, change)]; + }]; + }] + takeUntil:deallocSignal] + setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", self.rac_description, keyPath, (unsigned long)options, observer.rac_description]; +} + +@end + +static RACSignal *signalWithoutChangesFor(Class class, NSObject *object, NSString *keyPath, NSKeyValueObservingOptions options, NSObject *observer) { + NSCParameterAssert(object != nil); + NSCParameterAssert(keyPath != nil); + NSCParameterAssert(observer != nil); + + keyPath = [keyPath copy]; + + @unsafeify(object); + + return [[class + rac_signalWithChangesFor:object keyPath:keyPath options:options observer:observer] + map:^(NSDictionary *change) { + @strongify(object); + return [object valueForKeyPath:keyPath]; + }]; +} + +@implementation NSObject (RACPropertySubscribingDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + ++ (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { + return signalWithoutChangesFor(self, object, keyPath, 0, observer); +} + ++ (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { + return signalWithoutChangesFor(self, object, keyPath, NSKeyValueObservingOptionInitial, observer); +} + ++ (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer { + @unsafeify(observer, object); + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + + @strongify(observer, object); + RACKVOTrampoline *KVOTrampoline = [object rac_addObserver:observer forKeyPath:keyPath options:options block:^(id target, id observer, NSDictionary *change) { + [subscriber sendNext:change]; + }]; + + @weakify(subscriber); + RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ + @strongify(subscriber); + [KVOTrampoline dispose]; + [subscriber sendCompleted]; + }]; + + [observer.rac_deallocDisposable addDisposable:deallocDisposable]; + [object.rac_deallocDisposable addDisposable:deallocDisposable]; + + RACCompoundDisposable *observerDisposable = observer.rac_deallocDisposable; + RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; + return [RACDisposable disposableWithBlock:^{ + [observerDisposable removeDisposable:deallocDisposable]; + [objectDisposable removeDisposable:deallocDisposable]; + [KVOTrampoline dispose]; + }]; + }] setNameWithFormat:@"RACAble(%@, %@)", object.rac_description, keyPath]; +} + +- (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { + return [self.class rac_signalFor:self keyPath:keyPath observer:observer]; +} + +- (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { + return [self.class rac_signalWithStartingValueFor:self keyPath:keyPath observer:observer]; +} + +- (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal { + return [signal setKeyPath:keyPath onObject:self]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h new file mode 100644 index 0000000..c6f3de5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h
@@ -0,0 +1,79 @@ +// +// NSObject+RACSelectorSignal.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +/// The domain for any errors originating from -rac_signalForSelector:. +extern NSString * const RACSelectorSignalErrorDomain; + +/// -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. +extern const NSInteger RACSelectorSignalErrorMethodSwizzlingRace; + +@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 *)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 *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.m new file mode 100644 index 0000000..b7085c0 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.m
@@ -0,0 +1,322 @@ +// +// NSObject+RACSelectorSignal.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACSelectorSignal.h" +#import "RACEXTRuntimeExtensions.h" +#import "NSInvocation+RACTypeParsing.h" +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACObjCRuntime.h" +#import "RACSubject.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" +#import <objc/message.h> +#import <objc/runtime.h> + +NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain"; +const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1; + +static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_"; +static NSString * const RACSubclassSuffix = @"_RACSelectorSignal"; + +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(object_getClass(self), selector); + + if (method != NULL && method_getImplementation(method) == _objc_msgForward) { + SEL aliasSelector = RACAliasForSelector(selector); + return objc_getAssociatedObject(self, aliasSelector) != nil; + } + + 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", self.rac_description, 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); + NSString *className = NSStringFromClass(baseClass); + + if ([className hasSuffix:RACSubclassSuffix]) { + return baseClass; + } else 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 = [RACObjCRuntime createClass:subclassName inheritingFromClass:baseClass]; + 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); + 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.h new file mode 100644 index 0000000..8bea2db --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSOrderedSet+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSOrderedSet (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 *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.m new file mode 100644 index 0000000..55dfd0b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.m
@@ -0,0 +1,19 @@ +// +// NSOrderedSet+RACSequenceAdditions.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.h new file mode 100644 index 0000000..6665501 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSSet+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSSet (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 *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.m new file mode 100644 index 0000000..cc07f75 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.m
@@ -0,0 +1,19 @@ +// +// NSSet+RACSequenceAdditions.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.h b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.h new file mode 100644 index 0000000..d56cf59 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.h
@@ -0,0 +1,34 @@ +// +// NSString+RACKeyPathUtilities.h +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.m b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.m new file mode 100644 index 0000000..63a100c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.m
@@ -0,0 +1,36 @@ +// +// NSString+RACKeyPathUtilities.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.h new file mode 100644 index 0000000..0116231 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.h
@@ -0,0 +1,21 @@ +// +// NSString+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@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 *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.m new file mode 100644 index 0000000..eb6a76e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.m
@@ -0,0 +1,18 @@ +// +// NSString+RACSequenceAdditions.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.h new file mode 100644 index 0000000..d904a4c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.h
@@ -0,0 +1,22 @@ +// +// NSString+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; +@class RACScheduler; + +@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 *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.m new file mode 100644 index 0000000..c6ebe0b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.m
@@ -0,0 +1,35 @@ +// +// NSString+RACSupport.m +// ReactiveCocoa +// +// 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(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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.h new file mode 100644 index 0000000..e9bf04f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.h
@@ -0,0 +1,25 @@ +// +// NSURLConnection+RACSupport.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +@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 `RACTuple` of the received `NSURLResponse` and downloaded +// `NSData`, and complete on a background thread. If any errors occur, the +// returned signal will error out. ++ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.m new file mode 100644 index 0000000..3eaa2c3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.m
@@ -0,0 +1,53 @@ +// +// NSURLConnection+RACSupport.m +// ReactiveCocoa +// +// 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 "RACSignal+Operations.h" +#import "RACSubscriber.h" +#import "RACTuple.h" + +@implementation NSURLConnection (RACSupport) + ++ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request { + NSCParameterAssert(request != nil); + + return [[RACSignal + createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + queue.name = @"com.github.ReactiveCocoa.NSURLConnectionRACSupport"; + + [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]; + } + }]; + + 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.h new file mode 100644 index 0000000..8482fb3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.h
@@ -0,0 +1,27 @@ +// +// NSUserDefaults+RACSupport.h +// ReactiveCocoa +// +// Created by Matt Diephouse on 12/19/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACChannelTerminal; + +@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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.m new file mode 100644 index 0000000..eaef5a6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.m
@@ -0,0 +1,58 @@ +// +// NSUserDefaults+RACSupport.m +// ReactiveCocoa +// +// Created by Matt Diephouse on 12/19/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSUserDefaults+RACSupport.h" + +#import "RACEXTScope.h" +#import "RACChannel.h" +#import "RACScheduler.h" +#import "RACSignal+Operations.h" +#import "NSNotificationCenter+RACSupport.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACLifting.h" + +@implementation NSUserDefaults (RACSupport) + +- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key { + 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.h new file mode 100644 index 0000000..e9d948e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.h
@@ -0,0 +1,18 @@ +// +// RACArraySequence.h +// ReactiveCocoa +// +// 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. ++ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.m new file mode 100644 index 0000000..f7ca69d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.m
@@ -0,0 +1,125 @@ +// +// RACArraySequence.m +// ReactiveCocoa +// +// 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 + ++ (instancetype)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 objectAtIndex: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 + +- (id)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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.h b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.h new file mode 100644 index 0000000..8588c80 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.h
@@ -0,0 +1,70 @@ +// +// RACBacktrace.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-08-20. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#ifdef DEBUG + +extern void rac_dispatch_async(dispatch_queue_t queue, dispatch_block_t block); +extern void rac_dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); +extern void rac_dispatch_after(dispatch_time_t time, dispatch_queue_t queue, dispatch_block_t block); +extern void rac_dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function); +extern void rac_dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function); +extern void rac_dispatch_after_f(dispatch_time_t time, dispatch_queue_t queue, void *context, dispatch_function_t function); + +#define dispatch_async rac_dispatch_async +#define dispatch_barrier_async rac_dispatch_barrier_async +#define dispatch_after rac_dispatch_after +#define dispatch_async_f rac_dispatch_async_f +#define dispatch_barrier_async_f rac_dispatch_barrier_async_f +#define dispatch_after_f rac_dispatch_after_f + +/// Preserves backtraces across asynchronous calls. +/// +/// On OS X, you can enable the automatic capturing of asynchronous backtraces +/// (in Debug builds) by setting the `DYLD_INSERT_LIBRARIES` environment variable +/// to `@executable_path/../Frameworks/ReactiveCocoa.framework/ReactiveCocoa` in +/// your scheme's Run action settings. +/// +/// On iOS, your project and RAC will automatically use the `rac_` GCD functions +/// (declared above) for asynchronous work. Unfortunately, unlike OS X, it's +/// impossible to capture backtraces inside NSOperationQueue or other code +/// outside of your project. +/// +/// Once backtraces are being captured, you can `po [RACBacktrace backtrace]` in +/// the debugger to print them out at any time. You can even set up an alias in +/// ~/.lldbinit to do so: +/// +/// command alias racbt po [RACBacktrace backtrace] +/// +@interface RACBacktrace : NSObject + +/// The backtrace from any previous thread. +@property (nonatomic, strong, readonly) RACBacktrace *previousThreadBacktrace; + +/// The call stack of this backtrace's thread. +@property (nonatomic, copy, readonly) NSArray *callStackSymbols; + +/// Captures the current thread's backtrace, appending it to any backtrace from +/// a previous thread. ++ (instancetype)backtrace; + +/// Same as +backtrace, but omits the specified number of frames at the +/// top of the stack (in addition to this method itself). ++ (instancetype)backtraceIgnoringFrames:(NSUInteger)ignoreCount; + +@end + +#else + +#define rac_dispatch_async dispatch_async +#define rac_dispatch_barrier_async dispatch_barrier_async +#define rac_dispatch_after dispatch_after +#define rac_dispatch_async_f dispatch_async_f +#define rac_dispatch_barrier_async_f dispatch_barrier_async_f +#define rac_dispatch_after_f dispatch_after_f + +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.m b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.m new file mode 100644 index 0000000..c2724d4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.m
@@ -0,0 +1,249 @@ +// +// RACBacktrace.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-08-16. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <execinfo.h> +#import <pthread.h> +#import "RACBacktrace.h" + +#define RAC_BACKTRACE_MAX_CALL_STACK_FRAMES 128 + +#ifdef DEBUG + +// Undefine the macros that hide the real GCD functions. +#undef dispatch_async +#undef dispatch_barrier_async +#undef dispatch_after +#undef dispatch_async_f +#undef dispatch_barrier_async_f +#undef dispatch_after_f + +@interface RACBacktrace () { + void *_callStackAddresses[RAC_BACKTRACE_MAX_CALL_STACK_FRAMES]; + int _callStackSize; +} + +@property (nonatomic, strong, readwrite) RACBacktrace *previousThreadBacktrace; +@end + +@interface RACDispatchInfo : NSObject + +// The recorded backtrace. +@property (nonatomic, strong, readonly) RACBacktrace *backtrace; + +// The information for the original dispatch. +@property (nonatomic, readonly) dispatch_function_t function; +@property (nonatomic, readonly) void *context; +@property (nonatomic, readonly) dispatch_queue_t queue; + +- (id)initWithQueue:(dispatch_queue_t)queue function:(dispatch_function_t)function context:(void *)context; + +@end + +// Function for use with dispatch_async_f and friends, which will save the +// backtrace onto the current queue, then call through to the original dispatch. +static void RACTraceDispatch (void *ptr) { + // Balance out the retain necessary for async calls. + RACDispatchInfo *info __attribute__((objc_precise_lifetime)) = CFBridgingRelease(ptr); + + dispatch_queue_set_specific(info.queue, (void *)pthread_self(), (__bridge void *)info.backtrace, NULL); + info.function(info.context); + dispatch_queue_set_specific(info.queue, (void *)pthread_self(), NULL, NULL); +} + +// Always inline this function, for consistency in backtraces. +__attribute__((always_inline)) +static dispatch_block_t RACBacktraceBlock (dispatch_queue_t queue, dispatch_block_t block) { + RACBacktrace *backtrace = [RACBacktrace backtrace]; + + return [^{ + RACBacktrace *backtraceKeptAlive __attribute__((objc_precise_lifetime)) = backtrace; + + dispatch_queue_set_specific(queue, (void *)pthread_self(), (__bridge void *)backtraceKeptAlive, NULL); + block(); + dispatch_queue_set_specific(queue, (void *)pthread_self(), NULL, NULL); + } copy]; +} + +void rac_dispatch_async(dispatch_queue_t queue, dispatch_block_t block) { + dispatch_async(queue, RACBacktraceBlock(queue, block)); +} + +void rac_dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block) { + dispatch_barrier_async(queue, RACBacktraceBlock(queue, block)); +} + +void rac_dispatch_after(dispatch_time_t time, dispatch_queue_t queue, dispatch_block_t block) { + dispatch_after(time, queue, RACBacktraceBlock(queue, block)); +} + +void rac_dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function) { + RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context]; + dispatch_async_f(queue, (void *)CFBridgingRetain(info), &RACTraceDispatch); +} + +void rac_dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function) { + RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context]; + dispatch_barrier_async_f(queue, (void *)CFBridgingRetain(info), &RACTraceDispatch); +} + +void rac_dispatch_after_f(dispatch_time_t time, dispatch_queue_t queue, void *context, dispatch_function_t function) { + RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context]; + dispatch_after_f(time, queue, (void *)CFBridgingRetain(info), &RACTraceDispatch); +} + +// This is what actually performs the injection. +// +// The DYLD_INSERT_LIBRARIES environment variable must include the RAC dynamic +// library in order for this to work. +__attribute__((used)) static struct { const void *replacement; const void *replacee; } interposers[] __attribute__((section("__DATA,__interpose"))) = { + { (const void *)&rac_dispatch_async, (const void *)&dispatch_async }, + { (const void *)&rac_dispatch_barrier_async, (const void *)&dispatch_barrier_async }, + { (const void *)&rac_dispatch_after, (const void *)&dispatch_after }, + { (const void *)&rac_dispatch_async_f, (const void *)&dispatch_async_f }, + { (const void *)&rac_dispatch_barrier_async_f, (const void *)&dispatch_barrier_async_f }, + { (const void *)&rac_dispatch_after_f, (const void *)&dispatch_after_f }, +}; + +static void RACSignalHandler (int sig) { + NSLog(@"Backtrace: %@", [RACBacktrace backtrace]); + fflush(stdout); + + // Restore the default action and raise the signal again. + signal(sig, SIG_DFL); + raise(sig); +} + +static void RACExceptionHandler (NSException *ex) { + NSLog(@"Uncaught exception %@", ex); + NSLog(@"Backtrace: %@", [RACBacktrace backtrace]); + fflush(stdout); +} + +@implementation RACBacktrace + +#pragma mark Properties + +- (NSArray *)callStackSymbols { + if (_callStackSize == 0) return @[]; + + char **symbols = backtrace_symbols(_callStackAddresses, _callStackSize); + NSMutableArray *array = [NSMutableArray arrayWithCapacity:(NSUInteger)_callStackSize]; + + for (int i = 0; i < _callStackSize; i++) { + NSString *str = @(symbols[i]); + [array addObject:str]; + } + + free(symbols); + return array; +} + +#pragma mark Lifecycle + ++ (void)load { + @autoreleasepool { + NSString *libraries = [[[NSProcessInfo processInfo] environment] objectForKey:@"DYLD_INSERT_LIBRARIES"]; + + // Don't install our handlers if we're not actually intercepting function + // calls. + if ([libraries rangeOfString:@"ReactiveCocoa"].length == 0) return; + + NSLog(@"*** Enabling asynchronous backtraces"); + + NSSetUncaughtExceptionHandler(&RACExceptionHandler); + } + + signal(SIGILL, &RACSignalHandler); + signal(SIGTRAP, &RACSignalHandler); + signal(SIGABRT, &RACSignalHandler); + signal(SIGFPE, &RACSignalHandler); + signal(SIGBUS, &RACSignalHandler); + signal(SIGSEGV, &RACSignalHandler); + signal(SIGSYS, &RACSignalHandler); + signal(SIGPIPE, &RACSignalHandler); +} + +- (void)dealloc { + __autoreleasing RACBacktrace *previous __attribute__((unused)) = self.previousThreadBacktrace; + self.previousThreadBacktrace = nil; +} + +#pragma mark Backtraces + ++ (instancetype)backtrace { + return [self backtraceIgnoringFrames:1]; +} + ++ (instancetype)backtraceIgnoringFrames:(NSUInteger)ignoreCount { + @autoreleasepool { + RACBacktrace *oldBacktrace = (__bridge id)dispatch_get_specific((void *)pthread_self()); + + RACBacktrace *newBacktrace = [[RACBacktrace alloc] init]; + newBacktrace.previousThreadBacktrace = oldBacktrace; + + int size = backtrace(newBacktrace->_callStackAddresses, RAC_BACKTRACE_MAX_CALL_STACK_FRAMES); + + // Omit this method plus however many others from the backtrace. + ++ignoreCount; + if ((NSUInteger)size > ignoreCount) { + memmove(newBacktrace->_callStackAddresses, newBacktrace->_callStackAddresses + ignoreCount, ((NSUInteger)size - ignoreCount) * sizeof(char *)); + size -= (int)ignoreCount; + } + + newBacktrace->_callStackSize = size; + return newBacktrace; + } +} + +#pragma mark NSObject + +- (NSString *)description { + NSString *str = [NSString stringWithFormat:@"%@", self.callStackSymbols]; + if (self.previousThreadBacktrace != nil) { + str = [str stringByAppendingFormat:@"\n\n... asynchronously invoked from: %@", self.previousThreadBacktrace]; + } + + return str; +} + +@end + +@implementation RACDispatchInfo + +#pragma mark Lifecycle + +- (id)initWithQueue:(dispatch_queue_t)queue function:(dispatch_function_t)function context:(void *)context { + @autoreleasepool { + NSCParameterAssert(queue != NULL); + NSCParameterAssert(function != NULL); + + self = [super init]; + if (self == nil) return nil; + + _backtrace = [RACBacktrace backtraceIgnoringFrames:1]; + + dispatch_retain(queue); + _queue = queue; + + _function = function; + _context = context; + + return self; + } +} + +- (void)dealloc { + if (_queue != NULL) { + dispatch_release(_queue); + _queue = NULL; + } +} + +@end + +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.h b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.h new file mode 100644 index 0000000..2f9e0db --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.h
@@ -0,0 +1,19 @@ +// +// RACBehaviorSubject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" + + +/// A behavior subject sends the last value it received when it is subscribed to. +@interface RACBehaviorSubject : RACSubject + +/// 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:(id)value; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.m b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.m new file mode 100644 index 0000000..dfda2ac --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.m
@@ -0,0 +1,56 @@ +// +// RACBehaviorSubject.m +// ReactiveCocoa +// +// 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 () + +// This property should only be used while synchronized on self. +@property (nonatomic, strong) id 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.h b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.h new file mode 100644 index 0000000..3857d7c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.h
@@ -0,0 +1,30 @@ +// +// RACBlockTrampoline.h +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.m b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.m new file mode 100644 index 0000000..07903dd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.m
@@ -0,0 +1,156 @@ +// +// RACBlockTrampoline.m +// ReactiveCocoa +// +// 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 + +- (id)initWithBlock:(id)block { + self = [super init]; + if (self == nil) return nil; + + _block = [block copy]; + + return self; +} + ++ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments { + NSCParameterAssert(block != NULL); + + RACBlockTrampoline *trampoline = [[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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.h b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.h new file mode 100644 index 0000000..ef1d6f1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.h
@@ -0,0 +1,70 @@ +// +// RACChannel.h +// ReactiveCocoa +// +// Created by Uri Baghin on 01/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACSubscriber.h" + +@class RACChannelTerminal; + +/// 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 : 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 *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 *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 : RACSignal <RACSubscriber> + +- (id)init __attribute__((unavailable("Instantiate a RACChannel instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.m b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.m new file mode 100644 index 0000000..7222858 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.m
@@ -0,0 +1,91 @@ +// +// RACChannel.m +// ReactiveCocoa +// +// 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" +#import "RACUnit.h" + +@interface RACChannelTerminal () + +// The values for this terminal. +@property (nonatomic, strong, readonly) RACSignal *values; + +// A subscriber will will send values to the other terminal. +@property (nonatomic, strong, readonly) id<RACSubscriber> otherTerminal; + +- (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal; + +@end + +@implementation RACChannel + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + // 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 + +- (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal { + NSCParameterAssert(values != nil); + NSCParameterAssert(otherTerminal != nil); + + self = [super init]; + if (self == nil) return nil; + + _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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h new file mode 100644 index 0000000..653d46b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h
@@ -0,0 +1,123 @@ +// +// RACCommand.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/3/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +/// The domain for errors originating within `RACCommand`. +extern NSString * const RACCommandErrorDomain; + +/// -execute: was invoked while the command was disabled. +extern const NSInteger RACCommandErrorNotEnabled; + +/// 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 : 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 *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 *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 *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 *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`. +- (id)initWithSignalBlock:(RACSignal * (^)(id 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. +- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id 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 *)execute:(id)input; + +@end + +@interface RACCommand (Unavailable) + +@property (atomic, readonly) BOOL canExecute __attribute__((unavailable("Use the 'enabled' signal instead"))); + ++ (instancetype)command __attribute__((unavailable("Use -initWithSignalBlock: instead"))); ++ (instancetype)commandWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead"))); +- (id)initWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead"))); +- (RACSignal *)addSignalBlock:(RACSignal * (^)(id value))signalBlock __attribute__((unavailable("Pass the signalBlock to -initWithSignalBlock: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.m b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.m new file mode 100644 index 0000000..1431fcb --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.m
@@ -0,0 +1,268 @@ +// +// RACCommand.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/3/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACCommand.h" +#import "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 "RACSerialDisposable.h" +#import "RACSignal+Operations.h" +#import <libkern/OSAtomic.h> + +NSString * const RACCommandErrorDomain = @"RACCommandErrorDomain"; +NSString * const RACUnderlyingCommandErrorKey = @"RACUnderlyingCommandErrorKey"; + +const NSInteger RACCommandErrorNotEnabled = 1; + +@interface RACCommand () { + // The mutable array backing `activeExecutionSignals`. + // + // This should only be used while synchronized on `self`. + NSMutableArray *_activeExecutionSignals; + + // Atomic backing variable for `allowsConcurrentExecution`. + volatile uint32_t _allowsConcurrentExecution; +} + +// An array of signals representing in-flight executions, in the order they +// began. +// +// This property is KVO-compliant. +@property (atomic, copy, readonly) NSArray *activeExecutionSignals; + +// `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); + +// Adds a signal to `activeExecutionSignals` and generates a KVO notification. +- (void)addActiveExecutionSignal:(RACSignal *)signal; + +// Removes a signal from `activeExecutionSignals` and generates a KVO +// notification. +- (void)removeActiveExecutionSignal:(RACSignal *)signal; + +@end + +@implementation RACCommand + +#pragma mark Properties + +- (BOOL)allowsConcurrentExecution { + return _allowsConcurrentExecution != 0; +} + +- (void)setAllowsConcurrentExecution:(BOOL)allowed { + [self willChangeValueForKey:@keypath(self.allowsConcurrentExecution)]; + + if (allowed) { + OSAtomicOr32Barrier(1, &_allowsConcurrentExecution); + } else { + OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution); + } + + [self didChangeValueForKey:@keypath(self.allowsConcurrentExecution)]; +} + +- (NSArray *)activeExecutionSignals { + @synchronized (self) { + return [_activeExecutionSignals copy]; + } +} + +- (void)addActiveExecutionSignal:(RACSignal *)signal { + NSCParameterAssert([signal isKindOfClass:RACSignal.class]); + + @synchronized (self) { + // The KVO notification has to be generated while synchronized, because + // it depends on the index remaining consistent. + NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:_activeExecutionSignals.count]; + [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + [_activeExecutionSignals addObject:signal]; + [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + } +} + +- (void)removeActiveExecutionSignal:(RACSignal *)signal { + NSCParameterAssert([signal isKindOfClass:RACSignal.class]); + + @synchronized (self) { + // The indexes have to be calculated and the notification generated + // while synchronized, because they depend on the indexes remaining + // consistent. + NSIndexSet *indexes = [_activeExecutionSignals indexesOfObjectsPassingTest:^ BOOL (RACSignal *obj, NSUInteger index, BOOL *stop) { + return obj == signal; + }]; + + if (indexes.count == 0) return; + + [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + [_activeExecutionSignals removeObjectsAtIndexes:indexes]; + [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + } +} + +#pragma mark Lifecycle + +- (id)init { + NSCAssert(NO, @"Use -initWithSignalBlock: instead"); + return nil; +} + +- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock { + return [self initWithEnabled:nil signalBlock:signalBlock]; +} + +- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock { + NSCParameterAssert(signalBlock != nil); + + self = [super init]; + if (self == nil) return nil; + + _activeExecutionSignals = [[NSMutableArray alloc] init]; + _signalBlock = [signalBlock copy]; + + // A signal of additions to `activeExecutionSignals`. + RACSignal *newActiveExecutionSignals = [[[[[self + rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil] + reduceEach:^(id _, NSDictionary *change) { + NSArray *signals = change[NSKeyValueChangeNewKey]; + if (signals == nil) return [RACSignal empty]; + + return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler]; + }] + concat] + publish] + autoconnect]; + + _executionSignals = [[[newActiveExecutionSignals + 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 = [[[newActiveExecutionSignals + 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 = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) { + return @(activeSignals.count > 0); + }]; + + _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:RACObserve(self, allowsConcurrentExecution) + then:[RACSignal return:@YES] + else:[immediateExecuting not]]; + + if (enabledSignal == nil) { + enabledSignal = [RACSignal return:@YES]; + } else { + enabledSignal = [[[enabledSignal + startWith:@YES] + takeUntil:self.rac_willDeallocSignal] + replayLast]; + } + + _immediateEnabled = [[RACSignal + combineLatest:@[ enabledSignal, moreExecutionsAllowed ]] + and]; + + _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]]; + + @weakify(self); + + [self addActiveExecutionSignal:connection.signal]; + [connection.signal subscribeError:^(NSError *error) { + @strongify(self); + [self removeActiveExecutionSignal:connection.signal]; + } completed:^{ + @strongify(self); + [self removeActiveExecutionSignal:connection.signal]; + }]; + + [connection connect]; + return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]]; +} + +#pragma mark NSKeyValueObserving + ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { + // Generate all KVO notifications manually to avoid the performance impact + // of unnecessary swizzling. + return NO; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.h new file mode 100644 index 0000000..bb25f7d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.h
@@ -0,0 +1,48 @@ +// +// RACCompoundDisposable.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" + +/// 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:(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:(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:(RACDisposable *)disposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.m new file mode 100644 index 0000000..3b0471c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.m
@@ -0,0 +1,239 @@ +// +// RACCompoundDisposable.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACCompoundDisposable.h" +#import "RACCompoundDisposableProvider.h" +#import <libkern/OSAtomic.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. + OSSpinLock _spinLock; + + #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 _spinLock is held. + RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount]; + #endif + + // Contains the receiver's disposables. + // + // This array should only be manipulated while _spinLock 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 _spinLock is held. + BOOL _disposed; +} + +@end + +@implementation RACCompoundDisposable + +#pragma mark Properties + +- (BOOL)isDisposed { + OSSpinLockLock(&_spinLock); + BOOL disposed = _disposed; + OSSpinLockUnlock(&_spinLock); + + return disposed; +} + +#pragma mark Lifecycle + ++ (instancetype)compoundDisposable { + return [[self alloc] initWithDisposables:nil]; +} + ++ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables { + return [[self alloc] initWithDisposables:disposables]; +} + +- (id)initWithDisposables:(NSArray *)otherDisposables { + self = [self init]; + if (self == nil) return nil; + + #if RACCompoundDisposableInlineCount + [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) { + _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; +} + +- (id)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; + } +} + +#pragma mark Addition and Removal + +- (void)addDisposable:(RACDisposable *)disposable { + NSCParameterAssert(disposable != self); + if (disposable == nil) return; + + BOOL shouldDispose = NO; + + OSSpinLockLock(&_spinLock); + { + 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 + } + } + OSSpinLockUnlock(&_spinLock); + + // 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; + + OSSpinLockLock(&_spinLock); + { + 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); + } + } + } + } + OSSpinLockUnlock(&_spinLock); +} + +#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; + + OSSpinLockLock(&_spinLock); + { + _disposed = YES; + + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + inlineCopy[i] = _inlineDisposables[i]; + _inlineDisposables[i] = nil; + } + #endif + + remainingDisposables = _disposables; + _disposables = NULL; + } + OSSpinLockUnlock(&_spinLock); + + #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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposableProvider.d b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposableProvider.d new file mode 100644 index 0000000..847db19 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposableProvider.d
@@ -0,0 +1,4 @@ +provider RACCompoundDisposable { + probe added(char *compoundDisposable, char *disposable, long newTotal); + probe removed(char *compoundDisposable, char *disposable, long newTotal); +};
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.h new file mode 100644 index 0000000..9ec96bd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.h
@@ -0,0 +1,28 @@ +// +// RACDelegateProxy.h +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.m new file mode 100644 index 0000000..1d4fcbc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.m
@@ -0,0 +1,78 @@ +// +// RACDelegateProxy.m +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "NSObject+RACSelectorSignal.h" +#import "NSObject+RACDeallocating.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]; + if (self == nil) return nil; + + 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h new file mode 100644 index 0000000..1267d44 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h
@@ -0,0 +1,36 @@ +// +// RACDisposable.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACScopedDisposable; + + +/// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.m new file mode 100644 index 0000000..08eceb2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.m
@@ -0,0 +1,92 @@ +// +// RACDisposable.m +// ReactiveCocoa +// +// 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 + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + _disposeBlock = (__bridge void *)self; + OSMemoryBarrier(); + + return self; +} + +- (id)initWithBlock:(void (^)(void))block { + NSCParameterAssert(block != nil); + + self = [super init]; + if (self == nil) return nil; + + _disposeBlock = (void *)CFBridgingRetain([block copy]); + OSMemoryBarrier(); + + return self; +} + ++ (instancetype)disposableWithBlock:(void (^)(void))block { + return [[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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.h new file mode 100644 index 0000000..1e103e3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.h
@@ -0,0 +1,20 @@ +// +// RACDynamicSequence.h +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.m new file mode 100644 index 0000000..48a977b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.m
@@ -0,0 +1,197 @@ +// +// RACDynamicSequence.m +// ReactiveCocoa +// +// 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 *(^)(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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.h new file mode 100644 index 0000000..81ac6db --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.h
@@ -0,0 +1,17 @@ +// +// RACDynamicSignal.h +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.m new file mode 100644 index 0000000..7af1e12 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.m
@@ -0,0 +1,189 @@ +// +// RACDynamicSignal.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACDynamicSignal.h" +#import "RACEXTScope.h" +#import "RACPassthroughSubscriber.h" +#import "RACScheduler+Private.h" +#import "RACSubscriber.h" +#import "RACCompoundDisposable.h" +#import <libkern/OSAtomic.h> + +// Retains dynamic signals while they wait for subscriptions. +// +// This set must only be used on the main thread. +static CFMutableSetRef RACActiveSignals = nil; + +// A linked list of RACDynamicSignals, used in RACActiveSignalsToCheck. +typedef struct RACSignalList { + CFTypeRef retainedSignal; + struct RACSignalList * restrict next; +} RACSignalList; + +// An atomic queue of signals to check for subscribers. If any signals with zero +// subscribers are found in this queue, they are removed from RACActiveSignals. +static OSQueueHead RACActiveSignalsToCheck = OS_ATOMIC_QUEUE_INIT; + +// Whether RACActiveSignalsToCheck will be enumerated on the next iteration on +// the main run loop. +static volatile uint32_t RACWillCheckActiveSignals = 0; + +@interface RACDynamicSignal () { + // Contains all subscribers to the receiver. + // + // All access to this array must be synchronized using `_subscribersLock`. + NSMutableArray *_subscribers; + + // Synchronizes access to `_subscribers`. + OSSpinLock _subscribersLock; +} + +// The block to invoke for each subscriber. +@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber); + +@end + +@implementation RACDynamicSignal + +#pragma mark Lifecycle + ++ (void)initialize { + if (self != RACDynamicSignal.class) return; + + CFSetCallBacks callbacks = kCFTypeSetCallBacks; + + // Use pointer equality and hashes for membership testing. + callbacks.equal = NULL; + callbacks.hash = NULL; + + RACActiveSignals = CFSetCreateMutable(NULL, 0, &callbacks); +} + ++ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { + RACDynamicSignal *signal = [[self alloc] init]; + signal->_didSubscribe = [didSubscribe copy]; + return [signal setNameWithFormat:@"+createSignal:"]; +} + +- (instancetype)init { + self = [super init]; + if (self == nil) return nil; + + // As soon as we're created we're already trying to be released. Such is life. + [self invalidateGlobalRefIfNoNewSubscribersShowUp]; + + return self; +} + +static void RACCheckActiveSignals(void) { + // Clear this flag now, so another thread can re-dispatch to the main queue + // as needed. + OSAtomicAnd32Barrier(0, &RACWillCheckActiveSignals); + + RACSignalList * restrict elem; + + while ((elem = OSAtomicDequeue(&RACActiveSignalsToCheck, offsetof(RACSignalList, next))) != NULL) { + RACDynamicSignal *signal = CFBridgingRelease(elem->retainedSignal); + free(elem); + + if (signal.hasSubscribers) { + // We want to keep the signal around until all its subscribers are done + CFSetAddValue(RACActiveSignals, (__bridge void *)signal); + } else { + CFSetRemoveValue(RACActiveSignals, (__bridge void *)signal); + } + } +} + +- (void)invalidateGlobalRefIfNoNewSubscribersShowUp { + // If no one subscribes in one pass of the main run loop, then we're free to + // go. It's up to the caller to keep us alive if they still want us. + RACSignalList *elem = malloc(sizeof(*elem)); + + // This also serves to retain the signal until the next pass. + elem->retainedSignal = CFBridgingRetain(self); + OSAtomicEnqueue(&RACActiveSignalsToCheck, elem, offsetof(RACSignalList, next)); + + // Not using a barrier because duplicate scheduling isn't erroneous, just + // less optimized. + int32_t willCheck = OSAtomicOr32Orig(1, &RACWillCheckActiveSignals); + + // Only schedule a check if RACWillCheckActiveSignals was 0 before. + if (willCheck == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + RACCheckActiveSignals(); + }); + } +} + +#pragma mark Managing Subscribers + +- (BOOL)hasSubscribers { + OSSpinLockLock(&_subscribersLock); + BOOL hasSubscribers = _subscribers.count > 0; + OSSpinLockUnlock(&_subscribersLock); + + return hasSubscribers; +} + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCParameterAssert(subscriber != nil); + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; + + OSSpinLockLock(&_subscribersLock); + if (_subscribers == nil) { + _subscribers = [NSMutableArray arrayWithObject:subscriber]; + } else { + [_subscribers addObject:subscriber]; + } + OSSpinLockUnlock(&_subscribersLock); + + @weakify(self); + RACDisposable *defaultDisposable = [RACDisposable disposableWithBlock:^{ + @strongify(self); + if (self == nil) return; + + BOOL stillHasSubscribers = YES; + + OSSpinLockLock(&_subscribersLock); + { + // Since newer subscribers are generally shorter-lived, search + // starting from the end of the list. + NSUInteger index = [_subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) { + return obj == subscriber; + }]; + + if (index != NSNotFound) { + [_subscribers removeObjectAtIndex:index]; + stillHasSubscribers = _subscribers.count > 0; + } + } + OSSpinLockUnlock(&_subscribersLock); + + if (!stillHasSubscribers) { + [self invalidateGlobalRefIfNoNewSubscribersShowUp]; + } + }]; + + [disposable addDisposable:defaultDisposable]; + + if (self.didSubscribe != NULL) { + RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ + RACDisposable *innerDisposable = self.didSubscribe(subscriber); + [disposable addDisposable:innerDisposable]; + }]; + + [disposable addDisposable:schedulingDisposable]; + } + + return disposable; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.h new file mode 100644 index 0000000..ab5aa9f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.h
@@ -0,0 +1,14 @@ +// +// RACEagerSequence.h +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.m new file mode 100644 index 0000000..f12fbe9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.m
@@ -0,0 +1,66 @@ +// +// RACEagerSequence.m +// ReactiveCocoa +// +// 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 + ++ (instancetype)return:(id)value { + return [[self sequenceWithArray:@[ value ] offset:0] setNameWithFormat:@"+return: %@", [value rac_description]]; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(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]; +} + +- (instancetype)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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.h new file mode 100644 index 0000000..140c78b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.h
@@ -0,0 +1,14 @@ +// +// RACEmptySequence.h +// ReactiveCocoa +// +// 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 + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.m new file mode 100644 index 0000000..e4df150 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.m
@@ -0,0 +1,71 @@ +// +// RACEmptySequence.m +// ReactiveCocoa +// +// 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; +} + +- (id)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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.h new file mode 100644 index 0000000..90b4f00 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.h
@@ -0,0 +1,17 @@ +// +// RACEmptySignal.h +// ReactiveCocoa +// +// 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 : RACSignal + ++ (RACSignal *)empty; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.m new file mode 100644 index 0000000..8bc8f41 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.m
@@ -0,0 +1,62 @@ +// +// RACEmptySignal.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.h new file mode 100644 index 0000000..c942f9e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.h
@@ -0,0 +1,17 @@ +// +// RACErrorSignal.h +// ReactiveCocoa +// +// 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 an error to any +// subscribers. +@interface RACErrorSignal : RACSignal + ++ (RACSignal *)error:(NSError *)error; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.m new file mode 100644 index 0000000..fd6778c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.m
@@ -0,0 +1,47 @@ +// +// RACErrorSignal.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h new file mode 100644 index 0000000..9e64e5a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h
@@ -0,0 +1,51 @@ +// +// RACEvent.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-01-07. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +/// Describes the type of a RACEvent. +/// +/// RACEventTypeCompleted - A `completed` event. +/// RACEventTypeError - An `error` event. +/// RACEventTypeNext - A `next` event. +typedef enum : NSUInteger { + RACEventTypeCompleted, + RACEventTypeError, + RACEventTypeNext +} RACEventType; + +/// Represents an event sent by a RACSignal. +/// +/// This corresponds to the `Notification` class in Rx. +@interface RACEvent : NSObject <NSCopying> + +/// Returns a singleton RACEvent representing the `completed` event. ++ (instancetype)completedEvent; + +/// Returns a new event of type RACEventTypeError, containing the given error. ++ (instancetype)eventWithError:(NSError *)error; + +/// Returns a new event of type RACEventTypeNext, containing the given value. ++ (instancetype)eventWithValue:(id)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) NSError *error; + +/// The value associated with an event of type RACEventTypeNext. This will be +/// nil for all other event types. +@property (nonatomic, strong, readonly) id value; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.m new file mode 100644 index 0000000..270a95d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.m
@@ -0,0 +1,113 @@ +// +// RACEvent.m +// ReactiveCocoa +// +// 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. +- (id)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]; +} + +- (id)initWithEventType:(RACEventType)type object:(id)object { + self = [super init]; + if (self == nil) return nil; + + _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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.h new file mode 100644 index 0000000..697d922 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.h
@@ -0,0 +1,20 @@ +// +// RACGroupedSignal.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" + + +/// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.m new file mode 100644 index 0000000..00e0378 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.m
@@ -0,0 +1,25 @@ +// +// RACGroupedSignal.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.h new file mode 100644 index 0000000..76b5b50 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.h
@@ -0,0 +1,14 @@ +// +// RACImmediateScheduler.h +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.m new file mode 100644 index 0000000..f32eda3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.m
@@ -0,0 +1,44 @@ +// +// RACImmediateScheduler.m +// ReactiveCocoa +// +// 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 + +- (id)init { + return [super initWithName:@"com.ReactiveCocoa.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; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.h new file mode 100644 index 0000000..eab84a3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.h
@@ -0,0 +1,16 @@ +// +// RACIndexSetSequence.h +// ReactiveCocoa +// +// 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 + ++ (instancetype)sequenceWithIndexSet:(NSIndexSet *)indexSet; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.m new file mode 100644 index 0000000..7ac8f53 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.m
@@ -0,0 +1,108 @@ +// +// RACIndexSetSequence.m +// ReactiveCocoa +// +// 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 + ++ (instancetype)sequenceWithIndexSet:(NSIndexSet *)indexSet { + NSUInteger count = indexSet.count; + 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.h b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.h new file mode 100644 index 0000000..060a4cc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.h
@@ -0,0 +1,97 @@ +// +// RACKVOChannel.h +// ReactiveCocoa +// +// Created by Uri Baghin on 27/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACChannel.h" +#import "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)] + +/// A RACChannel that observes a KVO-compliant key path for changes. +@interface RACKVOChannel : RACChannel + +/// 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`). +- (id)initWithTarget:(NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue; + +- (id)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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.m b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.m new file mode 100644 index 0000000..e0d1a82 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.m
@@ -0,0 +1,206 @@ +// +// RACKVOChannel.m +// ReactiveCocoa +// +// Created by Uri Baghin on 27/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACKVOChannel.h" +#import "RACEXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACKVOWrapper.h" +#import "NSObject+RACPropertySubscribing.h" +#import "NSString+RACKeyPathUtilities.h" +#import "RACChannel.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACReplaySubject.h" +#import "RACSignal+Operations.h" +#import "RACSubscriber+Private.h" +#import "RACSubject.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, unsafe_unretained) 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 + +- (id)initWithTarget:(NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue { + NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); + + self = [super init]; + if (self == nil) return nil; + + _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]; + + // 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 = [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change) { + // 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 (![change[RACKeyValueChangeCausedByDeallocationKey] boolValue] && [change[RACKeyValueChangeAffectedOnlyLastComponentKey] boolValue] && 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); + + [target.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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.h b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.h new file mode 100644 index 0000000..5701486 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.h
@@ -0,0 +1,31 @@ +// +// RACKVOTrampoline.h +// ReactiveCocoa +// +// 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. +- (id)initWithTarget:(NSObject *)target observer:(NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.m b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.m new file mode 100644 index 0000000..1e4feae --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.m
@@ -0,0 +1,100 @@ +// +// RACKVOTrampoline.m +// ReactiveCocoa +// +// 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" + +static void *RACKVOWrapperContext = &RACKVOWrapperContext; + +@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 *target; +@property (nonatomic, readonly, unsafe_unretained) NSObject *observer; + +@end + +@implementation RACKVOTrampoline + +#pragma mark Lifecycle + +- (id)initWithTarget:(NSObject *)target observer:(NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { + NSCParameterAssert(target != nil); + NSCParameterAssert(keyPath != nil); + NSCParameterAssert(block != nil); + + self = [super init]; + if (self == nil) return nil; + + _keyPath = [keyPath copy]; + + _block = [block copy]; + _target = target; + _observer = observer; + + [self.target addObserver:self forKeyPath:self.keyPath options:options context:&RACKVOWrapperContext]; + [self.target.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; + + target = self.target; + observer = self.observer; + + _target = nil; + _observer = nil; + } + + [target.rac_deallocDisposable removeDisposable:self]; + [observer.rac_deallocDisposable removeDisposable:self]; + + [target removeObserver:self forKeyPath:self.keyPath context:&RACKVOWrapperContext]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context != &RACKVOWrapperContext) { + [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.target; + } + + if (block == nil) return; + + block(target, observer, change); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection+Private.h new file mode 100644 index 0000000..43931f8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection+Private.h
@@ -0,0 +1,17 @@ +// +// RACMulticastConnection+Private.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACMulticastConnection.h" + +@class RACSubject; + +@interface RACMulticastConnection () + +- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h new file mode 100644 index 0000000..5361f5d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h
@@ -0,0 +1,46 @@ +// +// RACMulticastConnection.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +@class RACSignal; +@class RACDisposable; + +/// 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 : NSObject + +/// The multicasted signal. +@property (nonatomic, strong, readonly) RACSignal *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 *)autoconnect; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.m b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.m new file mode 100644 index 0000000..1534d96 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.m
@@ -0,0 +1,85 @@ +// +// RACMulticastConnection.m +// ReactiveCocoa +// +// 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 + +- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject { + NSCParameterAssert(source != nil); + NSCParameterAssert(subject != nil); + + self = [super init]; + if (self == nil) return nil; + + _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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.h b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.h new file mode 100644 index 0000000..fbec61c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.h
@@ -0,0 +1,17 @@ +// +// RACObjCRuntime.h +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +// A private class containing wrappers to runtime functions. +@interface RACObjCRuntime : NSObject + +// Invokes objc_allocateClassPair(). Can be called from ARC code. ++ (Class)createClass:(const char *)className inheritingFromClass:(Class)superclass; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.m b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.m new file mode 100644 index 0000000..43cfc19 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.m
@@ -0,0 +1,22 @@ +// +// RACObjCRuntime.m +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACObjCRuntime.h" +#import <objc/runtime.h> + +#if __has_feature(objc_arc) +#error "This file must be compiled without ARC." +#endif + +@implementation RACObjCRuntime + ++ (Class)createClass:(const char *)className inheritingFromClass:(Class)superclass { + return objc_allocateClassPair(superclass, className, 0); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.h b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.h new file mode 100644 index 0000000..9ae547b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.h
@@ -0,0 +1,29 @@ +// +// RACPassthroughSubscriber.h +// ReactiveCocoa +// +// 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; + +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.m b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.m new file mode 100644 index 0000000..614d7d5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.m
@@ -0,0 +1,103 @@ +// +// RACPassthroughSubscriber.m +// ReactiveCocoa +// +// 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" + +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); +} + +@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]; + if (self == nil) return nil; + + _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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler+Subclass.h b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler+Subclass.h new file mode 100644 index 0000000..48ef636 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler+Subclass.h
@@ -0,0 +1,46 @@ +// +// RACQueueScheduler+Subclass.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/6/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACQueueScheduler.h" + +/// An interface for use by subclasses. +/// +/// Subclasses should use `-performAsCurrentScheduler:` to do the actual block +/// invocation so that +[RACScheduler currentScheduler] behaves as expected. +/// +/// **Note that RACSchedulers are expected to be serial**. Subclasses must honor +/// that contract. See `RACTargetQueueScheduler` for a queue-based scheduler +/// which will enforce the serialization guarantee. +@interface RACQueueScheduler () + +/// The queue on which blocks are enqueued. +@property (nonatomic, readonly) dispatch_queue_t queue; + +/// 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. +- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue; + +/// Performs the given block with the receiver as the current scheduler for +/// `queue`. This should only be called by subclasses to perform scheduled blocks +/// on their queue. +/// +/// block - The block to execute. Cannot be NULL. +- (void)performAsCurrentScheduler:(void (^)(void))block; + +/// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.h new file mode 100644 index 0000000..ef42512 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.h
@@ -0,0 +1,18 @@ +// +// RACQueueScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +/// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.m new file mode 100644 index 0000000..7ae8202 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.m
@@ -0,0 +1,120 @@ +// +// RACQueueScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACQueueScheduler.h" +#import "RACBacktrace.h" +#import "RACDisposable.h" +#import "RACQueueScheduler+Subclass.h" +#import "RACScheduler+Private.h" + +@implementation RACQueueScheduler + +#pragma mark Lifecycle + +- (void)dealloc { + dispatch_release(_queue); +} + +- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue { + NSCParameterAssert(queue != NULL); + + self = [super initWithName:name]; + if (self == nil) return nil; + + dispatch_retain(queue); + _queue = queue; + + return self; +} + +#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); + dispatch_release(timer); + }]; +} + +- (void)performAsCurrentScheduler:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + // If we're using a concurrent queue, we could end up in here concurrently, + // in which case we *don't* want to clear the current scheduler immediately + // after our block is done executing, but only *after* all our concurrent + // invocations are done. + + RACScheduler *previousScheduler = RACScheduler.currentScheduler; + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; + + block(); + + if (previousScheduler != nil) { + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; + } else { + [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; + } +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.h b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.h new file mode 100644 index 0000000..cd6f2ec --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.h
@@ -0,0 +1,23 @@ +// +// RACReplaySubject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/14/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" + +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 : RACSubject + +/// Creates a new replay subject with the given capacity. A capacity of +/// RACReplaySubjectUnlimitedCapacity means values are never trimmed. ++ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.m b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.m new file mode 100644 index 0000000..bca008e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.m
@@ -0,0 +1,112 @@ +// +// RACReplaySubject.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/14/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACReplaySubject.h" +#import "RACDisposable.h" +#import "RACScheduler+Private.h" +#import "RACSubscriber.h" +#import "RACTuple.h" +#import "RACCompoundDisposable.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 [[self alloc] initWithCapacity:capacity]; +} + +- (instancetype)init { + return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity]; +} + +- (instancetype)initWithCapacity:(NSUInteger)capacity { + self = [super init]; + if (self == nil) return nil; + + _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 isKindOfClass:RACTupleNil.class] ? 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]; + [super sendNext:value]; + + if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) { + [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)]; + } + } +} + +- (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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.h new file mode 100644 index 0000000..73e5674 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.h
@@ -0,0 +1,17 @@ +// +// RACReturnSignal.h +// ReactiveCocoa +// +// 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 : RACSignal + ++ (RACSignal *)return:(id)value; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.m new file mode 100644 index 0000000..3006634 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.m
@@ -0,0 +1,90 @@ +// +// RACReturnSignal.m +// ReactiveCocoa +// +// 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
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler+Private.h new file mode 100644 index 0000000..2c91e66 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler+Private.h
@@ -0,0 +1,34 @@ +// +// RACScheduler+Private.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/29/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +// The thread-specific current scheduler key. +extern NSString * const RACSchedulerCurrentSchedulerKey; + +// A private interface for internal RAC use only. +@interface RACScheduler () + +// A dedicated scheduler that fills two requirements: +// +// 1. By the time subscription happens, we need a valid +currentScheduler. +// 2. Subscription should happen as soon as possible. +// +// To fulfill those two, if we already have a valid +currentScheduler, it +// immediately executes scheduled blocks. If we don't, it will execute scheduled +// blocks with a private background scheduler. ++ (instancetype)subscriptionScheduler; + +// Initializes the receiver with the given name. +// +// name - The name of the scheduler. If nil, a default name will be used. +// +// Returns the initialized object. +- (id)initWithName:(NSString *)name; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h new file mode 100644 index 0000000..ce1ee32 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h
@@ -0,0 +1,148 @@ +// +// RACScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +/// The priority for the scheduler. +/// +/// RACSchedulerPriorityHigh - High priority. +/// RACSchedulerPriorityDefault - Default priority. +/// RACSchedulerPriorityLow - Low priority. +/// RACSchedulerPriorityBackground - Background priority. +typedef enum : long { + RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH, + RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT, + RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW, + RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND, +} RACSchedulerPriority; + +/// Scheduled with -scheduleRecursiveBlock:, this type of block is passed a block +/// with which it can call itself recursively. +typedef void (^RACSchedulerRecursiveBlock)(void (^reschedule)(void)); + +@class RACDisposable; + +/// Schedulers are used to control when and where work is performed. +@interface RACScheduler : NSObject + +/// A singleton scheduler that immediately executes the blocks it is given. +/// +/// **Note:** Unlike most other schedulers, this does not set the current +/// scheduler. There may still be a valid +currentScheduler if this is used +/// within a block scheduled on a different scheduler. ++ (RACScheduler *)immediateScheduler; + +/// A singleton scheduler that executes blocks in the main thread. ++ (RACScheduler *)mainThreadScheduler; + +/// Creates and returns a new background scheduler with the given priority and +/// name. The name is for debug and instrumentation purposes only. +/// +/// Scheduler creation is cheap. It's unnecessary to save the result of this +/// method call unless you want to serialize some actions on the same background +/// scheduler. ++ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name; + +/// Invokes +schedulerWithPriority:name: with a default name. ++ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority; + +/// Invokes +schedulerWithPriority: with RACSchedulerPriorityDefault. ++ (RACScheduler *)scheduler; + +/// The current scheduler. This will only be valid when used from within a +/// -[RACScheduler schedule:] block or when on the main thread. ++ (RACScheduler *)currentScheduler; + +/// Schedule the given block for execution on the scheduler. +/// +/// Scheduled blocks will be executed in the order in which they were scheduled. +/// +/// block - The block to schedule for execution. Cannot be nil. +/// +/// Returns a disposable which can be used to cancel the scheduled block before +/// it begins executing, or nil if cancellation is not supported. +- (RACDisposable *)schedule:(void (^)(void))block; + +/// Schedule the given block for execution on the scheduler at or after +/// a specific time. +/// +/// Note that blocks scheduled for a certain time will not preempt any other +/// scheduled work that is executing at the time. +/// +/// When invoked on the +immediateScheduler, the calling thread **will block** +/// until the specified time. +/// +/// date - The earliest time at which `block` should begin executing. The block +/// may not execute immediately at this time, whether due to system load +/// or another block on the scheduler currently being run. Cannot be nil. +/// block - The block to schedule for execution. Cannot be nil. +/// +/// Returns a disposable which can be used to cancel the scheduled block before +/// it begins executing, or nil if cancellation is not supported. +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block; + +/// Schedule the given block for execution on the scheduler after the delay. +/// +/// Converts the delay into an NSDate, then invokes `-after:schedule:`. +- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block; + +/// Reschedule the given block at a particular interval, starting at a specific +/// time, and with a given leeway for deferral. +/// +/// Note that blocks scheduled for a certain time will not preempt any other +/// scheduled work that is executing at the time. +/// +/// Regardless of the value of `leeway`, the given block may not execute exactly +/// at `when` or exactly on successive intervals, whether due to system load or +/// because another block is currently being run on the scheduler. +/// +/// It is considered undefined behavior to invoke this method on the +/// +immediateScheduler. +/// +/// date - The earliest time at which `block` should begin executing. The +/// block may not execute immediately at this time, whether due to +/// system load or another block on the scheduler currently being +/// run. Cannot be nil. +/// interval - The interval at which the block should be rescheduled, starting +/// from `date`. This will use the system wall clock, to avoid +/// skew when the computer goes to sleep. +/// leeway - A hint to the system indicating the number of seconds that each +/// scheduling can be deferred. Note that this is just a hint, and +/// there may be some additional latency no matter what. +/// block - The block to repeatedly schedule for execution. Cannot be nil. +/// +/// Returns a disposable which can be used to cancel the automatic scheduling and +/// rescheduling, or nil if cancellation is not supported. +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block; + +/// Schedule the given recursive block for execution on the scheduler. The +/// scheduler will automatically flatten any recursive scheduling into iteration +/// instead, so this can be used without issue for blocks that may keep invoking +/// themselves forever. +/// +/// Scheduled blocks will be executed in the order in which they were scheduled. +/// +/// recursiveBlock - The block to schedule for execution. When invoked, the +/// recursive block will be passed a `void (^)(void)` block +/// which will reschedule the recursive block at the end of the +/// receiver's queue. This passed-in block will automatically +/// skip scheduling if the scheduling of the `recursiveBlock` +/// was disposed in the meantime. +/// +/// Returns a disposable which can be used to cancel the scheduled block before +/// it begins executing, or to stop it from rescheduling if it's already begun +/// execution. +- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock; + +@end + +@interface RACScheduler (Deprecated) + ++ (RACScheduler *)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name __attribute__((deprecated("Use -[RACScheduler initWithName:targetQueue:] instead."))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.m new file mode 100644 index 0000000..c829896 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.m
@@ -0,0 +1,206 @@ +// +// RACScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" +#import "RACBacktrace.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACImmediateScheduler.h" +#import "RACScheduler+Private.h" +#import "RACSubscriptionScheduler.h" +#import "RACTargetQueueScheduler.h" + +// The key for the thread-specific current scheduler. +NSString * const RACSchedulerCurrentSchedulerKey = @"RACSchedulerCurrentSchedulerKey"; + +@interface RACScheduler () +@property (nonatomic, readonly, copy) NSString *name; +@end + +@implementation RACScheduler + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.name]; +} + +#pragma mark Initializers + +- (id)initWithName:(NSString *)name { + self = [super init]; + if (self == nil) return nil; + + if (name == nil) { + _name = [NSString stringWithFormat:@"com.ReactiveCocoa.%@.anonymousScheduler", self.class]; + } else { + _name = [name copy]; + } + + return self; +} + +#pragma mark Schedulers + ++ (instancetype)immediateScheduler { + static dispatch_once_t onceToken; + static RACScheduler *immediateScheduler; + dispatch_once(&onceToken, ^{ + immediateScheduler = [[RACImmediateScheduler alloc] init]; + }); + + return immediateScheduler; +} + ++ (instancetype)mainThreadScheduler { + static dispatch_once_t onceToken; + static RACScheduler *mainThreadScheduler; + dispatch_once(&onceToken, ^{ + mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"com.ReactiveCocoa.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()]; + }); + + return mainThreadScheduler; +} + ++ (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name { + return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)]; +} + ++ (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority { + return [self schedulerWithPriority:priority name:@"com.ReactiveCocoa.RACScheduler.backgroundScheduler"]; +} + ++ (instancetype)scheduler { + return [self schedulerWithPriority:RACSchedulerPriorityDefault]; +} + ++ (instancetype)subscriptionScheduler { + static dispatch_once_t onceToken; + static RACScheduler *subscriptionScheduler; + dispatch_once(&onceToken, ^{ + subscriptionScheduler = [[RACSubscriptionScheduler alloc] init]; + }); + + return subscriptionScheduler; +} + ++ (BOOL)isOnMainThread { + return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread]; +} + ++ (instancetype)currentScheduler { + RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey]; + if (scheduler != nil) return scheduler; + if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler; + + return nil; +} + +#pragma mark Scheduling + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; +} + +- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block { + return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block]; +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; +} + +- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable]; + return disposable; +} + +- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable { + @autoreleasepool { + RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; + [disposable addDisposable:selfDisposable]; + + __weak RACDisposable *weakSelfDisposable = selfDisposable; + + RACDisposable *schedulingDisposable = [self schedule:^{ + @autoreleasepool { + // At this point, we've been invoked, so our disposable is now useless. + [disposable removeDisposable:weakSelfDisposable]; + } + + if (disposable.disposed) return; + + void (^reallyReschedule)(void) = ^{ + if (disposable.disposed) return; + [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable]; + }; + + // Protects the variables below. + // + // This doesn't actually need to be __block qualified, but Clang + // complains otherwise. :C + __block NSLock *lock = [[NSLock alloc] init]; + lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)]; + + __block NSUInteger rescheduleCount = 0; + + // Set to YES once synchronous execution has finished. Further + // rescheduling should occur immediately (rather than being + // flattened). + __block BOOL rescheduleImmediately = NO; + + @autoreleasepool { + recursiveBlock(^{ + [lock lock]; + BOOL immediate = rescheduleImmediately; + if (!immediate) ++rescheduleCount; + [lock unlock]; + + if (immediate) reallyReschedule(); + }); + } + + [lock lock]; + NSUInteger synchronousCount = rescheduleCount; + rescheduleImmediately = YES; + [lock unlock]; + + for (NSUInteger i = 0; i < synchronousCount; i++) { + reallyReschedule(); + } + }]; + + [selfDisposable addDisposable:schedulingDisposable]; + } +} + +@end + +@implementation RACScheduler (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + ++ (instancetype)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name { + NSCParameterAssert(queue != NULL); + + return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:queue]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.h new file mode 100644 index 0000000..03da6a2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.h
@@ -0,0 +1,19 @@ +// +// RACScopedDisposable.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" + + +/// A disposable that calls its own -dispose when it is dealloc'd. +@interface RACScopedDisposable : RACDisposable + +/// Creates a new scoped disposable that will also dispose of the given +/// disposable when it is dealloc'd. ++ (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.m new file mode 100644 index 0000000..91115be --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.m
@@ -0,0 +1,32 @@ +// +// RACScopedDisposable.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScopedDisposable.h" + +@implementation RACScopedDisposable + +#pragma mark Lifecycle + ++ (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable { + return [self disposableWithBlock:^{ + [disposable dispose]; + }]; +} + +- (void)dealloc { + [self dispose]; +} + +#pragma mark RACDisposable + +- (RACScopedDisposable *)asScopedDisposable { + // totally already are + return self; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h new file mode 100644 index 0000000..a39f840 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h
@@ -0,0 +1,154 @@ +// +// RACSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACStream.h" + +@class RACScheduler; +@class RACSignal; + +/// Represents an immutable sequence of values. Unless otherwise specified, the +/// sequences' values are evaluated lazily on demand. Like Cocoa collections, +/// sequences cannot contain nil. +/// +/// Most inherited RACStream methods that accept a block will execute the block +/// _at most_ once for each value that is evaluated in the returned sequence. +/// Side effects are subject to the behavior described in +/// +sequenceWithHeadBlock:tailBlock:. +/// +/// Implemented as a class cluster. A minimal implementation for a subclass +/// consists simply of -head and -tail. +@interface RACSequence : RACStream <NSCoding, NSCopying, NSFastEnumeration> + +/// The first object in the sequence, or nil if the sequence is empty. +/// +/// Subclasses must provide an implementation of this method. +@property (nonatomic, strong, readonly) id head; + +/// All but the first object in the sequence, or nil if the sequence is empty. +/// +/// Subclasses must provide an implementation of this method. +@property (nonatomic, strong, readonly) RACSequence *tail; + +/// Evaluates the full sequence to produce an equivalently-sized array. +@property (nonatomic, copy, readonly) NSArray *array; + +/// Returns an enumerator of all objects in the sequence. +@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator; + +/// Converts a sequence into an eager sequence. +/// +/// An eager sequence fully evaluates all of its values immediately. Sequences +/// derived from an eager sequence will also be eager. +/// +/// Returns a new eager sequence, or the receiver if the sequence is already +/// eager. +@property (nonatomic, copy, readonly) RACSequence *eagerSequence; + +/// Converts a sequence into a lazy sequence. +/// +/// A lazy sequence evaluates its values on demand, as they are accessed. +/// Sequences derived from a lazy sequence will also be lazy. +/// +/// Returns a new lazy sequence, or the receiver if the sequence is already lazy. +@property (nonatomic, copy, readonly) RACSequence *lazySequence; + +/// Invokes -signalWithScheduler: with a new RACScheduler. +- (RACSignal *)signal; + +/// Evaluates the full sequence on the given scheduler. +/// +/// Each item is evaluated in its own scheduled block, such that control of the +/// scheduler is yielded between each value. +/// +/// Returns a signal which sends the receiver's values on the given scheduler as +/// they're evaluated. +- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler; + +/// Applies a left fold to the sequence. +/// +/// This is the same as iterating the sequence along with a provided start value. +/// This uses a constant amount of memory. A left fold is left-associative so in +/// the sequence [1,2,3] the block would applied in the following order: +/// reduce(reduce(reduce(start, 1), 2), 3) +/// +/// start - The starting value for the fold. Used as `accumulator` for the +/// first fold. +/// reduce - The block used to combine the accumulated value and the next value. +/// Cannot be nil. +/// +/// Returns a reduced value. +- (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce; + +/// Applies a right fold to the sequence. +/// +/// A right fold is equivalent to recursion on the list. The block is evaluated +/// from the right to the left in list. It is right associative so it's applied +/// to the rightmost elements first. For example, in the sequence [1,2,3] the +/// block is applied in the order: +/// reduce(1, reduce(2, reduce(3, start))) +/// +/// start - The starting value for the fold. +/// reduce - The block used to combine the accumulated value and the next head. +/// The block is given the accumulated value and the value of the rest +/// of the computation (result of the recursion). This is computed when +/// you retrieve its value using `rest.head`. This allows you to +/// prevent unnecessary computation by not accessing `rest.head` if you +/// don't need to. +/// +/// Returns a reduced value. +- (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce; + +/// Check if any value in sequence passes the block. +/// +/// block - The block predicate used to check each item. Cannot be nil. +/// +/// Returns a boolean indiciating if any value in the sequence passed. +- (BOOL)any:(BOOL (^)(id value))block; + +/// Check if all values in the sequence pass the block. +/// +/// block - The block predicate used to check each item. Cannot be nil. +/// +/// Returns a boolean indicating if all values in the sequence passed. +- (BOOL)all:(BOOL (^)(id value))block; + +/// Returns the first object that passes the block. +/// +/// block - The block predicate used to check each item. Cannot be nil. +/// +/// Returns an object that passes the block or nil if no objects passed. +- (id)objectPassingTest:(BOOL (^)(id value))block; + +/// Creates a sequence that dynamically generates its values. +/// +/// headBlock - Invoked the first time -head is accessed. +/// tailBlock - Invoked the first time -tail is accessed. +/// +/// The results from each block are memoized, so each block will be invoked at +/// most once, no matter how many times the head and tail properties of the +/// sequence are accessed. +/// +/// Any side effects in `headBlock` or `tailBlock` should be thread-safe, since +/// the sequence may be evaluated at any time from any thread. Not only that, but +/// -tail may be accessed before -head, or both may be accessed simultaneously. +/// As noted above, side effects will only be triggered the _first_ time -head or +/// -tail is invoked. +/// +/// Returns a sequence that lazily invokes the given blocks to provide head and +/// tail. `headBlock` must not be nil. ++ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock; + +@end + +@interface RACSequence (Deprecated) + +- (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine __attribute__((deprecated("Renamed to -foldLeftWithStart:reduce:"))); +- (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine __attribute__((deprecated("Renamed to -foldRightWithStart:reduce:"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.m new file mode 100644 index 0000000..9567ea8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.m
@@ -0,0 +1,384 @@ +// +// RACSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACSequence.h" +#import "RACArraySequence.h" +#import "RACDynamicSequence.h" +#import "RACEagerSequence.h" +#import "RACEmptySequence.h" +#import "RACScheduler.h" +#import "RACSignal.h" +#import "RACSubscriber.h" +#import "RACTuple.h" +#import "RACUnarySequence.h" + +// An enumerator over sequences. +@interface RACSequenceEnumerator : NSEnumerator + +// The sequence the enumerator is enumerating. +// +// This will change as the enumerator is exhausted. This property should only be +// accessed while synchronized on self. +@property (nonatomic, strong) RACSequence *sequence; + +@end + +@interface RACSequence () + +// Performs one iteration of lazy binding, passing through values from `current` +// until the sequence is exhausted, then recursively binding the remaining +// values in the receiver. +// +// Returns a new sequence which contains `current`, followed by the combined +// result of all applications of `block` to the remaining values in the receiver. +- (instancetype)bind:(RACStreamBindBlock)block passingThroughValuesFromSequence:(RACSequence *)current; + +@end + +@implementation RACSequenceEnumerator + +- (id)nextObject { + id object = nil; + + @synchronized (self) { + object = self.sequence.head; + self.sequence = self.sequence.tail; + } + + return object; +} + +@end + +@implementation RACSequence + +#pragma mark Lifecycle + ++ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock { + return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"]; +} + +#pragma mark Class cluster primitives + +- (id)head { + NSCAssert(NO, @"%s must be overridden by subclasses", __func__); + return nil; +} + +- (RACSequence *)tail { + NSCAssert(NO, @"%s must be overridden by subclasses", __func__); + return nil; +} + +#pragma mark RACStream + ++ (instancetype)empty { + return RACEmptySequence.empty; +} + ++ (instancetype)return:(id)value { + return [RACUnarySequence return:value]; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(void))block { + RACStreamBindBlock bindBlock = block(); + return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name]; +} + +- (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence { + // Store values calculated in the dependency here instead, avoiding any kind + // of temporary collection and boxing. + // + // This relies on the implementation of RACDynamicSequence synchronizing + // access to its head, tail, and dependency, and we're only doing it because + // we really need the performance. + __block RACSequence *valuesSeq = self; + __block RACSequence *current = passthroughSequence; + __block BOOL stop = NO; + + RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id { + while (current.head == nil) { + if (stop) return nil; + + // We've exhausted the current sequence, create a sequence from the + // next value. + id value = valuesSeq.head; + + if (value == nil) { + // We've exhausted all the sequences. + stop = YES; + return nil; + } + + current = (id)bindBlock(value, &stop); + if (current == nil) { + stop = YES; + return nil; + } + + valuesSeq = valuesSeq.tail; + } + + NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current); + return nil; + } headBlock:^(id _) { + return current.head; + } tailBlock:^ id (id _) { + if (stop) return nil; + + return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail]; + }]; + + sequence.name = self.name; + return sequence; +} + +- (instancetype)concat:(RACStream *)stream { + NSCParameterAssert(stream != nil); + + return [[[RACArraySequence sequenceWithArray:@[ self, stream ] offset:0] + flatten] + setNameWithFormat:@"[%@] -concat: %@", self.name, stream]; +} + +- (instancetype)zipWith:(RACSequence *)sequence { + NSCParameterAssert(sequence != nil); + + return [[RACSequence + sequenceWithHeadBlock:^ id { + if (self.head == nil || sequence.head == nil) return nil; + return RACTuplePack(self.head, sequence.head); + } tailBlock:^ id { + if (self.tail == nil || [[RACSequence empty] isEqual:self.tail]) return nil; + if (sequence.tail == nil || [[RACSequence empty] isEqual:sequence.tail]) return nil; + + return [self.tail zipWith:sequence.tail]; + }] + setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence]; +} + +#pragma mark Extended methods + +- (NSArray *)array { + NSMutableArray *array = [NSMutableArray array]; + for (id obj in self) { + [array addObject:obj]; + } + + return [array copy]; +} + +- (NSEnumerator *)objectEnumerator { + RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init]; + enumerator.sequence = self; + return enumerator; +} + +- (RACSignal *)signal { + return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name]; +} + +- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block RACSequence *sequence = self; + + return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) { + if (sequence.head == nil) { + [subscriber sendCompleted]; + return; + } + + [subscriber sendNext:sequence.head]; + + sequence = sequence.tail; + reschedule(); + }]; + }] setNameWithFormat:@"[%@] -signalWithScheduler:", self.name]; +} + +- (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce { + NSCParameterAssert(reduce != NULL); + + if (self.head == nil) return start; + + for (id value in self) { + start = reduce(start, value); + } + + return start; +} + +- (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *))reduce { + NSCParameterAssert(reduce != NULL); + + if (self.head == nil) return start; + + RACSequence *rest = [RACSequence sequenceWithHeadBlock:^{ + return [self.tail foldRightWithStart:start reduce:reduce]; + } tailBlock:nil]; + + return reduce(self.head, rest); +} + +- (BOOL)any:(BOOL (^)(id))block { + NSCParameterAssert(block != NULL); + + return [self objectPassingTest:block] != nil; +} + +- (BOOL)all:(BOOL (^)(id))block { + NSCParameterAssert(block != NULL); + + NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) { + return @(accumulator.boolValue && block(value)); + }]; + + return result.boolValue; +} + +- (id)objectPassingTest:(BOOL (^)(id))block { + NSCParameterAssert(block != NULL); + + return [self filter:block].head; +} + +- (RACSequence *)eagerSequence { + return [RACEagerSequence sequenceWithArray:self.array offset:0]; +} + +- (RACSequence *)lazySequence { + return self; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +#pragma mark NSCoding + +- (Class)classForCoder { + // Most sequences should be archived as RACArraySequences. + return RACArraySequence.class; +} + +- (id)initWithCoder:(NSCoder *)coder { + if (![self isKindOfClass:RACArraySequence.class]) return [[RACArraySequence alloc] initWithCoder:coder]; + + // Decoding is handled in RACArraySequence. + return [super init]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:self.array forKey:@"array"]; +} + +#pragma mark NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len { + if (state->state == ULONG_MAX) { + // Enumeration has completed. + return 0; + } + + // We need to traverse the sequence itself on repeated calls to this + // method, so use the 'state' field to track the current head. + RACSequence *(^getSequence)(void) = ^{ + return (__bridge RACSequence *)(void *)state->state; + }; + + void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) { + // Release the old sequence and retain the new one. + CFBridgingRelease((void *)state->state); + + state->state = (unsigned long)CFBridgingRetain(sequence); + }; + + void (^complete)(void) = ^{ + // Release any stored sequence. + setSequence(nil); + state->state = ULONG_MAX; + }; + + if (state->state == 0) { + // Since a sequence doesn't mutate, this just needs to be set to + // something non-NULL. + state->mutationsPtr = state->extra; + + setSequence(self); + } + + state->itemsPtr = stackbuf; + + NSUInteger enumeratedCount = 0; + while (enumeratedCount < len) { + RACSequence *seq = getSequence(); + + // Because the objects in a sequence may be generated lazily, we want to + // prevent them from being released until the enumerator's used them. + __autoreleasing id obj = seq.head; + if (obj == nil) { + complete(); + break; + } + + stackbuf[enumeratedCount++] = obj; + + if (seq.tail == nil) { + complete(); + break; + } + + setSequence(seq.tail); + } + + return enumeratedCount; +} + +#pragma mark NSObject + +- (NSUInteger)hash { + return [self.head hash]; +} + +- (BOOL)isEqual:(RACSequence *)seq { + if (self == seq) return YES; + if (![seq isKindOfClass:RACSequence.class]) return NO; + + for (id<NSObject> selfObj in self) { + id<NSObject> seqObj = seq.head; + + // Handles the nil case too. + if (![seqObj isEqual:selfObj]) return NO; + + seq = seq.tail; + } + + // self is now depleted -- the argument should be too. + return (seq.head == nil); +} + +@end + +@implementation RACSequence (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine { + return [self foldLeftWithStart:start reduce:combine]; +} + +- (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine { + return [self foldRightWithStart:start reduce:combine]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.h new file mode 100644 index 0000000..a3fc1d4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.h
@@ -0,0 +1,43 @@ +// +// RACSerialDisposable.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" + +/// A disposable that contains exactly one other disposable and allows it to be +/// swapped out atomically. +@interface RACSerialDisposable : RACDisposable + +/// The inner disposable managed by the serial disposable. +/// +/// This property is thread-safe for reading and writing. However, if you want to +/// read the current value _and_ write a new one atomically, use +/// -swapInDisposable: instead. +/// +/// Disposing of the receiver will also dispose of the current disposable set for +/// this property, then set the property to nil. If any new disposable is set +/// after the receiver is disposed, it will be disposed immediately and this +/// property will remain set to nil. +@property (atomic, strong) RACDisposable *disposable; + +/// Creates a serial disposable which will wrap the given disposable. +/// +/// disposable - The value to set for `disposable`. This may be nil. +/// +/// Returns a RACSerialDisposable, or nil if an error occurs. ++ (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable; + +/// Atomically swaps the receiver's `disposable` for `newDisposable`. +/// +/// newDisposable - The new value for `disposable`. If the receiver has already +/// been disposed, this disposable will be too, and `disposable` +/// will remain set to nil. This argument may be nil. +/// +/// Returns the previous value for the `disposable` property. +- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.m new file mode 100644 index 0000000..975afbf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.m
@@ -0,0 +1,133 @@ +// +// RACSerialDisposable.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSerialDisposable.h" +#import <libkern/OSAtomic.h> + +@interface RACSerialDisposable () { + // A reference to the receiver's `disposable`. This variable must only be + // modified atomically. + // + // If this is `self`, no `disposable` has been set, but the receiver has not + // been disposed of yet. `self` is never stored retained. + // + // If this is `nil`, the receiver has been disposed. + // + // Otherwise, this is a retained reference to the inner disposable and the + // receiver has not been disposed of yet. + void * volatile _disposablePtr; +} + +@end + +@implementation RACSerialDisposable + +#pragma mark Properties + +- (BOOL)isDisposed { + return _disposablePtr == nil; +} + +- (RACDisposable *)disposable { + RACDisposable *disposable = (__bridge id)_disposablePtr; + return (disposable == self ? nil : disposable); +} + +- (void)setDisposable:(RACDisposable *)disposable { + [self swapInDisposable:disposable]; +} + +#pragma mark Lifecycle + ++ (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable { + RACSerialDisposable *serialDisposable = [[self alloc] init]; + serialDisposable.disposable = disposable; + return serialDisposable; +} + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + _disposablePtr = (__bridge void *)self; + OSMemoryBarrier(); + + return self; +} + +- (id)initWithBlock:(void (^)(void))block { + self = [self init]; + if (self == nil) return nil; + + self.disposable = [RACDisposable disposableWithBlock:block]; + + return self; +} + +- (void)dealloc { + self.disposable = nil; +} + +#pragma mark Inner Disposable + +- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable { + void * const selfPtr = (__bridge void *)self; + + // Only retain the new disposable if it's not `self`. + // Take ownership before attempting the swap so that a subsequent swap + // receives an owned reference. + void *newDisposablePtr = selfPtr; + if (newDisposable != nil) { + newDisposablePtr = (void *)CFBridgingRetain(newDisposable); + } + + void *existingDisposablePtr; + // Keep trying while we're not disposed. + while ((existingDisposablePtr = _disposablePtr) != NULL) { + if (!OSAtomicCompareAndSwapPtrBarrier(existingDisposablePtr, newDisposablePtr, &_disposablePtr)) { + continue; + } + + // Return nil if _disposablePtr was set to self. Otherwise, release + // the old value and return it as an object. + if (existingDisposablePtr == selfPtr) { + return nil; + } else { + return CFBridgingRelease(existingDisposablePtr); + } + } + + // At this point, we've found out that we were already disposed. + [newDisposable dispose]; + + // Failed to swap, clean up the ownership we took prior to the swap. + if (newDisposable != nil) { + CFRelease(newDisposablePtr); + } + + return nil; +} + +#pragma mark Disposal + +- (void)dispose { + void *existingDisposablePtr; + + while ((existingDisposablePtr = _disposablePtr) != NULL) { + if (OSAtomicCompareAndSwapPtrBarrier(existingDisposablePtr, NULL, &_disposablePtr)) { + if (existingDisposablePtr != (__bridge void *)self) { + RACDisposable *existingDisposable = CFBridgingRelease(existingDisposablePtr); + [existingDisposable dispose]; + } + + break; + } + } +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h new file mode 100644 index 0000000..53b10d6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h
@@ -0,0 +1,625 @@ +// +// RACSignal+Operations.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-09-06. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACSignal.h" + +/// The domain for errors originating in RACSignal operations. +extern NSString * const RACSignalErrorDomain; + +/// The error code used with -timeout:. +extern const NSInteger RACSignalErrorTimedOut; + +/// The error code used when a value passed into +switch:cases:default: does not +/// match any of the cases, and no default was given. +extern const NSInteger RACSignalErrorNoMatchingCase; + +@class RACMulticastConnection; +@class RACDisposable; +@class RACScheduler; +@class RACSequence; +@class RACSubject; +@class RACTuple; +@class RACCommand; +@protocol RACSubscriber; + +@interface RACSignal (Operations) + +/// Do the given block on `next`. This should be used to inject side effects into +/// the signal. +- (RACSignal *)doNext:(void (^)(id x))block; + +/// Do the given block on `error`. This should be used to inject side effects +/// into the signal. +- (RACSignal *)doError:(void (^)(NSError *error))block; + +/// Do the given block on `completed`. This should be used to inject side effects +/// into the signal. +- (RACSignal *)doCompleted:(void (^)(void))block; + +/// Send `next`s only if we don't receive another `next` in `interval` seconds. +/// +/// If a `next` is received, and then another `next` is received before +/// `interval` seconds have passed, the first value is discarded. +/// +/// After `interval` seconds have passed since the most recent `next` was sent, +/// the most recent `next` is forwarded on the scheduler that the value was +/// originally received on. If +[RACScheduler currentScheduler] was nil at the +/// time, a private background scheduler is used. +/// +/// Returns a signal which sends throttled and delayed `next` events. Completion +/// and errors are always forwarded immediately. +- (RACSignal *)throttle:(NSTimeInterval)interval; + +/// Throttles `next`s for which `predicate` returns YES. +/// +/// When `predicate` returns YES for a `next`: +/// +/// 1. If another `next` is received before `interval` seconds have passed, the +/// prior value is discarded. This happens regardless of whether the new +/// value will be throttled. +/// 2. After `interval` seconds have passed since the value was originally +/// received, it will be forwarded on the scheduler that it was received +/// upon. If +[RACScheduler currentScheduler] was nil at the time, a private +/// background scheduler is used. +/// +/// When `predicate` returns NO for a `next`, it is forwarded immediately, +/// without any throttling. +/// +/// interval - The number of seconds for which to buffer the latest value that +/// passes `predicate`. +/// predicate - Passed each `next` from the receiver, this block returns +/// whether the given value should be throttled. This argument must +/// not be nil. +/// +/// Returns a signal which sends `next` events, throttled when `predicate` +/// returns YES. Completion and errors are always forwarded immediately. +- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate; + +/// Forwards `next` and `completed` events after delaying for `interval` seconds +/// on the current scheduler (on which the events were delivered). +/// +/// If +[RACScheduler currentScheduler] is nil when `next` or `completed` is +/// received, a private background scheduler is used. +/// +/// Returns a signal which sends delayed `next` and `completed` events. Errors +/// are always forwarded immediately. +- (RACSignal *)delay:(NSTimeInterval)interval; + +/// Resubscribes when the signal completes. +- (RACSignal *)repeat; + +/// Execute the given block each time a subscription is created. +/// +/// block - A block which defines the subscription side effects. Cannot be `nil`. +/// +/// Example: +/// +/// // Write new file, with backup. +/// [[[[fileManager +/// rac_createFileAtPath:path contents:data] +/// initially:^{ +/// // 2. Second, backup current file +/// [fileManager moveItemAtPath:path toPath:backupPath error:nil]; +/// }] +/// initially:^{ +/// // 1. First, acquire write lock. +/// [writeLock lock]; +/// }] +/// finally:^{ +/// [writeLock unlock]; +/// }]; +/// +/// Returns a signal that passes through all events of the receiver, plus +/// introduces side effects which occur prior to any subscription side effects +/// of the receiver. +- (RACSignal *)initially:(void (^)(void))block; + +/// Execute the given block when the signal completes or errors. +- (RACSignal *)finally:(void (^)(void))block; + +/// Divides the receiver's `next`s into buffers which deliver every `interval` +/// seconds. +/// +/// interval - The interval in which values are grouped into one buffer. +/// scheduler - The scheduler upon which the returned signal will deliver its +/// values. This must not be nil or +[RACScheduler +/// immediateScheduler]. +/// +/// Returns a signal which sends RACTuples of the buffered values at each +/// interval on `scheduler`. When the receiver completes, any currently-buffered +/// values will be sent immediately. +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; + +/// Collect all receiver's `next`s into a NSArray. nil values will be converted +/// to NSNull. +/// +/// This corresponds to the `ToArray` method in Rx. +/// +/// Returns a signal which sends a single NSArray when the receiver completes +/// successfully. +- (RACSignal *)collect; + +/// Takes the last `count` `next`s after the receiving signal completes. +- (RACSignal *)takeLast:(NSUInteger)count; + +/// Combines the latest values from the receiver and the given signal into +/// RACTuples, once both have sent at least one `next`. +/// +/// Any additional `next`s will result in a new RACTuple with the latest values +/// from both signals. +/// +/// signal - The signal to combine with. This argument must not be nil. +/// +/// Returns a signal which sends RACTuples of the combined values, forwards any +/// `error` events, and completes when both input signals complete. +- (RACSignal *)combineLatestWith:(RACSignal *)signal; + +/// Combines the latest values from the given signals into RACTuples, once all +/// the signals have sent at least one `next`. +/// +/// Any additional `next`s will result in a new RACTuple with the latest values +/// from all signals. +/// +/// signals - The signals to combine. If this collection is empty, the returned +/// signal will immediately complete upon subscription. +/// +/// Returns a signal which sends RACTuples of the combined values, forwards any +/// `error` events, and completes when all input signals complete. ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals; + +/// Combines signals using +combineLatest:, then reduces the resulting tuples +/// into a single value using -reduceEach:. +/// +/// signals - The signals to combine. If this collection is empty, the +/// returned signal will immediately complete upon subscription. +/// reduceBlock - The block which reduces the latest values from all the +/// signals into one value. It must take as many arguments as the +/// number of signals given. Each argument will be an object +/// argument. The return value must be an object. This argument +/// must not be nil. +/// +/// Example: +/// +/// [RACSignal combineLatest:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) { +/// return [NSString stringWithFormat:@"%@: %@", string, number]; +/// }]; +/// +/// Returns a signal which sends the results from each invocation of +/// `reduceBlock`. ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock; + +/// Merges the receiver and the given signal with `+merge:` and returns the +/// resulting signal. +- (RACSignal *)merge:(RACSignal *)signal; + +/// Sends the latest `next` from any of the signals. +/// +/// Returns a signal that passes through values from each of the given signals, +/// and sends `completed` when all of them complete. If any signal sends an error, +/// the returned signal sends `error` immediately. ++ (RACSignal *)merge:(id<NSFastEnumeration>)signals; + +/// Merges the signals sent by the receiver into a flattened signal, but only +/// subscribes to `maxConcurrent` number of signals at a time. New signals are +/// queued and subscribed to as other signals complete. +/// +/// If an error occurs on any of the signals, it is sent on the returned signal. +/// It completes only after the receiver and all sent signals have completed. +/// +/// This corresponds to `Merge<TSource>(IObservable<IObservable<TSource>>, Int32)` +/// in Rx. +/// +/// maxConcurrent - the maximum number of signals to subscribe to at a +/// time. If 0, it subscribes to an unlimited number of +/// signals. +- (RACSignal *)flatten:(NSUInteger)maxConcurrent; + +/// Ignores all `next`s from the receiver, waits for the receiver to complete, +/// then subscribes to a new signal. +/// +/// block - A block which will create or obtain a new signal to subscribe to, +/// executed only after the receiver completes. This block must not be +/// nil, and it must not return a nil signal. +/// +/// Returns a signal which will pass through the events of the signal created in +/// `block`. If the receiver errors out, the returned signal will error as well. +- (RACSignal *)then:(RACSignal * (^)(void))block; + +/// Concats the inner signals of a signal of signals. +- (RACSignal *)concat; + +/// Aggregate `next`s with the given start and combination. +- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock; + +/// Aggregate `next`s with the given start and combination. The start factory +/// block is called to get a new start object for each subscription. +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock; + +/// Invokes -setKeyPath:onObject:nilValue: with `nil` for the nil value. +/// +/// WARNING: Under certain conditions, this method is known to be thread-unsafe. +/// See the description in -setKeyPath:onObject:nilValue:. +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object; + +/// Binds the receiver to an object, automatically setting the given key path on +/// every `next`. When the signal completes, the binding is automatically +/// disposed of. +/// +/// WARNING: Under certain conditions, this method is known to be thread-unsafe. +/// A crash can result if `object` is deallocated concurrently on +/// another thread within a window of time between a value being sent +/// on this signal and immediately prior to the invocation of +/// -setValue:forKeyPath:, which sets the property. To prevent this, +/// ensure `object` is deallocated on the same thread the receiver +/// sends on, or ensure that the returned disposable is disposed of +/// before `object` deallocates. +/// See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1184 +/// +/// Sending an error on the signal is considered undefined behavior, and will +/// generate an assertion failure in Debug builds. +/// +/// A given key on an object should only have one active signal bound to it at any +/// given time. Binding more than one signal to the same property is considered +/// undefined behavior. +/// +/// keyPath - The key path to update with `next`s from the receiver. +/// object - The object that `keyPath` is relative to. +/// nilValue - The value to set at the key path whenever `nil` is sent by the +/// receiver. This may be nil when binding to 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`). +/// +/// Returns a disposable which can be used to terminate the binding. +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue; + +/// Sends NSDate.date every `interval` seconds. +/// +/// interval - The time interval in seconds at which the current time is sent. +/// scheduler - The scheduler upon which the current NSDate should be sent. This +/// must not be nil or +[RACScheduler immediateScheduler]. +/// +/// Returns a signal that sends the current date/time every `interval` on +/// `scheduler`. ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; + +/// Sends NSDate.date at intervals of at least `interval` seconds, up to +/// approximately `interval` + `leeway` seconds. +/// +/// The created signal will defer sending each `next` for at least `interval` +/// seconds, and for an additional amount of time up to `leeway` seconds in the +/// interest of performance or power consumption. Note that some additional +/// latency is to be expected, even when specifying a `leeway` of 0. +/// +/// interval - The base interval between `next`s. +/// scheduler - The scheduler upon which the current NSDate should be sent. This +/// must not be nil or +[RACScheduler immediateScheduler]. +/// leeway - The maximum amount of additional time the `next` can be deferred. +/// +/// Returns a signal that sends the current date/time at intervals of at least +/// `interval seconds` up to approximately `interval` + `leeway` seconds on +/// `scheduler`. ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway; + +/// Take `next`s until the `signalTrigger` sends `next` or `completed`. +/// +/// Returns a signal which passes through all events from the receiver until +/// `signalTrigger` sends `next` or `completed`, at which point the returned signal +/// will send `completed`. +- (RACSignal *)takeUntil:(RACSignal *)signalTrigger; + +/// Take `next`s until the `replacement` sends an event. +/// +/// replacement - The signal which replaces the receiver as soon as it sends an +/// event. +/// +/// Returns a signal which passes through `next`s and `error` from the receiver +/// until `replacement` sends an event, at which point the returned signal will +/// send that event and switch to passing through events from `replacement` +/// instead, regardless of whether the receiver has sent events already. +- (RACSignal *)takeUntilReplacement:(RACSignal *)replacement; + +/// Subscribe to the returned signal when an error occurs. +- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock; + +/// Subscribe to the given signal when an error occurs. +- (RACSignal *)catchTo:(RACSignal *)signal; + +/// Runs `tryBlock` against each of the receiver's values, passing values +/// until `tryBlock` returns NO, or the receiver completes. +/// +/// tryBlock - An action to run against each of the receiver's values. +/// The block should return YES to indicate that the action was +/// successful. This block must not be nil. +/// +/// Example: +/// +/// // The returned signal will send an error if data values cannot be +/// // written to `someFileURL`. +/// [signal try:^(NSData *data, NSError **errorPtr) { +/// return [data writeToURL:someFileURL options:NSDataWritingAtomic error:errorPtr]; +/// }]; +/// +/// Returns a signal which passes through all the values of the receiver. If +/// `tryBlock` fails for any value, the returned signal will error using the +/// `NSError` passed out from the block. +- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock; + +/// Runs `mapBlock` against each of the receiver's values, mapping values until +/// `mapBlock` returns nil, or the receiver completes. +/// +/// mapBlock - An action to map each of the receiver's values. The block should +/// return a non-nil value to indicate that the action was successful. +/// This block must not be nil. +/// +/// Example: +/// +/// // The returned signal will send an error if data cannot be read from +/// // `fileURL`. +/// [signal tryMap:^(NSURL *fileURL, NSError **errorPtr) { +/// return [NSData dataWithContentsOfURL:fileURL options:0 error:errorPtr]; +/// }]; +/// +/// Returns a signal which transforms all the values of the receiver. If +/// `mapBlock` returns nil for any value, the returned signal will error using +/// the `NSError` passed out from the block. +- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock; + +/// Returns the first `next`. Note that this is a blocking call. +- (id)first; + +/// Returns the first `next` or `defaultValue` if the signal completes or errors +/// without sending a `next`. Note that this is a blocking call. +- (id)firstOrDefault:(id)defaultValue; + +/// Returns the first `next` or `defaultValue` if the signal completes or errors +/// without sending a `next`. If an error occurs success will be NO and error +/// will be populated. Note that this is a blocking call. +/// +/// Both success and error may be NULL. +- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error; + +/// Blocks the caller and waits for the signal to complete. +/// +/// error - If not NULL, set to any error that occurs. +/// +/// Returns whether the signal completed successfully. If NO, `error` will be set +/// to the error that occurred. +- (BOOL)waitUntilCompleted:(NSError **)error; + +/// Defer creation of a signal until the signal's actually subscribed to. +/// +/// This can be used to effectively turn a hot signal into a cold signal. ++ (RACSignal *)defer:(RACSignal * (^)(void))block; + +/// Every time the receiver sends a new RACSignal, subscribes and sends `next`s and +/// `error`s only for that signal. +/// +/// The receiver must be a signal of signals. +/// +/// Returns a signal which passes through `next`s and `error`s from the latest +/// signal sent by the receiver, and sends `completed` when both the receiver and +/// the last sent signal complete. +- (RACSignal *)switchToLatest; + +/// Switches between the signals in `cases` as well as `defaultSignal` based on +/// the latest value sent by `signal`. +/// +/// signal - A signal of objects used as keys in the `cases` dictionary. +/// This argument must not be nil. +/// cases - A dictionary that has signals as values. This argument must +/// not be nil. A RACTupleNil key in this dictionary will match +/// nil `next` events that are received on `signal`. +/// defaultSignal - The signal to pass through after `signal` sends a value for +/// which `cases` does not contain a signal. If nil, any +/// unmatched values will result in +/// a RACSignalErrorNoMatchingCase error. +/// +/// Returns a signal which passes through `next`s and `error`s from one of the +/// the signals in `cases` or `defaultSignal`, and sends `completed` when both +/// `signal` and the last used signal complete. If no `defaultSignal` is given, +/// an unmatched `next` will result in an error on the returned signal. ++ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal; + +/// Switches between `trueSignal` and `falseSignal` based on the latest value +/// sent by `boolSignal`. +/// +/// boolSignal - A signal of BOOLs determining whether `trueSignal` or +/// `falseSignal` should be active. This argument must not be nil. +/// trueSignal - The signal to pass through after `boolSignal` has sent YES. +/// This argument must not be nil. +/// falseSignal - The signal to pass through after `boolSignal` has sent NO. This +/// argument must not be nil. +/// +/// Returns a signal which passes through `next`s and `error`s from `trueSignal` +/// and/or `falseSignal`, and sends `completed` when both `boolSignal` and the +/// last switched signal complete. ++ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal; + +/// Add every `next` to an array. Nils are represented by NSNulls. Note that this +/// is a blocking call. +/// +/// **This is not the same as the `ToArray` method in Rx.** See -collect for +/// that behavior instead. +/// +/// Returns the array of `next` values, or nil if an error occurs. +- (NSArray *)toArray; + +/// Add every `next` to a sequence. Nils are represented by NSNulls. +/// +/// This corresponds to the `ToEnumerable` method in Rx. +/// +/// Returns a sequence which provides values from the signal as they're sent. +/// Trying to retrieve a value from the sequence which has not yet been sent will +/// block. +@property (nonatomic, strong, readonly) RACSequence *sequence; + +/// Creates and returns a multicast connection. This allows you to share a single +/// subscription to the underlying signal. +- (RACMulticastConnection *)publish; + +/// Creates and returns a multicast connection that pushes values into the given +/// subject. This allows you to share a single subscription to the underlying +/// signal. +- (RACMulticastConnection *)multicast:(RACSubject *)subject; + +/// Multicasts the signal to a RACReplaySubject of unlimited capacity, and +/// immediately connects to the resulting RACMulticastConnection. +/// +/// Returns the connected, multicasted signal. +- (RACSignal *)replay; + +/// Multicasts the signal to a RACReplaySubject of capacity 1, and immediately +/// connects to the resulting RACMulticastConnection. +/// +/// Returns the connected, multicasted signal. +- (RACSignal *)replayLast; + +/// Multicasts the signal to a RACReplaySubject of unlimited capacity, and +/// lazily connects to the resulting RACMulticastConnection. +/// +/// This means the returned signal will subscribe to the multicasted signal only +/// when the former receives its first subscription. +/// +/// Returns the lazily connected, multicasted signal. +- (RACSignal *)replayLazily; + +/// Sends an error after `interval` seconds if the source doesn't complete +/// before then. +/// +/// The error will be in the RACSignalErrorDomain and have a code of +/// RACSignalErrorTimedOut. +/// +/// interval - The number of seconds after which the signal should error out. +/// scheduler - The scheduler upon which any timeout error should be sent. This +/// must not be nil or +[RACScheduler immediateScheduler]. +/// +/// Returns a signal that passes through the receiver's events, until the stream +/// finishes or times out, at which point an error will be sent on `scheduler`. +- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; + +/// Creates and returns a signal that delivers its events on the given scheduler. +/// Any side effects of the receiver will still be performed on the original +/// thread. +/// +/// This is ideal when the signal already performs its work on the desired +/// thread, but you want to handle its events elsewhere. +/// +/// This corresponds to the `ObserveOn` method in Rx. +- (RACSignal *)deliverOn:(RACScheduler *)scheduler; + +/// Creates and returns a signal that executes its side effects and delivers its +/// events on the given scheduler. +/// +/// Use of this operator should be avoided whenever possible, because the +/// receiver's side effects may not be safe to run on another thread. If you just +/// want to receive the signal's events on `scheduler`, use -deliverOn: instead. +- (RACSignal *)subscribeOn:(RACScheduler *)scheduler; + +/// Groups each received object into a group, as determined by calling `keyBlock` +/// with that object. The object sent is transformed by calling `transformBlock` +/// with the object. If `transformBlock` is nil, it sends the original object. +/// +/// The returned signal is a signal of RACGroupedSignal. +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock; + +/// Calls -[RACSignal groupBy:keyBlock transform:nil]. +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock; + +/// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any +/// objects. +- (RACSignal *)any; + +/// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any +/// objects that pass `predicateBlock`. +/// +/// predicateBlock - cannot be nil. +- (RACSignal *)any:(BOOL (^)(id object))predicateBlock; + +/// Sends an [NSNumber numberWithBool:YES] if all the objects the receiving +/// signal sends pass `predicateBlock`. +/// +/// predicateBlock - cannot be nil. +- (RACSignal *)all:(BOOL (^)(id object))predicateBlock; + +/// Resubscribes to the receiving signal if an error occurs, up until it has +/// retried the given number of times. +/// +/// retryCount - if 0, it keeps retrying until it completes. +- (RACSignal *)retry:(NSInteger)retryCount; + +/// Resubscribes to the receiving signal if an error occurs. +- (RACSignal *)retry; + +/// Sends the latest value from the receiver only when `sampler` sends a value. +/// The returned signal could repeat values if `sampler` fires more often than +/// the receiver. Values from `sampler` are ignored before the receiver sends +/// its first value. +/// +/// sampler - The signal that controls when the latest value from the receiver +/// is sent. Cannot be nil. +- (RACSignal *)sample:(RACSignal *)sampler; + +/// Ignores all `next`s from the receiver. +/// +/// Returns a signal which only passes through `error` or `completed` events from +/// the receiver. +- (RACSignal *)ignoreValues; + +/// Converts each of the receiver's events into a RACEvent object. +/// +/// Returns a signal which sends the receiver's events as RACEvents, and +/// completes after the receiver sends `completed` or `error`. +- (RACSignal *)materialize; + +/// Converts each RACEvent in the receiver back into "real" RACSignal events. +/// +/// Returns a signal which sends `next` for each value RACEvent, `error` for each +/// error RACEvent, and `completed` for each completed RACEvent. +- (RACSignal *)dematerialize; + +/// Inverts each NSNumber-wrapped BOOL sent by the receiver. It will assert if +/// the receiver sends anything other than NSNumbers. +/// +/// Returns a signal of inverted NSNumber-wrapped BOOLs. +- (RACSignal *)not; + +/// Performs a boolean AND on all of the RACTuple of NSNumbers in sent by the receiver. +/// +/// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers. +/// +/// Returns a signal that applies AND to each NSNumber in the tuple. +- (RACSignal *)and; + +/// Performs a boolean OR on all of the RACTuple of NSNumbers in sent by the receiver. +/// +/// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers. +/// +/// Returns a signal that applies OR to each NSNumber in the tuple. +- (RACSignal *)or; + +@end + +@interface RACSignal (OperationsDeprecated) + +- (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); +- (RACSignal *)buffer:(NSUInteger)bufferCount __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); +- (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock __attribute__((deprecated("Use -publish instead"))); ++ (RACSignal *)interval:(NSTimeInterval)interval __attribute__((deprecated("Use +interval:onScheduler: instead"))); ++ (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway __attribute__((deprecated("Use +interval:onScheduler:withLeeway: instead"))); +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval __attribute__((deprecated("Use -bufferWithTime:onScheduler: instead"))); +- (RACSignal *)timeout:(NSTimeInterval)interval __attribute__((deprecated("Use -timeout:onScheduler: instead"))); +- (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object __attribute__((deprecated("Renamed to -setKeyPath:onObject:"))); +- (RACSignal *)ignoreElements __attribute__((deprecated("Renamed to -ignoreValues"))); +- (RACSignal *)sequenceNext:(RACSignal * (^)(void))block __attribute__((deprecated("Renamed to -then:"))); +- (RACSignal *)aggregateWithStart:(id)start combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStart:reduce:"))); +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStartFactory:reduce:"))); +- (RACDisposable *)executeCommand:(RACCommand *)command __attribute__((deprecated("Use -flattenMap: or -subscribeNext: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.m new file mode 100644 index 0000000..6e36dd6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.m
@@ -0,0 +1,1436 @@ +// +// RACSignal+Operations.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-09-06. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignal+Operations.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACEvent.h" +#import "RACGroupedSignal.h" +#import "RACMulticastConnection+Private.h" +#import "RACReplaySubject.h" +#import "RACScheduler+Private.h" +#import "RACScheduler.h" +#import "RACSerialDisposable.h" +#import "RACSignalSequence.h" +#import "RACStream+Private.h" +#import "RACSubject.h" +#import "RACSubscriber+Private.h" +#import "RACSubscriber.h" +#import "RACTuple.h" +#import "RACUnit.h" +#import <libkern/OSAtomic.h> +#import <objc/runtime.h> + +NSString * const RACSignalErrorDomain = @"RACSignalErrorDomain"; + +const NSInteger RACSignalErrorTimedOut = 1; +const NSInteger RACSignalErrorNoMatchingCase = 2; + +// Subscribes to the given signal with the given blocks. +// +// If the signal errors or completes, the corresponding block is invoked. If the +// disposable passed to the block is _not_ disposed, then the signal is +// subscribed to again. +static RACDisposable *subscribeForever (RACSignal *signal, void (^next)(id), void (^error)(NSError *, RACDisposable *), void (^completed)(RACDisposable *)) { + next = [next copy]; + error = [error copy]; + completed = [completed copy]; + + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + RACSchedulerRecursiveBlock recursiveBlock = ^(void (^recurse)(void)) { + RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; + [compoundDisposable addDisposable:selfDisposable]; + + __weak RACDisposable *weakSelfDisposable = selfDisposable; + + RACDisposable *subscriptionDisposable = [signal subscribeNext:next error:^(NSError *e) { + @autoreleasepool { + error(e, compoundDisposable); + [compoundDisposable removeDisposable:weakSelfDisposable]; + } + + recurse(); + } completed:^{ + @autoreleasepool { + completed(compoundDisposable); + [compoundDisposable removeDisposable:weakSelfDisposable]; + } + + recurse(); + }]; + + [selfDisposable addDisposable:subscriptionDisposable]; + }; + + // Subscribe once immediately, and then use recursive scheduling for any + // further resubscriptions. + recursiveBlock(^{ + RACScheduler *recursiveScheduler = RACScheduler.currentScheduler ?: [RACScheduler scheduler]; + + RACDisposable *schedulingDisposable = [recursiveScheduler scheduleRecursiveBlock:recursiveBlock]; + [compoundDisposable addDisposable:schedulingDisposable]; + }); + + return compoundDisposable; +} + +@implementation RACSignal (Operations) + +- (RACSignal *)doNext:(void (^)(id x))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + block(x); + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -doNext:", self.name]; +} + +- (RACSignal *)doError:(void (^)(NSError *error))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + block(error); + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -doError:", self.name]; +} + +- (RACSignal *)doCompleted:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + block(); + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -doCompleted:", self.name]; +} + +- (RACSignal *)throttle:(NSTimeInterval)interval { + return [[self throttle:interval valuesPassingTest:^(id _) { + return YES; + }] setNameWithFormat:@"[%@] -throttle: %f", self.name, (double)interval]; +} + +- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate { + NSCParameterAssert(interval >= 0); + NSCParameterAssert(predicate != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + // We may never use this scheduler, but we need to set it up ahead of + // time so that our scheduled blocks are run serially if we do. + RACScheduler *scheduler = [RACScheduler scheduler]; + + // Information about any currently-buffered `next` event. + __block id nextValue = nil; + __block BOOL hasNextValue = NO; + RACSerialDisposable *nextDisposable = [[RACSerialDisposable alloc] init]; + + void (^flushNext)(BOOL send) = ^(BOOL send) { + @synchronized (compoundDisposable) { + [nextDisposable.disposable dispose]; + + if (!hasNextValue) return; + if (send) [subscriber sendNext:nextValue]; + + nextValue = nil; + hasNextValue = NO; + } + }; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler; + BOOL shouldThrottle = predicate(x); + + @synchronized (compoundDisposable) { + flushNext(NO); + if (!shouldThrottle) { + [subscriber sendNext:x]; + return; + } + + nextValue = x; + hasNextValue = YES; + nextDisposable.disposable = [delayScheduler afterDelay:interval schedule:^{ + flushNext(YES); + }]; + } + } error:^(NSError *error) { + [compoundDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + flushNext(YES); + [subscriber sendCompleted]; + }]; + + [compoundDisposable addDisposable:subscriptionDisposable]; + return compoundDisposable; + }] setNameWithFormat:@"[%@] -throttle: %f valuesPassingTest:", self.name, (double)interval]; +} + +- (RACSignal *)delay:(NSTimeInterval)interval { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + // We may never use this scheduler, but we need to set it up ahead of + // time so that our scheduled blocks are run serially if we do. + RACScheduler *scheduler = [RACScheduler scheduler]; + + void (^schedule)(dispatch_block_t) = ^(dispatch_block_t block) { + RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler; + RACDisposable *schedulerDisposable = [delayScheduler afterDelay:interval schedule:block]; + [disposable addDisposable:schedulerDisposable]; + }; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + schedule(^{ + [subscriber sendNext:x]; + }); + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + schedule(^{ + [subscriber sendCompleted]; + }); + }]; + + [disposable addDisposable:subscriptionDisposable]; + return disposable; + }] setNameWithFormat:@"[%@] -delay: %f", self.name, (double)interval]; +} + +- (RACSignal *)repeat { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return subscribeForever(self, + ^(id x) { + [subscriber sendNext:x]; + }, + ^(NSError *error, RACDisposable *disposable) { + [disposable dispose]; + [subscriber sendError:error]; + }, + ^(RACDisposable *disposable) { + // Resubscribe. + }); + }] setNameWithFormat:@"[%@] -repeat", self.name]; +} + +- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock { + NSCParameterAssert(catchBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *catchDisposable = [[RACSerialDisposable alloc] init]; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + RACSignal *signal = catchBlock(error); + NSCAssert(signal != nil, @"Expected non-nil signal from catch block on %@", self); + catchDisposable.disposable = [signal subscribe:subscriber]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [catchDisposable dispose]; + [subscriptionDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -catch:", self.name]; +} + +- (RACSignal *)catchTo:(RACSignal *)signal { + return [[self catch:^(NSError *error) { + return signal; + }] setNameWithFormat:@"[%@] -catchTo: %@", self.name, signal]; +} + +- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock { + NSCParameterAssert(tryBlock != NULL); + + return [[self flattenMap:^(id value) { + NSError *error = nil; + BOOL passed = tryBlock(value, &error); + return (passed ? [RACSignal return:value] : [RACSignal error:error]); + }] setNameWithFormat:@"[%@] -try:", self.name]; +} + +- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock { + NSCParameterAssert(mapBlock != NULL); + + return [[self flattenMap:^(id value) { + NSError *error = nil; + id mappedValue = mapBlock(value, &error); + return (mappedValue == nil ? [RACSignal error:error] : [RACSignal return:mappedValue]); + }] setNameWithFormat:@"[%@] -tryMap:", self.name]; +} + +- (RACSignal *)initially:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal defer:^{ + block(); + return self; + }] setNameWithFormat:@"[%@] -initially:", self.name]; +} + +- (RACSignal *)finally:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[[self + doError:^(NSError *error) { + block(); + }] + doCompleted:^{ + block(); + }] + setNameWithFormat:@"[%@] -finally:", self.name]; +} + +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *timerDisposable = [[RACSerialDisposable alloc] init]; + NSMutableArray *values = [NSMutableArray array]; + + void (^flushValues)() = ^{ + @synchronized (values) { + [timerDisposable.disposable dispose]; + + if (values.count == 0) return; + + RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:values convertNullsToNils:NO]; + [values removeAllObjects]; + [subscriber sendNext:tuple]; + } + }; + + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + @synchronized (values) { + if (values.count == 0) { + timerDisposable.disposable = [scheduler afterDelay:interval schedule:flushValues]; + } + + [values addObject:x ?: RACTupleNil.tupleNil]; + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + flushValues(); + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [selfDisposable dispose]; + [timerDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -bufferWithTime: %f", self.name, (double)interval]; +} + +- (RACSignal *)collect { + return [[self aggregateWithStartFactory:^{ + return [[NSMutableArray alloc] init]; + } reduce:^(NSMutableArray *collectedValues, id x) { + [collectedValues addObject:(x ?: NSNull.null)]; + return collectedValues; + }] setNameWithFormat:@"[%@] -collect", self.name]; +} + +- (RACSignal *)takeLast:(NSUInteger)count { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSMutableArray *valuesTaken = [NSMutableArray arrayWithCapacity:count]; + return [self subscribeNext:^(id x) { + [valuesTaken addObject:x ? : [RACTupleNil tupleNil]]; + + while(valuesTaken.count > count) { + [valuesTaken removeObjectAtIndex:0]; + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + for(id value in valuesTaken) { + [subscriber sendNext:[value isKindOfClass:[RACTupleNil class]] ? nil : value]; + } + + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -takeLast: %lu", self.name, (unsigned long)count]; +} + +- (RACSignal *)combineLatestWith:(RACSignal *)signal { + NSCParameterAssert(signal != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + __block id lastSelfValue = nil; + __block BOOL selfCompleted = NO; + + __block id lastOtherValue = nil; + __block BOOL otherCompleted = NO; + + void (^sendNext)(void) = ^{ + @synchronized (disposable) { + if (lastSelfValue == nil || lastOtherValue == nil) return; + [subscriber sendNext:[RACTuple tupleWithObjects:lastSelfValue, lastOtherValue, nil]]; + } + }; + + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + @synchronized (disposable) { + lastSelfValue = x ?: RACTupleNil.tupleNil; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (disposable) { + selfCompleted = YES; + if (otherCompleted) [subscriber sendCompleted]; + } + }]; + + [disposable addDisposable:selfDisposable]; + + RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { + @synchronized (disposable) { + lastOtherValue = x ?: RACTupleNil.tupleNil; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (disposable) { + otherCompleted = YES; + if (selfCompleted) [subscriber sendCompleted]; + } + }]; + + [disposable addDisposable:otherDisposable]; + + return disposable; + }] setNameWithFormat:@"[%@] -combineLatestWith: %@", self.name, signal]; +} + ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals { + return [[self join:signals block:^(RACSignal *left, RACSignal *right) { + return [left combineLatestWith:right]; + }] setNameWithFormat:@"+combineLatest: %@", signals]; +} + ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock { + NSCParameterAssert(reduceBlock != nil); + + RACSignal *result = [self combineLatest:signals]; + + // Although we assert this condition above, older versions of this method + // supported this argument being nil. Avoid crashing Release builds of + // apps that depended on that. + if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; + + return [result setNameWithFormat:@"+combineLatest: %@ reduce:", signals]; +} + +- (RACSignal *)merge:(RACSignal *)signal { + return [[RACSignal + merge:@[ self, signal ]] + setNameWithFormat:@"[%@] -merge: %@", self.name, signal]; +} + ++ (RACSignal *)merge:(id<NSFastEnumeration>)signals { + NSMutableArray *copiedSignals = [[NSMutableArray alloc] init]; + for (RACSignal *signal in signals) { + [copiedSignals addObject:signal]; + } + + return [[[RACSignal + createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + for (RACSignal *signal in copiedSignals) { + [subscriber sendNext:signal]; + } + + [subscriber sendCompleted]; + return nil; + }] + flatten] + setNameWithFormat:@"+merge: %@", copiedSignals]; +} + +- (RACSignal *)flatten:(NSUInteger)maxConcurrent { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init]; + + // Contains disposables for the currently active subscriptions. + // + // This should only be used while synchronized on `subscriber`. + NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent]; + + // Whether the signal-of-signals has completed yet. + // + // This should only be used while synchronized on `subscriber`. + __block BOOL selfCompleted = NO; + + // Subscribes to the given signal. + // + // This will be set to nil once all signals have completed (to break + // a retain cycle in the recursive block). + __block void (^subscribeToSignal)(RACSignal *); + + // Sends completed to the subscriber if all signals are finished. + // + // This should only be used while synchronized on `subscriber`. + void (^completeIfAllowed)(void) = ^{ + if (selfCompleted && activeDisposables.count == 0) { + [subscriber sendCompleted]; + subscribeToSignal = nil; + } + }; + + // The signals waiting to be started. + // + // This array should only be used while synchronized on `subscriber`. + NSMutableArray *queuedSignals = [NSMutableArray array]; + + subscribeToSignal = ^(RACSignal *signal) { + RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; + + @synchronized (subscriber) { + [compoundDisposable addDisposable:serialDisposable]; + [activeDisposables addObject:serialDisposable]; + } + + serialDisposable.disposable = [signal subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + RACSignal *nextSignal; + + @synchronized (subscriber) { + [compoundDisposable removeDisposable:serialDisposable]; + [activeDisposables removeObjectIdenticalTo:serialDisposable]; + + if (queuedSignals.count == 0) { + completeIfAllowed(); + return; + } + + nextSignal = queuedSignals[0]; + [queuedSignals removeObjectAtIndex:0]; + } + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-retain-cycles" + // This retain cycle is broken in `completeIfAllowed`. + subscribeToSignal(nextSignal); + #pragma clang diagnostic pop + }]; + }; + + [compoundDisposable addDisposable:[self subscribeNext:^(RACSignal *signal) { + if (signal == nil) return; + + NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal); + + @synchronized (subscriber) { + if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) { + [queuedSignals addObject:signal]; + + // If we need to wait, skip subscribing to this + // signal. + return; + } + } + + subscribeToSignal(signal); + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (subscriber) { + selfCompleted = YES; + completeIfAllowed(); + } + }]]; + + return compoundDisposable; + }] setNameWithFormat:@"[%@] -flatten: %lu", self.name, (unsigned long)maxConcurrent]; +} + +- (RACSignal *)then:(RACSignal * (^)(void))block { + NSCParameterAssert(block != nil); + + return [[[self + ignoreValues] + concat:[RACSignal defer:block]] + setNameWithFormat:@"[%@] -then:", self.name]; +} + +- (RACSignal *)concat { + return [[self flatten:1] setNameWithFormat:@"[%@] -concat", self.name]; +} + +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock { + NSCParameterAssert(startFactory != NULL); + NSCParameterAssert(reduceBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block id runningValue = startFactory(); + return [self subscribeNext:^(id x) { + runningValue = reduceBlock(runningValue, x); + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendNext:runningValue]; + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -aggregateWithStartFactory:reduce:", self.name]; +} + +- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock { + RACSignal *signal = [self aggregateWithStartFactory:^{ + return start; + } reduce:reduceBlock]; + + return [signal setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduce:", self.name, [start rac_description]]; +} + +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object { + return [self setKeyPath:keyPath onObject:object nilValue:nil]; +} + +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue { + NSCParameterAssert(keyPath != nil); + NSCParameterAssert(object != nil); + + keyPath = [keyPath copy]; + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + // Purposely not retaining 'object', since we want to tear down the binding + // when it deallocates normally. + __block void * volatile objectPtr = (__bridge void *)object; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + // Possibly spec, possibly compiler bug, but this __bridge cast does not + // result in a retain here, effectively an invisible __unsafe_unretained + // qualifier. Using objc_precise_lifetime gives the __strong reference + // desired. The explicit use of __strong is strictly defensive. + __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr; + [object setValue:x ?: nilValue forKeyPath:keyPath]; + } error:^(NSError *error) { + __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr; + + NSCAssert(NO, @"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error); + + // Log the error if we're running with assertions disabled. + NSLog(@"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error); + + [disposable dispose]; + } completed:^{ + [disposable dispose]; + }]; + + [disposable addDisposable:subscriptionDisposable]; + + #if DEBUG + static void *bindingsKey = &bindingsKey; + NSMutableDictionary *bindings; + + @synchronized (object) { + bindings = objc_getAssociatedObject(object, bindingsKey); + if (bindings == nil) { + bindings = [NSMutableDictionary dictionary]; + objc_setAssociatedObject(object, bindingsKey, bindings, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + } + + @synchronized (bindings) { + NSCAssert(bindings[keyPath] == nil, @"Signal %@ is already bound to key path \"%@\" on object %@, adding signal %@ is undefined behavior", [bindings[keyPath] nonretainedObjectValue], keyPath, object, self); + + bindings[keyPath] = [NSValue valueWithNonretainedObject:self]; + } + #endif + + RACDisposable *clearPointerDisposable = [RACDisposable disposableWithBlock:^{ + #if DEBUG + @synchronized (bindings) { + [bindings removeObjectForKey:keyPath]; + } + #endif + + while (YES) { + void *ptr = objectPtr; + if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr)) { + break; + } + } + }]; + + [disposable addDisposable:clearPointerDisposable]; + + [object.rac_deallocDisposable addDisposable:disposable]; + + RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; + return [RACDisposable disposableWithBlock:^{ + [objectDisposable removeDisposable:disposable]; + [disposable dispose]; + }]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { + return [[RACSignal interval:interval onScheduler:scheduler withLeeway:0.0] setNameWithFormat:@"+interval: %f onScheduler: %@", (double)interval, scheduler]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [scheduler after:[NSDate dateWithTimeIntervalSinceNow:interval] repeatingEvery:interval withLeeway:leeway schedule:^{ + [subscriber sendNext:[NSDate date]]; + }]; + }] setNameWithFormat:@"+interval: %f onScheduler: %@ withLeeway: %f", (double)interval, scheduler, (double)leeway]; +} + +- (RACSignal *)takeUntil:(RACSignal *)signalTrigger { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + void (^triggerCompletion)(void) = ^{ + [disposable dispose]; + [subscriber sendCompleted]; + }; + + RACDisposable *triggerDisposable = [signalTrigger subscribeNext:^(id _) { + triggerCompletion(); + } completed:^{ + triggerCompletion(); + }]; + + [disposable addDisposable:triggerDisposable]; + + if (!disposable.disposed) { + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [disposable dispose]; + [subscriber sendCompleted]; + }]; + + [disposable addDisposable:selfDisposable]; + } + + return disposable; + }] setNameWithFormat:@"[%@] -takeUntil: %@", self.name, signalTrigger]; +} + +- (RACSignal *)takeUntilReplacement:(RACSignal *)replacement { + return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; + + RACDisposable *replacementDisposable = [replacement subscribeNext:^(id x) { + [selfDisposable dispose]; + [subscriber sendNext:x]; + } error:^(NSError *error) { + [selfDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [selfDisposable dispose]; + [subscriber sendCompleted]; + }]; + + if (!selfDisposable.disposed) { + selfDisposable.disposable = [[self + concat:[RACSignal never]] + subscribe:subscriber]; + } + + return [RACDisposable disposableWithBlock:^{ + [selfDisposable dispose]; + [replacementDisposable dispose]; + }]; + }]; +} + +- (RACSignal *)switchToLatest { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACMulticastConnection *connection = [self publish]; + + RACDisposable *subscriptionDisposable = [[connection.signal + flattenMap:^(RACSignal *x) { + NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x); + + // -concat:[RACSignal never] prevents completion of the receiver from + // prematurely terminating the inner signal. + return [x takeUntil:[connection.signal concat:[RACSignal never]]]; + }] + subscribe:subscriber]; + + RACDisposable *connectionDisposable = [connection connect]; + return [RACDisposable disposableWithBlock:^{ + [subscriptionDisposable dispose]; + [connectionDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -switchToLatest", self.name]; +} + ++ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal { + NSCParameterAssert(signal != nil); + NSCParameterAssert(cases != nil); + + for (id key in cases) { + id value __attribute__((unused)) = cases[key]; + NSCAssert([value isKindOfClass:RACSignal.class], @"Expected all cases to be RACSignals, %@ isn't", value); + } + + NSDictionary *copy = [cases copy]; + + return [[[signal + map:^(id key) { + if (key == nil) key = RACTupleNil.tupleNil; + + RACSignal *signal = copy[key] ?: defaultSignal; + if (signal == nil) { + NSString *description = [NSString stringWithFormat:NSLocalizedString(@"No matching signal found for value %@", @""), key]; + return [RACSignal error:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorNoMatchingCase userInfo:@{ NSLocalizedDescriptionKey: description }]]; + } + + return signal; + }] + switchToLatest] + setNameWithFormat:@"+switch: %@ cases: %@ default: %@", signal, cases, defaultSignal]; +} + ++ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal { + NSCParameterAssert(boolSignal != nil); + NSCParameterAssert(trueSignal != nil); + NSCParameterAssert(falseSignal != nil); + + return [[[boolSignal + map:^(NSNumber *value) { + NSCAssert([value isKindOfClass:NSNumber.class], @"Expected %@ to send BOOLs, not %@", boolSignal, value); + + return (value.boolValue ? trueSignal : falseSignal); + }] + switchToLatest] + setNameWithFormat:@"+if: %@ then: %@ else: %@", boolSignal, trueSignal, falseSignal]; +} + +- (id)first { + return [self firstOrDefault:nil]; +} + +- (id)firstOrDefault:(id)defaultValue { + return [self firstOrDefault:defaultValue success:NULL error:NULL]; +} + +- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error { + NSCondition *condition = [[NSCondition alloc] init]; + condition.name = [NSString stringWithFormat:@"[%@] -firstOrDefault: %@ success:error:", self.name, defaultValue]; + + __block id value = defaultValue; + __block BOOL done = NO; + + // Ensures that we don't pass values across thread boundaries by reference. + __block NSError *localError; + __block BOOL localSuccess; + + [[self take:1] subscribeNext:^(id x) { + [condition lock]; + + value = x; + localSuccess = YES; + + done = YES; + [condition broadcast]; + [condition unlock]; + } error:^(NSError *e) { + [condition lock]; + + if (!done) { + localSuccess = NO; + localError = e; + + done = YES; + [condition broadcast]; + } + + [condition unlock]; + } completed:^{ + [condition lock]; + + localSuccess = YES; + + done = YES; + [condition broadcast]; + [condition unlock]; + }]; + + [condition lock]; + while (!done) { + [condition wait]; + } + + if (success != NULL) *success = localSuccess; + if (error != NULL) *error = localError; + + [condition unlock]; + return value; +} + +- (BOOL)waitUntilCompleted:(NSError **)error { + BOOL success = NO; + + [[[self + ignoreValues] + setNameWithFormat:@"[%@] -waitUntilCompleted:", self.name] + firstOrDefault:nil success:&success error:error]; + + return success; +} + ++ (RACSignal *)defer:(RACSignal * (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [block() subscribe:subscriber]; + }] setNameWithFormat:@"+defer:"]; +} + +- (NSArray *)toArray { + return [[[self collect] first] copy]; +} + +- (RACSequence *)sequence { + return [[RACSignalSequence sequenceWithSignal:self] setNameWithFormat:@"[%@] -sequence", self.name]; +} + +- (RACMulticastConnection *)publish { + RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name]; + RACMulticastConnection *connection = [self multicast:subject]; + return connection; +} + +- (RACMulticastConnection *)multicast:(RACSubject *)subject { + [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name]; + RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject]; + return connection; +} + +- (RACSignal *)replay { + RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name]; + + RACMulticastConnection *connection = [self multicast:subject]; + [connection connect]; + + return connection.signal; +} + +- (RACSignal *)replayLast { + RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name]; + + RACMulticastConnection *connection = [self multicast:subject]; + [connection connect]; + + return connection.signal; +} + +- (RACSignal *)replayLazily { + RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]]; + return [[RACSignal + defer:^{ + [connection connect]; + return connection.signal; + }] + setNameWithFormat:@"[%@] -replayLazily", self.name]; +} + +- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + RACDisposable *timeoutDisposable = [scheduler afterDelay:interval schedule:^{ + [disposable dispose]; + [subscriber sendError:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorTimedOut userInfo:nil]]; + }]; + + [disposable addDisposable:timeoutDisposable]; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [disposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [disposable dispose]; + [subscriber sendCompleted]; + }]; + + [disposable addDisposable:subscriptionDisposable]; + return disposable; + }] setNameWithFormat:@"[%@] -timeout: %f", self.name, (double)interval]; +} + +- (RACSignal *)deliverOn:(RACScheduler *)scheduler { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [scheduler schedule:^{ + [subscriber sendNext:x]; + }]; + } error:^(NSError *error) { + [scheduler schedule:^{ + [subscriber sendError:error]; + }]; + } completed:^{ + [scheduler schedule:^{ + [subscriber sendCompleted]; + }]; + }]; + }] setNameWithFormat:@"[%@] -deliverOn: %@", self.name, scheduler]; +} + +- (RACSignal *)subscribeOn:(RACScheduler *)scheduler { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + RACDisposable *schedulingDisposable = [scheduler schedule:^{ + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + [disposable addDisposable:subscriptionDisposable]; + }]; + + [disposable addDisposable:schedulingDisposable]; + return disposable; + }] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler]; +} + +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock { + NSCParameterAssert(keyBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSMutableDictionary *groups = [NSMutableDictionary dictionary]; + + return [self subscribeNext:^(id x) { + id<NSCopying> key = keyBlock(x); + RACGroupedSignal *groupSubject = nil; + @synchronized(groups) { + groupSubject = [groups objectForKey:key]; + if(groupSubject == nil) { + groupSubject = [RACGroupedSignal signalWithKey:key]; + [groups setObject:groupSubject forKey:key]; + [subscriber sendNext:groupSubject]; + } + } + + [groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + + [groups.allValues makeObjectsPerformSelector:@selector(sendError:) withObject:error]; + } completed:^{ + [subscriber sendCompleted]; + + [groups.allValues makeObjectsPerformSelector:@selector(sendCompleted)]; + }]; + }] setNameWithFormat:@"[%@] -groupBy:transform:", self.name]; +} + +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock { + return [[self groupBy:keyBlock transform:nil] setNameWithFormat:@"[%@] -groupBy:", self.name]; +} + +- (RACSignal *)any { + return [[self any:^(id x) { + return YES; + }] setNameWithFormat:@"[%@] -any", self.name]; +} + +- (RACSignal *)any:(BOOL (^)(id object))predicateBlock { + NSCParameterAssert(predicateBlock != NULL); + + return [[[self materialize] bind:^{ + return ^(RACEvent *event, BOOL *stop) { + if (event.finished) { + *stop = YES; + return [RACSignal return:@NO]; + } + + if (predicateBlock(event.value)) { + *stop = YES; + return [RACSignal return:@YES]; + } + + return [RACSignal empty]; + }; + }] setNameWithFormat:@"[%@] -any:", self.name]; +} + +- (RACSignal *)all:(BOOL (^)(id object))predicateBlock { + NSCParameterAssert(predicateBlock != NULL); + + return [[[self materialize] bind:^{ + return ^(RACEvent *event, BOOL *stop) { + if (event.eventType == RACEventTypeCompleted) { + *stop = YES; + return [RACSignal return:@YES]; + } + + if (event.eventType == RACEventTypeError || !predicateBlock(event.value)) { + *stop = YES; + return [RACSignal return:@NO]; + } + + return [RACSignal empty]; + }; + }] setNameWithFormat:@"[%@] -all:", self.name]; +} + +- (RACSignal *)retry:(NSInteger)retryCount { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block NSInteger currentRetryCount = 0; + return subscribeForever(self, + ^(id x) { + [subscriber sendNext:x]; + }, + ^(NSError *error, RACDisposable *disposable) { + if (retryCount == 0 || currentRetryCount < retryCount) { + // Resubscribe. + currentRetryCount++; + return; + } + + [disposable dispose]; + [subscriber sendError:error]; + }, + ^(RACDisposable *disposable) { + [disposable dispose]; + [subscriber sendCompleted]; + }); + }] setNameWithFormat:@"[%@] -retry: %lu", self.name, (unsigned long)retryCount]; +} + +- (RACSignal *)retry { + return [[self retry:0] setNameWithFormat:@"[%@] -retry", self.name]; +} + +- (RACSignal *)sample:(RACSignal *)sampler { + NSCParameterAssert(sampler != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSLock *lock = [[NSLock alloc] init]; + __block id lastValue; + __block BOOL hasValue = NO; + + RACSerialDisposable *samplerDisposable = [[RACSerialDisposable alloc] init]; + RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { + [lock lock]; + hasValue = YES; + lastValue = x; + [lock unlock]; + } error:^(NSError *error) { + [samplerDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [samplerDisposable dispose]; + [subscriber sendCompleted]; + }]; + + samplerDisposable.disposable = [sampler subscribeNext:^(id _) { + BOOL shouldSend = NO; + id value; + [lock lock]; + shouldSend = hasValue; + value = lastValue; + [lock unlock]; + + if (shouldSend) { + [subscriber sendNext:value]; + } + } error:^(NSError *error) { + [sourceDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [sourceDisposable dispose]; + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [samplerDisposable dispose]; + [sourceDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -sample: %@", self.name, sampler]; +} + +- (RACSignal *)ignoreValues { + return [[self filter:^(id _) { + return NO; + }] setNameWithFormat:@"[%@] -ignoreValues", self.name]; +} + +- (RACSignal *)materialize { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [subscriber sendNext:[RACEvent eventWithValue:x]]; + } error:^(NSError *error) { + [subscriber sendNext:[RACEvent eventWithError:error]]; + [subscriber sendCompleted]; + } completed:^{ + [subscriber sendNext:RACEvent.completedEvent]; + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -materialize", self.name]; +} + +- (RACSignal *)dematerialize { + return [[self bind:^{ + return ^(RACEvent *event, BOOL *stop) { + switch (event.eventType) { + case RACEventTypeCompleted: + *stop = YES; + return [RACSignal empty]; + + case RACEventTypeError: + *stop = YES; + return [RACSignal error:event.error]; + + case RACEventTypeNext: + return [RACSignal return:event.value]; + } + }; + }] setNameWithFormat:@"[%@] -dematerialize", self.name]; +} + +- (RACSignal *)not { + return [[self map:^(NSNumber *value) { + NSCAssert([value isKindOfClass:NSNumber.class], @"-not must only be used on a signal of NSNumbers. Instead, got: %@", value); + + return @(!value.boolValue); + }] setNameWithFormat:@"[%@] -not", self.name]; +} + +- (RACSignal *)and { + return [[self map:^(RACTuple *tuple) { + NSCAssert([tuple isKindOfClass:RACTuple.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple); + NSCAssert(tuple.count > 0, @"-and must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple"); + + return @([tuple.rac_sequence all:^(NSNumber *number) { + NSCAssert([number isKindOfClass:NSNumber.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple); + + return number.boolValue; + }]); + }] setNameWithFormat:@"[%@] -and", self.name]; +} + +- (RACSignal *)or { + return [[self map:^(RACTuple *tuple) { + NSCAssert([tuple isKindOfClass:RACTuple.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple); + NSCAssert(tuple.count > 0, @"-or must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple"); + + return @([tuple.rac_sequence any:^(NSNumber *number) { + NSCAssert([number isKindOfClass:NSNumber.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple); + + return number.boolValue; + }]); + }] setNameWithFormat:@"[%@] -or", self.name]; +} + +@end + +@implementation RACSignal (OperationsDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock { + NSCParameterAssert(openSignal != nil); + NSCParameterAssert(closeBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block RACSubject *currentWindow = nil; + __block RACSignal *currentCloseWindow = nil; + __block RACDisposable *closeObserverDisposable = NULL; + + void (^closeCurrentWindow)(void) = ^{ + [currentWindow sendCompleted]; + currentWindow = nil; + currentCloseWindow = nil; + [closeObserverDisposable dispose], closeObserverDisposable = nil; + }; + + RACDisposable *openObserverDisposable = [openSignal subscribe:[RACSubscriber subscriberWithNext:^(id x) { + if(currentWindow == nil) { + currentWindow = [RACSubject subject]; + [subscriber sendNext:currentWindow]; + + currentCloseWindow = closeBlock(currentWindow); + closeObserverDisposable = [currentCloseWindow subscribe:[RACSubscriber subscriberWithNext:^(id x) { + closeCurrentWindow(); + } error:^(NSError *error) { + closeCurrentWindow(); + } completed:^{ + closeCurrentWindow(); + }]]; + } + } error:^(NSError *error) { + + } completed:^{ + + }]]; + + RACDisposable *selfObserverDisposable = [self subscribeNext:^(id x) { + [currentWindow sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [closeObserverDisposable dispose]; + [openObserverDisposable dispose]; + [selfObserverDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -windowWithStart: %@ close:", self.name, openSignal]; +} + +- (RACSignal *)buffer:(NSUInteger)bufferCount { + NSCParameterAssert(bufferCount > 0); + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSMutableArray *values = [NSMutableArray arrayWithCapacity:bufferCount]; + RACSubject *windowCloseSubject = [RACSubject subject]; + + RACDisposable *closeDisposable = [windowCloseSubject subscribeNext:^(id x) { + [subscriber sendNext:[RACTuple tupleWithObjectsFromArray:values convertNullsToNils:NO]]; + [values removeAllObjects]; + }]; + + __block RACDisposable *innerDisposable = nil; + RACDisposable *outerDisposable = [[self windowWithStart:self close:^(RACSignal *start) { + return windowCloseSubject; + }] subscribeNext:^(id x) { + innerDisposable = [x subscribeNext:^(id x) { + [values addObject:x ? : [RACTupleNil tupleNil]]; + if(values.count % bufferCount == 0) { + [windowCloseSubject sendNext:[RACUnit defaultUnit]]; + } + }]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [innerDisposable dispose]; + [outerDisposable dispose]; + [closeDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -buffer: %lu", self.name, (unsigned long)bufferCount]; +} + +- (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock { + NSCParameterAssert(letBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACMulticastConnection *connection = [self publish]; + RACDisposable *finalDisposable = [letBlock(connection.signal) subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + RACDisposable *connectionDisposable = [connection connect]; + + return [RACDisposable disposableWithBlock:^{ + [connectionDisposable dispose]; + [finalDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -let:", self.name]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval { + return [RACSignal interval:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway { + return [RACSignal interval:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh] withLeeway:leeway]; +} + +- (RACSignal *)timeout:(NSTimeInterval)interval { + return [self timeout:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; +} + +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval { + return [self bufferWithTime:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; +} + +- (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object { + return [self setKeyPath:keyPath onObject:object]; +} + +- (RACSignal *)ignoreElements { + return [self ignoreValues]; +} + +- (RACSignal *)sequenceNext:(RACSignal * (^)(void))block { + return [self then:block]; +} + +- (RACSignal *)aggregateWithStart:(id)start combine:(id (^)(id running, id next))combineBlock { + return [self aggregateWithStart:start reduce:combineBlock]; +} + +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory combine:(id (^)(id running, id next))combineBlock { + return [self aggregateWithStartFactory:startFactory reduce:combineBlock]; +} + +- (RACDisposable *)executeCommand:(RACCommand *)command { + NSCParameterAssert(command != nil); + + return [self subscribeNext:^(id x) { + [command execute:x]; + }]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h new file mode 100644 index 0000000..ed644cf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h
@@ -0,0 +1,219 @@ +// +// RACSignal.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/1/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACStream.h" + +@class RACDisposable; +@class RACScheduler; +@class RACSubject; +@protocol RACSubscriber; + +@interface RACSignal : RACStream + +/// Creates a new signal. This is the preferred way to create a new signal +/// operation or behavior. +/// +/// Events can be sent to new subscribers immediately in the `didSubscribe` +/// block, but the subscriber will not be able to dispose of the signal until +/// a RACDisposable is returned from `didSubscribe`. In the case of infinite +/// signals, this won't _ever_ happen if events are sent immediately. +/// +/// To ensure that the signal is disposable, events can be scheduled on the +/// +[RACScheduler currentScheduler] (so that they're deferred, not sent +/// immediately), or they can be sent in the background. The RACDisposable +/// returned by the `didSubscribe` block should cancel any such scheduling or +/// asynchronous work. +/// +/// didSubscribe - Called when the signal is subscribed to. The new subscriber is +/// passed in. You can then manually control the <RACSubscriber> by +/// sending it -sendNext:, -sendError:, and -sendCompleted, +/// as defined by the operation you're implementing. This block +/// should return a RACDisposable which cancels any ongoing work +/// triggered by the subscription, and cleans up any resources or +/// disposables created as part of it. When the disposable is +/// disposed of, the signal must not send any more events to the +/// `subscriber`. If no cleanup is necessary, return nil. +/// +/// **Note:** The `didSubscribe` block is called every time a new subscriber +/// subscribes. Any side effects within the block will thus execute once for each +/// subscription, not necessarily on one thread, and possibly even +/// simultaneously! ++ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe; + +/// Returns a signal that immediately sends the given error. ++ (RACSignal *)error:(NSError *)error; + +/// Returns a signal that never completes. ++ (RACSignal *)never; + +/// Immediately schedules the given block on the given scheduler. The block is +/// given a subscriber to which it can send events. +/// +/// scheduler - The scheduler on which `block` will be scheduled and results +/// delivered. Cannot be nil. +/// block - The block to invoke. Cannot be NULL. +/// +/// Returns a signal which will send all events sent on the subscriber given to +/// `block`. All events will be sent on `scheduler` and it will replay any missed +/// events to new subscribers. ++ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block; + +/// Invokes the given block only on the first subscription. The block is given a +/// subscriber to which it can send events. +/// +/// Note that disposing of the subscription to the returned signal will *not* +/// dispose of the underlying subscription. If you need that behavior, see +/// -[RACMulticastConnection autoconnect]. The underlying subscription will never +/// be disposed of. Because of this, `block` should never return an infinite +/// signal since there would be no way of ending it. +/// +/// scheduler - The scheduler on which the block should be scheduled. Note that +/// if given +[RACScheduler immediateScheduler], the block will be +/// invoked synchronously on the first subscription. Cannot be nil. +/// block - The block to invoke on the first subscription. Cannot be NULL. +/// +/// Returns a signal which will pass through the events sent to the subscriber +/// given to `block` and replay any missed events to new subscribers. ++ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block; + +@end + +@interface RACSignal (RACStream) + +/// Returns a signal that immediately sends the given value and then completes. ++ (RACSignal *)return:(id)value; + +/// Returns a signal that immediately completes. ++ (RACSignal *)empty; + +/// Subscribes to `signal` when the source signal completes. +- (RACSignal *)concat:(RACSignal *)signal; + +/// Zips the values in the receiver with those of the given signal to create +/// RACTuples. +/// +/// The first `next` of each stream will be combined, then the second `next`, and +/// so forth, until either signal completes or errors. +/// +/// signal - The signal to zip with. This must not be `nil`. +/// +/// Returns a new signal of RACTuples, representing the combined values of the +/// two signals. Any error from one of the original signals will be forwarded on +/// the returned signal. +- (RACSignal *)zipWith:(RACSignal *)signal; + +@end + +@interface RACSignal (Subscription) + +/// Subscribes `subscriber` to changes on the receiver. The receiver defines which +/// events it actually sends and in what situations the events are sent. +/// +/// Subscription will always happen on a valid RACScheduler. If the +/// +[RACScheduler currentScheduler] cannot be determined at the time of +/// subscription (e.g., because the calling code is running on a GCD queue or +/// NSOperationQueue), subscription will occur on a private background scheduler. +/// On the main thread, subscriptions will always occur immediately, with a +/// +[RACScheduler currentScheduler] of +[RACScheduler mainThreadScheduler]. +/// +/// This method must be overridden by any subclasses. +/// +/// Returns nil or a disposable. You can call -[RACDisposable dispose] if you +/// need to end your subscription before it would "naturally" end, either by +/// completing or erroring. Once the disposable has been disposed, the subscriber +/// won't receive any more events from the subscription. +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber; + +/// Convenience method to subscribe to the `next` event. +/// +/// This corresponds to `IObserver<T>.OnNext` in Rx. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock; + +/// Convenience method to subscribe to the `next` and `completed` events. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock; + +/// Convenience method to subscribe to the `next`, `completed`, and `error` events. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; + +/// Convenience method to subscribe to `error` events. +/// +/// This corresponds to the `IObserver<T>.OnError` in Rx. +- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock; + +/// Convenience method to subscribe to `completed` events. +/// +/// This corresponds to the `IObserver<T>.OnCompleted` in Rx. +- (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock; + +/// Convenience method to subscribe to `next` and `error` events. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock; + +/// Convenience method to subscribe to `error` and `completed` events. +- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; + +@end + +/// Additional methods to assist with debugging. +@interface RACSignal (Debugging) + +/// Logs all events that the receiver sends. +- (RACSignal *)logAll; + +/// Logs each `next` that the receiver sends. +- (RACSignal *)logNext; + +/// Logs any error that the receiver sends. +- (RACSignal *)logError; + +/// Logs any `completed` event that the receiver sends. +- (RACSignal *)logCompleted; + +@end + +/// Additional methods to assist with unit testing. +/// +/// **These methods should never ship in production code.** +@interface RACSignal (Testing) + +/// Spins the main run loop for a short while, waiting for the receiver to send a `next`. +/// +/// **Because this method executes the run loop recursively, it should only be used +/// on the main thread, and only from a unit test.** +/// +/// defaultValue - Returned if the receiver completes or errors before sending +/// a `next`, or if the method times out. This argument may be +/// nil. +/// success - If not NULL, set to whether the receiver completed +/// successfully. +/// error - If not NULL, set to any error that occurred. +/// +/// Returns the first value received, or `defaultValue` if no value is received +/// before the signal finishes or the method times out. +- (id)asynchronousFirstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error; + +/// Spins the main run loop for a short while, waiting for the receiver to complete. +/// +/// **Because this method executes the run loop recursively, it should only be used +/// on the main thread, and only from a unit test.** +/// +/// error - If not NULL, set to any error that occurs. +/// +/// Returns whether the signal completed successfully before timing out. If NO, +/// `error` will be set to any error that occurred. +- (BOOL)asynchronouslyWaitUntilCompleted:(NSError **)error; + +@end + +@interface RACSignal (Deprecated) + ++ (RACSignal *)start:(id (^)(BOOL *success, NSError **error))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler subjectBlock:(void (^)(RACSubject *subject))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler block:(id (^)(BOOL *success, NSError **error))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.m new file mode 100644 index 0000000..69d9700 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.m
@@ -0,0 +1,447 @@ +// +// RACSignal.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/15/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACDynamicSignal.h" +#import "RACEmptySignal.h" +#import "RACErrorSignal.h" +#import "RACMulticastConnection.h" +#import "RACReplaySubject.h" +#import "RACReturnSignal.h" +#import "RACScheduler.h" +#import "RACSerialDisposable.h" +#import "RACSignal+Operations.h" +#import "RACSubject.h" +#import "RACSubscriber+Private.h" +#import "RACTuple.h" + +@implementation RACSignal + +#pragma mark Lifecycle + ++ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { + return [RACDynamicSignal createSignal:didSubscribe]; +} + ++ (RACSignal *)error:(NSError *)error { + return [RACErrorSignal error:error]; +} + ++ (RACSignal *)never { + return [[self createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + return nil; + }] setNameWithFormat:@"+never"]; +} + ++ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(block != NULL); + + RACSignal *signal = [self startLazilyWithScheduler:scheduler block:block]; + // Subscribe to force the lazy signal to call its block. + [[signal publish] connect]; + return [signal setNameWithFormat:@"+startEagerlyWithScheduler:%@ block:", scheduler]; +} + ++ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(block != NULL); + + RACMulticastConnection *connection = [[RACSignal + createSignal:^ id (id<RACSubscriber> subscriber) { + block(subscriber); + return nil; + }] + multicast:[RACReplaySubject subject]]; + + return [[[RACSignal + createSignal:^ id (id<RACSubscriber> subscriber) { + [connection.signal subscribe:subscriber]; + [connection connect]; + return nil; + }] + subscribeOn:scheduler] + setNameWithFormat:@"+startLazilyWithScheduler:%@ block:", scheduler]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p> name: %@", self.class, self, self.name]; +} + +@end + +@implementation RACSignal (RACStream) + ++ (RACSignal *)empty { + return [RACEmptySignal empty]; +} + ++ (RACSignal *)return:(id)value { + return [RACReturnSignal return:value]; +} + +- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block { + NSCParameterAssert(block != NULL); + + /* + * -bind: should: + * + * 1. Subscribe to the original signal of values. + * 2. Any time the original signal sends a value, transform it using the binding block. + * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received. + * 4. If the binding block asks the bind to terminate, complete the _original_ signal. + * 5. When _all_ signals complete, send completed to the subscriber. + * + * If any signal sends an error at any point, send that to the subscriber. + */ + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACStreamBindBlock bindingBlock = block(); + + NSMutableArray *signals = [NSMutableArray arrayWithObject:self]; + + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) { + BOOL removeDisposable = NO; + + @synchronized (signals) { + [signals removeObject:signal]; + + if (signals.count == 0) { + [subscriber sendCompleted]; + [compoundDisposable dispose]; + } else { + removeDisposable = YES; + } + } + + if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; + }; + + void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { + @synchronized (signals) { + [signals addObject:signal]; + } + + RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; + [compoundDisposable addDisposable:selfDisposable]; + + RACDisposable *disposable = [signal subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [compoundDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + @autoreleasepool { + completeSignal(signal, selfDisposable); + } + }]; + + selfDisposable.disposable = disposable; + }; + + @autoreleasepool { + RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; + [compoundDisposable addDisposable:selfDisposable]; + + RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { + BOOL stop = NO; + id signal = bindingBlock(x, &stop); + + @autoreleasepool { + if (signal != nil) addSignal(signal); + if (signal == nil || stop) { + [selfDisposable dispose]; + completeSignal(self, selfDisposable); + } + } + } error:^(NSError *error) { + [compoundDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + @autoreleasepool { + completeSignal(self, selfDisposable); + } + }]; + + selfDisposable.disposable = bindingDisposable; + } + + return compoundDisposable; + }] setNameWithFormat:@"[%@] -bind:", self.name]; +} + +- (RACSignal *)concat:(RACSignal *)signal { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; + + RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + RACDisposable *concattedDisposable = [signal subscribe:subscriber]; + serialDisposable.disposable = concattedDisposable; + }]; + + serialDisposable.disposable = sourceDisposable; + return serialDisposable; + }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal]; +} + +- (RACSignal *)zipWith:(RACSignal *)signal { + NSCParameterAssert(signal != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block BOOL selfCompleted = NO; + NSMutableArray *selfValues = [NSMutableArray array]; + + __block BOOL otherCompleted = NO; + NSMutableArray *otherValues = [NSMutableArray array]; + + void (^sendCompletedIfNecessary)(void) = ^{ + @synchronized (selfValues) { + BOOL selfEmpty = (selfCompleted && selfValues.count == 0); + BOOL otherEmpty = (otherCompleted && otherValues.count == 0); + if (selfEmpty || otherEmpty) [subscriber sendCompleted]; + } + }; + + void (^sendNext)(void) = ^{ + @synchronized (selfValues) { + if (selfValues.count == 0) return; + if (otherValues.count == 0) return; + + RACTuple *tuple = [RACTuple tupleWithObjects:selfValues[0], otherValues[0], nil]; + [selfValues removeObjectAtIndex:0]; + [otherValues removeObjectAtIndex:0]; + + [subscriber sendNext:tuple]; + sendCompletedIfNecessary(); + } + }; + + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + @synchronized (selfValues) { + [selfValues addObject:x ?: RACTupleNil.tupleNil]; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (selfValues) { + selfCompleted = YES; + sendCompletedIfNecessary(); + } + }]; + + RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { + @synchronized (selfValues) { + [otherValues addObject:x ?: RACTupleNil.tupleNil]; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (selfValues) { + otherCompleted = YES; + sendCompletedIfNecessary(); + } + }]; + + return [RACDisposable disposableWithBlock:^{ + [selfDisposable dispose]; + [otherDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal]; +} + +@end + +@implementation RACSignal (Subscription) + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCAssert(NO, @"This method must be overridden by subclasses"); + return nil; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock { + NSCParameterAssert(nextBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock { + NSCParameterAssert(nextBlock != NULL); + NSCParameterAssert(completedBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:completedBlock]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock { + NSCParameterAssert(nextBlock != NULL); + NSCParameterAssert(errorBlock != NULL); + NSCParameterAssert(completedBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock { + NSCParameterAssert(errorBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:errorBlock completed:NULL]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock { + NSCParameterAssert(completedBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:NULL completed:completedBlock]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock { + NSCParameterAssert(nextBlock != NULL); + NSCParameterAssert(errorBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:NULL]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeError:(void (^)(NSError *))errorBlock completed:(void (^)(void))completedBlock { + NSCParameterAssert(completedBlock != NULL); + NSCParameterAssert(errorBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:errorBlock completed:completedBlock]; + return [self subscribe:o]; +} + +@end + +@implementation RACSignal (Debugging) + +- (RACSignal *)logAll { + return [[[self logNext] logError] logCompleted]; +} + +- (RACSignal *)logNext { + return [[self doNext:^(id x) { + NSLog(@"%@ next: %@", self, x); + }] setNameWithFormat:@"%@", self.name]; +} + +- (RACSignal *)logError { + return [[self doError:^(NSError *error) { + NSLog(@"%@ error: %@", self, error); + }] setNameWithFormat:@"%@", self.name]; +} + +- (RACSignal *)logCompleted { + return [[self doCompleted:^{ + NSLog(@"%@ completed", self); + }] setNameWithFormat:@"%@", self.name]; +} + +@end + +@implementation RACSignal (Testing) + +static const NSTimeInterval RACSignalAsynchronousWaitTimeout = 10; + +- (id)asynchronousFirstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error { + NSCAssert([NSThread isMainThread], @"%s should only be used from the main thread", __func__); + + __block id result = defaultValue; + __block BOOL done = NO; + + // Ensures that we don't pass values across thread boundaries by reference. + __block NSError *localError; + __block BOOL localSuccess = YES; + + [[[[self + take:1] + timeout:RACSignalAsynchronousWaitTimeout onScheduler:[RACScheduler scheduler]] + deliverOn:RACScheduler.mainThreadScheduler] + subscribeNext:^(id x) { + result = x; + done = YES; + } error:^(NSError *e) { + if (!done) { + localSuccess = NO; + localError = e; + done = YES; + } + } completed:^{ + done = YES; + }]; + + do { + [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } while (!done); + + if (success != NULL) *success = localSuccess; + if (error != NULL) *error = localError; + + return result; +} + +- (BOOL)asynchronouslyWaitUntilCompleted:(NSError **)error { + BOOL success = NO; + [[self ignoreValues] asynchronousFirstOrDefault:nil success:&success error:error]; + return success; +} + +@end + +@implementation RACSignal (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler subjectBlock:(void (^)(RACSubject *subject))block { + NSCParameterAssert(block != NULL); + + RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"+startWithScheduler:subjectBlock:"]; + + [scheduler schedule:^{ + block(subject); + }]; + + return subject; +} + ++ (RACSignal *)start:(id (^)(BOOL *success, NSError **error))block { + return [[self startWithScheduler:[RACScheduler scheduler] block:block] setNameWithFormat:@"+start:"]; +} + ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler block:(id (^)(BOOL *success, NSError **error))block { + return [[self startWithScheduler:scheduler subjectBlock:^(id<RACSubscriber> subscriber) { + BOOL success = YES; + NSError *error = nil; + id returned = block(&success, &error); + + if (!success) { + [subscriber sendError:error]; + } else { + [subscriber sendNext:returned]; + [subscriber sendCompleted]; + } + }] setNameWithFormat:@"+startWithScheduler:block:"]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignalProvider.d b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalProvider.d new file mode 100644 index 0000000..8add9a1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalProvider.d
@@ -0,0 +1,5 @@ +provider RACSignal { + probe next(char *signal, char *subscriber, char *valueDescription); + probe completed(char *signal, char *subscriber); + probe error(char *signal, char *subscriber, char *errorDescription); +};
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.h new file mode 100644 index 0000000..e3ab77d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.h
@@ -0,0 +1,19 @@ +// +// RACSignalSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-09. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSequence.h" + +@class RACSignal; + +// Private class that adapts a RACSignal to the RACSequence interface. +@interface RACSignalSequence : RACSequence + +// Returns a sequence for enumerating over the given signal. ++ (RACSequence *)sequenceWithSignal:(RACSignal *)signal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.m new file mode 100644 index 0000000..52ea1b9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.m
@@ -0,0 +1,79 @@ +// +// RACSignalSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-09. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignalSequence.h" +#import "RACDisposable.h" +#import "RACReplaySubject.h" +#import "RACSignal+Operations.h" + +@interface RACSignalSequence () + +// Replays the signal given on initialization. +@property (nonatomic, strong, readonly) RACReplaySubject *subject; + +@end + +@implementation RACSignalSequence + +#pragma mark Lifecycle + ++ (RACSequence *)sequenceWithSignal:(RACSignal *)signal { + RACSignalSequence *seq = [[self alloc] init]; + + RACReplaySubject *subject = [RACReplaySubject subject]; + [signal subscribeNext:^(id value) { + [subject sendNext:value]; + } error:^(NSError *error) { + [subject sendError:error]; + } completed:^{ + [subject sendCompleted]; + }]; + + seq->_subject = subject; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + id value = [self.subject firstOrDefault:self]; + + if (value == self) { + return nil; + } else { + return value ?: NSNull.null; + } +} + +- (RACSequence *)tail { + RACSequence *sequence = [self.class sequenceWithSignal:[self.subject skip:1]]; + sequence.name = self.name; + return sequence; +} + +- (NSArray *)array { + return self.subject.toArray; +} + +#pragma mark NSObject + +- (NSString *)description { + // Synchronously accumulate the values that have been sent so far. + NSMutableArray *values = [NSMutableArray array]; + RACDisposable *disposable = [self.subject subscribeNext:^(id value) { + @synchronized (values) { + [values addObject:value ?: NSNull.null]; + } + }]; + + [disposable dispose]; + + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, values = %@ … }", self.class, self, self.name, values]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStream+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACStream+Private.h new file mode 100644 index 0000000..f6c0407 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStream+Private.h
@@ -0,0 +1,23 @@ +// +// RACStream+Private.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACStream.h" + +@interface RACStream () + +// Combines a list of streams using the logic of the given block. +// +// streams - The streams to combine. +// block - An operator that combines two streams and returns a new one. The +// returned stream should contain 2-tuples of the streams' combined +// values. +// +// Returns a combined stream. ++ (instancetype)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStream.h b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.h new file mode 100644 index 0000000..8853ffe --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.h
@@ -0,0 +1,321 @@ +// +// RACStream.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-31. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACStream; + +/// A block which accepts a value from a RACStream and returns a new instance +/// of the same stream class. +/// +/// Setting `stop` to `YES` will cause the bind to terminate after the returned +/// value. Returning `nil` will result in immediate termination. +typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop); + +/// An abstract class representing any stream of values. +/// +/// This class represents a monad, upon which many stream-based operations can +/// be built. +/// +/// When subclassing RACStream, only the methods in the main @interface body need +/// to be overridden. +@interface RACStream : NSObject + +/// Returns an empty stream. ++ (instancetype)empty; + +/// Lifts `value` into the stream monad. +/// +/// Returns a stream containing only the given value. ++ (instancetype)return:(id)value; + +/// Lazily binds a block to the values in the receiver. +/// +/// This should only be used if you need to terminate the bind early, or close +/// over some state. -flattenMap: is more appropriate for all other cases. +/// +/// block - A block returning a RACStreamBindBlock. This block will be invoked +/// each time the bound stream is re-evaluated. This block must not be +/// nil or return nil. +/// +/// Returns a new stream which represents the combined result of all lazy +/// applications of `block`. +- (instancetype)bind:(RACStreamBindBlock (^)(void))block; + +/// Appends the values of `stream` to the values in the receiver. +/// +/// stream - A stream to concatenate. This must be an instance of the same +/// concrete class as the receiver, and should not be `nil`. +/// +/// Returns a new stream representing the receiver followed by `stream`. +- (instancetype)concat:(RACStream *)stream; + +/// Zips the values in the receiver with those of the given stream to create +/// RACTuples. +/// +/// The first value of each stream will be combined, then the second value, and +/// so forth, until at least one of the streams is exhausted. +/// +/// stream - The stream to zip with. This must be an instance of the same +/// concrete class as the receiver, and should not be `nil`. +/// +/// Returns a new stream of RACTuples, representing the zipped values of the +/// two streams. +- (instancetype)zipWith:(RACStream *)stream; + +@end + +/// This extension contains functionality to support naming streams for +/// debugging. +/// +/// Subclasses do not need to override the methods here. +@interface RACStream () + +/// The name of the stream. This is for debugging/human purposes only. +@property (copy) NSString *name; + +/// Sets the name of the receiver to the given format string. +/// +/// This is for debugging purposes only, and won't do anything unless the +/// RAC_DEBUG_SIGNAL_NAMES environment variable is set. +/// +/// Returns the receiver, for easy method chaining. +- (instancetype)setNameWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); + +@end + +/// Operations built on the RACStream primitives. +/// +/// These methods do not need to be overridden, although subclasses may +/// occasionally gain better performance from doing so. +@interface RACStream (Operations) + +/// Maps `block` across the values in the receiver and flattens the result. +/// +/// Note that operators applied _after_ -flattenMap: behave differently from +/// operators _within_ -flattenMap:. See the Examples section below. +/// +/// This corresponds to the `SelectMany` method in Rx. +/// +/// block - A block which accepts the values in the receiver and returns a new +/// instance of the receiver's class. Returning `nil` from this block is +/// equivalent to returning an empty signal. +/// +/// Examples +/// +/// [signal flattenMap:^(id x) { +/// // Logs each time a returned signal completes. +/// return [[RACSignal return:x] logCompleted]; +/// }]; +/// +/// [[signal +/// flattenMap:^(id x) { +/// return [RACSignal return:x]; +/// }] +/// // Logs only once, when all of the signals complete. +/// logCompleted]; +/// +/// Returns a new stream which represents the combined streams resulting from +/// mapping `block`. +- (instancetype)flattenMap:(RACStream * (^)(id value))block; + +/// Flattens a stream of streams. +/// +/// This corresponds to the `Merge` method in Rx. +/// +/// Returns a stream consisting of the combined streams obtained from the +/// receiver. +- (instancetype)flatten; + +/// Maps `block` across the values in the receiver. +/// +/// This corresponds to the `Select` method in Rx. +/// +/// Returns a new stream with the mapped values. +- (instancetype)map:(id (^)(id value))block; + +/// Replace each value in the receiver with the given object. +/// +/// Returns a new stream which includes the given object once for each value in +/// the receiver. +- (instancetype)mapReplace:(id)object; + +/// Filters out values in the receiver that don't pass the given test. +/// +/// This corresponds to the `Where` method in Rx. +/// +/// Returns a new stream with only those values that passed. +- (instancetype)filter:(BOOL (^)(id value))block; + +/// Filters out values in the receiver that equal (via -isEqual:) the provided value. +/// +/// value - The value can be `nil`, in which case it ignores `nil` values. +/// +/// Returns a new stream containing only the values which did not compare equal +/// to `value`. +- (instancetype)ignore:(id)value; + +/// Unpacks each RACTuple in the receiver and maps the values to a new value. +/// +/// reduceBlock - The block which reduces each RACTuple's values into one value. +/// It must take as many arguments as the number of tuple elements +/// to process. Each argument will be an object argument. The +/// return value must be an object. This argument cannot be nil. +/// +/// Returns a new stream of reduced tuple values. +- (instancetype)reduceEach:(id (^)())reduceBlock; + +/// Returns a stream consisting of `value`, followed by the values in the +/// receiver. +- (instancetype)startWith:(id)value; + +/// Skips the first `skipCount` values in the receiver. +/// +/// Returns the receiver after skipping the first `skipCount` values. If +/// `skipCount` is greater than the number of values in the stream, an empty +/// stream is returned. +- (instancetype)skip:(NSUInteger)skipCount; + +/// Returns a stream of the first `count` values in the receiver. If `count` is +/// greater than or equal to the number of values in the stream, a stream +/// equivalent to the receiver is returned. +- (instancetype)take:(NSUInteger)count; + +/// Zips the values in the given streams to create RACTuples. +/// +/// The first value of each stream will be combined, then the second value, and +/// so forth, until at least one of the streams is exhausted. +/// +/// streams - The streams to combine. These must all be instances of the same +/// concrete class implementing the protocol. If this collection is +/// empty, the returned stream will be empty. +/// +/// Returns a new stream containing RACTuples of the zipped values from the +/// streams. ++ (instancetype)zip:(id<NSFastEnumeration>)streams; + +/// Zips streams using +zip:, then reduces the resulting tuples into a single +/// value using -reduceEach: +/// +/// streams - The streams to combine. These must all be instances of the +/// same concrete class implementing the protocol. If this +/// collection is empty, the returned stream will be empty. +/// reduceBlock - The block which reduces the values from all the streams +/// into one value. It must take as many arguments as the +/// number of streams given. Each argument will be an object +/// argument. The return value must be an object. This argument +/// must not be nil. +/// +/// Example: +/// +/// [RACStream zip:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) { +/// return [NSString stringWithFormat:@"%@: %@", string, number]; +/// }]; +/// +/// Returns a new stream containing the results from each invocation of +/// `reduceBlock`. ++ (instancetype)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock; + +/// Returns a stream obtained by concatenating `streams` in order. ++ (instancetype)concat:(id<NSFastEnumeration>)streams; + +/// Combines values in the receiver from left to right using the given block. +/// +/// The algorithm proceeds as follows: +/// +/// 1. `startingValue` is passed into the block as the `running` value, and the +/// first element of the receiver is passed into the block as the `next` value. +/// 2. The result of the invocation is added to the returned stream. +/// 3. The result of the invocation (`running`) and the next element of the +/// receiver (`next`) is passed into `block`. +/// 4. Steps 2 and 3 are repeated until all values have been processed. +/// +/// startingValue - The value to be combined with the first element of the +/// receiver. This value may be `nil`. +/// block - A block that describes how to combine values of the +/// receiver. If the receiver is empty, this block will never be +/// invoked. +/// +/// Examples +/// +/// RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence; +/// +/// // Contains 1, 3, 6, 10 +/// RACSequence *sums = [numbers scanWithStart:@0 reduce:^(NSNumber *sum, NSNumber *next) { +/// return @(sum.integerValue + next.integerValue); +/// }]; +/// +/// Returns a new stream that consists of each application of `block`. If the +/// receiver is empty, an empty stream is returned. +- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))block; + +/// Combines each previous and current value into one object. +/// +/// This method is similar to -scanWithStart:reduce:, but only ever operates on +/// the previous and current values (instead of the whole stream), and does not +/// pass the return value of `reduceBlock` into the next invocation of it. +/// +/// start - The value passed into `reduceBlock` as `previous` for the +/// first value. +/// reduceBlock - The block that combines the previous value and the current +/// value to create the reduced value. Cannot be nil. +/// +/// Examples +/// +/// RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence; +/// +/// // Contains 1, 3, 5, 7 +/// RACSequence *sums = [numbers combinePreviousWithStart:@0 reduce:^(NSNumber *previous, NSNumber *next) { +/// return @(previous.integerValue + next.integerValue); +/// }]; +/// +/// Returns a new stream consisting of the return values from each application of +/// `reduceBlock`. +- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id current))reduceBlock; + +/// Takes values until the given block returns `YES`. +/// +/// Returns a stream of the initial values in the receiver that fail `predicate`. +/// If `predicate` never returns `YES`, a stream equivalent to the receiver is +/// returned. +- (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate; + +/// Takes values until the given block returns `NO`. +/// +/// Returns a stream of the initial values in the receiver that pass `predicate`. +/// If `predicate` never returns `NO`, a stream equivalent to the receiver is +/// returned. +- (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate; + +/// Skips values until the given block returns `YES`. +/// +/// Returns a stream containing the values of the receiver that follow any +/// initial values failing `predicate`. If `predicate` never returns `YES`, +/// an empty stream is returned. +- (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate; + +/// Skips values until the given block returns `NO`. +/// +/// Returns a stream containing the values of the receiver that follow any +/// initial values passing `predicate`. If `predicate` never returns `NO`, an +/// empty stream is returned. +- (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate; + +/// Returns a stream of values for which -isEqual: returns NO when compared to the +/// previous value. +- (instancetype)distinctUntilChanged; + +@end + +@interface RACStream (Deprecated) + +- (instancetype)sequenceMany:(RACStream * (^)(void))block __attribute__((deprecated("Use -flattenMap: instead"))); +- (instancetype)scanWithStart:(id)startingValue combine:(id (^)(id running, id next))block __attribute__((deprecated("Renamed to -scanWithStart:reduce:"))); +- (instancetype)mapPreviousWithStart:(id)start reduce:(id (^)(id previous, id current))combineBlock __attribute__((deprecated("Renamed to -combinePreviousWithStart:reduce:"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStream.m b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.m new file mode 100644 index 0000000..7c37c69 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.m
@@ -0,0 +1,362 @@ +// +// RACStream.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-31. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACStream.h" +#import "NSObject+RACDescription.h" +#import "RACBlockTrampoline.h" +#import "RACTuple.h" + +@implementation RACStream + +#pragma mark Lifecycle + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + self.name = @""; + return self; +} + +#pragma mark Abstract methods + ++ (instancetype)empty { + return nil; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(void))block { + return nil; +} + ++ (instancetype)return:(id)value { + return nil; +} + +- (instancetype)concat:(RACStream *)stream { + return nil; +} + +- (instancetype)zipWith:(RACStream *)stream { + return nil; +} + +#pragma mark Naming + +- (instancetype)setNameWithFormat:(NSString *)format, ... { + if (getenv("RAC_DEBUG_SIGNAL_NAMES") == NULL) return self; + + NSCParameterAssert(format != nil); + + va_list args; + va_start(args, format); + + NSString *str = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + self.name = str; + return self; +} + +@end + +@implementation RACStream (Operations) + +- (instancetype)flattenMap:(RACStream * (^)(id value))block { + Class class = self.class; + + return [[self bind:^{ + return ^(id value, BOOL *stop) { + id stream = block(value) ?: [class empty]; + NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream); + + return stream; + }; + }] setNameWithFormat:@"[%@] -flattenMap:", self.name]; +} + +- (instancetype)flatten { + __weak RACStream *stream __attribute__((unused)) = self; + return [[self flattenMap:^(id value) { + return value; + }] setNameWithFormat:@"[%@] -flatten", self.name]; +} + +- (instancetype)map:(id (^)(id value))block { + NSCParameterAssert(block != nil); + + Class class = self.class; + + return [[self flattenMap:^(id value) { + return [class return:block(value)]; + }] setNameWithFormat:@"[%@] -map:", self.name]; +} + +- (instancetype)mapReplace:(id)object { + return [[self map:^(id _) { + return object; + }] setNameWithFormat:@"[%@] -mapReplace: %@", self.name, [object rac_description]]; +} + +- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock { + NSCParameterAssert(reduceBlock != NULL); + return [[[self + scanWithStart:[RACTuple tupleWithObjects:start, nil] + reduce:^(RACTuple *previousTuple, id next) { + id value = reduceBlock(previousTuple[0], next); + return [RACTuple tupleWithObjects:next ?: RACTupleNil.tupleNil, value ?: RACTupleNil.tupleNil, nil]; + }] + map:^(RACTuple *tuple) { + return tuple[1]; + }] + setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, [start rac_description]]; +} + +- (instancetype)filter:(BOOL (^)(id value))block { + NSCParameterAssert(block != nil); + + Class class = self.class; + + return [[self flattenMap:^ id (id value) { + if (block(value)) { + return [class return:value]; + } else { + return class.empty; + } + }] setNameWithFormat:@"[%@] -filter:", self.name]; +} + +- (instancetype)ignore:(id)value { + return [[self filter:^ BOOL (id innerValue) { + return innerValue != value && ![innerValue isEqual:value]; + }] setNameWithFormat:@"[%@] -ignore: %@", self.name, [value rac_description]]; +} + +- (instancetype)reduceEach:(id (^)())reduceBlock { + NSCParameterAssert(reduceBlock != nil); + + __weak RACStream *stream __attribute__((unused)) = self; + return [[self map:^(RACTuple *t) { + NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t); + return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t]; + }] setNameWithFormat:@"[%@] -reduceEach:", self.name]; +} + +- (instancetype)startWith:(id)value { + return [[[self.class return:value] + concat:self] + setNameWithFormat:@"[%@] -startWith: %@", self.name, [value rac_description]]; +} + +- (instancetype)skip:(NSUInteger)skipCount { + Class class = self.class; + + return [[self bind:^{ + __block NSUInteger skipped = 0; + + return ^(id value, BOOL *stop) { + if (skipped >= skipCount) return [class return:value]; + + skipped++; + return class.empty; + }; + }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount]; +} + +- (instancetype)take:(NSUInteger)count { + Class class = self.class; + + return [[self bind:^{ + __block NSUInteger taken = 0; + + return ^ id (id value, BOOL *stop) { + RACStream *result = class.empty; + + if (taken < count) result = [class return:value]; + if (++taken >= count) *stop = YES; + + return result; + }; + }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count]; +} + ++ (instancetype)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block { + RACStream *current = nil; + + // Creates streams of successively larger tuples by combining the input + // streams one-by-one. + for (RACStream *stream in streams) { + // For the first stream, just wrap its values in a RACTuple. That way, + // if only one stream is given, the result is still a stream of tuples. + if (current == nil) { + current = [stream map:^(id x) { + return RACTuplePack(x); + }]; + + continue; + } + + current = block(current, stream); + } + + if (current == nil) return [self empty]; + + return [current map:^(RACTuple *xs) { + // Right now, each value is contained in its own tuple, sorta like: + // + // (((1), 2), 3) + // + // We need to unwrap all the layers and create a tuple out of the result. + NSMutableArray *values = [[NSMutableArray alloc] init]; + + while (xs != nil) { + [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0]; + xs = (xs.count > 1 ? xs.first : nil); + } + + return [RACTuple tupleWithObjectsFromArray:values]; + }]; +} + ++ (instancetype)zip:(id<NSFastEnumeration>)streams { + return [[self join:streams block:^(RACStream *left, RACStream *right) { + return [left zipWith:right]; + }] setNameWithFormat:@"+zip: %@", streams]; +} + ++ (instancetype)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock { + NSCParameterAssert(reduceBlock != nil); + + RACStream *result = [self zip:streams]; + + // Although we assert this condition above, older versions of this method + // supported this argument being nil. Avoid crashing Release builds of + // apps that depended on that. + if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; + + return [result setNameWithFormat:@"+zip: %@ reduce:", streams]; +} + ++ (instancetype)concat:(id<NSFastEnumeration>)streams { + RACStream *result = self.empty; + for (RACStream *stream in streams) { + result = [result concat:stream]; + } + + return [result setNameWithFormat:@"+concat: %@", streams]; +} + +- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))block { + NSCParameterAssert(block != nil); + + Class class = self.class; + + return [[self bind:^{ + __block id running = startingValue; + + return ^(id value, BOOL *stop) { + running = block(running, value); + return [class return:running]; + }; + }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, [startingValue rac_description]]; +} + +- (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + Class class = self.class; + + return [[self bind:^{ + return ^ id (id value, BOOL *stop) { + if (predicate(value)) return nil; + + return [class return:value]; + }; + }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name]; +} + +- (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + return [[self takeUntilBlock:^ BOOL (id x) { + return !predicate(x); + }] setNameWithFormat:@"[%@] -takeWhileBlock:", self.name]; +} + +- (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + Class class = self.class; + + return [[self bind:^{ + __block BOOL skipping = YES; + + return ^ id (id value, BOOL *stop) { + if (skipping) { + if (predicate(value)) { + skipping = NO; + } else { + return class.empty; + } + } + + return [class return:value]; + }; + }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; +} + +- (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + return [[self skipUntilBlock:^ BOOL (id x) { + return !predicate(x); + }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; +} + +- (instancetype)distinctUntilChanged { + Class class = self.class; + + return [[self bind:^{ + __block id lastValue = nil; + __block BOOL initial = YES; + + return ^(id x, BOOL *stop) { + if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty]; + + initial = NO; + lastValue = x; + return [class return:x]; + }; + }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name]; +} + +@end + +@implementation RACStream (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (instancetype)sequenceMany:(RACStream * (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[self flattenMap:^(id _) { + return block(); + }] setNameWithFormat:@"[%@] -sequenceMany:", self.name]; +} + +- (instancetype)scanWithStart:(id)startingValue combine:(id (^)(id running, id next))block { + return [self scanWithStart:startingValue reduce:block]; +} + +- (instancetype)mapPreviousWithStart:(id)start reduce:(id (^)(id previous, id current))combineBlock { + return [self combinePreviousWithStart:start reduce:combineBlock]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.h new file mode 100644 index 0000000..b37ddea --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.h
@@ -0,0 +1,18 @@ +// +// RACStringSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACSequence.h" + +// Private class that adapts a string to the RACSequence interface. +@interface RACStringSequence : RACSequence + +// Returns a sequence for enumerating over the given string, starting from the +// given character offset. The string will be copied to prevent mutation. ++ (RACSequence *)sequenceWithString:(NSString *)string offset:(NSUInteger)offset; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.m new file mode 100644 index 0000000..dd10f3c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.m
@@ -0,0 +1,65 @@ +// +// RACStringSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACStringSequence.h" + +@interface RACStringSequence () + +// The string being sequenced. +@property (nonatomic, copy, readonly) NSString *string; + +// The index in the string from which the sequence starts. +@property (nonatomic, assign, readonly) NSUInteger offset; + +@end + +@implementation RACStringSequence + +#pragma mark Lifecycle + ++ (RACSequence *)sequenceWithString:(NSString *)string offset:(NSUInteger)offset { + NSCParameterAssert(offset <= string.length); + + if (offset == string.length) return self.empty; + + RACStringSequence *seq = [[self alloc] init]; + seq->_string = [string copy]; + seq->_offset = offset; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + return [self.string substringWithRange:NSMakeRange(self.offset, 1)]; +} + +- (RACSequence *)tail { + RACSequence *sequence = [self.class sequenceWithString:self.string offset:self.offset + 1]; + sequence.name = self.name; + return sequence; +} + +- (NSArray *)array { + NSUInteger substringLength = self.string.length - self.offset; + NSMutableArray *array = [NSMutableArray arrayWithCapacity:substringLength]; + + [self.string enumerateSubstringsInRange:NSMakeRange(self.offset, substringLength) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + [array addObject:substring]; + }]; + + return [array copy]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, string = %@ }", self.class, self, self.name, [self.string substringFromIndex:self.offset]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.h new file mode 100644 index 0000000..30c100b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.h
@@ -0,0 +1,22 @@ +// +// RACSubject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/9/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACSubscriber.h" + +/// A subject can be thought of as a signal that you can manually control by +/// sending next, completed, and error. +/// +/// They're most helpful in bridging the non-RAC world to RAC, since they let you +/// manually control the sending of events. +@interface RACSubject : RACSignal <RACSubscriber> + +/// Returns a new subject. ++ (instancetype)subject; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.m new file mode 100644 index 0000000..3c155ab --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.m
@@ -0,0 +1,124 @@ +// +// RACSubject.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/9/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" +#import "RACEXTScope.h" +#import "RACCompoundDisposable.h" +#import "RACPassthroughSubscriber.h" + +@interface RACSubject () + +// Contains all current subscribers to the receiver. +// +// This should only be used while synchronized on `self`. +@property (nonatomic, strong, readonly) NSMutableArray *subscribers; + +// Contains all of the receiver's subscriptions to other signals. +@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; + +// Enumerates over each of the receiver's `subscribers` and invokes `block` for +// each. +- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block; + +@end + +@implementation RACSubject + +#pragma mark Lifecycle + ++ (instancetype)subject { + return [[self alloc] init]; +} + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + _disposable = [RACCompoundDisposable compoundDisposable]; + _subscribers = [[NSMutableArray alloc] initWithCapacity:1]; + + return self; +} + +- (void)dealloc { + [self.disposable dispose]; +} + +#pragma mark Subscription + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCParameterAssert(subscriber != nil); + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; + + NSMutableArray *subscribers = self.subscribers; + @synchronized (subscribers) { + [subscribers addObject:subscriber]; + } + + return [RACDisposable disposableWithBlock:^{ + @synchronized (subscribers) { + // Since newer subscribers are generally shorter-lived, search + // starting from the end of the list. + NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) { + return obj == subscriber; + }]; + + if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; + } + }]; +} + +- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block { + NSArray *subscribers; + @synchronized (self.subscribers) { + subscribers = [self.subscribers copy]; + } + + for (id<RACSubscriber> subscriber in subscribers) { + block(subscriber); + } +} + +#pragma mark RACSubscriber + +- (void)sendNext:(id)value { + [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { + [subscriber sendNext:value]; + }]; +} + +- (void)sendError:(NSError *)error { + [self.disposable dispose]; + + [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { + [subscriber sendError:error]; + }]; +} + +- (void)sendCompleted { + [self.disposable dispose]; + + [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { + [subscriber sendCompleted]; + }]; +} + +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d { + if (d.disposed) return; + [self.disposable addDisposable:d]; + + @weakify(self, d); + [d addDisposable:[RACDisposable disposableWithBlock:^{ + @strongify(self, d); + [self.disposable removeDisposable:d]; + }]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber+Private.h new file mode 100644 index 0000000..0ad81d2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber+Private.h
@@ -0,0 +1,17 @@ +// +// RACSubscriber+Private.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriber.h" + +// A simple block-based subscriber. +@interface RACSubscriber : NSObject <RACSubscriber> + +// Creates a new subscriber with the given blocks. ++ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h new file mode 100644 index 0000000..173e6dc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h
@@ -0,0 +1,51 @@ +// +// RACSubscriber.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/1/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACCompoundDisposable; + +/// Represents any object which can directly receive values from a RACSignal. +/// +/// You generally shouldn't need to implement this protocol. +[RACSignal +/// createSignal:], RACSignal's subscription methods, or RACSubject should work +/// for most uses. +/// +/// Implementors of this protocol may receive messages and values from multiple +/// threads simultaneously, and so should be thread-safe. Subscribers will also +/// be weakly referenced so implementations must allow that. +@protocol RACSubscriber <NSObject> +@required + +/// Send the next value to subscribers. +/// +/// value - The value to send. This can be `nil`. +- (void)sendNext:(id)value; + +/// Send the error to subscribers. +/// +/// error - The error to send. This can be `nil`. +/// +/// This terminates the subscription, and invalidates the subscriber (such that +/// it cannot subscribe to anything else in the future). +- (void)sendError:(NSError *)error; + +/// Send completed to subscribers. +/// +/// This terminates the subscription, and invalidates the subscriber (such that +/// it cannot subscribe to anything else in the future). +- (void)sendCompleted; + +/// Sends the subscriber a disposable that represents one of its subscriptions. +/// +/// A subscriber may receive multiple disposables if it gets subscribed to +/// multiple signals; however, any error or completed events must terminate _all_ +/// subscriptions. +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.m new file mode 100644 index 0000000..1237077 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.m
@@ -0,0 +1,108 @@ +// +// RACSubscriber.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/1/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriber.h" +#import "RACSubscriber+Private.h" +#import "RACEXTScope.h" +#import "RACCompoundDisposable.h" + +@interface RACSubscriber () + +// These callbacks should only be accessed while synchronized on self. +@property (nonatomic, copy) void (^next)(id value); +@property (nonatomic, copy) void (^error)(NSError *error); +@property (nonatomic, copy) void (^completed)(void); + +@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; + +@end + +@implementation RACSubscriber + +#pragma mark Lifecycle + ++ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { + RACSubscriber *subscriber = [[self alloc] init]; + + subscriber->_next = [next copy]; + subscriber->_error = [error copy]; + subscriber->_completed = [completed copy]; + + return subscriber; +} + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + @weakify(self); + + RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{ + @strongify(self); + if (self == nil) return; + + @synchronized (self) { + self.next = nil; + self.error = nil; + self.completed = nil; + } + }]; + + _disposable = [RACCompoundDisposable compoundDisposable]; + [_disposable addDisposable:selfDisposable]; + + return self; +} + +- (void)dealloc { + [self.disposable dispose]; +} + +#pragma mark RACSubscriber + +- (void)sendNext:(id)value { + @synchronized (self) { + void (^nextBlock)(id) = [self.next copy]; + if (nextBlock == nil) return; + + nextBlock(value); + } +} + +- (void)sendError:(NSError *)e { + @synchronized (self) { + void (^errorBlock)(NSError *) = [self.error copy]; + [self.disposable dispose]; + + if (errorBlock == nil) return; + errorBlock(e); + } +} + +- (void)sendCompleted { + @synchronized (self) { + void (^completedBlock)(void) = [self.completed copy]; + [self.disposable dispose]; + + if (completedBlock == nil) return; + completedBlock(); + } +} + +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d { + if (d.disposed) return; + [self.disposable addDisposable:d]; + + @weakify(self, d); + [d addDisposable:[RACDisposable disposableWithBlock:^{ + @strongify(self, d); + [self.disposable removeDisposable:d]; + }]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h new file mode 100644 index 0000000..a95b496 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h
@@ -0,0 +1,54 @@ +// +// RACSubscriptingAssignmentTrampoline.h +// iOSDemo +// +// Created by Josh Abernathy on 9/24/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACEXTKeyPathCoding.h" + +@class RACSignal; + +/// Assigns a signal to an object property, automatically setting the given key +/// path on every `next`. When the signal completes, the binding is automatically +/// disposed of. +/// +/// There are two different versions of this macro: +/// +/// - RAC(TARGET, KEYPATH, NILVALUE) will bind the `KEYPATH` of `TARGET` to the +/// given signal. If the signal ever sends 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`). +/// - RAC(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to +/// `nil`. +/// +/// See -[RACSignal setKeyPath:onObject:nilValue:] for more information about the +/// binding's semantics. +/// +/// Examples +/// +/// RAC(self, objectProperty) = objectSignal; +/// RAC(self, stringProperty, @"foobar") = stringSignal; +/// RAC(self, integerProperty, @42) = integerSignal; +/// +/// WARNING: Under certain conditions, use of this macro can be thread-unsafe. +/// See the documentation of -setKeyPath:onObject:nilValue:. +#define RAC(TARGET, ...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ + (RAC_(TARGET, __VA_ARGS__, nil)) \ + (RAC_(TARGET, __VA_ARGS__)) + +/// Do not use this directly. Use the RAC macro above. +#define RAC_(TARGET, KEYPATH, NILVALUE) \ + [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)] + +@interface RACSubscriptingAssignmentTrampoline : NSObject + +- (id)initWithTarget:(id)target nilValue:(id)nilValue; +- (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.m new file mode 100644 index 0000000..2ab8cd3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.m
@@ -0,0 +1,42 @@ +// +// RACSubscriptingAssignmentTrampoline.m +// iOSDemo +// +// Created by Josh Abernathy on 9/24/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriptingAssignmentTrampoline.h" +#import "RACSignal+Operations.h" + +@interface RACSubscriptingAssignmentTrampoline () + +// The object to bind to. +@property (nonatomic, strong, readonly) id target; + +// A value to use when `nil` is sent on the bound signal. +@property (nonatomic, strong, readonly) id nilValue; + +@end + +@implementation RACSubscriptingAssignmentTrampoline + +- (id)initWithTarget:(id)target nilValue:(id)nilValue { + // This is often a programmer error, but this prevents crashes if the target + // object has unexpectedly deallocated. + if (target == nil) return nil; + + self = [super init]; + if (self == nil) return nil; + + _target = target; + _nilValue = nilValue; + + return self; +} + +- (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath { + [signal setKeyPath:keyPath onObject:self.target nilValue:self.nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.h new file mode 100644 index 0000000..34f5dc5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.h
@@ -0,0 +1,15 @@ +// +// RACSubscriptionScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +// A private scheduler used only for subscriptions. See the private +// +[RACScheduler subscriptionScheduler] method for more information. +@interface RACSubscriptionScheduler : RACScheduler + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.m new file mode 100644 index 0000000..2aab9c3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.m
@@ -0,0 +1,54 @@ +// +// RACSubscriptionScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriptionScheduler.h" +#import "RACScheduler+Private.h" + +@interface RACSubscriptionScheduler () + +// A private background scheduler on which to subscribe if the +currentScheduler +// is unknown. +@property (nonatomic, strong, readonly) RACScheduler *backgroundScheduler; + +@end + +@implementation RACSubscriptionScheduler + +#pragma mark Lifecycle + +- (id)init { + self = [super initWithName:@"com.ReactiveCocoa.RACScheduler.subscriptionScheduler"]; + if (self == nil) return nil; + + _backgroundScheduler = [RACScheduler scheduler]; + + return self; +} + +#pragma mark RACScheduler + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block]; + + block(); + return nil; +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; + return [scheduler after:date schedule:block]; +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; + return [scheduler after:date repeatingEvery:interval withLeeway:leeway schedule:block]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.h new file mode 100644 index 0000000..429e595 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.h
@@ -0,0 +1,24 @@ +// +// RACTargetQueueScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/6/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACQueueScheduler.h" + +/// A scheduler that enqueues blocks on a private serial queue, targeting an +/// arbitrary GCD queue. +@interface RACTargetQueueScheduler : RACQueueScheduler + +/// Initializes the receiver with a serial queue that will target the given +/// `targetQueue`. +/// +/// name - The name of the scheduler. If nil, a default name will be used. +/// targetQueue - The queue to target. Cannot be NULL. +/// +/// Returns the initialized object. +- (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.m new file mode 100644 index 0000000..65114d3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.m
@@ -0,0 +1,37 @@ +// +// RACTargetQueueScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/6/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTargetQueueScheduler.h" +#import "RACBacktrace.h" +#import "RACQueueScheduler+Subclass.h" + +@implementation RACTargetQueueScheduler + +#pragma mark Lifecycle + +- (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue { + NSCParameterAssert(targetQueue != NULL); + + if (name == nil) { + name = [NSString stringWithFormat:@"com.ReactiveCocoa.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)]; + } + + dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL); + if (queue == NULL) return nil; + + dispatch_set_target_queue(queue, targetQueue); + + self = [super initWithName:name queue:queue]; + if (self == nil) return nil; + + dispatch_release(queue); + + return self; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.h new file mode 100644 index 0000000..a790f5b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.h
@@ -0,0 +1,42 @@ +// +// RACTestScheduler.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +/// A special kind of scheduler that steps through virtualized time. +/// +/// This scheduler class can be used in unit tests to verify asynchronous +/// behaviors without spending significant time waiting. +/// +/// This class can be used from multiple threads, but only one thread can `step` +/// through the enqueued actions at a time. Other threads will wait while the +/// scheduled blocks are being executed. +@interface RACTestScheduler : RACScheduler + +/// Initializes a new test scheduler. +- (instancetype)init; + +/// Executes the next scheduled block, if any. +/// +/// This method will block until the scheduled action has completed. +- (void)step; + +/// Executes up to the next `ticks` scheduled blocks. +/// +/// This method will block until the scheduled actions have completed. +/// +/// ticks - The number of scheduled blocks to execute. If there aren't this many +/// blocks enqueued, all scheduled blocks are executed. +- (void)step:(NSUInteger)ticks; + +/// Executes all of the scheduled blocks on the receiver. +/// +/// This method will block until the scheduled actions have completed. +- (void)stepAll; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.m new file mode 100644 index 0000000..374fbe2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.m
@@ -0,0 +1,223 @@ +// +// RACTestScheduler.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestScheduler.h" +#import "RACEXTScope.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACScheduler+Private.h" + +@interface RACTestSchedulerAction : NSObject + +// The date at which the action should be executed. +// +// This absolute time will not actually be honored. This date is only used for +// comparison, to determine which block should be run _next_. +@property (nonatomic, copy, readonly) NSDate *date; + +// The scheduled block. +@property (nonatomic, copy, readonly) void (^block)(void); + +// A disposable for this action. +// +// When disposed, the action should not start executing if it hasn't already. +@property (nonatomic, strong, readonly) RACDisposable *disposable; + +// Initializes a new scheduler action. +- (id)initWithDate:(NSDate *)date block:(void (^)(void))block; + +@end + +static CFComparisonResult RACCompareScheduledActions(const void *ptr1, const void *ptr2, void *info) { + RACTestSchedulerAction *action1 = (__bridge id)ptr1; + RACTestSchedulerAction *action2 = (__bridge id)ptr2; + return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL); +} + +static const void *RACRetainScheduledAction(CFAllocatorRef allocator, const void *ptr) { + return CFRetain(ptr); +} + +static void RACReleaseScheduledAction(CFAllocatorRef allocator, const void *ptr) { + CFRelease(ptr); +} + +@interface RACTestScheduler () + +// All of the RACTestSchedulerActions that have been enqueued and not yet +// executed. +// +// The minimum value in the heap represents the action to execute next. +// +// This property should only be used while synchronized on self. +@property (nonatomic, assign, readonly) CFBinaryHeapRef scheduledActions; + +// The number of blocks that have been directly enqueued with -schedule: so +// far. +// +// This is used to ensure unique dates when two blocks are enqueued +// simultaneously. +// +// This property should only be used while synchronized on self. +@property (nonatomic, assign) NSUInteger numberOfDirectlyScheduledBlocks; + +@end + +@implementation RACTestScheduler + +#pragma mark Lifecycle + +- (instancetype)init { + self = [super initWithName:@"com.github.ReactiveCocoa.RACTestScheduler"]; + if (self == nil) return nil; + + CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){ + .version = 0, + .retain = &RACRetainScheduledAction, + .release = &RACReleaseScheduledAction, + .copyDescription = &CFCopyDescription, + .compare = &RACCompareScheduledActions + }; + + _scheduledActions = CFBinaryHeapCreate(NULL, 0, &callbacks, NULL); + return self; +} + +- (void)dealloc { + [self stepAll]; + + if (_scheduledActions != NULL) { + CFRelease(_scheduledActions); + _scheduledActions = NULL; + } +} + +#pragma mark Execution + +- (void)step { + [self step:1]; +} + +- (void)step:(NSUInteger)ticks { + @synchronized (self) { + for (NSUInteger i = 0; i < ticks; i++) { + const void *actionPtr = NULL; + if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break; + + RACTestSchedulerAction *action = (__bridge id)actionPtr; + CFBinaryHeapRemoveMinimumValue(self.scheduledActions); + + if (action.disposable.disposed) continue; + + RACScheduler *previousScheduler = RACScheduler.currentScheduler; + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; + + action.block(); + + if (previousScheduler != nil) { + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; + } else { + [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; + } + } + } +} + +- (void)stepAll { + [self step:NSUIntegerMax]; +} + +#pragma mark RACScheduler + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCParameterAssert(block != nil); + + @synchronized (self) { + NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks]; + self.numberOfDirectlyScheduledBlocks++; + + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + + return action.disposable; + } +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); + + @synchronized (self) { + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + + return action.disposable; + } +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); + NSCParameterAssert(interval >= 0); + NSCParameterAssert(leeway >= 0); + + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + @weakify(self); + @synchronized (self) { + __block RACDisposable *thisDisposable = nil; + + void (^reschedulingBlock)(void) = ^{ + @strongify(self); + + [compoundDisposable removeDisposable:thisDisposable]; + + // Schedule the next interval. + RACDisposable *schedulingDisposable = [self after:[date dateByAddingTimeInterval:interval] repeatingEvery:interval withLeeway:leeway schedule:block]; + [compoundDisposable addDisposable:schedulingDisposable]; + + block(); + }; + + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:reschedulingBlock]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + + thisDisposable = action.disposable; + [compoundDisposable addDisposable:thisDisposable]; + } + + return compoundDisposable; +} + +@end + +@implementation RACTestSchedulerAction + +#pragma mark Lifecycle + +- (id)initWithDate:(NSDate *)date block:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); + + self = [super init]; + if (self == nil) return nil; + + _date = [date copy]; + _block = [block copy]; + _disposable = [[RACDisposable alloc] init]; + + return self; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ date: %@ }", self.class, self, self.date]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h new file mode 100644 index 0000000..c364630 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h
@@ -0,0 +1,159 @@ +// +// RACTuple.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/12/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACmetamacros.h" + +@class RACSequence; + +/// Creates a new tuple with the given values. At least one value must be given. +/// Values can be nil. +#define RACTuplePack(...) \ + RACTuplePack_(__VA_ARGS__) + +/// Declares new object variables and unpacks a RACTuple into them. +/// +/// This macro should be used on the left side of an assignment, with the +/// tuple on the right side. Nothing else should appear on the same line, and the +/// macro should not be the only statement in a conditional or loop body. +/// +/// If the tuple has more values than there are variables listed, the excess +/// values are ignored. +/// +/// If the tuple has fewer values than there are variables listed, the excess +/// variables are initialized to nil. +/// +/// Examples +/// +/// RACTupleUnpack(NSString *string, NSNumber *num) = [RACTuple tupleWithObjects:@"foo", @5, nil]; +/// NSLog(@"string: %@", string); +/// NSLog(@"num: %@", num); +/// +/// /* The above is equivalent to: */ +/// RACTuple *t = [RACTuple tupleWithObjects:@"foo", @5, nil]; +/// NSString *string = t[0]; +/// NSNumber *num = t[1]; +/// NSLog(@"string: %@", string); +/// NSLog(@"num: %@", num); +#define RACTupleUnpack(...) \ + RACTupleUnpack_(__VA_ARGS__) + +/// A sentinel object that represents nils in the tuple. +/// +/// It should never be necessary to create a tuple nil yourself. Just use +/// +tupleNil. +@interface RACTupleNil : NSObject <NSCopying, NSCoding> +/// A singleton instance. ++ (RACTupleNil *)tupleNil; +@end + + +/// A tuple is an ordered collection of objects. It may contain nils, represented +/// by RACTupleNil. +@interface RACTuple : NSObject <NSCoding, NSCopying, NSFastEnumeration> + +@property (nonatomic, readonly) NSUInteger count; + +/// These properties all return the object at that index or nil if the number of +/// objects is less than the index. +@property (nonatomic, readonly) id first; +@property (nonatomic, readonly) id second; +@property (nonatomic, readonly) id third; +@property (nonatomic, readonly) id fourth; +@property (nonatomic, readonly) id fifth; +@property (nonatomic, readonly) id last; + +/// Creates a new tuple out of the array. Does not convert nulls to nils. ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array; + +/// Creates a new tuple out of the array. If `convert` is YES, it also converts +/// every NSNull to RACTupleNil. ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert; + +/// Creates a new tuple with the given objects. Use RACTupleNil to represent +/// nils. ++ (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION; + +/// Returns the object at `index` or nil if the object is a RACTupleNil. Unlike +/// NSArray and friends, it's perfectly fine to ask for the object at an index +/// past the tuple's count - 1. It will simply return nil. +- (id)objectAtIndex:(NSUInteger)index; + +/// Returns an array of all the objects. RACTupleNils are converted to NSNulls. +- (NSArray *)allObjects; + +/// Appends `obj` to the receiver. +/// +/// obj - The object to add to the tuple. This argument may be nil. +/// +/// Returns a new tuple. +- (instancetype)tupleByAddingObject:(id)obj; + +@end + +@interface RACTuple (RACSequenceAdditions) + +/// Returns a sequence of all the objects. RACTupleNils are converted to NSNulls. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end + +@interface RACTuple (ObjectSubscripting) +/// Returns the object at that index or nil if the number of objects is less +/// than the index. +- (id)objectAtIndexedSubscript:(NSUInteger)idx; +@end + +/// This and everything below is for internal use only. +/// +/// See RACTuplePack() and RACTupleUnpack() instead. +#define RACTuplePack_(...) \ + ([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]]) + +#define RACTuplePack_object_or_ractuplenil(INDEX, ARG) \ + (ARG) ?: RACTupleNil.tupleNil, + +#define RACTupleUnpack_(...) \ + metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \ + \ + int RACTupleUnpack_state = 0; \ + \ + RACTupleUnpack_after: \ + ; \ + metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \ + if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \ + \ + while (RACTupleUnpack_state != 2) \ + if (RACTupleUnpack_state == 1) { \ + goto RACTupleUnpack_after; \ + } else \ + for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \ + [RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ] + +#define RACTupleUnpack_state metamacro_concat(RACTupleUnpack_state, __LINE__) +#define RACTupleUnpack_after metamacro_concat(RACTupleUnpack_after, __LINE__) +#define RACTupleUnpack_loop metamacro_concat(RACTupleUnpack_loop, __LINE__) + +#define RACTupleUnpack_decl_name(INDEX) \ + metamacro_concat(metamacro_concat(RACTupleUnpack, __LINE__), metamacro_concat(_var, INDEX)) + +#define RACTupleUnpack_decl(INDEX, ARG) \ + __strong id RACTupleUnpack_decl_name(INDEX); + +#define RACTupleUnpack_assign(INDEX, ARG) \ + __strong ARG = RACTupleUnpack_decl_name(INDEX); + +#define RACTupleUnpack_value(INDEX, ARG) \ + [NSValue valueWithPointer:&RACTupleUnpack_decl_name(INDEX)], + +@interface RACTupleUnpackingTrampoline : NSObject + ++ (instancetype)trampoline; +- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m new file mode 100644 index 0000000..c3e979c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m
@@ -0,0 +1,252 @@ +// +// RACTuple.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/12/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACTuple.h" +#import "RACEXTKeyPathCoding.h" +#import "RACTupleSequence.h" + +@implementation RACTupleNil + ++ (RACTupleNil *)tupleNil { + static dispatch_once_t onceToken; + static RACTupleNil *tupleNil = nil; + dispatch_once(&onceToken, ^{ + tupleNil = [[self alloc] init]; + }); + + return tupleNil; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder { + // Always return the singleton. + return self.class.tupleNil; +} + +- (void)encodeWithCoder:(NSCoder *)coder { +} + +@end + + +@interface RACTuple () +@property (nonatomic, strong) NSArray *backingArray; +@end + + +@implementation RACTuple + +- (instancetype)init { + self = [super init]; + if (self == nil) return nil; + + self.backingArray = [NSArray array]; + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects]; +} + +- (BOOL)isEqual:(RACTuple *)object { + if (object == self) return YES; + if (![object isKindOfClass:self.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; +} + +- (NSUInteger)hash { + return self.backingArray.hash; +} + + +#pragma mark NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { + return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len]; +} + + +#pragma mark NSCopying + +- (instancetype)copyWithZone:(NSZone *)zone { + // we're immutable, bitches! + return self; +} + + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder { + self = [self init]; + if (self == nil) return nil; + + self.backingArray = [coder decodeObjectForKey:@keypath(self.backingArray)]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + if (self.backingArray != nil) [coder encodeObject:self.backingArray forKey:@keypath(self.backingArray)]; +} + + +#pragma mark API + ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array { + return [self tupleWithObjectsFromArray:array convertNullsToNils:NO]; +} + ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert { + RACTuple *tuple = [[self alloc] init]; + + if (convert) { + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + for (id object in array) { + [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)]; + } + + tuple.backingArray = newArray; + } else { + tuple.backingArray = [array copy]; + } + + return tuple; +} + ++ (instancetype)tupleWithObjects:(id)object, ... { + RACTuple *tuple = [[self alloc] init]; + + va_list args; + va_start(args, object); + + NSUInteger count = 0; + for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { + ++count; + } + + va_end(args); + + if (count == 0) { + tuple.backingArray = @[]; + return tuple; + } + + NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count]; + + va_start(args, object); + for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { + [objects addObject:currentObject]; + } + + va_end(args); + + tuple.backingArray = objects; + return tuple; +} + +- (id)objectAtIndex:(NSUInteger)index { + if (index >= self.count) return nil; + + id object = [self.backingArray objectAtIndex:index]; + return (object == RACTupleNil.tupleNil ? nil : object); +} + +- (NSArray *)allObjects { + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count]; + for (id object in self.backingArray) { + [newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)]; + } + + return newArray; +} + +- (instancetype)tupleByAddingObject:(id)obj { + NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; + return [self.class tupleWithObjectsFromArray:newArray convertNullsToNils:NO]; +} + +- (NSUInteger)count { + return self.backingArray.count; +} + +- (id)first { + return [self objectAtIndex:0]; +} + +- (id)second { + return [self objectAtIndex:1]; +} + +- (id)third { + return [self objectAtIndex:2]; +} + +- (id)fourth { + return [self objectAtIndex:3]; +} + +- (id)fifth { + return [self objectAtIndex:4]; +} + +- (id)last { + return [self objectAtIndex:self.count - 1]; +} + +@end + + +@implementation RACTuple (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0]; +} + +@end + +@implementation RACTuple (ObjectSubscripting) + +- (id)objectAtIndexedSubscript:(NSUInteger)idx { + return [self objectAtIndex:idx]; +} + +@end + + +@implementation RACTupleUnpackingTrampoline + +#pragma mark Lifecycle + ++ (instancetype)trampoline { + static dispatch_once_t onceToken; + static id trampoline = nil; + dispatch_once(&onceToken, ^{ + trampoline = [[self alloc] init]; + }); + + return trampoline; +} + +- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables { + NSCParameterAssert(variables != nil); + + [variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) { + __strong id *ptr = (__strong id *)value.pointerValue; + *ptr = tuple[index]; + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.h new file mode 100644 index 0000000..f6cd021 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.h
@@ -0,0 +1,18 @@ +// +// RACTupleSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSequence.h" + +// Private class that adapts a RACTuple to the RACSequence interface. +@interface RACTupleSequence : RACSequence + +// Returns a sequence for enumerating over the given backing array (from a +// RACTuple), starting from the given offset. ++ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.m new file mode 100644 index 0000000..ba268f5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.m
@@ -0,0 +1,68 @@ +// +// RACTupleSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTupleSequence.h" +#import "RACTuple.h" + +@interface RACTupleSequence () + +// The array being sequenced, as taken from RACTuple.backingArray. +@property (nonatomic, strong, readonly) NSArray *tupleBackingArray; + +// The index in the array from which the sequence starts. +@property (nonatomic, assign, readonly) NSUInteger offset; + +@end + +@implementation RACTupleSequence + +#pragma mark Lifecycle + ++ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset { + NSCParameterAssert(offset <= backingArray.count); + + if (offset == backingArray.count) return self.empty; + + RACTupleSequence *seq = [[self alloc] init]; + seq->_tupleBackingArray = backingArray; + seq->_offset = offset; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + id object = [self.tupleBackingArray objectAtIndex:self.offset]; + return (object == RACTupleNil.tupleNil ? NSNull.null : object); +} + +- (RACSequence *)tail { + RACSequence *sequence = [self.class sequenceWithTupleBackingArray:self.tupleBackingArray offset:self.offset + 1]; + sequence.name = self.name; + return sequence; +} + +- (NSArray *)array { + NSRange range = NSMakeRange(self.offset, self.tupleBackingArray.count - self.offset); + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:range.length]; + + [self.tupleBackingArray enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:range] options:0 usingBlock:^(id object, NSUInteger index, BOOL *stop) { + id mappedObject = (object == RACTupleNil.tupleNil ? NSNull.null : object); + [array addObject:mappedObject]; + }]; + + return array; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, tuple = %@ }", self.class, self, self.name, self.tupleBackingArray]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.h new file mode 100644 index 0000000..86a514b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.h
@@ -0,0 +1,14 @@ +// +// RACUnarySequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSequence.h" + +// Private class representing a sequence of exactly one value. +@interface RACUnarySequence : RACSequence + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.m new file mode 100644 index 0000000..fefca84 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.m
@@ -0,0 +1,81 @@ +// +// RACUnarySequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACUnarySequence.h" +#import "RACEXTKeyPathCoding.h" +#import "NSObject+RACDescription.h" + +@interface RACUnarySequence () + +// The single value stored in this sequence. +@property (nonatomic, strong, readwrite) id head; + +@end + +@implementation RACUnarySequence + +#pragma mark Properties + +@synthesize head = _head; + +#pragma mark Lifecycle + ++ (instancetype)return:(id)value { + RACUnarySequence *sequence = [[self alloc] init]; + sequence.head = value; + return [sequence setNameWithFormat:@"+return: %@", [value rac_description]]; +} + +#pragma mark RACSequence + +- (RACSequence *)tail { + return nil; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(void))block { + RACStreamBindBlock bindBlock = block(); + BOOL stop = NO; + + RACSequence *result = (id)[bindBlock(self.head, &stop) setNameWithFormat:@"[%@] -bind:", self.name]; + return result ?: self.class.empty; +} + +#pragma mark NSCoding + +- (Class)classForCoder { + // Unary sequences should be encoded as themselves, not array sequences. + return self.class; +} + +- (id)initWithCoder:(NSCoder *)coder { + id value = [coder decodeObjectForKey:@keypath(self.head)]; + return [self.class return:value]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + if (self.head != nil) [coder encodeObject:self.head forKey:@keypath(self.head)]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@ }", self.class, self, self.name, self.head]; +} + +- (NSUInteger)hash { + return [self.head hash]; +} + +- (BOOL)isEqual:(RACUnarySequence *)seq { + if (self == seq) return YES; + if (![seq isKindOfClass:RACUnarySequence.class]) return NO; + + return self.head == seq.head || [(NSObject *)self.head isEqual:seq.head]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.h b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.h new file mode 100644 index 0000000..7713e1d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.h
@@ -0,0 +1,20 @@ +// +// RACUnit.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/27/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + + +/// A unit represents an empty value. +/// +/// It should never be necessary to create a unit yourself. Just use +defaultUnit. +@interface RACUnit : NSObject + +/// A singleton instance. ++ (RACUnit *)defaultUnit; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.m b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.m new file mode 100644 index 0000000..76b8425 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.m
@@ -0,0 +1,27 @@ +// +// RACUnit.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/27/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACUnit.h" + + +@implementation RACUnit + + +#pragma mark API + ++ (RACUnit *)defaultUnit { + static dispatch_once_t onceToken; + static RACUnit *defaultUnit = nil; + dispatch_once(&onceToken, ^{ + defaultUnit = [[self alloc] init]; + }); + + return defaultUnit; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.h b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.h new file mode 100644 index 0000000..740b861 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.h
@@ -0,0 +1,16 @@ +// +// RACValueTransformer.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/6/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +// A private block based transformer. +@interface RACValueTransformer : NSValueTransformer + ++ (instancetype)transformerWithBlock:(id (^)(id value))block; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.m b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.m new file mode 100644 index 0000000..ccec07d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.m
@@ -0,0 +1,42 @@ +// +// RACValueTransformer.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/6/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACValueTransformer.h" + +@interface RACValueTransformer () +@property (nonatomic, copy) id (^transformBlock)(id value); +@end + + +@implementation RACValueTransformer + + +#pragma mark NSValueTransformer + ++ (BOOL)allowsReverseTransformation { + return NO; +} + +- (id)transformedValue:(id)value { + return self.transformBlock(value); +} + + +#pragma mark API + +@synthesize transformBlock; + ++ (instancetype)transformerWithBlock:(id (^)(id value))block { + NSCParameterAssert(block != NULL); + + RACValueTransformer *transformer = [[self alloc] init]; + transformer.transformBlock = block; + return transformer; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h new file mode 100644 index 0000000..7c1441d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h
@@ -0,0 +1,74 @@ +// +// ReactiveCocoa.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/5/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACEXTKeyPathCoding.h" +#import "NSArray+RACSequenceAdditions.h" +#import "NSData+RACSupport.h" +#import "NSDictionary+RACSequenceAdditions.h" +#import "NSEnumerator+RACSequenceAdditions.h" +#import "NSFileHandle+RACSupport.h" +#import "NSNotificationCenter+RACSupport.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACLifting.h" +#import "NSObject+RACPropertySubscribing.h" +#import "NSObject+RACSelectorSignal.h" +#import "NSOrderedSet+RACSequenceAdditions.h" +#import "NSSet+RACSequenceAdditions.h" +#import "NSString+RACSequenceAdditions.h" +#import "NSString+RACSupport.h" +#import "NSIndexSet+RACSequenceAdditions.h" +#import "NSURLConnection+RACSupport.h" +#import "NSUserDefaults+RACSupport.h" +#import "RACBehaviorSubject.h" +#import "RACChannel.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACEvent.h" +#import "RACGroupedSignal.h" +#import "RACKVOChannel.h" +#import "RACMulticastConnection.h" +#import "RACQueueScheduler.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" +#import "RACScopedDisposable.h" +#import "RACSequence.h" +#import "RACSerialDisposable.h" +#import "RACSignal+Operations.h" +#import "RACSignal.h" +#import "RACStream.h" +#import "RACSubject.h" +#import "RACSubscriber.h" +#import "RACSubscriptingAssignmentTrampoline.h" +#import "RACTargetQueueScheduler.h" +#import "RACTestScheduler.h" +#import "RACTuple.h" +#import "RACUnit.h" + +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED + #import "UIActionSheet+RACSignalSupport.h" + #import "UIAlertView+RACSignalSupport.h" + #import "UIBarButtonItem+RACCommandSupport.h" + #import "UIButton+RACCommandSupport.h" + #import "UICollectionReusableView+RACSignalSupport.h" + #import "UIControl+RACSignalSupport.h" + #import "UIDatePicker+RACSignalSupport.h" + #import "UIGestureRecognizer+RACSignalSupport.h" + #import "UISegmentedControl+RACSignalSupport.h" + #import "UISlider+RACSignalSupport.h" + #import "UIStepper+RACSignalSupport.h" + #import "UISwitch+RACSignalSupport.h" + #import "UITableViewCell+RACSignalSupport.h" + #import "UITextField+RACSignalSupport.h" + #import "UITextView+RACSignalSupport.h" +#elif TARGET_OS_MAC + #import "NSControl+RACCommandSupport.h" + #import "NSControl+RACTextSignalSupport.h" + #import "NSObject+RACAppKitBindings.h" + #import "NSText+RACSignalSupport.h" +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.h new file mode 100644 index 0000000..3d667e9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.h
@@ -0,0 +1,32 @@ +// +// UIActionSheet+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Dave Lee on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACDelegateProxy; +@class RACSignal; + +@interface UIActionSheet (RACSignalSupport) + +/// A delegate proxy which will be set as the receiver's delegate when any of the +/// methods in this category are used. +@property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; + +/// Creates a signal for button clicks on the receiver. +/// +/// When this method is invoked, the `rac_delegateProxy` will become the +/// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy +/// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't +/// know how to handle. Setting the receiver's `delegate` afterward is +/// considered undefined behavior. +/// +/// Returns a signal which will send the index of the specific button clicked. +/// The signal will complete when the receiver is deallocated. +- (RACSignal *)rac_buttonClickedSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.m new file mode 100644 index 0000000..6198797 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.m
@@ -0,0 +1,50 @@ +// +// UIActionSheet+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Dave Lee on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIActionSheet+RACSignalSupport.h" +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "NSObject+RACDeallocating.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" +#import <objc/runtime.h> + +@implementation UIActionSheet (RACSignalSupport) + +static void RACUseDelegateProxy(UIActionSheet *self) { + if (self.delegate == self.rac_delegateProxy) return; + + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; +} + +- (RACDelegateProxy *)rac_delegateProxy { + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIActionSheetDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + return proxy; +} + +- (RACSignal *)rac_buttonClickedSignal { + RACSignal *signal = [[[[self.rac_delegateProxy + signalForSelector:@selector(actionSheet:clickedButtonAtIndex:)] + reduceEach:^(UIActionSheet *actionSheet, NSNumber *buttonIndex) { + return buttonIndex; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_buttonClickedSignal", [self rac_description]]; + + RACUseDelegateProxy(self); + + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.h new file mode 100644 index 0000000..4586460 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.h
@@ -0,0 +1,32 @@ +// +// UIAlertView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Henrik Hodne on 6/16/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACDelegateProxy; +@class RACSignal; + +@interface UIAlertView (RACSignalSupport) + +/// A delegate proxy which will be set as the receiver's delegate when any of the +/// methods in this category are used. +@property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; + +/// Creates a signal for button clicks on the receiver. +/// +/// When this method is invoked, the `rac_delegateProxy` will become the +/// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy +/// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't +/// know how to handle. Setting the receiver's `delegate` afterward is considered +/// undefined behavior. +/// +/// Returns a signal which will send the index of the specific button clicked. +/// The signal will complete itself when the receiver is deallocated. +- (RACSignal *)rac_buttonClickedSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.m new file mode 100644 index 0000000..bf15b27 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.m
@@ -0,0 +1,50 @@ +// +// UIAlertView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Henrik Hodne on 6/16/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIAlertView+RACSignalSupport.h" +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "RACTuple.h" +#import <objc/runtime.h> + +@implementation UIAlertView (RACSignalSupport) + +static void RACUseDelegateProxy(UIAlertView *self) { + if (self.delegate == self.rac_delegateProxy) return; + + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; +} + +- (RACDelegateProxy *)rac_delegateProxy { + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIAlertViewDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + return proxy; +} + +- (RACSignal *)rac_buttonClickedSignal { + RACSignal *signal = [[[[self.rac_delegateProxy + signalForSelector:@selector(alertView:clickedButtonAtIndex:)] + reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { + return buttonIndex; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_buttonClickedSignal", [self rac_description]]; + + RACUseDelegateProxy(self); + + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h new file mode 100644 index 0000000..c04648a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h
@@ -0,0 +1,22 @@ +// +// UIBarButtonItem+RACCommandSupport.h +// ReactiveCocoa +// +// Created by Kyle LeNeau on 3/27/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACCommand; + +@interface UIBarButtonItem (RACCommandSupport) + +/// Sets the control's command. When the control is clicked, the command is +/// executed with the sender of the event. The control's enabledness is bound +/// to the command's `canExecute`. +/// +/// Note: this will reset the control's target and action. +@property (nonatomic, strong) RACCommand *rac_command; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m new file mode 100644 index 0000000..7370af2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
@@ -0,0 +1,55 @@ +// +// UIBarButtonItem+RACCommandSupport.m +// ReactiveCocoa +// +// Created by Kyle LeNeau on 3/27/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIBarButtonItem+RACCommandSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCommand.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import <objc/runtime.h> + +static void *UIControlRACCommandKey = &UIControlRACCommandKey; +static void *UIControlEnabledDisposableKey = &UIControlEnabledDisposableKey; + +@implementation UIBarButtonItem (RACCommandSupport) + +- (RACCommand *)rac_command { + return objc_getAssociatedObject(self, UIControlRACCommandKey); +} + +- (void)setRac_command:(RACCommand *)command { + objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Check for stored signal in order to remove it and add a new one + RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey); + [disposable dispose]; + + if (command == nil) return; + + disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; + objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [self rac_hijackActionAndTargetIfNeeded]; +} + +- (void)rac_hijackActionAndTargetIfNeeded { + SEL hijackSelector = @selector(rac_commandPerformAction:); + if (self.target == self && self.action == hijackSelector) return; + + if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action."); + + self.target = self; + self.action = hijackSelector; +} + +- (void)rac_commandPerformAction:(id)sender { + [self.rac_command execute:sender]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.h new file mode 100644 index 0000000..642ba8e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.h
@@ -0,0 +1,20 @@ +// +// UIButton+RACCommandSupport.h +// ReactiveCocoa +// +// Created by Ash Furrow on 2013-06-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACCommand; + +@interface UIButton (RACCommandSupport) + +/// Sets the button's command. When the button is clicked, the command is +/// executed with the sender of the event. The button's enabledness is bound +/// to the command's `canExecute`. +@property (nonatomic, strong) RACCommand *rac_command; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.m new file mode 100644 index 0000000..38fd470 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.m
@@ -0,0 +1,57 @@ +// +// UIButton+RACCommandSupport.m +// ReactiveCocoa +// +// Created by Ash Furrow on 2013-06-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIButton+RACCommandSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCommand.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import <objc/runtime.h> + +static void *UIButtonRACCommandKey = &UIButtonRACCommandKey; +static void *UIButtonEnabledDisposableKey = &UIButtonEnabledDisposableKey; + +@implementation UIButton (RACCommandSupport) + +- (RACCommand *)rac_command { + return objc_getAssociatedObject(self, UIButtonRACCommandKey); +} + +- (void)setRac_command:(RACCommand *)command { + objc_setAssociatedObject(self, UIButtonRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Check for stored signal in order to remove it and add a new one + RACDisposable *disposable = objc_getAssociatedObject(self, UIButtonEnabledDisposableKey); + [disposable dispose]; + + if (command == nil) return; + + disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; + objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [self rac_hijackActionAndTargetIfNeeded]; +} + +- (void)rac_hijackActionAndTargetIfNeeded { + SEL hijackSelector = @selector(rac_commandPerformAction:); + + for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) { + if (hijackSelector == NSSelectorFromString(selector)) { + return; + } + } + + [self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside]; +} + +- (void)rac_commandPerformAction:(id)sender { + [self.rac_command execute:sender]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.h new file mode 100644 index 0000000..96b3dfe --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.h
@@ -0,0 +1,29 @@ +// +// UICollectionReusableView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Kent Wong on 2013-10-04. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +// This category is only applicable to iOS >= 6.0. +@interface UICollectionReusableView (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 *rac_prepareForReuseSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.m new file mode 100644 index 0000000..7e8680c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.m
@@ -0,0 +1,31 @@ +// +// UICollectionReusableView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Kent Wong on 2013-10-04. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UICollectionReusableView+RACSignalSupport.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACSignal+Operations.h" +#import "RACUnit.h" +#import <objc/runtime.h> + +@implementation UICollectionReusableView (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", self.rac_description]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.h new file mode 100644 index 0000000..2de86cf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.h
@@ -0,0 +1,19 @@ +// +// UIControl+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +@interface UIControl (RACSignalSupport) + +/// Creates and returns a signal that sends the sender of the control event +/// whenever one of the control events is triggered. +- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.m new file mode 100644 index 0000000..3cc503e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.m
@@ -0,0 +1,41 @@ +// +// UIControl+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "UIControl+RACSignalSupport.h" +#import "RACEXTScope.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "RACSubscriber+Private.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" + +@implementation UIControl (RACSignalSupport) + +- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents { + @weakify(self); + + return [[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + @strongify(self); + + [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subscriber sendCompleted]; + }]]; + + return [RACDisposable disposableWithBlock:^{ + @strongify(self); + [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; + }]; + }] + setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", [self rac_description], (unsigned long)controlEvents]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.h b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.h new file mode 100644 index 0000000..7f778a4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.h
@@ -0,0 +1,29 @@ +// +// UIControl+RACSignalSupportPrivate.h +// ReactiveCocoa +// +// Created by Uri Baghin on 06/08/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UIControl (RACSignalSupportPrivate) + +// Adds a RACChannel-based interface to the receiver for the given +// UIControlEvents and exposes it. +// +// controlEvents - A mask of UIControlEvents on which to send new values. +// key - The key whose value should be read and set when a control +// event fires and when a value is sent to the +// RACChannelTerminal respectively. +// nilValue - The value to be assigned to the key when `nil` is sent to the +// RACChannelTerminal. +// +// Returns a RACChannelTerminal which will send future values from the receiver, +// and update the receiver when values are sent to the terminal. +- (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.m b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.m new file mode 100644 index 0000000..df5c505 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.m
@@ -0,0 +1,50 @@ +// +// UIControl+RACSignalSupportPrivate.m +// ReactiveCocoa +// +// Created by Uri Baghin on 06/08/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIControl+RACSignalSupportPrivate.h" +#import "RACEXTScope.h" +#import "NSInvocation+RACTypeParsing.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACLifting.h" +#import "RACChannel.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import "UIControl+RACSignalSupport.h" + +@implementation UIControl (RACSignalSupportPrivate) + +- (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue { + NSCParameterAssert(key.length > 0); + key = [key copy]; + RACChannel *channel = [[RACChannel alloc] init]; + + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [channel.followingTerminal sendCompleted]; + }]]; + + RACSignal *eventSignal = [[[self + rac_signalForControlEvents:controlEvents] + mapReplace:key] + takeUntil:[[channel.followingTerminal + ignoreValues] + catchTo:RACSignal.empty]]; + [[self + rac_liftSelector:@selector(valueForKey:) withSignals:eventSignal, nil] + subscribe:channel.followingTerminal]; + + RACSignal *valuesSignal = [channel.followingTerminal + map:^(id value) { + return value ?: nilValue; + }]; + [self rac_liftSelector:@selector(setValue:forKey:) withSignals:valuesSignal, [RACSignal return:key], nil]; + + return channel.leadingTerminal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.h new file mode 100644 index 0000000..e620dfc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UIDatePicker+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UIDatePicker (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The date to set when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's date whenever the +/// UIControlEventValueChanged control event is fired, and sets the date to the +/// values it receives. +- (RACChannelTerminal *)rac_newDateChannelWithNilValue:(NSDate *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.m new file mode 100644 index 0000000..1c11066 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UIDatePicker+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIDatePicker+RACSignalSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UIDatePicker (RACSignalSupport) + +- (RACChannelTerminal *)rac_newDateChannelWithNilValue:(NSDate *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.date) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.h new file mode 100644 index 0000000..ee774fe --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.h
@@ -0,0 +1,18 @@ +// +// UIGestureRecognizer+RACSignalSupport.h +// Talks +// +// Created by Josh Vera on 5/5/13. +// Copyright (c) 2013 GitHub. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +@interface UIGestureRecognizer (RACSignalSupport) + +/// Returns a signal that sends the receiver when its gesture occurs. +- (RACSignal *)rac_gestureSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.m new file mode 100644 index 0000000..510d3ba --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.m
@@ -0,0 +1,40 @@ +// +// UIGestureRecognizer+RACSignalSupport.m +// Talks +// +// Created by Josh Vera on 5/5/13. +// Copyright (c) 2013 GitHub. All rights reserved. +// + +#import "UIGestureRecognizer+RACSignalSupport.h" +#import "RACEXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "RACSubscriber.h" +#import "NSObject+RACDescription.h" + +@implementation UIGestureRecognizer (RACSignalSupport) + +- (RACSignal *)rac_gestureSignal { + @weakify(self); + + return [[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + @strongify(self); + + [self addTarget:subscriber action:@selector(sendNext:)]; + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subscriber sendCompleted]; + }]]; + + return [RACDisposable disposableWithBlock:^{ + @strongify(self); + [self removeTarget:subscriber action:@selector(sendNext:)]; + }]; + }] + setNameWithFormat:@"%@ -rac_gestureSignal", [self rac_description]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.h new file mode 100644 index 0000000..0bd1bf1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.h
@@ -0,0 +1,22 @@ +// +// UIRefreshControl+RACCommandSupport.h +// ReactiveCocoa +// +// Created by Dave Lee on 2013-10-17. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACCommand; + +@interface UIRefreshControl (RACCommandSupport) + +/// Manipulate the RACCommand property associated with this refresh control. +/// +/// When this refresh control is activated by the user, the command will be +/// executed. Upon completion or error of the execution signal, -endRefreshing +/// will be invoked. +@property (nonatomic, strong) RACCommand *rac_command; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.m new file mode 100644 index 0000000..2b7d7a4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.m
@@ -0,0 +1,59 @@ +// +// UIRefreshControl+RACCommandSupport.m +// ReactiveCocoa +// +// Created by Dave Lee on 2013-10-17. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIRefreshControl+RACCommandSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACDisposable.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "UIControl+RACSignalSupport.h" +#import <objc/runtime.h> + +static void *UIRefreshControlRACCommandKey = &UIRefreshControlRACCommandKey; +static void *UIRefreshControlDisposableKey = &UIRefreshControlDisposableKey; + +@implementation UIRefreshControl (RACCommandSupport) + +- (RACCommand *)rac_command { + return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey); +} + +- (void)setRac_command:(RACCommand *)command { + objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Dispose of any active command associations. + [objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose]; + + if (command == nil) return; + + // Like RAC(self, enabled) = command.enabled; but with access to disposable. + RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; + + RACDisposable *executionDisposable = [[[[self + rac_signalForControlEvents:UIControlEventValueChanged] + map:^(UIRefreshControl *x) { + return [[[command + execute:x] + catchTo:[RACSignal empty]] + then:^{ + return [RACSignal return:x]; + }]; + }] + concat] + subscribeNext:^(UIRefreshControl *x) { + [x endRefreshing]; + }]; + + RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]]; + objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.h new file mode 100644 index 0000000..2d3c3e7 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UISegmentedControl+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UISegmentedControl (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The segment to select when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's currently selected +/// segment's index whenever the UIControlEventValueChanged control event is +/// fired, and sets the selected segment index to the values it receives. +- (RACChannelTerminal *)rac_newSelectedSegmentIndexChannelWithNilValue:(NSNumber *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.m new file mode 100644 index 0000000..96ea556 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UISegmentedControl+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UISegmentedControl+RACSignalSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UISegmentedControl (RACSignalSupport) + +- (RACChannelTerminal *)rac_newSelectedSegmentIndexChannelWithNilValue:(NSNumber *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.selectedSegmentIndex) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.h new file mode 100644 index 0000000..75626ad --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UISlider+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UISlider (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The value to set when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's value whenever the +/// UIControlEventValueChanged control event is fired, and sets the value to the +/// values it receives. +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.m new file mode 100644 index 0000000..1643f46 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UISlider+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UISlider+RACSignalSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UISlider (RACSignalSupport) + +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.value) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.h new file mode 100644 index 0000000..da6d97b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UIStepper+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UIStepper (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The value to set when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's value whenever the +/// UIControlEventValueChanged control event is fired, and sets the value to the +/// values it receives. +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.m new file mode 100644 index 0000000..c7dc6ea --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UIStepper+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIStepper+RACSignalSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UIStepper (RACSignalSupport) + +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.value) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.h new file mode 100644 index 0000000..1313a2c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.h
@@ -0,0 +1,22 @@ +// +// UISwitch+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UISwitch (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// Returns a RACChannelTerminal that sends whether the receiver is on whenever +/// the UIControlEventValueChanged control event is fired, and sets it on or off +/// when it receives @YES or @NO respectively. +- (RACChannelTerminal *)rac_newOnChannel; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.m new file mode 100644 index 0000000..8146507 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UISwitch+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UISwitch+RACSignalSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UISwitch (RACSignalSupport) + +- (RACChannelTerminal *)rac_newOnChannel { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.on) nilValue:@NO]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.h new file mode 100644 index 0000000..c29d47c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.h
@@ -0,0 +1,28 @@ +// +// UITableViewCell+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +@interface UITableViewCell (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 *rac_prepareForReuseSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.m new file mode 100644 index 0000000..8a81c30 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.m
@@ -0,0 +1,31 @@ +// +// UITableViewCell+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UITableViewCell+RACSignalSupport.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACSignal+Operations.h" +#import "RACUnit.h" +#import <objc/runtime.h> + +@implementation UITableViewCell (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", self.rac_description]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.h new file mode 100644 index 0000000..6c5d6b8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.h
@@ -0,0 +1,29 @@ +// +// UITableViewHeaderFooterView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Syo Ikeda on 12/30/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +// This category is only applicable to iOS >= 6.0. +@interface UITableViewHeaderFooterView (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 *rac_prepareForReuseSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.m new file mode 100644 index 0000000..0ecfc37 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.m
@@ -0,0 +1,31 @@ +// +// UITableViewHeaderFooterView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Syo Ikeda on 12/30/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UITableViewHeaderFooterView+RACSignalSupport.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACSignal+Operations.h" +#import "RACUnit.h" +#import <objc/runtime.h> + +@implementation UITableViewHeaderFooterView (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", self.rac_description]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.h new file mode 100644 index 0000000..4ff6ee4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.h
@@ -0,0 +1,27 @@ +// +// UITextField+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal, RACChannelTerminal; + +@interface UITextField (RACSignalSupport) + +/// Creates and returns a signal for the text of the field. It always starts with +/// the current text. The signal sends next when the UIControlEventEditingChanged +/// or UIControlEventEditingDidBegin control event is fired on the control. +- (RACSignal *)rac_textSignal; + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// Returns a RACChannelTerminal that sends the receiver's text whenever the +/// UIControlEventEditingChanged or UIControlEventEditingDidBegin control event +/// is fired, and sets the text to the values it receives. +- (RACChannelTerminal *)rac_newTextChannel; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.m new file mode 100644 index 0000000..6e6d523 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.m
@@ -0,0 +1,39 @@ +// +// UITextField+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "UITextField+RACSignalSupport.h" +#import "RACEXTKeyPathCoding.h" +#import "RACEXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "RACSignal+Operations.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UITextField (RACSignalSupport) + +- (RACSignal *)rac_textSignal { + @weakify(self); + return [[[[[RACSignal + defer:^{ + @strongify(self); + return [RACSignal return:self]; + }] + concat:[self rac_signalForControlEvents:UIControlEventEditingChanged | UIControlEventEditingDidBegin]] + map:^(UITextField *x) { + return x.text; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_textSignal", [self rac_description]]; +} + +- (RACChannelTerminal *)rac_newTextChannel { + return [self rac_channelForControlEvents:UIControlEventEditingChanged | UIControlEventEditingDidBegin key:@keypath(self.text) nilValue:@""]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.h new file mode 100644 index 0000000..174b1ba --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.h
@@ -0,0 +1,39 @@ +// +// UITextView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Cody Krieger on 5/18/12. +// Copyright (c) 2012 Cody Krieger. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACDelegateProxy; +@class RACSignal; + +@interface UITextView (RACSignalSupport) + +/// A delegate proxy which will be set as the receiver's delegate when any of the +/// methods in this category are used. +@property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; + +/// Creates a signal for the text of the receiver. +/// +/// When this method is invoked, the `rac_delegateProxy` will become the +/// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy +/// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't +/// know how to handle. Setting the receiver's `delegate` afterward is +/// considered undefined behavior. +/// +/// Returns a signal which will send the current text upon subscription, then +/// again whenever the receiver's text is changed. The signal will complete when +/// the receiver is deallocated. +- (RACSignal *)rac_textSignal; + +@end + +@interface UITextView (RACSignalSupportUnavailable) + +- (RACSignal *)rac_signalForDelegateMethod:(SEL)method __attribute__((unavailable("Use -rac_signalForSelector:fromProtocol: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.m new file mode 100644 index 0000000..7208698 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.m
@@ -0,0 +1,56 @@ +// +// UITextView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Cody Krieger on 5/18/12. +// Copyright (c) 2012 Cody Krieger. All rights reserved. +// + +#import "UITextView+RACSignalSupport.h" +#import "RACEXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" +#import <objc/runtime.h> + +@implementation UITextView (RACSignalSupport) + +static void RACUseDelegateProxy(UITextView *self) { + if (self.delegate == self.rac_delegateProxy) return; + + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; +} + +- (RACDelegateProxy *)rac_delegateProxy { + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UITextViewDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + return proxy; +} + +- (RACSignal *)rac_textSignal { + @weakify(self); + RACSignal *signal = [[[[[RACSignal + defer:^{ + @strongify(self); + return [RACSignal return:RACTuplePack(self)]; + }] + concat:[self.rac_delegateProxy signalForSelector:@selector(textViewDidChange:)]] + reduceEach:^(UITextView *x) { + return x.text; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_textSignal", [self rac_description]]; + + RACUseDelegateProxy(self); + + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTKeyPathCoding.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTKeyPathCoding.h new file mode 100644 index 0000000..0106319 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTKeyPathCoding.h
@@ -0,0 +1,68 @@ +// +// EXTKeyPathCoding.h +// extobjc +// +// Created by Justin Spahr-Summers on 19.06.12. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import <Foundation/Foundation.h> +#import "RACmetamacros.h" + +/** + * \@keypath allows compile-time verification of key paths. Given a real object + * receiver and key path: + * + * @code + +NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String); +// => @"lowercaseString.UTF8String" + +NSString *versionPath = @keypath(NSObject, version); +// => @"version" + +NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString); +// => @"lowercaseString" + + * @endcode + * + * ... the macro returns an \c NSString containing all but the first path + * component or argument (e.g., @"lowercaseString.UTF8String", @"version"). + * + * In addition to simply creating a key path, this macro ensures that the key + * path is valid at compile-time (causing a syntax error if not), and supports + * refactoring, such that changing the name of the property will also update any + * uses of \@keypath. + */ +#define keypath(...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__)) + +#define keypath1(PATH) \ + (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1)) + +#define keypath2(OBJ, PATH) \ + (((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) + +/** + * \@collectionKeypath allows compile-time verification of key paths across collections NSArray/NSSet etc. Given a real object + * receiver, collection object receiver and related keypaths: + * + * @code + + NSString *employessFirstNamePath = @collectionKeypath(department.employees, Employee.new, firstName) + // => @"employees.firstName" + + NSString *employessFirstNamePath = @collectionKeypath(Department.new, employees, Employee.new, firstName) + // => @"employees.firstName" + + * @endcode + * + */ +#define collectionKeypath(...) \ + metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__)) + +#define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) + +#define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(OBJ, PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) +
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTRuntimeExtensions.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTRuntimeExtensions.h new file mode 100644 index 0000000..ab4e11d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTRuntimeExtensions.h
@@ -0,0 +1,122 @@ +// +// EXTRuntimeExtensions.h +// extobjc +// +// Created by Justin Spahr-Summers on 2011-03-05. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import <objc/runtime.h> + +/** + * Describes the memory management policy of a property. + */ +typedef enum { + /** + * The value is assigned. + */ + rac_propertyMemoryManagementPolicyAssign = 0, + + /** + * The value is retained. + */ + rac_propertyMemoryManagementPolicyRetain, + + /** + * The value is copied. + */ + rac_propertyMemoryManagementPolicyCopy +} rac_propertyMemoryManagementPolicy; + +/** + * Describes the attributes and type information of a property. + */ +typedef struct { + /** + * Whether this property was declared with the \c readonly attribute. + */ + BOOL readonly; + + /** + * Whether this property was declared with the \c nonatomic attribute. + */ + BOOL nonatomic; + + /** + * Whether the property is a weak reference. + */ + BOOL weak; + + /** + * Whether the property is eligible for garbage collection. + */ + BOOL canBeCollected; + + /** + * Whether this property is defined with \c \@dynamic. + */ + BOOL dynamic; + + /** + * The memory management policy for this property. This will always be + * #rac_propertyMemoryManagementPolicyAssign if #readonly is \c YES. + */ + rac_propertyMemoryManagementPolicy memoryManagementPolicy; + + /** + * The selector for the getter of this property. This will reflect any + * custom \c getter= attribute provided in the property declaration, or the + * inferred getter name otherwise. + */ + SEL getter; + + /** + * The selector for the setter of this property. This will reflect any + * custom \c setter= attribute provided in the property declaration, or the + * inferred setter name otherwise. + * + * @note If #readonly is \c YES, this value will represent what the setter + * \e would be, if the property were writable. + */ + SEL setter; + + /** + * The backing instance variable for this property, or \c NULL if \c + * \c @synthesize was not used, and therefore no instance variable exists. This + * would also be the case if the property is implemented dynamically. + */ + const char *ivar; + + /** + * If this property is defined as being an instance of a specific class, + * this will be the class object representing it. + * + * This will be \c nil if the property was defined as type \c id, if the + * property is not of an object type, or if the class could not be found at + * runtime. + */ + Class objectClass; + + /** + * The type encoding for the value of this property. This is the type as it + * would be returned by the \c \@encode() directive. + */ + char type[]; +} rac_propertyAttributes; + +/** + * Finds the instance method named \a aSelector on \a aClass and returns it, or + * returns \c NULL if no such instance method exists. Unlike \c + * class_getInstanceMethod(), this does not search superclasses. + * + * @note To get class methods in this manner, use a metaclass for \a aClass. + */ +Method rac_getImmediateInstanceMethod (Class aClass, SEL aSelector); + +/** + * Returns a pointer to a structure containing information about \a property. + * You must \c free() the returned pointer. Returns \c NULL if there is an error + * obtaining information from \a property. + */ +rac_propertyAttributes *rac_copyPropertyAttributes (objc_property_t property);
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTRuntimeExtensions.m b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTRuntimeExtensions.m new file mode 100644 index 0000000..2d2010e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTRuntimeExtensions.m
@@ -0,0 +1,232 @@ +// +// EXTRuntimeExtensions.m +// extobjc +// +// Created by Justin Spahr-Summers on 2011-03-05. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import "RACEXTRuntimeExtensions.h" +#import <ctype.h> +#import <libkern/OSAtomic.h> +#import <objc/message.h> +#import <pthread.h> +#import <stdio.h> +#import <stdlib.h> +#import <string.h> + +rac_propertyAttributes *rac_copyPropertyAttributes (objc_property_t property) { + const char * const attrString = property_getAttributes(property); + if (!attrString) { + fprintf(stderr, "ERROR: Could not get attribute string from property %s\n", property_getName(property)); + return NULL; + } + + if (attrString[0] != 'T') { + fprintf(stderr, "ERROR: Expected attribute string \"%s\" for property %s to start with 'T'\n", attrString, property_getName(property)); + return NULL; + } + + const char *typeString = attrString + 1; + const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL); + if (!next) { + fprintf(stderr, "ERROR: Could not read past type in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + size_t typeLength = (size_t)(next - typeString); + if (!typeLength) { + fprintf(stderr, "ERROR: Invalid type in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + // allocate enough space for the structure and the type string (plus a NUL) + rac_propertyAttributes *attributes = calloc(1, sizeof(rac_propertyAttributes) + typeLength + 1); + if (!attributes) { + fprintf(stderr, "ERROR: Could not allocate rac_propertyAttributes structure for attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + // copy the type string + strncpy(attributes->type, typeString, typeLength); + attributes->type[typeLength] = '\0'; + + // if this is an object type, and immediately followed by a quoted string... + if (typeString[0] == *(@encode(id)) && typeString[1] == '"') { + // we should be able to extract a class name + const char *className = typeString + 2; + next = strchr(className, '"'); + + if (!next) { + fprintf(stderr, "ERROR: Could not read class name in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + if (className != next) { + size_t classNameLength = (size_t)(next - className); + char trimmedName[classNameLength + 1]; + + strncpy(trimmedName, className, classNameLength); + trimmedName[classNameLength] = '\0'; + + // attempt to look up the class in the runtime + attributes->objectClass = objc_getClass(trimmedName); + } + } + + if (*next != '\0') { + // skip past any junk before the first flag + next = strchr(next, ','); + } + + while (next && *next == ',') { + char flag = next[1]; + next += 2; + + switch (flag) { + case '\0': + break; + + case 'R': + attributes->readonly = YES; + break; + + case 'C': + attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyCopy; + break; + + case '&': + attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyRetain; + break; + + case 'N': + attributes->nonatomic = YES; + break; + + case 'G': + case 'S': + { + const char *nextFlag = strchr(next, ','); + SEL name = NULL; + + if (!nextFlag) { + // assume that the rest of the string is the selector + const char *selectorString = next; + next = ""; + + name = sel_registerName(selectorString); + } else { + size_t selectorLength = (size_t)(nextFlag - next); + if (!selectorLength) { + fprintf(stderr, "ERROR: Found zero length selector name in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + goto errorOut; + } + + char selectorString[selectorLength + 1]; + + strncpy(selectorString, next, selectorLength); + selectorString[selectorLength] = '\0'; + + name = sel_registerName(selectorString); + next = nextFlag; + } + + if (flag == 'G') + attributes->getter = name; + else + attributes->setter = name; + } + + break; + + case 'D': + attributes->dynamic = YES; + attributes->ivar = NULL; + break; + + case 'V': + // assume that the rest of the string (if present) is the ivar name + if (*next == '\0') { + // if there's nothing there, let's assume this is dynamic + attributes->ivar = NULL; + } else { + attributes->ivar = next; + next = ""; + } + + break; + + case 'W': + attributes->weak = YES; + break; + + case 'P': + attributes->canBeCollected = YES; + break; + + case 't': + fprintf(stderr, "ERROR: Old-style type encoding is unsupported in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + + // skip over this type encoding + while (*next != ',' && *next != '\0') + ++next; + + break; + + default: + fprintf(stderr, "ERROR: Unrecognized attribute string flag '%c' in attribute string \"%s\" for property %s\n", flag, attrString, property_getName(property)); + } + } + + if (next && *next != '\0') { + fprintf(stderr, "Warning: Unparsed data \"%s\" in attribute string \"%s\" for property %s\n", next, attrString, property_getName(property)); + } + + if (!attributes->getter) { + // use the property name as the getter by default + attributes->getter = sel_registerName(property_getName(property)); + } + + if (!attributes->setter) { + const char *propertyName = property_getName(property); + size_t propertyNameLength = strlen(propertyName); + + // we want to transform the name to setProperty: style + size_t setterLength = propertyNameLength + 4; + + char setterName[setterLength + 1]; + strncpy(setterName, "set", 3); + strncpy(setterName + 3, propertyName, propertyNameLength); + + // capitalize property name for the setter + setterName[3] = (char)toupper(setterName[3]); + + setterName[setterLength - 1] = ':'; + setterName[setterLength] = '\0'; + + attributes->setter = sel_registerName(setterName); + } + + return attributes; + +errorOut: + free(attributes); + return NULL; +} + +Method rac_getImmediateInstanceMethod (Class aClass, SEL aSelector) { + unsigned methodCount = 0; + Method *methods = class_copyMethodList(aClass, &methodCount); + Method foundMethod = NULL; + + for (unsigned methodIndex = 0;methodIndex < methodCount;++methodIndex) { + if (method_getName(methods[methodIndex]) == aSelector) { + foundMethod = methods[methodIndex]; + break; + } + } + + free(methods); + return foundMethod; +}
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTScope.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTScope.h new file mode 100644 index 0000000..029e0c4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACEXTScope.h
@@ -0,0 +1,101 @@ +// +// EXTScope.h +// extobjc +// +// Created by Justin Spahr-Summers on 2011-05-04. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import "RACmetamacros.h" + +/** + * \@onExit defines some code to be executed when the current scope exits. The + * code must be enclosed in braces and terminated with a semicolon, and will be + * executed regardless of how the scope is exited, including from exceptions, + * \c goto, \c return, \c break, and \c continue. + * + * Provided code will go into a block to be executed later. Keep this in mind as + * it pertains to memory management, restrictions on assignment, etc. Because + * the code is used within a block, \c return is a legal (though perhaps + * confusing) way to exit the cleanup block early. + * + * Multiple \@onExit statements in the same scope are executed in reverse + * lexical order. This helps when pairing resource acquisition with \@onExit + * statements, as it guarantees teardown in the opposite order of acquisition. + * + * @note This statement cannot be used within scopes defined without braces + * (like a one line \c if). In practice, this is not an issue, since \@onExit is + * a useless construct in such a case anyways. + */ +#define onExit \ + autoreleasepool {} \ + __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^ + +/** + * Creates \c __weak shadow variables for each of the variables provided as + * arguments, which can later be made strong again with #strongify. + * + * This is typically used to weakly reference variables in a block, but then + * ensure that the variables stay alive during the actual execution of the block + * (if they were live upon entry). + * + * See #strongify for an example of usage. + */ +#define weakify(...) \ + autoreleasepool {} \ + metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__) + +/** + * Like #weakify, but uses \c __unsafe_unretained instead, for targets or + * classes that do not support weak references. + */ +#define unsafeify(...) \ + autoreleasepool {} \ + metamacro_foreach_cxt(rac_weakify_,, __unsafe_unretained, __VA_ARGS__) + +/** + * Strongly references each of the variables provided as arguments, which must + * have previously been passed to #weakify. + * + * The strong references created will shadow the original variable names, such + * that the original names can be used without issue (and a significantly + * reduced risk of retain cycles) in the current scope. + * + * @code + + id foo = [[NSObject alloc] init]; + id bar = [[NSObject alloc] init]; + + @weakify(foo, bar); + + // this block will not keep 'foo' or 'bar' alive + BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){ + // but now, upon entry, 'foo' and 'bar' will stay alive until the block has + // finished executing + @strongify(foo, bar); + + return [foo isEqual:obj] || [bar isEqual:obj]; + }; + + * @endcode + */ +#define strongify(...) \ + try {} @finally {} \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + metamacro_foreach(rac_strongify_,, __VA_ARGS__) \ + _Pragma("clang diagnostic pop") + +/*** implementation details follow ***/ +typedef void (^rac_cleanupBlock_t)(); + +static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) { + (*block)(); +} + +#define rac_weakify_(INDEX, CONTEXT, VAR) \ + CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR); + +#define rac_strongify_(INDEX, VAR) \ + __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACmetamacros.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACmetamacros.h new file mode 100644 index 0000000..77a77b5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/RACmetamacros.h
@@ -0,0 +1,666 @@ +/** + * Macros for metaprogramming + * ExtendedC + * + * Copyright (C) 2012 Justin Spahr-Summers + * Released under the MIT license + */ + +#ifndef EXTC_METAMACROS_H +#define EXTC_METAMACROS_H + +/** + * Executes one or more expressions (which may have a void type, such as a call + * to a function that returns no value) and always returns true. + */ +#define metamacro_exprify(...) \ + ((__VA_ARGS__), true) + +/** + * Returns a string representation of VALUE after full macro expansion. + */ +#define metamacro_stringify(VALUE) \ + metamacro_stringify_(VALUE) + +/** + * Returns A and B concatenated after full macro expansion. + */ +#define metamacro_concat(A, B) \ + metamacro_concat_(A, B) + +/** + * Returns the Nth variadic argument (starting from zero). At least + * N + 1 variadic arguments must be given. N must be between zero and twenty, + * inclusive. + */ +#define metamacro_at(N, ...) \ + metamacro_concat(metamacro_at, N)(__VA_ARGS__) + +/** + * Returns the number of arguments (up to twenty) provided to the macro. At + * least one argument must be provided. + * + * Inspired by P99: http://p99.gforge.inria.fr + */ +#define metamacro_argcount(...) \ + metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) + +/** + * Identical to #metamacro_foreach_cxt, except that no CONTEXT argument is + * given. Only the index and current argument will thus be passed to MACRO. + */ +#define metamacro_foreach(MACRO, SEP, ...) \ + metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__) + +/** + * For each consecutive variadic argument (up to twenty), MACRO is passed the + * zero-based index of the current argument, CONTEXT, and then the argument + * itself. The results of adjoining invocations of MACRO are then separated by + * SEP. + * + * Inspired by P99: http://p99.gforge.inria.fr + */ +#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \ + metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) + +/** + * Identical to #metamacro_foreach_cxt. This can be used when the former would + * fail due to recursive macro expansion. + */ +#define metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...) \ + metamacro_concat(metamacro_foreach_cxt_recursive, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) + +/** + * In consecutive order, appends each variadic argument (up to twenty) onto + * BASE. The resulting concatenations are then separated by SEP. + * + * This is primarily useful to manipulate a list of macro invocations into instead + * invoking a different, possibly related macro. + */ +#define metamacro_foreach_concat(BASE, SEP, ...) \ + metamacro_foreach_cxt(metamacro_foreach_concat_iter, SEP, BASE, __VA_ARGS__) + +/** + * Iterates COUNT times, each time invoking MACRO with the current index + * (starting at zero) and CONTEXT. The results of adjoining invocations of MACRO + * are then separated by SEP. + * + * COUNT must be an integer between zero and twenty, inclusive. + */ +#define metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT) \ + metamacro_concat(metamacro_for_cxt, COUNT)(MACRO, SEP, CONTEXT) + +/** + * Returns the first argument given. At least one argument must be provided. + * + * This is useful when implementing a variadic macro, where you may have only + * one variadic argument, but no way to retrieve it (for example, because \c ... + * always needs to match at least one argument). + * + * @code + +#define varmacro(...) \ + metamacro_head(__VA_ARGS__) + + * @endcode + */ +#define metamacro_head(...) \ + metamacro_head_(__VA_ARGS__, 0) + +/** + * Returns every argument except the first. At least two arguments must be + * provided. + */ +#define metamacro_tail(...) \ + metamacro_tail_(__VA_ARGS__) + +/** + * Returns the first N (up to twenty) variadic arguments as a new argument list. + * At least N variadic arguments must be provided. + */ +#define metamacro_take(N, ...) \ + metamacro_concat(metamacro_take, N)(__VA_ARGS__) + +/** + * Removes the first N (up to twenty) variadic arguments from the given argument + * list. At least N variadic arguments must be provided. + */ +#define metamacro_drop(N, ...) \ + metamacro_concat(metamacro_drop, N)(__VA_ARGS__) + +/** + * Decrements VAL, which must be a number between zero and twenty, inclusive. + * + * This is primarily useful when dealing with indexes and counts in + * metaprogramming. + */ +#define metamacro_dec(VAL) \ + metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) + +/** + * Increments VAL, which must be a number between zero and twenty, inclusive. + * + * This is primarily useful when dealing with indexes and counts in + * metaprogramming. + */ +#define metamacro_inc(VAL) \ + metamacro_at(VAL, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21) + +/** + * If A is equal to B, the next argument list is expanded; otherwise, the + * argument list after that is expanded. A and B must be numbers between zero + * and twenty, inclusive. Additionally, B must be greater than or equal to A. + * + * @code + +// expands to true +metamacro_if_eq(0, 0)(true)(false) + +// expands to false +metamacro_if_eq(0, 1)(true)(false) + + * @endcode + * + * This is primarily useful when dealing with indexes and counts in + * metaprogramming. + */ +#define metamacro_if_eq(A, B) \ + metamacro_concat(metamacro_if_eq, A)(B) + +/** + * Identical to #metamacro_if_eq. This can be used when the former would fail + * due to recursive macro expansion. + */ +#define metamacro_if_eq_recursive(A, B) \ + metamacro_concat(metamacro_if_eq_recursive, A)(B) + +/** + * Returns 1 if N is an even number, or 0 otherwise. N must be between zero and + * twenty, inclusive. + * + * For the purposes of this test, zero is considered even. + */ +#define metamacro_is_even(N) \ + metamacro_at(N, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) + +/** + * Returns the logical NOT of B, which must be the number zero or one. + */ +#define metamacro_not(B) \ + metamacro_at(B, 1, 0) + +// IMPLEMENTATION DETAILS FOLLOW! +// Do not write code that depends on anything below this line. +#define metamacro_stringify_(VALUE) # VALUE +#define metamacro_concat_(A, B) A ## B +#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG) +#define metamacro_head_(FIRST, ...) FIRST +#define metamacro_tail_(FIRST, ...) __VA_ARGS__ +#define metamacro_consume_(...) +#define metamacro_expand_(...) __VA_ARGS__ + +// implemented from scratch so that metamacro_concat() doesn't end up nesting +#define metamacro_foreach_concat_iter(INDEX, BASE, ARG) metamacro_foreach_concat_iter_(BASE, ARG) +#define metamacro_foreach_concat_iter_(BASE, ARG) BASE ## ARG + +// metamacro_at expansions +#define metamacro_at0(...) metamacro_head(__VA_ARGS__) +#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__) + +// metamacro_foreach_cxt expansions +#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT) +#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) + +#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ + metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \ + SEP \ + MACRO(1, CONTEXT, _1) + +#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ + SEP \ + MACRO(2, CONTEXT, _2) + +#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + SEP \ + MACRO(3, CONTEXT, _3) + +#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + SEP \ + MACRO(4, CONTEXT, _4) + +#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + SEP \ + MACRO(5, CONTEXT, _5) + +#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + SEP \ + MACRO(6, CONTEXT, _6) + +#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + SEP \ + MACRO(7, CONTEXT, _7) + +#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + SEP \ + MACRO(8, CONTEXT, _8) + +#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + SEP \ + MACRO(9, CONTEXT, _9) + +#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + SEP \ + MACRO(10, CONTEXT, _10) + +#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + SEP \ + MACRO(11, CONTEXT, _11) + +#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + SEP \ + MACRO(12, CONTEXT, _12) + +#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + SEP \ + MACRO(13, CONTEXT, _13) + +#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + SEP \ + MACRO(14, CONTEXT, _14) + +#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + SEP \ + MACRO(15, CONTEXT, _15) + +#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + SEP \ + MACRO(16, CONTEXT, _16) + +#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + SEP \ + MACRO(17, CONTEXT, _17) + +#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + SEP \ + MACRO(18, CONTEXT, _18) + +#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ + metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + SEP \ + MACRO(19, CONTEXT, _19) + +// metamacro_foreach_cxt_recursive expansions +#define metamacro_foreach_cxt_recursive0(MACRO, SEP, CONTEXT) +#define metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) + +#define metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ + metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) \ + SEP \ + MACRO(1, CONTEXT, _1) + +#define metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ + SEP \ + MACRO(2, CONTEXT, _2) + +#define metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + SEP \ + MACRO(3, CONTEXT, _3) + +#define metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + SEP \ + MACRO(4, CONTEXT, _4) + +#define metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + SEP \ + MACRO(5, CONTEXT, _5) + +#define metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + SEP \ + MACRO(6, CONTEXT, _6) + +#define metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + SEP \ + MACRO(7, CONTEXT, _7) + +#define metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + SEP \ + MACRO(8, CONTEXT, _8) + +#define metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + SEP \ + MACRO(9, CONTEXT, _9) + +#define metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + SEP \ + MACRO(10, CONTEXT, _10) + +#define metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + SEP \ + MACRO(11, CONTEXT, _11) + +#define metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + SEP \ + MACRO(12, CONTEXT, _12) + +#define metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + SEP \ + MACRO(13, CONTEXT, _13) + +#define metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + SEP \ + MACRO(14, CONTEXT, _14) + +#define metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + SEP \ + MACRO(15, CONTEXT, _15) + +#define metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + SEP \ + MACRO(16, CONTEXT, _16) + +#define metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + SEP \ + MACRO(17, CONTEXT, _17) + +#define metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + SEP \ + MACRO(18, CONTEXT, _18) + +#define metamacro_foreach_cxt_recursive20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ + metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + SEP \ + MACRO(19, CONTEXT, _19) + +// metamacro_for_cxt expansions +#define metamacro_for_cxt0(MACRO, SEP, CONTEXT) +#define metamacro_for_cxt1(MACRO, SEP, CONTEXT) MACRO(0, CONTEXT) + +#define metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt1(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(1, CONTEXT) + +#define metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(2, CONTEXT) + +#define metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(3, CONTEXT) + +#define metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(4, CONTEXT) + +#define metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(5, CONTEXT) + +#define metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(6, CONTEXT) + +#define metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(7, CONTEXT) + +#define metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(8, CONTEXT) + +#define metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(9, CONTEXT) + +#define metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(10, CONTEXT) + +#define metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(11, CONTEXT) + +#define metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(12, CONTEXT) + +#define metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(13, CONTEXT) + +#define metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(14, CONTEXT) + +#define metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(15, CONTEXT) + +#define metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(16, CONTEXT) + +#define metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(17, CONTEXT) + +#define metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(18, CONTEXT) + +#define metamacro_for_cxt20(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(19, CONTEXT) + +// metamacro_if_eq expansions +#define metamacro_if_eq0(VALUE) \ + metamacro_concat(metamacro_if_eq0_, VALUE) + +#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_ +#define metamacro_if_eq0_1(...) metamacro_expand_ +#define metamacro_if_eq0_2(...) metamacro_expand_ +#define metamacro_if_eq0_3(...) metamacro_expand_ +#define metamacro_if_eq0_4(...) metamacro_expand_ +#define metamacro_if_eq0_5(...) metamacro_expand_ +#define metamacro_if_eq0_6(...) metamacro_expand_ +#define metamacro_if_eq0_7(...) metamacro_expand_ +#define metamacro_if_eq0_8(...) metamacro_expand_ +#define metamacro_if_eq0_9(...) metamacro_expand_ +#define metamacro_if_eq0_10(...) metamacro_expand_ +#define metamacro_if_eq0_11(...) metamacro_expand_ +#define metamacro_if_eq0_12(...) metamacro_expand_ +#define metamacro_if_eq0_13(...) metamacro_expand_ +#define metamacro_if_eq0_14(...) metamacro_expand_ +#define metamacro_if_eq0_15(...) metamacro_expand_ +#define metamacro_if_eq0_16(...) metamacro_expand_ +#define metamacro_if_eq0_17(...) metamacro_expand_ +#define metamacro_if_eq0_18(...) metamacro_expand_ +#define metamacro_if_eq0_19(...) metamacro_expand_ +#define metamacro_if_eq0_20(...) metamacro_expand_ + +#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE)) +#define metamacro_if_eq2(VALUE) metamacro_if_eq1(metamacro_dec(VALUE)) +#define metamacro_if_eq3(VALUE) metamacro_if_eq2(metamacro_dec(VALUE)) +#define metamacro_if_eq4(VALUE) metamacro_if_eq3(metamacro_dec(VALUE)) +#define metamacro_if_eq5(VALUE) metamacro_if_eq4(metamacro_dec(VALUE)) +#define metamacro_if_eq6(VALUE) metamacro_if_eq5(metamacro_dec(VALUE)) +#define metamacro_if_eq7(VALUE) metamacro_if_eq6(metamacro_dec(VALUE)) +#define metamacro_if_eq8(VALUE) metamacro_if_eq7(metamacro_dec(VALUE)) +#define metamacro_if_eq9(VALUE) metamacro_if_eq8(metamacro_dec(VALUE)) +#define metamacro_if_eq10(VALUE) metamacro_if_eq9(metamacro_dec(VALUE)) +#define metamacro_if_eq11(VALUE) metamacro_if_eq10(metamacro_dec(VALUE)) +#define metamacro_if_eq12(VALUE) metamacro_if_eq11(metamacro_dec(VALUE)) +#define metamacro_if_eq13(VALUE) metamacro_if_eq12(metamacro_dec(VALUE)) +#define metamacro_if_eq14(VALUE) metamacro_if_eq13(metamacro_dec(VALUE)) +#define metamacro_if_eq15(VALUE) metamacro_if_eq14(metamacro_dec(VALUE)) +#define metamacro_if_eq16(VALUE) metamacro_if_eq15(metamacro_dec(VALUE)) +#define metamacro_if_eq17(VALUE) metamacro_if_eq16(metamacro_dec(VALUE)) +#define metamacro_if_eq18(VALUE) metamacro_if_eq17(metamacro_dec(VALUE)) +#define metamacro_if_eq19(VALUE) metamacro_if_eq18(metamacro_dec(VALUE)) +#define metamacro_if_eq20(VALUE) metamacro_if_eq19(metamacro_dec(VALUE)) + +// metamacro_if_eq_recursive expansions +#define metamacro_if_eq_recursive0(VALUE) \ + metamacro_concat(metamacro_if_eq_recursive0_, VALUE) + +#define metamacro_if_eq_recursive0_0(...) __VA_ARGS__ metamacro_consume_ +#define metamacro_if_eq_recursive0_1(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_2(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_3(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_4(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_5(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_6(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_7(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_8(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_9(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_10(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_11(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_12(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_13(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_14(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_15(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_16(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_17(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_18(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_19(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_20(...) metamacro_expand_ + +#define metamacro_if_eq_recursive1(VALUE) metamacro_if_eq_recursive0(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive2(VALUE) metamacro_if_eq_recursive1(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive3(VALUE) metamacro_if_eq_recursive2(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive4(VALUE) metamacro_if_eq_recursive3(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive5(VALUE) metamacro_if_eq_recursive4(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive6(VALUE) metamacro_if_eq_recursive5(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive7(VALUE) metamacro_if_eq_recursive6(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive8(VALUE) metamacro_if_eq_recursive7(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive9(VALUE) metamacro_if_eq_recursive8(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive10(VALUE) metamacro_if_eq_recursive9(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive11(VALUE) metamacro_if_eq_recursive10(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive12(VALUE) metamacro_if_eq_recursive11(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive13(VALUE) metamacro_if_eq_recursive12(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive14(VALUE) metamacro_if_eq_recursive13(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive15(VALUE) metamacro_if_eq_recursive14(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive16(VALUE) metamacro_if_eq_recursive15(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive17(VALUE) metamacro_if_eq_recursive16(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive18(VALUE) metamacro_if_eq_recursive17(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive19(VALUE) metamacro_if_eq_recursive18(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive20(VALUE) metamacro_if_eq_recursive19(metamacro_dec(VALUE)) + +// metamacro_take expansions +#define metamacro_take0(...) +#define metamacro_take1(...) metamacro_head(__VA_ARGS__) +#define metamacro_take2(...) metamacro_head(__VA_ARGS__), metamacro_take1(metamacro_tail(__VA_ARGS__)) +#define metamacro_take3(...) metamacro_head(__VA_ARGS__), metamacro_take2(metamacro_tail(__VA_ARGS__)) +#define metamacro_take4(...) metamacro_head(__VA_ARGS__), metamacro_take3(metamacro_tail(__VA_ARGS__)) +#define metamacro_take5(...) metamacro_head(__VA_ARGS__), metamacro_take4(metamacro_tail(__VA_ARGS__)) +#define metamacro_take6(...) metamacro_head(__VA_ARGS__), metamacro_take5(metamacro_tail(__VA_ARGS__)) +#define metamacro_take7(...) metamacro_head(__VA_ARGS__), metamacro_take6(metamacro_tail(__VA_ARGS__)) +#define metamacro_take8(...) metamacro_head(__VA_ARGS__), metamacro_take7(metamacro_tail(__VA_ARGS__)) +#define metamacro_take9(...) metamacro_head(__VA_ARGS__), metamacro_take8(metamacro_tail(__VA_ARGS__)) +#define metamacro_take10(...) metamacro_head(__VA_ARGS__), metamacro_take9(metamacro_tail(__VA_ARGS__)) +#define metamacro_take11(...) metamacro_head(__VA_ARGS__), metamacro_take10(metamacro_tail(__VA_ARGS__)) +#define metamacro_take12(...) metamacro_head(__VA_ARGS__), metamacro_take11(metamacro_tail(__VA_ARGS__)) +#define metamacro_take13(...) metamacro_head(__VA_ARGS__), metamacro_take12(metamacro_tail(__VA_ARGS__)) +#define metamacro_take14(...) metamacro_head(__VA_ARGS__), metamacro_take13(metamacro_tail(__VA_ARGS__)) +#define metamacro_take15(...) metamacro_head(__VA_ARGS__), metamacro_take14(metamacro_tail(__VA_ARGS__)) +#define metamacro_take16(...) metamacro_head(__VA_ARGS__), metamacro_take15(metamacro_tail(__VA_ARGS__)) +#define metamacro_take17(...) metamacro_head(__VA_ARGS__), metamacro_take16(metamacro_tail(__VA_ARGS__)) +#define metamacro_take18(...) metamacro_head(__VA_ARGS__), metamacro_take17(metamacro_tail(__VA_ARGS__)) +#define metamacro_take19(...) metamacro_head(__VA_ARGS__), metamacro_take18(metamacro_tail(__VA_ARGS__)) +#define metamacro_take20(...) metamacro_head(__VA_ARGS__), metamacro_take19(metamacro_tail(__VA_ARGS__)) + +// metamacro_drop expansions +#define metamacro_drop0(...) __VA_ARGS__ +#define metamacro_drop1(...) metamacro_tail(__VA_ARGS__) +#define metamacro_drop2(...) metamacro_drop1(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop3(...) metamacro_drop2(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop4(...) metamacro_drop3(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop5(...) metamacro_drop4(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop6(...) metamacro_drop5(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop7(...) metamacro_drop6(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop8(...) metamacro_drop7(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop9(...) metamacro_drop8(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop10(...) metamacro_drop9(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop11(...) metamacro_drop10(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop12(...) metamacro_drop11(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop13(...) metamacro_drop12(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop14(...) metamacro_drop13(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop15(...) metamacro_drop14(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop16(...) metamacro_drop15(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop17(...) metamacro_drop16(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop18(...) metamacro_drop17(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop19(...) metamacro_drop18(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop20(...) metamacro_drop19(metamacro_tail(__VA_ARGS__)) + +#endif