This commit is contained in:
启星
2025-08-08 10:49:36 +08:00
parent 6400cf78bb
commit b5ce3d580a
8780 changed files with 978183 additions and 0 deletions

19
Pods/ReactiveObjC/LICENSE.md generated Normal file
View File

@@ -0,0 +1,19 @@
**Copyright (c) 2012 - 2016, 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.

547
Pods/ReactiveObjC/README.md generated Normal file
View File

@@ -0,0 +1,547 @@
# 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

112
Pods/ReactiveObjC/ReactiveObjC/RACEvent.m generated Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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