|
|
@@ -1,547 +0,0 @@
|
|
|
-# ReactiveObjC
|
|
|
-
|
|
|
-_NOTE: This is legacy introduction to the Objective-C ReactiveCocoa, which is
|
|
|
-now known as ReactiveObjC. For the updated version that uses Swift, please see
|
|
|
-[ReactiveCocoa][] or [ReactiveSwift][]_
|
|
|
-
|
|
|
-ReactiveObjC (formally ReactiveCocoa or RAC) is an Objective-C framework
|
|
|
-inspired by [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 ReactiveObjC, check out the other documentation in this folder for a
|
|
|
-framework overview and more in-depth information about how it all works in practice.
|
|
|
-
|
|
|
-## New to ReactiveObjC?
|
|
|
-
|
|
|
-ReactiveObjC 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 ReactiveObjC](#when-to-use-reactiveobjc)
|
|
|
- 1. [Framework Overview][]
|
|
|
- 1. [Basic Operators][]
|
|
|
- 1. [Header documentation](ReactiveObjC/)
|
|
|
- 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 this 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/ReactiveObjC/issues/new).
|
|
|
-
|
|
|
-## Introduction
|
|
|
-
|
|
|
-ReactiveObjC is inspired by [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, logs 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 logs names that starts 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
|
|
|
-// Creates 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
|
|
|
-// Logs 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
|
|
|
-// Hooks 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!");
|
|
|
- }];
|
|
|
-}];
|
|
|
-
|
|
|
-// Executes 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
|
|
|
-triggered after a group of operations completes:
|
|
|
-
|
|
|
-```objc
|
|
|
-// Performs 2 network operations and logs 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
|
|
|
-// Logs in the user, then loads any cached messages, then fetches the remaining
|
|
|
-// messages from the server. After that's all done, logs 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
|
|
|
-// returns 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
|
|
|
-// Creates a one-way binding so that self.imageView.image will be set as 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 ReactiveObjC. Additional information about RAC can be found
|
|
|
-in this folder.
|
|
|
-
|
|
|
-## When to use ReactiveObjC
|
|
|
-
|
|
|
-Upon first glance, ReactiveObjC 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];
|
|
|
-}];
|
|
|
-```
|
|
|
-
|
|
|
-ReactiveObjC 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
|
|
|
-
|
|
|
-ReactiveObjC supports OS X 10.8+ and iOS 8.0+.
|
|
|
-
|
|
|
-## Importing ReactiveObjC
|
|
|
-
|
|
|
-To add RAC to your application:
|
|
|
-
|
|
|
- 1. Add the ReactiveObjC repository as a submodule of your application's
|
|
|
- repository.
|
|
|
- 1. Run `git submodule update --init --recursive` from within the ReactiveObjC folder.
|
|
|
- 1. Drag and drop `ReactiveObjC.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.
|
|
|
- 1. Add `ReactiveObjC.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.
|
|
|
-
|
|
|
-To see a project already set up with RAC, check out [C-41][] or [GroceryList][],
|
|
|
-which are real iOS apps written using ReactiveObjC.
|
|
|
-
|
|
|
-## More Info
|
|
|
-
|
|
|
-ReactiveObjC is inspired by .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 frameworks inspired by functional reactive programming. Here
|
|
|
-are some resources related to 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)
|
|
|
-* [Specification for a Functional Reactive Language - Stack Overflow](http://stackoverflow.com/questions/5875929/specification-for-a-functional-reactive-programming-language#5878525)
|
|
|
-* [Principles of Reactive Programming on Coursera](https://www.coursera.org/course/reactive)
|
|
|
-
|
|
|
-[ReactiveCocoa]: https://github.com/ReactiveCocoa/ReactiveCocoa
|
|
|
-[ReactiveSwift]: https://github.com/ReactiveCocoa/ReactiveSwift
|
|
|
-[Basic Operators]: Documentation/BasicOperators.md
|
|
|
-[Framework Overview]: Documentation/FrameworkOverview.md
|
|
|
-[Functional Reactive Programming]: http://en.wikipedia.org/wiki/Functional_reactive_programming
|
|
|
-[GroceryList]: https://github.com/jspahrsummers/GroceryList
|
|
|
-[RACSequence]: ReactiveObjC/RACSequence.h
|
|
|
-[RACSignal]: ReactiveOjC/RACSignal.h
|
|
|
-[futures and promises]: http://en.wikipedia.org/wiki/Futures_and_promises
|
|
|
-[C-41]: https://github.com/AshFurrow/C-41
|