Project import
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bfcbe8a --- /dev/null +++ b/CHANGELOG.md
@@ -0,0 +1,417 @@ +# 2.0 + +This release of ReactiveCocoa contains major breaking changes that we were +unable to make after freezing the 1.0 API. All changes are focused on +eliminating bad code and bad usage patterns, reducing confusion, and increasing +flexibility in the framework. + +For a complete list of changes in ReactiveCocoa 2.0, see [the +milestone](https://github.com/ReactiveCocoa/ReactiveCocoa/issues?milestone=3&state=closed). + +**[Breaking changes](#breaking-changes)** + + 1. [Simplified and safer KVO](#simplified-and-safer-kvo) + 1. [Safer commands with less state](#safer-commands-with-less-state) + 1. [Fallback nil value for RAC macro](#fallback-nil-values-for-rac-macro) + 1. [Explicit schedulers for time-based operators](#explicit-schedulers-for-time-based-operators) + 1. [More powerful selector signals](#more-powerful-selector-signals) + 1. [Simpler two-way bindings](#simpler-two-way-bindings) + 1. [Better bindings for AppKit](#better-bindings-for-appkit) + 1. [More obvious sequencing operator](#more-obvious-sequencing-operator) + 1. [Renamed signal binding method](#renamed-signal-binding-method) + 1. [Consistent selector lifting](#consistent-selector-lifting) + 1. [Renamed scheduled signal constructors](#renamed-scheduled-signal-constructors) + 1. [Notification immediately before object deallocation](#notification-immediately-before-object-deallocation) + 1. [Extensible queue-based schedulers](#extensible-queue-based-schedulers) + 1. [GCD time values replaced with NSDate](#gcd-time-values-replaced-with-nsdate) + 1. [Windows and numbered buffers removed](#windows-and-numbered-buffers-removed) + 1. [NSTask extension removed](#nstask-extension-removed) + 1. [RACSubscriber class now private](#racsubscriber-class-now-private) + +**[Additions and improvements](#additions-and-improvements)** + + 1. [Commands for UIButton](#commands-for-uibutton) + 1. [Signal for UIActionSheet button clicks](#signal-for-uiactionsheet-button-clicks) + 1. [Better documentation for asynchronous backtraces](#better-documentation-for-asynchronous-backtraces) + 1. [Fixed libextobjc duplicated symbols](#fixed-libextobjc-duplicated-symbols) + 1. [Bindings for UIKit classes](#bindings-for-uikit-classes) + 1. [Signal subscription side effects](#signal-subscription-side-effects) + 1. [Test scheduler](#test-scheduler) + +## Breaking changes + +### Simplified and safer KVO + +`RACAble` and `RACAbleWithStart` have been replaced with a single +[RACObserve](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/601) macro. +`RACObserve` always starts with the current value of the property, and will +[notice the +deallocation](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/678) of `weak` +properties (unlike vanilla KVO). + +Unlike the previous macros, which only required one argument for key paths on +`self`, `RACObserve` always requires two arguments. + +**To update:** + + * Replace uses of `RACAbleWithStart(self.key)` with `RACObserve(self, key)`. + * Replace uses of `RACAble(self.key)` with `[RACObserve(self, key) skip:1]` (if + skipping the starting value is necessary). + +### Safer commands with less state + +`RACCommand` has been [completely +refactored](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/733). It is no +longer a `RACSignal`, and the behavior of `-addSignalBlock:` has been moved to +the initializer, making the class almost entirely immutable. + +Reflecting the most common use case, KVO-notifying properties have been changed +into signals instead. During the change, `canExecute` was also renamed to +`enabled`, which should make its purpose more obvious for binding to the UI. + +In addition, changes to `executing`, `errors`, and `enabled` are now guaranteed +to fire on the main thread, so UI observers no longer [run in the background +unexpectedly](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/533). + +All together, these improvements should make `RACCommand` more composable and +less imperative, so it fits into the framework better. + +**To update:** + + * Move execution logic from `-addSignalBlock:` or `-subscribeNext:` into the + `signalBlock` passed to the initializer. + * Instead of subscribing to the result of `-addSignalBlock:`, subscribe to + `executionSignals` or the result of `-execute:` instead. + * Replace uses of `RACAbleWithStart(command, executing)` with + `command.executing`. + * Replace uses of `RACAbleWithStart(command, canExecute)` with + `command.enabled`. + * Remove uses of `deliverOn:RACScheduler.mainThreadScheduler` on + `RACCommand` properties, as they are now unnecessary. + +### Fallback nil values for RAC macro + +The `RAC` macro now [always +requires](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/670) two or three +arguments: + + 1. The object to bind to. + 1. The key path to set when new values are sent. + 1. (Optional) A value to set when `nil` is sent on the signal. + +This is necessary to avoid a `-setNilValueForKey:` exception when observing +a primitive property _through_ an intermediate object which gets set to `nil`. + +This is not actually a change in key-value observing behavior — it's a caveat +with KVO regardless — but `RACObserve` makes it more prominent, because the +deallocation of `weak` properties will be considered a change to `nil`. + +**To update:** + + * Replace uses of `RAC(self.objectProperty)` with `RAC(self, objectProperty)`. + * When binding a signal that might send nil (like a key path observation) to + a primitive property, provide a default value: `RAC(self, integerProperty, @5)` + +### Explicit schedulers for time-based operators + +`-bufferWithTime:`, `+interval:`, and `-timeout:` have been unintuitive and +error-prone because of their implicit use of a background scheduler. + +All of the aforementioned methods now require [a scheduler +argument](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/588), so that it's +clear how events should be delivered. + +**To update:** + + * To match the previous behavior exactly, pass in `[RACScheduler scheduler]`. + Note that this creates a _new_ background scheduler for events to arrive upon. + * If you were already using `-deliverOn:` to force one of the above operators + to deliver onto a specific scheduler, you can eliminate that hop and pass the + scheduler into the operator directly. + +### More powerful selector signals + +`-rac_signalForSelector:` has been [completely +refactored](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/590) to support +listening for invocations of existing methods, new and existing methods with +multiple arguments, and existing methods with return values. + +`+rac_signalForSelector:` (the class method variant) was removed, because +swizzling class methods is dangerous and difficult to do correctly. + +**To update:** + + * Most existing uses of `-rac_signalForSelector:` shouldn't require + any changes. However, the `super` implementation (if available) of any targeted selector will + now be invoked, where it wasn't previously. Verify that existing uses can + handle this case. + * Replace uses of `+rac_signalForSelector:` by implementing the class method + and sending arguments onto a `RACSubject` instead. + +### Simpler two-way bindings + +`RACPropertySubject` and `RACBinding` have been +[replaced](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/695) with +`RACChannel` and `RACChannelTerminal`. Similarly, `RACObservablePropertySubject` +has been replaced with `RACKVOChannel`. + +In addition to slightly better terminology and more obvious usage, channels only +offer two-way bindings by default, which is a simplification over the previous N-way binding +interface. + +Because of the sweeping conceptual changes, the old APIs have been completely +removed without deprecation. + +**To update:** + + * Instead of creating a `RACPropertySubject`, create a `RACChannel`. Replace + N-way property subjects (where N is greater than 2) with multiple + `RACChannel`s. + * Instead of creating a `RACObservablePropertySubject`, create + a `RACKVOChannel` or use the `RACChannelTo` macro. + * Replace uses of `RACBinding` with `RACChannelTerminal`. + * Replace uses of `RACBind(self.objectProperty)` with `RACChannelTo(self, + objectProperty)`. Add a default value for primitive properties: + `RACChannelTo(self, integerProperty, @5)` + * Replace uses of `-bindTo:` with the explicit subscription of two endpoints: + +```objc +[binding1.followingEndpoint subscribe:binding2.leadingEndpoint]; +[[binding2.leadingEndpoint skip:1] subscribe:binding1.followingEndpoint]; +``` + +### Better bindings for AppKit + +`-rac_bind:toObject:withKeyPath:` and related methods have been +[replaced](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/638) with +`-rac_channelToBinding:options:`, which returns a `RACChannelTerminal` that can be used as +a two-way binding or a one-way signal. + +**To update:** + + * If possible, refactor code to use the new `RACChannel` interface. This + bridges Cocoa Bindings with the full power of ReactiveCocoa. + * For a direct conversion, use `-bind:toObject:withKeyPath:options:` with the + following options: + 1. `@{ NSContinuouslyUpdatesValueBindingOption: @YES }` for `-rac_bind:toObject:withKeyPath:` + 1. `@{ NSContinuouslyUpdatesValueBindingOption: @YES, NSNullPlaceholderBindingOption: nilValue }` for `-rac_bind:toObject:withKeyPath:nilValue:` + 1. `@{ NSContinuouslyUpdatesValueBindingOption: @YES, NSValueTransformerBindingOption: valueTransformer }` for `-rac_bind:toObject:withKeyPath:transform:` + 1. `@{ NSContinuouslyUpdatesValueBindingOption: @YES, NSValueTransformerBindingOption: NSNegateBooleanTransformerName }` for `-rac_bind:toObject:withNegatedKeyPath:` + +### More obvious sequencing operator + +To make the sequencing and transformation operators less confusing, +`-sequenceMany:` has been removed, and `-sequenceNext:` has been +[renamed](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/635) to `-then:`. + +**To update:** + + * Replace uses of `-sequenceMany:` with `-flattenMap:` and a block that doesn't + use its argument. + * Replace uses of `-sequenceNext:` with `-then:`. + +### Renamed signal binding method + +`-toProperty:onObject:` and `-[NSObject rac_deriveProperty:from:]` have been +[combined](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/617) into a new +`-[RACSignal setKeyPath:onObject:nilValue:]` method. + +The `nilValue` parameter was added in parallel with the +[RAC](#fallback-nil-values-for-rac-macro) macro, but the semantics are otherwise +identical. + +**To update:** + + * Replace `-toProperty:onObject:` and `-rac_deriveProperty:from:` with + `-setKeyPath:onObject:`. + * When binding a signal that might send nil (like a key path observation) to + a primitive property, provide a default value: `[signal setKeyPath:@"integerProperty" onObject:self nilValue:@5]` + +### Consistent selector lifting + +In the interests of [parametricity](http://en.wikipedia.org/wiki/Parametricity), +`-rac_liftSelector:withObjects:` has been +[replaced](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/609) with +`-rac_liftSelector:withSignals:`, which requires all arguments to be signals. +This makes usage more consistent. + +`-rac_liftBlock:withArguments:` has been removed, because it's redundant with +`RACSignal` operators. + +The `-rac_lift` proxy has also been removed, because there's no way to [make it +consistent](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/520) in the +same way. + +**To update:** + + * Wrap non-signal arguments with `+[RACSignal return:]` and add a nil + terminator. + * Replace block lifting with `+combineLatest:reduce:`. + * Replace uses of `-rac_lift` with `-rac_liftSelector:withSignals:`. + +### Renamed scheduled signal constructors + +`+start:`, `+startWithScheduler:block`, and `+startWithScheduler:subjectBlock:` +have been combined into a single +[+startEagerlyWithScheduler:block:](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/536) +constructor. + +The key improvements here are a more intuitive name, a required `RACScheduler` +to make it clear where the block is executed, and use of `<RACSubscriber>` +instead of `RACSubject` to make it more obvious how to use the block argument. + +**To update:** + + * Use `[RACScheduler scheduler]` to match the previous implicit scheduling + behavior of `+start:`. + * Refactor blocks that return values and set `success`/`error`, to send events + to the given `<RACSubscriber>` instead. + +### Notification immediately before object deallocation + +`-rac_didDeallocSignal` has been +[removed](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/585) in favor of +[-rac_willDeallocSignal](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/580), +because most teardown should happen _before_ the object becomes invalid. + +`-rac_addDeallocDisposable:` has also been +[removed](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/586) in favor of +using the object's `rac_deallocDisposable` directly. + +**To update:** + + * Replace uses of `-rac_didDeallocSignal` with `rac_willDeallocSignal`. + * Replace uses of `-rac_addDeallocDisposable:` by invoking `-addDisposable:` on + the object's `rac_deallocDisposable` instead. + +### Extensible queue-based schedulers + +`RACQueueScheduler` has been [exposed as a public +class](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/561), so consumers can create +their own scheduler implementations using GCD queues. + +The +[RACTargetQueueScheduler](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/561) +subclass replaces the `+schedulerWithQueue:name:` method. + +**To update:** + +Replace uses of `+schedulerWithQueue:name:` with `-[RACTargetQueueScheduler initWithName:targetQueue:]`. + +### GCD time values replaced with NSDate + +`NSDate` now [replaces](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/664) +`dispatch_time_t` values in `RACScheduler`, because dates are easier to use, more +convertible to other formats, and can be used to implement a [virtualized time +scheduler](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/171). + +**To update:** + +Replace `dispatch_time_t` calculations with `NSDate`. + +### Windows and numbered buffers removed + +`-windowWithStart:close:` and `-buffer:` have been +[removed](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/616) because +they're not well-tested, and their functionality can be achieved with other +operators. + +`-bufferWithTime:` is still supported. + +**To update:** + + * Refactor uses of `-windowWithStart:close:` with different patterns. + * Replace uses of `-buffer:` with [take, collect, and + repeat](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587). + +### NSTask extension removed + +`NSTask+RACSupport` has been +[removed](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/556), because it +was buggy and unsupported. + +**To update:** + +Use a vanilla `NSTask`, and send events onto `RACSubject`s instead. + +### RACSubscriber class now private + +The `RACSubscriber` class (not to be confused with the protocol) should never be +used directly, so it has been +[hidden](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/584). + +**To update:** + +Replace uses of `RACSubscriber` with `id<RACSubscriber>` or `RACSubject`. + +## Additions and improvements + +### Commands for UIButton + +`UIButton` now has a [rac_command +property](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/558). + +Any command set will be executed when the button is tapped, and the button will +be disabled whenever the command is unable to execute. + +### Signal for UIActionSheet button clicks + +`UIActionSheet` now has a [rac_buttonClickedSignal +property](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/611), which will +fire whenever one of the sheet's buttons is clicked. + +### Better documentation for asynchronous backtraces + +Documentation has been +[added](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/680) to +`RACBacktrace` explaining how to start collecting backtraces and print them out +in the debugger. + +The `+captureBacktrace…` methods have been renamed to `+backtrace…`, and +`+printBacktrace` has been removed in favor of using `po [RACBacktrace backtrace]` +from the debugger. + +The `rac_` GCD functions which collect backtraces have also been exposed, +primarily for use on iOS. + +### Fixed libextobjc duplicated symbols + +If [libextobjc](https://github.com/jspahrsummers/libextobjc) was used in +a project that statically linked ReactiveCocoa, duplicate symbol errors could +result. + +To avoid this issue, RAC now +[renames](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/612) the +libextobjc symbols that it uses. + +### Bindings for UIKit classes + +RACChannel interfaces have been [added](https://github.com/ReactiveCocoa/pull/686) +to many UIKit classes, greatly simplifying glue code between your models and views. + +For example, assuming you want to bind a `person` model's `name` property: + +```objc +UITextField *nameField = ...; +RACChannelTerminal *nameTerminal = RACChannelTo(model, name); +RACChannelTerminal *nameFieldTerminal = [nameField rac_newTextChannel]; +[nameTerminal subscribe:nameFieldTerminal]; +[nameFieldTerminal subscribe:nameTerminal]; +``` + +You may also bind multiple controls to the same property, for example a UISlider for +coarse editing and a UIStepper for fine-grained editing. + +### Signal subscription side effects + +`RACSignal` now has the +[-initially:](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/685) +operator, which executes a given block each time the signal is subscribed to. +This is symmetric to `-finally:`. + +### Test scheduler + +`RACTestScheduler` is a [new kind](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/716) of scheduler that +virtualizes time. Enqueued blocks can be stepped through at any pace, no matter +how far in the future they're scheduled for, making it easy to test time-based +behavior without actually waiting in unit tests.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9956451 --- /dev/null +++ b/CONTRIBUTING.md
@@ -0,0 +1,23 @@ +We love that you're interested in contributing to this project! + +To make the process as painless as possible, we have just a couple of guidelines +that should make life easier for everyone involved. + +## Prefer Pull Requests + +If you know exactly how to implement the feature being suggested or fix the bug +being reported, please open a pull request instead of an issue. Pull requests are easier than +patches or inline code blocks for discussing and merging the changes. + +If you can't make the change yourself, please open an issue after making sure +that one isn't already logged. + +## Contributing Code + +Fork this repository, make it awesomer (preferably in a branch named for the +topic), send a pull request! + +All code contributions should match our [coding +conventions](https://github.com/github/objective-c-conventions). + +Thanks for contributing! :boom::camel:
diff --git a/Documentation/BasicOperators.md b/Documentation/BasicOperators.md new file mode 100644 index 0000000..03a4ca0 --- /dev/null +++ b/Documentation/BasicOperators.md
@@ -0,0 +1,364 @@ +# Basic Operators + +This document explains some of the most common operators used in ReactiveCocoa, +and includes examples demonstrating their use. + +Operators that apply to [sequences][Sequences] _and_ [signals][Signals] are +known as [stream][Streams] operators. + +**[Performing side effects with signals](#performing-side-effects-with-signals)** + + 1. [Subscription](#subscription) + 1. [Injecting effects](#injecting-effects) + +**[Transforming streams](#transforming-streams)** + + 1. [Mapping](#mapping) + 1. [Filtering](#filtering) + +**[Combining streams](#combining-streams)** + + 1. [Concatenating](#concatenating) + 1. [Flattening](#flattening) + 1. [Mapping and flattening](#mapping-and-flattening) + +**[Combining signals](#combining-signals)** + + 1. [Sequencing](#sequencing) + 1. [Merging](#merging) + 1. [Combining latest values](#combining-latest-values) + 1. [Switching](#switching) + +## Performing side effects with signals + +Most signals start out "cold," which means that they will not do any work until +[subscription](#subscription). + +Upon subscription, a signal or its [subscribers][Subscription] can perform _side +effects_, like logging to the console, making a network request, updating the +user interface, etc. + +Side effects can also be [injected](#injecting-effects) into a signal, where +they won't be performed immediately, but will instead take effect with each +subscription later. + +### Subscription + +The [-subscribe…][RACSignal] methods give you access to the current and future values in a signal: + +```objc +RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; + +// Outputs: A B C D E F G H I +[letters subscribeNext:^(NSString *x) { + NSLog(@"%@", x); +}]; +``` + +For a cold signal, side effects will be performed once _per subscription_: + +```objc +__block unsigned subscriptions = 0; + +RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + subscriptions++; + [subscriber sendCompleted]; + return nil; +}]; + +// Outputs: +// subscription 1 +[loggingSignal subscribeCompleted:^{ + NSLog(@"subscription %u", subscriptions); +}]; + +// Outputs: +// subscription 2 +[loggingSignal subscribeCompleted:^{ + NSLog(@"subscription %u", subscriptions); +}]; +``` + +This behavior can be changed using a [connection][Connections]. + +### Injecting effects + +The [-do…][RACSignal+Operations] methods add side effects to a signal without actually +subscribing to it: + +```objc +__block unsigned subscriptions = 0; + +RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + subscriptions++; + [subscriber sendCompleted]; + return nil; +}]; + +// Does not output anything yet +loggingSignal = [loggingSignal doCompleted:^{ + NSLog(@"about to complete subscription %u", subscriptions); +}]; + +// Outputs: +// about to complete subscription 1 +// subscription 1 +[loggingSignal subscribeCompleted:^{ + NSLog(@"subscription %u", subscriptions); +}]; +``` + +## Transforming streams + +These operators transform a single stream into a new stream. + +### Mapping + +The [-map:][RACStream] method is used to transform the values in a stream, and +create a new stream with the results: + +```objc +RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; + +// Contains: AA BB CC DD EE FF GG HH II +RACSequence *mapped = [letters map:^(NSString *value) { + return [value stringByAppendingString:value]; +}]; +``` + +### Filtering + +The [-filter:][RACStream] method uses a block to test each value, including it +into the resulting stream only if the test passes: + +```objc +RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; + +// Contains: 2 4 6 8 +RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) { + return (value.intValue % 2) == 0; +}]; +``` + +## Combining streams + +These operators combine multiple streams into a single new stream. + +### Concatenating + +The [-concat:][RACStream] method appends one stream's values to another: + +```objc +RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; +RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; + +// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 +RACSequence *concatenated = [letters concat:numbers]; +``` + +### Flattening + +The [-flatten][RACStream] operator is applied to a stream-of-streams, and +combines their values into a single new stream. + +Sequences are [concatenated](#concatenating): + +```objc +RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; +RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; +RACSequence *sequenceOfSequences = @[ letters, numbers ].rac_sequence; + +// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 +RACSequence *flattened = [sequenceOfSequences flatten]; +``` + +Signals are [merged](#merging): + +```objc +RACSubject *letters = [RACSubject subject]; +RACSubject *numbers = [RACSubject subject]; +RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:letters]; + [subscriber sendNext:numbers]; + [subscriber sendCompleted]; + return nil; +}]; + +RACSignal *flattened = [signalOfSignals flatten]; + +// Outputs: A 1 B C 2 +[flattened subscribeNext:^(NSString *x) { + NSLog(@"%@", x); +}]; + +[letters sendNext:@"A"]; +[numbers sendNext:@"1"]; +[letters sendNext:@"B"]; +[letters sendNext:@"C"]; +[numbers sendNext:@"2"]; +``` + +### Mapping and flattening + +[Flattening](#flattening) isn't that interesting on its own, but understanding +how it works is important for [-flattenMap:][RACStream]. + +`-flattenMap:` is used to transform each of a stream's values into _a new +stream_. Then, all of the streams returned will be flattened down into a single +stream. In other words, it's [-map:](#mapping) followed by [-flatten](#flattening). + +This can be used to extend or edit sequences: + +```objc +RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; + +// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 +RACSequence *extended = [numbers flattenMap:^(NSString *num) { + return @[ num, num ].rac_sequence; +}]; + +// Contains: 1_ 3_ 5_ 7_ 9_ +RACSequence *edited = [numbers flattenMap:^(NSString *num) { + if (num.intValue % 2 == 0) { + return [RACSequence empty]; + } else { + NSString *newNum = [num stringByAppendingString:@"_"]; + return [RACSequence return:newNum]; + } +}]; +``` + +Or create multiple signals of work which are automatically recombined: + +```objc +RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; + +[[letters + flattenMap:^(NSString *letter) { + return [database saveEntriesForLetter:letter]; + }] + subscribeCompleted:^{ + NSLog(@"All database entries saved successfully."); + }]; +``` + +## Combining signals + +These operators combine multiple signals into a single new [RACSignal][]. + +### Sequencing + +[-then:][RACSignal+Operations] starts the original signal, +waits for it to complete, and then only forwards the values from a new signal: + +```objc +RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; + +// The new signal only contains: 1 2 3 4 5 6 7 8 9 +// +// But when subscribed to, it also outputs: A B C D E F G H I +RACSignal *sequenced = [[letters + doNext:^(NSString *letter) { + NSLog(@"%@", letter); + }] + then:^{ + return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal; + }]; +``` + +This is most useful for executing all the side effects of one signal, then +starting another, and only returning the second signal's values. + +### Merging + +The [+merge:][RACSignal+Operations] method will forward the values from many +signals into a single stream, as soon as those values arrive: + +```objc +RACSubject *letters = [RACSubject subject]; +RACSubject *numbers = [RACSubject subject]; +RACSignal *merged = [RACSignal merge:@[ letters, numbers ]]; + +// Outputs: A 1 B C 2 +[merged subscribeNext:^(NSString *x) { + NSLog(@"%@", x); +}]; + +[letters sendNext:@"A"]; +[numbers sendNext:@"1"]; +[letters sendNext:@"B"]; +[letters sendNext:@"C"]; +[numbers sendNext:@"2"]; +``` + +### Combining latest values + +The [+combineLatest:][RACSignal+Operations] and `+combineLatest:reduce:` methods +will watch multiple signals for changes, and then send the latest values from +_all_ of them when a change occurs: + +```objc +RACSubject *letters = [RACSubject subject]; +RACSubject *numbers = [RACSubject subject]; +RACSignal *combined = [RACSignal + combineLatest:@[ letters, numbers ] + reduce:^(NSString *letter, NSString *number) { + return [letter stringByAppendingString:number]; + }]; + +// Outputs: B1 B2 C2 C3 +[combined subscribeNext:^(id x) { + NSLog(@"%@", x); +}]; + +[letters sendNext:@"A"]; +[letters sendNext:@"B"]; +[numbers sendNext:@"1"]; +[numbers sendNext:@"2"]; +[letters sendNext:@"C"]; +[numbers sendNext:@"3"]; +``` + +Note that the combined signal will only send its first value when all of the +inputs have sent at least one. In the example above, `@"A"` was never +forwarded because `numbers` had not sent a value yet. + +### Switching + +The [-switchToLatest][RACSignal+Operations] operator is applied to +a signal-of-signals, and always forwards the values from the latest signal: + +```objc +RACSubject *letters = [RACSubject subject]; +RACSubject *numbers = [RACSubject subject]; +RACSubject *signalOfSignals = [RACSubject subject]; + +RACSignal *switched = [signalOfSignals switchToLatest]; + +// Outputs: A B 1 D +[switched subscribeNext:^(NSString *x) { + NSLog(@"%@", x); +}]; + +[signalOfSignals sendNext:letters]; +[letters sendNext:@"A"]; +[letters sendNext:@"B"]; + +[signalOfSignals sendNext:numbers]; +[letters sendNext:@"C"]; +[numbers sendNext:@"1"]; + +[signalOfSignals sendNext:letters]; +[numbers sendNext:@"2"]; +[letters sendNext:@"D"]; +``` + +[Connections]: FrameworkOverview.md#connections +[RACSequence]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h +[RACSignal]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h +[RACSignal+Operations]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h +[RACStream]: ../ReactiveCocoaFramework/ReactiveCocoa/RACStream.h +[Sequences]: FrameworkOverview.md#sequences +[Signals]: FrameworkOverview.md#signals +[Streams]: FrameworkOverview.md#streams +[Subscription]: FrameworkOverview.md#subscription
diff --git a/Documentation/DesignGuidelines.md b/Documentation/DesignGuidelines.md new file mode 100644 index 0000000..106e845 --- /dev/null +++ b/Documentation/DesignGuidelines.md
@@ -0,0 +1,751 @@ +# Design Guidelines + +This document contains guidelines for projects that want to make use of +ReactiveCocoa. The content here is heavily inspired by the [Rx Design +Guidelines](http://blogs.msdn.com/b/rxteam/archive/2010/10/28/rx-design-guidelines.aspx). + +This document assumes basic familiarity +with the features of ReactiveCocoa. The [Framework Overview][] is a better +resource for getting up to speed on the functionality provided by RAC. + +**[The RACSequence contract](#the-racsequence-contract)** + + 1. [Evaluation occurs lazily by default](#evaluation-occurs-lazily-by-default) + 1. [Evaluation blocks the caller](#evaluation-blocks-the-caller) + 1. [Side effects occur only once](#side-effects-occur-only-once) + +**[The RACSignal contract](#the-racsignal-contract)** + + 1. [Signal events are serialized](#signal-events-are-serialized) + 1. [Subscription will always occur on a scheduler](#subscription-will-always-occur-on-a-scheduler) + 1. [Errors are propagated immediately](#errors-are-propagated-immediately) + 1. [Side effects occur for each subscription](#side-effects-occur-for-each-subscription) + 1. [Subscriptions are automatically disposed upon completion or error](#subscriptions-are-automatically-disposed-upon-completion-or-error) + 1. [Disposal cancels in-progress work and cleans up resources](#disposal-cancels-in-progress-work-and-cleans-up-resources) + +**[Best practices](#best-practices)** + + 1. [Use descriptive declarations for methods and properties that return a signal](#use-descriptive-declarations-for-methods-and-properties-that-return-a-signal) + 1. [Indent stream operations consistently](#indent-stream-operations-consistently) + 1. [Use the same type for all the values of a stream](#use-the-same-type-for-all-the-values-of-a-stream) + 1. [Avoid retaining streams for too long](#avoid-retaining-streams-for-too-long) + 1. [Process only as much of a stream as needed](#process-only-as-much-of-a-stream-as-needed) + 1. [Deliver signal events onto a known scheduler](#deliver-signal-events-onto-a-known-scheduler) + 1. [Switch schedulers in as few places as possible](#switch-schedulers-in-as-few-places-as-possible) + 1. [Make the side effects of a signal explicit](#make-the-side-effects-of-a-signal-explicit) + 1. [Share the side effects of a signal by multicasting](#share-the-side-effects-of-a-signal-by-multicasting) + 1. [Debug streams by giving them names](#debug-streams-by-giving-them-names) + 1. [Avoid explicit subscriptions and disposal](#avoid-explicit-subscriptions-and-disposal) + 1. [Avoid using subjects when possible](#avoid-using-subjects-when-possible) + +**[Implementing new operators](#implementing-new-operators)** + + 1. [Prefer building on RACStream methods](#prefer-building-on-racstream-methods) + 1. [Compose existing operators when possible](#compose-existing-operators-when-possible) + 1. [Avoid introducing concurrency](#avoid-introducing-concurrency) + 1. [Cancel work and clean up all resources in a disposable](#cancel-work-and-clean-up-all-resources-in-a-disposable) + 1. [Do not block in an operator](#do-not-block-in-an-operator) + 1. [Avoid stack overflow from deep recursion](#avoid-stack-overflow-from-deep-recursion) + +## The RACSequence contract + +[RACSequence][] is a _pull-driven_ stream. Sequences behave similarly to +built-in collections, but with a few unique twists. + +### Evaluation occurs lazily by default + +Sequences are evaluated lazily by default. For example, in this sequence: + +```objc +NSArray *strings = @[ @"A", @"B", @"C" ]; +RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) { + return [str stringByAppendingString:@"_"]; +}]; +``` + +… no string appending is actually performed until the values of the sequence are +needed. Accessing `sequence.head` will perform the concatenation of `A_`, +accessing `sequence.tail.head` will perform the concatenation of `B_`, and so +on. + +This generally avoids performing unnecessary work (since values that are never +used are never calculated), but means that sequence processing [should be +limited only to what's actually +needed](#process-only-as-much-of-a-stream-as-needed). + +Once evaluated, the values in a sequence are memoized and do not need to be +recalculated. Accessing `sequence.head` multiple times will only do the work of +one string concatenation. + +If lazy evaluation is undesirable – for instance, because limiting memory usage +is more important than avoiding unnecessary work – the +[eagerSequence][RACSequence] property can be used to force a sequence (and any +sequences derived from it afterward) to evaluate eagerly. + +### Evaluation blocks the caller + +Regardless of whether a sequence is lazy or eager, evaluation of any part of +a sequence will block the calling thread until completed. This is necessary +because values must be synchronously retrieved from a sequence. + +If evaluating a sequence is expensive enough that it might block the thread for +a significant amount of time, consider creating a signal with +[-signalWithScheduler:][RACSequence] and using that instead. + +### Side effects occur only once + +When the block passed to a sequence operator involves side effects, it is +important to realize that those side effects will only occur once per value +– namely, when the value is evaluated: + +```objc +NSArray *strings = @[ @"A", @"B", @"C" ]; +RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) { + NSLog(@"%@", str); + return [str stringByAppendingString:@"_"]; +}]; + +// Logs "A" during this call. +NSString *concatA = sequence.head; + +// Logs "B" during this call. +NSString *concatB = sequence.tail.head; + +// Does not log anything. +NSString *concatB2 = sequence.tail.head; + +RACSequence *derivedSequence = [sequence map:^(NSString *str) { + return [@"_" stringByAppendingString:str]; +}]; + +// Still does not log anything, because "B_" was already evaluated, and the log +// statement associated with it will never be re-executed. +NSString *concatB3 = derivedSequence.tail.head; +``` + +## The RACSignal contract + +[RACSignal][] is a _push-driven_ stream with a focus on asynchronous event +delivery through _subscriptions_. For more information about signals and +subscriptions, see the [Framework Overview][]. + +### Signal events are serialized + +A signal may choose to deliver its events on any thread. Consecutive events are +even allowed to arrive on different threads or schedulers, unless explicitly +[delivered onto a particular +scheduler](#deliver-signal-events-onto-a-known-scheduler). + +However, RAC guarantees that no two signal events will ever arrive concurrently. +While an event is being processed, no other events will be delivered. The +senders of any other events will be forced to wait until the current event has +been handled. + +Most notably, this means that the blocks passed to +[-subscribeNext:error:completed:][RACSignal] do not need to be synchronized with +respect to each other, because they will never be invoked simultaneously. + +### Subscription will always occur on a scheduler + +To ensure consistent behavior for the `+createSignal:` and `-subscribe:` +methods, each [RACSignal][] subscription is guaranteed to take place on +a valid [RACScheduler][]. + +If the subscriber's thread already has a [+currentScheduler][RACScheduler], +scheduling takes place immediately; otherwise, scheduling occurs as soon as +possible on a background scheduler. Note that the main thread is always +associated with the [+mainThreadScheduler][RACScheduler], so subscription will +always be immediate there. + +See the documentation for [-subscribe:][RACSignal] for more information. + +### Errors are propagated immediately + +In RAC, `error` events have exception semantics. When an error is sent on +a signal, it will be immediately forwarded to all dependent signals, causing the +entire chain to terminate. + +[Operators][RACSignal+Operations] whose primary purpose is to change +error-handling behavior – like `-catch:`, `-catchTo:`, or `-materialize` – are +obviously not subject to this rule. + +### Side effects occur for each subscription + +Each new subscription to a [RACSignal][] will trigger its side effects. This +means that any side effects will happen as many times as subscriptions to the +signal itself. + +Consider this example: +```objc +__block int aNumber = 0; + +// Signal that will have the side effect of incrementing `aNumber` block +// variable for each subscription before sending it. +RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + aNumber++; + [subscriber sendNext:@(aNumber)]; + [subscriber sendCompleted]; + return nil; +}]; + +// This will print "subscriber one: 1" +[aSignal subscribeNext:^(id x) { + NSLog(@"subscriber one: %@", x); +}]; + +// This will print "subscriber two: 2" +[aSignal subscribeNext:^(id x) { + NSLog(@"subscriber two: %@", x); +}]; +``` + +Side effects are repeated for each subscription. The same applies to +[stream][RACStream] and [signal][RACSignal+Operations] operators: + +```objc +__block int missilesToLaunch = 0; + +// Signal that will have the side effect of changing `missilesToLaunch` on +// subscription. +RACSignal *processedSignal = [[RACSignal + return:@"missiles"] + map:^(id x) { + missilesToLaunch++; + return [NSString stringWithFormat:@"will launch %d %@", missilesToLaunch, x]; + }]; + +// This will print "First will launch 1 missiles" +[processedSignal subscribeNext:^(id x) { + NSLog(@"First %@", x); +}]; + +// This will print "Second will launch 2 missiles" +[processedSignal subscribeNext:^(id x) { + NSLog(@"Second %@", x); +}]; +``` + +To suppress this behavior and have multiple subscriptions to a signal execute +its side effects only once, a signal can be +[multicasted](#share-the-side-effects-of-a-signal-by-multicasting). + +Side effects can be insidious and produce problems that are difficult to +diagnose. For this reason it is suggested to +[make side effects explicit](#make-the-side-effects-of-a-signal-explicit) when +possible. + +### Subscriptions are automatically disposed upon completion or error + +When a [subscriber][RACSubscriber] is sent a `completed` or `error` event, the +associated subscription will automatically be disposed. This behavior usually +eliminates the need to manually dispose of subscriptions. + +See the [Memory Management][] document for more information about signal +lifetime. + +### Disposal cancels in-progress work and cleans up resources + +When a subscription is disposed, manually or automatically, any in-progress or +outstanding work associated with that subscription is gracefully cancelled as +soon as possible, and any resources associated with the subscription are cleaned +up. + +Disposing of the subscription to a signal representing a file upload, for +example, would cancel any in-flight network request, and free the file data from +memory. + +## Best practices + +The following recommendations are intended to help keep RAC-based code +predictable, understandable, and performant. + +They are, however, only guidelines. Use best judgement when determining whether +to apply the recommendations here to a given piece of code. + +### Use descriptive declarations for methods and properties that return a signal + +When a method or property has a return type of [RACSignal][], it can be +difficult to understand the signal's semantics at a glance. + +There are three key questions that can inform a declaration: + + 1. Is the signal _hot_ (already activated by the time it's returned to the + caller) or _cold_ (activated when subscribed to)? + 1. Will the signal include zero, one, or more values? + 1. Does the signal have side effects? + +**Hot signals without side effects** should typically be properties instead of +methods. The use of a property indicates that no initialization is needed before +subscribing to the signal's events, and that additional subscribers will not +change the semantics. Signal properties should usually be named after events +(e.g., `textChanged`). + +**Cold signals without side effects** should be returned from methods that have +noun-like names (e.g., `-currentText`). A method declaration indicates that the +signal might not be kept around, hinting that work is performed at the time of +subscription. If the signal sends multiple values, the noun should be pluralized +(e.g., `-currentModels`). + +**Signals with side effects** should be returned from methods that have +verb-like names (e.g., `-logIn`). The verb indicates that the method is not +idempotent and that callers must be careful to call it only when the side +effects are desired. If the signal will send one or more values, include a noun +that describes them (e.g., `-loadConfiguration`, `-fetchLatestEvents`). + +### Indent stream operations consistently + +It's easy for stream-heavy code to become very dense and confusing if not +properly formatted. Use consistent indentation to highlight where chains of +streams begin and end. + +When invoking a single method upon a stream, no additional indentation is +necessary (block arguments aside): + +```objc +RACStream *result = [stream startWith:@0]; + +RACStream *result2 = [stream map:^(NSNumber *value) { + return @(value.integerValue + 1); +}]; +``` + +When transforming the same stream multiple times, ensure that all of the +steps are aligned. Complex operators like [+zip:reduce:][RACStream] or +[+combineLatest:reduce:][RACSignal+Operations] may be split over multiple lines +for readability: + +```objc +RACStream *result = [[[RACStream + zip:@[ firstStream, secondStream ] + reduce:^(NSNumber *first, NSNumber *second) { + return @(first.integerValue + second.integerValue); + }] + filter:^ BOOL (NSNumber *value) { + return value.integerValue >= 0; + }] + map:^(NSNumber *value) { + return @(value.integerValue + 1); + }]; +``` + +Of course, streams nested within block arguments should start at the natural +indentation of the block: + +```objc +[[signal + then:^{ + @strongify(self); + + return [[self + doSomethingElse] + catch:^(NSError *error) { + @strongify(self); + [self presentError:error]; + + return [RACSignal empty]; + }]; + }] + subscribeCompleted:^{ + NSLog(@"All done."); + }]; +``` + +### Use the same type for all the values of a stream + +[RACStream][] (and, by extension, [RACSignal][] and [RACSequence][]) allows +streams to be composed of heterogenous objects, just like Cocoa collections do. +However, using different object types within the same stream complicates the use +of operators and +puts an additional burden on any consumers of that stream, who must be careful to +only invoke supported methods. + +Whenever possible, streams should only contain objects of the same type. + +### Avoid retaining streams for too long + +Retaining any [RACStream][] longer than it's needed will cause any dependencies +to be retained as well, potentially keeping memory usage much higher than it +would be otherwise. + +A [RACSequence][] should be retained only for as long as the `head` of the +sequence is needed. If the head will no longer be used, retain the `tail` of the +node instead of the node itself. + +See the [Memory Management][] guide for more information on object lifetime. + +### Process only as much of a stream as needed + +As well as [consuming additional +memory](#avoid-retaining-streams-for-too-long), unnecessarily +keeping a stream or [RACSignal][] subscription alive can result in increased CPU +usage, as unnecessary work is performed for results that will never be used. + +If only a certain number of values are needed from a stream, the +[-take:][RACStream] operator can be used to retrieve only that many values, and +then automatically terminate the stream immediately thereafter. + +Operators like `-take:` and [-takeUntil:][RACSignal+Operations] automatically propagate cancellation +up the stack as well. If nothing else needs the rest of the values, any +dependencies will be terminated too, potentially saving a significant amount of +work. + +### Deliver signal events onto a known scheduler + +When a signal is returned from a method, or combined with such a signal, it can +be difficult to know which thread events will be delivered upon. Although +events are [guaranteed to be serial](#signal-events-are-serialized), sometimes +stronger guarantees are needed, like when performing UI updates (which must +occur on the main thread). + +Whenever such a guarantee is important, the [-deliverOn:][RACSignal+Operations] +operator should be used to force a signal's events to arrive on a specific +[RACScheduler][]. + +### Switch schedulers in as few places as possible + +Notwithstanding the above, events should only be delivered to a specific +[scheduler][RACScheduler] when absolutely necessary. Switching schedulers can +introduce unnecessary delays and cause an increase in CPU load. + +Generally, the use of [-deliverOn:][RACSignal+Operations] should be restricted +to the end of a signal chain – e.g., before subscription, or before the values +are bound to a property. + +### Make the side effects of a signal explicit + +As much as possible, [RACSignal][] side effects should be avoided, because +subscribers may find the [behavior of side +effects](#side-effects-occur-for-each-subscription) unexpected. + +However, because Cocoa is predominantly imperative, it is sometimes useful to +perform side effects when signal events occur. Although most [RACStream][] and +[RACSignal][RACSignal+Operations] operators accept arbitrary blocks (which can +contain side effects), the use of `-doNext:`, `-doError:`, and `-doCompleted:` +will make side effects more explicit and self-documenting: + +```objc +NSMutableArray *nexts = [NSMutableArray array]; +__block NSError *receivedError = nil; +__block BOOL success = NO; + +RACSignal *bookkeepingSignal = [[[valueSignal + doNext:^(id x) { + [nexts addObject:x]; + }] + doError:^(NSError *error) { + receivedError = error; + }] + doCompleted:^{ + success = YES; + }]; + +RAC(self, value) = bookkeepingSignal; +``` + +### Share the side effects of a signal by multicasting + +[Side effects occur for each +subscription](#side-effects-occur-for-each-subscription) by default, but there +are certain situations where side effects should only occur once – for example, +a network request typically should not be repeated when a new subscriber is +added. + +The `-publish` and `-multicast:` operators of [RACSignal][RACSignal+Operations] +allow a single subscription to be shared to any number of subscribers by using +a [RACMulticastConnection][]: + +```objc +// This signal starts a new request on each subscription. +RACSignal *networkRequest = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + AFHTTPRequestOperation *operation = [client + HTTPRequestOperationWithRequest:request + success:^(AFHTTPRequestOperation *operation, id response) { + [subscriber sendNext:response]; + [subscriber sendCompleted]; + } + failure:^(AFHTTPRequestOperation *operation, NSError *error) { + [subscriber sendError:error]; + }]; + + [client enqueueHTTPRequestOperation:operation]; + return [RACDisposable disposableWithBlock:^{ + [operation cancel]; + }]; +}]; + +// Starts a single request, no matter how many subscriptions `connection.signal` +// gets. This is equivalent to the -replay operator, or similar to +// +startEagerlyWithScheduler:block:. +RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]]; +[connection connect]; + +[connection.signal subscribeNext:^(id response) { + NSLog(@"subscriber one: %@", response); +}]; + +[connection.signal subscribeNext:^(id response) { + NSLog(@"subscriber two: %@", response); +}]; +``` + +### Debug streams by giving them names + +Every [RACStream][] has a `name` property to assist with debugging. A stream's +`-description` includes its name, and all operators provided by RAC will +automatically add to the name. This usually makes it possible to identify +a stream from its default name alone. + +For example, this snippet: + +```objc +RACSignal *signal = [[[RACObserve(self, username) + distinctUntilChanged] + take:3] + filter:^(NSString *newUsername) { + return [newUsername isEqualToString:@"joshaber"]; + }]; + +NSLog(@"%@", signal); +``` + +… would log a name similar to `[[[RACObserve(self, username)] -distinctUntilChanged] +-take: 3] -filter:`. + +Names can also be manually applied by using [-setNameWithFormat:][RACStream]. + +[RACSignal][] also offers `-logNext`, `-logError`, +`-logCompleted`, and `-logAll` methods, which will automatically log signal +events as they occur, and include the name of the signal in the messages. This +can be used to conveniently inspect a signal in real-time. + +### Avoid explicit subscriptions and disposal + +Although [-subscribeNext:error:completed:][RACSignal] and its variants are the +most basic way to process a signal, their use can complicate code by +being less declarative, encouraging the use of side effects, and potentially +duplicating built-in functionality. + +Likewise, explicit use of the [RACDisposable][] class can quickly lead to +a rat's nest of resource management and cleanup code. + +There are almost always higher-level patterns that can be used instead of manual +subscriptions and disposal: + + * The [RAC()][RAC] or [RACChannelTo()][RACChannelTo] macros can be used to bind + a signal to a property, instead of performing manual updates when changes + occur. + * The [-rac_liftSelector:withSignals:][NSObject+RACLifting] method can be used + to automatically invoke a selector when one or more signals fire. + * Operators like [-takeUntil:][RACSignal+Operations] can be used to + automatically dispose of a subscription when an event occurs (like a "Cancel" + button being pressed in the UI). + +Generally, the use of built-in [stream][RACStream] and +[signal][RACSignal+Operations] operators will lead to simpler and less +error-prone code than replicating the same behaviors in a subscription callback. + +### Avoid using subjects when possible + +[Subjects][] are a powerful tool for bridging imperative code +into the world of signals, but, as the "mutable variables" of RAC, they can +quickly lead to complexity when overused. + +Since they can be manipulated from anywhere, at any time, subjects often break +the linear flow of stream processing and make logic much harder to follow. They +also don't support meaningful +[disposal](#disposal-cancels-in-progress-work-and-cleans-up-resources), which +can result in unnecessary work. + +Subjects can usually be replaced with other patterns from ReactiveCocoa: + + * Instead of feeding initial values into a subject, consider generating the + values in a [+createSignal:][RACSignal] block instead. + * Instead of delivering intermediate results to a subject, try combining the + output of multiple signals with operators like + [+combineLatest:][RACSignal+Operations] or [+zip:][RACStream]. + * Instead of using subjects to share results with multiple subscribers, + [multicast](#share-the-side-effects-of-a-signal-by-multicasting) a base + signal instead. + * Instead of implementing an action method which simply controls a subject, use + a [command][RACCommand] or + [-rac_signalForSelector:][NSObject+RACSelectorSignal] instead. + +When subjects _are_ necessary, they should almost always be the "base" input +for a signal chain, not used in the middle of one. + +## Implementing new operators + +RAC provides a long list of built-in operators for [streams][RACStream] and +[signals][RACSignal+Operations] that should cover most use cases; however, RAC +is not a closed system. It's entirely valid to implement additional operators +for specialized uses, or for consideration in ReactiveCocoa itself. + +Implementing a new operator requires a careful attention to detail and a focus +on simplicity, to avoid introducing bugs into the calling code. + +These guidelines cover some of the common pitfalls and help preserve the +expected API contracts. + +### Prefer building on RACStream methods + +[RACStream][] offers a simpler interface than [RACSequence][] and [RACSignal][], +and all stream operators are automatically applicable to sequences and signals +as well. + +For these reasons, new operators should be implemented using only [RACStream][] +methods whenever possible. The minimal required methods of the class, including +`-bind:`, `+zipWith:`, and `-concat:`, are quite powerful, and many tasks can +be accomplished without needing anything else. + +If a new [RACSignal][] operator needs to handle `error` and `completed` events, +consider using the [-materialize][RACSignal+Operations] method to bring the +events into the stream. All of the events of a materialized signal can be +manipulated by stream operators, which helps minimize the use of non-stream +operators. + +### Compose existing operators when possible + +Considerable thought has been put into the operators provided by RAC, and they +have been validated through automated tests and through their real world use in +other projects. An operator that has been written from scratch may not be as +robust, or might not handle a special case that the built-in operators are aware +of. + +To minimize duplication and possible bugs, use the provided operators as much as +possible in a custom operator implementation. Generally, there should be very +little code written from scratch. + +### Avoid introducing concurrency + +Concurrency is an extremely common source of bugs in programming. To minimize +the potential for deadlocks and race conditions, operators should not +concurrently perform their work. + +Callers always have the ability to subscribe or deliver events on a specific +[RACScheduler][], and RAC offers powerful ways to [parallelize +work][Parallelizing Independent Work] without making operators unnecessarily +complex. + +### Cancel work and clean up all resources in a disposable + +When implementing a signal with the [+createSignal:][RACSignal] method, the +provided block is expected to return a [RACDisposable][]. This disposable +should: + + * As soon as it is convenient, gracefully cancel any in-progress work that was + started by the signal. + * Immediately dispose of any subscriptions to other signals, thus triggering + their cancellation and cleanup code as well. + * Release any memory or other resources that were allocated by the signal. + +This helps fulfill [the RACSignal +contract](#disposal-cancels-in-progress-work-and-cleans-up-resources). + +### Do not block in an operator + +Stream operators should return a new stream more-or-less immediately. Any work +that the operator needs to perform should be part of evaluating the new stream, +_not_ part of the operator invocation itself. + +```objc +// WRONG! +- (RACSequence *)map:(id (^)(id))block { + RACSequence *result = [RACSequence empty]; + for (id obj in self) { + id mappedObj = block(obj); + result = [result concat:[RACSequence return:mappedObj]]; + } + + return result; +} + +// Right! +- (RACSequence *)map:(id (^)(id))block { + return [self flattenMap:^(id obj) { + id mappedObj = block(obj); + return [RACSequence return:mappedObj]; + }]; +} +``` + +This guideline can be safely ignored when the purpose of an operator is to +synchronously retrieve one or more values from a stream (like +[-first][RACSignal+Operations]). + +### Avoid stack overflow from deep recursion + +Any operator that might recurse indefinitely should use the +`-scheduleRecursiveBlock:` method of [RACScheduler][]. This method will +transform recursion into iteration instead, preventing a stack overflow. + +For example, this would be an incorrect implementation of +[-repeat][RACSignal+Operations], due to its potential to overflow the call stack +and cause a crash: + +```objc +- (RACSignal *)repeat { + return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + __block void (^resubscribe)(void) = ^{ + RACDisposable *disposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + resubscribe(); + }]; + + [compoundDisposable addDisposable:disposable]; + }; + + return compoundDisposable; + }]; +} +``` + +By contrast, this version will avoid a stack overflow: + +```objc +- (RACSignal *)repeat { + return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + RACScheduler *scheduler = RACScheduler.currentScheduler ?: [RACScheduler scheduler]; + RACDisposable *disposable = [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) { + RACDisposable *disposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + reschedule(); + }]; + + [compoundDisposable addDisposable:disposable]; + }]; + + [compoundDisposable addDisposable:disposable]; + return compoundDisposable; + }]; +} +``` + +[Framework Overview]: FrameworkOverview.md +[Memory Management]: MemoryManagement.md +[NSObject+RACLifting]: ../ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h +[NSObject+RACSelectorSignal]: ../ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h +[RAC]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h +[RACChannelTo]: ../ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.h +[RACCommand]: ../ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h +[RACDisposable]: ../ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h +[RACEvent]: ../ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h +[RACMulticastConnection]: ../ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h +[RACObserve]: ../ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.h +[RACScheduler]: ../ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h +[RACSequence]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h +[RACSignal]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h +[RACSignal+Operations]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h +[RACStream]: ../ReactiveCocoaFramework/ReactiveCocoa/RACStream.h +[RACSubscriber]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h +[Subjects]: FrameworkOverview.md#subjects +[Parallelizing Independent Work]: ../README.md#parallelizing-independent-work
diff --git a/Documentation/DifferencesFromRx.md b/Documentation/DifferencesFromRx.md new file mode 100644 index 0000000..6e94e64 --- /dev/null +++ b/Documentation/DifferencesFromRx.md
@@ -0,0 +1,59 @@ +# Differences from Rx + +ReactiveCocoa (RAC) is significantly inspired by .NET's [Reactive +Extensions](http://msdn.microsoft.com/en-us/data/gg577609.aspx) (Rx), but it is not +a direct port. Some concepts or interfaces presented in RAC may be initially +confusing to a developer already familiar with Rx, but it's usually possible to +express the same algorithms. + +Some of the differences, like the naming of methods and classes, are meant to +keep RAC in line with existing Cocoa conventions. Other differences are intended +as improvements over Rx, or may be inspired by other functional reactive +programming paradigms (like the [Elm programming +language](http://elm-lang.org)). + +Here, we'll attempt to document the high-level differences between RAC and Rx. + +## Interfaces + +RAC does not offer protocols that correspond to the `IEnumerable` and +`IObservable` interfaces in .NET. Instead, the functionality is covered by three +main classes: + + * **[RACStream](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoaFramework/ReactiveCocoa/RACStream.h)** + is an abstract class that implements stream operations using a few basic + primitives. The equivalents to generic LINQ operators can generally be found + on this class. + * **[RACSignal](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h)** + is a concrete subclass of `RACStream` that implements a _push-driven_ stream, + much like `IObservable`. Time-based operators, or methods dealing with the + `completed` and `error` events, can be found on this class or in the + [RACSignal+Operations](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoaFramework/ReactiveCocoa/RACSignal%2BOperations.h) + category upon it. + * **[RACSequence](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h)** + is a concrete subclass of `RACStream` that implements a _pull-driven_ stream, + much like `IEnumerable`. + +## Names of Stream Operations + +RAC generally uses LINQ-style naming for its stream methods. Most of the +exceptions are inspired by significantly better alternatives in Haskell or Elm. + +Notable differences include: + + * `-map:` instead of `Select` + * `-filter:` instead of `Where` + * `-flatten` instead of `Merge` + * `-flattenMap:` instead of `SelectMany` + +LINQ operators that go by different names in RAC (but behave more or less +equivalently) will be referenced from method documentation, like so: + +```objc +// Maps `block` across the values in the receiver. +// +// This corresponds to the `Select` method in Rx. +// +// Returns a new stream with the mapped values. +- (instancetype)map:(id (^)(id value))block; +```
diff --git a/Documentation/FrameworkOverview.md b/Documentation/FrameworkOverview.md new file mode 100644 index 0000000..174303d --- /dev/null +++ b/Documentation/FrameworkOverview.md
@@ -0,0 +1,216 @@ +# Framework Overview + +This document contains a high-level description of the different components +within the ReactiveCocoa framework, and an attempt to explain how they work +together and divide responsibilities. This is meant to be a starting point for +learning about new modules and finding more specific documentation. + +For examples and help understanding how to use RAC, see the [README][] or +the [Design Guidelines][]. + +## Streams + +A **stream**, represented by the [RACStream][] abstract class, is any series of +object values. + +Values may be available immediately or in the future, but must be retrieved +sequentially. There is no way to retrieve the second value of a stream without +evaluating or waiting for the first value. + +Streams are [monads][]. Among other things, this allows complex operations to be +built on a few basic primitives (`-bind:` in particular). [RACStream][] also +implements the equivalent of the [Monoid][] and [MonadZip][] typeclasses from +[Haskell][]. + +[RACStream][] isn't terribly useful on its own. Most streams are treated as +[signals](#signals) or [sequences](#sequences) instead. + +## Signals + +A **signal**, represented by the [RACSignal][] class, is a _push-driven_ +[stream](#streams). + +Signals generally represent data that will be delivered in the future. As work +is performed or data is received, values are _sent_ on the signal, which pushes +them out to any subscribers. Users must [subscribe](#subscription) to a signal +in order to access its values. + +Signals send three different types of events to their subscribers: + + * The **next** event provides a new value from the stream. [RACStream][] + methods only operate on events of this type. Unlike Cocoa collections, it is + completely valid for a signal to include `nil`. + * The **error** event indicates that an error occurred before the signal could + finish. The event may include an `NSError` object that indicates what went + wrong. Errors must be handled specially – they are not included in the + stream's values. + * The **completed** event indicates that the signal finished successfully, and + that no more values will be added to the stream. Completion must be handled + specially – it is not included in the stream of values. + +The lifetime of a signal consists of any number of `next` events, followed by +one `error` or `completed` event (but not both). + +### Subscription + +A **subscriber** is anything that is waiting or capable of waiting for events +from a [signal](#signals). Within RAC, a subscriber is represented as any object +that conforms to the [RACSubscriber][] protocol. + +A **subscription** is created through any call to +[-subscribeNext:error:completed:][RACSignal], or one of the corresponding +convenience methods. Technically, most [RACStream][] and +[RACSignal][RACSignal+Operations] operators create subscriptions as well, but +these intermediate subscriptions are usually an implementation detail. + +Subscriptions [retain their signals][Memory Management], and are automatically +disposed of when the signal completes or errors. Subscriptions can also be +[disposed of manually](#disposables). + +### Subjects + +A **subject**, represented by the [RACSubject][] class, is a [signal](#signals) +that can be manually controlled. + +Subjects can be thought of as the "mutable" variant of a signal, much like +`NSMutableArray` is for `NSArray`. They are extremely useful for bridging +non-RAC code into the world of signals. + +For example, instead of handling application logic in block callbacks, the +blocks can simply send events to a shared subject instead. The subject can then +be returned as a [RACSignal][], hiding the implementation detail of the +callbacks. + +Some subjects offer additional behaviors as well. In particular, +[RACReplaySubject][] can be used to buffer events for future +[subscribers](#subscription), like when a network request finishes before +anything is ready to handle the result. + +### Commands + +A **command**, represented by the [RACCommand][] class, creates and subscribes +to a signal in response to some action. This makes it easy to perform +side-effecting work as the user interacts with the app. + +Usually the action triggering a command is UI-driven, like when a button is +clicked. Commands can also be automatically disabled based on a signal, and this +disabled state can be represented in a UI by disabling any controls associated +with the command. + +On OS X, RAC adds a `rac_command` property to +[NSButton][NSButton+RACCommandSupport] for setting up these behaviors +automatically. + +### Connections + +A **connection**, represented by the [RACMulticastConnection][] class, is +a [subscription](#subscription) that is shared between any number of +subscribers. + +[Signals](#signals) are _cold_ by default, meaning that they start doing work +_each_ time a new subscription is added. This behavior is usually desirable, +because it means that data will be freshly recalculated for each subscriber, but +it can be problematic if the signal has side effects or the work is expensive +(for example, sending a network request). + +A connection is created through the `-publish` or `-multicast:` methods on +[RACSignal][RACSignal+Operations], and ensures that only one underlying +subscription is created, no matter how many times the connection is subscribed +to. Once connected, the connection's signal is said to be _hot_, and the +underlying subscription will remain active until _all_ subscriptions to the +connection are [disposed](#disposables). + +## Sequences + +A **sequence**, represented by the [RACSequence][] class, is a _pull-driven_ +[stream](#streams). + +Sequences are a kind of collection, similar in purpose to `NSArray`. Unlike +an array, the values in a sequence are evaluated _lazily_ (i.e., only when they +are needed) by default, potentially improving performance if only part of +a sequence is used. Just like Cocoa collections, sequences cannot contain `nil`. + +Sequences are similar to [Clojure's sequences][seq] ([lazy-seq][] in particular), or +the [List][] type in [Haskell][]. + +RAC adds a `-rac_sequence` method to most of Cocoa's collection classes, +allowing them to be used as [RACSequences][RACSequence] instead. + +## Disposables + +The **[RACDisposable][]** class is used for cancellation and resource cleanup. + +Disposables are most commonly used to unsubscribe from a [signal](#signals). +When a [subscription](#subscription) is disposed, the corresponding subscriber +will not receive _any_ further events from the signal. Additionally, any work +associated with the subscription (background processing, network requests, etc.) +will be cancelled, since the results are no longer needed. + +For more information about cancellation, see the RAC [Design Guidelines][]. + +## Schedulers + +A **scheduler**, represented by the [RACScheduler][] class, is a serial +execution queue for [signals](#signals) to perform work or deliver their results upon. + +Schedulers are similar to Grand Central Dispatch queues, but schedulers support +cancellation (via [disposables](#disposables)), and always execute serially. +With the exception of the [+immediateScheduler][RACScheduler], schedulers do not +offer synchronous execution. This helps avoid deadlocks, and encourages the use +of [signal operators][RACSignal+Operations] instead of blocking work. + +[RACScheduler][] is also somewhat similar to `NSOperationQueue`, but schedulers +do not allow tasks to be reordered or depend on one another. + +## Value types + +RAC offers a few miscellaneous classes for conveniently representing values in +a [stream](#streams): + + * **[RACTuple][]** is a small, constant-sized collection that can contain + `nil` (represented by `RACTupleNil`). It is generally used to represent + the combined values of multiple streams. + * **[RACUnit][]** is a singleton "empty" value. It is used as a value in + a stream for those times when more meaningful data doesn't exist. + * **[RACEvent][]** represents any [signal event](#signals) as a single value. + It is primarily used by the `-materialize` method of + [RACSignal][RACSignal+Operations]. + +## Asynchronous Backtraces + +Because RAC-based code often involves asynchronous work and queue-hopping, the +framework supports [capturing asynchronous backtraces][RACBacktrace] to make debugging +easier. + +On OS X, backtraces can be automatically captured from any code, including +system libraries. + +On iOS, only queue hops from within RAC and your project will be captured (but +the information is still valuable). + +[Design Guidelines]: DesignGuidelines.md +[Haskell]: http://www.haskell.org +[lazy-seq]: http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/lazy-seq +[List]: http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.6.0.1/Data-List.html +[Memory Management]: MemoryManagement.md +[monads]: http://en.wikipedia.org/wiki/Monad_(functional_programming) +[Monoid]: http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.6.0.1/Data-Monoid.html#t:Monoid +[MonadZip]: http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.6.0.1/Control-Monad-Zip.html#t:MonadZip +[NSButton+RACCommandSupport]: ../ReactiveCocoaFramework/ReactiveCocoa/NSButton+RACCommandSupport.h +[RACBacktrace]: ../ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.h +[RACCommand]: ../ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h +[RACDisposable]: ../ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h +[RACEvent]: ../ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h +[RACMulticastConnection]: ../ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h +[RACReplaySubject]: ../ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.h +[RACScheduler]: ../ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h +[RACSequence]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h +[RACSignal]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h +[RACSignal+Operations]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h +[RACStream]: ../ReactiveCocoaFramework/ReactiveCocoa/RACStream.h +[RACSubject]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSubject.h +[RACSubscriber]: ../ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h +[RACTuple]: ../ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h +[RACUnit]: ../ReactiveCocoaFramework/ReactiveCocoa/RACUnit.h +[README]: ../README.md +[seq]: http://clojure.org/sequences
diff --git a/Documentation/MemoryManagement.md b/Documentation/MemoryManagement.md new file mode 100644 index 0000000..e442b1f --- /dev/null +++ b/Documentation/MemoryManagement.md
@@ -0,0 +1,139 @@ +# Memory Management + +ReactiveCocoa's memory management is quite complex, but the end result is that +**you don't need to retain signals in order to process them**. + +If the framework required you to retain every signal, it'd be much more unwieldy +to use, especially for one-shot signals that are used like futures (e.g., +network requests). You'd have to save any long-lived signal into a property, and +then also make sure to clear it out when you're done with it. Not fun. + +## Subscribers + +Before going any further, it's worth noting that +`subscribeNext:error:completed:` (and all variants thereof) create an _implicit_ +subscriber using the given blocks. Any objects referenced from those blocks will +therefore be retained as part of the subscription. Just like any other object, +`self` won't be retained without a direct or indirect reference to it. + +## Finite or Short-Lived Signals + +The most important guideline to RAC memory management is that a **subscription +is automatically terminated upon completion or error, and the subscriber +removed**. + +For example, if you have some code like this in your view controller: + +```objc +self.disposable = [signal subscribeCompleted:^{ + doSomethingPossiblyInvolving(self); +}]; +``` + +… the memory management will look something like the following: + +``` +view controller -> RACDisposable -> RACSignal -> RACSubscriber -> view controller +``` + +However, the `RACSignal -> RACSubscriber` relationship is torn down as soon as +`signal` finishes, breaking the retain cycle. + +**This is often all you need**, because the lifetime of the `RACSignal` in +memory will naturally match the logical lifetime of the event stream. + +## Infinite Signals + +Infinite signals (or signals that live so long that they might as well be +infinite), however, will never tear down naturally. This is where disposables +shine. + +**Disposing of a subscription will remove the associated subscriber**, and just +generally clean up any resources associated with that subscription. To that one +subscriber, it's just as if the signal had completed or errored, except no final +event is sent on the signal. All other subscribers will remain intact. + +However, as a general rule of thumb, if you have to manually manage +a subscription's lifecycle, [there's probably a better way to do what you want][avoid-explicit-subscriptions-and-disposal]. + +## Signals Derived from `self` + +There's still a bit of a tricky middle case here, though. Any time a signal's +lifetime is tied to the calling scope, you'll have a much harder cycle to break. + +This commonly occurs when using `RACObserve()` on a key +path that's relative to `self`, and then applying a block that needs to capture +`self`. + +The easiest answer here is just to **capture `self` weakly**: + +```objc +__weak id weakSelf = self; +[RACObserve(self, username) subscribeNext:^(NSString *username) { + id strongSelf = weakSelf; + [strongSelf validateUsername]; +}]; +``` + +Or, after importing the included +[EXTScope.h](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTScope.h) +header: + +```objc +@weakify(self); +[RACObserve(self, username) subscribeNext:^(NSString *username) { + @strongify(self); + [self validateUsername]; +}]; +``` + +*(Replace `__weak` or `@weakify` with `__unsafe_unretained` or `@unsafeify`, +respectively, if the object doesn't support weak references.)* + +However, [there's probably a better pattern you could use instead][avoid-explicit-subscriptions-and-disposal]. For +example, the above sample could perhaps be written like: + +```objc +[self rac_liftSelector:@selector(validateUsername:) withSignals:RACObserve(self, username), nil]; +``` + +or: + +```objc +RACSignal *validated = [RACObserve(self, username) map:^(NSString *username) { + // Put validation logic here. + return @YES; +}]; +``` + +As with infinite signals, there are generally ways you can avoid referencing +`self` (or any object) from blocks in a signal chain. + +---- + +The above information is really all you should need in order to use +ReactiveCocoa effectively. However, there's one more point to address, just for +the technically curious or for anyone interested in contributing to RAC. + +The design goal of "no retaining necessary" begs the question: how do we know +when a signal should be deallocated? What if it was just created, escaped an +autorelease pool, and hasn't been retained yet? + +The real answer is _we don't_, BUT we can usually assume that the caller will +retain the signal within the current run loop iteration if they want to keep it. + +Consequently: + + 1. A created signal is automatically added to a global set of active signals. + 2. The signal will wait for a single pass of the main run loop, and then remove + itself from the active set _if it has no subscribers_. Unless the signal was + retained somehow, it would deallocate at this point. + 3. If something did subscribe in that run loop iteration, the signal stays in + the set. + 4. Later, when all the subscribers are gone, step 2 is triggered again. + +This could backfire if the run loop is spun recursively (like in a modal event +loop on OS X), but it makes the life of the framework consumer much easier for +most or all other cases. + +[avoid-explicit-subscriptions-and-disposal]: DesignGuidelines.md#avoid-explicit-subscriptions-and-disposal
diff --git a/Documentation/README.md b/Documentation/README.md new file mode 100644 index 0000000..4e0d4ed --- /dev/null +++ b/Documentation/README.md
@@ -0,0 +1,2 @@ +This folder contains conceptual documentation and design guidelines that don't +fit well on a single class or in any specific header file.
diff --git a/Instruments/Disposable Growth.tracetemplate b/Instruments/Disposable Growth.tracetemplate new file mode 100644 index 0000000..24dc61c --- /dev/null +++ b/Instruments/Disposable Growth.tracetemplate Binary files differ
diff --git a/Instruments/README.md b/Instruments/README.md new file mode 100644 index 0000000..e2f1d4a --- /dev/null +++ b/Instruments/README.md
@@ -0,0 +1,14 @@ +This folder contains Instruments templates to make it easier to debug +code using ReactiveCocoa. + +To get started with a template, simply double-click it. + +### Signal Names + +The `name` property of `RACSignal` requires that the `RAC_DEBUG_SIGNAL_NAMES` +environment variable be set, which means that you won't have access to +meaningful names in Instruments by default. + +To add signal names, open your application's scheme in Xcode, select the Profile +action, and add `RAC_DEBUG_SIGNAL_NAMES` with a value of `1` to the list of +environment variables.
diff --git a/Instruments/Signal Events.tracetemplate b/Instruments/Signal Events.tracetemplate new file mode 100644 index 0000000..4c90d88 --- /dev/null +++ b/Instruments/Signal Events.tracetemplate Binary files differ
diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..56cda91 --- /dev/null +++ b/LICENSE.md
@@ -0,0 +1,19 @@ +**Copyright (c) 2012 - 2014, GitHub, Inc.** +**All rights reserved.** + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md new file mode 100644 index 0000000..c919c15 --- /dev/null +++ b/README.md
@@ -0,0 +1,562 @@ +# ReactiveCocoa + +ReactiveCocoa (RAC) is an Objective-C framework for [Functional Reactive +Programming][]. It provides APIs for **composing and transforming streams of +values**. + +If you're already familiar with functional reactive programming or know the basic +premise of ReactiveCocoa, check out the [Documentation][] folder for a framework +overview and more in-depth information about how it all works in practice. + +## New to ReactiveCocoa? + +ReactiveCocoa is documented like crazy, and there's a wealth of introductory +material available to explain what RAC is and how you can use it. + +If you want to learn more, we recommend these resources, roughly in order: + + 1. [Introduction](#introduction) + 1. [When to use ReactiveCocoa](#when-to-use-reactivecocoa) + 1. [Framework Overview][] + 1. [Basic Operators][] + 1. [Header documentation](ReactiveCocoaFramework/ReactiveCocoa) + 1. Previously answered [Stack Overflow](https://github.com/ReactiveCocoa/ReactiveCocoa/wiki) + questions and [GitHub issues](https://github.com/ReactiveCocoa/ReactiveCocoa/issues?labels=question&state=closed) + 1. The rest of the [Documentation][] folder + 1. [Functional Reactive Programming on iOS](https://leanpub.com/iosfrp/) + (eBook) + +If you have any further questions, please feel free to [file an issue](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/new). + +## Introduction + +ReactiveCocoa is an implementation of [functional reactive +programming](http://blog.maybeapps.com/post/42894317939/input-and-output). +Rather than using mutable variables which are replaced and modified in-place, +RAC provides signals (represented by `RACSignal`) that capture present and +future values. + +By chaining, combining, and reacting to signals, software can be written +declaratively, without the need for code that continually observes and updates +values. + +For example, a text field can be bound to the latest time, even as it changes, +instead of using additional code that watches the clock and updates the +text field every second. It works much like KVO, but with blocks instead of +overriding `-observeValueForKeyPath:ofObject:change:context:`. + +Signals can also represent asynchronous operations, much like [futures and +promises][]. This greatly simplifies asynchronous software, including networking +code. + +One of the major advantages of RAC is that it provides a single, unified +approach to dealing with asynchronous behaviors, including delegate methods, +callback blocks, target-action mechanisms, notifications, and KVO. + +Here's a simple example: + +```objc +// When self.username changes, log the new name to the console. +// +// RACObserve(self, username) creates a new RACSignal that sends the current +// value of self.username, then the new value whenever it changes. +// -subscribeNext: will execute the block whenever the signal sends a value. +[RACObserve(self, username) subscribeNext:^(NSString *newName) { + NSLog(@"%@", newName); +}]; +``` + +But unlike KVO notifications, signals can be chained together and operated on: + +```objc +// Only log names that start with "j". +// +// -filter returns a new RACSignal that only sends a new value when its block +// returns YES. +[[RACObserve(self, username) + filter:^(NSString *newName) { + return [newName hasPrefix:@"j"]; + }] + subscribeNext:^(NSString *newName) { + NSLog(@"%@", newName); + }]; +``` + +Signals can also be used to derive state. Instead of observing properties and +setting other properties in response to the new values, RAC makes it possible to +express properties in terms of signals and operations: + +```objc +// Create a one-way binding so that self.createEnabled will be +// true whenever self.password and self.passwordConfirmation +// are equal. +// +// RAC() is a macro that makes the binding look nicer. +// +// +combineLatest:reduce: takes an array of signals, executes the block with the +// latest value from each signal whenever any of them changes, and returns a new +// RACSignal that sends the return value of that block as values. +RAC(self, createEnabled) = [RACSignal + combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] + reduce:^(NSString *password, NSString *passwordConfirm) { + return @([passwordConfirm isEqualToString:password]); + }]; +``` + +Signals can be built on any stream of values over time, not just KVO. For +example, they can also represent button presses: + +```objc +// Log a message whenever the button is pressed. +// +// RACCommand creates signals to represent UI actions. Each signal can +// represent a button press, for example, and have additional work associated +// with it. +// +// -rac_command is an addition to NSButton. The button will send itself on that +// command whenever it's pressed. +self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + NSLog(@"button was pressed!"); + return [RACSignal empty]; +}] +``` + +Or asynchronous network operations: + +```objc +// Hook up a "Log in" button to log in over the network. +// +// This block will be run whenever the login command is executed, starting +// the login process. +self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) { + // The hypothetical -logIn method returns a signal that sends a value when + // the network request finishes. + return [client logIn]; +}]; + +// -executionSignals returns a signal that includes the signals returned from +// the above block, one for each time the command is executed. +[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) { + // Log a message whenever we log in successfully. + [loginSignal subscribeCompleted:^ { + NSLog(@"Logged in successfully!"); + }]; +}]; + +// Execute the login command when the button is pressed. +self.loginButton.rac_command = self.loginCommand; +``` + +Signals can also represent timers, other UI events, or anything else that +changes over time. + +Using signals for asynchronous operations makes it possible to build up more +complex behavior by chaining and transforming those signals. Work can easily be +trigged after a group of operations completes: + +```objc +// Perform 2 network operations and log a message to the console when they are +// both completed. +// +// +merge: takes an array of signals and returns a new RACSignal that passes +// through the values of all of the signals and completes when all of the +// signals complete. +// +// -subscribeCompleted: will execute the block when the signal completes. +[[RACSignal + merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] + subscribeCompleted:^{ + NSLog(@"They're both done!"); + }]; +``` + +Signals can be chained to sequentially execute asynchronous operations, instead +of nesting callbacks with blocks. This is similar to how [futures and promises][] +are usually used: + +```objc +// Log in the user, then load any cached messages, then fetch the remaining +// messages from the server. After that's all done, log a message to the +// console. +// +// The hypothetical -logInUser methods returns a signal that completes after +// logging in. +// +// -flattenMap: will execute its block whenever the signal sends a value, and +// return a new RACSignal that merges all of the signals returned from the block +// into a single signal. +[[[[client + logInUser] + flattenMap:^(User *user) { + // Return a signal that loads cached messages for the user. + return [client loadCachedMessagesForUser:user]; + }] + flattenMap:^(NSArray *messages) { + // Return a signal that fetches any remaining messages. + return [client fetchMessagesAfterMessage:messages.lastObject]; + }] + subscribeNext:^(NSArray *newMessages) { + NSLog(@"New messages: %@", newMessages); + } completed:^{ + NSLog(@"Fetched all messages."); + }]; +``` + +RAC even makes it easy to bind to the result of an asynchronous operation: + +```objc +// Create a one-way binding so that self.imageView.image will be set the user's +// avatar as soon as it's downloaded. +// +// The hypothetical -fetchUserWithUsername: method returns a signal which sends +// the user. +// +// -deliverOn: creates new signals that will do their work on other queues. In +// this example, it's used to move work to a background queue and then back to the main thread. +// +// -map: calls its block with each user that's fetched and returns a new +// RACSignal that sends values returned from the block. +RAC(self.imageView, image) = [[[[client + fetchUserWithUsername:@"joshaber"] + deliverOn:[RACScheduler scheduler]] + map:^(User *user) { + // Download the avatar (this is done on a background queue). + return [[NSImage alloc] initWithContentsOfURL:user.avatarURL]; + }] + // Now the assignment will be done on the main thread. + deliverOn:RACScheduler.mainThreadScheduler]; +``` + +That demonstrates some of what RAC can do, but it doesn't demonstrate why RAC is +so powerful. It's hard to appreciate RAC from README-sized examples, but it +makes it possible to write code with less state, less boilerplate, better code +locality, and better expression of intent. + +For more sample code, check out [C-41][] or [GroceryList][], which are real iOS +apps written using ReactiveCocoa. Additional information about RAC can be found +in the [Documentation][] folder. + +## When to use ReactiveCocoa + +Upon first glance, ReactiveCocoa is very abstract, and it can be difficult to +understand how to apply it to concrete problems. + +Here are some of the use cases that RAC excels at. + +### Handling asynchronous or event-driven data sources + +Much of Cocoa programming is focused on reacting to user events or changes in +application state. Code that deals with such events can quickly become very +complex and spaghetti-like, with lots of callbacks and state variables to handle +ordering issues. + +Patterns that seem superficially different, like UI callbacks, network +responses, and KVO notifications, actually have a lot in common. [RACSignal][] +unifies all these different APIs so that they can be composed together and +manipulated in the same way. + +For example, the following code: + +```objc + +static void *ObservationContext = &ObservationContext; + +- (void)viewDidLoad { + [super viewDidLoad]; + + [LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext]; + [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager]; + + [self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; + [self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; + [self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside]; +} + +- (void)dealloc { + [LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext]; + [NSNotificationCenter.defaultCenter removeObserver:self]; +} + +- (void)updateLogInButton { + BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0; + BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn; + self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn; +} + +- (IBAction)logInPressed:(UIButton *)sender { + [[LoginManager sharedManager] + logInWithUsername:self.usernameTextField.text + password:self.passwordTextField.text + success:^{ + self.loggedIn = YES; + } failure:^(NSError *error) { + [self presentError:error]; + }]; +} + +- (void)loggedOut:(NSNotification *)notification { + self.loggedIn = NO; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context == ObservationContext) { + [self updateLogInButton]; + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} +``` + +… could be expressed in RAC like so: + +```objc +- (void)viewDidLoad { + [super viewDidLoad]; + + @weakify(self); + + RAC(self.logInButton, enabled) = [RACSignal + combineLatest:@[ + self.usernameTextField.rac_textSignal, + self.passwordTextField.rac_textSignal, + RACObserve(LoginManager.sharedManager, loggingIn), + RACObserve(self, loggedIn) + ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) { + return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue); + }]; + + [[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) { + @strongify(self); + + RACSignal *loginSignal = [LoginManager.sharedManager + logInWithUsername:self.usernameTextField.text + password:self.passwordTextField.text]; + + [loginSignal subscribeError:^(NSError *error) { + @strongify(self); + [self presentError:error]; + } completed:^{ + @strongify(self); + self.loggedIn = YES; + }]; + }]; + + RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter + rac_addObserverForName:UserDidLogOutNotification object:nil] + mapReplace:@NO]; +} +``` + +### Chaining dependent operations + +Dependencies are most often found in network requests, where a previous request +to the server needs to complete before the next one can be constructed, and so +on: + +```objc +[client logInWithSuccess:^{ + [client loadCachedMessagesWithSuccess:^(NSArray *messages) { + [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) { + NSLog(@"Fetched all messages."); + } failure:^(NSError *error) { + [self presentError:error]; + }]; + } failure:^(NSError *error) { + [self presentError:error]; + }]; +} failure:^(NSError *error) { + [self presentError:error]; +}]; +``` + +ReactiveCocoa makes this pattern particularly easy: + +```objc +[[[[client logIn] + then:^{ + return [client loadCachedMessages]; + }] + flattenMap:^(NSArray *messages) { + return [client fetchMessagesAfterMessage:messages.lastObject]; + }] + subscribeError:^(NSError *error) { + [self presentError:error]; + } completed:^{ + NSLog(@"Fetched all messages."); + }]; +``` + +### Parallelizing independent work + +Working with independent data sets in parallel and then combining them into +a final result is non-trivial in Cocoa, and often involves a lot of +synchronization: + +```objc +__block NSArray *databaseObjects; +__block NSArray *fileContents; + +NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init]; +NSBlockOperation *databaseOperation = [NSBlockOperation blockOperationWithBlock:^{ + databaseObjects = [databaseClient fetchObjectsMatchingPredicate:predicate]; +}]; + +NSBlockOperation *filesOperation = [NSBlockOperation blockOperationWithBlock:^{ + NSMutableArray *filesInProgress = [NSMutableArray array]; + for (NSString *path in files) { + [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; + } + + fileContents = [filesInProgress copy]; +}]; + +NSBlockOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{ + [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; + NSLog(@"Done processing"); +}]; + +[finishOperation addDependency:databaseOperation]; +[finishOperation addDependency:filesOperation]; +[backgroundQueue addOperation:databaseOperation]; +[backgroundQueue addOperation:filesOperation]; +[backgroundQueue addOperation:finishOperation]; +``` + +The above code can be cleaned up and optimized by simply composing signals: + +```objc +RACSignal *databaseSignal = [[databaseClient + fetchObjectsMatchingPredicate:predicate] + subscribeOn:[RACScheduler scheduler]]; + +RACSignal *fileSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) { + NSMutableArray *filesInProgress = [NSMutableArray array]; + for (NSString *path in files) { + [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; + } + + [subscriber sendNext:[filesInProgress copy]]; + [subscriber sendCompleted]; +}]; + +[[RACSignal + combineLatest:@[ databaseSignal, fileSignal ] + reduce:^ id (NSArray *databaseObjects, NSArray *fileContents) { + [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; + return nil; + }] + subscribeCompleted:^{ + NSLog(@"Done processing"); + }]; +``` + +### Simplifying collection transformations + +Higher-order functions like `map`, `filter`, `fold`/`reduce` are sorely missing +from Foundation, leading to loop-focused code like this: + +```objc +NSMutableArray *results = [NSMutableArray array]; +for (NSString *str in strings) { + if (str.length < 2) { + continue; + } + + NSString *newString = [str stringByAppendingString:@"foobar"]; + [results addObject:newString]; +} +``` + +[RACSequence][] allows any Cocoa collection to be manipulated in a uniform and +declarative way: + +```objc +RACSequence *results = [[strings.rac_sequence + filter:^ BOOL (NSString *str) { + return str.length >= 2; + }] + map:^(NSString *str) { + return [str stringByAppendingString:@"foobar"]; + }]; +``` + +## System Requirements + +ReactiveCocoa supports OS X 10.7+ and iOS 5.0+. + +## Importing ReactiveCocoa + +To add RAC to your application: + + 1. Add the ReactiveCocoa repository as a submodule of your application's + repository. + 1. Run `script/bootstrap` from within the ReactiveCocoa folder. + 1. Drag and drop `ReactiveCocoaFramework/ReactiveCocoa.xcodeproj` into your + application's Xcode project or workspace. + 1. On the "Build Phases" tab of your application target, add RAC to the "Link + Binary With Libraries" phase. + * **On iOS**, add `libReactiveCocoa-iOS.a`. + * **On OS X**, add `ReactiveCocoa.framework`. RAC must also be added to any + "Copy Frameworks" build phase. If you don't already have one, simply add + a "Copy Files" build phase and target the "Frameworks" destination. + 1. Add `"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/include" + $(inherited)` to the "Header Search Paths" build setting (this is only + necessary for archive builds, but it has no negative effect otherwise). + 1. **For iOS targets**, add `-ObjC` to the "Other Linker Flags" build setting. + 1. **If you added RAC to a project (not a workspace)**, you will also need to + add the appropriate RAC target to the "Target Dependencies" of your + application. + +If you would prefer to use [CocoaPods](http://cocoapods.org), there are some +[ReactiveCocoa +podspecs](https://github.com/CocoaPods/Specs/tree/master/ReactiveCocoa) that +have been generously contributed by third parties. + +To see a project already set up with RAC, check out [C-41][] or [GroceryList][], +which are real iOS apps written using ReactiveCocoa. + +## Standalone Development + +If you’re working on RAC in isolation instead of integrating it into another project, you’ll want to open `ReactiveCocoaFramework/ReactiveCocoa.xcworkspace` and not the `.xcodeproj`. + +## More Info + +ReactiveCocoa is based on .NET's [Reactive +Extensions](http://msdn.microsoft.com/en-us/data/gg577609) (Rx). Most of the +principles of Rx apply to RAC as well. There are some really good Rx resources +out there: + +* [Reactive Extensions MSDN entry](http://msdn.microsoft.com/en-us/library/hh242985.aspx) +* [Reactive Extensions for .NET Introduction](http://leecampbell.blogspot.com/2010/08/reactive-extensions-for-net.html) +* [Rx - Channel 9 videos](http://channel9.msdn.com/tags/Rx/) +* [Reactive Extensions wiki](http://rxwiki.wikidot.com/) +* [101 Rx Samples](http://rxwiki.wikidot.com/101samples) +* [Programming Reactive Extensions and LINQ](http://www.amazon.com/Programming-Reactive-Extensions-Jesse-Liberty/dp/1430237473) + +RAC and Rx are both implementations of functional reactive programming. Here are +some more resources for learning about FRP: + +* [What is FRP? - Elm Language](http://elm-lang.org/learn/What-is-FRP.elm) +* [What is Functional Reactive Programming - Stack Overflow](http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming/1030631#1030631) +* [Escape from Callback Hell](http://elm-lang.org/learn/Escape-from-Callback-Hell.elm) +* [Principles of Reactive Programming on Coursera](https://www.coursera.org/course/reactive) + +[Basic Operators]: Documentation/BasicOperators.md +[Documentation]: Documentation +[Framework Overview]: Documentation/FrameworkOverview.md +[Functional Reactive Programming]: http://en.wikipedia.org/wiki/Functional_reactive_programming +[GroceryList]: https://github.com/jspahrsummers/GroceryList +[Memory Management]: Documentation/MemoryManagement.md +[NSObject+RACLifting]: ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h +[RACDisposable]: ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h +[RACEvent]: ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h +[RACMulticastConnection]: ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h +[RACScheduler]: ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h +[RACSequence]: ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h +[RACSignal+Operations]: ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h +[RACSignal]: ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h +[RACStream]: ReactiveCocoaFramework/ReactiveCocoa/RACStream.h +[RACSubscriber]: ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h +[RAC]: ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h +[futures and promises]: http://en.wikipedia.org/wiki/Futures_and_promises +[C-41]: https://github.com/AshFurrow/C-41
diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj new file mode 100644 index 0000000..58014f0 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.pbxproj
@@ -0,0 +1,3128 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1646747B17FFA0610036E30B /* UICollectionReusableView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 1668027817FE75E900C724B4 /* UICollectionReusableView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1668028017FE75F900C724B4 /* UICollectionReusableView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 1668027917FE75E900C724B4 /* UICollectionReusableView+RACSignalSupport.m */; }; + 1668028317FE775200C724B4 /* UICollectionReusableViewRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1668028117FE774800C724B4 /* UICollectionReusableViewRACSupportSpec.m */; }; + 1860F414177C91B500C7B3C9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1860F413177C91B500C7B3C9 /* UIKit.framework */; }; + 1860F416177C91B500C7B3C9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1860F415177C91B500C7B3C9 /* Foundation.framework */; }; + 1860F418177C91B500C7B3C9 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1860F417177C91B500C7B3C9 /* CoreGraphics.framework */; }; + 1860F41E177C91B500C7B3C9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1860F41C177C91B500C7B3C9 /* InfoPlist.strings */; }; + 1860F420177C91B500C7B3C9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1860F41F177C91B500C7B3C9 /* main.m */; }; + 1860F424177C91B500C7B3C9 /* RACAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1860F423177C91B500C7B3C9 /* RACAppDelegate.m */; }; + 1860F426177C91B500C7B3C9 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 1860F425177C91B500C7B3C9 /* Default.png */; }; + 1860F428177C91B500C7B3C9 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1860F427177C91B500C7B3C9 /* Default@2x.png */; }; + 1860F42A177C91B500C7B3C9 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1860F429177C91B500C7B3C9 /* Default-568h@2x.png */; }; + 1860F432177C91B500C7B3C9 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1860F431177C91B500C7B3C9 /* SenTestingKit.framework */; }; + 1860F433177C91B500C7B3C9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1860F413177C91B500C7B3C9 /* UIKit.framework */; }; + 1860F434177C91B500C7B3C9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1860F415177C91B500C7B3C9 /* Foundation.framework */; }; + 1860F43C177C91B500C7B3C9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1860F43A177C91B500C7B3C9 /* InfoPlist.strings */; }; + 1860F44F177C958300C7B3C9 /* UITextFieldRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C55CD817758A73008CDDCA /* UITextFieldRACSupportSpec.m */; }; + 1860F450177C958900C7B3C9 /* UITextViewRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C55CDE17758C2A008CDDCA /* UITextViewRACSupportSpec.m */; }; + 1E893381171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E893380171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.m */; }; + 1EC06B17173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EC06B15173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1EC06B18173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC06B16173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m */; }; + 27A887D11703DC6800040001 /* UIBarButtonItem+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A887C81703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.m */; }; + 27A887D21703DDEB00040001 /* UIBarButtonItem+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 27A887C71703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4925E806181BCC71000B2FEE /* NSControllerRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4925E805181BCC71000B2FEE /* NSControllerRACSupportSpec.m */; }; + 554D9E5D181064E200F21262 /* UIRefreshControl+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 554D9E5B181064E200F21262 /* UIRefreshControl+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 554D9E5E181064E200F21262 /* UIRefreshControl+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 554D9E5C181064E200F21262 /* UIRefreshControl+RACCommandSupport.m */; }; + 5564537F18107203002BD2E4 /* RACControlCommandExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D075A72917BCB7E100C24FB7 /* RACControlCommandExamples.m */; }; + 5564542418107275002BD2E4 /* UIRefreshControlRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5564542318107275002BD2E4 /* UIRefreshControlRACSupportSpec.m */; }; + 557A4B5A177648C7008EF796 /* UIActionSheet+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 557A4B58177648C7008EF796 /* UIActionSheet+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 557A4B5B177648C7008EF796 /* UIActionSheet+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 557A4B59177648C7008EF796 /* UIActionSheet+RACSignalSupport.m */; }; + 55C39DE417F1EC6E006DC60C /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8816090C1500636B49 /* NSData+RACSupport.m */; }; + 55C39DE517F1EC6E006DC60C /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8A16090C1500636B49 /* NSFileHandle+RACSupport.m */; }; + 55C39DE617F1EC6E006DC60C /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8C16090C1500636B49 /* NSNotificationCenter+RACSupport.m */; }; + 55C39DE717F1EC6E006DC60C /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8E16090C1500636B49 /* NSString+RACSupport.m */; }; + 55C39DE817F1EC6E006DC60C /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8816090C1500636B49 /* NSData+RACSupport.m */; }; + 55C39DE917F1EC6E006DC60C /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8A16090C1500636B49 /* NSFileHandle+RACSupport.m */; }; + 55C39DEA17F1EC6E006DC60C /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8C16090C1500636B49 /* NSNotificationCenter+RACSupport.m */; }; + 55C39DEB17F1EC6E006DC60C /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8E16090C1500636B49 /* NSString+RACSupport.m */; }; + 55C39DEC17F1EC84006DC60C /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8716090C1500636B49 /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55C39DED17F1EC84006DC60C /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8916090C1500636B49 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55C39DEE17F1EC84006DC60C /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8B16090C1500636B49 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55C39DEF17F1EC84006DC60C /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8D16090C1500636B49 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55C39DF017F1EC84006DC60C /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8716090C1500636B49 /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55C39DF117F1EC84006DC60C /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8916090C1500636B49 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55C39DF217F1EC84006DC60C /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8B16090C1500636B49 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 55C39DF317F1EC84006DC60C /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8D16090C1500636B49 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5EE9A7931760D61300EAF5A2 /* UIButton+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5EE9A7911760D61300EAF5A2 /* UIButton+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5EE9A7941760D61300EAF5A2 /* UIButton+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE9A7921760D61300EAF5A2 /* UIButton+RACCommandSupport.m */; }; + 5EE9A79B1760D88500EAF5A2 /* UIButtonRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE9A79A1760D88500EAF5A2 /* UIButtonRACSupportSpec.m */; }; + 5F016DF717B10AA8002EEC69 /* UIControl+RACSignalSupportPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F016DF317B10AA8002EEC69 /* UIControl+RACSignalSupportPrivate.m */; }; + 5F2447AD167E87C50062180C /* RACKVOChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F2447AC167E87C50062180C /* RACKVOChannelSpec.m */; }; + 5F45A885168CFA3E00B58A2B /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F45A883168CFA3E00B58A2B /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F45A886168CFA3E00B58A2B /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F45A883168CFA3E00B58A2B /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F45A887168CFA3E00B58A2B /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F45A884168CFA3E00B58A2B /* RACKVOChannel.m */; }; + 5F45A888168CFA3E00B58A2B /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F45A884168CFA3E00B58A2B /* RACKVOChannel.m */; }; + 5F6FE8531692568A00A8D7A6 /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F6FE8511692568A00A8D7A6 /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F6FE8541692568A00A8D7A6 /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F6FE8511692568A00A8D7A6 /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F6FE8551692568A00A8D7A6 /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F6FE8521692568A00A8D7A6 /* RACChannel.m */; }; + 5F6FE8561692568A00A8D7A6 /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F6FE8521692568A00A8D7A6 /* RACChannel.m */; }; + 5F70B2AF17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F70B2AD17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F70B2B017AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F70B2AE17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.m */; }; + 5F70B2BE17AB1857009AEDF9 /* UISegmentedControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F70B2B617AB1856009AEDF9 /* UISegmentedControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F70B2BF17AB1857009AEDF9 /* UISegmentedControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F70B2B717AB1856009AEDF9 /* UISegmentedControl+RACSignalSupport.m */; }; + 5F70B2C017AB1857009AEDF9 /* UISlider+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F70B2B817AB1856009AEDF9 /* UISlider+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F70B2C117AB1857009AEDF9 /* UISlider+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F70B2B917AB1856009AEDF9 /* UISlider+RACSignalSupport.m */; }; + 5F70B2C217AB1857009AEDF9 /* UIStepper+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F70B2BA17AB1857009AEDF9 /* UIStepper+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F70B2C317AB1857009AEDF9 /* UIStepper+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F70B2BB17AB1857009AEDF9 /* UIStepper+RACSignalSupport.m */; }; + 5F70B2C417AB1857009AEDF9 /* UISwitch+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F70B2BC17AB1857009AEDF9 /* UISwitch+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F70B2C517AB1857009AEDF9 /* UISwitch+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F70B2BD17AB1857009AEDF9 /* UISwitch+RACSignalSupport.m */; }; + 5F773DEA169B46670023069D /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F773DE8169B46670023069D /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F773DEB169B46670023069D /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F773DE8169B46670023069D /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5F773DEC169B46670023069D /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F773DE9169B46670023069D /* NSEnumerator+RACSequenceAdditions.m */; }; + 5F773DED169B46670023069D /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F773DE9169B46670023069D /* NSEnumerator+RACSequenceAdditions.m */; }; + 5F773DF0169B48830023069D /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F773DEF169B48830023069D /* NSEnumeratorRACSequenceAdditionsSpec.m */; }; + 5F7EFECF168FBC4B0037E500 /* RACChannelExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F7EFECD168FBC4B0037E500 /* RACChannelExamples.m */; }; + 5F7EFED0168FBC4B0037E500 /* RACChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F7EFECE168FBC4B0037E500 /* RACChannelSpec.m */; }; + 5F9743F91694A2460024EB82 /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F9743F61694A2460024EB82 /* RACEagerSequence.m */; }; + 5F9743FA1694A2460024EB82 /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F9743F61694A2460024EB82 /* RACEagerSequence.m */; }; + 5FAF5224174D4C2000CAC810 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CDF7DD15000FCF00163A9F /* SenTestingKit.framework */; }; + 5FAF523E174D4D3200CAC810 /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F773DEF169B48830023069D /* NSEnumeratorRACSequenceAdditionsSpec.m */; }; + 5FAF523F174D4D3600CAC810 /* NSNotificationCenterRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0700F4B1672994D00D7CD30 /* NSNotificationCenterRACSupportSpec.m */; }; + 5FAF5240174D4D5600CAC810 /* NSObjectRACDeallocatingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E58405E16F3414200F588A6 /* NSObjectRACDeallocatingSpec.m */; }; + 5FAF5241174D4D5600CAC810 /* NSObjectRACLiftingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8801E7501644BDE200A155FE /* NSObjectRACLiftingSpec.m */; }; + 5FAF5242174D4D5600CAC810 /* NSObjectRACPropertySubscribingExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E893380171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.m */; }; + 5FAF5243174D4D5600CAC810 /* NSObjectRACPropertySubscribingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8851A38A16161D500050D47F /* NSObjectRACPropertySubscribingSpec.m */; }; + 5FAF5244174D4D5600CAC810 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC350E1736F81800792E52 /* NSStringRACKeyPathUtilitiesSpec.m */; }; + 5FAF5246174D4D5600CAC810 /* RACBacktraceSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0870C6E16884A0600D0E11D /* RACBacktraceSpec.m */; }; + 5FAF5247174D4D5600CAC810 /* RACBlockTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 886CEACC163DE669007632D1 /* RACBlockTrampolineSpec.m */; }; + 5FAF5248174D4D5600CAC810 /* RACCommandSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 882CCA1D15F1564D00937D6E /* RACCommandSpec.m */; }; + 5FAF5249174D4D5600CAC810 /* RACCompoundDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E86B91669350B00667F7B /* RACCompoundDisposableSpec.m */; }; + 5FAF524A174D4D5600CAC810 /* RACEventSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D077A171169B79A900057BB1 /* RACEventSpec.m */; }; + 5FAF524B174D4D5600CAC810 /* RACKVOWrapperSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D041376815D2281C004BBF80 /* RACKVOWrapperSpec.m */; }; + 5FAF524C174D4D5600CAC810 /* RACMulticastConnectionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C5A02816924BFC0045EF05 /* RACMulticastConnectionSpec.m */; }; + 5FAF524D174D4D5600CAC810 /* RACKVOChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F2447AC167E87C50062180C /* RACKVOChannelSpec.m */; }; + 5FAF524E174D4D5600CAC810 /* RACPropertySignalExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDE76616968AB10072A780 /* RACPropertySignalExamples.m */; }; + 5FAF524F174D4D5600CAC810 /* RACChannelExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F7EFECD168FBC4B0037E500 /* RACChannelExamples.m */; }; + 5FAF5250174D4D5600CAC810 /* RACChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F7EFECE168FBC4B0037E500 /* RACChannelSpec.m */; }; + 5FAF5251174D4D5600CAC810 /* RACSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8803C010166732BA00C36839 /* RACSchedulerSpec.m */; }; + 5FAF5252174D4D5600CAC810 /* RACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70F8F164337A2007027B4 /* RACSequenceAdditionsSpec.m */; }; + 5FAF5253174D4D5600CAC810 /* RACSequenceExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70F92164337E3007027B4 /* RACSequenceExamples.m */; }; + 5FAF5254174D4D5600CAC810 /* RACSequenceSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D487051642651400DD7605 /* RACSequenceSpec.m */; }; + 5FAF5255174D4D5600CAC810 /* RACSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820937B1501C8A600796685 /* RACSignalSpec.m */; }; + 5FAF5256174D4D5600CAC810 /* RACStreamExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0487AB2164314430085D890 /* RACStreamExamples.m */; }; + 5FAF5257174D4D5600CAC810 /* RACSubjectSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 889D0A7F15974B2A00F833E3 /* RACSubjectSpec.m */; }; + 5FAF5258174D4D5600CAC810 /* RACSubscriberExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70EC516659333005AAD03 /* RACSubscriberExamples.m */; }; + 5FAF5259174D4D5600CAC810 /* RACSubscriberSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70EC7166595AD005AAD03 /* RACSubscriberSpec.m */; }; + 5FAF525A174D4D5600CAC810 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88FC735A16114FFB00F8A774 /* RACSubscriptingAssignmentTrampolineSpec.m */; }; + 5FAF525B174D4D5600CAC810 /* RACTestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442A331608A9AD00636B49 /* RACTestObject.m */; }; + 5FAF525C174D4D5600CAC810 /* RACTupleSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D02221611678910900DBD031 /* RACTupleSpec.m */; }; + 5FAF525D174D4D5600CAC810 /* NSObjectRACSelectorSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 880D7A6516F7BB1A004A3361 /* NSObjectRACSelectorSignalSpec.m */; }; + 5FAF525E174D4D5600CAC810 /* RACSubclassObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 880D7A6816F7BCC7004A3361 /* RACSubclassObject.m */; }; + 5FAF5262174D4D8F00CAC810 /* UIBarButtonItemRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FAF5261174D4D8E00CAC810 /* UIBarButtonItemRACSupportSpec.m */; }; + 5FAF5265174D500D00CAC810 /* libReactiveCocoa-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 88F440AB153DAC820097B4C3 /* libReactiveCocoa-iOS.a */; }; + 5FAF5289174E9CD300CAC810 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FAF5288174E9CD200CAC810 /* CoreGraphics.framework */; }; + 5FD7DC7A174F9EAF008710B4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CDF7C415000FCE00163A9F /* Foundation.framework */; }; + 5FD7DC7C174F9EEB008710B4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FD7DC7B174F9EEB008710B4 /* UIKit.framework */; }; + 5FD7DC7F174F9FC8008710B4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FD7DC7B174F9EEB008710B4 /* UIKit.framework */; }; + 5FDC35051736F54700792E52 /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC35021736F54700792E52 /* NSString+RACKeyPathUtilities.m */; }; + 5FDC35061736F54700792E52 /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC35021736F54700792E52 /* NSString+RACKeyPathUtilities.m */; }; + 5FDC350F1736F81900792E52 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC350E1736F81800792E52 /* NSStringRACKeyPathUtilitiesSpec.m */; }; + 6E58405516F22D7500F588A6 /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E58405316F22D7500F588A6 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6E58405616F22D7500F588A6 /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E58405416F22D7500F588A6 /* NSObject+RACDeallocating.m */; }; + 6E58405D16F22F7800F588A6 /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E58405416F22D7500F588A6 /* NSObject+RACDeallocating.m */; }; + 6E58405F16F3414200F588A6 /* NSObjectRACDeallocatingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E58405E16F3414200F588A6 /* NSObjectRACDeallocatingSpec.m */; }; + 6EA0C08216F4AEC1006EBEB2 /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E58405316F22D7500F588A6 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7479F6E8186177D200575CDB /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 7479F6E4186177D200575CDB /* RACIndexSetSequence.m */; }; + 7479F6E9186177D200575CDB /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 7479F6E4186177D200575CDB /* RACIndexSetSequence.m */; }; + 7479F6EA186177D200575CDB /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 7479F6E4186177D200575CDB /* RACIndexSetSequence.m */; }; + 74F17318186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 74F17316186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 74F17319186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 74F17316186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 74F1731A186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 74F17316186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 74F1731B186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 74F17317186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m */; }; + 74F1731C186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 74F17317186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m */; }; + 74F1731D186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 74F17317186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m */; }; + 8801E7511644BDE200A155FE /* NSObjectRACLiftingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8801E7501644BDE200A155FE /* NSObjectRACLiftingSpec.m */; }; + 88037F8415056328001A5B19 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CDF7BF15000FCE00163A9F /* Cocoa.framework */; }; + 88037FB81505645C001A5B19 /* ReactiveCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 88037F8C15056328001A5B19 /* ReactiveCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88037FBB1505646C001A5B19 /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF82C15008C0500163A9F /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88037FBC1505646C001A5B19 /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF80415001CA800163A9F /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88037FBE1505646C001A5B19 /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF7FA150019CA00163A9F /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88037FC11505646C001A5B19 /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 882093E91501E6EE00796685 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88037FC21505646C001A5B19 /* NSControl+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 882093E61501E6CB00796685 /* NSControl+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88037FC71505647E001A5B19 /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CDF82915008BB900163A9F /* NSObject+RACKVOWrapper.m */; }; + 88037FC91505648C001A5B19 /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CDF7FB150019CA00163A9F /* RACSubscriber.m */; }; + 88037FCC1505648C001A5B19 /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 882093EA1501E6EE00796685 /* RACCommand.m */; }; + 88037FCD1505648C001A5B19 /* NSControl+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 882093E71501E6CB00796685 /* NSControl+RACCommandSupport.m */; }; + 88037FD9150564D9001A5B19 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88037F8315056328001A5B19 /* ReactiveCocoa.framework */; }; + 8803C011166732BA00C36839 /* RACSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8803C010166732BA00C36839 /* RACSchedulerSpec.m */; }; + 880B9176150B09190008488E /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 880B9174150B09190008488E /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 880B9177150B09190008488E /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 880B9175150B09190008488E /* RACSubject.m */; }; + 880D7A5A16F7B351004A3361 /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 880D7A5816F7B351004A3361 /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 880D7A5B16F7B351004A3361 /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 880D7A5816F7B351004A3361 /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 880D7A5C16F7B351004A3361 /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 880D7A5916F7B351004A3361 /* NSObject+RACSelectorSignal.m */; }; + 880D7A5D16F7B351004A3361 /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 880D7A5916F7B351004A3361 /* NSObject+RACSelectorSignal.m */; }; + 880D7A6616F7BB1A004A3361 /* NSObjectRACSelectorSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 880D7A6516F7BB1A004A3361 /* NSObjectRACSelectorSignalSpec.m */; }; + 880D7A6916F7BCC7004A3361 /* RACSubclassObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 880D7A6816F7BCC7004A3361 /* RACSubclassObject.m */; }; + 881B37CC152260BF0079220B /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 881B37CA152260BF0079220B /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 881B37CD152260BF0079220B /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = 881B37CB152260BF0079220B /* RACUnit.m */; }; + 881E86A21669304800667F7B /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E86A01669304700667F7B /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 881E86A41669304800667F7B /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E86A11669304700667F7B /* RACCompoundDisposable.m */; }; + 881E86A51669304800667F7B /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E86A11669304700667F7B /* RACCompoundDisposable.m */; }; + 881E86BA1669350B00667F7B /* RACCompoundDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E86B91669350B00667F7B /* RACCompoundDisposableSpec.m */; }; + 881E87AC16695C5600667F7B /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E87AA16695C5600667F7B /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 881E87AE16695C5600667F7B /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87AB16695C5600667F7B /* RACQueueScheduler.m */; }; + 881E87AF16695C5600667F7B /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87AB16695C5600667F7B /* RACQueueScheduler.m */; }; + 881E87B416695EDF00667F7B /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87B116695EDF00667F7B /* RACImmediateScheduler.m */; }; + 881E87B516695EDF00667F7B /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87B116695EDF00667F7B /* RACImmediateScheduler.m */; }; + 881E87C41669636000667F7B /* RACSubscriptionScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E87C21669635F00667F7B /* RACSubscriptionScheduler.h */; }; + 881E87C61669636000667F7B /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87C31669636000667F7B /* RACSubscriptionScheduler.m */; }; + 881E87C71669636000667F7B /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87C31669636000667F7B /* RACSubscriptionScheduler.m */; }; + 8820937C1501C8A600796685 /* RACSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820937B1501C8A600796685 /* RACSignalSpec.m */; }; + 882CCA1E15F1564D00937D6E /* RACCommandSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 882CCA1D15F1564D00937D6E /* RACCommandSpec.m */; }; + 882D071917614FA7009EDA69 /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 882D071717614FA7009EDA69 /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 882D071A17614FA7009EDA69 /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 882D071817614FA7009EDA69 /* RACTargetQueueScheduler.m */; }; + 882D071F17615139009EDA69 /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 882D071817614FA7009EDA69 /* RACTargetQueueScheduler.m */; }; + 882D072117615381009EDA69 /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = 882D07201761521B009EDA69 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88302BFD1762A9E6003633BD /* RACTestExampleScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 88302BFC1762A9E6003633BD /* RACTestExampleScheduler.m */; }; + 88302BFE1762A9E6003633BD /* RACTestExampleScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 88302BFC1762A9E6003633BD /* RACTestExampleScheduler.m */; }; + 88302C2E1762C180003633BD /* RACTargetQueueSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88302C2D1762C180003633BD /* RACTargetQueueSchedulerSpec.m */; }; + 88302C2F1762C180003633BD /* RACTargetQueueSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88302C2D1762C180003633BD /* RACTargetQueueSchedulerSpec.m */; }; + 88302C961762EC79003633BD /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E87AA16695C5600667F7B /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88302C9B1762EC7E003633BD /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = 882D07201761521B009EDA69 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88302CA21762F62D003633BD /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 882D071717614FA7009EDA69 /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8837EA1816A5A33300FC3CDF /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 8837EA1516A5A33300FC3CDF /* RACKVOTrampoline.m */; }; + 8837EA1916A5A33300FC3CDF /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 8837EA1516A5A33300FC3CDF /* RACKVOTrampoline.m */; }; + 883A84DA1513964B006DB4C7 /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 883A84D81513964B006DB4C7 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 883A84DB1513964B006DB4C7 /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 883A84D91513964B006DB4C7 /* RACBehaviorSubject.m */; }; + 883A84DF1513B5EC006DB4C7 /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 883A84DD1513B5EC006DB4C7 /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 883A84E01513B5EC006DB4C7 /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 883A84DE1513B5EC006DB4C7 /* RACDisposable.m */; }; + 88442A341608A9AD00636B49 /* RACTestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442A331608A9AD00636B49 /* RACTestObject.m */; }; + 884476E4152367D100958F44 /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 884476E2152367D100958F44 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 884476E5152367D100958F44 /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 884476E3152367D100958F44 /* RACScopedDisposable.m */; }; + 884848B615F658B800B11BD0 /* NSControlRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 884848B515F658B800B11BD0 /* NSControlRACSupportSpec.m */; }; + 8851A38B16161D500050D47F /* NSObjectRACPropertySubscribingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8851A38A16161D500050D47F /* NSObjectRACPropertySubscribingSpec.m */; }; + 886678711518DCD800DE77EC /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = 886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */; }; + 886CEACD163DE669007632D1 /* RACBlockTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 886CEACC163DE669007632D1 /* RACBlockTrampolineSpec.m */; }; + 886CEAE2163DE942007632D1 /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = 886CEAE0163DE942007632D1 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 886CEAE4163DE942007632D1 /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = 886CEAE1163DE942007632D1 /* NSObject+RACLifting.m */; }; + 886CEAE5163DE942007632D1 /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = 886CEAE1163DE942007632D1 /* NSObject+RACLifting.m */; }; + 886F702A1551CF920045D68B /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 886F70281551CF920045D68B /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 886F702B1551CF920045D68B /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 886F70291551CF920045D68B /* RACGroupedSignal.m */; }; + 886F702C1551CF9D0045D68B /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 886F70291551CF920045D68B /* RACGroupedSignal.m */; }; + 887ACDA9165878A8009190AD /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = 887ACDA6165878A7009190AD /* NSInvocation+RACTypeParsing.m */; }; + 887ACDAA165878A8009190AD /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = 887ACDA6165878A7009190AD /* NSInvocation+RACTypeParsing.m */; }; + 8882D4601673B0450080E7CD /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 888439A21634E10D00DED0DB /* RACBlockTrampoline.m */; }; + 8884DD651756ACF600F6C379 /* RACSignalStartExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = 8884DD641756ACF600F6C379 /* RACSignalStartExamples.m */; }; + 8884DD6B1756B65300F6C379 /* RACSignalStartExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = 8884DD641756ACF600F6C379 /* RACSignalStartExamples.m */; }; + 88977C3E1512914A00A09EC5 /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 88977C3D1512914A00A09EC5 /* RACSignal.m */; }; + 889D0A8015974B2A00F833E3 /* RACSubjectSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 889D0A7F15974B2A00F833E3 /* RACSubjectSpec.m */; }; + 88A0B6D2165B2B09005DE8F3 /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 888439A21634E10D00DED0DB /* RACBlockTrampoline.m */; }; + 88A0B6D3165B2B77005DE8F3 /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = 88FC735316114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88B76F8E153726B00053EAE2 /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B76F8C153726B00053EAE2 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88B76F8F153726B00053EAE2 /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B76F8D153726B00053EAE2 /* RACTuple.m */; }; + 88C5A0241692460A0045EF05 /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 88C5A0231692460A0045EF05 /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88C5A026169246140045EF05 /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C5A025169246140045EF05 /* RACMulticastConnection.m */; }; + 88C5A027169246140045EF05 /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C5A025169246140045EF05 /* RACMulticastConnection.m */; }; + 88C5A02916924BFC0045EF05 /* RACMulticastConnectionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C5A02816924BFC0045EF05 /* RACMulticastConnectionSpec.m */; }; + 88CDF7DE15000FCF00163A9F /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CDF7DD15000FCF00163A9F /* SenTestingKit.framework */; }; + 88CDF7DF15000FCF00163A9F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CDF7BF15000FCE00163A9F /* Cocoa.framework */; }; + 88CDF7E715000FCF00163A9F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 88CDF7E515000FCF00163A9F /* InfoPlist.strings */; }; + 88D4AB3E1510F6C30011494F /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 88D4AB3C1510F6C30011494F /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88D4AB3F1510F6C30011494F /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D4AB3D1510F6C30011494F /* RACReplaySubject.m */; }; + 88DA309815071CBA00C19D0F /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DA309615071CBA00C19D0F /* RACValueTransformer.m */; }; + 88E2C6B4153C771C00C7493C /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 88E2C6B2153C771C00C7493C /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88E2C6B5153C771C00C7493C /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 88E2C6B3153C771C00C7493C /* RACScheduler.m */; }; + 88F440BA153DAD570097B4C3 /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 882093EA1501E6EE00796685 /* RACCommand.m */; }; + 88F440BC153DAD5A0097B4C3 /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CDF7FB150019CA00163A9F /* RACSubscriber.m */; }; + 88F440BD153DAD5C0097B4C3 /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 88977C3D1512914A00A09EC5 /* RACSignal.m */; }; + 88F440C0153DAD630097B4C3 /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 880B9175150B09190008488E /* RACSubject.m */; }; + 88F440C1153DAD640097B4C3 /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D4AB3D1510F6C30011494F /* RACReplaySubject.m */; }; + 88F440C3153DAD690097B4C3 /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 883A84D91513964B006DB4C7 /* RACBehaviorSubject.m */; }; + 88F440C5153DAD6C0097B4C3 /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 883A84DE1513B5EC006DB4C7 /* RACDisposable.m */; }; + 88F440C6153DAD6E0097B4C3 /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 884476E3152367D100958F44 /* RACScopedDisposable.m */; }; + 88F440C9153DAD740097B4C3 /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = 881B37CB152260BF0079220B /* RACUnit.m */; }; + 88F440CA153DAD760097B4C3 /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B76F8D153726B00053EAE2 /* RACTuple.m */; }; + 88F440CB153DAD780097B4C3 /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 88E2C6B3153C771C00C7493C /* RACScheduler.m */; }; + 88F440CE153DAD830097B4C3 /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CDF82915008BB900163A9F /* NSObject+RACKVOWrapper.m */; }; + 88F440CF153DAD850097B4C3 /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = 886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */; }; + 88F440D3153DADEA0097B4C3 /* NSObject+RACAppKitBindings.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F440D1153DADEA0097B4C3 /* NSObject+RACAppKitBindings.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88F440D4153DADEA0097B4C3 /* NSObject+RACAppKitBindings.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F440D2153DADEA0097B4C3 /* NSObject+RACAppKitBindings.m */; }; + 88F44263153DC2C70097B4C3 /* UIControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F44260153DC0450097B4C3 /* UIControl+RACSignalSupport.m */; }; + 88F44267153DCAC50097B4C3 /* UITextField+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F44265153DCAC50097B4C3 /* UITextField+RACSignalSupport.m */; }; + 88FC735716114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 88FC735416114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m */; }; + 88FC735816114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 88FC735416114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m */; }; + 88FC735B16114FFB00F8A774 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88FC735A16114FFB00F8A774 /* RACSubscriptingAssignmentTrampolineSpec.m */; }; + A1FCC27715666AA3008C9686 /* UITextView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = A1FCC27315666AA3008C9686 /* UITextView+RACSignalSupport.m */; }; + A1FCC374156754A7008C9686 /* RACObjCRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = A1FCC371156754A7008C9686 /* RACObjCRuntime.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + A1FCC375156754A7008C9686 /* RACObjCRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = A1FCC371156754A7008C9686 /* RACObjCRuntime.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + A1FCC37B1567DED0008C9686 /* RACDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = A1FCC3771567DED0008C9686 /* RACDelegateProxy.m */; }; + AC65FD52176DECB1005ED22B /* UIAlertViewRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = AC65FD51176DECB1005ED22B /* UIAlertViewRACSupportSpec.m */; }; + ACB0EAF31797DDD400942FFC /* UIAlertView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = ACB0EAF11797DDD400942FFC /* UIAlertView+RACSignalSupport.m */; }; + ACB0EAF41797DDD400942FFC /* UIAlertView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = ACB0EAF21797DDD400942FFC /* UIAlertView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BE527E9318636F7F006349E8 /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = BE527E9118636F7F006349E8 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BE527E9418636F7F006349E8 /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = BE527E9118636F7F006349E8 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BE527E9518636F7F006349E8 /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = BE527E9118636F7F006349E8 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BE527E9618636F7F006349E8 /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = BE527E9218636F7F006349E8 /* NSUserDefaults+RACSupport.m */; }; + BE527E9718636F7F006349E8 /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = BE527E9218636F7F006349E8 /* NSUserDefaults+RACSupport.m */; }; + BE527E9818636F7F006349E8 /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = BE527E9218636F7F006349E8 /* NSUserDefaults+RACSupport.m */; }; + BE527E9F1863705B006349E8 /* NSUserDefaultsRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = BE527E9E1863705B006349E8 /* NSUserDefaultsRACSupportSpec.m */; }; + BE527EA01863706B006349E8 /* NSUserDefaultsRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = BE527E9E1863705B006349E8 /* NSUserDefaultsRACSupportSpec.m */; }; + CD11C6F318714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = CD11C6F118714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CD11C6F418714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = CD11C6F218714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.m */; }; + CD11C6FF18714F00007C7CFD /* RACTestTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD11C6FD18714DFB007C7CFD /* RACTestTableViewController.m */; }; + CD11C700187151AE007C7CFD /* UITableViewHeaderFooterViewRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = CD11C6F918714DC1007C7CFD /* UITableViewHeaderFooterViewRACSupportSpec.m */; }; + D004BC9C177E1A2B00A5B8C5 /* UIActionSheetRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D004BC9B177E1A2B00A5B8C5 /* UIActionSheetRACSupportSpec.m */; }; + D005A259169A3B7D00A9D2DB /* RACBacktrace.h in Headers */ = {isa = PBXBuildFile; fileRef = D02538A115E2D7FB005BACB8 /* RACBacktrace.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D00930791788AB7B00EE7E8B /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D00930771788AB7B00EE7E8B /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D009307A1788AB7B00EE7E8B /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D00930771788AB7B00EE7E8B /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D009307B1788AB7B00EE7E8B /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D00930781788AB7B00EE7E8B /* RACTestScheduler.m */; }; + D009307C1788AB7B00EE7E8B /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D00930781788AB7B00EE7E8B /* RACTestScheduler.m */; }; + D011F9D01782AFD400EE7E38 /* NSObjectRACAppKitBindingsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D011F9CF1782AFD400EE7E38 /* NSObjectRACAppKitBindingsSpec.m */; }; + D013A3D91807B5ED0072B6CE /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3D51807B5ED0072B6CE /* RACErrorSignal.m */; }; + D013A3DA1807B5ED0072B6CE /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3D51807B5ED0072B6CE /* RACErrorSignal.m */; }; + D013A3DB1807B5ED0072B6CE /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3D51807B5ED0072B6CE /* RACErrorSignal.m */; }; + D013A3E11807B7450072B6CE /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3DD1807B7450072B6CE /* RACEmptySignal.m */; }; + D013A3E21807B7450072B6CE /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3DD1807B7450072B6CE /* RACEmptySignal.m */; }; + D013A3E31807B7450072B6CE /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3DD1807B7450072B6CE /* RACEmptySignal.m */; }; + D013A3E91807B7C30072B6CE /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3E51807B7C30072B6CE /* RACReturnSignal.m */; }; + D013A3EA1807B7C30072B6CE /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3E51807B7C30072B6CE /* RACReturnSignal.m */; }; + D013A3EB1807B7C30072B6CE /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3E51807B7C30072B6CE /* RACReturnSignal.m */; }; + D013A3F21807B9690072B6CE /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3EE1807B9690072B6CE /* RACDynamicSignal.m */; }; + D013A3F31807B9690072B6CE /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3EE1807B9690072B6CE /* RACDynamicSignal.m */; }; + D013A3F41807B9690072B6CE /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D013A3EE1807B9690072B6CE /* RACDynamicSignal.m */; }; + D020F3DB17F6A3E40092BED2 /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D020F3DA17F6A3E40092BED2 /* RACCompoundDisposableProvider.d */; }; + D020F3DC17F6A3E40092BED2 /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D020F3DA17F6A3E40092BED2 /* RACCompoundDisposableProvider.d */; }; + D02221621678910900DBD031 /* RACTupleSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D02221611678910900DBD031 /* RACTupleSpec.m */; }; + D028DB74179E53CB00D1042F /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D028DB72179E53CB00D1042F /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D028DB75179E53CB00D1042F /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D028DB72179E53CB00D1042F /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D028DB76179E53CB00D1042F /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D028DB73179E53CB00D1042F /* RACSerialDisposable.m */; }; + D028DB77179E53CB00D1042F /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D028DB73179E53CB00D1042F /* RACSerialDisposable.m */; }; + D028DB7D179E591E00D1042F /* RACSerialDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D028DB7C179E591E00D1042F /* RACSerialDisposableSpec.m */; }; + D028DB7E179E591E00D1042F /* RACSerialDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D028DB7C179E591E00D1042F /* RACSerialDisposableSpec.m */; }; + D028DB87179E616700D1042F /* UITableViewCell+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D028DB85179E616700D1042F /* UITableViewCell+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D028DB88179E616700D1042F /* UITableViewCell+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D028DB86179E616700D1042F /* UITableViewCell+RACSignalSupport.m */; }; + D0307EDF1731AAE100D83211 /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0307EDC1731AAE100D83211 /* RACTupleSequence.m */; }; + D0307EE01731AAE100D83211 /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0307EDC1731AAE100D83211 /* RACTupleSequence.m */; }; + D03525D417E2EBC90099CBAB /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03525D317E2EBC90099CBAB /* RACSignalProvider.d */; }; + D03525D917E2FAAD0099CBAB /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03525D317E2EBC90099CBAB /* RACSignalProvider.d */; }; + D041376915D2281C004BBF80 /* RACKVOWrapperSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D041376815D2281C004BBF80 /* RACKVOWrapperSpec.m */; }; + D0487AB3164314430085D890 /* RACStreamExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0487AB2164314430085D890 /* RACStreamExamples.m */; }; + D049804217F91F42001EE042 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D05F9D3417984EC000FD7982 /* EXTRuntimeExtensions.m */; }; + D05AD39617F2D5700080895B /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05AD39517F2D5700080895B /* Cocoa.framework */; }; + D05AD3BF17F2DA2A0080895B /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CDF7FB150019CA00163A9F /* RACSubscriber.m */; }; + D05AD3C017F2DA300080895B /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF7FA150019CA00163A9F /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3C117F2DB100080895B /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0E22D176A8CD6007273ED /* RACPassthroughSubscriber.m */; }; + D05AD3C217F2DB100080895B /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 888439A21634E10D00DED0DB /* RACBlockTrampoline.m */; }; + D05AD3C317F2DB100080895B /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 881B37CA152260BF0079220B /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3C417F2DB100080895B /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = 881B37CB152260BF0079220B /* RACUnit.m */; }; + D05AD3C517F2DB100080895B /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B76F8C153726B00053EAE2 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3C617F2DB100080895B /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B76F8D153726B00053EAE2 /* RACTuple.m */; }; + D05AD3C717F2DB100080895B /* RACBacktrace.h in Headers */ = {isa = PBXBuildFile; fileRef = D02538A115E2D7FB005BACB8 /* RACBacktrace.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3C817F2DB100080895B /* RACBacktrace.m in Sources */ = {isa = PBXBuildFile; fileRef = D0DFBCCD15DD6D40009DADB3 /* RACBacktrace.m */; }; + D05AD3C917F2DB100080895B /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = 88FC735316114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3CA17F2DB100080895B /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 88FC735416114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m */; }; + D05AD3CB17F2DB100080895B /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E58405316F22D7500F588A6 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3CC17F2DB100080895B /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E58405416F22D7500F588A6 /* NSObject+RACDeallocating.m */; }; + D05AD3CD17F2DB100080895B /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = 886CEAE0163DE942007632D1 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3CE17F2DB100080895B /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = 886CEAE1163DE942007632D1 /* NSObject+RACLifting.m */; }; + D05AD3CF17F2DB100080895B /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = 887ACDA6165878A7009190AD /* NSInvocation+RACTypeParsing.m */; }; + D05AD3D017F2DB100080895B /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D486FF1642550100DD7605 /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3D117F2DB100080895B /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D487001642550100DD7605 /* RACStream.m */; }; + D05AD3D217F2DB1D0080895B /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF80415001CA800163A9F /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3D317F2DB1D0080895B /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 88977C3D1512914A00A09EC5 /* RACSignal.m */; }; + D05AD3D417F2DB1D0080895B /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D910CC15F915BD00AD2DDA /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3D517F2DB1D0080895B /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D910CD15F915BD00AD2DDA /* RACSignal+Operations.m */; }; + D05AD3D617F2DB1D0080895B /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D077A16B169B740200057BB1 /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3D717F2DB1D0080895B /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D077A16C169B740200057BB1 /* RACEvent.m */; }; + D05AD3D817F2DB1D0080895B /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 88C5A0231692460A0045EF05 /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3D917F2DB1D0080895B /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C5A025169246140045EF05 /* RACMulticastConnection.m */; }; + D05AD3DA17F2DB1D0080895B /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 886F70281551CF920045D68B /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3DB17F2DB1D0080895B /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 886F70291551CF920045D68B /* RACGroupedSignal.m */; }; + D05AD3DC17F2DB1D0080895B /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 880B9174150B09190008488E /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3DD17F2DB1D0080895B /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 880B9175150B09190008488E /* RACSubject.m */; }; + D05AD3DE17F2DB1D0080895B /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 88D4AB3C1510F6C30011494F /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3DF17F2DB1D0080895B /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D4AB3D1510F6C30011494F /* RACReplaySubject.m */; }; + D05AD3E017F2DB1D0080895B /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 883A84D81513964B006DB4C7 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3E117F2DB1D0080895B /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 883A84D91513964B006DB4C7 /* RACBehaviorSubject.m */; }; + D05AD3E217F2DB230080895B /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 883A84DD1513B5EC006DB4C7 /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3E317F2DB230080895B /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 883A84DE1513B5EC006DB4C7 /* RACDisposable.m */; }; + D05AD3E417F2DB230080895B /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 884476E2152367D100958F44 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3E517F2DB230080895B /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 884476E3152367D100958F44 /* RACScopedDisposable.m */; }; + D05AD3E617F2DB230080895B /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E86A01669304700667F7B /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3E717F2DB230080895B /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E86A11669304700667F7B /* RACCompoundDisposable.m */; }; + D05AD3E817F2DB230080895B /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D028DB72179E53CB00D1042F /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3E917F2DB230080895B /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D028DB73179E53CB00D1042F /* RACSerialDisposable.m */; }; + D05AD3EA17F2DB270080895B /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 882093E91501E6EE00796685 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3EB17F2DB270080895B /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 882093EA1501E6EE00796685 /* RACCommand.m */; }; + D05AD3EE17F2DB4F0080895B /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967671641EF9C00FCFF06 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3EF17F2DB4F0080895B /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967681641EF9C00FCFF06 /* RACSequence.m */; }; + D05AD3F017F2DB4F0080895B /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967621641EF9C00FCFF06 /* RACArraySequence.m */; }; + D05AD3F117F2DB4F0080895B /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967641641EF9C00FCFF06 /* RACDynamicSequence.m */; }; + D05AD3F217F2DB4F0080895B /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F9743F61694A2460024EB82 /* RACEagerSequence.m */; }; + D05AD3F317F2DB4F0080895B /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967661641EF9C00FCFF06 /* RACEmptySequence.m */; }; + D05AD3F417F2DB4F0080895B /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9676A1641EF9C00FCFF06 /* RACStringSequence.m */; }; + D05AD3F517F2DB4F0080895B /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EE284A164D906B006954A4 /* RACSignalSequence.m */; }; + D05AD3F617F2DB4F0080895B /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0307EDC1731AAE100D83211 /* RACTupleSequence.m */; }; + D05AD3F717F2DB4F0080895B /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D07CD7151731BA3900DE2394 /* RACUnarySequence.m */; }; + D05AD3FA17F2DB5D0080895B /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 88E2C6B2153C771C00C7493C /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3FB17F2DB5D0080895B /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 88E2C6B3153C771C00C7493C /* RACScheduler.m */; }; + D05AD3FC17F2DB5D0080895B /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E87AA16695C5600667F7B /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3FD17F2DB5D0080895B /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = 882D07201761521B009EDA69 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD3FE17F2DB5D0080895B /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87AB16695C5600667F7B /* RACQueueScheduler.m */; }; + D05AD3FF17F2DB5D0080895B /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 882D071717614FA7009EDA69 /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD40017F2DB5D0080895B /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 882D071817614FA7009EDA69 /* RACTargetQueueScheduler.m */; }; + D05AD40217F2DB5D0080895B /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87B116695EDF00667F7B /* RACImmediateScheduler.m */; }; + D05AD40317F2DB5D0080895B /* RACSubscriptionScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E87C21669635F00667F7B /* RACSubscriptionScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD40417F2DB5D0080895B /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 881E87C31669636000667F7B /* RACSubscriptionScheduler.m */; }; + D05AD40517F2DB5D0080895B /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D00930771788AB7B00EE7E8B /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD40617F2DB5D0080895B /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D00930781788AB7B00EE7E8B /* RACTestScheduler.m */; }; + D05AD40717F2DB6A0080895B /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967571641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD40817F2DB6A0080895B /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967581641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m */; }; + D05AD40917F2DB6A0080895B /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8716090C1500636B49 /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD40A17F2DB6A0080895B /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8816090C1500636B49 /* NSData+RACSupport.m */; }; + D05AD40B17F2DB6A0080895B /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967591641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD40C17F2DB6A0080895B /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675A1641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m */; }; + D05AD40D17F2DB6A0080895B /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F773DE8169B46670023069D /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD40E17F2DB6A0080895B /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F773DE9169B46670023069D /* NSEnumerator+RACSequenceAdditions.m */; }; + D05AD40F17F2DB6A0080895B /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8916090C1500636B49 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41017F2DB6A0080895B /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8A16090C1500636B49 /* NSFileHandle+RACSupport.m */; }; + D05AD41117F2DB6A0080895B /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8B16090C1500636B49 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41217F2DB6A0080895B /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8C16090C1500636B49 /* NSNotificationCenter+RACSupport.m */; }; + D05AD41317F2DB6A0080895B /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D243B61741FA0E004359C6 /* NSObject+RACDescription.m */; }; + D05AD41417F2DB6A0080895B /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 880D7A5816F7B351004A3361 /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41517F2DB6A0080895B /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 880D7A5916F7B351004A3361 /* NSObject+RACSelectorSignal.m */; }; + D05AD41617F2DB6A0080895B /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675B1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41717F2DB6A0080895B /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675C1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m */; }; + D05AD41817F2DB6A0080895B /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675D1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41917F2DB6A0080895B /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675E1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m */; }; + D05AD41A17F2DB6A0080895B /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675F1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41B17F2DB6A0080895B /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967601641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m */; }; + D05AD41C17F2DB6A0080895B /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88442C8D16090C1500636B49 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41D17F2DB6A0080895B /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 88442C8E16090C1500636B49 /* NSString+RACSupport.m */; }; + D05AD41E17F2DB6E0080895B /* NSControl+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 882093E61501E6CB00796685 /* NSControl+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD41F17F2DB6E0080895B /* NSControl+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 882093E71501E6CB00796685 /* NSControl+RACCommandSupport.m */; }; + D05AD42017F2DB6E0080895B /* NSControl+RACTextSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A0B03916EAA9AC00C47593 /* NSControl+RACTextSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD42117F2DB6E0080895B /* NSControl+RACTextSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0B03A16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.m */; }; + D05AD42217F2DB6E0080895B /* NSObject+RACAppKitBindings.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F440D1153DADEA0097B4C3 /* NSObject+RACAppKitBindings.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD42317F2DB6E0080895B /* NSObject+RACAppKitBindings.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F440D2153DADEA0097B4C3 /* NSObject+RACAppKitBindings.m */; }; + D05AD42417F2DB6E0080895B /* NSText+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A0B01316EAA3D100C47593 /* NSText+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD42517F2DB6E0080895B /* NSText+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0B01416EAA3D100C47593 /* NSText+RACSignalSupport.m */; }; + D05AD42617F2DB840080895B /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F6FE8511692568A00A8D7A6 /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD42717F2DB840080895B /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F6FE8521692568A00A8D7A6 /* RACChannel.m */; }; + D05AD42817F2DB840080895B /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F45A883168CFA3E00B58A2B /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD42917F2DB840080895B /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F45A884168CFA3E00B58A2B /* RACKVOChannel.m */; }; + D05AD42B17F2DB840080895B /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CDF82915008BB900163A9F /* NSObject+RACKVOWrapper.m */; }; + D05AD42C17F2DB840080895B /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDC35021736F54700792E52 /* NSString+RACKeyPathUtilities.m */; }; + D05AD42D17F2DB840080895B /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 8837EA1516A5A33300FC3CDF /* RACKVOTrampoline.m */; }; + D05AD42E17F2DB840080895B /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF82C15008C0500163A9F /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05AD42F17F2DB840080895B /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = 886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */; }; + D05AD43017F2DB840080895B /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DA309615071CBA00C19D0F /* RACValueTransformer.m */; }; + D05AD43117F2DB950080895B /* RACObjCRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = A1FCC371156754A7008C9686 /* RACObjCRuntime.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + D05AD43217F2DBCA0080895B /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03525D317E2EBC90099CBAB /* RACSignalProvider.d */; }; + D05F9D3717984EC000FD7982 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D05F9D3417984EC000FD7982 /* EXTRuntimeExtensions.m */; }; + D05F9D3817984EC000FD7982 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D05F9D3417984EC000FD7982 /* EXTRuntimeExtensions.m */; }; + D066C796176D262500C242D2 /* UIControlRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D066C795176D262500C242D2 /* UIControlRACSupportSpec.m */; }; + D066C79D176D263D00C242D2 /* RACTestUIButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D066C79C176D263D00C242D2 /* RACTestUIButton.m */; }; + D0700F4C1672994D00D7CD30 /* NSNotificationCenterRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0700F4B1672994D00D7CD30 /* NSNotificationCenterRACSupportSpec.m */; }; + D070CBC517FB5E370017F121 /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D020F3DA17F6A3E40092BED2 /* RACCompoundDisposableProvider.d */; }; + D07200251788C57200987F70 /* RACTestSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D07200241788C57200987F70 /* RACTestSchedulerSpec.m */; }; + D07200261788C57200987F70 /* RACTestSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D07200241788C57200987F70 /* RACTestSchedulerSpec.m */; }; + D075A72A17BCB7E100C24FB7 /* RACControlCommandExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D075A72917BCB7E100C24FB7 /* RACControlCommandExamples.m */; }; + D075A72B17BCB7E100C24FB7 /* RACControlCommandExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D075A72917BCB7E100C24FB7 /* RACControlCommandExamples.m */; }; + D077A16D169B740200057BB1 /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D077A16B169B740200057BB1 /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D077A16E169B740200057BB1 /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D077A16B169B740200057BB1 /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D077A16F169B740200057BB1 /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D077A16C169B740200057BB1 /* RACEvent.m */; }; + D077A170169B740200057BB1 /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D077A16C169B740200057BB1 /* RACEvent.m */; }; + D077A172169B79A900057BB1 /* RACEventSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D077A171169B79A900057BB1 /* RACEventSpec.m */; }; + D07CD7181731BA3900DE2394 /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D07CD7151731BA3900DE2394 /* RACUnarySequence.m */; }; + D07CD7191731BA3900DE2394 /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D07CD7151731BA3900DE2394 /* RACUnarySequence.m */; }; + D0870C6F16884A0600D0E11D /* RACBacktraceSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0870C6E16884A0600D0E11D /* RACBacktraceSpec.m */; }; + D08FF264169A32D100743C6D /* ReactiveCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 88037F8C15056328001A5B19 /* ReactiveCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF265169A32DC00743C6D /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF7FA150019CA00163A9F /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF267169A330000743C6D /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 881B37CA152260BF0079220B /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF268169A330000743C6D /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B76F8C153726B00053EAE2 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF269169A330000743C6D /* RACBacktrace.h in Headers */ = {isa = PBXBuildFile; fileRef = D02538A115E2D7FB005BACB8 /* RACBacktrace.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF26A169A330000743C6D /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = 88FC735316114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF26B169A330000743C6D /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = 886CEAE0163DE942007632D1 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF26C169A331A00743C6D /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D486FF1642550100DD7605 /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF26D169A331A00743C6D /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF80415001CA800163A9F /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF26E169A331A00743C6D /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D910CC15F915BD00AD2DDA /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF26F169A331A00743C6D /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 88C5A0231692460A0045EF05 /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF270169A331A00743C6D /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 886F70281551CF920045D68B /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF271169A331A00743C6D /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 880B9174150B09190008488E /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF272169A331A00743C6D /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 88D4AB3C1510F6C30011494F /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF273169A331A00743C6D /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 883A84D81513964B006DB4C7 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF274169A331A00743C6D /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 883A84DD1513B5EC006DB4C7 /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF275169A331A00743C6D /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 884476E2152367D100958F44 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF276169A331A00743C6D /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 881E86A01669304700667F7B /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF277169A331B00743C6D /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 882093E91501E6EE00796685 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF278169A331B00743C6D /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967671641EF9C00FCFF06 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF27F169A331B00743C6D /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 88E2C6B2153C771C00C7493C /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF280169A333400743C6D /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967571641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF281169A333400743C6D /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967591641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF282169A333400743C6D /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675B1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF283169A333400743C6D /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675D1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF284169A333400743C6D /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675F1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF285169A333400743C6D /* UIControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F4425F153DC0450097B4C3 /* UIControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF286169A333400743C6D /* UITextField+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F44264153DCAC50097B4C3 /* UITextField+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF287169A333400743C6D /* UITextView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = A1FCC27215666AA3008C9686 /* UITextView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D08FF289169A333400743C6D /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CDF82C15008C0500163A9F /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D090767F17FBEADE00EB087A /* NSURLConnection+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D090767D17FBEADE00EB087A /* NSURLConnection+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D090768017FBEADE00EB087A /* NSURLConnection+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D090767D17FBEADE00EB087A /* NSURLConnection+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D090768117FBEADE00EB087A /* NSURLConnection+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D090767D17FBEADE00EB087A /* NSURLConnection+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D090768217FBEADE00EB087A /* NSURLConnection+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D090767E17FBEADE00EB087A /* NSURLConnection+RACSupport.m */; }; + D090768317FBEADE00EB087A /* NSURLConnection+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D090767E17FBEADE00EB087A /* NSURLConnection+RACSupport.m */; }; + D090768417FBEADE00EB087A /* NSURLConnection+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D090767E17FBEADE00EB087A /* NSURLConnection+RACSupport.m */; }; + D090768A17FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D090768917FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m */; }; + D090768B17FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D090768917FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m */; }; + D090768D17FBED2E00EB087A /* test-data.json in Resources */ = {isa = PBXBuildFile; fileRef = D090768C17FBED2E00EB087A /* test-data.json */; }; + D090768E17FBED2E00EB087A /* test-data.json in Resources */ = {isa = PBXBuildFile; fileRef = D090768C17FBED2E00EB087A /* test-data.json */; }; + D094E44917775AF200906BF7 /* EXTKeyPathCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = D094E44517775AF200906BF7 /* EXTKeyPathCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D094E44A17775AF200906BF7 /* EXTKeyPathCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = D094E44517775AF200906BF7 /* EXTKeyPathCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D094E44B17775AF200906BF7 /* EXTScope.h in Headers */ = {isa = PBXBuildFile; fileRef = D094E44617775AF200906BF7 /* EXTScope.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D094E44C17775AF200906BF7 /* EXTScope.h in Headers */ = {isa = PBXBuildFile; fileRef = D094E44617775AF200906BF7 /* EXTScope.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D094E44F17775AF200906BF7 /* metamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = D094E44817775AF200906BF7 /* metamacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D094E45017775AF200906BF7 /* metamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = D094E44817775AF200906BF7 /* metamacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0A0B01516EAA3D100C47593 /* NSText+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A0B01316EAA3D100C47593 /* NSText+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0A0B01616EAA3D100C47593 /* NSText+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0B01416EAA3D100C47593 /* NSText+RACSignalSupport.m */; }; + D0A0B01816EAA5CC00C47593 /* NSTextRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0B01716EAA5CC00C47593 /* NSTextRACSupportSpec.m */; }; + D0A0B03B16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A0B03916EAA9AC00C47593 /* NSControl+RACTextSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0A0B03C16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0B03A16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.m */; }; + D0A0E226176A84DB007273ED /* RACDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0E225176A84DA007273ED /* RACDisposableSpec.m */; }; + D0A0E227176A84DB007273ED /* RACDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0E225176A84DA007273ED /* RACDisposableSpec.m */; }; + D0A0E230176A8CD6007273ED /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0E22D176A8CD6007273ED /* RACPassthroughSubscriber.m */; }; + D0A0E231176A8CD6007273ED /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A0E22D176A8CD6007273ED /* RACPassthroughSubscriber.m */; }; + D0C55CE217759559008CDDCA /* RACDelegateProxySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C55CE117759559008CDDCA /* RACDelegateProxySpec.m */; }; + D0C70EC616659333005AAD03 /* RACSubscriberExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70EC516659333005AAD03 /* RACSubscriberExamples.m */; }; + D0C70EC8166595AD005AAD03 /* RACSubscriberSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70EC7166595AD005AAD03 /* RACSubscriberSpec.m */; }; + D0C70F90164337A2007027B4 /* RACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70F8F164337A2007027B4 /* RACSequenceAdditionsSpec.m */; }; + D0C70F93164337E3007027B4 /* RACSequenceExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C70F92164337E3007027B4 /* RACSequenceExamples.m */; }; + D0D243BD1741FA13004359C6 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D243B61741FA0E004359C6 /* NSObject+RACDescription.m */; }; + D0D243BE1741FA13004359C6 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D243B61741FA0E004359C6 /* NSObject+RACDescription.m */; }; + D0D487011642550100DD7605 /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D486FF1642550100DD7605 /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0D487031642550100DD7605 /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D487001642550100DD7605 /* RACStream.m */; }; + D0D487041642550100DD7605 /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D487001642550100DD7605 /* RACStream.m */; }; + D0D487061642651400DD7605 /* RACSequenceSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D487051642651400DD7605 /* RACSequenceSpec.m */; }; + D0D910CE15F915BD00AD2DDA /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D910CC15F915BD00AD2DDA /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0D910D015F915BD00AD2DDA /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D910CD15F915BD00AD2DDA /* RACSignal+Operations.m */; }; + D0D910D115F915BD00AD2DDA /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D910CD15F915BD00AD2DDA /* RACSignal+Operations.m */; }; + D0DFBCCE15DD6D40009DADB3 /* RACBacktrace.m in Sources */ = {isa = PBXBuildFile; fileRef = D0DFBCCD15DD6D40009DADB3 /* RACBacktrace.m */; }; + D0DFBCCF15DD6D40009DADB3 /* RACBacktrace.m in Sources */ = {isa = PBXBuildFile; fileRef = D0DFBCCD15DD6D40009DADB3 /* RACBacktrace.m */; }; + D0DFBCD015DD70CC009DADB3 /* ReactiveCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 88037F8315056328001A5B19 /* ReactiveCocoa.framework */; }; + D0E9676B1641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967571641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0E9676D1641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967581641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m */; }; + D0E9676E1641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967581641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m */; }; + D0E9676F1641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967591641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0E967711641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675A1641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m */; }; + D0E967721641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675A1641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m */; }; + D0E967731641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675B1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0E967751641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675C1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m */; }; + D0E967761641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675C1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m */; }; + D0E967771641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675D1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0E967791641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675E1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m */; }; + D0E9677A1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9675E1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m */; }; + D0E9677B1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E9675F1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0E9677D1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967601641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m */; }; + D0E9677E1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967601641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m */; }; + D0E967811641EF9C00FCFF06 /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967621641EF9C00FCFF06 /* RACArraySequence.m */; }; + D0E967821641EF9C00FCFF06 /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967621641EF9C00FCFF06 /* RACArraySequence.m */; }; + D0E967851641EF9C00FCFF06 /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967641641EF9C00FCFF06 /* RACDynamicSequence.m */; }; + D0E967861641EF9C00FCFF06 /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967641641EF9C00FCFF06 /* RACDynamicSequence.m */; }; + D0E967891641EF9C00FCFF06 /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967661641EF9C00FCFF06 /* RACEmptySequence.m */; }; + D0E9678A1641EF9C00FCFF06 /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967661641EF9C00FCFF06 /* RACEmptySequence.m */; }; + D0E9678B1641EF9C00FCFF06 /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E967671641EF9C00FCFF06 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0E9678D1641EF9C00FCFF06 /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967681641EF9C00FCFF06 /* RACSequence.m */; }; + D0E9678E1641EF9C00FCFF06 /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E967681641EF9C00FCFF06 /* RACSequence.m */; }; + D0E967911641EF9C00FCFF06 /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9676A1641EF9C00FCFF06 /* RACStringSequence.m */; }; + D0E967921641EF9C00FCFF06 /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E9676A1641EF9C00FCFF06 /* RACStringSequence.m */; }; + D0EDE76716968AB10072A780 /* RACPropertySignalExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDE76616968AB10072A780 /* RACPropertySignalExamples.m */; }; + D0EE284D164D906B006954A4 /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EE284A164D906B006954A4 /* RACSignalSequence.m */; }; + D0EE284E164D906B006954A4 /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EE284A164D906B006954A4 /* RACSignalSequence.m */; }; + D0F117C8179F0A95006CE68F /* libReactiveCocoa-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 88F440AB153DAC820097B4C3 /* libReactiveCocoa-iOS.a */; }; + D0F117CC179F0B97006CE68F /* UITableViewCellRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F117CB179F0B97006CE68F /* UITableViewCellRACSupportSpec.m */; }; + D40D7AAB18E22B5E0065BB70 /* libExpecta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D40D7AAA18E22B5E0065BB70 /* libExpecta.a */; }; + D40D7AAD18E22B7E0065BB70 /* libSpecta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D40D7AAC18E22B7E0065BB70 /* libSpecta.a */; }; + D40D7AAF18E22BE30065BB70 /* libExpecta-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D40D7AAE18E22BE30065BB70 /* libExpecta-iOS.a */; }; + D40D7AB118E22BF60065BB70 /* libSpecta-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D40D7AB018E22BF60065BB70 /* libSpecta-iOS.a */; }; + D40D7AB218E22EC60065BB70 /* libSpecta-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D40D7AB018E22BF60065BB70 /* libSpecta-iOS.a */; }; + D40D7AB318E22EC90065BB70 /* libExpecta-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D40D7AAE18E22BE30065BB70 /* libExpecta-iOS.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 1860F435177C91B500C7B3C9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 88CDF7B215000FCE00163A9F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1860F411177C91B500C7B3C9; + remoteInfo = "ReactiveCocoa-iOS-UIKitTestHost"; + }; + 88037FDA150564E9001A5B19 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 88CDF7B215000FCE00163A9F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 88037F8215056328001A5B19; + remoteInfo = ReactiveCocoa; + }; + D0ED9DB517501806003859A6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 88CDF7B215000FCE00163A9F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 88F440AA153DAC820097B4C3; + remoteInfo = "ReactiveCocoa-iOS"; + }; + D0F117C2179F0A91006CE68F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 88CDF7B215000FCE00163A9F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 88F440AA153DAC820097B4C3; + remoteInfo = "ReactiveCocoa-iOS"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8820937F1501C94E00796685 /* Copy Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D0DFBCD015DD70CC009DADB3 /* ReactiveCocoa.framework in Copy Frameworks */, + ); + name = "Copy Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1668027817FE75E900C724B4 /* UICollectionReusableView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionReusableView+RACSignalSupport.h"; sourceTree = "<group>"; }; + 1668027917FE75E900C724B4 /* UICollectionReusableView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionReusableView+RACSignalSupport.m"; sourceTree = "<group>"; }; + 1668028117FE774800C724B4 /* UICollectionReusableViewRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICollectionReusableViewRACSupportSpec.m; sourceTree = "<group>"; }; + 1860F412177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ReactiveCocoa-iOS-UIKitTestHost.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1860F413177C91B500C7B3C9 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 1860F415177C91B500C7B3C9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 1860F417177C91B500C7B3C9 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + 1860F41B177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ReactiveCocoa-iOS-UIKitTestHost-Info.plist"; sourceTree = "<group>"; }; + 1860F41D177C91B500C7B3C9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 1860F41F177C91B500C7B3C9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; + 1860F421177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactiveCocoa-iOS-UIKitTestHost-Prefix.pch"; sourceTree = "<group>"; }; + 1860F422177C91B500C7B3C9 /* RACAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RACAppDelegate.h; sourceTree = "<group>"; }; + 1860F423177C91B500C7B3C9 /* RACAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RACAppDelegate.m; sourceTree = "<group>"; }; + 1860F425177C91B500C7B3C9 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = "<group>"; }; + 1860F427177C91B500C7B3C9 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = "<group>"; }; + 1860F429177C91B500C7B3C9 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; }; + 1860F430177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHostTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ReactiveCocoa-iOS-UIKitTestHostTests.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1860F431177C91B500C7B3C9 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + 1860F43B177C91B500C7B3C9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 1860F455177C96B200C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist"; path = "ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist"; sourceTree = SOURCE_ROOT; }; + 1860F457177C972E00C7B3C9 /* ReactiveCocoa-iOS-UIKitTest-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "ReactiveCocoa-iOS-UIKitTest-Prefix.pch"; path = "ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTest-Prefix.pch"; sourceTree = SOURCE_ROOT; }; + 1E89337F171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSObjectRACPropertySubscribingExamples.h; sourceTree = "<group>"; }; + 1E893380171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACPropertySubscribingExamples.m; sourceTree = "<group>"; }; + 1EC06B15173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIGestureRecognizer+RACSignalSupport.h"; sourceTree = "<group>"; }; + 1EC06B16173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIGestureRecognizer+RACSignalSupport.m"; sourceTree = "<group>"; }; + 27A887C71703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+RACCommandSupport.h"; sourceTree = "<group>"; }; + 27A887C81703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+RACCommandSupport.m"; sourceTree = "<group>"; }; + 4925E805181BCC71000B2FEE /* NSControllerRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSControllerRACSupportSpec.m; sourceTree = "<group>"; }; + 554D9E5B181064E200F21262 /* UIRefreshControl+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIRefreshControl+RACCommandSupport.h"; sourceTree = "<group>"; }; + 554D9E5C181064E200F21262 /* UIRefreshControl+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIRefreshControl+RACCommandSupport.m"; sourceTree = "<group>"; }; + 5564542318107275002BD2E4 /* UIRefreshControlRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIRefreshControlRACSupportSpec.m; sourceTree = "<group>"; }; + 557A4B58177648C7008EF796 /* UIActionSheet+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIActionSheet+RACSignalSupport.h"; sourceTree = "<group>"; }; + 557A4B59177648C7008EF796 /* UIActionSheet+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIActionSheet+RACSignalSupport.m"; sourceTree = "<group>"; }; + 5EE9A7911760D61300EAF5A2 /* UIButton+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+RACCommandSupport.h"; sourceTree = "<group>"; }; + 5EE9A7921760D61300EAF5A2 /* UIButton+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+RACCommandSupport.m"; sourceTree = "<group>"; }; + 5EE9A79A1760D88500EAF5A2 /* UIButtonRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIButtonRACSupportSpec.m; sourceTree = "<group>"; }; + 5F016DF217B10AA8002EEC69 /* UIControl+RACSignalSupportPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+RACSignalSupportPrivate.h"; sourceTree = "<group>"; }; + 5F016DF317B10AA8002EEC69 /* UIControl+RACSignalSupportPrivate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+RACSignalSupportPrivate.m"; sourceTree = "<group>"; }; + 5F2447AC167E87C50062180C /* RACKVOChannelSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOChannelSpec.m; sourceTree = "<group>"; }; + 5F45A883168CFA3E00B58A2B /* RACKVOChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACKVOChannel.h; sourceTree = "<group>"; }; + 5F45A884168CFA3E00B58A2B /* RACKVOChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOChannel.m; sourceTree = "<group>"; }; + 5F6FE8511692568A00A8D7A6 /* RACChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACChannel.h; sourceTree = "<group>"; }; + 5F6FE8521692568A00A8D7A6 /* RACChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACChannel.m; sourceTree = "<group>"; }; + 5F70B2AD17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIDatePicker+RACSignalSupport.h"; sourceTree = "<group>"; }; + 5F70B2AE17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDatePicker+RACSignalSupport.m"; sourceTree = "<group>"; }; + 5F70B2B617AB1856009AEDF9 /* UISegmentedControl+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UISegmentedControl+RACSignalSupport.h"; sourceTree = "<group>"; }; + 5F70B2B717AB1856009AEDF9 /* UISegmentedControl+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UISegmentedControl+RACSignalSupport.m"; sourceTree = "<group>"; }; + 5F70B2B817AB1856009AEDF9 /* UISlider+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UISlider+RACSignalSupport.h"; sourceTree = "<group>"; }; + 5F70B2B917AB1856009AEDF9 /* UISlider+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UISlider+RACSignalSupport.m"; sourceTree = "<group>"; }; + 5F70B2BA17AB1857009AEDF9 /* UIStepper+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIStepper+RACSignalSupport.h"; sourceTree = "<group>"; }; + 5F70B2BB17AB1857009AEDF9 /* UIStepper+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIStepper+RACSignalSupport.m"; sourceTree = "<group>"; }; + 5F70B2BC17AB1857009AEDF9 /* UISwitch+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UISwitch+RACSignalSupport.h"; sourceTree = "<group>"; }; + 5F70B2BD17AB1857009AEDF9 /* UISwitch+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UISwitch+RACSignalSupport.m"; sourceTree = "<group>"; }; + 5F773DE8169B46670023069D /* NSEnumerator+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSEnumerator+RACSequenceAdditions.h"; sourceTree = "<group>"; }; + 5F773DE9169B46670023069D /* NSEnumerator+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSEnumerator+RACSequenceAdditions.m"; sourceTree = "<group>"; }; + 5F773DEF169B48830023069D /* NSEnumeratorRACSequenceAdditionsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSEnumeratorRACSequenceAdditionsSpec.m; sourceTree = "<group>"; }; + 5F7EFECC168FBC4B0037E500 /* RACChannelExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACChannelExamples.h; sourceTree = "<group>"; }; + 5F7EFECD168FBC4B0037E500 /* RACChannelExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACChannelExamples.m; sourceTree = "<group>"; }; + 5F7EFECE168FBC4B0037E500 /* RACChannelSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACChannelSpec.m; sourceTree = "<group>"; }; + 5F9743F51694A2460024EB82 /* RACEagerSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEagerSequence.h; sourceTree = "<group>"; }; + 5F9743F61694A2460024EB82 /* RACEagerSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEagerSequence.m; sourceTree = "<group>"; }; + 5FAF5223174D4C2000CAC810 /* ReactiveCocoaTests-iOS.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ReactiveCocoaTests-iOS.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5FAF5261174D4D8E00CAC810 /* UIBarButtonItemRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIBarButtonItemRACSupportSpec.m; sourceTree = "<group>"; }; + 5FAF5288174E9CD200CAC810 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + 5FD7DC7B174F9EEB008710B4 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 5FDC35011736F54600792E52 /* NSString+RACKeyPathUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RACKeyPathUtilities.h"; sourceTree = "<group>"; }; + 5FDC35021736F54700792E52 /* NSString+RACKeyPathUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RACKeyPathUtilities.m"; sourceTree = "<group>"; }; + 5FDC350E1736F81800792E52 /* NSStringRACKeyPathUtilitiesSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSStringRACKeyPathUtilitiesSpec.m; sourceTree = "<group>"; }; + 6E58405316F22D7500F588A6 /* NSObject+RACDeallocating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACDeallocating.h"; sourceTree = "<group>"; }; + 6E58405416F22D7500F588A6 /* NSObject+RACDeallocating.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACDeallocating.m"; sourceTree = "<group>"; }; + 6E58405E16F3414200F588A6 /* NSObjectRACDeallocatingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACDeallocatingSpec.m; sourceTree = "<group>"; }; + 7479F6E3186177D200575CDB /* RACIndexSetSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACIndexSetSequence.h; sourceTree = "<group>"; }; + 7479F6E4186177D200575CDB /* RACIndexSetSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACIndexSetSequence.m; sourceTree = "<group>"; }; + 74F17316186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+RACSequenceAdditions.h"; sourceTree = "<group>"; }; + 74F17317186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+RACSequenceAdditions.m"; sourceTree = "<group>"; }; + 8801E7501644BDE200A155FE /* NSObjectRACLiftingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACLiftingSpec.m; sourceTree = "<group>"; }; + 88037F8315056328001A5B19 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 88037F8C15056328001A5B19 /* ReactiveCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactiveCocoa.h; sourceTree = "<group>"; }; + 8803C010166732BA00C36839 /* RACSchedulerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSchedulerSpec.m; sourceTree = "<group>"; }; + 880B9174150B09190008488E /* RACSubject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubject.h; sourceTree = "<group>"; }; + 880B9175150B09190008488E /* RACSubject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubject.m; sourceTree = "<group>"; }; + 880D7A5816F7B351004A3361 /* NSObject+RACSelectorSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACSelectorSignal.h"; sourceTree = "<group>"; }; + 880D7A5916F7B351004A3361 /* NSObject+RACSelectorSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACSelectorSignal.m"; sourceTree = "<group>"; }; + 880D7A6516F7BB1A004A3361 /* NSObjectRACSelectorSignalSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACSelectorSignalSpec.m; sourceTree = "<group>"; }; + 880D7A6716F7BCC7004A3361 /* RACSubclassObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubclassObject.h; sourceTree = "<group>"; }; + 880D7A6816F7BCC7004A3361 /* RACSubclassObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubclassObject.m; sourceTree = "<group>"; }; + 881B37CA152260BF0079220B /* RACUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACUnit.h; sourceTree = "<group>"; }; + 881B37CB152260BF0079220B /* RACUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACUnit.m; sourceTree = "<group>"; }; + 881E86A01669304700667F7B /* RACCompoundDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACCompoundDisposable.h; sourceTree = "<group>"; }; + 881E86A11669304700667F7B /* RACCompoundDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACCompoundDisposable.m; sourceTree = "<group>"; }; + 881E86B91669350B00667F7B /* RACCompoundDisposableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACCompoundDisposableSpec.m; sourceTree = "<group>"; }; + 881E87AA16695C5600667F7B /* RACQueueScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACQueueScheduler.h; sourceTree = "<group>"; }; + 881E87AB16695C5600667F7B /* RACQueueScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACQueueScheduler.m; sourceTree = "<group>"; }; + 881E87B016695EDF00667F7B /* RACImmediateScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACImmediateScheduler.h; sourceTree = "<group>"; }; + 881E87B116695EDF00667F7B /* RACImmediateScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACImmediateScheduler.m; sourceTree = "<group>"; }; + 881E87C21669635F00667F7B /* RACSubscriptionScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriptionScheduler.h; sourceTree = "<group>"; }; + 881E87C31669636000667F7B /* RACSubscriptionScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriptionScheduler.m; sourceTree = "<group>"; }; + 8820937B1501C8A600796685 /* RACSignalSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSignalSpec.m; sourceTree = "<group>"; }; + 882093E61501E6CB00796685 /* NSControl+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSControl+RACCommandSupport.h"; sourceTree = "<group>"; }; + 882093E71501E6CB00796685 /* NSControl+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSControl+RACCommandSupport.m"; sourceTree = "<group>"; }; + 882093E91501E6EE00796685 /* RACCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RACCommand.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 882093EA1501E6EE00796685 /* RACCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RACCommand.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 882CCA1D15F1564D00937D6E /* RACCommandSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACCommandSpec.m; sourceTree = "<group>"; }; + 882D071717614FA7009EDA69 /* RACTargetQueueScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTargetQueueScheduler.h; sourceTree = "<group>"; }; + 882D071817614FA7009EDA69 /* RACTargetQueueScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTargetQueueScheduler.m; sourceTree = "<group>"; }; + 882D07201761521B009EDA69 /* RACQueueScheduler+Subclass.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RACQueueScheduler+Subclass.h"; sourceTree = "<group>"; }; + 88302BFB1762A9E6003633BD /* RACTestExampleScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestExampleScheduler.h; sourceTree = "<group>"; }; + 88302BFC1762A9E6003633BD /* RACTestExampleScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestExampleScheduler.m; sourceTree = "<group>"; }; + 88302C2D1762C180003633BD /* RACTargetQueueSchedulerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTargetQueueSchedulerSpec.m; sourceTree = "<group>"; }; + 8837EA1416A5A33300FC3CDF /* RACKVOTrampoline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACKVOTrampoline.h; sourceTree = "<group>"; }; + 8837EA1516A5A33300FC3CDF /* RACKVOTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOTrampoline.m; sourceTree = "<group>"; }; + 883A84D81513964B006DB4C7 /* RACBehaviorSubject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACBehaviorSubject.h; sourceTree = "<group>"; }; + 883A84D91513964B006DB4C7 /* RACBehaviorSubject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RACBehaviorSubject.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 883A84DD1513B5EC006DB4C7 /* RACDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDisposable.h; sourceTree = "<group>"; }; + 883A84DE1513B5EC006DB4C7 /* RACDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDisposable.m; sourceTree = "<group>"; }; + 88442A321608A9AD00636B49 /* RACTestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestObject.h; sourceTree = "<group>"; }; + 88442A331608A9AD00636B49 /* RACTestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestObject.m; sourceTree = "<group>"; }; + 88442C8716090C1500636B49 /* NSData+RACSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+RACSupport.h"; sourceTree = "<group>"; }; + 88442C8816090C1500636B49 /* NSData+RACSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+RACSupport.m"; sourceTree = "<group>"; }; + 88442C8916090C1500636B49 /* NSFileHandle+RACSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSFileHandle+RACSupport.h"; sourceTree = "<group>"; }; + 88442C8A16090C1500636B49 /* NSFileHandle+RACSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSFileHandle+RACSupport.m"; sourceTree = "<group>"; }; + 88442C8B16090C1500636B49 /* NSNotificationCenter+RACSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+RACSupport.h"; sourceTree = "<group>"; }; + 88442C8C16090C1500636B49 /* NSNotificationCenter+RACSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSNotificationCenter+RACSupport.m"; sourceTree = "<group>"; }; + 88442C8D16090C1500636B49 /* NSString+RACSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+RACSupport.h"; sourceTree = "<group>"; }; + 88442C8E16090C1500636B49 /* NSString+RACSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+RACSupport.m"; sourceTree = "<group>"; }; + 884476E2152367D100958F44 /* RACScopedDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACScopedDisposable.h; sourceTree = "<group>"; }; + 884476E3152367D100958F44 /* RACScopedDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACScopedDisposable.m; sourceTree = "<group>"; }; + 884848B515F658B800B11BD0 /* NSControlRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSControlRACSupportSpec.m; sourceTree = "<group>"; }; + 8851A38A16161D500050D47F /* NSObjectRACPropertySubscribingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACPropertySubscribingSpec.m; sourceTree = "<group>"; }; + 8857BB81152A27A9009804CC /* NSObject+RACKVOWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACKVOWrapper.h"; sourceTree = "<group>"; }; + 886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACPropertySubscribing.m"; sourceTree = "<group>"; }; + 886CEACC163DE669007632D1 /* RACBlockTrampolineSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACBlockTrampolineSpec.m; sourceTree = "<group>"; }; + 886CEAE0163DE942007632D1 /* NSObject+RACLifting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACLifting.h"; sourceTree = "<group>"; }; + 886CEAE1163DE942007632D1 /* NSObject+RACLifting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACLifting.m"; sourceTree = "<group>"; }; + 886D98581667C86D00F22541 /* RACScheduler+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RACScheduler+Private.h"; sourceTree = "<group>"; }; + 886F70281551CF920045D68B /* RACGroupedSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RACGroupedSignal.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 886F70291551CF920045D68B /* RACGroupedSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACGroupedSignal.m; sourceTree = "<group>"; }; + 887ACDA5165878A7009190AD /* NSInvocation+RACTypeParsing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSInvocation+RACTypeParsing.h"; sourceTree = "<group>"; }; + 887ACDA6165878A7009190AD /* NSInvocation+RACTypeParsing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSInvocation+RACTypeParsing.m"; sourceTree = "<group>"; }; + 888439A11634E10D00DED0DB /* RACBlockTrampoline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACBlockTrampoline.h; sourceTree = "<group>"; }; + 888439A21634E10D00DED0DB /* RACBlockTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACBlockTrampoline.m; sourceTree = "<group>"; }; + 8884DD641756ACF600F6C379 /* RACSignalStartExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSignalStartExamples.m; sourceTree = "<group>"; }; + 8884DD6A1756AD3600F6C379 /* RACSignalStartExamples.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RACSignalStartExamples.h; sourceTree = "<group>"; }; + 88977C3D1512914A00A09EC5 /* RACSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSignal.m; sourceTree = "<group>"; }; + 889D0A7F15974B2A00F833E3 /* RACSubjectSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubjectSpec.m; sourceTree = "<group>"; }; + 88B76F8C153726B00053EAE2 /* RACTuple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTuple.h; sourceTree = "<group>"; }; + 88B76F8D153726B00053EAE2 /* RACTuple.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTuple.m; sourceTree = "<group>"; }; + 88C5A0231692460A0045EF05 /* RACMulticastConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACMulticastConnection.h; sourceTree = "<group>"; }; + 88C5A025169246140045EF05 /* RACMulticastConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACMulticastConnection.m; sourceTree = "<group>"; }; + 88C5A02816924BFC0045EF05 /* RACMulticastConnectionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACMulticastConnectionSpec.m; sourceTree = "<group>"; }; + 88CDF7BF15000FCE00163A9F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 88CDF7C315000FCE00163A9F /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + 88CDF7C415000FCE00163A9F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 88CDF7C715000FCE00163A9F /* ReactiveCocoa-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "ReactiveCocoa-Info.plist"; sourceTree = "<group>"; }; + 88CDF7C915000FCE00163A9F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 88CDF7CD15000FCE00163A9F /* ReactiveCocoa-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactiveCocoa-Prefix.pch"; sourceTree = "<group>"; }; + 88CDF7DC15000FCF00163A9F /* ReactiveCocoaTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveCocoaTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + 88CDF7DD15000FCF00163A9F /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + 88CDF7E415000FCF00163A9F /* ReactiveCocoaTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "ReactiveCocoaTests-Info.plist"; sourceTree = "<group>"; }; + 88CDF7E615000FCF00163A9F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 88CDF7FA150019CA00163A9F /* RACSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriber.h; sourceTree = "<group>"; }; + 88CDF7FB150019CA00163A9F /* RACSubscriber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriber.m; sourceTree = "<group>"; }; + 88CDF80415001CA800163A9F /* RACSignal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RACSignal.h; sourceTree = "<group>"; }; + 88CDF82915008BB900163A9F /* NSObject+RACKVOWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACKVOWrapper.m"; sourceTree = "<group>"; }; + 88CDF82C15008C0500163A9F /* NSObject+RACPropertySubscribing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "NSObject+RACPropertySubscribing.h"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 88D4AB3C1510F6C30011494F /* RACReplaySubject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACReplaySubject.h; sourceTree = "<group>"; }; + 88D4AB3D1510F6C30011494F /* RACReplaySubject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RACReplaySubject.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 88DA309515071CBA00C19D0F /* RACValueTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACValueTransformer.h; sourceTree = "<group>"; }; + 88DA309615071CBA00C19D0F /* RACValueTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACValueTransformer.m; sourceTree = "<group>"; }; + 88E2C6B2153C771C00C7493C /* RACScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACScheduler.h; sourceTree = "<group>"; }; + 88E2C6B3153C771C00C7493C /* RACScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACScheduler.m; sourceTree = "<group>"; }; + 88F440AB153DAC820097B4C3 /* libReactiveCocoa-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libReactiveCocoa-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 88F440D1153DADEA0097B4C3 /* NSObject+RACAppKitBindings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACAppKitBindings.h"; sourceTree = "<group>"; }; + 88F440D2153DADEA0097B4C3 /* NSObject+RACAppKitBindings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACAppKitBindings.m"; sourceTree = "<group>"; }; + 88F4425F153DC0450097B4C3 /* UIControl+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+RACSignalSupport.h"; sourceTree = "<group>"; }; + 88F44260153DC0450097B4C3 /* UIControl+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+RACSignalSupport.m"; sourceTree = "<group>"; }; + 88F44264153DCAC50097B4C3 /* UITextField+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+RACSignalSupport.h"; sourceTree = "<group>"; }; + 88F44265153DCAC50097B4C3 /* UITextField+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+RACSignalSupport.m"; sourceTree = "<group>"; }; + 88F5870515361C170084BD32 /* RACMulticastConnection+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "RACMulticastConnection+Private.h"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 88FC735316114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriptingAssignmentTrampoline.h; sourceTree = "<group>"; }; + 88FC735416114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriptingAssignmentTrampoline.m; sourceTree = "<group>"; }; + 88FC735A16114FFB00F8A774 /* RACSubscriptingAssignmentTrampolineSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriptingAssignmentTrampolineSpec.m; sourceTree = "<group>"; }; + A1FCC27215666AA3008C9686 /* UITextView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextView+RACSignalSupport.h"; sourceTree = "<group>"; }; + A1FCC27315666AA3008C9686 /* UITextView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextView+RACSignalSupport.m"; sourceTree = "<group>"; }; + A1FCC370156754A7008C9686 /* RACObjCRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACObjCRuntime.h; sourceTree = "<group>"; }; + A1FCC371156754A7008C9686 /* RACObjCRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACObjCRuntime.m; sourceTree = "<group>"; }; + A1FCC3761567DED0008C9686 /* RACDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDelegateProxy.h; sourceTree = "<group>"; }; + A1FCC3771567DED0008C9686 /* RACDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDelegateProxy.m; sourceTree = "<group>"; }; + AC65FD51176DECB1005ED22B /* UIAlertViewRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIAlertViewRACSupportSpec.m; sourceTree = "<group>"; }; + ACB0EAF11797DDD400942FFC /* UIAlertView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertView+RACSignalSupport.m"; sourceTree = "<group>"; }; + ACB0EAF21797DDD400942FFC /* UIAlertView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertView+RACSignalSupport.h"; sourceTree = "<group>"; }; + BE527E9118636F7F006349E8 /* NSUserDefaults+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserDefaults+RACSupport.h"; sourceTree = "<group>"; }; + BE527E9218636F7F006349E8 /* NSUserDefaults+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUserDefaults+RACSupport.m"; sourceTree = "<group>"; }; + BE527E9E1863705B006349E8 /* NSUserDefaultsRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSUserDefaultsRACSupportSpec.m; sourceTree = "<group>"; }; + CD11C6F118714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITableViewHeaderFooterView+RACSignalSupport.h"; sourceTree = "<group>"; }; + CD11C6F218714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITableViewHeaderFooterView+RACSignalSupport.m"; sourceTree = "<group>"; }; + CD11C6F918714DC1007C7CFD /* UITableViewHeaderFooterViewRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UITableViewHeaderFooterViewRACSupportSpec.m; sourceTree = "<group>"; }; + CD11C6FC18714DFB007C7CFD /* RACTestTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestTableViewController.h; sourceTree = "<group>"; }; + CD11C6FD18714DFB007C7CFD /* RACTestTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestTableViewController.m; sourceTree = "<group>"; }; + D004BC9B177E1A2B00A5B8C5 /* UIActionSheetRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UIActionSheetRACSupportSpec.m; path = ReactiveCocoaTests/UIActionSheetRACSupportSpec.m; sourceTree = SOURCE_ROOT; }; + D00930771788AB7B00EE7E8B /* RACTestScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestScheduler.h; sourceTree = "<group>"; }; + D00930781788AB7B00EE7E8B /* RACTestScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestScheduler.m; sourceTree = "<group>"; }; + D011F9CF1782AFD400EE7E38 /* NSObjectRACAppKitBindingsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACAppKitBindingsSpec.m; sourceTree = "<group>"; }; + D013A3D41807B5ED0072B6CE /* RACErrorSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACErrorSignal.h; sourceTree = "<group>"; }; + D013A3D51807B5ED0072B6CE /* RACErrorSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACErrorSignal.m; sourceTree = "<group>"; }; + D013A3DC1807B7450072B6CE /* RACEmptySignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEmptySignal.h; sourceTree = "<group>"; }; + D013A3DD1807B7450072B6CE /* RACEmptySignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEmptySignal.m; sourceTree = "<group>"; }; + D013A3E41807B7C30072B6CE /* RACReturnSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACReturnSignal.h; sourceTree = "<group>"; }; + D013A3E51807B7C30072B6CE /* RACReturnSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACReturnSignal.m; sourceTree = "<group>"; }; + D013A3ED1807B9690072B6CE /* RACDynamicSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDynamicSignal.h; sourceTree = "<group>"; }; + D013A3EE1807B9690072B6CE /* RACDynamicSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDynamicSignal.m; sourceTree = "<group>"; }; + D01DB9AE166819B9003E8F7F /* ReactiveCocoaTests-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ReactiveCocoaTests-Prefix.pch"; sourceTree = "<group>"; }; + D020F3DA17F6A3E40092BED2 /* RACCompoundDisposableProvider.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; path = RACCompoundDisposableProvider.d; sourceTree = "<group>"; }; + D02221611678910900DBD031 /* RACTupleSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTupleSpec.m; sourceTree = "<group>"; }; + D02538A115E2D7FB005BACB8 /* RACBacktrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RACBacktrace.h; sourceTree = "<group>"; }; + D028DB72179E53CB00D1042F /* RACSerialDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSerialDisposable.h; sourceTree = "<group>"; }; + D028DB73179E53CB00D1042F /* RACSerialDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSerialDisposable.m; sourceTree = "<group>"; }; + D028DB7C179E591E00D1042F /* RACSerialDisposableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSerialDisposableSpec.m; sourceTree = "<group>"; }; + D028DB85179E616700D1042F /* UITableViewCell+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITableViewCell+RACSignalSupport.h"; sourceTree = "<group>"; }; + D028DB86179E616700D1042F /* UITableViewCell+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITableViewCell+RACSignalSupport.m"; sourceTree = "<group>"; }; + D0307EDB1731AAE100D83211 /* RACTupleSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTupleSequence.h; sourceTree = "<group>"; }; + D0307EDC1731AAE100D83211 /* RACTupleSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTupleSequence.m; sourceTree = "<group>"; }; + D03525D317E2EBC90099CBAB /* RACSignalProvider.d */ = {isa = PBXFileReference; explicitFileType = sourcecode.dtrace; fileEncoding = 4; path = RACSignalProvider.d; sourceTree = "<group>"; }; + D041376815D2281C004BBF80 /* RACKVOWrapperSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOWrapperSpec.m; sourceTree = "<group>"; }; + D0487AB1164314430085D890 /* RACStreamExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACStreamExamples.h; sourceTree = "<group>"; }; + D0487AB2164314430085D890 /* RACStreamExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACStreamExamples.m; sourceTree = "<group>"; }; + D05AD39417F2D56F0080895B /* libReactiveCocoa-Mac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libReactiveCocoa-Mac.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D05AD39517F2D5700080895B /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + D05AD39717F2D5700080895B /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + D05AD3A317F2D5700080895B /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D05F9D3317984EC000FD7982 /* EXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTRuntimeExtensions.h; sourceTree = "<group>"; }; + D05F9D3417984EC000FD7982 /* EXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTRuntimeExtensions.m; sourceTree = "<group>"; }; + D066C795176D262500C242D2 /* UIControlRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIControlRACSupportSpec.m; sourceTree = "<group>"; }; + D066C79B176D263D00C242D2 /* RACTestUIButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestUIButton.h; sourceTree = "<group>"; }; + D066C79C176D263D00C242D2 /* RACTestUIButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestUIButton.m; sourceTree = "<group>"; }; + D0700F4B1672994D00D7CD30 /* NSNotificationCenterRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSNotificationCenterRACSupportSpec.m; sourceTree = "<group>"; }; + D07200241788C57200987F70 /* RACTestSchedulerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestSchedulerSpec.m; sourceTree = "<group>"; }; + D075A72817BCB7E100C24FB7 /* RACControlCommandExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACControlCommandExamples.h; sourceTree = "<group>"; }; + D075A72917BCB7E100C24FB7 /* RACControlCommandExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACControlCommandExamples.m; sourceTree = "<group>"; }; + D077A16B169B740200057BB1 /* RACEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEvent.h; sourceTree = "<group>"; }; + D077A16C169B740200057BB1 /* RACEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEvent.m; sourceTree = "<group>"; }; + D077A171169B79A900057BB1 /* RACEventSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEventSpec.m; sourceTree = "<group>"; }; + D07CD7141731BA3900DE2394 /* RACUnarySequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACUnarySequence.h; sourceTree = "<group>"; }; + D07CD7151731BA3900DE2394 /* RACUnarySequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACUnarySequence.m; sourceTree = "<group>"; }; + D07E9489179DD21E00A6F609 /* RACStream+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RACStream+Private.h"; sourceTree = "<group>"; }; + D0870C6E16884A0600D0E11D /* RACBacktraceSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACBacktraceSpec.m; sourceTree = "<group>"; }; + D090767D17FBEADE00EB087A /* NSURLConnection+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLConnection+RACSupport.h"; sourceTree = "<group>"; }; + D090767E17FBEADE00EB087A /* NSURLConnection+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLConnection+RACSupport.m"; sourceTree = "<group>"; }; + D090768917FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSURLConnectionRACSupportSpec.m; sourceTree = "<group>"; }; + D090768C17FBED2E00EB087A /* test-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "test-data.json"; sourceTree = "<group>"; }; + D094E44517775AF200906BF7 /* EXTKeyPathCoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTKeyPathCoding.h; sourceTree = "<group>"; }; + D094E44617775AF200906BF7 /* EXTScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTScope.h; sourceTree = "<group>"; }; + D094E44817775AF200906BF7 /* metamacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = metamacros.h; sourceTree = "<group>"; }; + D094E45317775B1000906BF7 /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = "<group>"; }; + D094E45517775B1000906BF7 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; + D094E45617775B1000906BF7 /* Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Profile.xcconfig; sourceTree = "<group>"; }; + D094E45717775B1000906BF7 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; + D094E45817775B1000906BF7 /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = "<group>"; }; + D094E45A17775B1000906BF7 /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = "<group>"; }; + D094E45B17775B1000906BF7 /* StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticLibrary.xcconfig; sourceTree = "<group>"; }; + D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Application.xcconfig"; sourceTree = "<group>"; }; + D094E45E17775B1000906BF7 /* iOS-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Base.xcconfig"; sourceTree = "<group>"; }; + D094E45F17775B1000906BF7 /* iOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-StaticLibrary.xcconfig"; sourceTree = "<group>"; }; + D094E46117775B1000906BF7 /* Mac-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Application.xcconfig"; sourceTree = "<group>"; }; + D094E46217775B1000906BF7 /* Mac-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Base.xcconfig"; sourceTree = "<group>"; }; + D094E46317775B1000906BF7 /* Mac-DynamicLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-DynamicLibrary.xcconfig"; sourceTree = "<group>"; }; + D094E46417775B1000906BF7 /* Mac-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Framework.xcconfig"; sourceTree = "<group>"; }; + D094E46517775B1000906BF7 /* Mac-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-StaticLibrary.xcconfig"; sourceTree = "<group>"; }; + D094E46617775B1000906BF7 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; }; + D0A0B01316EAA3D100C47593 /* NSText+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSText+RACSignalSupport.h"; sourceTree = "<group>"; }; + D0A0B01416EAA3D100C47593 /* NSText+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSText+RACSignalSupport.m"; sourceTree = "<group>"; }; + D0A0B01716EAA5CC00C47593 /* NSTextRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSTextRACSupportSpec.m; sourceTree = "<group>"; }; + D0A0B03916EAA9AC00C47593 /* NSControl+RACTextSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSControl+RACTextSignalSupport.h"; sourceTree = "<group>"; }; + D0A0B03A16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSControl+RACTextSignalSupport.m"; sourceTree = "<group>"; }; + D0A0E225176A84DA007273ED /* RACDisposableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDisposableSpec.m; sourceTree = "<group>"; }; + D0A0E22C176A8CD6007273ED /* RACPassthroughSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACPassthroughSubscriber.h; sourceTree = "<group>"; }; + D0A0E22D176A8CD6007273ED /* RACPassthroughSubscriber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACPassthroughSubscriber.m; sourceTree = "<group>"; }; + D0C55CD817758A73008CDDCA /* UITextFieldRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UITextFieldRACSupportSpec.m; sourceTree = "<group>"; }; + D0C55CDE17758C2A008CDDCA /* UITextViewRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UITextViewRACSupportSpec.m; sourceTree = "<group>"; }; + D0C55CE117759559008CDDCA /* RACDelegateProxySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDelegateProxySpec.m; sourceTree = "<group>"; }; + D0C70EC416659333005AAD03 /* RACSubscriberExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriberExamples.h; sourceTree = "<group>"; }; + D0C70EC516659333005AAD03 /* RACSubscriberExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriberExamples.m; sourceTree = "<group>"; }; + D0C70EC7166595AD005AAD03 /* RACSubscriberSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriberSpec.m; sourceTree = "<group>"; }; + D0C70F8F164337A2007027B4 /* RACSequenceAdditionsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequenceAdditionsSpec.m; sourceTree = "<group>"; }; + D0C70F91164337E3007027B4 /* RACSequenceExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSequenceExamples.h; sourceTree = "<group>"; }; + D0C70F92164337E3007027B4 /* RACSequenceExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequenceExamples.m; sourceTree = "<group>"; }; + D0D243B51741FA0E004359C6 /* NSObject+RACDescription.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACDescription.h"; sourceTree = "<group>"; }; + D0D243B61741FA0E004359C6 /* NSObject+RACDescription.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACDescription.m"; sourceTree = "<group>"; }; + D0D486FF1642550100DD7605 /* RACStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACStream.h; sourceTree = "<group>"; }; + D0D487001642550100DD7605 /* RACStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACStream.m; sourceTree = "<group>"; }; + D0D487051642651400DD7605 /* RACSequenceSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequenceSpec.m; sourceTree = "<group>"; }; + D0D910CC15F915BD00AD2DDA /* RACSignal+Operations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACSignal+Operations.h"; sourceTree = "<group>"; }; + D0D910CD15F915BD00AD2DDA /* RACSignal+Operations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RACSignal+Operations.m"; sourceTree = "<group>"; }; + D0DFBCCD15DD6D40009DADB3 /* RACBacktrace.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACBacktrace.m; sourceTree = "<group>"; }; + D0E967571641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+RACSequenceAdditions.h"; sourceTree = "<group>"; }; + D0E967581641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+RACSequenceAdditions.m"; sourceTree = "<group>"; }; + D0E967591641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+RACSequenceAdditions.h"; sourceTree = "<group>"; }; + D0E9675A1641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+RACSequenceAdditions.m"; sourceTree = "<group>"; }; + D0E9675B1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSOrderedSet+RACSequenceAdditions.h"; sourceTree = "<group>"; }; + D0E9675C1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSOrderedSet+RACSequenceAdditions.m"; sourceTree = "<group>"; }; + D0E9675D1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSSet+RACSequenceAdditions.h"; sourceTree = "<group>"; }; + D0E9675E1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSSet+RACSequenceAdditions.m"; sourceTree = "<group>"; }; + D0E9675F1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RACSequenceAdditions.h"; sourceTree = "<group>"; }; + D0E967601641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RACSequenceAdditions.m"; sourceTree = "<group>"; }; + D0E967611641EF9C00FCFF06 /* RACArraySequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACArraySequence.h; sourceTree = "<group>"; }; + D0E967621641EF9C00FCFF06 /* RACArraySequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACArraySequence.m; sourceTree = "<group>"; }; + D0E967631641EF9C00FCFF06 /* RACDynamicSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDynamicSequence.h; sourceTree = "<group>"; }; + D0E967641641EF9C00FCFF06 /* RACDynamicSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDynamicSequence.m; sourceTree = "<group>"; }; + D0E967651641EF9C00FCFF06 /* RACEmptySequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEmptySequence.h; sourceTree = "<group>"; }; + D0E967661641EF9C00FCFF06 /* RACEmptySequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEmptySequence.m; sourceTree = "<group>"; }; + D0E967671641EF9C00FCFF06 /* RACSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RACSequence.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + D0E967681641EF9C00FCFF06 /* RACSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequence.m; sourceTree = "<group>"; }; + D0E967691641EF9C00FCFF06 /* RACStringSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACStringSequence.h; sourceTree = "<group>"; }; + D0E9676A1641EF9C00FCFF06 /* RACStringSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACStringSequence.m; sourceTree = "<group>"; }; + D0EDE76516968AB10072A780 /* RACPropertySignalExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACPropertySignalExamples.h; sourceTree = "<group>"; }; + D0EDE76616968AB10072A780 /* RACPropertySignalExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACPropertySignalExamples.m; sourceTree = "<group>"; }; + D0EE2849164D906B006954A4 /* RACSignalSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSignalSequence.h; sourceTree = "<group>"; }; + D0EE284A164D906B006954A4 /* RACSignalSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSignalSequence.m; sourceTree = "<group>"; }; + D0F117CB179F0B97006CE68F /* UITableViewCellRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UITableViewCellRACSupportSpec.m; sourceTree = "<group>"; }; + D0FAEC02176AEEE600D3C1A7 /* RACSubscriber+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RACSubscriber+Private.h"; sourceTree = "<group>"; }; + D40D7AAA18E22B5E0065BB70 /* libExpecta.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libExpecta.a; sourceTree = BUILT_PRODUCTS_DIR; }; + D40D7AAC18E22B7E0065BB70 /* libSpecta.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libSpecta.a; sourceTree = BUILT_PRODUCTS_DIR; }; + D40D7AAE18E22BE30065BB70 /* libExpecta-iOS.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libExpecta-iOS.a"; path = "../external/expecta/build/Debug-iphoneos/libExpecta-iOS.a"; sourceTree = "<group>"; }; + D40D7AB018E22BF60065BB70 /* libSpecta-iOS.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libSpecta-iOS.a"; path = "../external/specta/build/Debug-iphoneos/libSpecta-iOS.a"; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1860F40F177C91B500C7B3C9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D0F117C8179F0A95006CE68F /* libReactiveCocoa-iOS.a in Frameworks */, + 1860F414177C91B500C7B3C9 /* UIKit.framework in Frameworks */, + 1860F416177C91B500C7B3C9 /* Foundation.framework in Frameworks */, + 1860F418177C91B500C7B3C9 /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1860F42C177C91B500C7B3C9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1860F432177C91B500C7B3C9 /* SenTestingKit.framework in Frameworks */, + 1860F433177C91B500C7B3C9 /* UIKit.framework in Frameworks */, + 1860F434177C91B500C7B3C9 /* Foundation.framework in Frameworks */, + D40D7AB218E22EC60065BB70 /* libSpecta-iOS.a in Frameworks */, + D40D7AB318E22EC90065BB70 /* libExpecta-iOS.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5FAF521F174D4C2000CAC810 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5FD7DC7C174F9EEB008710B4 /* UIKit.framework in Frameworks */, + 5FAF5289174E9CD300CAC810 /* CoreGraphics.framework in Frameworks */, + 5FAF5265174D500D00CAC810 /* libReactiveCocoa-iOS.a in Frameworks */, + 5FAF5224174D4C2000CAC810 /* SenTestingKit.framework in Frameworks */, + D40D7AAF18E22BE30065BB70 /* libExpecta-iOS.a in Frameworks */, + D40D7AB118E22BF60065BB70 /* libSpecta-iOS.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88037F7F15056328001A5B19 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 88037F8415056328001A5B19 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88CDF7D815000FCF00163A9F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 88037FD9150564D9001A5B19 /* ReactiveCocoa.framework in Frameworks */, + 88CDF7DE15000FCF00163A9F /* SenTestingKit.framework in Frameworks */, + 88CDF7DF15000FCF00163A9F /* Cocoa.framework in Frameworks */, + D40D7AAB18E22B5E0065BB70 /* libExpecta.a in Frameworks */, + D40D7AAD18E22B7E0065BB70 /* libSpecta.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88F440A8153DAC820097B4C3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5FD7DC7A174F9EAF008710B4 /* Foundation.framework in Frameworks */, + 5FD7DC7F174F9FC8008710B4 /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D05AD39117F2D56F0080895B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D05AD39617F2D5700080895B /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1860F419177C91B500C7B3C9 /* UIKitTestHost */ = { + isa = PBXGroup; + children = ( + 1860F422177C91B500C7B3C9 /* RACAppDelegate.h */, + 1860F423177C91B500C7B3C9 /* RACAppDelegate.m */, + CD11C6FC18714DFB007C7CFD /* RACTestTableViewController.h */, + CD11C6FD18714DFB007C7CFD /* RACTestTableViewController.m */, + 1860F41A177C91B500C7B3C9 /* Supporting Files */, + ); + path = UIKitTestHost; + sourceTree = "<group>"; + }; + 1860F41A177C91B500C7B3C9 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 1860F41B177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost-Info.plist */, + 1860F41C177C91B500C7B3C9 /* InfoPlist.strings */, + 1860F41F177C91B500C7B3C9 /* main.m */, + 1860F421177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost-Prefix.pch */, + 1860F425177C91B500C7B3C9 /* Default.png */, + 1860F427177C91B500C7B3C9 /* Default@2x.png */, + 1860F429177C91B500C7B3C9 /* Default-568h@2x.png */, + ); + name = "Supporting Files"; + sourceTree = "<group>"; + }; + 1860F437177C91B500C7B3C9 /* UIKitTests */ = { + isa = PBXGroup; + children = ( + D004BC9B177E1A2B00A5B8C5 /* UIActionSheetRACSupportSpec.m */, + 1668028117FE774800C724B4 /* UICollectionReusableViewRACSupportSpec.m */, + D0F117CB179F0B97006CE68F /* UITableViewCellRACSupportSpec.m */, + CD11C6F918714DC1007C7CFD /* UITableViewHeaderFooterViewRACSupportSpec.m */, + D0C55CD817758A73008CDDCA /* UITextFieldRACSupportSpec.m */, + D0C55CDE17758C2A008CDDCA /* UITextViewRACSupportSpec.m */, + ); + path = UIKitTests; + sourceTree = "<group>"; + }; + 1860F438177C91B500C7B3C9 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 1860F455177C96B200C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist */, + 1860F457177C972E00C7B3C9 /* ReactiveCocoa-iOS-UIKitTest-Prefix.pch */, + 1860F43A177C91B500C7B3C9 /* InfoPlist.strings */, + ); + name = "Supporting Files"; + path = UIKitTestHost; + sourceTree = "<group>"; + }; + 1860F44E177C949A00C7B3C9 /* UIKit */ = { + isa = PBXGroup; + children = ( + 1860F437177C91B500C7B3C9 /* UIKitTests */, + 1860F419177C91B500C7B3C9 /* UIKitTestHost */, + 1860F438177C91B500C7B3C9 /* Supporting Files */, + ); + path = UIKit; + sourceTree = "<group>"; + }; + 5FAF525F174D4D6100CAC810 /* OS X */ = { + isa = PBXGroup; + children = ( + 884848B515F658B800B11BD0 /* NSControlRACSupportSpec.m */, + D011F9CF1782AFD400EE7E38 /* NSObjectRACAppKitBindingsSpec.m */, + D0A0B01716EAA5CC00C47593 /* NSTextRACSupportSpec.m */, + 4925E805181BCC71000B2FEE /* NSControllerRACSupportSpec.m */, + ); + name = "OS X"; + sourceTree = "<group>"; + }; + 5FAF5260174D4D7100CAC810 /* iOS */ = { + isa = PBXGroup; + children = ( + 1860F44E177C949A00C7B3C9 /* UIKit */, + D0C55CE117759559008CDDCA /* RACDelegateProxySpec.m */, + D066C79B176D263D00C242D2 /* RACTestUIButton.h */, + D066C79C176D263D00C242D2 /* RACTestUIButton.m */, + AC65FD51176DECB1005ED22B /* UIAlertViewRACSupportSpec.m */, + 5FAF5261174D4D8E00CAC810 /* UIBarButtonItemRACSupportSpec.m */, + 5EE9A79A1760D88500EAF5A2 /* UIButtonRACSupportSpec.m */, + D066C795176D262500C242D2 /* UIControlRACSupportSpec.m */, + 5564542318107275002BD2E4 /* UIRefreshControlRACSupportSpec.m */, + ); + name = iOS; + sourceTree = "<group>"; + }; + 88977C5915129AB200A09EC5 /* KVO + Bindings */ = { + isa = PBXGroup; + children = ( + 5F6FE8511692568A00A8D7A6 /* RACChannel.h */, + 5F6FE8521692568A00A8D7A6 /* RACChannel.m */, + 5F45A883168CFA3E00B58A2B /* RACKVOChannel.h */, + 5F45A884168CFA3E00B58A2B /* RACKVOChannel.m */, + 8857BB81152A27A9009804CC /* NSObject+RACKVOWrapper.h */, + 88CDF82915008BB900163A9F /* NSObject+RACKVOWrapper.m */, + 5FDC35011736F54600792E52 /* NSString+RACKeyPathUtilities.h */, + 5FDC35021736F54700792E52 /* NSString+RACKeyPathUtilities.m */, + 8837EA1416A5A33300FC3CDF /* RACKVOTrampoline.h */, + 8837EA1516A5A33300FC3CDF /* RACKVOTrampoline.m */, + 88CDF82C15008C0500163A9F /* NSObject+RACPropertySubscribing.h */, + 886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */, + 88DA309515071CBA00C19D0F /* RACValueTransformer.h */, + 88DA309615071CBA00C19D0F /* RACValueTransformer.m */, + ); + name = "KVO + Bindings"; + sourceTree = "<group>"; + }; + 889C04BB155DA37600F19F0C /* Foundation Support */ = { + isa = PBXGroup; + children = ( + D0E967571641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.h */, + D0E967581641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m */, + 88442C8716090C1500636B49 /* NSData+RACSupport.h */, + 88442C8816090C1500636B49 /* NSData+RACSupport.m */, + D0E967591641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.h */, + D0E9675A1641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m */, + 5F773DE8169B46670023069D /* NSEnumerator+RACSequenceAdditions.h */, + 5F773DE9169B46670023069D /* NSEnumerator+RACSequenceAdditions.m */, + 88442C8916090C1500636B49 /* NSFileHandle+RACSupport.h */, + 88442C8A16090C1500636B49 /* NSFileHandle+RACSupport.m */, + 74F17316186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h */, + 74F17317186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m */, + 88442C8B16090C1500636B49 /* NSNotificationCenter+RACSupport.h */, + 88442C8C16090C1500636B49 /* NSNotificationCenter+RACSupport.m */, + D0D243B51741FA0E004359C6 /* NSObject+RACDescription.h */, + D0D243B61741FA0E004359C6 /* NSObject+RACDescription.m */, + 880D7A5816F7B351004A3361 /* NSObject+RACSelectorSignal.h */, + 880D7A5916F7B351004A3361 /* NSObject+RACSelectorSignal.m */, + D0E9675B1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.h */, + D0E9675C1641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m */, + D0E9675D1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.h */, + D0E9675E1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m */, + D0E9675F1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.h */, + D0E967601641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m */, + 88442C8D16090C1500636B49 /* NSString+RACSupport.h */, + 88442C8E16090C1500636B49 /* NSString+RACSupport.m */, + D090767D17FBEADE00EB087A /* NSURLConnection+RACSupport.h */, + D090767E17FBEADE00EB087A /* NSURLConnection+RACSupport.m */, + BE527E9118636F7F006349E8 /* NSUserDefaults+RACSupport.h */, + BE527E9218636F7F006349E8 /* NSUserDefaults+RACSupport.m */, + ); + name = "Foundation Support"; + sourceTree = "<group>"; + }; + 88CDF7B015000FCE00163A9F = { + isa = PBXGroup; + children = ( + 88CDF7C515000FCE00163A9F /* ReactiveCocoa */, + 88CDF7E215000FCF00163A9F /* ReactiveCocoaTests */, + 88CDF7BE15000FCE00163A9F /* Frameworks */, + D094E45117775B1000906BF7 /* Configuration */, + 88CDF7BC15000FCE00163A9F /* Products */, + ); + sourceTree = "<group>"; + usesTabs = 1; + }; + 88CDF7BC15000FCE00163A9F /* Products */ = { + isa = PBXGroup; + children = ( + 88CDF7DC15000FCF00163A9F /* ReactiveCocoaTests.octest */, + 88037F8315056328001A5B19 /* ReactiveCocoa.framework */, + 88F440AB153DAC820097B4C3 /* libReactiveCocoa-iOS.a */, + 5FAF5223174D4C2000CAC810 /* ReactiveCocoaTests-iOS.octest */, + 1860F412177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost.app */, + 1860F430177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHostTests.octest */, + D05AD39417F2D56F0080895B /* libReactiveCocoa-Mac.a */, + ); + name = Products; + sourceTree = "<group>"; + }; + 88CDF7BE15000FCE00163A9F /* Frameworks */ = { + isa = PBXGroup; + children = ( + D40D7AAC18E22B7E0065BB70 /* libSpecta.a */, + D40D7AB018E22BF60065BB70 /* libSpecta-iOS.a */, + D40D7AAA18E22B5E0065BB70 /* libExpecta.a */, + D40D7AAE18E22BE30065BB70 /* libExpecta-iOS.a */, + D094E44417775ACD00906BF7 /* libextobjc */, + 1860F413177C91B500C7B3C9 /* UIKit.framework */, + 1860F415177C91B500C7B3C9 /* Foundation.framework */, + 1860F417177C91B500C7B3C9 /* CoreGraphics.framework */, + 1860F431177C91B500C7B3C9 /* SenTestingKit.framework */, + D05AD39517F2D5700080895B /* Cocoa.framework */, + D05AD3A317F2D5700080895B /* XCTest.framework */, + 88CDF7C115000FCE00163A9F /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; + 88CDF7C115000FCE00163A9F /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 88CDF7BF15000FCE00163A9F /* Cocoa.framework */, + 88CDF7C315000FCE00163A9F /* CoreData.framework */, + 5FAF5288174E9CD200CAC810 /* CoreGraphics.framework */, + 88CDF7C415000FCE00163A9F /* Foundation.framework */, + 88CDF7DD15000FCF00163A9F /* SenTestingKit.framework */, + 5FD7DC7B174F9EEB008710B4 /* UIKit.framework */, + D05AD39717F2D5700080895B /* AppKit.framework */, + ); + name = "Other Frameworks"; + sourceTree = "<group>"; + }; + 88CDF7C515000FCE00163A9F /* ReactiveCocoa */ = { + isa = PBXGroup; + children = ( + 88037F8C15056328001A5B19 /* ReactiveCocoa.h */, + 88DA308C15071C4C00C19D0F /* Core */, + 889C04BB155DA37600F19F0C /* Foundation Support */, + 88DA309415071C5F00C19D0F /* AppKit Support */, + 88F44257153DC0100097B4C3 /* UIKit Support */, + 88977C5915129AB200A09EC5 /* KVO + Bindings */, + A1FCC36F15675466008C9686 /* Objective-C Runtime */, + 88CDF7C615000FCE00163A9F /* Supporting Files */, + ); + path = ReactiveCocoa; + sourceTree = "<group>"; + }; + 88CDF7C615000FCE00163A9F /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 88CDF7C715000FCE00163A9F /* ReactiveCocoa-Info.plist */, + 88CDF7C815000FCE00163A9F /* InfoPlist.strings */, + 88CDF7CD15000FCE00163A9F /* ReactiveCocoa-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = "<group>"; + }; + 88CDF7E215000FCF00163A9F /* ReactiveCocoaTests */ = { + isa = PBXGroup; + children = ( + 5FAF5260174D4D7100CAC810 /* iOS */, + 5FAF525F174D4D6100CAC810 /* OS X */, + 88CDF7E315000FCF00163A9F /* Supporting Files */, + 5F773DEF169B48830023069D /* NSEnumeratorRACSequenceAdditionsSpec.m */, + D0700F4B1672994D00D7CD30 /* NSNotificationCenterRACSupportSpec.m */, + 6E58405E16F3414200F588A6 /* NSObjectRACDeallocatingSpec.m */, + 8801E7501644BDE200A155FE /* NSObjectRACLiftingSpec.m */, + 1E89337F171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.h */, + 1E893380171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.m */, + 8851A38A16161D500050D47F /* NSObjectRACPropertySubscribingSpec.m */, + 880D7A6516F7BB1A004A3361 /* NSObjectRACSelectorSignalSpec.m */, + 5FDC350E1736F81800792E52 /* NSStringRACKeyPathUtilitiesSpec.m */, + D090768917FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m */, + BE527E9E1863705B006349E8 /* NSUserDefaultsRACSupportSpec.m */, + D0870C6E16884A0600D0E11D /* RACBacktraceSpec.m */, + 5F7EFECC168FBC4B0037E500 /* RACChannelExamples.h */, + 5F7EFECD168FBC4B0037E500 /* RACChannelExamples.m */, + 5F7EFECE168FBC4B0037E500 /* RACChannelSpec.m */, + D075A72817BCB7E100C24FB7 /* RACControlCommandExamples.h */, + D075A72917BCB7E100C24FB7 /* RACControlCommandExamples.m */, + 886CEACC163DE669007632D1 /* RACBlockTrampolineSpec.m */, + 882CCA1D15F1564D00937D6E /* RACCommandSpec.m */, + 881E86B91669350B00667F7B /* RACCompoundDisposableSpec.m */, + D0A0E225176A84DA007273ED /* RACDisposableSpec.m */, + D077A171169B79A900057BB1 /* RACEventSpec.m */, + 5F2447AC167E87C50062180C /* RACKVOChannelSpec.m */, + D041376815D2281C004BBF80 /* RACKVOWrapperSpec.m */, + 88C5A02816924BFC0045EF05 /* RACMulticastConnectionSpec.m */, + D0EDE76516968AB10072A780 /* RACPropertySignalExamples.h */, + D0EDE76616968AB10072A780 /* RACPropertySignalExamples.m */, + 8803C010166732BA00C36839 /* RACSchedulerSpec.m */, + D0C70F8F164337A2007027B4 /* RACSequenceAdditionsSpec.m */, + D0C70F91164337E3007027B4 /* RACSequenceExamples.h */, + D0C70F92164337E3007027B4 /* RACSequenceExamples.m */, + D0D487051642651400DD7605 /* RACSequenceSpec.m */, + 8820937B1501C8A600796685 /* RACSignalSpec.m */, + 8884DD6A1756AD3600F6C379 /* RACSignalStartExamples.h */, + 8884DD641756ACF600F6C379 /* RACSignalStartExamples.m */, + D0487AB1164314430085D890 /* RACStreamExamples.h */, + D0487AB2164314430085D890 /* RACStreamExamples.m */, + 880D7A6716F7BCC7004A3361 /* RACSubclassObject.h */, + 880D7A6816F7BCC7004A3361 /* RACSubclassObject.m */, + 889D0A7F15974B2A00F833E3 /* RACSubjectSpec.m */, + D0C70EC416659333005AAD03 /* RACSubscriberExamples.h */, + D0C70EC516659333005AAD03 /* RACSubscriberExamples.m */, + D0C70EC7166595AD005AAD03 /* RACSubscriberSpec.m */, + 88FC735A16114FFB00F8A774 /* RACSubscriptingAssignmentTrampolineSpec.m */, + 88302C2D1762C180003633BD /* RACTargetQueueSchedulerSpec.m */, + 88302BFB1762A9E6003633BD /* RACTestExampleScheduler.h */, + 88302BFC1762A9E6003633BD /* RACTestExampleScheduler.m */, + 88442A321608A9AD00636B49 /* RACTestObject.h */, + 88442A331608A9AD00636B49 /* RACTestObject.m */, + D02221611678910900DBD031 /* RACTupleSpec.m */, + 8803C010166732BA00C36839 /* RACSchedulerSpec.m */, + D028DB7C179E591E00D1042F /* RACSerialDisposableSpec.m */, + D07200241788C57200987F70 /* RACTestSchedulerSpec.m */, + ); + path = ReactiveCocoaTests; + sourceTree = "<group>"; + }; + 88CDF7E315000FCF00163A9F /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 88CDF7E415000FCF00163A9F /* ReactiveCocoaTests-Info.plist */, + 88CDF7E515000FCF00163A9F /* InfoPlist.strings */, + D01DB9AE166819B9003E8F7F /* ReactiveCocoaTests-Prefix.pch */, + D090768C17FBED2E00EB087A /* test-data.json */, + ); + name = "Supporting Files"; + sourceTree = "<group>"; + }; + 88DA308C15071C4C00C19D0F /* Core */ = { + isa = PBXGroup; + children = ( + 88CDF7FA150019CA00163A9F /* RACSubscriber.h */, + D0FAEC02176AEEE600D3C1A7 /* RACSubscriber+Private.h */, + 88CDF7FB150019CA00163A9F /* RACSubscriber.m */, + D0A0E22C176A8CD6007273ED /* RACPassthroughSubscriber.h */, + D0A0E22D176A8CD6007273ED /* RACPassthroughSubscriber.m */, + 888439A11634E10D00DED0DB /* RACBlockTrampoline.h */, + 888439A21634E10D00DED0DB /* RACBlockTrampoline.m */, + 881B37CA152260BF0079220B /* RACUnit.h */, + 881B37CB152260BF0079220B /* RACUnit.m */, + 88B76F8C153726B00053EAE2 /* RACTuple.h */, + 88B76F8D153726B00053EAE2 /* RACTuple.m */, + D02538A115E2D7FB005BACB8 /* RACBacktrace.h */, + D0DFBCCD15DD6D40009DADB3 /* RACBacktrace.m */, + 88FC735316114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.h */, + 88FC735416114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m */, + 6E58405316F22D7500F588A6 /* NSObject+RACDeallocating.h */, + 6E58405416F22D7500F588A6 /* NSObject+RACDeallocating.m */, + 886CEAE0163DE942007632D1 /* NSObject+RACLifting.h */, + 886CEAE1163DE942007632D1 /* NSObject+RACLifting.m */, + 887ACDA5165878A7009190AD /* NSInvocation+RACTypeParsing.h */, + 887ACDA6165878A7009190AD /* NSInvocation+RACTypeParsing.m */, + D0D486FF1642550100DD7605 /* RACStream.h */, + D07E9489179DD21E00A6F609 /* RACStream+Private.h */, + D0D487001642550100DD7605 /* RACStream.m */, + D0D486FB164253B600DD7605 /* Signals */, + D0D486FD164253D500DD7605 /* Disposables */, + D0D486FE164253E100DD7605 /* Commands */, + D0E967561641EF8200FCFF06 /* Sequences */, + D0087C1B16705C5600679459 /* Schedulers */, + ); + name = Core; + sourceTree = "<group>"; + }; + 88DA309415071C5F00C19D0F /* AppKit Support */ = { + isa = PBXGroup; + children = ( + 882093E61501E6CB00796685 /* NSControl+RACCommandSupport.h */, + 882093E71501E6CB00796685 /* NSControl+RACCommandSupport.m */, + D0A0B03916EAA9AC00C47593 /* NSControl+RACTextSignalSupport.h */, + D0A0B03A16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.m */, + 88F440D1153DADEA0097B4C3 /* NSObject+RACAppKitBindings.h */, + 88F440D2153DADEA0097B4C3 /* NSObject+RACAppKitBindings.m */, + D0A0B01316EAA3D100C47593 /* NSText+RACSignalSupport.h */, + D0A0B01416EAA3D100C47593 /* NSText+RACSignalSupport.m */, + ); + name = "AppKit Support"; + sourceTree = "<group>"; + }; + 88F44257153DC0100097B4C3 /* UIKit Support */ = { + isa = PBXGroup; + children = ( + A1FCC3761567DED0008C9686 /* RACDelegateProxy.h */, + A1FCC3771567DED0008C9686 /* RACDelegateProxy.m */, + 557A4B58177648C7008EF796 /* UIActionSheet+RACSignalSupport.h */, + 557A4B59177648C7008EF796 /* UIActionSheet+RACSignalSupport.m */, + ACB0EAF21797DDD400942FFC /* UIAlertView+RACSignalSupport.h */, + ACB0EAF11797DDD400942FFC /* UIAlertView+RACSignalSupport.m */, + 27A887C71703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.h */, + 27A887C81703DB4F00040001 /* UIBarButtonItem+RACCommandSupport.m */, + 5EE9A7911760D61300EAF5A2 /* UIButton+RACCommandSupport.h */, + 5EE9A7921760D61300EAF5A2 /* UIButton+RACCommandSupport.m */, + 1668027817FE75E900C724B4 /* UICollectionReusableView+RACSignalSupport.h */, + 1668027917FE75E900C724B4 /* UICollectionReusableView+RACSignalSupport.m */, + 88F4425F153DC0450097B4C3 /* UIControl+RACSignalSupport.h */, + 88F44260153DC0450097B4C3 /* UIControl+RACSignalSupport.m */, + 5F016DF217B10AA8002EEC69 /* UIControl+RACSignalSupportPrivate.h */, + 5F016DF317B10AA8002EEC69 /* UIControl+RACSignalSupportPrivate.m */, + 5F70B2AD17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.h */, + 5F70B2AE17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.m */, + 1EC06B15173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.h */, + 1EC06B16173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m */, + 554D9E5B181064E200F21262 /* UIRefreshControl+RACCommandSupport.h */, + 554D9E5C181064E200F21262 /* UIRefreshControl+RACCommandSupport.m */, + 5F70B2B617AB1856009AEDF9 /* UISegmentedControl+RACSignalSupport.h */, + 5F70B2B717AB1856009AEDF9 /* UISegmentedControl+RACSignalSupport.m */, + 5F70B2B817AB1856009AEDF9 /* UISlider+RACSignalSupport.h */, + 5F70B2B917AB1856009AEDF9 /* UISlider+RACSignalSupport.m */, + 5F70B2BA17AB1857009AEDF9 /* UIStepper+RACSignalSupport.h */, + 5F70B2BB17AB1857009AEDF9 /* UIStepper+RACSignalSupport.m */, + 5F70B2BC17AB1857009AEDF9 /* UISwitch+RACSignalSupport.h */, + 5F70B2BD17AB1857009AEDF9 /* UISwitch+RACSignalSupport.m */, + D028DB85179E616700D1042F /* UITableViewCell+RACSignalSupport.h */, + D028DB86179E616700D1042F /* UITableViewCell+RACSignalSupport.m */, + CD11C6F118714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.h */, + CD11C6F218714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.m */, + 88F44264153DCAC50097B4C3 /* UITextField+RACSignalSupport.h */, + 88F44265153DCAC50097B4C3 /* UITextField+RACSignalSupport.m */, + A1FCC27215666AA3008C9686 /* UITextView+RACSignalSupport.h */, + A1FCC27315666AA3008C9686 /* UITextView+RACSignalSupport.m */, + ); + name = "UIKit Support"; + sourceTree = "<group>"; + }; + A1FCC36F15675466008C9686 /* Objective-C Runtime */ = { + isa = PBXGroup; + children = ( + A1FCC370156754A7008C9686 /* RACObjCRuntime.h */, + A1FCC371156754A7008C9686 /* RACObjCRuntime.m */, + ); + name = "Objective-C Runtime"; + sourceTree = "<group>"; + }; + D0087C1B16705C5600679459 /* Schedulers */ = { + isa = PBXGroup; + children = ( + 88E2C6B2153C771C00C7493C /* RACScheduler.h */, + 886D98581667C86D00F22541 /* RACScheduler+Private.h */, + 88E2C6B3153C771C00C7493C /* RACScheduler.m */, + 881E87AA16695C5600667F7B /* RACQueueScheduler.h */, + 882D07201761521B009EDA69 /* RACQueueScheduler+Subclass.h */, + 881E87AB16695C5600667F7B /* RACQueueScheduler.m */, + 882D071717614FA7009EDA69 /* RACTargetQueueScheduler.h */, + 882D071817614FA7009EDA69 /* RACTargetQueueScheduler.m */, + 881E87B016695EDF00667F7B /* RACImmediateScheduler.h */, + 881E87B116695EDF00667F7B /* RACImmediateScheduler.m */, + 881E87C21669635F00667F7B /* RACSubscriptionScheduler.h */, + 881E87C31669636000667F7B /* RACSubscriptionScheduler.m */, + D00930771788AB7B00EE7E8B /* RACTestScheduler.h */, + D00930781788AB7B00EE7E8B /* RACTestScheduler.m */, + ); + name = Schedulers; + sourceTree = "<group>"; + }; + D013A3EC1807B9260072B6CE /* Private */ = { + isa = PBXGroup; + children = ( + D013A3ED1807B9690072B6CE /* RACDynamicSignal.h */, + D013A3EE1807B9690072B6CE /* RACDynamicSignal.m */, + D013A3DC1807B7450072B6CE /* RACEmptySignal.h */, + D013A3DD1807B7450072B6CE /* RACEmptySignal.m */, + D013A3D41807B5ED0072B6CE /* RACErrorSignal.h */, + D013A3D51807B5ED0072B6CE /* RACErrorSignal.m */, + D013A3E41807B7C30072B6CE /* RACReturnSignal.h */, + D013A3E51807B7C30072B6CE /* RACReturnSignal.m */, + ); + name = Private; + sourceTree = "<group>"; + }; + D094E44417775ACD00906BF7 /* libextobjc */ = { + isa = PBXGroup; + children = ( + D094E44517775AF200906BF7 /* EXTKeyPathCoding.h */, + D05F9D3317984EC000FD7982 /* EXTRuntimeExtensions.h */, + D05F9D3417984EC000FD7982 /* EXTRuntimeExtensions.m */, + D094E44617775AF200906BF7 /* EXTScope.h */, + D094E44817775AF200906BF7 /* metamacros.h */, + ); + name = libextobjc; + path = ReactiveCocoa/extobjc; + sourceTree = "<group>"; + }; + D094E45117775B1000906BF7 /* Configuration */ = { + isa = PBXGroup; + children = ( + D094E45217775B1000906BF7 /* Base */, + D094E45C17775B1000906BF7 /* iOS */, + D094E46017775B1000906BF7 /* Mac OS X */, + D094E46617775B1000906BF7 /* README.md */, + ); + name = Configuration; + path = ../external/xcconfigs; + sourceTree = "<group>"; + }; + D094E45217775B1000906BF7 /* Base */ = { + isa = PBXGroup; + children = ( + D094E45317775B1000906BF7 /* Common.xcconfig */, + D094E45417775B1000906BF7 /* Configurations */, + D094E45917775B1000906BF7 /* Targets */, + ); + path = Base; + sourceTree = "<group>"; + }; + D094E45417775B1000906BF7 /* Configurations */ = { + isa = PBXGroup; + children = ( + D094E45517775B1000906BF7 /* Debug.xcconfig */, + D094E45617775B1000906BF7 /* Profile.xcconfig */, + D094E45717775B1000906BF7 /* Release.xcconfig */, + D094E45817775B1000906BF7 /* Test.xcconfig */, + ); + path = Configurations; + sourceTree = "<group>"; + }; + D094E45917775B1000906BF7 /* Targets */ = { + isa = PBXGroup; + children = ( + D094E45A17775B1000906BF7 /* Application.xcconfig */, + D094E45B17775B1000906BF7 /* StaticLibrary.xcconfig */, + ); + path = Targets; + sourceTree = "<group>"; + }; + D094E45C17775B1000906BF7 /* iOS */ = { + isa = PBXGroup; + children = ( + D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */, + D094E45E17775B1000906BF7 /* iOS-Base.xcconfig */, + D094E45F17775B1000906BF7 /* iOS-StaticLibrary.xcconfig */, + ); + path = iOS; + sourceTree = "<group>"; + }; + D094E46017775B1000906BF7 /* Mac OS X */ = { + isa = PBXGroup; + children = ( + D094E46117775B1000906BF7 /* Mac-Application.xcconfig */, + D094E46217775B1000906BF7 /* Mac-Base.xcconfig */, + D094E46317775B1000906BF7 /* Mac-DynamicLibrary.xcconfig */, + D094E46417775B1000906BF7 /* Mac-Framework.xcconfig */, + D094E46517775B1000906BF7 /* Mac-StaticLibrary.xcconfig */, + ); + path = "Mac OS X"; + sourceTree = "<group>"; + }; + D0D486FB164253B600DD7605 /* Signals */ = { + isa = PBXGroup; + children = ( + 88CDF80415001CA800163A9F /* RACSignal.h */, + 88977C3D1512914A00A09EC5 /* RACSignal.m */, + D03525D317E2EBC90099CBAB /* RACSignalProvider.d */, + D0D910CC15F915BD00AD2DDA /* RACSignal+Operations.h */, + D0D910CD15F915BD00AD2DDA /* RACSignal+Operations.m */, + D077A16B169B740200057BB1 /* RACEvent.h */, + D077A16C169B740200057BB1 /* RACEvent.m */, + 88C5A0231692460A0045EF05 /* RACMulticastConnection.h */, + 88F5870515361C170084BD32 /* RACMulticastConnection+Private.h */, + 88C5A025169246140045EF05 /* RACMulticastConnection.m */, + 886F70281551CF920045D68B /* RACGroupedSignal.h */, + 886F70291551CF920045D68B /* RACGroupedSignal.m */, + D013A3EC1807B9260072B6CE /* Private */, + D0D486FC164253C400DD7605 /* Subjects */, + ); + name = Signals; + sourceTree = "<group>"; + }; + D0D486FC164253C400DD7605 /* Subjects */ = { + isa = PBXGroup; + children = ( + 880B9174150B09190008488E /* RACSubject.h */, + 880B9175150B09190008488E /* RACSubject.m */, + 88D4AB3C1510F6C30011494F /* RACReplaySubject.h */, + 88D4AB3D1510F6C30011494F /* RACReplaySubject.m */, + 883A84D81513964B006DB4C7 /* RACBehaviorSubject.h */, + 883A84D91513964B006DB4C7 /* RACBehaviorSubject.m */, + ); + name = Subjects; + sourceTree = "<group>"; + }; + D0D486FD164253D500DD7605 /* Disposables */ = { + isa = PBXGroup; + children = ( + 883A84DD1513B5EC006DB4C7 /* RACDisposable.h */, + 883A84DE1513B5EC006DB4C7 /* RACDisposable.m */, + 884476E2152367D100958F44 /* RACScopedDisposable.h */, + 884476E3152367D100958F44 /* RACScopedDisposable.m */, + 881E86A01669304700667F7B /* RACCompoundDisposable.h */, + 881E86A11669304700667F7B /* RACCompoundDisposable.m */, + D020F3DA17F6A3E40092BED2 /* RACCompoundDisposableProvider.d */, + D028DB72179E53CB00D1042F /* RACSerialDisposable.h */, + D028DB73179E53CB00D1042F /* RACSerialDisposable.m */, + ); + name = Disposables; + sourceTree = "<group>"; + }; + D0D486FE164253E100DD7605 /* Commands */ = { + isa = PBXGroup; + children = ( + 882093E91501E6EE00796685 /* RACCommand.h */, + 882093EA1501E6EE00796685 /* RACCommand.m */, + ); + name = Commands; + sourceTree = "<group>"; + }; + D0E967561641EF8200FCFF06 /* Sequences */ = { + isa = PBXGroup; + children = ( + D0E967671641EF9C00FCFF06 /* RACSequence.h */, + D0E967681641EF9C00FCFF06 /* RACSequence.m */, + D0E967611641EF9C00FCFF06 /* RACArraySequence.h */, + D0E967621641EF9C00FCFF06 /* RACArraySequence.m */, + D0E967631641EF9C00FCFF06 /* RACDynamicSequence.h */, + D0E967641641EF9C00FCFF06 /* RACDynamicSequence.m */, + 5F9743F51694A2460024EB82 /* RACEagerSequence.h */, + 5F9743F61694A2460024EB82 /* RACEagerSequence.m */, + D0E967651641EF9C00FCFF06 /* RACEmptySequence.h */, + D0E967661641EF9C00FCFF06 /* RACEmptySequence.m */, + 7479F6E3186177D200575CDB /* RACIndexSetSequence.h */, + 7479F6E4186177D200575CDB /* RACIndexSetSequence.m */, + D0E967691641EF9C00FCFF06 /* RACStringSequence.h */, + D0E9676A1641EF9C00FCFF06 /* RACStringSequence.m */, + D0EE2849164D906B006954A4 /* RACSignalSequence.h */, + D0EE284A164D906B006954A4 /* RACSignalSequence.m */, + D0307EDB1731AAE100D83211 /* RACTupleSequence.h */, + D0307EDC1731AAE100D83211 /* RACTupleSequence.m */, + D07CD7141731BA3900DE2394 /* RACUnarySequence.h */, + D07CD7151731BA3900DE2394 /* RACUnarySequence.m */, + ); + name = Sequences; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 88037F8015056328001A5B19 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 882D071917614FA7009EDA69 /* RACTargetQueueScheduler.h in Headers */, + 881E87AC16695C5600667F7B /* RACQueueScheduler.h in Headers */, + 88037FB81505645C001A5B19 /* ReactiveCocoa.h in Headers */, + 88037FBB1505646C001A5B19 /* NSObject+RACPropertySubscribing.h in Headers */, + 88037FBC1505646C001A5B19 /* RACSignal.h in Headers */, + 88037FBE1505646C001A5B19 /* RACSubscriber.h in Headers */, + 88037FC11505646C001A5B19 /* RACCommand.h in Headers */, + 88037FC21505646C001A5B19 /* NSControl+RACCommandSupport.h in Headers */, + D028DB74179E53CB00D1042F /* RACSerialDisposable.h in Headers */, + 880B9176150B09190008488E /* RACSubject.h in Headers */, + D090767F17FBEADE00EB087A /* NSURLConnection+RACSupport.h in Headers */, + 88F440D3153DADEA0097B4C3 /* NSObject+RACAppKitBindings.h in Headers */, + 88D4AB3E1510F6C30011494F /* RACReplaySubject.h in Headers */, + 883A84DA1513964B006DB4C7 /* RACBehaviorSubject.h in Headers */, + 883A84DF1513B5EC006DB4C7 /* RACDisposable.h in Headers */, + 886F702A1551CF920045D68B /* RACGroupedSignal.h in Headers */, + 881B37CC152260BF0079220B /* RACUnit.h in Headers */, + 884476E4152367D100958F44 /* RACScopedDisposable.h in Headers */, + 88E2C6B4153C771C00C7493C /* RACScheduler.h in Headers */, + 88B76F8E153726B00053EAE2 /* RACTuple.h in Headers */, + 886CEAE2163DE942007632D1 /* NSObject+RACLifting.h in Headers */, + D0D910CE15F915BD00AD2DDA /* RACSignal+Operations.h in Headers */, + D0E9676B1641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.h in Headers */, + D0E9676F1641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.h in Headers */, + D0E967731641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.h in Headers */, + D0E967771641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.h in Headers */, + D0E9677B1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.h in Headers */, + D0E9678B1641EF9C00FCFF06 /* RACSequence.h in Headers */, + 88A0B6D3165B2B77005DE8F3 /* RACSubscriptingAssignmentTrampoline.h in Headers */, + D0D487011642550100DD7605 /* RACStream.h in Headers */, + 88C5A0241692460A0045EF05 /* RACMulticastConnection.h in Headers */, + 881E86A21669304800667F7B /* RACCompoundDisposable.h in Headers */, + 881E87C41669636000667F7B /* RACSubscriptionScheduler.h in Headers */, + 5F45A885168CFA3E00B58A2B /* RACKVOChannel.h in Headers */, + 880D7A5A16F7B351004A3361 /* NSObject+RACSelectorSignal.h in Headers */, + 6E58405516F22D7500F588A6 /* NSObject+RACDeallocating.h in Headers */, + 5F6FE8531692568A00A8D7A6 /* RACChannel.h in Headers */, + 882D072117615381009EDA69 /* RACQueueScheduler+Subclass.h in Headers */, + D005A259169A3B7D00A9D2DB /* RACBacktrace.h in Headers */, + BE527E9318636F7F006349E8 /* NSUserDefaults+RACSupport.h in Headers */, + 5F773DEA169B46670023069D /* NSEnumerator+RACSequenceAdditions.h in Headers */, + D077A16D169B740200057BB1 /* RACEvent.h in Headers */, + D0A0B01516EAA3D100C47593 /* NSText+RACSignalSupport.h in Headers */, + D0A0B03B16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.h in Headers */, + D094E44917775AF200906BF7 /* EXTKeyPathCoding.h in Headers */, + D094E44B17775AF200906BF7 /* EXTScope.h in Headers */, + D094E44F17775AF200906BF7 /* metamacros.h in Headers */, + D00930791788AB7B00EE7E8B /* RACTestScheduler.h in Headers */, + 74F17318186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h in Headers */, + 55C39DEC17F1EC84006DC60C /* NSData+RACSupport.h in Headers */, + 55C39DED17F1EC84006DC60C /* NSFileHandle+RACSupport.h in Headers */, + 55C39DEE17F1EC84006DC60C /* NSNotificationCenter+RACSupport.h in Headers */, + 55C39DEF17F1EC84006DC60C /* NSString+RACSupport.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88F440A9153DAC820097B4C3 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 880D7A5B16F7B351004A3361 /* NSObject+RACSelectorSignal.h in Headers */, + CD11C6F318714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.h in Headers */, + 5F45A886168CFA3E00B58A2B /* RACKVOChannel.h in Headers */, + 554D9E5D181064E200F21262 /* UIRefreshControl+RACCommandSupport.h in Headers */, + 5F6FE8541692568A00A8D7A6 /* RACChannel.h in Headers */, + D08FF264169A32D100743C6D /* ReactiveCocoa.h in Headers */, + D08FF265169A32DC00743C6D /* RACSubscriber.h in Headers */, + D08FF267169A330000743C6D /* RACUnit.h in Headers */, + D08FF268169A330000743C6D /* RACTuple.h in Headers */, + D08FF269169A330000743C6D /* RACBacktrace.h in Headers */, + D08FF26A169A330000743C6D /* RACSubscriptingAssignmentTrampoline.h in Headers */, + 74F17319186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h in Headers */, + D08FF26C169A331A00743C6D /* RACStream.h in Headers */, + D08FF26D169A331A00743C6D /* RACSignal.h in Headers */, + D08FF26E169A331A00743C6D /* RACSignal+Operations.h in Headers */, + D08FF26F169A331A00743C6D /* RACMulticastConnection.h in Headers */, + D08FF270169A331A00743C6D /* RACGroupedSignal.h in Headers */, + D08FF271169A331A00743C6D /* RACSubject.h in Headers */, + D08FF272169A331A00743C6D /* RACReplaySubject.h in Headers */, + D08FF273169A331A00743C6D /* RACBehaviorSubject.h in Headers */, + D08FF274169A331A00743C6D /* RACDisposable.h in Headers */, + D08FF275169A331A00743C6D /* RACScopedDisposable.h in Headers */, + D08FF276169A331A00743C6D /* RACCompoundDisposable.h in Headers */, + D08FF277169A331B00743C6D /* RACCommand.h in Headers */, + D08FF278169A331B00743C6D /* RACSequence.h in Headers */, + D08FF27F169A331B00743C6D /* RACScheduler.h in Headers */, + D08FF26B169A330000743C6D /* NSObject+RACLifting.h in Headers */, + D08FF280169A333400743C6D /* NSArray+RACSequenceAdditions.h in Headers */, + D028DB75179E53CB00D1042F /* RACSerialDisposable.h in Headers */, + D08FF281169A333400743C6D /* NSDictionary+RACSequenceAdditions.h in Headers */, + D08FF282169A333400743C6D /* NSOrderedSet+RACSequenceAdditions.h in Headers */, + D028DB87179E616700D1042F /* UITableViewCell+RACSignalSupport.h in Headers */, + 1646747B17FFA0610036E30B /* UICollectionReusableView+RACSignalSupport.h in Headers */, + D08FF283169A333400743C6D /* NSSet+RACSequenceAdditions.h in Headers */, + D08FF284169A333400743C6D /* NSString+RACSequenceAdditions.h in Headers */, + D08FF285169A333400743C6D /* UIControl+RACSignalSupport.h in Headers */, + D08FF286169A333400743C6D /* UITextField+RACSignalSupport.h in Headers */, + D08FF287169A333400743C6D /* UITextView+RACSignalSupport.h in Headers */, + D08FF289169A333400743C6D /* NSObject+RACPropertySubscribing.h in Headers */, + 5F773DEB169B46670023069D /* NSEnumerator+RACSequenceAdditions.h in Headers */, + D077A16E169B740200057BB1 /* RACEvent.h in Headers */, + 6EA0C08216F4AEC1006EBEB2 /* NSObject+RACDeallocating.h in Headers */, + 27A887D21703DDEB00040001 /* UIBarButtonItem+RACCommandSupport.h in Headers */, + 1EC06B17173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.h in Headers */, + D090768017FBEADE00EB087A /* NSURLConnection+RACSupport.h in Headers */, + 5EE9A7931760D61300EAF5A2 /* UIButton+RACCommandSupport.h in Headers */, + 88302C961762EC79003633BD /* RACQueueScheduler.h in Headers */, + BE527E9418636F7F006349E8 /* NSUserDefaults+RACSupport.h in Headers */, + 88302C9B1762EC7E003633BD /* RACQueueScheduler+Subclass.h in Headers */, + 88302CA21762F62D003633BD /* RACTargetQueueScheduler.h in Headers */, + 557A4B5A177648C7008EF796 /* UIActionSheet+RACSignalSupport.h in Headers */, + ACB0EAF41797DDD400942FFC /* UIAlertView+RACSignalSupport.h in Headers */, + D094E44A17775AF200906BF7 /* EXTKeyPathCoding.h in Headers */, + D094E44C17775AF200906BF7 /* EXTScope.h in Headers */, + D094E45017775AF200906BF7 /* metamacros.h in Headers */, + D009307A1788AB7B00EE7E8B /* RACTestScheduler.h in Headers */, + 5F70B2AF17AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.h in Headers */, + 5F70B2BE17AB1857009AEDF9 /* UISegmentedControl+RACSignalSupport.h in Headers */, + 5F70B2C017AB1857009AEDF9 /* UISlider+RACSignalSupport.h in Headers */, + 5F70B2C217AB1857009AEDF9 /* UIStepper+RACSignalSupport.h in Headers */, + 5F70B2C417AB1857009AEDF9 /* UISwitch+RACSignalSupport.h in Headers */, + 55C39DF017F1EC84006DC60C /* NSData+RACSupport.h in Headers */, + 55C39DF117F1EC84006DC60C /* NSFileHandle+RACSupport.h in Headers */, + 55C39DF217F1EC84006DC60C /* NSNotificationCenter+RACSupport.h in Headers */, + 55C39DF317F1EC84006DC60C /* NSString+RACSupport.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D05AD39217F2D56F0080895B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D05AD3E017F2DB1D0080895B /* RACBehaviorSubject.h in Headers */, + D05AD3C917F2DB100080895B /* RACSubscriptingAssignmentTrampoline.h in Headers */, + D05AD3C517F2DB100080895B /* RACTuple.h in Headers */, + 74F1731A186024A900BC937C /* NSIndexSet+RACSequenceAdditions.h in Headers */, + D05AD41A17F2DB6A0080895B /* NSString+RACSequenceAdditions.h in Headers */, + D05AD41E17F2DB6E0080895B /* NSControl+RACCommandSupport.h in Headers */, + D05AD3E417F2DB230080895B /* RACScopedDisposable.h in Headers */, + D05AD40F17F2DB6A0080895B /* NSFileHandle+RACSupport.h in Headers */, + D05AD40517F2DB5D0080895B /* RACTestScheduler.h in Headers */, + D05AD42817F2DB840080895B /* RACKVOChannel.h in Headers */, + D05AD3FF17F2DB5D0080895B /* RACTargetQueueScheduler.h in Headers */, + D05AD3E217F2DB230080895B /* RACDisposable.h in Headers */, + D05AD42617F2DB840080895B /* RACChannel.h in Headers */, + D05AD3EA17F2DB270080895B /* RACCommand.h in Headers */, + D05AD3DE17F2DB1D0080895B /* RACReplaySubject.h in Headers */, + D05AD3D817F2DB1D0080895B /* RACMulticastConnection.h in Headers */, + D05AD3E817F2DB230080895B /* RACSerialDisposable.h in Headers */, + D05AD42E17F2DB840080895B /* NSObject+RACPropertySubscribing.h in Headers */, + D05AD3CB17F2DB100080895B /* NSObject+RACDeallocating.h in Headers */, + D05AD3D417F2DB1D0080895B /* RACSignal+Operations.h in Headers */, + D05AD41417F2DB6A0080895B /* NSObject+RACSelectorSignal.h in Headers */, + D05AD3FC17F2DB5D0080895B /* RACQueueScheduler.h in Headers */, + D05AD42017F2DB6E0080895B /* NSControl+RACTextSignalSupport.h in Headers */, + D05AD3FD17F2DB5D0080895B /* RACQueueScheduler+Subclass.h in Headers */, + D05AD42217F2DB6E0080895B /* NSObject+RACAppKitBindings.h in Headers */, + BE527E9518636F7F006349E8 /* NSUserDefaults+RACSupport.h in Headers */, + D05AD3DA17F2DB1D0080895B /* RACGroupedSignal.h in Headers */, + D05AD41617F2DB6A0080895B /* NSOrderedSet+RACSequenceAdditions.h in Headers */, + D05AD3E617F2DB230080895B /* RACCompoundDisposable.h in Headers */, + D05AD40B17F2DB6A0080895B /* NSDictionary+RACSequenceAdditions.h in Headers */, + D05AD3D617F2DB1D0080895B /* RACEvent.h in Headers */, + D05AD3D017F2DB100080895B /* RACStream.h in Headers */, + D05AD3DC17F2DB1D0080895B /* RACSubject.h in Headers */, + D05AD41C17F2DB6A0080895B /* NSString+RACSupport.h in Headers */, + D05AD42417F2DB6E0080895B /* NSText+RACSignalSupport.h in Headers */, + D05AD3C017F2DA300080895B /* RACSubscriber.h in Headers */, + D05AD40717F2DB6A0080895B /* NSArray+RACSequenceAdditions.h in Headers */, + D090768117FBEADE00EB087A /* NSURLConnection+RACSupport.h in Headers */, + D05AD3C717F2DB100080895B /* RACBacktrace.h in Headers */, + D05AD3EE17F2DB4F0080895B /* RACSequence.h in Headers */, + D05AD3D217F2DB1D0080895B /* RACSignal.h in Headers */, + D05AD3CD17F2DB100080895B /* NSObject+RACLifting.h in Headers */, + D05AD40D17F2DB6A0080895B /* NSEnumerator+RACSequenceAdditions.h in Headers */, + D05AD3FA17F2DB5D0080895B /* RACScheduler.h in Headers */, + D05AD40317F2DB5D0080895B /* RACSubscriptionScheduler.h in Headers */, + D05AD41817F2DB6A0080895B /* NSSet+RACSequenceAdditions.h in Headers */, + D05AD41117F2DB6A0080895B /* NSNotificationCenter+RACSupport.h in Headers */, + D05AD40917F2DB6A0080895B /* NSData+RACSupport.h in Headers */, + D05AD3C317F2DB100080895B /* RACUnit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 1860F411177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1860F44C177C91B500C7B3C9 /* Build configuration list for PBXNativeTarget "ReactiveCocoa-iOS-UIKitTestHost" */; + buildPhases = ( + 1860F40E177C91B500C7B3C9 /* Sources */, + 1860F40F177C91B500C7B3C9 /* Frameworks */, + 1860F410177C91B500C7B3C9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D0F117C3179F0A91006CE68F /* PBXTargetDependency */, + ); + name = "ReactiveCocoa-iOS-UIKitTestHost"; + productName = "ReactiveCocoa-iOS-UIKitTestHost"; + productReference = 1860F412177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost.app */; + productType = "com.apple.product-type.application"; + }; + 1860F42F177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHostTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1860F44D177C91B500C7B3C9 /* Build configuration list for PBXNativeTarget "ReactiveCocoa-iOS-UIKitTestHostTests" */; + buildPhases = ( + 1860F42B177C91B500C7B3C9 /* Sources */, + 1860F42C177C91B500C7B3C9 /* Frameworks */, + 1860F42D177C91B500C7B3C9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1860F436177C91B500C7B3C9 /* PBXTargetDependency */, + ); + name = "ReactiveCocoa-iOS-UIKitTestHostTests"; + productName = "ReactiveCocoa-iOS-UIKitTestHostTests"; + productReference = 1860F430177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHostTests.octest */; + productType = "com.apple.product-type.bundle"; + }; + 5FAF5222174D4C2000CAC810 /* ReactiveCocoaTests-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5FAF523D174D4C2000CAC810 /* Build configuration list for PBXNativeTarget "ReactiveCocoaTests-iOS" */; + buildPhases = ( + 5FAF521E174D4C2000CAC810 /* Sources */, + 5FAF521F174D4C2000CAC810 /* Frameworks */, + 5FAF5220174D4C2000CAC810 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D0ED9DB617501806003859A6 /* PBXTargetDependency */, + ); + name = "ReactiveCocoaTests-iOS"; + productName = "ReactiveCocoaTests-iOS"; + productReference = 5FAF5223174D4C2000CAC810 /* ReactiveCocoaTests-iOS.octest */; + productType = "com.apple.product-type.bundle"; + }; + 88037F8215056328001A5B19 /* ReactiveCocoa */ = { + isa = PBXNativeTarget; + buildConfigurationList = 88037F8F15056328001A5B19 /* Build configuration list for PBXNativeTarget "ReactiveCocoa" */; + buildPhases = ( + 88037F7E15056328001A5B19 /* Sources */, + 88037F7F15056328001A5B19 /* Frameworks */, + 88037F8015056328001A5B19 /* Headers */, + 88037F8115056328001A5B19 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ReactiveCocoa; + productName = ReactiveCocoa; + productReference = 88037F8315056328001A5B19 /* ReactiveCocoa.framework */; + productType = "com.apple.product-type.framework"; + }; + 88CDF7DB15000FCF00163A9F /* ReactiveCocoaTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 88CDF7F015000FCF00163A9F /* Build configuration list for PBXNativeTarget "ReactiveCocoaTests" */; + buildPhases = ( + 88CDF7D715000FCF00163A9F /* Sources */, + 88CDF7D815000FCF00163A9F /* Frameworks */, + 88CDF7D915000FCF00163A9F /* Resources */, + 8820937F1501C94E00796685 /* Copy Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 88037FDB150564E9001A5B19 /* PBXTargetDependency */, + ); + name = ReactiveCocoaTests; + productName = GHObservableTests; + productReference = 88CDF7DC15000FCF00163A9F /* ReactiveCocoaTests.octest */; + productType = "com.apple.product-type.bundle"; + }; + 88F440AA153DAC820097B4C3 /* ReactiveCocoa-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 88F440B3153DAC820097B4C3 /* Build configuration list for PBXNativeTarget "ReactiveCocoa-iOS" */; + buildPhases = ( + 88F440A7153DAC820097B4C3 /* Sources */, + 88F440A8153DAC820097B4C3 /* Frameworks */, + 88F440A9153DAC820097B4C3 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveCocoa-iOS"; + productName = ReactiveCocoaLib; + productReference = 88F440AB153DAC820097B4C3 /* libReactiveCocoa-iOS.a */; + productType = "com.apple.product-type.library.static"; + }; + D05AD39317F2D56F0080895B /* ReactiveCocoa-Mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = D05AD3BD17F2D5710080895B /* Build configuration list for PBXNativeTarget "ReactiveCocoa-Mac" */; + buildPhases = ( + D05AD39017F2D56F0080895B /* Sources */, + D05AD39117F2D56F0080895B /* Frameworks */, + D05AD39217F2D56F0080895B /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveCocoa-Mac"; + productName = "ReactiveCocoa-Mac"; + productReference = D05AD39417F2D56F0080895B /* libReactiveCocoa-Mac.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 88CDF7B215000FCE00163A9F /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = RAC; + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "GitHub, Inc."; + }; + buildConfigurationList = 88CDF7B515000FCE00163A9F /* Build configuration list for PBXProject "ReactiveCocoa" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 88CDF7B015000FCE00163A9F; + productRefGroup = 88CDF7BC15000FCE00163A9F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 88037F8215056328001A5B19 /* ReactiveCocoa */, + 88F440AA153DAC820097B4C3 /* ReactiveCocoa-iOS */, + 88CDF7DB15000FCF00163A9F /* ReactiveCocoaTests */, + 5FAF5222174D4C2000CAC810 /* ReactiveCocoaTests-iOS */, + 1860F411177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost */, + 1860F42F177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHostTests */, + D05AD39317F2D56F0080895B /* ReactiveCocoa-Mac */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1860F410177C91B500C7B3C9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1860F41E177C91B500C7B3C9 /* InfoPlist.strings in Resources */, + 1860F426177C91B500C7B3C9 /* Default.png in Resources */, + 1860F428177C91B500C7B3C9 /* Default@2x.png in Resources */, + 1860F42A177C91B500C7B3C9 /* Default-568h@2x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1860F42D177C91B500C7B3C9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1860F43C177C91B500C7B3C9 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5FAF5220174D4C2000CAC810 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D090768E17FBED2E00EB087A /* test-data.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88037F8115056328001A5B19 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88CDF7D915000FCF00163A9F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D090768D17FBED2E00EB087A /* test-data.json in Resources */, + 88CDF7E715000FCF00163A9F /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1860F40E177C91B500C7B3C9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CD11C6FF18714F00007C7CFD /* RACTestTableViewController.m in Sources */, + 1860F420177C91B500C7B3C9 /* main.m in Sources */, + 1860F424177C91B500C7B3C9 /* RACAppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1860F42B177C91B500C7B3C9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CD11C700187151AE007C7CFD /* UITableViewHeaderFooterViewRACSupportSpec.m in Sources */, + 1668028317FE775200C724B4 /* UICollectionReusableViewRACSupportSpec.m in Sources */, + 5564537F18107203002BD2E4 /* RACControlCommandExamples.m in Sources */, + D0F117CC179F0B97006CE68F /* UITableViewCellRACSupportSpec.m in Sources */, + 1860F44F177C958300C7B3C9 /* UITextFieldRACSupportSpec.m in Sources */, + 1860F450177C958900C7B3C9 /* UITextViewRACSupportSpec.m in Sources */, + D004BC9C177E1A2B00A5B8C5 /* UIActionSheetRACSupportSpec.m in Sources */, + 5564542418107275002BD2E4 /* UIRefreshControlRACSupportSpec.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5FAF521E174D4C2000CAC810 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5FAF523E174D4D3200CAC810 /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */, + 5FAF523F174D4D3600CAC810 /* NSNotificationCenterRACSupportSpec.m in Sources */, + D090768B17FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m in Sources */, + 5FAF5240174D4D5600CAC810 /* NSObjectRACDeallocatingSpec.m in Sources */, + 5FAF5241174D4D5600CAC810 /* NSObjectRACLiftingSpec.m in Sources */, + 5FAF5242174D4D5600CAC810 /* NSObjectRACPropertySubscribingExamples.m in Sources */, + 5FAF5243174D4D5600CAC810 /* NSObjectRACPropertySubscribingSpec.m in Sources */, + 5FAF5244174D4D5600CAC810 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */, + 5FAF5246174D4D5600CAC810 /* RACBacktraceSpec.m in Sources */, + 5FAF5247174D4D5600CAC810 /* RACBlockTrampolineSpec.m in Sources */, + 5FAF5248174D4D5600CAC810 /* RACCommandSpec.m in Sources */, + 5FAF5249174D4D5600CAC810 /* RACCompoundDisposableSpec.m in Sources */, + BE527EA01863706B006349E8 /* NSUserDefaultsRACSupportSpec.m in Sources */, + 5FAF524A174D4D5600CAC810 /* RACEventSpec.m in Sources */, + 5FAF524B174D4D5600CAC810 /* RACKVOWrapperSpec.m in Sources */, + 5FAF524C174D4D5600CAC810 /* RACMulticastConnectionSpec.m in Sources */, + 5FAF524D174D4D5600CAC810 /* RACKVOChannelSpec.m in Sources */, + 5FAF524E174D4D5600CAC810 /* RACPropertySignalExamples.m in Sources */, + 5FAF524F174D4D5600CAC810 /* RACChannelExamples.m in Sources */, + 5FAF5250174D4D5600CAC810 /* RACChannelSpec.m in Sources */, + 5FAF5251174D4D5600CAC810 /* RACSchedulerSpec.m in Sources */, + 5FAF5252174D4D5600CAC810 /* RACSequenceAdditionsSpec.m in Sources */, + 5FAF5253174D4D5600CAC810 /* RACSequenceExamples.m in Sources */, + 5FAF5254174D4D5600CAC810 /* RACSequenceSpec.m in Sources */, + 5FAF5255174D4D5600CAC810 /* RACSignalSpec.m in Sources */, + 5FAF5256174D4D5600CAC810 /* RACStreamExamples.m in Sources */, + 5FAF5257174D4D5600CAC810 /* RACSubjectSpec.m in Sources */, + 5FAF5258174D4D5600CAC810 /* RACSubscriberExamples.m in Sources */, + 5FAF5259174D4D5600CAC810 /* RACSubscriberSpec.m in Sources */, + AC65FD52176DECB1005ED22B /* UIAlertViewRACSupportSpec.m in Sources */, + 5FAF525A174D4D5600CAC810 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */, + 5FAF525B174D4D5600CAC810 /* RACTestObject.m in Sources */, + 5FAF525C174D4D5600CAC810 /* RACTupleSpec.m in Sources */, + 5FAF525D174D4D5600CAC810 /* NSObjectRACSelectorSignalSpec.m in Sources */, + 5FAF525E174D4D5600CAC810 /* RACSubclassObject.m in Sources */, + 5FAF5262174D4D8F00CAC810 /* UIBarButtonItemRACSupportSpec.m in Sources */, + 88302BFE1762A9E6003633BD /* RACTestExampleScheduler.m in Sources */, + 8884DD6B1756B65300F6C379 /* RACSignalStartExamples.m in Sources */, + D028DB7E179E591E00D1042F /* RACSerialDisposableSpec.m in Sources */, + 5EE9A79B1760D88500EAF5A2 /* UIButtonRACSupportSpec.m in Sources */, + 88302C2F1762C180003633BD /* RACTargetQueueSchedulerSpec.m in Sources */, + D0A0E227176A84DB007273ED /* RACDisposableSpec.m in Sources */, + D066C796176D262500C242D2 /* UIControlRACSupportSpec.m in Sources */, + D066C79D176D263D00C242D2 /* RACTestUIButton.m in Sources */, + D0C55CE217759559008CDDCA /* RACDelegateProxySpec.m in Sources */, + D07200261788C57200987F70 /* RACTestSchedulerSpec.m in Sources */, + D075A72B17BCB7E100C24FB7 /* RACControlCommandExamples.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88037F7E15056328001A5B19 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88A0B6D2165B2B09005DE8F3 /* RACBlockTrampoline.m in Sources */, + 88037FC71505647E001A5B19 /* NSObject+RACKVOWrapper.m in Sources */, + 88037FC91505648C001A5B19 /* RACSubscriber.m in Sources */, + 88037FCC1505648C001A5B19 /* RACCommand.m in Sources */, + 88037FCD1505648C001A5B19 /* NSControl+RACCommandSupport.m in Sources */, + D090768217FBEADE00EB087A /* NSURLConnection+RACSupport.m in Sources */, + 88DA309815071CBA00C19D0F /* RACValueTransformer.m in Sources */, + 880B9177150B09190008488E /* RACSubject.m in Sources */, + 88D4AB3F1510F6C30011494F /* RACReplaySubject.m in Sources */, + 88977C3E1512914A00A09EC5 /* RACSignal.m in Sources */, + 883A84DB1513964B006DB4C7 /* RACBehaviorSubject.m in Sources */, + 883A84E01513B5EC006DB4C7 /* RACDisposable.m in Sources */, + 886678711518DCD800DE77EC /* NSObject+RACPropertySubscribing.m in Sources */, + 881B37CD152260BF0079220B /* RACUnit.m in Sources */, + 884476E5152367D100958F44 /* RACScopedDisposable.m in Sources */, + 88B76F8F153726B00053EAE2 /* RACTuple.m in Sources */, + 88E2C6B5153C771C00C7493C /* RACScheduler.m in Sources */, + 88F440D4153DADEA0097B4C3 /* NSObject+RACAppKitBindings.m in Sources */, + 886F702B1551CF920045D68B /* RACGroupedSignal.m in Sources */, + A1FCC374156754A7008C9686 /* RACObjCRuntime.m in Sources */, + BE527E9618636F7F006349E8 /* NSUserDefaults+RACSupport.m in Sources */, + D0DFBCCE15DD6D40009DADB3 /* RACBacktrace.m in Sources */, + D0D910D015F915BD00AD2DDA /* RACSignal+Operations.m in Sources */, + 88FC735716114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m in Sources */, + 886CEAE4163DE942007632D1 /* NSObject+RACLifting.m in Sources */, + D013A3E11807B7450072B6CE /* RACEmptySignal.m in Sources */, + 887ACDA9165878A8009190AD /* NSInvocation+RACTypeParsing.m in Sources */, + D0E9676D1641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m in Sources */, + D0E967711641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m in Sources */, + D0E967751641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m in Sources */, + D0E967791641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m in Sources */, + D0E9677D1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m in Sources */, + D0E967811641EF9C00FCFF06 /* RACArraySequence.m in Sources */, + D0E967851641EF9C00FCFF06 /* RACDynamicSequence.m in Sources */, + D0E967891641EF9C00FCFF06 /* RACEmptySequence.m in Sources */, + D03525D417E2EBC90099CBAB /* RACSignalProvider.d in Sources */, + D028DB76179E53CB00D1042F /* RACSerialDisposable.m in Sources */, + D0E9678D1641EF9C00FCFF06 /* RACSequence.m in Sources */, + D0E967911641EF9C00FCFF06 /* RACStringSequence.m in Sources */, + D0D487031642550100DD7605 /* RACStream.m in Sources */, + D0EE284D164D906B006954A4 /* RACSignalSequence.m in Sources */, + 881E86A41669304800667F7B /* RACCompoundDisposable.m in Sources */, + 881E87AE16695C5600667F7B /* RACQueueScheduler.m in Sources */, + 881E87B416695EDF00667F7B /* RACImmediateScheduler.m in Sources */, + 881E87C61669636000667F7B /* RACSubscriptionScheduler.m in Sources */, + 5F45A887168CFA3E00B58A2B /* RACKVOChannel.m in Sources */, + 5F6FE8551692568A00A8D7A6 /* RACChannel.m in Sources */, + 88C5A026169246140045EF05 /* RACMulticastConnection.m in Sources */, + 5F9743F91694A2460024EB82 /* RACEagerSequence.m in Sources */, + 5F773DEC169B46670023069D /* NSEnumerator+RACSequenceAdditions.m in Sources */, + D077A16F169B740200057BB1 /* RACEvent.m in Sources */, + 8837EA1816A5A33300FC3CDF /* RACKVOTrampoline.m in Sources */, + D0A0B01616EAA3D100C47593 /* NSText+RACSignalSupport.m in Sources */, + D020F3DB17F6A3E40092BED2 /* RACCompoundDisposableProvider.d in Sources */, + D0A0B03C16EAA9AC00C47593 /* NSControl+RACTextSignalSupport.m in Sources */, + D013A3E91807B7C30072B6CE /* RACReturnSignal.m in Sources */, + 6E58405616F22D7500F588A6 /* NSObject+RACDeallocating.m in Sources */, + 880D7A5C16F7B351004A3361 /* NSObject+RACSelectorSignal.m in Sources */, + D0307EDF1731AAE100D83211 /* RACTupleSequence.m in Sources */, + D07CD7181731BA3900DE2394 /* RACUnarySequence.m in Sources */, + 5FDC35051736F54700792E52 /* NSString+RACKeyPathUtilities.m in Sources */, + D0D243BD1741FA13004359C6 /* NSObject+RACDescription.m in Sources */, + 882D071A17614FA7009EDA69 /* RACTargetQueueScheduler.m in Sources */, + 74F1731B186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m in Sources */, + D0A0E230176A8CD6007273ED /* RACPassthroughSubscriber.m in Sources */, + D013A3D91807B5ED0072B6CE /* RACErrorSignal.m in Sources */, + 7479F6E8186177D200575CDB /* RACIndexSetSequence.m in Sources */, + D05F9D3717984EC000FD7982 /* EXTRuntimeExtensions.m in Sources */, + D009307B1788AB7B00EE7E8B /* RACTestScheduler.m in Sources */, + 55C39DE417F1EC6E006DC60C /* NSData+RACSupport.m in Sources */, + 55C39DE517F1EC6E006DC60C /* NSFileHandle+RACSupport.m in Sources */, + 55C39DE617F1EC6E006DC60C /* NSNotificationCenter+RACSupport.m in Sources */, + D013A3F21807B9690072B6CE /* RACDynamicSignal.m in Sources */, + 55C39DE717F1EC6E006DC60C /* NSString+RACSupport.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88CDF7D715000FCF00163A9F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8820937C1501C8A600796685 /* RACSignalSpec.m in Sources */, + 889D0A8015974B2A00F833E3 /* RACSubjectSpec.m in Sources */, + D041376915D2281C004BBF80 /* RACKVOWrapperSpec.m in Sources */, + D028DB7D179E591E00D1042F /* RACSerialDisposableSpec.m in Sources */, + 882CCA1E15F1564D00937D6E /* RACCommandSpec.m in Sources */, + 884848B615F658B800B11BD0 /* NSControlRACSupportSpec.m in Sources */, + 88442A341608A9AD00636B49 /* RACTestObject.m in Sources */, + 88FC735B16114FFB00F8A774 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */, + 8851A38B16161D500050D47F /* NSObjectRACPropertySubscribingSpec.m in Sources */, + D0D487061642651400DD7605 /* RACSequenceSpec.m in Sources */, + D0487AB3164314430085D890 /* RACStreamExamples.m in Sources */, + D0C70F90164337A2007027B4 /* RACSequenceAdditionsSpec.m in Sources */, + D0C70F93164337E3007027B4 /* RACSequenceExamples.m in Sources */, + 886CEACD163DE669007632D1 /* RACBlockTrampolineSpec.m in Sources */, + 8801E7511644BDE200A155FE /* NSObjectRACLiftingSpec.m in Sources */, + D0C70EC616659333005AAD03 /* RACSubscriberExamples.m in Sources */, + D090768A17FBECBF00EB087A /* NSURLConnectionRACSupportSpec.m in Sources */, + D0C70EC8166595AD005AAD03 /* RACSubscriberSpec.m in Sources */, + 8803C011166732BA00C36839 /* RACSchedulerSpec.m in Sources */, + 881E86BA1669350B00667F7B /* RACCompoundDisposableSpec.m in Sources */, + D0700F4C1672994D00D7CD30 /* NSNotificationCenterRACSupportSpec.m in Sources */, + 5F2447AD167E87C50062180C /* RACKVOChannelSpec.m in Sources */, + D02221621678910900DBD031 /* RACTupleSpec.m in Sources */, + 5F7EFECF168FBC4B0037E500 /* RACChannelExamples.m in Sources */, + 5F7EFED0168FBC4B0037E500 /* RACChannelSpec.m in Sources */, + D0870C6F16884A0600D0E11D /* RACBacktraceSpec.m in Sources */, + 88C5A02916924BFC0045EF05 /* RACMulticastConnectionSpec.m in Sources */, + D0EDE76716968AB10072A780 /* RACPropertySignalExamples.m in Sources */, + BE527E9F1863705B006349E8 /* NSUserDefaultsRACSupportSpec.m in Sources */, + 5F773DF0169B48830023069D /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */, + D077A172169B79A900057BB1 /* RACEventSpec.m in Sources */, + D0A0B01816EAA5CC00C47593 /* NSTextRACSupportSpec.m in Sources */, + 6E58405F16F3414200F588A6 /* NSObjectRACDeallocatingSpec.m in Sources */, + 1E893381171647A5009071B0 /* NSObjectRACPropertySubscribingExamples.m in Sources */, + 880D7A6616F7BB1A004A3361 /* NSObjectRACSelectorSignalSpec.m in Sources */, + 880D7A6916F7BCC7004A3361 /* RACSubclassObject.m in Sources */, + 5FDC350F1736F81900792E52 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */, + D075A72A17BCB7E100C24FB7 /* RACControlCommandExamples.m in Sources */, + 88302BFD1762A9E6003633BD /* RACTestExampleScheduler.m in Sources */, + 4925E806181BCC71000B2FEE /* NSControllerRACSupportSpec.m in Sources */, + 8884DD651756ACF600F6C379 /* RACSignalStartExamples.m in Sources */, + 88302C2E1762C180003633BD /* RACTargetQueueSchedulerSpec.m in Sources */, + D0A0E226176A84DB007273ED /* RACDisposableSpec.m in Sources */, + D011F9D01782AFD400EE7E38 /* NSObjectRACAppKitBindingsSpec.m in Sources */, + D07200251788C57200987F70 /* RACTestSchedulerSpec.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88F440A7153DAC820097B4C3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 27A887D11703DC6800040001 /* UIBarButtonItem+RACCommandSupport.m in Sources */, + 8882D4601673B0450080E7CD /* RACBlockTrampoline.m in Sources */, + 88F440BA153DAD570097B4C3 /* RACCommand.m in Sources */, + 88F440BC153DAD5A0097B4C3 /* RACSubscriber.m in Sources */, + 88F440BD153DAD5C0097B4C3 /* RACSignal.m in Sources */, + 88F440CF153DAD850097B4C3 /* NSObject+RACPropertySubscribing.m in Sources */, + ACB0EAF31797DDD400942FFC /* UIAlertView+RACSignalSupport.m in Sources */, + 88F440CE153DAD830097B4C3 /* NSObject+RACKVOWrapper.m in Sources */, + 88F440C1153DAD640097B4C3 /* RACReplaySubject.m in Sources */, + 88F440C0153DAD630097B4C3 /* RACSubject.m in Sources */, + 88F440C6153DAD6E0097B4C3 /* RACScopedDisposable.m in Sources */, + 88F440C3153DAD690097B4C3 /* RACBehaviorSubject.m in Sources */, + 88F440C9153DAD740097B4C3 /* RACUnit.m in Sources */, + 88F440CB153DAD780097B4C3 /* RACScheduler.m in Sources */, + 88F440CA153DAD760097B4C3 /* RACTuple.m in Sources */, + 88F440C5153DAD6C0097B4C3 /* RACDisposable.m in Sources */, + D090768317FBEADE00EB087A /* NSURLConnection+RACSupport.m in Sources */, + 88F44263153DC2C70097B4C3 /* UIControl+RACSignalSupport.m in Sources */, + 886F702C1551CF9D0045D68B /* RACGroupedSignal.m in Sources */, + 88F44267153DCAC50097B4C3 /* UITextField+RACSignalSupport.m in Sources */, + A1FCC27715666AA3008C9686 /* UITextView+RACSignalSupport.m in Sources */, + D03525D917E2FAAD0099CBAB /* RACSignalProvider.d in Sources */, + A1FCC375156754A7008C9686 /* RACObjCRuntime.m in Sources */, + A1FCC37B1567DED0008C9686 /* RACDelegateProxy.m in Sources */, + D0DFBCCF15DD6D40009DADB3 /* RACBacktrace.m in Sources */, + D0D910D115F915BD00AD2DDA /* RACSignal+Operations.m in Sources */, + 88FC735816114F9C00F8A774 /* RACSubscriptingAssignmentTrampoline.m in Sources */, + CD11C6F418714CD0007C7CFD /* UITableViewHeaderFooterView+RACSignalSupport.m in Sources */, + 886CEAE5163DE942007632D1 /* NSObject+RACLifting.m in Sources */, + 887ACDAA165878A8009190AD /* NSInvocation+RACTypeParsing.m in Sources */, + D0E9676E1641EF9C00FCFF06 /* NSArray+RACSequenceAdditions.m in Sources */, + D013A3E21807B7450072B6CE /* RACEmptySignal.m in Sources */, + D0E967721641EF9C00FCFF06 /* NSDictionary+RACSequenceAdditions.m in Sources */, + D0E967761641EF9C00FCFF06 /* NSOrderedSet+RACSequenceAdditions.m in Sources */, + D0E9677A1641EF9C00FCFF06 /* NSSet+RACSequenceAdditions.m in Sources */, + D0E9677E1641EF9C00FCFF06 /* NSString+RACSequenceAdditions.m in Sources */, + D0E967821641EF9C00FCFF06 /* RACArraySequence.m in Sources */, + D0E967861641EF9C00FCFF06 /* RACDynamicSequence.m in Sources */, + D028DB88179E616700D1042F /* UITableViewCell+RACSignalSupport.m in Sources */, + BE527E9718636F7F006349E8 /* NSUserDefaults+RACSupport.m in Sources */, + D0E9678A1641EF9C00FCFF06 /* RACEmptySequence.m in Sources */, + D0E9678E1641EF9C00FCFF06 /* RACSequence.m in Sources */, + D0E967921641EF9C00FCFF06 /* RACStringSequence.m in Sources */, + D013A3F31807B9690072B6CE /* RACDynamicSignal.m in Sources */, + D0D487041642550100DD7605 /* RACStream.m in Sources */, + D013A3DA1807B5ED0072B6CE /* RACErrorSignal.m in Sources */, + D0EE284E164D906B006954A4 /* RACSignalSequence.m in Sources */, + 74F1731C186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m in Sources */, + 881E86A51669304800667F7B /* RACCompoundDisposable.m in Sources */, + 881E87AF16695C5600667F7B /* RACQueueScheduler.m in Sources */, + 881E87B516695EDF00667F7B /* RACImmediateScheduler.m in Sources */, + 1668028017FE75F900C724B4 /* UICollectionReusableView+RACSignalSupport.m in Sources */, + 881E87C71669636000667F7B /* RACSubscriptionScheduler.m in Sources */, + 5F45A888168CFA3E00B58A2B /* RACKVOChannel.m in Sources */, + D028DB77179E53CB00D1042F /* RACSerialDisposable.m in Sources */, + 5F6FE8561692568A00A8D7A6 /* RACChannel.m in Sources */, + 88C5A027169246140045EF05 /* RACMulticastConnection.m in Sources */, + 5F9743FA1694A2460024EB82 /* RACEagerSequence.m in Sources */, + 5F773DED169B46670023069D /* NSEnumerator+RACSequenceAdditions.m in Sources */, + D077A170169B740200057BB1 /* RACEvent.m in Sources */, + 8837EA1916A5A33300FC3CDF /* RACKVOTrampoline.m in Sources */, + 6E58405D16F22F7800F588A6 /* NSObject+RACDeallocating.m in Sources */, + 880D7A5D16F7B351004A3361 /* NSObject+RACSelectorSignal.m in Sources */, + D0307EE01731AAE100D83211 /* RACTupleSequence.m in Sources */, + D07CD7191731BA3900DE2394 /* RACUnarySequence.m in Sources */, + 554D9E5E181064E200F21262 /* UIRefreshControl+RACCommandSupport.m in Sources */, + 1EC06B18173CB04000365258 /* UIGestureRecognizer+RACSignalSupport.m in Sources */, + 5FDC35061736F54700792E52 /* NSString+RACKeyPathUtilities.m in Sources */, + D0D243BE1741FA13004359C6 /* NSObject+RACDescription.m in Sources */, + 5EE9A7941760D61300EAF5A2 /* UIButton+RACCommandSupport.m in Sources */, + 882D071F17615139009EDA69 /* RACTargetQueueScheduler.m in Sources */, + D0A0E231176A8CD6007273ED /* RACPassthroughSubscriber.m in Sources */, + 557A4B5B177648C7008EF796 /* UIActionSheet+RACSignalSupport.m in Sources */, + D05F9D3817984EC000FD7982 /* EXTRuntimeExtensions.m in Sources */, + D009307C1788AB7B00EE7E8B /* RACTestScheduler.m in Sources */, + 5F70B2B017AB1829009AEDF9 /* UIDatePicker+RACSignalSupport.m in Sources */, + 5F70B2BF17AB1857009AEDF9 /* UISegmentedControl+RACSignalSupport.m in Sources */, + 5F70B2C117AB1857009AEDF9 /* UISlider+RACSignalSupport.m in Sources */, + 5F70B2C317AB1857009AEDF9 /* UIStepper+RACSignalSupport.m in Sources */, + 5F70B2C517AB1857009AEDF9 /* UISwitch+RACSignalSupport.m in Sources */, + 5F016DF717B10AA8002EEC69 /* UIControl+RACSignalSupportPrivate.m in Sources */, + D013A3EA1807B7C30072B6CE /* RACReturnSignal.m in Sources */, + D020F3DC17F6A3E40092BED2 /* RACCompoundDisposableProvider.d in Sources */, + 7479F6E9186177D200575CDB /* RACIndexSetSequence.m in Sources */, + 55C39DE817F1EC6E006DC60C /* NSData+RACSupport.m in Sources */, + 55C39DE917F1EC6E006DC60C /* NSFileHandle+RACSupport.m in Sources */, + 55C39DEA17F1EC6E006DC60C /* NSNotificationCenter+RACSupport.m in Sources */, + 55C39DEB17F1EC6E006DC60C /* NSString+RACSupport.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D05AD39017F2D56F0080895B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D05AD3CF17F2DB100080895B /* NSInvocation+RACTypeParsing.m in Sources */, + D05AD3F117F2DB4F0080895B /* RACDynamicSequence.m in Sources */, + D05AD3F517F2DB4F0080895B /* RACSignalSequence.m in Sources */, + D05AD3C217F2DB100080895B /* RACBlockTrampoline.m in Sources */, + D05AD41917F2DB6A0080895B /* NSSet+RACSequenceAdditions.m in Sources */, + D090768417FBEADE00EB087A /* NSURLConnection+RACSupport.m in Sources */, + D05AD3CE17F2DB100080895B /* NSObject+RACLifting.m in Sources */, + D05AD3FE17F2DB5D0080895B /* RACQueueScheduler.m in Sources */, + D05AD42517F2DB6E0080895B /* NSText+RACSignalSupport.m in Sources */, + D05AD43017F2DB840080895B /* RACValueTransformer.m in Sources */, + D070CBC517FB5E370017F121 /* RACCompoundDisposableProvider.d in Sources */, + D05AD3F217F2DB4F0080895B /* RACEagerSequence.m in Sources */, + D05AD3F617F2DB4F0080895B /* RACTupleSequence.m in Sources */, + D05AD3E917F2DB230080895B /* RACSerialDisposable.m in Sources */, + D05AD42117F2DB6E0080895B /* NSControl+RACTextSignalSupport.m in Sources */, + D05AD40E17F2DB6A0080895B /* NSEnumerator+RACSequenceAdditions.m in Sources */, + D05AD41B17F2DB6A0080895B /* NSString+RACSequenceAdditions.m in Sources */, + D05AD43217F2DBCA0080895B /* RACSignalProvider.d in Sources */, + D05AD42D17F2DB840080895B /* RACKVOTrampoline.m in Sources */, + D05AD3D517F2DB1D0080895B /* RACSignal+Operations.m in Sources */, + BE527E9818636F7F006349E8 /* NSUserDefaults+RACSupport.m in Sources */, + D05AD41317F2DB6A0080895B /* NSObject+RACDescription.m in Sources */, + D05AD3CA17F2DB100080895B /* RACSubscriptingAssignmentTrampoline.m in Sources */, + D05AD3E717F2DB230080895B /* RACCompoundDisposable.m in Sources */, + D05AD3DD17F2DB1D0080895B /* RACSubject.m in Sources */, + D013A3E31807B7450072B6CE /* RACEmptySignal.m in Sources */, + D05AD3F417F2DB4F0080895B /* RACStringSequence.m in Sources */, + D05AD40C17F2DB6A0080895B /* NSDictionary+RACSequenceAdditions.m in Sources */, + D05AD41F17F2DB6E0080895B /* NSControl+RACCommandSupport.m in Sources */, + D05AD41517F2DB6A0080895B /* NSObject+RACSelectorSignal.m in Sources */, + D05AD3C817F2DB100080895B /* RACBacktrace.m in Sources */, + D05AD41217F2DB6A0080895B /* NSNotificationCenter+RACSupport.m in Sources */, + D05AD3D917F2DB1D0080895B /* RACMulticastConnection.m in Sources */, + D05AD43117F2DB950080895B /* RACObjCRuntime.m in Sources */, + D05AD3DB17F2DB1D0080895B /* RACGroupedSignal.m in Sources */, + D05AD3F017F2DB4F0080895B /* RACArraySequence.m in Sources */, + D05AD3E517F2DB230080895B /* RACScopedDisposable.m in Sources */, + D05AD40417F2DB5D0080895B /* RACSubscriptionScheduler.m in Sources */, + D05AD42917F2DB840080895B /* RACKVOChannel.m in Sources */, + D05AD42C17F2DB840080895B /* NSString+RACKeyPathUtilities.m in Sources */, + D05AD42717F2DB840080895B /* RACChannel.m in Sources */, + D05AD3FB17F2DB5D0080895B /* RACScheduler.m in Sources */, + D05AD3EB17F2DB270080895B /* RACCommand.m in Sources */, + D05AD3D117F2DB100080895B /* RACStream.m in Sources */, + D05AD3F717F2DB4F0080895B /* RACUnarySequence.m in Sources */, + D05AD3D317F2DB1D0080895B /* RACSignal.m in Sources */, + D05AD42F17F2DB840080895B /* NSObject+RACPropertySubscribing.m in Sources */, + D05AD3F317F2DB4F0080895B /* RACEmptySequence.m in Sources */, + D05AD42317F2DB6E0080895B /* NSObject+RACAppKitBindings.m in Sources */, + D05AD40817F2DB6A0080895B /* NSArray+RACSequenceAdditions.m in Sources */, + D05AD40017F2DB5D0080895B /* RACTargetQueueScheduler.m in Sources */, + D05AD41717F2DB6A0080895B /* NSOrderedSet+RACSequenceAdditions.m in Sources */, + D05AD40617F2DB5D0080895B /* RACTestScheduler.m in Sources */, + D05AD3EF17F2DB4F0080895B /* RACSequence.m in Sources */, + D05AD3BF17F2DA2A0080895B /* RACSubscriber.m in Sources */, + D013A3EB1807B7C30072B6CE /* RACReturnSignal.m in Sources */, + D05AD3DF17F2DB1D0080895B /* RACReplaySubject.m in Sources */, + D05AD3C417F2DB100080895B /* RACUnit.m in Sources */, + D05AD41017F2DB6A0080895B /* NSFileHandle+RACSupport.m in Sources */, + D05AD41D17F2DB6A0080895B /* NSString+RACSupport.m in Sources */, + D05AD3E317F2DB230080895B /* RACDisposable.m in Sources */, + D05AD40217F2DB5D0080895B /* RACImmediateScheduler.m in Sources */, + D05AD3CC17F2DB100080895B /* NSObject+RACDeallocating.m in Sources */, + 74F1731D186024A900BC937C /* NSIndexSet+RACSequenceAdditions.m in Sources */, + D05AD40A17F2DB6A0080895B /* NSData+RACSupport.m in Sources */, + D013A3DB1807B5ED0072B6CE /* RACErrorSignal.m in Sources */, + 7479F6EA186177D200575CDB /* RACIndexSetSequence.m in Sources */, + D05AD42B17F2DB840080895B /* NSObject+RACKVOWrapper.m in Sources */, + D05AD3E117F2DB1D0080895B /* RACBehaviorSubject.m in Sources */, + D05AD3D717F2DB1D0080895B /* RACEvent.m in Sources */, + D05AD3C117F2DB100080895B /* RACPassthroughSubscriber.m in Sources */, + D05AD3C617F2DB100080895B /* RACTuple.m in Sources */, + D013A3F41807B9690072B6CE /* RACDynamicSignal.m in Sources */, + D049804217F91F42001EE042 /* EXTRuntimeExtensions.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 1860F436177C91B500C7B3C9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1860F411177C91B500C7B3C9 /* ReactiveCocoa-iOS-UIKitTestHost */; + targetProxy = 1860F435177C91B500C7B3C9 /* PBXContainerItemProxy */; + }; + 88037FDB150564E9001A5B19 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 88037F8215056328001A5B19 /* ReactiveCocoa */; + targetProxy = 88037FDA150564E9001A5B19 /* PBXContainerItemProxy */; + }; + D0ED9DB617501806003859A6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 88F440AA153DAC820097B4C3 /* ReactiveCocoa-iOS */; + targetProxy = D0ED9DB517501806003859A6 /* PBXContainerItemProxy */; + }; + D0F117C3179F0A91006CE68F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 88F440AA153DAC820097B4C3 /* ReactiveCocoa-iOS */; + targetProxy = D0F117C2179F0A91006CE68F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 1860F41C177C91B500C7B3C9 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 1860F41D177C91B500C7B3C9 /* en */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; + 1860F43A177C91B500C7B3C9 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 1860F43B177C91B500C7B3C9 /* en */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; + 88CDF7C815000FCE00163A9F /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 88CDF7C915000FCE00163A9F /* en */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; + 88CDF7E515000FCF00163A9F /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 88CDF7E615000FCF00163A9F /* en */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1860F440177C91B500C7B3C9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Info.plist"; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 1860F441177C91B500C7B3C9 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Info.plist"; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Test; + }; + 1860F442177C91B500C7B3C9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Info.plist"; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + 1860F443177C91B500C7B3C9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Info.plist"; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Profile; + }; + 1860F444177C91B500C7B3C9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ReactiveCocoa-iOS-UIKitTestHost.app/ReactiveCocoa-iOS-UIKitTestHost"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"\n\n//:configuration", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist"; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + 1860F445177C91B500C7B3C9 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ReactiveCocoa-iOS-UIKitTestHost.app/ReactiveCocoa-iOS-UIKitTestHost"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"\n\n//:configuration", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist"; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = octest; + }; + name = Test; + }; + 1860F446177C91B500C7B3C9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ReactiveCocoa-iOS-UIKitTestHost.app/ReactiveCocoa-iOS-UIKitTestHost"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"\n\n//:configuration", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist"; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; + 1860F447177C91B500C7B3C9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/ReactiveCocoa-iOS-UIKitTestHost.app/ReactiveCocoa-iOS-UIKitTestHost"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"\n\n//:configuration", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist"; + OTHER_LDFLAGS = "-all_load"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = octest; + }; + name = Profile; + }; + 5FAF5233174D4C2000CAC810 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + 5FAF5234174D4C2000CAC810 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = octest; + }; + name = Test; + }; + 5FAF5235174D4C2000CAC810 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; + 5FAF5236174D4C2000CAC810 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45D17775B1000906BF7 /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + "$(inherited)", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = octest; + }; + name = Profile; + }; + 88037F9015056328001A5B19 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46417775B1000906BF7 /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + INFOPLIST_FILE = "ReactiveCocoa/ReactiveCocoa-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = framework; + }; + name = Debug; + }; + 88037F9115056328001A5B19 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46417775B1000906BF7 /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + INFOPLIST_FILE = "ReactiveCocoa/ReactiveCocoa-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = framework; + }; + name = Release; + }; + 88997CAA1728912B00C569A6 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45817775B1000906BF7 /* Test.xcconfig */; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + GCC_OPTIMIZATION_LEVEL = fast; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + MACOSX_DEPLOYMENT_TARGET = 10.7; + SDKROOT = macosx; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = x86_64; + WARNING_CFLAGS = ( + "-Werror", + "-Wall", + ); + }; + name = Test; + }; + 88997CAB1728912B00C569A6 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46417775B1000906BF7 /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + INFOPLIST_FILE = "ReactiveCocoa/ReactiveCocoa-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = framework; + }; + name = Test; + }; + 88997CAC1728912B00C569A6 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45F17775B1000906BF7 /* iOS-StaticLibrary.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = include/ReactiveCocoa; + }; + name = Test; + }; + 88997CAD1728912B00C569A6 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46117775B1000906BF7 /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = ReactiveCocoaTests; + VALID_ARCHS = x86_64; + WRAPPER_EXTENSION = octest; + }; + name = Test; + }; + 88CDF7EB15000FCF00163A9F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45517775B1000906BF7 /* Debug.xcconfig */; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + MACOSX_DEPLOYMENT_TARGET = 10.7; + SDKROOT = macosx; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = x86_64; + WARNING_CFLAGS = ( + "-Werror", + "-Wall", + ); + }; + name = Debug; + }; + 88CDF7EC15000FCF00163A9F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45717775B1000906BF7 /* Release.xcconfig */; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + MACOSX_DEPLOYMENT_TARGET = 10.7; + SDKROOT = macosx; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = x86_64; + WARNING_CFLAGS = ( + "-Werror", + "-Wall", + ); + }; + name = Release; + }; + 88CDF7F115000FCF00163A9F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46117775B1000906BF7 /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = ReactiveCocoaTests; + VALID_ARCHS = x86_64; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + 88CDF7F215000FCF00163A9F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46117775B1000906BF7 /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = ReactiveCocoaTests; + VALID_ARCHS = x86_64; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; + 88F440B4153DAC820097B4C3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45F17775B1000906BF7 /* iOS-StaticLibrary.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = include/ReactiveCocoa; + }; + name = Debug; + }; + 88F440B5153DAC820097B4C3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45F17775B1000906BF7 /* iOS-StaticLibrary.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = include/ReactiveCocoa; + }; + name = Release; + }; + D05AD3B117F2D5710080895B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46517775B1000906BF7 /* Mac-StaticLibrary.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + D05AD3B217F2D5710080895B /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46517775B1000906BF7 /* Mac-StaticLibrary.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Test; + }; + D05AD3B317F2D5710080895B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46517775B1000906BF7 /* Mac-StaticLibrary.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + D05AD3B417F2D5710080895B /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46517775B1000906BF7 /* Mac-StaticLibrary.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + D095BDD115CB2F9D00E9BB13 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45617775B1000906BF7 /* Profile.xcconfig */; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + MACOSX_DEPLOYMENT_TARGET = 10.7; + SDKROOT = macosx; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = x86_64; + WARNING_CFLAGS = ( + "-Werror", + "-Wall", + ); + }; + name = Profile; + }; + D095BDD215CB2F9D00E9BB13 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46417775B1000906BF7 /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + INFOPLIST_FILE = "ReactiveCocoa/ReactiveCocoa-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = framework; + }; + name = Profile; + }; + D095BDD315CB2F9D00E9BB13 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E45F17775B1000906BF7 /* iOS-StaticLibrary.xcconfig */; + buildSettings = { + GCC_PREFIX_HEADER = "ReactiveCocoa/ReactiveCocoa-Prefix.pch"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = include/ReactiveCocoa; + }; + name = Profile; + }; + D095BDD415CB2F9D00E9BB13 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D094E46117775B1000906BF7 /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + GCC_PREFIX_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch"; + HEADER_SEARCH_PATHS = ( + "\"$(PROJECT_DIR)/../external/specta/src\"", + "\"$(PROJECT_DIR)/../external/expecta/src\"/**", + ); + INFOPLIST_FILE = "ReactiveCocoaTests/ReactiveCocoaTests-Info.plist"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = ReactiveCocoaTests; + VALID_ARCHS = x86_64; + WRAPPER_EXTENSION = octest; + }; + name = Profile; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1860F44C177C91B500C7B3C9 /* Build configuration list for PBXNativeTarget "ReactiveCocoa-iOS-UIKitTestHost" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1860F440177C91B500C7B3C9 /* Debug */, + 1860F441177C91B500C7B3C9 /* Test */, + 1860F442177C91B500C7B3C9 /* Release */, + 1860F443177C91B500C7B3C9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1860F44D177C91B500C7B3C9 /* Build configuration list for PBXNativeTarget "ReactiveCocoa-iOS-UIKitTestHostTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1860F444177C91B500C7B3C9 /* Debug */, + 1860F445177C91B500C7B3C9 /* Test */, + 1860F446177C91B500C7B3C9 /* Release */, + 1860F447177C91B500C7B3C9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5FAF523D174D4C2000CAC810 /* Build configuration list for PBXNativeTarget "ReactiveCocoaTests-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5FAF5233174D4C2000CAC810 /* Debug */, + 5FAF5234174D4C2000CAC810 /* Test */, + 5FAF5235174D4C2000CAC810 /* Release */, + 5FAF5236174D4C2000CAC810 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 88037F8F15056328001A5B19 /* Build configuration list for PBXNativeTarget "ReactiveCocoa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 88037F9015056328001A5B19 /* Debug */, + 88997CAB1728912B00C569A6 /* Test */, + 88037F9115056328001A5B19 /* Release */, + D095BDD215CB2F9D00E9BB13 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 88CDF7B515000FCE00163A9F /* Build configuration list for PBXProject "ReactiveCocoa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 88CDF7EB15000FCF00163A9F /* Debug */, + 88997CAA1728912B00C569A6 /* Test */, + 88CDF7EC15000FCF00163A9F /* Release */, + D095BDD115CB2F9D00E9BB13 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 88CDF7F015000FCF00163A9F /* Build configuration list for PBXNativeTarget "ReactiveCocoaTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 88CDF7F115000FCF00163A9F /* Debug */, + 88997CAD1728912B00C569A6 /* Test */, + 88CDF7F215000FCF00163A9F /* Release */, + D095BDD415CB2F9D00E9BB13 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 88F440B3153DAC820097B4C3 /* Build configuration list for PBXNativeTarget "ReactiveCocoa-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 88F440B4153DAC820097B4C3 /* Debug */, + 88997CAC1728912B00C569A6 /* Test */, + 88F440B5153DAC820097B4C3 /* Release */, + D095BDD315CB2F9D00E9BB13 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D05AD3BD17F2D5710080895B /* Build configuration list for PBXNativeTarget "ReactiveCocoa-Mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D05AD3B117F2D5710080895B /* Debug */, + D05AD3B217F2D5710080895B /* Test */, + D05AD3B317F2D5710080895B /* Release */, + D05AD3B417F2D5710080895B /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 88CDF7B215000FCE00163A9F /* Project object */; +}
diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..a8ff253 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "self:ReactiveCocoa.xcodeproj"> + </FileRef> +</Workspace>
diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme new file mode 100644 index 0000000..b7efc3f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme
@@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0510" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "D05AD39317F2D56F0080895B" + BuildableName = "libReactiveCocoa-Mac.a" + BlueprintName = "ReactiveCocoa-Mac" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + buildConfiguration = "Debug"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "D05AD3A117F2D5700080895B" + BuildableName = "ReactiveCocoa-MacTests.xctest" + BlueprintName = "ReactiveCocoa-MacTests" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Debug" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Release" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme>
diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS-UIKitTestHost.xcscheme b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS-UIKitTestHost.xcscheme new file mode 100644 index 0000000..306864a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS-UIKitTestHost.xcscheme
@@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0500" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "1860F411177C91B500C7B3C9" + BuildableName = "ReactiveCocoa-iOS-UIKitTestHost.app" + BlueprintName = "ReactiveCocoa-iOS-UIKitTestHost" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "1860F42F177C91B500C7B3C9" + BuildableName = "ReactiveCocoa-iOS-UIKitTestHostTests.octest" + BlueprintName = "ReactiveCocoa-iOS-UIKitTestHostTests" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + buildConfiguration = "Test"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "1860F42F177C91B500C7B3C9" + BuildableName = "ReactiveCocoa-iOS-UIKitTestHostTests.octest" + BlueprintName = "ReactiveCocoa-iOS-UIKitTestHostTests" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "1860F411177C91B500C7B3C9" + BuildableName = "ReactiveCocoa-iOS-UIKitTestHost.app" + BlueprintName = "ReactiveCocoa-iOS-UIKitTestHost" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </MacroExpansion> + </TestAction> + <LaunchAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Debug" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + allowLocationSimulation = "YES"> + <BuildableProductRunnable> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "1860F411177C91B500C7B3C9" + BuildableName = "ReactiveCocoa-iOS-UIKitTestHost.app" + BlueprintName = "ReactiveCocoa-iOS-UIKitTestHost" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Profile" + debugDocumentVersioning = "YES"> + <BuildableProductRunnable> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "1860F411177C91B500C7B3C9" + BuildableName = "ReactiveCocoa-iOS-UIKitTestHost.app" + BlueprintName = "ReactiveCocoa-iOS-UIKitTestHost" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme>
diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS.xcscheme b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS.xcscheme new file mode 100644 index 0000000..a31da25 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS.xcscheme
@@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0510" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "88F440AA153DAC820097B4C3" + BuildableName = "libReactiveCocoa-iOS.a" + BlueprintName = "ReactiveCocoa-iOS" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5FAF5222174D4C2000CAC810" + BuildableName = "ReactiveCocoaTests-iOS.octest" + BlueprintName = "ReactiveCocoaTests-iOS" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + buildConfiguration = "Test"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "5FAF5222174D4C2000CAC810" + BuildableName = "ReactiveCocoaTests-iOS.octest" + BlueprintName = "ReactiveCocoaTests-iOS" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Debug" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Profile" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme>
diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa.xcscheme b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa.xcscheme new file mode 100644 index 0000000..12209cd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa.xcscheme
@@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0510" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "88037F8215056328001A5B19" + BuildableName = "ReactiveCocoa.framework" + BlueprintName = "ReactiveCocoa" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "88CDF7DB15000FCF00163A9F" + BuildableName = "ReactiveCocoaTests.octest" + BlueprintName = "ReactiveCocoaTests" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "NO" + buildConfiguration = "Test"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "88CDF7DB15000FCF00163A9F" + BuildableName = "ReactiveCocoaTests.octest" + BlueprintName = "ReactiveCocoaTests" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "88037F8215056328001A5B19" + BuildableName = "ReactiveCocoa.framework" + BlueprintName = "ReactiveCocoa" + ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <EnvironmentVariables> + <EnvironmentVariable + key = "DYLD_INSERT_LIBRARIES" + value = "$(BUILT_PRODUCTS_DIR)/ReactiveCocoa.framework/ReactiveCocoa" + isEnabled = "YES"> + </EnvironmentVariable> + </EnvironmentVariables> + </TestAction> + <LaunchAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Debug" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + buildConfiguration = "Profile" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme>
diff --git a/ReactiveCocoaFramework/ReactiveCocoa.xcworkspace/contents.xcworkspacedata b/ReactiveCocoaFramework/ReactiveCocoa.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..4fb1f75 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "group:ReactiveCocoa.xcodeproj"> + </FileRef> + <FileRef + location = "group:../external/expecta/Expecta.xcodeproj"> + </FileRef> + <FileRef + location = "group:../external/specta/Specta.xcodeproj"> + </FileRef> +</Workspace>
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.h new file mode 100644 index 0000000..d2d0b3f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSArray+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSArray (RACSequenceAdditions) + +/// Creates and returns a sequence corresponding to the receiver. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.m new file mode 100644 index 0000000..ca2b951 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSArray+RACSequenceAdditions.m
@@ -0,0 +1,18 @@ +// +// NSArray+RACSequenceAdditions.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSArray+RACSequenceAdditions.h" +#import "RACArraySequence.h" + +@implementation NSArray (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + return [RACArraySequence sequenceWithArray:self offset:0]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACCommandSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACCommandSupport.h new file mode 100644 index 0000000..4e51577 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACCommandSupport.h
@@ -0,0 +1,22 @@ +// +// NSControl+RACCommandSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/3/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +@class RACCommand; + +@interface NSControl (RACCommandSupport) + +/// Sets the control's command. When the control is clicked, the command is +/// executed with the sender of the event. The control's enabledness is bound +/// to the command's `canExecute`. +/// +/// Note: this will reset the control's target and action. +@property (nonatomic, strong) RACCommand *rac_command; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACCommandSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACCommandSupport.m new file mode 100644 index 0000000..8778f69 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACCommandSupport.m
@@ -0,0 +1,59 @@ +// +// NSControl+RACCommandSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/3/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSControl+RACCommandSupport.h" +#import "EXTScope.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCommand.h" +#import "RACScopedDisposable.h" +#import "RACSignal+Operations.h" +#import <objc/runtime.h> + +static void *NSControlRACCommandKey = &NSControlRACCommandKey; +static void *NSControlEnabledDisposableKey = &NSControlEnabledDisposableKey; + +@implementation NSControl (RACCommandSupport) + +- (RACCommand *)rac_command { + return objc_getAssociatedObject(self, NSControlRACCommandKey); +} + +- (void)setRac_command:(RACCommand *)command { + objc_setAssociatedObject(self, NSControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Tear down any previous binding before setting up our new one, or else we + // might get assertion failures. + [objc_getAssociatedObject(self, NSControlEnabledDisposableKey) dispose]; + objc_setAssociatedObject(self, NSControlEnabledDisposableKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + if (command == nil) { + self.enabled = YES; + return; + } + + [self rac_hijackActionAndTargetIfNeeded]; + + RACScopedDisposable *disposable = [[command.enabled setKeyPath:@"enabled" onObject:self] asScopedDisposable]; + objc_setAssociatedObject(self, NSControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (void)rac_hijackActionAndTargetIfNeeded { + SEL hijackSelector = @selector(rac_commandPerformAction:); + if (self.target == self && self.action == hijackSelector) return; + + if (self.target != nil) NSLog(@"WARNING: NSControl.rac_command hijacks the control's existing target and action."); + + self.target = self; + self.action = hijackSelector; +} + +- (void)rac_commandPerformAction:(id)sender { + [self.rac_command execute:sender]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACTextSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACTextSignalSupport.h new file mode 100644 index 0000000..3d3618d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACTextSignalSupport.h
@@ -0,0 +1,24 @@ +// +// NSControl+RACTextSignalSupport.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-03-08. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +@class RACSignal; + +@interface NSControl (RACTextSignalSupport) + +/// Observes a text-based control for changes. +/// +/// Using this method on a control without editable text is considered undefined +/// behavior. +/// +/// Returns a signal which sends the current string value of the receiver, then +/// the new value any time it changes. +- (RACSignal *)rac_textSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACTextSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACTextSignalSupport.m new file mode 100644 index 0000000..3541bc2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSControl+RACTextSignalSupport.m
@@ -0,0 +1,38 @@ +// +// NSControl+RACTextSignalSupport.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-03-08. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSControl+RACTextSignalSupport.h" +#import "EXTScope.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "RACSubscriber.h" +#import "NSObject+RACDescription.h" + +@implementation NSControl (RACTextSignalSupport) + +- (RACSignal *)rac_textSignal { + @weakify(self); + return [[[[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + @strongify(self); + id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSControlTextDidChangeNotification object:self queue:nil usingBlock:^(NSNotification *note) { + [subscriber sendNext:note.object]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [NSNotificationCenter.defaultCenter removeObserver:observer]; + }]; + }] + map:^(NSControl *control) { + return [control.stringValue copy]; + }] + startWith:[self.stringValue copy]] + setNameWithFormat:@"%@ -rac_textSignal", [self rac_description]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.h new file mode 100644 index 0000000..42198a6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.h
@@ -0,0 +1,22 @@ +// +// NSData+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; +@class RACScheduler; + +@interface NSData (RACSupport) + +// Read the data at the URL using -[NSData initWithContentsOfURL:options:error:]. +// Sends the data or the error. +// +// scheduler - cannot be nil. ++ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.m new file mode 100644 index 0000000..227eb4d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.m
@@ -0,0 +1,35 @@ +// +// NSData+RACSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSData+RACSupport.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" + +@implementation NSData (RACSupport) + ++ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler { + NSCParameterAssert(scheduler != nil); + + RACReplaySubject *subject = [RACReplaySubject subject]; + [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ options: %lu scheduler: %@", URL, (unsigned long)options, scheduler]; + + [scheduler schedule:^{ + NSError *error = nil; + NSData *data = [[NSData alloc] initWithContentsOfURL:URL options:options error:&error]; + if(data == nil) { + [subject sendError:error]; + } else { + [subject sendNext:data]; + [subject sendCompleted]; + } + }]; + + return subject; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.h new file mode 100644 index 0000000..4871fe7 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.h
@@ -0,0 +1,31 @@ +// +// NSDictionary+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSDictionary (RACSequenceAdditions) + +/// Creates and returns a sequence of RACTuple key/value pairs. The key will be +/// the first element in the tuple, and the value will be the second. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +/// Creates and returns a sequence corresponding to the keys in the receiver. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_keySequence; + +/// Creates and returns a sequence corresponding to the values in the receiver. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_valueSequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.m new file mode 100644 index 0000000..192e6fd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSDictionary+RACSequenceAdditions.m
@@ -0,0 +1,34 @@ +// +// NSDictionary+RACSequenceAdditions.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSDictionary+RACSequenceAdditions.h" +#import "NSArray+RACSequenceAdditions.h" +#import "RACSequence.h" +#import "RACTuple.h" + +@implementation NSDictionary (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + NSDictionary *immutableDict = [self copy]; + + // TODO: First class support for dictionary sequences. + return [immutableDict.allKeys.rac_sequence map:^(id key) { + id value = immutableDict[key]; + return [RACTuple tupleWithObjects:key, value, nil]; + }]; +} + +- (RACSequence *)rac_keySequence { + return self.allKeys.rac_sequence; +} + +- (RACSequence *)rac_valueSequence { + return self.allValues.rac_sequence; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.h new file mode 100644 index 0000000..1d8fc84 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSEnumerator+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Uri Baghin on 07/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSEnumerator (RACSequenceAdditions) + +/// Creates and returns a sequence corresponding to the receiver. +/// +/// The receiver is exhausted lazily as the sequence is enumerated. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.m new file mode 100644 index 0000000..aa56eaa --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSEnumerator+RACSequenceAdditions.m
@@ -0,0 +1,22 @@ +// +// NSEnumerator+RACSequenceAdditions.m +// ReactiveCocoa +// +// Created by Uri Baghin on 07/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSEnumerator+RACSequenceAdditions.h" +#import "RACSequence.h" + +@implementation NSEnumerator (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + return [RACSequence sequenceWithHeadBlock:^{ + return [self nextObject]; + } tailBlock:^{ + return self.rac_sequence; + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.h new file mode 100644 index 0000000..985398d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.h
@@ -0,0 +1,19 @@ +// +// NSFileHandle+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +@interface NSFileHandle (RACSupport) + +// Read any available data in the background and send it. Completes when data +// length is <= 0. +- (RACSignal *)rac_readInBackground; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.m new file mode 100644 index 0000000..a258789 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.m
@@ -0,0 +1,39 @@ +// +// NSFileHandle+RACSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSFileHandle+RACSupport.h" +#import "NSNotificationCenter+RACSupport.h" +#import "RACReplaySubject.h" +#import "RACDisposable.h" + +@implementation NSFileHandle (RACSupport) + +- (RACSignal *)rac_readInBackground { + RACReplaySubject *subject = [RACReplaySubject subject]; + [subject setNameWithFormat:@"%@ -rac_readInBackground", self]; + + RACSignal *dataNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:NSFileHandleReadCompletionNotification object:self] map:^(NSNotification *note) { + return [note.userInfo objectForKey:NSFileHandleNotificationDataItem]; + }]; + + __block RACDisposable *subscription = [dataNotification subscribeNext:^(NSData *data) { + if(data.length > 0) { + [subject sendNext:data]; + [self readInBackgroundAndNotify]; + } else { + [subject sendCompleted]; + [subscription dispose]; + } + }]; + + [self readInBackgroundAndNotify]; + + return subject; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.h new file mode 100644 index 0000000..b2066e4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSIndexSet+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Sergey Gavrilyuk on 12/17/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACSequence.h" + +@interface NSIndexSet (RACSequenceAdditions) + +/// Creates and returns a sequence of indexes (as `NSNumber`s) corresponding to +/// the receiver. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.m new file mode 100644 index 0000000..b3557e6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSIndexSet+RACSequenceAdditions.m
@@ -0,0 +1,18 @@ +// +// NSIndexSet+RACSequenceAdditions.m +// ReactiveCocoa +// +// Created by Sergey Gavrilyuk on 12/17/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSIndexSet+RACSequenceAdditions.h" +#import "RACIndexSetSequence.h" + +@implementation NSIndexSet (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + return [RACIndexSetSequence sequenceWithIndexSet:self]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.h b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.h new file mode 100644 index 0000000..d1e72bf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.h
@@ -0,0 +1,56 @@ +// +// NSInvocation+RACTypeParsing.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACTuple; + +// A private category of methods to handle wrapping and unwrapping of values. +@interface NSInvocation (RACTypeParsing) + +// Sets the argument for the invocation at the given index by unboxing the given +// object based on the type signature of the argument. +// +// This does not support C arrays or unions. +// +// Note that calling this on a char * or const char * argument can cause all +// arguments to be retained. +// +// object - The object to unbox and set as the argument. +// index - The index of the argument to set. +- (void)rac_setArgument:(id)object atIndex:(NSUInteger)index; + +// Gets the argument for the invocation at the given index based on the +// invocation's method signature. The value is then wrapped in the appropriate +// object type. +// +// This does not support C arrays or unions. +// +// index - The index of the argument to get. +// +// Returns the argument of the invocation, wrapped in an object. +- (id)rac_argumentAtIndex:(NSUInteger)index; + +// Arguments tuple for the invocation. +// +// The arguments tuple excludes implicit variables `self` and `_cmd`. +// +// See -rac_argumentAtIndex: and -rac_setArgumentAtIndex: for further +// description of the underlying behavior. +@property (nonatomic, copy) RACTuple *rac_argumentsTuple; + +// Gets the return value from the invocation based on the invocation's method +// signature. The value is then wrapped in the appropriate object type. +// +// This does not support C arrays or unions. +// +// Returns the return value of the invocation, wrapped in an object. Voids are +// returned as `RACUnit.defaultUnit`. +- (id)rac_returnValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.m b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.m new file mode 100644 index 0000000..a5318e7 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSInvocation+RACTypeParsing.m
@@ -0,0 +1,232 @@ +// +// NSInvocation+RACTypeParsing.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSInvocation+RACTypeParsing.h" +#import "RACTuple.h" +#import "RACUnit.h" +#import <CoreGraphics/CoreGraphics.h> + +@implementation NSInvocation (RACTypeParsing) + +- (void)rac_setArgument:(id)object atIndex:(NSUInteger)index { +#define PULL_AND_SET(type, selector) \ + do { \ + type val = [object selector]; \ + [self setArgument:&val atIndex:(NSInteger)index]; \ + } while(0) + + const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; + // Skip const type qualifier. + if (argType[0] == 'r') { + argType++; + } + + if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { + [self setArgument:&object atIndex:(NSInteger)index]; + } else if (strcmp(argType, @encode(char)) == 0) { + PULL_AND_SET(char, charValue); + } else if (strcmp(argType, @encode(int)) == 0) { + PULL_AND_SET(int, intValue); + } else if (strcmp(argType, @encode(short)) == 0) { + PULL_AND_SET(short, shortValue); + } else if (strcmp(argType, @encode(long)) == 0) { + PULL_AND_SET(long, longValue); + } else if (strcmp(argType, @encode(long long)) == 0) { + PULL_AND_SET(long long, longLongValue); + } else if (strcmp(argType, @encode(unsigned char)) == 0) { + PULL_AND_SET(unsigned char, unsignedCharValue); + } else if (strcmp(argType, @encode(unsigned int)) == 0) { + PULL_AND_SET(unsigned int, unsignedIntValue); + } else if (strcmp(argType, @encode(unsigned short)) == 0) { + PULL_AND_SET(unsigned short, unsignedShortValue); + } else if (strcmp(argType, @encode(unsigned long)) == 0) { + PULL_AND_SET(unsigned long, unsignedLongValue); + } else if (strcmp(argType, @encode(unsigned long long)) == 0) { + PULL_AND_SET(unsigned long long, unsignedLongLongValue); + } else if (strcmp(argType, @encode(float)) == 0) { + PULL_AND_SET(float, floatValue); + } else if (strcmp(argType, @encode(double)) == 0) { + PULL_AND_SET(double, doubleValue); + } else if (strcmp(argType, @encode(BOOL)) == 0) { + PULL_AND_SET(BOOL, boolValue); + } else if (strcmp(argType, @encode(char *)) == 0) { + const char *cString = [object UTF8String]; + [self setArgument:&cString atIndex:(NSInteger)index]; + [self retainArguments]; + } else if (strcmp(argType, @encode(void (^)(void))) == 0) { + [self setArgument:&object atIndex:(NSInteger)index]; + } else { + NSCParameterAssert([object isKindOfClass:NSValue.class]); + + NSUInteger valueSize = 0; + NSGetSizeAndAlignment([object objCType], &valueSize, NULL); + +#if DEBUG + NSUInteger argSize = 0; + NSGetSizeAndAlignment(argType, &argSize, NULL); + NSCAssert(valueSize == argSize, @"Value size does not match argument size in -rac_setArgument: %@ atIndex: %lu", object, (unsigned long)index); +#endif + + unsigned char valueBytes[valueSize]; + [object getValue:valueBytes]; + + [self setArgument:valueBytes atIndex:(NSInteger)index]; + } + +#undef PULL_AND_SET +} + +- (id)rac_argumentAtIndex:(NSUInteger)index { +#define WRAP_AND_RETURN(type) \ + do { \ + type val = 0; \ + [self getArgument:&val atIndex:(NSInteger)index]; \ + return @(val); \ + } while (0) + + const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; + // Skip const type qualifier. + if (argType[0] == 'r') { + argType++; + } + + if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { + __autoreleasing id returnObj; + [self getArgument:&returnObj atIndex:(NSInteger)index]; + return returnObj; + } else if (strcmp(argType, @encode(char)) == 0) { + WRAP_AND_RETURN(char); + } else if (strcmp(argType, @encode(int)) == 0) { + WRAP_AND_RETURN(int); + } else if (strcmp(argType, @encode(short)) == 0) { + WRAP_AND_RETURN(short); + } else if (strcmp(argType, @encode(long)) == 0) { + WRAP_AND_RETURN(long); + } else if (strcmp(argType, @encode(long long)) == 0) { + WRAP_AND_RETURN(long long); + } else if (strcmp(argType, @encode(unsigned char)) == 0) { + WRAP_AND_RETURN(unsigned char); + } else if (strcmp(argType, @encode(unsigned int)) == 0) { + WRAP_AND_RETURN(unsigned int); + } else if (strcmp(argType, @encode(unsigned short)) == 0) { + WRAP_AND_RETURN(unsigned short); + } else if (strcmp(argType, @encode(unsigned long)) == 0) { + WRAP_AND_RETURN(unsigned long); + } else if (strcmp(argType, @encode(unsigned long long)) == 0) { + WRAP_AND_RETURN(unsigned long long); + } else if (strcmp(argType, @encode(float)) == 0) { + WRAP_AND_RETURN(float); + } else if (strcmp(argType, @encode(double)) == 0) { + WRAP_AND_RETURN(double); + } else if (strcmp(argType, @encode(BOOL)) == 0) { + WRAP_AND_RETURN(BOOL); + } else if (strcmp(argType, @encode(char *)) == 0) { + WRAP_AND_RETURN(const char *); + } else if (strcmp(argType, @encode(void (^)(void))) == 0) { + __unsafe_unretained id block = nil; + [self getArgument:&block atIndex:(NSInteger)index]; + return [block copy]; + } else { + NSUInteger valueSize = 0; + NSGetSizeAndAlignment(argType, &valueSize, NULL); + + unsigned char valueBytes[valueSize]; + [self getArgument:valueBytes atIndex:(NSInteger)index]; + + return [NSValue valueWithBytes:valueBytes objCType:argType]; + } + + return nil; + +#undef WRAP_AND_RETURN +} + +- (RACTuple *)rac_argumentsTuple { + NSUInteger numberOfArguments = self.methodSignature.numberOfArguments; + NSMutableArray *argumentsArray = [NSMutableArray arrayWithCapacity:numberOfArguments - 2]; + for (NSUInteger index = 2; index < numberOfArguments; index++) { + [argumentsArray addObject:[self rac_argumentAtIndex:index] ?: RACTupleNil.tupleNil]; + } + + return [RACTuple tupleWithObjectsFromArray:argumentsArray]; +} + +- (void)setRac_argumentsTuple:(RACTuple *)arguments { + NSCAssert(arguments.count == self.methodSignature.numberOfArguments - 2, @"Number of supplied arguments (%lu), does not match the number expected by the signature (%lu)", (unsigned long)arguments.count, (unsigned long)self.methodSignature.numberOfArguments - 2); + + NSUInteger index = 2; + for (id arg in arguments) { + [self rac_setArgument:(arg == RACTupleNil.tupleNil ? nil : arg) atIndex:index]; + index++; + } +} + +- (id)rac_returnValue { +#define WRAP_AND_RETURN(type) \ + do { \ + type val = 0; \ + [self getReturnValue:&val]; \ + return @(val); \ + } while (0) + + const char *returnType = self.methodSignature.methodReturnType; + // Skip const type qualifier. + if (returnType[0] == 'r') { + returnType++; + } + + if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0 || strcmp(returnType, @encode(void (^)(void))) == 0) { + __autoreleasing id returnObj; + [self getReturnValue:&returnObj]; + return returnObj; + } else if (strcmp(returnType, @encode(char)) == 0) { + WRAP_AND_RETURN(char); + } else if (strcmp(returnType, @encode(int)) == 0) { + WRAP_AND_RETURN(int); + } else if (strcmp(returnType, @encode(short)) == 0) { + WRAP_AND_RETURN(short); + } else if (strcmp(returnType, @encode(long)) == 0) { + WRAP_AND_RETURN(long); + } else if (strcmp(returnType, @encode(long long)) == 0) { + WRAP_AND_RETURN(long long); + } else if (strcmp(returnType, @encode(unsigned char)) == 0) { + WRAP_AND_RETURN(unsigned char); + } else if (strcmp(returnType, @encode(unsigned int)) == 0) { + WRAP_AND_RETURN(unsigned int); + } else if (strcmp(returnType, @encode(unsigned short)) == 0) { + WRAP_AND_RETURN(unsigned short); + } else if (strcmp(returnType, @encode(unsigned long)) == 0) { + WRAP_AND_RETURN(unsigned long); + } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { + WRAP_AND_RETURN(unsigned long long); + } else if (strcmp(returnType, @encode(float)) == 0) { + WRAP_AND_RETURN(float); + } else if (strcmp(returnType, @encode(double)) == 0) { + WRAP_AND_RETURN(double); + } else if (strcmp(returnType, @encode(BOOL)) == 0) { + WRAP_AND_RETURN(BOOL); + } else if (strcmp(returnType, @encode(char *)) == 0) { + WRAP_AND_RETURN(const char *); + } else if (strcmp(returnType, @encode(void)) == 0) { + return RACUnit.defaultUnit; + } else { + NSUInteger valueSize = 0; + NSGetSizeAndAlignment(returnType, &valueSize, NULL); + + unsigned char valueBytes[valueSize]; + [self getReturnValue:valueBytes]; + + return [NSValue valueWithBytes:valueBytes objCType:returnType]; + } + + return nil; + +#undef WRAP_AND_RETURN +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.h new file mode 100644 index 0000000..ddb3954 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.h
@@ -0,0 +1,18 @@ +// +// NSNotificationCenter+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +@interface NSNotificationCenter (RACSupport) + +// Sends the NSNotification every time the notification is posted. +- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.m new file mode 100644 index 0000000..a0112e9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.m
@@ -0,0 +1,31 @@ +// +// NSNotificationCenter+RACSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/10/12. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSNotificationCenter+RACSupport.h" +#import "EXTScope.h" +#import "RACSignal.h" +#import "RACSubscriber.h" +#import "RACDisposable.h" + +@implementation NSNotificationCenter (RACSupport) + +- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object { + @unsafeify(object); + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + @strongify(object); + id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) { + [subscriber sendNext:note]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [self removeObserver:observer]; + }]; + }] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACAppKitBindings.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACAppKitBindings.h new file mode 100644 index 0000000..eaec439 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACAppKitBindings.h
@@ -0,0 +1,40 @@ +// +// NSObject+RACAppKitBindings.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +@class RACChannelTerminal; + +@interface NSObject (RACAppKitBindings) + +/// Invokes -rac_channelToBinding:options: without any options. +- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding; + +/// Applies a Cocoa binding to the receiver, then exposes a RACChannel-based +/// interface for manipulating it. +/// +/// Creating two of the same bindings on the same object will result in undefined +/// behavior. +/// +/// binding - The name of the binding. This must not be nil. +/// options - Any options to pass to Cocoa Bindings. This may be nil. +/// +/// Returns a RACChannelTerminal which will send future values from the receiver, +/// and update the receiver when values are sent to the terminal. +- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding options:(NSDictionary *)options; + +@end + +@interface NSObject (RACAppKitBindingsDeprecated) + +- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath __attribute__((deprecated("Use -rac_bind:options: instead"))); +- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath nilValue:(id)nilValue __attribute__((deprecated("Use -rac_bind:options: instead"))); +- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath transform:(id (^)(id value))transformBlock __attribute__((deprecated("Use -rac_bind:options: instead"))); +- (void)rac_bind:(NSString *)binding toObject:(id)object withNegatedKeyPath:(NSString *)keyPath __attribute__((deprecated("Use -rac_bind:options: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACAppKitBindings.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACAppKitBindings.m new file mode 100644 index 0000000..ea80f83 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACAppKitBindings.m
@@ -0,0 +1,175 @@ +// +// NSObject+RACAppKitBindings.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACAppKitBindings.h" +#import "EXTKeyPathCoding.h" +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "RACChannel.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACKVOChannel.h" +#import "RACMulticastConnection.h" +#import "RACSignal+Operations.h" +#import "RACValueTransformer.h" +#import <objc/runtime.h> + +// Used as an object to bind to, so we can hide the object creation and just +// expose a RACChannel instead. +@interface RACChannelProxy : NSObject + +// The RACChannel used for this Cocoa binding. +@property (nonatomic, strong, readonly) RACChannel *channel; + +// The KVC- and KVO-compliant property to be read and written by the Cocoa +// binding. +// +// This should not be set manually. +@property (nonatomic, strong) id value; + +// The target of the Cocoa binding. +// +// This should be set to nil when the target deallocates. +@property (atomic, unsafe_unretained) id target; + +// The name of the Cocoa binding used. +@property (nonatomic, copy, readonly) NSString *bindingName; + +// Improves the performance of KVO on the receiver. +// +// See the documentation for <NSKeyValueObserving> for more information. +@property (atomic, assign) void *observationInfo; + +// Initializes the receiver and binds to the given target. +// +// target - The target of the Cocoa binding. This must not be nil. +// bindingName - The name of the Cocoa binding to use. This must not be nil. +// options - Any options to pass to the binding. This may be nil. +// +// Returns an initialized channel proxy. +- (id)initWithTarget:(id)target bindingName:(NSString *)bindingName options:(NSDictionary *)options; + +@end + +@implementation NSObject (RACAppKitBindings) + +- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding { + return [self rac_channelToBinding:binding options:nil]; +} + +- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding options:(NSDictionary *)options { + NSCParameterAssert(binding != nil); + + RACChannelProxy *proxy = [[RACChannelProxy alloc] initWithTarget:self bindingName:binding options:options]; + return proxy.channel.leadingTerminal; +} + +@end + +@implementation RACChannelProxy + +#pragma mark Properties + +- (void)setValue:(id)value { + [self willChangeValueForKey:@keypath(self.value)]; + _value = value; + [self didChangeValueForKey:@keypath(self.value)]; +} + +#pragma mark Lifecycle + +- (id)initWithTarget:(id)target bindingName:(NSString *)bindingName options:(NSDictionary *)options { + NSCParameterAssert(target != nil); + NSCParameterAssert(bindingName != nil); + + self = [super init]; + if (self == nil) return nil; + + _target = target; + _bindingName = [bindingName copy]; + _channel = [[RACChannel alloc] init]; + + @weakify(self); + + void (^cleanUp)() = ^{ + @strongify(self); + + id target = self.target; + if (target == nil) return; + + self.target = nil; + + [target unbind:bindingName]; + objc_setAssociatedObject(target, (__bridge void *)self, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + }; + + // When the channel terminates, tear down this proxy. + [self.channel.followingTerminal subscribeError:^(NSError *error) { + cleanUp(); + } completed:cleanUp]; + + [self.target bind:bindingName toObject:self withKeyPath:@keypath(self.value) options:options]; + + // Keep the proxy alive as long as the target, or until the property subject + // terminates. + objc_setAssociatedObject(self.target, (__bridge void *)self, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [[self.target rac_deallocDisposable] addDisposable:[RACDisposable disposableWithBlock:^{ + @strongify(self); + [self.channel.followingTerminal sendCompleted]; + }]]; + + RACChannelTo(self, value, options[NSNullPlaceholderBindingOption]) = self.channel.followingTerminal; + return self; +} + +- (void)dealloc { + [self.channel.followingTerminal sendCompleted]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ target: %@, binding: %@ }", self.class, self, self.target, self.bindingName]; +} + +#pragma mark NSKeyValueObserving + ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { + // Generating manual notifications for `value` is simpler and more + // performant than having KVO swizzle our class and add its own logic. + return NO; +} + +@end + +@implementation NSObject (RACAppKitBindingsDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath { + [self rac_bind:binding toObject:object withKeyPath:keyPath nilValue:nil]; +} + +- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath nilValue:(id)nilValue { + [self bind:binding toObject:object withKeyPath:keyPath options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption, nilValue, NSNullPlaceholderBindingOption, nil]]; +} + +- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath transform:(id (^)(id value))transformBlock { + RACValueTransformer *transformer = [RACValueTransformer transformerWithBlock:transformBlock]; + [self bind:binding toObject:object withKeyPath:keyPath options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption, transformer, NSValueTransformerBindingOption, nil]]; +} + +- (void)rac_bind:(NSString *)binding toObject:(id)object withNegatedKeyPath:(NSString *)keyPath { + [self bind:binding toObject:object withKeyPath:keyPath options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSContinuouslyUpdatesValueBindingOption, NSNegateBooleanTransformerName, NSValueTransformerNameBindingOption, nil]]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.h new file mode 100644 index 0000000..1530eb4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.h
@@ -0,0 +1,34 @@ +// +// NSObject+RACDeallocating.h +// ReactiveCocoa +// +// Created by Kazuo Koga on 2013/03/15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACCompoundDisposable; +@class RACDisposable; +@class RACSignal; + +@interface NSObject (RACDeallocating) + +/// The compound disposable which will be disposed of when the receiver is +/// deallocated. +@property (atomic, readonly, strong) RACCompoundDisposable *rac_deallocDisposable; + +/// Returns a signal that will complete immediately before the receiver is fully +/// deallocated. If already deallocated when the signal is subscribed to, +/// a `completed` event will be sent immediately. +- (RACSignal *)rac_willDeallocSignal; + +@end + +@interface NSObject (RACDeallocatingDeprecated) + +- (RACSignal *)rac_didDeallocSignal __attribute__((deprecated("Use -rac_willDeallocSignal"))); + +- (void)rac_addDeallocDisposable:(RACDisposable *)disposable __attribute__((deprecated("Add disposables to -rac_deallocDisposable instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.m new file mode 100644 index 0000000..a81d89c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDeallocating.m
@@ -0,0 +1,129 @@ +// +// NSObject+RACDeallocating.m +// ReactiveCocoa +// +// Created by Kazuo Koga on 2013/03/15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACReplaySubject.h" +#import <objc/message.h> +#import <objc/runtime.h> + +static const void *RACObjectCompoundDisposable = &RACObjectCompoundDisposable; + +static NSMutableSet *swizzledClasses() { + static dispatch_once_t onceToken; + static NSMutableSet *swizzledClasses = nil; + dispatch_once(&onceToken, ^{ + swizzledClasses = [[NSMutableSet alloc] init]; + }); + + return swizzledClasses; +} + +static void swizzleDeallocIfNeeded(Class classToSwizzle) { + @synchronized (swizzledClasses()) { + NSString *className = NSStringFromClass(classToSwizzle); + if ([swizzledClasses() containsObject:className]) return; + + SEL deallocSelector = sel_registerName("dealloc"); + + __block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL; + + id newDealloc = ^(__unsafe_unretained id self) { + RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); + [compoundDisposable dispose]; + + if (originalDealloc == NULL) { + struct objc_super superInfo = { + .receiver = self, + .super_class = class_getSuperclass(classToSwizzle) + }; + + void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper; + msgSend(&superInfo, deallocSelector); + } else { + originalDealloc(self, deallocSelector); + } + }; + + IMP newDeallocIMP = imp_implementationWithBlock(newDealloc); + + if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) { + // The class already contains a method implementation. + Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector); + + // We need to store original implementation before setting new implementation + // in case method is called at the time of setting. + originalDealloc = (__typeof__(originalDealloc))method_getImplementation(deallocMethod); + + // We need to store original implementation again, in case it just changed. + originalDealloc = (__typeof__(originalDealloc))method_setImplementation(deallocMethod, newDeallocIMP); + } + + [swizzledClasses() addObject:className]; + } +} + +@implementation NSObject (RACDeallocating) + +- (RACSignal *)rac_willDeallocSignal { + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; + + RACReplaySubject *subject = [RACReplaySubject subject]; + + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subject sendCompleted]; + }]]; + + objc_setAssociatedObject(self, _cmd, subject, OBJC_ASSOCIATION_RETAIN); + + return subject; +} + +- (RACCompoundDisposable *)rac_deallocDisposable { + @synchronized (self) { + RACCompoundDisposable *compoundDisposable = objc_getAssociatedObject(self, RACObjectCompoundDisposable); + if (compoundDisposable != nil) return compoundDisposable; + + swizzleDeallocIfNeeded(self.class); + + compoundDisposable = [RACCompoundDisposable compoundDisposable]; + objc_setAssociatedObject(self, RACObjectCompoundDisposable, compoundDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + return compoundDisposable; + } +} + +@end + +@implementation NSObject (RACDeallocatingDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (RACSignal *)rac_didDeallocSignal { + RACSubject *subject = [RACSubject subject]; + + RACScopedDisposable *disposable = [[RACDisposable + disposableWithBlock:^{ + [subject sendCompleted]; + }] + asScopedDisposable]; + + objc_setAssociatedObject(self, (__bridge void *)disposable, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return subject; +} + +- (void)rac_addDeallocDisposable:(RACDisposable *)disposable { + [self.rac_deallocDisposable addDisposable:disposable]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.h new file mode 100644 index 0000000..41e3d8a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.h
@@ -0,0 +1,21 @@ +// +// NSObject+RACDescription.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +// A private category providing a terser but faster alternative to -description. +@interface NSObject (RACDescription) + +// A simplified description of the receiver, which does not invoke -description +// (and thus should be much faster in many cases). +// +// This is for debugging purposes only, and will return a constant string +// unless the RAC_DEBUG_SIGNAL_NAMES environment variable is set. +- (NSString *)rac_description; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.m new file mode 100644 index 0000000..0088880 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACDescription.m
@@ -0,0 +1,46 @@ +// +// NSObject+RACDescription.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACDescription.h" +#import "RACTuple.h" + +@implementation NSObject (RACDescription) + +- (NSString *)rac_description { + if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { + return [[NSString alloc] initWithFormat:@"<%@: %p>", self.class, self]; + } else { + return @"(description skipped)"; + } +} + +@end + +@implementation NSValue (RACDescription) + +- (NSString *)rac_description { + return self.description; +} + +@end + +@implementation NSString (RACDescription) + +- (NSString *)rac_description { + return self.description; +} + +@end + +@implementation RACTuple (RACDescription) + +- (NSString *)rac_description { + return self.allObjects.description; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.h new file mode 100644 index 0000000..3b9ff2f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.h
@@ -0,0 +1,56 @@ +// +// NSObject+RACKVOWrapper.h +// GitHub +// +// Created by Josh Abernathy on 10/11/11. +// Copyright (c) 2011 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + + +// RAC-specific KVO change dictionary key: Will be @YES if the change was caused +// by the value at the key path or an intermediate value deallocating, @NO +// otherwise. +extern NSString * const RACKeyValueChangeCausedByDeallocationKey; + +// RAC-specific KVO change dictionary key: Will be @YES if the change only +// affected the value of the last key path component leaving the values of the +// intermediate key path components unaltered, @NO otherwise. +extern NSString * const RACKeyValueChangeAffectedOnlyLastComponentKey; + +@class RACDisposable, RACKVOTrampoline; + +// A private category providing a block based interface to KVO. +@interface NSObject (RACKVOWrapper) + +// Adds the given block as the callbacks for when the key path changes. +// +// Unlike direct KVO observation, this handles deallocation of `weak` properties +// by generating an appropriate notification. This will only occur if there is +// an `@property` declaration visible in the observed class, with the `weak` +// memory management attribute. +// +// The observation does not need to be explicitly removed. It will be removed +// when the observer or the receiver deallocate. +// +// keyPath - The key path to observe. Must not be nil. +// options - The KVO observation options. +// observer - The object that requested the observation. May be nil. +// block - The block called when the value at the key path changes. It is +// passed the current value of the key path and the extended KVO +// change dictionary including RAC-specific keys and values. Must not +// be nil. +// +// Returns a disposable that can be used to stop the observation. +- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer block:(void (^)(id value, NSDictionary *change))block; + +@end + +typedef void (^RACKVOBlock)(id target, id observer, NSDictionary *change); + +@interface NSObject (RACKVOWrapperDeprecated) + +- (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block __attribute((deprecated("Use rac_observeKeyPath:options:observer:block: instead."))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.m new file mode 100644 index 0000000..7f12312 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACKVOWrapper.m
@@ -0,0 +1,237 @@ +// +// NSObject+RACKVOWrapper.m +// GitHub +// +// Created by Josh Abernathy on 10/11/11. +// Copyright (c) 2011 GitHub. All rights reserved. +// + +#import "NSObject+RACKVOWrapper.h" +#import "EXTRuntimeExtensions.h" +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSString+RACKeyPathUtilities.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACKVOTrampoline.h" +#import "RACSerialDisposable.h" + +NSString * const RACKeyValueChangeCausedByDeallocationKey = @"RACKeyValueChangeCausedByDeallocationKey"; +NSString * const RACKeyValueChangeAffectedOnlyLastComponentKey = @"RACKeyValueChangeAffectedOnlyLastComponentKey"; + +@implementation NSObject (RACKVOWrapper) + +- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer block:(void (^)(id, NSDictionary *))block { + NSCParameterAssert(block != nil); + NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); + + keyPath = [keyPath copy]; + + @unsafeify(observer); + + NSArray *keyPathComponents = keyPath.rac_keyPathComponents; + BOOL keyPathHasOneComponent = (keyPathComponents.count == 1); + NSString *keyPathHead = keyPathComponents[0]; + NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent; + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + // The disposable that groups all disposal necessary to clean up the callbacks + // added to the value of the first key path component. + RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]]; + RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{ + return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable; + }; + + [disposable addDisposable:firstComponentSerialDisposable]; + + // Adds the callback block to the value's deallocation. Also adds the logic to + // clean up the callback to the firstComponentDisposable. + void (^addDeallocObserverToPropertyValue)(NSObject *, NSString *, NSObject *) = ^(NSObject *parent, NSString *propertyKey, NSObject *value) { + // If a key path value is the observer, commonly when a key path begins + // with "self", we prevent deallocation triggered callbacks for any such key + // path components. Thus, the observer's deallocation is not considered a + // change to the key path. + @strongify(observer); + if (value == observer) return; + + objc_property_t property = class_getProperty(object_getClass(parent), propertyKey.UTF8String); + if (property == NULL) { + // If we can't find an Objective-C property for this key, we assume + // that we don't need to observe its deallocation (thus matching + // vanilla KVO behavior). + // + // Even if we wanted to, there's not enough type information on + // ivars to figure out its memory management. + return; + } + + rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property); + if (attributes == NULL) return; + + @onExit { + free(attributes); + }; + + BOOL isNonObject = attributes->objectClass == nil && strstr(attributes->type, @encode(id)) != attributes->type; + BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol"); + BOOL isBlock = strcmp(attributes->type, @encode(void(^)())) == 0; + if (isNonObject || isProtocol || isBlock) { + // If this property isn't actually an object (or is a Class object), + // no point in observing the deallocation of the wrapper returned by + // KVC. + return; + } + + if (!attributes->weak) { + // If this property is an object, but not declared `weak`, we + // don't need to watch for it spontaneously being set to nil. + // + // Attempting to observe non-weak properties will result in + // broken behavior for dynamic getters, so don't even try. + return; + } + + NSDictionary *change = @{ + NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), + NSKeyValueChangeNewKey: NSNull.null, + RACKeyValueChangeCausedByDeallocationKey: @YES, + RACKeyValueChangeAffectedOnlyLastComponentKey: @(keyPathHasOneComponent) + }; + + RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable; + RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ + block(nil, change); + }]; + + [valueDisposable addDisposable:deallocDisposable]; + [firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{ + [valueDisposable removeDisposable:deallocDisposable]; + }]]; + }; + + // Adds the callback block to the remaining path components on the value. Also + // adds the logic to clean up the callbacks to the firstComponentDisposable. + void (^addObserverToValue)(NSObject *) = ^(NSObject *value) { + @strongify(observer); + RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:observer block:block]; + [firstComponentDisposable() addDisposable:observerDisposable]; + }; + + // Observe only the first key path component, when the value changes clean up + // the callbacks on the old value, add callbacks to the new value and call the + // callback block as needed. + // + // Note this does not use NSKeyValueObservingOptionInitial so this only + // handles changes to the value, callbacks to the initial value must be added + // separately. + NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial; + RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:observer keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) { + // Prepare the change dictionary by adding the RAC specific keys + { + NSMutableDictionary *newChange = [change mutableCopy]; + newChange[RACKeyValueChangeCausedByDeallocationKey] = @NO; + newChange[RACKeyValueChangeAffectedOnlyLastComponentKey] = @(keyPathHasOneComponent); + change = newChange.copy; + } + + // If this is a prior notification, clean up all the callbacks added to the + // previous value and call the callback block. Everything else is deferred + // until after we get the notification after the change. + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + [firstComponentDisposable() dispose]; + + if ((options & NSKeyValueObservingOptionPrior) != 0) { + block([trampolineTarget valueForKeyPath:keyPath], change); + } + + return; + } + + // From here the notification is not prior. + NSObject *value = [trampolineTarget valueForKey:keyPathHead]; + + // If the value has changed but is nil, there is no need to add callbacks to + // it, just call the callback block. + if (value == nil) { + block(nil, change); + return; + } + + // From here the notification is not prior and the value is not nil. + + // Create a new firstComponentDisposable while getting rid of the old one at + // the same time, in case this is being called concurrently. + RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]]; + [oldFirstComponentDisposable dispose]; + + addDeallocObserverToPropertyValue(trampolineTarget, keyPathHead, value); + + // If there are no further key path components, there is no need to add the + // other callbacks, just call the callback block with the value itself. + if (keyPathHasOneComponent) { + block(value, change); + return; + } + + // The value has changed, is not nil, and there are more key path components + // to consider. Add the callbacks to the value for the remaining key path + // components and call the callback block with the current value of the full + // key path. + addObserverToValue(value); + block([value valueForKeyPath:keyPathTail], change); + }]; + + // Stop the KVO observation when this one is disposed of. + [disposable addDisposable:trampoline]; + + // Add the callbacks to the initial value if needed. + NSObject *value = [self valueForKey:keyPathHead]; + if (value != nil) { + addDeallocObserverToPropertyValue(self, keyPathHead, value); + + if (!keyPathHasOneComponent) { + addObserverToValue(value); + } + } + + // Call the block with the initial value if needed. + if ((options & NSKeyValueObservingOptionInitial) != 0) { + id initialValue = [self valueForKeyPath:keyPath]; + NSDictionary *initialChange = @{ + NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), + NSKeyValueChangeNewKey: initialValue ?: NSNull.null, + RACKeyValueChangeCausedByDeallocationKey: @NO, + RACKeyValueChangeAffectedOnlyLastComponentKey: @(keyPathHasOneComponent) + }; + block(initialValue, initialChange); + } + + + RACCompoundDisposable *observerDisposable = observer.rac_deallocDisposable; + RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable; + // Dispose of this observation if the receiver or the observer deallocate. + [observerDisposable addDisposable:disposable]; + [selfDisposable addDisposable:disposable]; + + return [RACDisposable disposableWithBlock:^{ + [disposable dispose]; + [observerDisposable removeDisposable:disposable]; + [selfDisposable removeDisposable:disposable]; + }]; +} + +@end + +@implementation NSObject (RACKVOWrapperDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { + return [[RACKVOTrampoline alloc] initWithTarget:self observer:observer keyPath:keyPath options:options block:block]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h new file mode 100644 index 0000000..0feb272 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.h
@@ -0,0 +1,55 @@ +// +// NSObject+RACLifting.h +// iOSDemo +// +// Created by Josh Abernathy on 10/13/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +@class RACSignal; + +@interface NSObject (RACLifting) + +/// Lifts the selector on the receiver into the reactive world. The selector will +/// be invoked whenever any signal argument sends a value, but only after each +/// signal has sent an initial value. +/// +/// It will replay the most recently sent value to new subscribers. +/// +/// This does not support C arrays or unions. +/// +/// selector - The selector on self to invoke. +/// firstSignal - The signal corresponding to the first method argument. This +/// must not be nil. +/// ... - A list of RACSignals corresponding to the remaining arguments. +/// There must be a non-nil signal for each method argument. +/// +/// Examples +/// +/// [button rac_liftSelector:@selector(setTitleColor:forState:) withSignals:textColorSignal, [RACSignal return:@(UIControlStateNormal)], nil]; +/// +/// Returns a signal which sends the return value from each invocation of the +/// selector. If the selector returns void, it instead sends RACUnit.defaultUnit. +/// It completes only after all the signal arguments complete. +- (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... NS_REQUIRES_NIL_TERMINATION; + +/// Like -rac_liftSelector:withSignals:, but accepts an array instead of +/// a variadic list of arguments. +- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals; + +@end + +@interface NSObject (RACLiftingDeprecated) + +- (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... __attribute__((deprecated("Use -rac_liftSelector:withSignals: instead"))); +- (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args __attribute__((deprecated("Use -rac_liftSelector:withSignalsFromArray: instead"))); +- (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... NS_REQUIRES_NIL_TERMINATION __attribute__((deprecated("Use +combineLatest:reduce: instead"))); +- (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args __attribute__((deprecated("Use +combineLatest:reduce: instead"))); + +@end + +@interface NSObject (RACLiftingUnavailable) + +- (instancetype)rac_lift __attribute__((unavailable("Use -rac_liftSelector:withSignals: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.m new file mode 100644 index 0000000..96fea76 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACLifting.m
@@ -0,0 +1,132 @@ +// +// NSObject+RACLifting.m +// iOSDemo +// +// Created by Josh Abernathy on 10/13/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACLifting.h" +#import "EXTScope.h" +#import "NSInvocation+RACTypeParsing.h" +#import "NSObject+RACDeallocating.h" +#import "RACSignal+Operations.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" + +@implementation NSObject (RACLifting) + +- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals { + NSCParameterAssert(selector != NULL); + NSCParameterAssert(signals != nil); + NSCParameterAssert(signals.count > 0); + + NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; + NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); + + NSUInteger numberOfArguments __attribute__((unused)) = methodSignature.numberOfArguments - 2; + NSCAssert(numberOfArguments == signals.count, @"Wrong number of signals for %@ (expected %lu, got %lu)", NSStringFromSelector(selector), (unsigned long)numberOfArguments, (unsigned long)signals.count); + + @unsafeify(self); + + return [[[[[RACSignal + combineLatest:signals] + takeUntil:self.rac_willDeallocSignal] + map:^(RACTuple *arguments) { + @strongify(self); + + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = selector; + invocation.rac_argumentsTuple = arguments; + [invocation invokeWithTarget:self]; + + return invocation.rac_returnValue; + }] + replayLast] + setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsFromArray: %@", [self rac_description], sel_getName(selector), signals]; +} + +- (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... { + NSCParameterAssert(firstSignal != nil); + + NSMutableArray *signals = [NSMutableArray array]; + + va_list args; + va_start(args, firstSignal); + for (id currentSignal = firstSignal; currentSignal != nil; currentSignal = va_arg(args, id)) { + NSCAssert([currentSignal isKindOfClass:RACSignal.class], @"Argument %@ is not a RACSignal", currentSignal); + + [signals addObject:currentSignal]; + } + va_end(args); + + return [[self + rac_liftSelector:selector withSignalsFromArray:signals] + setNameWithFormat:@"%@ -rac_liftSelector: %s withSignals: %@", [self rac_description], sel_getName(selector), signals]; +} + +@end + +@implementation NSObject (RACLiftingDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +static NSArray *RACMapArgumentsToSignals(NSArray *args) { + NSMutableArray *mappedArgs = [NSMutableArray array]; + for (id arg in args) { + if ([arg isEqual:RACTupleNil.tupleNil]) { + [mappedArgs addObject:[RACSignal return:nil]]; + } else if ([arg isKindOfClass:RACSignal.class]) { + [mappedArgs addObject:arg]; + } else { + [mappedArgs addObject:[RACSignal return:arg]]; + } + } + + return mappedArgs; +} + +- (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... { + NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; + NSMutableArray *arguments = [NSMutableArray array]; + + va_list args; + va_start(args, arg); + for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { + id currentObject = (i == 2 ? arg : va_arg(args, id)); + [arguments addObject:currentObject ?: RACTupleNil.tupleNil]; + } + + va_end(args); + return [self rac_liftSelector:selector withObjectsFromArray:arguments]; +} + +- (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args { + return [self rac_liftSelector:selector withSignalsFromArray:RACMapArgumentsToSignals(args)]; +} + +- (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... { + NSMutableArray *arguments = [NSMutableArray array]; + + va_list args; + va_start(args, arg); + for (id currentObject = arg; currentObject != nil; currentObject = va_arg(args, id)) { + [arguments addObject:currentObject]; + } + + va_end(args); + return [self rac_liftBlock:block withArgumentsFromArray:arguments]; +} + +- (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args { + return [[[[RACSignal + combineLatest:RACMapArgumentsToSignals(args)] + reduceEach:block] + takeUntil:self.rac_willDeallocSignal] + replayLast]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.h new file mode 100644 index 0000000..2f55195 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.h
@@ -0,0 +1,102 @@ +// +// NSObject+RACPropertySubscribing.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "EXTKeyPathCoding.h" +#import "metamacros.h" + +/// Creates a signal which observes `KEYPATH` on `TARGET` for changes. +/// +/// In either case, the observation continues until `TARGET` _or self_ is +/// deallocated. If any intermediate object is deallocated instead, it will be +/// assumed to have been set to nil. +/// +/// Make sure to `@strongify(self)` when using this macro within a block! The +/// macro will _always_ reference `self`, which can silently introduce a retain +/// cycle within a block. As a result, you should make sure that `self` is a weak +/// reference (e.g., created by `@weakify` and `@strongify`) before the +/// expression that uses `RACObserve`. +/// +/// Examples +/// +/// // Observes self, and doesn't stop until self is deallocated. +/// RACSignal *selfSignal = RACObserve(self, arrayController.items); +/// +/// // Observes the array controller, and stops when self _or_ the array +/// // controller is deallocated. +/// RACSignal *arrayControllerSignal = RACObserve(self.arrayController, items); +/// +/// // Observes obj.arrayController, and stops when self _or_ the array +/// // controller is deallocated. +/// RACSignal *signal2 = RACObserve(obj.arrayController, items); +/// +/// @weakify(self); +/// RACSignal *signal3 = [anotherSignal flattenMap:^(NSArrayController *arrayController) { +/// // Avoids a retain cycle because of RACObserve implicitly referencing +/// // self. +/// @strongify(self); +/// return RACObserve(arrayController, items); +/// }]; +/// +/// Returns a signal which sends the current value of the key path on +/// subscription, then sends the new value every time it changes, and sends +/// completed if self or observer is deallocated. +#define RACObserve(TARGET, KEYPATH) \ + [(id)(TARGET) rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self] + +@class RACDisposable; +@class RACSignal; + +@interface NSObject (RACPropertySubscribing) + +/// Creates a signal to observe the value at the given key path. +/// +/// The initial value is sent on subscription, the subsequent values are sent +/// from whichever thread the change occured on, even if it doesn't have a valid +/// scheduler. +/// +/// Returns a signal that immediately sends the receiver's current value at the +/// given keypath, then any changes thereafter. +- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer; + +/// Creates a signal to observe the changes of the given key path. +/// +/// The initial value is sent on subscription, the subsequent values are sent +/// from whichever thread the change occured on, even if it doesn't have a valid +/// scheduler. +/// +/// Returns a signal that sends tuples containing the current value at the key +/// path and the change dictionary for each KVO callback. +- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer; + +@end + +#define RACAble(...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ + (_RACAbleObject(self, __VA_ARGS__)) \ + (_RACAbleObject(__VA_ARGS__)) + +#define _RACAbleObject(object, property) [object rac_signalForKeyPath:@keypath(object, property) observer:self] + +#define RACAbleWithStart(...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ + (_RACAbleWithStartObject(self, __VA_ARGS__)) \ + (_RACAbleWithStartObject(__VA_ARGS__)) + +#define _RACAbleWithStartObject(object, property) [object rac_signalWithStartingValueForKeyPath:@keypath(object, property) observer:self] + +@interface NSObject (RACPropertySubscribingDeprecated) + ++ (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); ++ (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); ++ (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesAndChangesForKeyPath:options:observer: instead."))); +- (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); +- (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); +- (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal __attribute__((deprecated("Use -[RACSignal setKeyPath:onObject:] instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.m new file mode 100644 index 0000000..edd2acb --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACPropertySubscribing.m
@@ -0,0 +1,161 @@ +// +// NSObject+RACPropertySubscribing.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACPropertySubscribing.h" +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACKVOWrapper.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACKVOTrampoline.h" +#import "RACSubscriber.h" +#import "RACSignal+Operations.h" +#import "RACTuple.h" +#import <libkern/OSAtomic.h> + +@implementation NSObject (RACPropertySubscribing) + +- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { + return [[[self + rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] + reduceEach:^(id value, NSDictionary *change) { + return value; + }] + setNameWithFormat:@"RACObserve(%@, %@)", self.rac_description, keyPath]; +} + +- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer { + keyPath = [keyPath copy]; + + NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; + objectLock.name = @"com.github.ReactiveCocoa.NSObjectRACPropertySubscribing"; + + __block __unsafe_unretained NSObject *unsafeSelf = self; + __block __unsafe_unretained NSObject *unsafeObserver = observer; + + RACSignal *deallocSignal = [[RACSignal + zip:@[ + self.rac_willDeallocSignal, + observer.rac_willDeallocSignal ?: [RACSignal never] + ]] + doCompleted:^{ + // Forces deallocation to wait if the object variables are currently + // being read on another thread. + [objectLock lock]; + @onExit { + [objectLock unlock]; + }; + + unsafeSelf = nil; + unsafeObserver = nil; + }]; + + return [[[RACSignal + createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + // Hold onto the lock the whole time we're setting up the KVO + // observation, because any resurrection that might be caused by our + // retaining below must be balanced out by the time -dealloc returns + // (if another thread is waiting on the lock above). + [objectLock lock]; + @onExit { + [objectLock unlock]; + }; + + __strong NSObject *observer __attribute__((objc_precise_lifetime)) = unsafeObserver; + __strong NSObject *self __attribute__((objc_precise_lifetime)) = unsafeSelf; + + if (self == nil) { + [subscriber sendCompleted]; + return nil; + } + + return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change) { + [subscriber sendNext:RACTuplePack(value, change)]; + }]; + }] + takeUntil:deallocSignal] + setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", self.rac_description, keyPath, (unsigned long)options, observer.rac_description]; +} + +@end + +static RACSignal *signalWithoutChangesFor(Class class, NSObject *object, NSString *keyPath, NSKeyValueObservingOptions options, NSObject *observer) { + NSCParameterAssert(object != nil); + NSCParameterAssert(keyPath != nil); + NSCParameterAssert(observer != nil); + + keyPath = [keyPath copy]; + + @unsafeify(object); + + return [[class + rac_signalWithChangesFor:object keyPath:keyPath options:options observer:observer] + map:^(NSDictionary *change) { + @strongify(object); + return [object valueForKeyPath:keyPath]; + }]; +} + +@implementation NSObject (RACPropertySubscribingDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + ++ (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { + return signalWithoutChangesFor(self, object, keyPath, 0, observer); +} + ++ (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer { + return signalWithoutChangesFor(self, object, keyPath, NSKeyValueObservingOptionInitial, observer); +} + ++ (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer { + @unsafeify(observer, object); + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + + @strongify(observer, object); + RACKVOTrampoline *KVOTrampoline = [object rac_addObserver:observer forKeyPath:keyPath options:options block:^(id target, id observer, NSDictionary *change) { + [subscriber sendNext:change]; + }]; + + @weakify(subscriber); + RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ + @strongify(subscriber); + [KVOTrampoline dispose]; + [subscriber sendCompleted]; + }]; + + [observer.rac_deallocDisposable addDisposable:deallocDisposable]; + [object.rac_deallocDisposable addDisposable:deallocDisposable]; + + RACCompoundDisposable *observerDisposable = observer.rac_deallocDisposable; + RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; + return [RACDisposable disposableWithBlock:^{ + [observerDisposable removeDisposable:deallocDisposable]; + [objectDisposable removeDisposable:deallocDisposable]; + [KVOTrampoline dispose]; + }]; + }] setNameWithFormat:@"RACAble(%@, %@)", object.rac_description, keyPath]; +} + +- (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { + return [self.class rac_signalFor:self keyPath:keyPath observer:observer]; +} + +- (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer { + return [self.class rac_signalWithStartingValueFor:self keyPath:keyPath observer:observer]; +} + +- (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal { + return [signal setKeyPath:keyPath onObject:self]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h new file mode 100644 index 0000000..c6f3de5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h
@@ -0,0 +1,79 @@ +// +// NSObject+RACSelectorSignal.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +/// The domain for any errors originating from -rac_signalForSelector:. +extern NSString * const RACSelectorSignalErrorDomain; + +/// -rac_signalForSelector: was going to add a new method implementation for +/// `selector`, but another thread added an implementation before it was able to. +/// +/// This will _not_ occur for cases where a method implementation exists before +/// -rac_signalForSelector: is invoked. +extern const NSInteger RACSelectorSignalErrorMethodSwizzlingRace; + +@interface NSObject (RACSelectorSignal) + +/// Creates a signal associated with the receiver, which will send a tuple of the +/// method's arguments each time the given selector is invoked. +/// +/// If the selector is already implemented on the receiver, the existing +/// implementation will be invoked _before_ the signal fires. +/// +/// If the selector is not yet implemented on the receiver, the injected +/// implementation will have a `void` return type and accept only object +/// arguments. Invoking the added implementation with non-object values, or +/// expecting a return value, will result in undefined behavior. +/// +/// This is useful for changing an event or delegate callback into a signal. For +/// example, on an NSView: +/// +/// [[view rac_signalForSelector:@selector(mouseDown:)] subscribeNext:^(RACTuple *args) { +/// NSEvent *event = args.first; +/// NSLog(@"mouse button pressed: %@", event); +/// }]; +/// +/// selector - The selector for whose invocations are to be observed. If it +/// doesn't exist, it will be implemented to accept object arguments +/// and return void. This cannot have C arrays or unions as arguments +/// or C arrays, unions, structs, complex or vector types as return +/// type. +/// +/// Returns a signal which will send a tuple of arguments upon each invocation of +/// the selector, then completes when the receiver is deallocated. `next` events +/// will be sent synchronously from the thread that invoked the method. If +/// a runtime call fails, the signal will send an error in the +/// RACSelectorSignalErrorDomain. +- (RACSignal *)rac_signalForSelector:(SEL)selector; + +/// Behaves like -rac_signalForSelector:, but if the selector is not yet +/// implemented on the receiver, its method signature is looked up within +/// `protocol`, and may accept non-object arguments. +/// +/// If the selector is not yet implemented and has a return value, the injected +/// method will return all zero bits (equal to `nil`, `NULL`, 0, 0.0f, etc.). +/// +/// selector - The selector for whose invocations are to be observed. If it +/// doesn't exist, it will be implemented using information from +/// `protocol`, and may accept non-object arguments and return +/// a value. This cannot have C arrays or unions as arguments or +/// return type. +/// protocol - The protocol in which `selector` is declared. This will be used +/// for type information if the selector is not already implemented on +/// the receiver. This must not be `NULL`, and `selector` must exist +/// in this protocol. +/// +/// Returns a signal which will send a tuple of arguments on each invocation of +/// the selector, or an error in RACSelectorSignalErrorDomain if a runtime +/// call fails. +- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.m new file mode 100644 index 0000000..4c3aab9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.m
@@ -0,0 +1,322 @@ +// +// NSObject+RACSelectorSignal.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACSelectorSignal.h" +#import "EXTRuntimeExtensions.h" +#import "NSInvocation+RACTypeParsing.h" +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACObjCRuntime.h" +#import "RACSubject.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" +#import <objc/message.h> +#import <objc/runtime.h> + +NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain"; +const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1; + +static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_"; +static NSString * const RACSubclassSuffix = @"_RACSelectorSignal"; + +static NSMutableSet *swizzledClasses() { + static NSMutableSet *set; + static dispatch_once_t pred; + + dispatch_once(&pred, ^{ + set = [[NSMutableSet alloc] init]; + }); + + return set; +} + +@implementation NSObject (RACSelectorSignal) + +static BOOL RACForwardInvocation(id self, NSInvocation *invocation) { + SEL aliasSelector = RACAliasForSelector(invocation.selector); + RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); + + Class class = object_getClass(invocation.target); + BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector]; + if (respondsToAlias) { + invocation.selector = aliasSelector; + [invocation invoke]; + } + + if (subject == nil) return respondsToAlias; + + [subject sendNext:invocation.rac_argumentsTuple]; + return YES; +} + +static void RACSwizzleForwardInvocation(Class class) { + SEL forwardInvocationSEL = @selector(forwardInvocation:); + Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL); + + // Preserve any existing implementation of -forwardInvocation:. + void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL; + if (forwardInvocationMethod != NULL) { + originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod); + } + + // Set up a new version of -forwardInvocation:. + // + // If the selector has been passed to -rac_signalForSelector:, invoke + // the aliased method, and forward the arguments to any attached signals. + // + // If the selector has not been passed to -rac_signalForSelector:, + // invoke any existing implementation of -forwardInvocation:. If there + // was no existing implementation, throw an unrecognized selector + // exception. + id newForwardInvocation = ^(id self, NSInvocation *invocation) { + BOOL matched = RACForwardInvocation(self, invocation); + if (matched) return; + + if (originalForwardInvocation == NULL) { + [self doesNotRecognizeSelector:invocation.selector]; + } else { + originalForwardInvocation(self, forwardInvocationSEL, invocation); + } + }; + + class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@"); +} + +static void RACSwizzleRespondsToSelector(Class class) { + SEL respondsToSelectorSEL = @selector(respondsToSelector:); + + // Preserve existing implementation of -respondsToSelector:. + Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL); + BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod); + + // Set up a new version of -respondsToSelector: that returns YES for methods + // added by -rac_signalForSelector:. + // + // If the selector has a method defined on the receiver's actual class, and + // if that method's implementation is _objc_msgForward, then returns whether + // the instance has a signal for the selector. + // Otherwise, call the original -respondsToSelector:. + id newRespondsToSelector = ^ BOOL (id self, SEL selector) { + Method method = rac_getImmediateInstanceMethod(object_getClass(self), selector); + + if (method != NULL && method_getImplementation(method) == _objc_msgForward) { + SEL aliasSelector = RACAliasForSelector(selector); + return objc_getAssociatedObject(self, aliasSelector) != nil; + } + + return originalRespondsToSelector(self, respondsToSelectorSEL, selector); + }; + + class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod)); +} + +static void RACSwizzleGetClass(Class class, Class statedClass) { + SEL selector = @selector(class); + Method method = class_getInstanceMethod(class, selector); + IMP newIMP = imp_implementationWithBlock(^(id self) { + return statedClass; + }); + class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method)); +} + +static void RACSwizzleMethodSignatureForSelector(Class class) { + IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) { + // Don't send the -class message to the receiver because we've changed + // that to return the original class. + Class actualClass = object_getClass(self); + Method method = class_getInstanceMethod(actualClass, selector); + if (method == NULL) { + // Messages that the original class dynamically implements fall + // here. + // + // Call the original class' -methodSignatureForSelector:. + struct objc_super target = { + .super_class = class_getSuperclass(class), + .receiver = self, + }; + NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper; + return messageSend(&target, @selector(methodSignatureForSelector:), selector); + } + + char const *encoding = method_getTypeEncoding(method); + return [NSMethodSignature signatureWithObjCTypes:encoding]; + }); + + SEL selector = @selector(methodSignatureForSelector:); + Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector); + class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod)); +} + +// It's hard to tell which struct return types use _objc_msgForward, and +// which use _objc_msgForward_stret instead, so just exclude all struct, array, +// union, complex and vector return types. +static void RACCheckTypeEncoding(const char *typeEncoding) { +#if !NS_BLOCK_ASSERTIONS + // Some types, including vector types, are not encoded. In these cases the + // signature starts with the size of the argument frame. + NSCAssert(*typeEncoding < '1' || *typeEncoding > '9', @"unknown method return type not supported in type encoding: %s", typeEncoding); + NSCAssert(strstr(typeEncoding, "(") != typeEncoding, @"union method return type not supported"); + NSCAssert(strstr(typeEncoding, "{") != typeEncoding, @"struct method return type not supported"); + NSCAssert(strstr(typeEncoding, "[") != typeEncoding, @"array method return type not supported"); + NSCAssert(strstr(typeEncoding, @encode(_Complex float)) != typeEncoding, @"complex float method return type not supported"); + NSCAssert(strstr(typeEncoding, @encode(_Complex double)) != typeEncoding, @"complex double method return type not supported"); + NSCAssert(strstr(typeEncoding, @encode(_Complex long double)) != typeEncoding, @"complex long double method return type not supported"); + +#endif // !NS_BLOCK_ASSERTIONS +} + +static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) { + SEL aliasSelector = RACAliasForSelector(selector); + + @synchronized (self) { + RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); + if (subject != nil) return subject; + + Class class = RACSwizzleClass(self); + NSCAssert(class != nil, @"Could not swizzle class of %@", self); + + subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", self.rac_description, sel_getName(selector)]; + objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN); + + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subject sendCompleted]; + }]]; + + Method targetMethod = class_getInstanceMethod(class, selector); + if (targetMethod == NULL) { + const char *typeEncoding; + if (protocol == NULL) { + typeEncoding = RACSignatureForUndefinedSelector(selector); + } else { + // Look for the selector as an optional instance method. + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + if (methodDescription.name == NULL) { + // Then fall back to looking for a required instance + // method. + methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES); + NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol)); + } + + typeEncoding = methodDescription.types; + } + + RACCheckTypeEncoding(typeEncoding); + + // Define the selector to call -forwardInvocation:. + if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class], + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil) + }; + + return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]]; + } + } else if (method_getImplementation(targetMethod) != _objc_msgForward) { + // Make a method alias for the existing method implementation. + const char *typeEncoding = method_getTypeEncoding(targetMethod); + + RACCheckTypeEncoding(typeEncoding); + + BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding); + NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class); + + // Redefine the selector to call -forwardInvocation:. + class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod)); + } + + return subject; + } +} + +static SEL RACAliasForSelector(SEL originalSelector) { + NSString *selectorName = NSStringFromSelector(originalSelector); + return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]); +} + +static const char *RACSignatureForUndefinedSelector(SEL selector) { + const char *name = sel_getName(selector); + NSMutableString *signature = [NSMutableString stringWithString:@"v@:"]; + + while ((name = strchr(name, ':')) != NULL) { + [signature appendString:@"@"]; + name++; + } + + return signature.UTF8String; +} + +static Class RACSwizzleClass(NSObject *self) { + Class statedClass = self.class; + Class baseClass = object_getClass(self); + NSString *className = NSStringFromClass(baseClass); + + if ([className hasSuffix:RACSubclassSuffix]) { + return baseClass; + } else if (statedClass != baseClass) { + // If the class is already lying about what it is, it's probably a KVO + // dynamic subclass or something else that we shouldn't subclass + // ourselves. + // + // Just swizzle -forwardInvocation: in-place. Since the object's class + // was almost certainly dynamically changed, we shouldn't see another of + // these classes in the hierarchy. + // + // Additionally, swizzle -respondsToSelector: because the default + // implementation may be ignorant of methods added to this class. + @synchronized (swizzledClasses()) { + if (![swizzledClasses() containsObject:className]) { + RACSwizzleForwardInvocation(baseClass); + RACSwizzleRespondsToSelector(baseClass); + RACSwizzleGetClass(baseClass, statedClass); + RACSwizzleGetClass(object_getClass(baseClass), statedClass); + RACSwizzleMethodSignatureForSelector(baseClass); + [swizzledClasses() addObject:className]; + } + } + + return baseClass; + } + + const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String; + Class subclass = objc_getClass(subclassName); + + if (subclass == nil) { + subclass = [RACObjCRuntime createClass:subclassName inheritingFromClass:baseClass]; + if (subclass == nil) return nil; + + RACSwizzleForwardInvocation(subclass); + RACSwizzleRespondsToSelector(subclass); + + RACSwizzleGetClass(subclass, statedClass); + RACSwizzleGetClass(object_getClass(subclass), statedClass); + + RACSwizzleMethodSignatureForSelector(subclass); + + objc_registerClassPair(subclass); + } + + object_setClass(self, subclass); + return subclass; +} + +- (RACSignal *)rac_signalForSelector:(SEL)selector { + NSCParameterAssert(selector != NULL); + + return NSObjectRACSignalForSelector(self, selector, NULL); +} + +- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol { + NSCParameterAssert(selector != NULL); + NSCParameterAssert(protocol != NULL); + + return NSObjectRACSignalForSelector(self, selector, protocol); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.h new file mode 100644 index 0000000..8bea2db --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSOrderedSet+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSOrderedSet (RACSequenceAdditions) + +/// Creates and returns a sequence corresponding to the receiver. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.m new file mode 100644 index 0000000..55dfd0b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSOrderedSet+RACSequenceAdditions.m
@@ -0,0 +1,19 @@ +// +// NSOrderedSet+RACSequenceAdditions.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSOrderedSet+RACSequenceAdditions.h" +#import "NSArray+RACSequenceAdditions.h" + +@implementation NSOrderedSet (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + // TODO: First class support for ordered set sequences. + return self.array.rac_sequence; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.h new file mode 100644 index 0000000..6665501 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.h
@@ -0,0 +1,20 @@ +// +// NSSet+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSSet (RACSequenceAdditions) + +/// Creates and returns a sequence corresponding to the receiver. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.m new file mode 100644 index 0000000..cc07f75 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSSet+RACSequenceAdditions.m
@@ -0,0 +1,19 @@ +// +// NSSet+RACSequenceAdditions.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSSet+RACSequenceAdditions.h" +#import "NSArray+RACSequenceAdditions.h" + +@implementation NSSet (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + // TODO: First class support for set sequences. + return self.allObjects.rac_sequence; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.h b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.h new file mode 100644 index 0000000..d56cf59 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.h
@@ -0,0 +1,34 @@ +// +// NSString+RACKeyPathUtilities.h +// ReactiveCocoa +// +// Created by Uri Baghin on 05/05/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +// A private category of methods to extract parts of a key path. +@interface NSString (RACKeyPathUtilities) + +// Returns an array of the components of the receiver. +// +// Calling this method on a string that isn't a key path is considered undefined +// behavior. +- (NSArray *)rac_keyPathComponents; + +// Returns a key path with all the components of the receiver except for the +// last one. +// +// Calling this method on a string that isn't a key path is considered undefined +// behavior. +- (NSString *)rac_keyPathByDeletingLastKeyPathComponent; + +// Returns a key path with all the components of the receiver expect for the +// first one. +// +// Calling this method on a string that isn't a key path is considered undefined +// behavior. +- (NSString *)rac_keyPathByDeletingFirstKeyPathComponent; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.m b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.m new file mode 100644 index 0000000..63a100c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACKeyPathUtilities.m
@@ -0,0 +1,36 @@ +// +// NSString+RACKeyPathUtilities.m +// ReactiveCocoa +// +// Created by Uri Baghin on 05/05/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSString+RACKeyPathUtilities.h" + +@implementation NSString (RACKeyPathUtilities) + +- (NSArray *)rac_keyPathComponents { + if (self.length == 0) { + return nil; + } + return [self componentsSeparatedByString:@"."]; +} + +- (NSString *)rac_keyPathByDeletingLastKeyPathComponent { + NSUInteger lastDotIndex = [self rangeOfString:@"." options:NSBackwardsSearch].location; + if (lastDotIndex == NSNotFound) { + return nil; + } + return [self substringToIndex:lastDotIndex]; +} + +- (NSString *)rac_keyPathByDeletingFirstKeyPathComponent { + NSUInteger firstDotIndex = [self rangeOfString:@"."].location; + if (firstDotIndex == NSNotFound) { + return nil; + } + return [self substringFromIndex:firstDotIndex + 1]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.h b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.h new file mode 100644 index 0000000..0116231 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.h
@@ -0,0 +1,21 @@ +// +// NSString+RACSequenceAdditions.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSequence; + +@interface NSString (RACSequenceAdditions) + +/// Creates and returns a sequence containing strings corresponding to each +/// composed character sequence in the receiver. +/// +/// Mutating the receiver will not affect the sequence after it's been created. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.m b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.m new file mode 100644 index 0000000..eb6a76e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSequenceAdditions.m
@@ -0,0 +1,18 @@ +// +// NSString+RACSequenceAdditions.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "NSString+RACSequenceAdditions.h" +#import "RACStringSequence.h" + +@implementation NSString (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + return [RACStringSequence sequenceWithString:self offset:0]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.h new file mode 100644 index 0000000..d904a4c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.h
@@ -0,0 +1,22 @@ +// +// NSString+RACSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; +@class RACScheduler; + +@interface NSString (RACSupport) + +// Reads in the contents of the file using +[NSString stringWithContentsOfURL:usedEncoding:error:]. +// Note that encoding won't be valid until the signal completes successfully. +// +// scheduler - cannot be nil. ++ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.m new file mode 100644 index 0000000..c6ebe0b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.m
@@ -0,0 +1,35 @@ +// +// NSString+RACSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSString+RACSupport.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" + +@implementation NSString (RACSupport) + ++ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler { + NSCParameterAssert(scheduler != nil); + + RACReplaySubject *subject = [RACReplaySubject subject]; + [subject setNameWithFormat:@"+rac_readContentsOfURL: %@ usedEncoding:scheduler: %@", URL, scheduler]; + + [scheduler schedule:^{ + NSError *error = nil; + NSString *string = [NSString stringWithContentsOfURL:URL usedEncoding:encoding error:&error]; + if(string == nil) { + [subject sendError:error]; + } else { + [subject sendNext:string]; + [subject sendCompleted]; + } + }]; + + return subject; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSText+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSText+RACSignalSupport.h new file mode 100644 index 0000000..e3fc8ed --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSText+RACSignalSupport.h
@@ -0,0 +1,19 @@ +// +// NSText+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-03-08. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +@class RACSignal; + +@interface NSText (RACSignalSupport) + +/// Returns a signal which sends the current `string` of the receiver, then the +/// new value any time it changes. +- (RACSignal *)rac_textSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSText+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSText+RACSignalSupport.m new file mode 100644 index 0000000..7e63f2a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSText+RACSignalSupport.m
@@ -0,0 +1,38 @@ +// +// NSText+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-03-08. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSText+RACSignalSupport.h" +#import "EXTScope.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "RACSubscriber.h" +#import "NSObject+RACDescription.h" + +@implementation NSText (RACSignalSupport) + +- (RACSignal *)rac_textSignal { + @unsafeify(self); + return [[[[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + @strongify(self); + id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSTextDidChangeNotification object:self queue:nil usingBlock:^(NSNotification *note) { + [subscriber sendNext:note.object]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [NSNotificationCenter.defaultCenter removeObserver:observer]; + }]; + }] + map:^(NSText *text) { + return [text.string copy]; + }] + startWith:[self.string copy]] + setNameWithFormat:@"%@ -rac_textSignal", [self rac_description]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.h new file mode 100644 index 0000000..e9bf04f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.h
@@ -0,0 +1,25 @@ +// +// NSURLConnection+RACSupport.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +@interface NSURLConnection (RACSupport) + +// Lazily loads data for the given request in the background. +// +// request - The URL request to load. This must not be nil. +// +// Returns a signal which will begin loading the request upon each subscription, +// then send a `RACTuple` of the received `NSURLResponse` and downloaded +// `NSData`, and complete on a background thread. If any errors occur, the +// returned signal will error out. ++ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.m new file mode 100644 index 0000000..3eaa2c3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.m
@@ -0,0 +1,53 @@ +// +// NSURLConnection+RACSupport.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSURLConnection+RACSupport.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "RACSubscriber.h" +#import "RACTuple.h" + +@implementation NSURLConnection (RACSupport) + ++ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request { + NSCParameterAssert(request != nil); + + return [[RACSignal + createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + queue.name = @"com.github.ReactiveCocoa.NSURLConnectionRACSupport"; + + [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + // The docs say that `nil` data means an error occurred, but + // `nil` responses can also occur in practice (circumstances + // unknown). Consider either to be an error. + // + // Note that _empty_ data is not necessarily erroneous, as there + // may be headers but no HTTP body. + if (response == nil || data == nil) { + [subscriber sendError:error]; + } else { + [subscriber sendNext:RACTuplePack(response, data)]; + [subscriber sendCompleted]; + } + }]; + + return [RACDisposable disposableWithBlock:^{ + // It's not clear if this will actually cancel the connection, + // but we can at least prevent _some_ unnecessary work -- + // without writing all the code for a proper delegate, which + // doesn't really belong in RAC. + queue.suspended = YES; + [queue cancelAllOperations]; + }]; + }] + setNameWithFormat:@"+rac_sendAsynchronousRequest: %@", request]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.h new file mode 100644 index 0000000..8482fb3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.h
@@ -0,0 +1,27 @@ +// +// NSUserDefaults+RACSupport.h +// ReactiveCocoa +// +// Created by Matt Diephouse on 12/19/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACChannelTerminal; + +@interface NSUserDefaults (RACSupport) + +/// Creates and returns a terminal for binding the user defaults key. +/// +/// **Note:** The value in the user defaults is *asynchronously* updated with +/// values sent to the channel. +/// +/// key - The user defaults key to create the channel terminal for. +/// +/// Returns a channel terminal that sends the value of the user defaults key +/// upon subscription, sends an updated value whenever the default changes, and +/// updates the default asynchronously with values it receives. +- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.m new file mode 100644 index 0000000..a8624a1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/NSUserDefaults+RACSupport.m
@@ -0,0 +1,58 @@ +// +// NSUserDefaults+RACSupport.m +// ReactiveCocoa +// +// Created by Matt Diephouse on 12/19/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSUserDefaults+RACSupport.h" + +#import "EXTScope.h" +#import "RACChannel.h" +#import "RACScheduler.h" +#import "RACSignal+Operations.h" +#import "NSNotificationCenter+RACSupport.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACLifting.h" + +@implementation NSUserDefaults (RACSupport) + +- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key { + RACChannel *channel = [RACChannel new]; + + RACScheduler *scheduler = [RACScheduler scheduler]; + __block BOOL ignoreNextValue = NO; + + @weakify(self); + [[[[[[[NSNotificationCenter.defaultCenter + rac_addObserverForName:NSUserDefaultsDidChangeNotification object:self] + map:^(id _) { + @strongify(self); + return [self objectForKey:key]; + }] + startWith:[self objectForKey:key]] + // Don't send values that were set on the other side of the terminal. + filter:^ BOOL (id _) { + if (RACScheduler.currentScheduler == scheduler && ignoreNextValue) { + ignoreNextValue = NO; + return NO; + } + return YES; + }] + distinctUntilChanged] + takeUntil:self.rac_willDeallocSignal] + subscribe:channel.leadingTerminal]; + + [[channel.leadingTerminal + deliverOn:scheduler] + subscribeNext:^(id value) { + @strongify(self); + ignoreNextValue = YES; + [self setObject:value forKey:key]; + }]; + + return channel.followingTerminal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.h new file mode 100644 index 0000000..e9d948e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.h
@@ -0,0 +1,18 @@ +// +// RACArraySequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACSequence.h" + +// Private class that adapts an array to the RACSequence interface. +@interface RACArraySequence : RACSequence + +// Returns a sequence for enumerating over the given array, starting from the +// given offset. The array will be copied to prevent mutation. ++ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.m new file mode 100644 index 0000000..f7ca69d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACArraySequence.m
@@ -0,0 +1,125 @@ +// +// RACArraySequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACArraySequence.h" + +@interface RACArraySequence () + +// Redeclared from the superclass and marked deprecated to prevent using `array` +// where `backingArray` is intended. +@property (nonatomic, copy, readonly) NSArray *array __attribute__((deprecated)); + +// The array being sequenced. +@property (nonatomic, copy, readonly) NSArray *backingArray; + +// The index in the array from which the sequence starts. +@property (nonatomic, assign, readonly) NSUInteger offset; + +@end + +@implementation RACArraySequence + +#pragma mark Lifecycle + ++ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset { + NSCParameterAssert(offset <= array.count); + + if (offset == array.count) return self.empty; + + RACArraySequence *seq = [[self alloc] init]; + seq->_backingArray = [array copy]; + seq->_offset = offset; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + return [self.backingArray objectAtIndex:self.offset]; +} + +- (RACSequence *)tail { + RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1]; + sequence.name = self.name; + return sequence; +} + +#pragma mark NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len { + NSCParameterAssert(len > 0); + + if (state->state >= self.backingArray.count) { + // Enumeration has completed. + return 0; + } + + if (state->state == 0) { + state->state = self.offset; + + // Since a sequence doesn't mutate, this just needs to be set to + // something non-NULL. + state->mutationsPtr = state->extra; + } + + state->itemsPtr = stackbuf; + + NSUInteger startIndex = state->state; + NSUInteger index = 0; + + for (id value in self.backingArray) { + // Constructing an index set for -enumerateObjectsAtIndexes: can actually be + // slower than just skipping the items we don't care about. + if (index < startIndex) { + ++index; + continue; + } + + stackbuf[index - startIndex] = value; + + ++index; + if (index - startIndex >= len) break; + } + + NSCAssert(index > startIndex, @"Final index (%lu) should be greater than start index (%lu)", (unsigned long)index, (unsigned long)startIndex); + + state->state = index; + return index - startIndex; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +- (NSArray *)array { + return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)]; +} +#pragma clang diagnostic pop + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + if (self == nil) return nil; + + _backingArray = [coder decodeObjectForKey:@"array"]; + _offset = 0; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + // Encoding is handled in RACSequence. + [super encodeWithCoder:coder]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, array = %@ }", self.class, self, self.name, self.backingArray]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.h b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.h new file mode 100644 index 0000000..8588c80 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.h
@@ -0,0 +1,70 @@ +// +// RACBacktrace.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-08-20. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#ifdef DEBUG + +extern void rac_dispatch_async(dispatch_queue_t queue, dispatch_block_t block); +extern void rac_dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); +extern void rac_dispatch_after(dispatch_time_t time, dispatch_queue_t queue, dispatch_block_t block); +extern void rac_dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function); +extern void rac_dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function); +extern void rac_dispatch_after_f(dispatch_time_t time, dispatch_queue_t queue, void *context, dispatch_function_t function); + +#define dispatch_async rac_dispatch_async +#define dispatch_barrier_async rac_dispatch_barrier_async +#define dispatch_after rac_dispatch_after +#define dispatch_async_f rac_dispatch_async_f +#define dispatch_barrier_async_f rac_dispatch_barrier_async_f +#define dispatch_after_f rac_dispatch_after_f + +/// Preserves backtraces across asynchronous calls. +/// +/// On OS X, you can enable the automatic capturing of asynchronous backtraces +/// (in Debug builds) by setting the `DYLD_INSERT_LIBRARIES` environment variable +/// to `@executable_path/../Frameworks/ReactiveCocoa.framework/ReactiveCocoa` in +/// your scheme's Run action settings. +/// +/// On iOS, your project and RAC will automatically use the `rac_` GCD functions +/// (declared above) for asynchronous work. Unfortunately, unlike OS X, it's +/// impossible to capture backtraces inside NSOperationQueue or other code +/// outside of your project. +/// +/// Once backtraces are being captured, you can `po [RACBacktrace backtrace]` in +/// the debugger to print them out at any time. You can even set up an alias in +/// ~/.lldbinit to do so: +/// +/// command alias racbt po [RACBacktrace backtrace] +/// +@interface RACBacktrace : NSObject + +/// The backtrace from any previous thread. +@property (nonatomic, strong, readonly) RACBacktrace *previousThreadBacktrace; + +/// The call stack of this backtrace's thread. +@property (nonatomic, copy, readonly) NSArray *callStackSymbols; + +/// Captures the current thread's backtrace, appending it to any backtrace from +/// a previous thread. ++ (instancetype)backtrace; + +/// Same as +backtrace, but omits the specified number of frames at the +/// top of the stack (in addition to this method itself). ++ (instancetype)backtraceIgnoringFrames:(NSUInteger)ignoreCount; + +@end + +#else + +#define rac_dispatch_async dispatch_async +#define rac_dispatch_barrier_async dispatch_barrier_async +#define rac_dispatch_after dispatch_after +#define rac_dispatch_async_f dispatch_async_f +#define rac_dispatch_barrier_async_f dispatch_barrier_async_f +#define rac_dispatch_after_f dispatch_after_f + +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.m b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.m new file mode 100644 index 0000000..c2724d4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBacktrace.m
@@ -0,0 +1,249 @@ +// +// RACBacktrace.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-08-16. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <execinfo.h> +#import <pthread.h> +#import "RACBacktrace.h" + +#define RAC_BACKTRACE_MAX_CALL_STACK_FRAMES 128 + +#ifdef DEBUG + +// Undefine the macros that hide the real GCD functions. +#undef dispatch_async +#undef dispatch_barrier_async +#undef dispatch_after +#undef dispatch_async_f +#undef dispatch_barrier_async_f +#undef dispatch_after_f + +@interface RACBacktrace () { + void *_callStackAddresses[RAC_BACKTRACE_MAX_CALL_STACK_FRAMES]; + int _callStackSize; +} + +@property (nonatomic, strong, readwrite) RACBacktrace *previousThreadBacktrace; +@end + +@interface RACDispatchInfo : NSObject + +// The recorded backtrace. +@property (nonatomic, strong, readonly) RACBacktrace *backtrace; + +// The information for the original dispatch. +@property (nonatomic, readonly) dispatch_function_t function; +@property (nonatomic, readonly) void *context; +@property (nonatomic, readonly) dispatch_queue_t queue; + +- (id)initWithQueue:(dispatch_queue_t)queue function:(dispatch_function_t)function context:(void *)context; + +@end + +// Function for use with dispatch_async_f and friends, which will save the +// backtrace onto the current queue, then call through to the original dispatch. +static void RACTraceDispatch (void *ptr) { + // Balance out the retain necessary for async calls. + RACDispatchInfo *info __attribute__((objc_precise_lifetime)) = CFBridgingRelease(ptr); + + dispatch_queue_set_specific(info.queue, (void *)pthread_self(), (__bridge void *)info.backtrace, NULL); + info.function(info.context); + dispatch_queue_set_specific(info.queue, (void *)pthread_self(), NULL, NULL); +} + +// Always inline this function, for consistency in backtraces. +__attribute__((always_inline)) +static dispatch_block_t RACBacktraceBlock (dispatch_queue_t queue, dispatch_block_t block) { + RACBacktrace *backtrace = [RACBacktrace backtrace]; + + return [^{ + RACBacktrace *backtraceKeptAlive __attribute__((objc_precise_lifetime)) = backtrace; + + dispatch_queue_set_specific(queue, (void *)pthread_self(), (__bridge void *)backtraceKeptAlive, NULL); + block(); + dispatch_queue_set_specific(queue, (void *)pthread_self(), NULL, NULL); + } copy]; +} + +void rac_dispatch_async(dispatch_queue_t queue, dispatch_block_t block) { + dispatch_async(queue, RACBacktraceBlock(queue, block)); +} + +void rac_dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block) { + dispatch_barrier_async(queue, RACBacktraceBlock(queue, block)); +} + +void rac_dispatch_after(dispatch_time_t time, dispatch_queue_t queue, dispatch_block_t block) { + dispatch_after(time, queue, RACBacktraceBlock(queue, block)); +} + +void rac_dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function) { + RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context]; + dispatch_async_f(queue, (void *)CFBridgingRetain(info), &RACTraceDispatch); +} + +void rac_dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t function) { + RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context]; + dispatch_barrier_async_f(queue, (void *)CFBridgingRetain(info), &RACTraceDispatch); +} + +void rac_dispatch_after_f(dispatch_time_t time, dispatch_queue_t queue, void *context, dispatch_function_t function) { + RACDispatchInfo *info = [[RACDispatchInfo alloc] initWithQueue:queue function:function context:context]; + dispatch_after_f(time, queue, (void *)CFBridgingRetain(info), &RACTraceDispatch); +} + +// This is what actually performs the injection. +// +// The DYLD_INSERT_LIBRARIES environment variable must include the RAC dynamic +// library in order for this to work. +__attribute__((used)) static struct { const void *replacement; const void *replacee; } interposers[] __attribute__((section("__DATA,__interpose"))) = { + { (const void *)&rac_dispatch_async, (const void *)&dispatch_async }, + { (const void *)&rac_dispatch_barrier_async, (const void *)&dispatch_barrier_async }, + { (const void *)&rac_dispatch_after, (const void *)&dispatch_after }, + { (const void *)&rac_dispatch_async_f, (const void *)&dispatch_async_f }, + { (const void *)&rac_dispatch_barrier_async_f, (const void *)&dispatch_barrier_async_f }, + { (const void *)&rac_dispatch_after_f, (const void *)&dispatch_after_f }, +}; + +static void RACSignalHandler (int sig) { + NSLog(@"Backtrace: %@", [RACBacktrace backtrace]); + fflush(stdout); + + // Restore the default action and raise the signal again. + signal(sig, SIG_DFL); + raise(sig); +} + +static void RACExceptionHandler (NSException *ex) { + NSLog(@"Uncaught exception %@", ex); + NSLog(@"Backtrace: %@", [RACBacktrace backtrace]); + fflush(stdout); +} + +@implementation RACBacktrace + +#pragma mark Properties + +- (NSArray *)callStackSymbols { + if (_callStackSize == 0) return @[]; + + char **symbols = backtrace_symbols(_callStackAddresses, _callStackSize); + NSMutableArray *array = [NSMutableArray arrayWithCapacity:(NSUInteger)_callStackSize]; + + for (int i = 0; i < _callStackSize; i++) { + NSString *str = @(symbols[i]); + [array addObject:str]; + } + + free(symbols); + return array; +} + +#pragma mark Lifecycle + ++ (void)load { + @autoreleasepool { + NSString *libraries = [[[NSProcessInfo processInfo] environment] objectForKey:@"DYLD_INSERT_LIBRARIES"]; + + // Don't install our handlers if we're not actually intercepting function + // calls. + if ([libraries rangeOfString:@"ReactiveCocoa"].length == 0) return; + + NSLog(@"*** Enabling asynchronous backtraces"); + + NSSetUncaughtExceptionHandler(&RACExceptionHandler); + } + + signal(SIGILL, &RACSignalHandler); + signal(SIGTRAP, &RACSignalHandler); + signal(SIGABRT, &RACSignalHandler); + signal(SIGFPE, &RACSignalHandler); + signal(SIGBUS, &RACSignalHandler); + signal(SIGSEGV, &RACSignalHandler); + signal(SIGSYS, &RACSignalHandler); + signal(SIGPIPE, &RACSignalHandler); +} + +- (void)dealloc { + __autoreleasing RACBacktrace *previous __attribute__((unused)) = self.previousThreadBacktrace; + self.previousThreadBacktrace = nil; +} + +#pragma mark Backtraces + ++ (instancetype)backtrace { + return [self backtraceIgnoringFrames:1]; +} + ++ (instancetype)backtraceIgnoringFrames:(NSUInteger)ignoreCount { + @autoreleasepool { + RACBacktrace *oldBacktrace = (__bridge id)dispatch_get_specific((void *)pthread_self()); + + RACBacktrace *newBacktrace = [[RACBacktrace alloc] init]; + newBacktrace.previousThreadBacktrace = oldBacktrace; + + int size = backtrace(newBacktrace->_callStackAddresses, RAC_BACKTRACE_MAX_CALL_STACK_FRAMES); + + // Omit this method plus however many others from the backtrace. + ++ignoreCount; + if ((NSUInteger)size > ignoreCount) { + memmove(newBacktrace->_callStackAddresses, newBacktrace->_callStackAddresses + ignoreCount, ((NSUInteger)size - ignoreCount) * sizeof(char *)); + size -= (int)ignoreCount; + } + + newBacktrace->_callStackSize = size; + return newBacktrace; + } +} + +#pragma mark NSObject + +- (NSString *)description { + NSString *str = [NSString stringWithFormat:@"%@", self.callStackSymbols]; + if (self.previousThreadBacktrace != nil) { + str = [str stringByAppendingFormat:@"\n\n... asynchronously invoked from: %@", self.previousThreadBacktrace]; + } + + return str; +} + +@end + +@implementation RACDispatchInfo + +#pragma mark Lifecycle + +- (id)initWithQueue:(dispatch_queue_t)queue function:(dispatch_function_t)function context:(void *)context { + @autoreleasepool { + NSCParameterAssert(queue != NULL); + NSCParameterAssert(function != NULL); + + self = [super init]; + if (self == nil) return nil; + + _backtrace = [RACBacktrace backtraceIgnoringFrames:1]; + + dispatch_retain(queue); + _queue = queue; + + _function = function; + _context = context; + + return self; + } +} + +- (void)dealloc { + if (_queue != NULL) { + dispatch_release(_queue); + _queue = NULL; + } +} + +@end + +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.h b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.h new file mode 100644 index 0000000..2f9e0db --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.h
@@ -0,0 +1,19 @@ +// +// RACBehaviorSubject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" + + +/// A behavior subject sends the last value it received when it is subscribed to. +@interface RACBehaviorSubject : RACSubject + +/// Creates a new behavior subject with a default value. If it hasn't received +/// any values when it gets subscribed to, it sends the default value. ++ (instancetype)behaviorSubjectWithDefaultValue:(id)value; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.m b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.m new file mode 100644 index 0000000..dfda2ac --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBehaviorSubject.m
@@ -0,0 +1,56 @@ +// +// RACBehaviorSubject.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACBehaviorSubject.h" +#import "RACDisposable.h" +#import "RACScheduler+Private.h" + +@interface RACBehaviorSubject () + +// This property should only be used while synchronized on self. +@property (nonatomic, strong) id currentValue; + +@end + +@implementation RACBehaviorSubject + +#pragma mark Lifecycle + ++ (instancetype)behaviorSubjectWithDefaultValue:(id)value { + RACBehaviorSubject *subject = [self subject]; + subject.currentValue = value; + return subject; +} + +#pragma mark RACSignal + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; + + RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ + @synchronized (self) { + [subscriber sendNext:self.currentValue]; + } + }]; + + return [RACDisposable disposableWithBlock:^{ + [subscriptionDisposable dispose]; + [schedulingDisposable dispose]; + }]; +} + +#pragma mark RACSubscriber + +- (void)sendNext:(id)value { + @synchronized (self) { + self.currentValue = value; + [super sendNext:value]; + } +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.h b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.h new file mode 100644 index 0000000..3857d7c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.h
@@ -0,0 +1,30 @@ +// +// RACBlockTrampoline.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 10/21/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACTuple; + +// A private class that allows a limited type of dynamic block invocation. +@interface RACBlockTrampoline : NSObject + +// Invokes the given block with the given arguments. All of the block's +// argument types must be objects and it must be typed to return an object. +// +// At this time, it only supports blocks that take up to 15 arguments. Any more +// is just cray. +// +// block - The block to invoke. Must accept as many arguments as are given in +// the arguments array. Cannot be nil. +// arguments - The arguments with which to invoke the block. `RACTupleNil`s will +// be passed as nils. +// +// Returns the return value of invoking the block. ++ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.m b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.m new file mode 100644 index 0000000..07903dd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACBlockTrampoline.m
@@ -0,0 +1,156 @@ +// +// RACBlockTrampoline.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 10/21/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACBlockTrampoline.h" +#import "RACTuple.h" + +@interface RACBlockTrampoline () +@property (nonatomic, readonly, copy) id block; +@end + +@implementation RACBlockTrampoline + +#pragma mark API + +- (id)initWithBlock:(id)block { + self = [super init]; + if (self == nil) return nil; + + _block = [block copy]; + + return self; +} + ++ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments { + NSCParameterAssert(block != NULL); + + RACBlockTrampoline *trampoline = [[self alloc] initWithBlock:block]; + return [trampoline invokeWithArguments:arguments]; +} + +- (id)invokeWithArguments:(RACTuple *)arguments { + SEL selector = [self selectorForArgumentCount:arguments.count]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]]; + invocation.selector = selector; + invocation.target = self; + + for (NSUInteger i = 0; i < arguments.count; i++) { + id arg = arguments[i]; + NSInteger argIndex = (NSInteger)(i + 2); + [invocation setArgument:&arg atIndex:argIndex]; + } + + [invocation invoke]; + + __unsafe_unretained id returnVal; + [invocation getReturnValue:&returnVal]; + return returnVal; +} + +- (SEL)selectorForArgumentCount:(NSUInteger)count { + NSCParameterAssert(count > 0); + + switch (count) { + case 0: return NULL; + case 1: return @selector(performWith:); + case 2: return @selector(performWith::); + case 3: return @selector(performWith:::); + case 4: return @selector(performWith::::); + case 5: return @selector(performWith:::::); + case 6: return @selector(performWith::::::); + case 7: return @selector(performWith:::::::); + case 8: return @selector(performWith::::::::); + case 9: return @selector(performWith:::::::::); + case 10: return @selector(performWith::::::::::); + case 11: return @selector(performWith:::::::::::); + case 12: return @selector(performWith::::::::::::); + case 13: return @selector(performWith:::::::::::::); + case 14: return @selector(performWith::::::::::::::); + case 15: return @selector(performWith:::::::::::::::); + } + + NSCAssert(NO, @"The argument count is too damn high! Only blocks of up to 15 arguments are currently supported."); + return NULL; +} + +- (id)performWith:(id)obj1 { + id (^block)(id) = self.block; + return block(obj1); +} + +- (id)performWith:(id)obj1 :(id)obj2 { + id (^block)(id, id) = self.block; + return block(obj1, obj2); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 { + id (^block)(id, id, id) = self.block; + return block(obj1, obj2, obj3); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 { + id (^block)(id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 { + id (^block)(id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 { + id (^block)(id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 { + id (^block)(id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 { + id (^block)(id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 { + id (^block)(id, id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 { + id (^block)(id, id, id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 { + id (^block)(id, id, id, id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 { + id (^block)(id, id, id, id, id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 { + id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 :(id)obj14 { + id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13, obj14); +} + +- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 :(id)obj4 :(id)obj5 :(id)obj6 :(id)obj7 :(id)obj8 :(id)obj9 :(id)obj10 :(id)obj11 :(id)obj12 :(id)obj13 :(id)obj14 :(id)obj15 { + id (^block)(id, id, id, id, id, id, id, id, id, id, id, id, id, id, id) = self.block; + return block(obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8, obj9, obj10, obj11, obj12, obj13, obj14, obj15); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.h b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.h new file mode 100644 index 0000000..ef1d6f1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.h
@@ -0,0 +1,70 @@ +// +// RACChannel.h +// ReactiveCocoa +// +// Created by Uri Baghin on 01/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACSubscriber.h" + +@class RACChannelTerminal; + +/// A two-way channel. +/// +/// Conceptually, RACChannel can be thought of as a bidirectional connection, +/// composed of two controllable signals that work in parallel. +/// +/// For example, when connecting between a view and a model: +/// +/// Model View +/// `leadingTerminal` ------> `followingTerminal` +/// `leadingTerminal` <------ `followingTerminal` +/// +/// The initial value of the model and all future changes to it are _sent on_ the +/// `leadingTerminal`, and _received by_ subscribers of the `followingTerminal`. +/// +/// Likewise, whenever the user changes the value of the view, that value is sent +/// on the `followingTerminal`, and received in the model from the +/// `leadingTerminal`. However, the initial value of the view is not received +/// from the `leadingTerminal` (only future changes). +@interface RACChannel : NSObject + +/// The terminal which "leads" the channel, by sending its latest value +/// immediately to new subscribers of the `followingTerminal`. +/// +/// New subscribers to this terminal will not receive a starting value, but will +/// receive all future values that are sent to the `followingTerminal`. +@property (nonatomic, strong, readonly) RACChannelTerminal *leadingTerminal; + +/// The terminal which "follows" the lead of the other terminal, only sending +/// _future_ values to the subscribers of the `leadingTerminal`. +/// +/// The latest value sent to the `leadingTerminal` (if any) will be sent +/// immediately to new subscribers of this terminal, and then all future values +/// as well. +@property (nonatomic, strong, readonly) RACChannelTerminal *followingTerminal; + +@end + +/// Represents one end of a RACChannel. +/// +/// An terminal is similar to a socket or pipe -- it represents one end of +/// a connection (the RACChannel, in this case). Values sent to this terminal +/// will _not_ be received by its subscribers. Instead, the values will be sent +/// to the subscribers of the RACChannel's _other_ terminal. +/// +/// For example, when using the `followingTerminal`, _sent_ values can only be +/// _received_ from the `leadingTerminal`, and vice versa. +/// +/// To make it easy to terminate a RACChannel, `error` and `completed` events +/// sent to either terminal will be received by the subscribers of _both_ +/// terminals. +/// +/// Do not instantiate this class directly. Create a RACChannel instead. +@interface RACChannelTerminal : RACSignal <RACSubscriber> + +- (id)init __attribute__((unavailable("Instantiate a RACChannel instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.m b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.m new file mode 100644 index 0000000..7222858 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACChannel.m
@@ -0,0 +1,91 @@ +// +// RACChannel.m +// ReactiveCocoa +// +// Created by Uri Baghin on 01/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACChannel.h" +#import "RACDisposable.h" +#import "RACReplaySubject.h" +#import "RACSignal+Operations.h" +#import "RACUnit.h" + +@interface RACChannelTerminal () + +// The values for this terminal. +@property (nonatomic, strong, readonly) RACSignal *values; + +// A subscriber will will send values to the other terminal. +@property (nonatomic, strong, readonly) id<RACSubscriber> otherTerminal; + +- (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal; + +@end + +@implementation RACChannel + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + // We don't want any starting value from the leadingSubject, but we do want + // error and completion to be replayed. + RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"]; + RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"]; + + // Propagate errors and completion to everything. + [[leadingSubject ignoreValues] subscribe:followingSubject]; + [[followingSubject ignoreValues] subscribe:leadingSubject]; + + _leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"]; + _followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"]; + + return self; +} + +@end + +@implementation RACChannelTerminal + +#pragma mark Lifecycle + +- (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal { + NSCParameterAssert(values != nil); + NSCParameterAssert(otherTerminal != nil); + + self = [super init]; + if (self == nil) return nil; + + _values = values; + _otherTerminal = otherTerminal; + + return self; +} + +#pragma mark RACSignal + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + return [self.values subscribe:subscriber]; +} + +#pragma mark <RACSubscriber> + +- (void)sendNext:(id)value { + [self.otherTerminal sendNext:value]; +} + +- (void)sendError:(NSError *)error { + [self.otherTerminal sendError:error]; +} + +- (void)sendCompleted { + [self.otherTerminal sendCompleted]; +} + +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { + [self.otherTerminal didSubscribeWithDisposable:disposable]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h new file mode 100644 index 0000000..653d46b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.h
@@ -0,0 +1,123 @@ +// +// RACCommand.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/3/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +/// The domain for errors originating within `RACCommand`. +extern NSString * const RACCommandErrorDomain; + +/// -execute: was invoked while the command was disabled. +extern const NSInteger RACCommandErrorNotEnabled; + +/// A `userInfo` key for an error, associated with the `RACCommand` that the +/// error originated from. +/// +/// This is included only when the error code is `RACCommandErrorNotEnabled`. +extern NSString * const RACUnderlyingCommandErrorKey; + +/// A command is a signal triggered in response to some action, typically +/// UI-related. +@interface RACCommand : NSObject + +/// A signal of the signals returned by successful invocations of -execute: +/// (i.e., while the receiver is `enabled`). +/// +/// Errors will be automatically caught upon the inner signals, and sent upon +/// `errors` instead. If you _want_ to receive inner errors, use -execute: or +/// -[RACSignal materialize]. +/// +/// Only executions that begin _after_ subscription will be sent upon this +/// signal. All inner signals will arrive upon the main thread. +@property (nonatomic, strong, readonly) RACSignal *executionSignals; + +/// A signal of whether this command is currently executing. +/// +/// This will send YES whenever -execute: is invoked and the created signal has +/// not yet terminated. Once all executions have terminated, `executing` will +/// send NO. +/// +/// This signal will send its current value upon subscription, and then all +/// future values on the main thread. +@property (nonatomic, strong, readonly) RACSignal *executing; + +/// A signal of whether this command is able to execute. +/// +/// This will send NO if: +/// +/// - The command was created with an `enabledSignal`, and NO is sent upon that +/// signal, or +/// - `allowsConcurrentExecution` is NO and the command has started executing. +/// +/// Once the above conditions are no longer met, the signal will send YES. +/// +/// This signal will send its current value upon subscription, and then all +/// future values on the main thread. +@property (nonatomic, strong, readonly) RACSignal *enabled; + +/// Forwards any errors that occur within signals returned by -execute:. +/// +/// When an error occurs on a signal returned from -execute:, this signal will +/// send the associated NSError value as a `next` event (since an `error` event +/// would terminate the stream). +/// +/// After subscription, this signal will send all future errors on the main +/// thread. +@property (nonatomic, strong, readonly) RACSignal *errors; + +/// Whether the command allows multiple executions to proceed concurrently. +/// +/// The default value for this property is NO. +@property (atomic, assign) BOOL allowsConcurrentExecution; + +/// Invokes -initWithEnabled:signalBlock: with a nil `enabledSignal`. +- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock; + +/// Initializes a command that is conditionally enabled. +/// +/// This is the designated initializer for this class. +/// +/// enabledSignal - A signal of BOOLs which indicate whether the command should +/// be enabled. `enabled` will be based on the latest value sent +/// from this signal. Before any values are sent, `enabled` will +/// default to YES. This argument may be nil. +/// signalBlock - A block which will map each input value (passed to -execute:) +/// to a signal of work. The returned signal will be multicasted +/// to a replay subject, sent on `executionSignals`, then +/// subscribed to synchronously. Neither the block nor the +/// returned signal may be nil. +- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock; + +/// If the receiver is enabled, this method will: +/// +/// 1. Invoke the `signalBlock` given at the time of initialization. +/// 2. Multicast the returned signal to a RACReplaySubject. +/// 3. Send the multicasted signal on `executionSignals`. +/// 4. Subscribe (connect) to the original signal on the main thread. +/// +/// input - The input value to pass to the receiver's `signalBlock`. This may be +/// nil. +/// +/// Returns the multicasted signal, after subscription. If the receiver is not +/// enabled, returns a signal that will send an error with code +/// RACCommandErrorNotEnabled. +- (RACSignal *)execute:(id)input; + +@end + +@interface RACCommand (Unavailable) + +@property (atomic, readonly) BOOL canExecute __attribute__((unavailable("Use the 'enabled' signal instead"))); + ++ (instancetype)command __attribute__((unavailable("Use -initWithSignalBlock: instead"))); ++ (instancetype)commandWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead"))); +- (id)initWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead"))); +- (RACSignal *)addSignalBlock:(RACSignal * (^)(id value))signalBlock __attribute__((unavailable("Pass the signalBlock to -initWithSignalBlock: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.m b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.m new file mode 100644 index 0000000..0bc864b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCommand.m
@@ -0,0 +1,268 @@ +// +// RACCommand.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/3/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACCommand.h" +#import "EXTScope.h" +#import "NSArray+RACSequenceAdditions.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACMulticastConnection.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" +#import "RACSequence.h" +#import "RACSerialDisposable.h" +#import "RACSignal+Operations.h" +#import <libkern/OSAtomic.h> + +NSString * const RACCommandErrorDomain = @"RACCommandErrorDomain"; +NSString * const RACUnderlyingCommandErrorKey = @"RACUnderlyingCommandErrorKey"; + +const NSInteger RACCommandErrorNotEnabled = 1; + +@interface RACCommand () { + // The mutable array backing `activeExecutionSignals`. + // + // This should only be used while synchronized on `self`. + NSMutableArray *_activeExecutionSignals; + + // Atomic backing variable for `allowsConcurrentExecution`. + volatile uint32_t _allowsConcurrentExecution; +} + +// An array of signals representing in-flight executions, in the order they +// began. +// +// This property is KVO-compliant. +@property (atomic, copy, readonly) NSArray *activeExecutionSignals; + +// `enabled`, but without a hop to the main thread. +// +// Values from this signal may arrive on any thread. +@property (nonatomic, strong, readonly) RACSignal *immediateEnabled; + +// The signal block that the receiver was initialized with. +@property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input); + +// Adds a signal to `activeExecutionSignals` and generates a KVO notification. +- (void)addActiveExecutionSignal:(RACSignal *)signal; + +// Removes a signal from `activeExecutionSignals` and generates a KVO +// notification. +- (void)removeActiveExecutionSignal:(RACSignal *)signal; + +@end + +@implementation RACCommand + +#pragma mark Properties + +- (BOOL)allowsConcurrentExecution { + return _allowsConcurrentExecution != 0; +} + +- (void)setAllowsConcurrentExecution:(BOOL)allowed { + [self willChangeValueForKey:@keypath(self.allowsConcurrentExecution)]; + + if (allowed) { + OSAtomicOr32Barrier(1, &_allowsConcurrentExecution); + } else { + OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution); + } + + [self didChangeValueForKey:@keypath(self.allowsConcurrentExecution)]; +} + +- (NSArray *)activeExecutionSignals { + @synchronized (self) { + return [_activeExecutionSignals copy]; + } +} + +- (void)addActiveExecutionSignal:(RACSignal *)signal { + NSCParameterAssert([signal isKindOfClass:RACSignal.class]); + + @synchronized (self) { + // The KVO notification has to be generated while synchronized, because + // it depends on the index remaining consistent. + NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:_activeExecutionSignals.count]; + [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + [_activeExecutionSignals addObject:signal]; + [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + } +} + +- (void)removeActiveExecutionSignal:(RACSignal *)signal { + NSCParameterAssert([signal isKindOfClass:RACSignal.class]); + + @synchronized (self) { + // The indexes have to be calculated and the notification generated + // while synchronized, because they depend on the indexes remaining + // consistent. + NSIndexSet *indexes = [_activeExecutionSignals indexesOfObjectsPassingTest:^ BOOL (RACSignal *obj, NSUInteger index, BOOL *stop) { + return obj == signal; + }]; + + if (indexes.count == 0) return; + + [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + [_activeExecutionSignals removeObjectsAtIndexes:indexes]; + [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; + } +} + +#pragma mark Lifecycle + +- (id)init { + NSCAssert(NO, @"Use -initWithSignalBlock: instead"); + return nil; +} + +- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock { + return [self initWithEnabled:nil signalBlock:signalBlock]; +} + +- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock { + NSCParameterAssert(signalBlock != nil); + + self = [super init]; + if (self == nil) return nil; + + _activeExecutionSignals = [[NSMutableArray alloc] init]; + _signalBlock = [signalBlock copy]; + + // A signal of additions to `activeExecutionSignals`. + RACSignal *newActiveExecutionSignals = [[[[[self + rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil] + reduceEach:^(id _, NSDictionary *change) { + NSArray *signals = change[NSKeyValueChangeNewKey]; + if (signals == nil) return [RACSignal empty]; + + return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler]; + }] + concat] + publish] + autoconnect]; + + _executionSignals = [[[newActiveExecutionSignals + map:^(RACSignal *signal) { + return [signal catchTo:[RACSignal empty]]; + }] + deliverOn:RACScheduler.mainThreadScheduler] + setNameWithFormat:@"%@ -executionSignals", self]; + + // `errors` needs to be multicasted so that it picks up all + // `activeExecutionSignals` that are added. + // + // In other words, if someone subscribes to `errors` _after_ an execution + // has started, it should still receive any error from that execution. + RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals + flattenMap:^(RACSignal *signal) { + return [[signal + ignoreValues] + catch:^(NSError *error) { + return [RACSignal return:error]; + }]; + }] + deliverOn:RACScheduler.mainThreadScheduler] + publish]; + + _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self]; + [errorsConnection connect]; + + RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) { + return @(activeSignals.count > 0); + }]; + + _executing = [[[[[immediateExecuting + deliverOn:RACScheduler.mainThreadScheduler] + // This is useful before the first value arrives on the main thread. + startWith:@NO] + distinctUntilChanged] + replayLast] + setNameWithFormat:@"%@ -executing", self]; + + RACSignal *moreExecutionsAllowed = [RACSignal + if:RACObserve(self, allowsConcurrentExecution) + then:[RACSignal return:@YES] + else:[immediateExecuting not]]; + + if (enabledSignal == nil) { + enabledSignal = [RACSignal return:@YES]; + } else { + enabledSignal = [[[enabledSignal + startWith:@YES] + takeUntil:self.rac_willDeallocSignal] + replayLast]; + } + + _immediateEnabled = [[RACSignal + combineLatest:@[ enabledSignal, moreExecutionsAllowed ]] + and]; + + _enabled = [[[[[self.immediateEnabled + take:1] + concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]] + distinctUntilChanged] + replayLast] + setNameWithFormat:@"%@ -enabled", self]; + + return self; +} + +#pragma mark Execution + +- (RACSignal *)execute:(id)input { + // `immediateEnabled` is guaranteed to send a value upon subscription, so + // -first is acceptable here. + BOOL enabled = [[self.immediateEnabled first] boolValue]; + if (!enabled) { + NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{ + NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil), + RACUnderlyingCommandErrorKey: self + }]; + + return [RACSignal error:error]; + } + + RACSignal *signal = self.signalBlock(input); + NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input); + + // We subscribe to the signal on the main thread so that it occurs _after_ + // -addActiveExecutionSignal: completes below. + // + // This means that `executing` and `enabled` will send updated values before + // the signal actually starts performing work. + RACMulticastConnection *connection = [[signal + subscribeOn:RACScheduler.mainThreadScheduler] + multicast:[RACReplaySubject subject]]; + + @weakify(self); + + [self addActiveExecutionSignal:connection.signal]; + [connection.signal subscribeError:^(NSError *error) { + @strongify(self); + [self removeActiveExecutionSignal:connection.signal]; + } completed:^{ + @strongify(self); + [self removeActiveExecutionSignal:connection.signal]; + }]; + + [connection connect]; + return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]]; +} + +#pragma mark NSKeyValueObserving + ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { + // Generate all KVO notifications manually to avoid the performance impact + // of unnecessary swizzling. + return NO; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.h new file mode 100644 index 0000000..bb25f7d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.h
@@ -0,0 +1,48 @@ +// +// RACCompoundDisposable.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" + +/// A disposable of disposables. When it is disposed, it disposes of all its +/// contained disposables. +/// +/// If -addDisposable: is called after the compound disposable has been disposed +/// of, the given disposable is immediately disposed. This allows a compound +/// disposable to act as a stand-in for a disposable that will be delivered +/// asynchronously. +@interface RACCompoundDisposable : RACDisposable + +/// Creates and returns a new compound disposable. ++ (instancetype)compoundDisposable; + +/// Creates and returns a new compound disposable containing the given +/// disposables. ++ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables; + +/// Adds the given disposable. If the receiving disposable has already been +/// disposed of, the given disposable is disposed immediately. +/// +/// This method is thread-safe. +/// +/// disposable - The disposable to add. This may be nil, in which case nothing +/// happens. +- (void)addDisposable:(RACDisposable *)disposable; + +/// Removes the specified disposable from the compound disposable (regardless of +/// its disposed status), or does nothing if it's not in the compound disposable. +/// +/// This is mainly useful for limiting the memory usage of the compound +/// disposable for long-running operations. +/// +/// This method is thread-safe. +/// +/// disposable - The disposable to remove. This argument may be nil (to make the +/// use of weak references easier). +- (void)removeDisposable:(RACDisposable *)disposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.m new file mode 100644 index 0000000..3b0471c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposable.m
@@ -0,0 +1,239 @@ +// +// RACCompoundDisposable.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACCompoundDisposable.h" +#import "RACCompoundDisposableProvider.h" +#import <libkern/OSAtomic.h> + +// The number of child disposables for which space will be reserved directly in +// `RACCompoundDisposable`. +// +// This number has been empirically determined to provide a good tradeoff +// between performance, memory usage, and `RACCompoundDisposable` instance size +// in a moderately complex GUI application. +// +// Profile any change! +#define RACCompoundDisposableInlineCount 2 + +static CFMutableArrayRef RACCreateDisposablesArray(void) { + // Compare values using only pointer equality. + CFArrayCallBacks callbacks = kCFTypeArrayCallBacks; + callbacks.equal = NULL; + + return CFArrayCreateMutable(NULL, 0, &callbacks); +} + +@interface RACCompoundDisposable () { + // Used for synchronization. + OSSpinLock _spinLock; + + #if RACCompoundDisposableInlineCount + // A fast array to the first N of the receiver's disposables. + // + // Once this is full, `_disposables` will be created and used for additional + // disposables. + // + // This array should only be manipulated while _spinLock is held. + RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount]; + #endif + + // Contains the receiver's disposables. + // + // This array should only be manipulated while _spinLock is held. If + // `_disposed` is YES, this may be NULL. + CFMutableArrayRef _disposables; + + // Whether the receiver has already been disposed. + // + // This ivar should only be accessed while _spinLock is held. + BOOL _disposed; +} + +@end + +@implementation RACCompoundDisposable + +#pragma mark Properties + +- (BOOL)isDisposed { + OSSpinLockLock(&_spinLock); + BOOL disposed = _disposed; + OSSpinLockUnlock(&_spinLock); + + return disposed; +} + +#pragma mark Lifecycle + ++ (instancetype)compoundDisposable { + return [[self alloc] initWithDisposables:nil]; +} + ++ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables { + return [[self alloc] initWithDisposables:disposables]; +} + +- (id)initWithDisposables:(NSArray *)otherDisposables { + self = [self init]; + if (self == nil) return nil; + + #if RACCompoundDisposableInlineCount + [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) { + _inlineDisposables[index] = disposable; + + // Stop after this iteration if we've reached the end of the inlined + // array. + if (index == RACCompoundDisposableInlineCount - 1) *stop = YES; + }]; + #endif + + if (otherDisposables.count > RACCompoundDisposableInlineCount) { + _disposables = RACCreateDisposablesArray(); + + CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount); + CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range); + } + + return self; +} + +- (id)initWithBlock:(void (^)(void))block { + RACDisposable *disposable = [RACDisposable disposableWithBlock:block]; + return [self initWithDisposables:@[ disposable ]]; +} + +- (void)dealloc { + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + _inlineDisposables[i] = nil; + } + #endif + + if (_disposables != NULL) { + CFRelease(_disposables); + _disposables = NULL; + } +} + +#pragma mark Addition and Removal + +- (void)addDisposable:(RACDisposable *)disposable { + NSCParameterAssert(disposable != self); + if (disposable == nil) return; + + BOOL shouldDispose = NO; + + OSSpinLockLock(&_spinLock); + { + if (_disposed) { + shouldDispose = YES; + } else { + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + if (_inlineDisposables[i] == nil) { + _inlineDisposables[i] = disposable; + goto foundSlot; + } + } + #endif + + if (_disposables == NULL) _disposables = RACCreateDisposablesArray(); + CFArrayAppendValue(_disposables, (__bridge void *)disposable); + + if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) { + RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); + } + + #if RACCompoundDisposableInlineCount + foundSlot:; + #endif + } + } + OSSpinLockUnlock(&_spinLock); + + // Performed outside of the lock in case the compound disposable is used + // recursively. + if (shouldDispose) [disposable dispose]; +} + +- (void)removeDisposable:(RACDisposable *)disposable { + if (disposable == nil) return; + + OSSpinLockLock(&_spinLock); + { + if (!_disposed) { + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil; + } + #endif + + if (_disposables != NULL) { + CFIndex count = CFArrayGetCount(_disposables); + for (CFIndex i = count - 1; i >= 0; i--) { + const void *item = CFArrayGetValueAtIndex(_disposables, i); + if (item == (__bridge void *)disposable) { + CFArrayRemoveValueAtIndex(_disposables, i); + } + } + + if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) { + RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); + } + } + } + } + OSSpinLockUnlock(&_spinLock); +} + +#pragma mark RACDisposable + +static void disposeEach(const void *value, void *context) { + RACDisposable *disposable = (__bridge id)value; + [disposable dispose]; +} + +- (void)dispose { + #if RACCompoundDisposableInlineCount + RACDisposable *inlineCopy[RACCompoundDisposableInlineCount]; + #endif + + CFArrayRef remainingDisposables = NULL; + + OSSpinLockLock(&_spinLock); + { + _disposed = YES; + + #if RACCompoundDisposableInlineCount + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + inlineCopy[i] = _inlineDisposables[i]; + _inlineDisposables[i] = nil; + } + #endif + + remainingDisposables = _disposables; + _disposables = NULL; + } + OSSpinLockUnlock(&_spinLock); + + #if RACCompoundDisposableInlineCount + // Dispose outside of the lock in case the compound disposable is used + // recursively. + for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { + [inlineCopy[i] dispose]; + } + #endif + + if (remainingDisposables == NULL) return; + + CFIndex count = CFArrayGetCount(remainingDisposables); + CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL); + CFRelease(remainingDisposables); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposableProvider.d b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposableProvider.d new file mode 100644 index 0000000..847db19 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACCompoundDisposableProvider.d
@@ -0,0 +1,4 @@ +provider RACCompoundDisposable { + probe added(char *compoundDisposable, char *disposable, long newTotal); + probe removed(char *compoundDisposable, char *disposable, long newTotal); +};
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.h new file mode 100644 index 0000000..9ec96bd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.h
@@ -0,0 +1,28 @@ +// +// RACDelegateProxy.h +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACSignal; + +// A private delegate object suitable for using +// -rac_signalForSelector:fromProtocol: upon. +@interface RACDelegateProxy : NSObject + +// The delegate to which messages should be forwarded if not handled by +// any -signalForSelector: applications. +@property (nonatomic, unsafe_unretained) id rac_proxiedDelegate; + +// Creates a delegate proxy capable of responding to selectors from `protocol`. +- (instancetype)initWithProtocol:(Protocol *)protocol; + +// Calls -rac_signalForSelector:fromProtocol: using the `protocol` specified +// during initialization. +- (RACSignal *)signalForSelector:(SEL)selector; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.m new file mode 100644 index 0000000..1d4fcbc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDelegateProxy.m
@@ -0,0 +1,78 @@ +// +// RACDelegateProxy.m +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "NSObject+RACSelectorSignal.h" +#import "NSObject+RACDeallocating.h" +#import <objc/runtime.h> + +@interface RACDelegateProxy () { + // Declared as an ivar to avoid method naming conflicts. + Protocol *_protocol; +} + +@end + +@implementation RACDelegateProxy + +#pragma mark Lifecycle + +- (instancetype)initWithProtocol:(Protocol *)protocol { + NSCParameterAssert(protocol != NULL); + + self = [super init]; + if (self == nil) return nil; + + class_addProtocol(self.class, protocol); + + _protocol = protocol; + + return self; +} + +#pragma mark API + +- (RACSignal *)signalForSelector:(SEL)selector { + return [self rac_signalForSelector:selector fromProtocol:_protocol]; +} + +#pragma mark NSObject + +- (BOOL)isProxy { + return YES; +} + +- (void)forwardInvocation:(NSInvocation *)invocation { + [invocation invokeWithTarget:self.rac_proxiedDelegate]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + // Look for the selector as an optional instance method. + struct objc_method_description methodDescription = protocol_getMethodDescription(_protocol, selector, NO, YES); + + if (methodDescription.name == NULL) { + // Then fall back to looking for a required instance + // method. + methodDescription = protocol_getMethodDescription(_protocol, selector, YES, YES); + if (methodDescription.name == NULL) return [super methodSignatureForSelector:selector]; + } + + return [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; +} + +- (BOOL)respondsToSelector:(SEL)selector { + // Add the delegate to the autorelease pool, so it doesn't get deallocated + // between this method call and -forwardInvocation:. + __autoreleasing id delegate = self.rac_proxiedDelegate; + if ([delegate respondsToSelector:selector]) return YES; + + return [super respondsToSelector:selector]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h new file mode 100644 index 0000000..1267d44 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.h
@@ -0,0 +1,36 @@ +// +// RACDisposable.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACScopedDisposable; + + +/// A disposable encapsulates the work necessary to tear down and cleanup a +/// subscription. +@interface RACDisposable : NSObject + +/// Whether the receiver has been disposed. +/// +/// Use of this property is discouraged, since it may be set to `YES` +/// concurrently at any time. +/// +/// This property is not KVO-compliant. +@property (atomic, assign, getter = isDisposed, readonly) BOOL disposed; + ++ (instancetype)disposableWithBlock:(void (^)(void))block; + +/// Performs the disposal work. Can be called multiple times, though subsequent +/// calls won't do anything. +- (void)dispose; + +/// Returns a new disposable which will dispose of this disposable when it gets +/// dealloc'd. +- (RACScopedDisposable *)asScopedDisposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.m new file mode 100644 index 0000000..08eceb2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDisposable.m
@@ -0,0 +1,92 @@ +// +// RACDisposable.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" +#import "RACScopedDisposable.h" +#import <libkern/OSAtomic.h> + +@interface RACDisposable () { + // A copied block of type void (^)(void) containing the logic for disposal, + // a pointer to `self` if no logic should be performed upon disposal, or + // NULL if the receiver is already disposed. + // + // This should only be used atomically. + void * volatile _disposeBlock; +} + +@end + +@implementation RACDisposable + +#pragma mark Properties + +- (BOOL)isDisposed { + return _disposeBlock == NULL; +} + +#pragma mark Lifecycle + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + _disposeBlock = (__bridge void *)self; + OSMemoryBarrier(); + + return self; +} + +- (id)initWithBlock:(void (^)(void))block { + NSCParameterAssert(block != nil); + + self = [super init]; + if (self == nil) return nil; + + _disposeBlock = (void *)CFBridgingRetain([block copy]); + OSMemoryBarrier(); + + return self; +} + ++ (instancetype)disposableWithBlock:(void (^)(void))block { + return [[self alloc] initWithBlock:block]; +} + +- (void)dealloc { + if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return; + + CFRelease(_disposeBlock); + _disposeBlock = NULL; +} + +#pragma mark Disposal + +- (void)dispose { + void (^disposeBlock)(void) = NULL; + + while (YES) { + void *blockPtr = _disposeBlock; + if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) { + if (blockPtr != (__bridge void *)self) { + disposeBlock = CFBridgingRelease(blockPtr); + } + + break; + } + } + + if (disposeBlock != nil) disposeBlock(); +} + +#pragma mark Scoped Disposables + +- (RACScopedDisposable *)asScopedDisposable { + return [RACScopedDisposable scopedDisposableWithDisposable:self]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.h new file mode 100644 index 0000000..1e103e3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.h
@@ -0,0 +1,20 @@ +// +// RACDynamicSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACSequence.h" + +// Private class that implements a sequence dynamically using blocks. +@interface RACDynamicSequence : RACSequence + +// Returns a sequence which evaluates `dependencyBlock` only once, the first +// time either `headBlock` or `tailBlock` is evaluated. The result of +// `dependencyBlock` will be passed into `headBlock` and `tailBlock` when +// invoked. ++ (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.m new file mode 100644 index 0000000..48a977b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSequence.m
@@ -0,0 +1,197 @@ +// +// RACDynamicSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACDynamicSequence.h" +#import <libkern/OSAtomic.h> + +// Determines how RACDynamicSequences will be deallocated before the next one is +// shifted onto the autorelease pool. +// +// This avoids stack overflows when deallocating long chains of dynamic +// sequences. +#define DEALLOC_OVERFLOW_GUARD 100 + +@interface RACDynamicSequence () { + // The value for the "head" property, if it's been evaluated already. + // + // Because it's legal for head to be nil, this ivar is valid any time + // headBlock is nil. + // + // This ivar should only be accessed while synchronized on self. + id _head; + + // The value for the "tail" property, if it's been evaluated already. + // + // Because it's legal for tail to be nil, this ivar is valid any time + // tailBlock is nil. + // + // This ivar should only be accessed while synchronized on self. + RACSequence *_tail; + + // The result of an evaluated `dependencyBlock`. + // + // This ivar is valid any time `hasDependency` is YES and `dependencyBlock` + // is nil. + // + // This ivar should only be accessed while synchronized on self. + id _dependency; +} + +// A block used to evaluate head. This should be set to nil after `_head` has been +// initialized. +// +// This is marked `strong` instead of `copy` because of some bizarre block +// copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. +// +// The signature of this block varies based on the value of `hasDependency`: +// +// - If YES, this block is of type `id (^)(id)`. +// - If NO, this block is of type `id (^)(void)`. +// +// This property should only be accessed while synchronized on self. +@property (nonatomic, strong) id headBlock; + +// A block used to evaluate tail. This should be set to nil after `_tail` has been +// initialized. +// +// This is marked `strong` instead of `copy` because of some bizarre block +// copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. +// +// The signature of this block varies based on the value of `hasDependency`: +// +// - If YES, this block is of type `RACSequence * (^)(id)`. +// - If NO, this block is of type `RACSequence * (^)(void)`. +// +// This property should only be accessed while synchronized on self. +@property (nonatomic, strong) id tailBlock; + +// Whether the receiver was initialized with a `dependencyBlock`. +// +// This property should only be accessed while synchronized on self. +@property (nonatomic, assign) BOOL hasDependency; + +// A dependency which must be evaluated before `headBlock` and `tailBlock`. This +// should be set to nil after `_dependency` and `dependencyBlockExecuted` have +// been set. +// +// This is marked `strong` instead of `copy` because of some bizarre block +// copying bug. See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/506. +// +// This property should only be accessed while synchronized on self. +@property (nonatomic, strong) id (^dependencyBlock)(void); + +@end + +@implementation RACDynamicSequence + +#pragma mark Lifecycle + ++ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock { + NSCParameterAssert(headBlock != nil); + + RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; + seq.headBlock = [headBlock copy]; + seq.tailBlock = [tailBlock copy]; + seq.hasDependency = NO; + return seq; +} + ++ (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock { + NSCParameterAssert(dependencyBlock != nil); + NSCParameterAssert(headBlock != nil); + + RACDynamicSequence *seq = [[RACDynamicSequence alloc] init]; + seq.headBlock = [headBlock copy]; + seq.tailBlock = [tailBlock copy]; + seq.dependencyBlock = [dependencyBlock copy]; + seq.hasDependency = YES; + return seq; +} + +- (void)dealloc { + static volatile int32_t directDeallocCount = 0; + + if (OSAtomicIncrement32(&directDeallocCount) >= DEALLOC_OVERFLOW_GUARD) { + OSAtomicAdd32(-DEALLOC_OVERFLOW_GUARD, &directDeallocCount); + + // Put this sequence's tail onto the autorelease pool so we stop + // recursing. + __autoreleasing RACSequence *tail __attribute__((unused)) = _tail; + } + + _tail = nil; +} + +#pragma mark RACSequence + +- (id)head { + @synchronized (self) { + id untypedHeadBlock = self.headBlock; + if (untypedHeadBlock == nil) return _head; + + if (self.hasDependency) { + if (self.dependencyBlock != nil) { + _dependency = self.dependencyBlock(); + self.dependencyBlock = nil; + } + + id (^headBlock)(id) = untypedHeadBlock; + _head = headBlock(_dependency); + } else { + id (^headBlock)(void) = untypedHeadBlock; + _head = headBlock(); + } + + self.headBlock = nil; + return _head; + } +} + +- (RACSequence *)tail { + @synchronized (self) { + id untypedTailBlock = self.tailBlock; + if (untypedTailBlock == nil) return _tail; + + if (self.hasDependency) { + if (self.dependencyBlock != nil) { + _dependency = self.dependencyBlock(); + self.dependencyBlock = nil; + } + + RACSequence * (^tailBlock)(id) = untypedTailBlock; + _tail = tailBlock(_dependency); + } else { + RACSequence * (^tailBlock)(void) = untypedTailBlock; + _tail = tailBlock(); + } + + if (_tail.name == nil) _tail.name = self.name; + + self.tailBlock = nil; + return _tail; + } +} + +#pragma mark NSObject + +- (NSString *)description { + id head = @"(unresolved)"; + id tail = @"(unresolved)"; + + @synchronized (self) { + if (self.headBlock == nil) head = _head; + if (self.tailBlock == nil) { + tail = _tail; + if (tail == self) tail = @"(self)"; + } + } + + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@, tail = %@ }", self.class, self, self.name, head, tail]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.h new file mode 100644 index 0000000..81ac6db --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.h
@@ -0,0 +1,17 @@ +// +// RACDynamicSignal.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" + +// A private `RACSignal` subclasses that implements its subscription behavior +// using a block. +@interface RACDynamicSignal : RACSignal + ++ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.m new file mode 100644 index 0000000..7a8ec32 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACDynamicSignal.m
@@ -0,0 +1,189 @@ +// +// RACDynamicSignal.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACDynamicSignal.h" +#import "EXTScope.h" +#import "RACPassthroughSubscriber.h" +#import "RACScheduler+Private.h" +#import "RACSubscriber.h" +#import "RACCompoundDisposable.h" +#import <libkern/OSAtomic.h> + +// Retains dynamic signals while they wait for subscriptions. +// +// This set must only be used on the main thread. +static CFMutableSetRef RACActiveSignals = nil; + +// A linked list of RACDynamicSignals, used in RACActiveSignalsToCheck. +typedef struct RACSignalList { + CFTypeRef retainedSignal; + struct RACSignalList * restrict next; +} RACSignalList; + +// An atomic queue of signals to check for subscribers. If any signals with zero +// subscribers are found in this queue, they are removed from RACActiveSignals. +static OSQueueHead RACActiveSignalsToCheck = OS_ATOMIC_QUEUE_INIT; + +// Whether RACActiveSignalsToCheck will be enumerated on the next iteration on +// the main run loop. +static volatile uint32_t RACWillCheckActiveSignals = 0; + +@interface RACDynamicSignal () { + // Contains all subscribers to the receiver. + // + // All access to this array must be synchronized using `_subscribersLock`. + NSMutableArray *_subscribers; + + // Synchronizes access to `_subscribers`. + OSSpinLock _subscribersLock; +} + +// The block to invoke for each subscriber. +@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber); + +@end + +@implementation RACDynamicSignal + +#pragma mark Lifecycle + ++ (void)initialize { + if (self != RACDynamicSignal.class) return; + + CFSetCallBacks callbacks = kCFTypeSetCallBacks; + + // Use pointer equality and hashes for membership testing. + callbacks.equal = NULL; + callbacks.hash = NULL; + + RACActiveSignals = CFSetCreateMutable(NULL, 0, &callbacks); +} + ++ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { + RACDynamicSignal *signal = [[self alloc] init]; + signal->_didSubscribe = [didSubscribe copy]; + return [signal setNameWithFormat:@"+createSignal:"]; +} + +- (instancetype)init { + self = [super init]; + if (self == nil) return nil; + + // As soon as we're created we're already trying to be released. Such is life. + [self invalidateGlobalRefIfNoNewSubscribersShowUp]; + + return self; +} + +static void RACCheckActiveSignals(void) { + // Clear this flag now, so another thread can re-dispatch to the main queue + // as needed. + OSAtomicAnd32Barrier(0, &RACWillCheckActiveSignals); + + RACSignalList * restrict elem; + + while ((elem = OSAtomicDequeue(&RACActiveSignalsToCheck, offsetof(RACSignalList, next))) != NULL) { + RACDynamicSignal *signal = CFBridgingRelease(elem->retainedSignal); + free(elem); + + if (signal.hasSubscribers) { + // We want to keep the signal around until all its subscribers are done + CFSetAddValue(RACActiveSignals, (__bridge void *)signal); + } else { + CFSetRemoveValue(RACActiveSignals, (__bridge void *)signal); + } + } +} + +- (void)invalidateGlobalRefIfNoNewSubscribersShowUp { + // If no one subscribes in one pass of the main run loop, then we're free to + // go. It's up to the caller to keep us alive if they still want us. + RACSignalList *elem = malloc(sizeof(*elem)); + + // This also serves to retain the signal until the next pass. + elem->retainedSignal = CFBridgingRetain(self); + OSAtomicEnqueue(&RACActiveSignalsToCheck, elem, offsetof(RACSignalList, next)); + + // Not using a barrier because duplicate scheduling isn't erroneous, just + // less optimized. + int32_t willCheck = OSAtomicOr32Orig(1, &RACWillCheckActiveSignals); + + // Only schedule a check if RACWillCheckActiveSignals was 0 before. + if (willCheck == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + RACCheckActiveSignals(); + }); + } +} + +#pragma mark Managing Subscribers + +- (BOOL)hasSubscribers { + OSSpinLockLock(&_subscribersLock); + BOOL hasSubscribers = _subscribers.count > 0; + OSSpinLockUnlock(&_subscribersLock); + + return hasSubscribers; +} + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCParameterAssert(subscriber != nil); + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; + + OSSpinLockLock(&_subscribersLock); + if (_subscribers == nil) { + _subscribers = [NSMutableArray arrayWithObject:subscriber]; + } else { + [_subscribers addObject:subscriber]; + } + OSSpinLockUnlock(&_subscribersLock); + + @weakify(self); + RACDisposable *defaultDisposable = [RACDisposable disposableWithBlock:^{ + @strongify(self); + if (self == nil) return; + + BOOL stillHasSubscribers = YES; + + OSSpinLockLock(&_subscribersLock); + { + // Since newer subscribers are generally shorter-lived, search + // starting from the end of the list. + NSUInteger index = [_subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) { + return obj == subscriber; + }]; + + if (index != NSNotFound) { + [_subscribers removeObjectAtIndex:index]; + stillHasSubscribers = _subscribers.count > 0; + } + } + OSSpinLockUnlock(&_subscribersLock); + + if (!stillHasSubscribers) { + [self invalidateGlobalRefIfNoNewSubscribersShowUp]; + } + }]; + + [disposable addDisposable:defaultDisposable]; + + if (self.didSubscribe != NULL) { + RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ + RACDisposable *innerDisposable = self.didSubscribe(subscriber); + [disposable addDisposable:innerDisposable]; + }]; + + [disposable addDisposable:schedulingDisposable]; + } + + return disposable; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.h new file mode 100644 index 0000000..ab5aa9f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.h
@@ -0,0 +1,14 @@ +// +// RACEagerSequence.h +// ReactiveCocoa +// +// Created by Uri Baghin on 02/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACArraySequence.h" + +// Private class that implements an eager sequence. +@interface RACEagerSequence : RACArraySequence + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.m new file mode 100644 index 0000000..f12fbe9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEagerSequence.m
@@ -0,0 +1,66 @@ +// +// RACEagerSequence.m +// ReactiveCocoa +// +// Created by Uri Baghin on 02/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACEagerSequence.h" +#import "NSObject+RACDescription.h" +#import "RACArraySequence.h" + +@implementation RACEagerSequence + +#pragma mark RACStream + ++ (instancetype)return:(id)value { + return [[self sequenceWithArray:@[ value ] offset:0] setNameWithFormat:@"+return: %@", [value rac_description]]; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(void))block { + NSCParameterAssert(block != nil); + RACStreamBindBlock bindBlock = block(); + NSArray *currentArray = self.array; + NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count]; + + for (id value in currentArray) { + BOOL stop = NO; + RACSequence *boundValue = (id)bindBlock(value, &stop); + if (boundValue == nil) break; + + for (id x in boundValue) { + [resultArray addObject:x]; + } + + if (stop) break; + } + + return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name]; +} + +- (instancetype)concat:(RACSequence *)sequence { + NSCParameterAssert(sequence != nil); + NSCParameterAssert([sequence isKindOfClass:RACSequence.class]); + + NSArray *array = [self.array arrayByAddingObjectsFromArray:sequence.array]; + return [[self.class sequenceWithArray:array offset:0] setNameWithFormat:@"[%@] -concat: %@", self.name, sequence]; +} + +#pragma mark Extended methods + +- (RACSequence *)eagerSequence { + return self; +} + +- (RACSequence *)lazySequence { + return [RACArraySequence sequenceWithArray:self.array offset:0]; +} + +- (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *rest))reduce { + return [super foldRightWithStart:start reduce:^(id first, RACSequence *rest) { + return reduce(first, rest.eagerSequence); + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.h new file mode 100644 index 0000000..140c78b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.h
@@ -0,0 +1,14 @@ +// +// RACEmptySequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACSequence.h" + +// Private class representing an empty sequence. +@interface RACEmptySequence : RACSequence + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.m new file mode 100644 index 0000000..e4df150 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySequence.m
@@ -0,0 +1,71 @@ +// +// RACEmptySequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACEmptySequence.h" + +@implementation RACEmptySequence + +#pragma mark Lifecycle + ++ (instancetype)empty { + static id singleton; + static dispatch_once_t pred; + + dispatch_once(&pred, ^{ + singleton = [[self alloc] init]; + }); + + return singleton; +} + +#pragma mark RACSequence + +- (id)head { + return nil; +} + +- (RACSequence *)tail { + return nil; +} + +- (RACSequence *)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence { + return passthroughSequence ?: self; +} + +#pragma mark NSCoding + +- (Class)classForCoder { + // Empty sequences should be encoded as themselves, not array sequences. + return self.class; +} + +- (id)initWithCoder:(NSCoder *)coder { + // Return the singleton. + return self.class.empty; +} + +- (void)encodeWithCoder:(NSCoder *)coder { +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@ }", self.class, self, self.name]; +} + +- (NSUInteger)hash { + // This hash isn't ideal, but it's better than -[RACSequence hash], which + // would just be zero because we have no head. + return (NSUInteger)(__bridge void *)self; +} + +- (BOOL)isEqual:(RACSequence *)seq { + return (self == seq); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.h new file mode 100644 index 0000000..90b4f00 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.h
@@ -0,0 +1,17 @@ +// +// RACEmptySignal.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" + +// A private `RACSignal` subclasses that synchronously sends completed to any +// subscribers. +@interface RACEmptySignal : RACSignal + ++ (RACSignal *)empty; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.m new file mode 100644 index 0000000..8bc8f41 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEmptySignal.m
@@ -0,0 +1,62 @@ +// +// RACEmptySignal.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACEmptySignal.h" +#import "RACScheduler+Private.h" +#import "RACSubscriber.h" + +@implementation RACEmptySignal + +#pragma mark Properties + +// Only allow this signal's name to be customized in DEBUG, since it's +// a singleton in release builds (see +empty). +- (void)setName:(NSString *)name { +#ifdef DEBUG + [super setName:name]; +#endif +} + +- (NSString *)name { +#ifdef DEBUG + return super.name; +#else + return @"+empty"; +#endif +} + +#pragma mark Lifecycle + ++ (RACSignal *)empty { +#ifdef DEBUG + // Create multiple instances of this class in DEBUG so users can set custom + // names on each. + return [[[self alloc] init] setNameWithFormat:@"+empty"]; +#else + static id singleton; + static dispatch_once_t pred; + + dispatch_once(&pred, ^{ + singleton = [[self alloc] init]; + }); + + return singleton; +#endif +} + +#pragma mark Subscription + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCParameterAssert(subscriber != nil); + + return [RACScheduler.subscriptionScheduler schedule:^{ + [subscriber sendCompleted]; + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.h new file mode 100644 index 0000000..c942f9e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.h
@@ -0,0 +1,17 @@ +// +// RACErrorSignal.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" + +// A private `RACSignal` subclasses that synchronously sends an error to any +// subscribers. +@interface RACErrorSignal : RACSignal + ++ (RACSignal *)error:(NSError *)error; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.m new file mode 100644 index 0000000..fd6778c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACErrorSignal.m
@@ -0,0 +1,47 @@ +// +// RACErrorSignal.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACErrorSignal.h" +#import "RACScheduler+Private.h" +#import "RACSubscriber.h" + +@interface RACErrorSignal () + +// The error to send upon subscription. +@property (nonatomic, strong, readonly) NSError *error; + +@end + +@implementation RACErrorSignal + +#pragma mark Lifecycle + ++ (RACSignal *)error:(NSError *)error { + RACErrorSignal *signal = [[self alloc] init]; + signal->_error = error; + +#ifdef DEBUG + [signal setNameWithFormat:@"+error: %@", error]; +#else + signal.name = @"+error:"; +#endif + + return signal; +} + +#pragma mark Subscription + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCParameterAssert(subscriber != nil); + + return [RACScheduler.subscriptionScheduler schedule:^{ + [subscriber sendError:self.error]; + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h new file mode 100644 index 0000000..9e64e5a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.h
@@ -0,0 +1,51 @@ +// +// RACEvent.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-01-07. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +/// Describes the type of a RACEvent. +/// +/// RACEventTypeCompleted - A `completed` event. +/// RACEventTypeError - An `error` event. +/// RACEventTypeNext - A `next` event. +typedef enum : NSUInteger { + RACEventTypeCompleted, + RACEventTypeError, + RACEventTypeNext +} RACEventType; + +/// Represents an event sent by a RACSignal. +/// +/// This corresponds to the `Notification` class in Rx. +@interface RACEvent : NSObject <NSCopying> + +/// Returns a singleton RACEvent representing the `completed` event. ++ (instancetype)completedEvent; + +/// Returns a new event of type RACEventTypeError, containing the given error. ++ (instancetype)eventWithError:(NSError *)error; + +/// Returns a new event of type RACEventTypeNext, containing the given value. ++ (instancetype)eventWithValue:(id)value; + +/// The type of event represented by the receiver. +@property (nonatomic, assign, readonly) RACEventType eventType; + +/// Returns whether the receiver is of type RACEventTypeCompleted or +/// RACEventTypeError. +@property (nonatomic, getter = isFinished, assign, readonly) BOOL finished; + +/// The error associated with an event of type RACEventTypeError. This will be +/// nil for all other event types. +@property (nonatomic, strong, readonly) NSError *error; + +/// The value associated with an event of type RACEventTypeNext. This will be +/// nil for all other event types. +@property (nonatomic, strong, readonly) id value; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.m b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.m new file mode 100644 index 0000000..270a95d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACEvent.m
@@ -0,0 +1,113 @@ +// +// RACEvent.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-01-07. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACEvent.h" + +@interface RACEvent () + +// An object associated with this event. This will be used for the error and +// value properties. +@property (nonatomic, strong, readonly) id object; + +// Initializes the receiver with the given type and object. +- (id)initWithEventType:(RACEventType)type object:(id)object; + +@end + +@implementation RACEvent + +#pragma mark Properties + +- (BOOL)isFinished { + return self.eventType == RACEventTypeCompleted || self.eventType == RACEventTypeError; +} + +- (NSError *)error { + return (self.eventType == RACEventTypeError ? self.object : nil); +} + +- (id)value { + return (self.eventType == RACEventTypeNext ? self.object : nil); +} + +#pragma mark Lifecycle + ++ (instancetype)completedEvent { + static dispatch_once_t pred; + static id singleton; + + dispatch_once(&pred, ^{ + singleton = [[self alloc] initWithEventType:RACEventTypeCompleted object:nil]; + }); + + return singleton; +} + ++ (instancetype)eventWithError:(NSError *)error { + return [[self alloc] initWithEventType:RACEventTypeError object:error]; +} + ++ (instancetype)eventWithValue:(id)value { + return [[self alloc] initWithEventType:RACEventTypeNext object:value]; +} + +- (id)initWithEventType:(RACEventType)type object:(id)object { + self = [super init]; + if (self == nil) return nil; + + _eventType = type; + _object = object; + + return self; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +#pragma mark NSObject + +- (NSString *)description { + NSString *eventDescription = nil; + + switch (self.eventType) { + case RACEventTypeCompleted: + eventDescription = @"completed"; + break; + + case RACEventTypeError: + eventDescription = [NSString stringWithFormat:@"error = %@", self.object]; + break; + + case RACEventTypeNext: + eventDescription = [NSString stringWithFormat:@"next = %@", self.object]; + break; + + default: + NSCAssert(NO, @"Unrecognized event type: %i", (int)self.eventType); + } + + return [NSString stringWithFormat:@"<%@: %p>{ %@ }", self.class, self, eventDescription]; +} + +- (NSUInteger)hash { + return self.eventType ^ [self.object hash]; +} + +- (BOOL)isEqual:(id)event { + if (event == self) return YES; + if (![event isKindOfClass:RACEvent.class]) return NO; + if (self.eventType != [event eventType]) return NO; + + // Catches the nil case too. + return self.object == [event object] || [self.object isEqual:[event object]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.h new file mode 100644 index 0000000..697d922 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.h
@@ -0,0 +1,20 @@ +// +// RACGroupedSignal.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" + + +/// A grouped signal is used by -[RACSignal groupBy:transform:]. +@interface RACGroupedSignal : RACSubject + +/// The key shared by the group. +@property (nonatomic, readonly, copy) id<NSCopying> key; + ++ (instancetype)signalWithKey:(id<NSCopying>)key; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.m new file mode 100644 index 0000000..00e0378 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACGroupedSignal.m
@@ -0,0 +1,25 @@ +// +// RACGroupedSignal.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACGroupedSignal.h" + +@interface RACGroupedSignal () +@property (nonatomic, copy) id<NSCopying> key; +@end + +@implementation RACGroupedSignal + +#pragma mark API + ++ (instancetype)signalWithKey:(id<NSCopying>)key { + RACGroupedSignal *subject = [self subject]; + subject.key = key; + return subject; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.h new file mode 100644 index 0000000..76b5b50 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.h
@@ -0,0 +1,14 @@ +// +// RACImmediateScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +// A private scheduler which immediately executes its scheduled blocks. +@interface RACImmediateScheduler : RACScheduler + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.m new file mode 100644 index 0000000..f32eda3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACImmediateScheduler.m
@@ -0,0 +1,44 @@ +// +// RACImmediateScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACImmediateScheduler.h" +#import "RACScheduler+Private.h" + +@implementation RACImmediateScheduler + +#pragma mark Lifecycle + +- (id)init { + return [super initWithName:@"com.ReactiveCocoa.RACScheduler.immediateScheduler"]; +} + +#pragma mark RACScheduler + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + block(); + return nil; +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != NULL); + + [NSThread sleepUntilDate:date]; + block(); + + return nil; +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + NSCAssert(NO, @"+[RACScheduler immediateScheduler] does not support %@.", NSStringFromSelector(_cmd)); + return nil; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.h new file mode 100644 index 0000000..eab84a3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.h
@@ -0,0 +1,16 @@ +// +// RACIndexSetSequence.h +// ReactiveCocoa +// +// Created by Sergey Gavrilyuk on 12/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSequence.h" + +// Private class that adapts an array to the RACSequence interface. +@interface RACIndexSetSequence : RACSequence + ++ (instancetype)sequenceWithIndexSet:(NSIndexSet *)indexSet; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.m new file mode 100644 index 0000000..7ac8f53 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACIndexSetSequence.m
@@ -0,0 +1,108 @@ +// +// RACIndexSetSequence.m +// ReactiveCocoa +// +// Created by Sergey Gavrilyuk on 12/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACIndexSetSequence.h" + +@interface RACIndexSetSequence () + +// A buffer holding the `NSUInteger` values to enumerate over. +// +// This is mostly used for memory management. Most access should go through +// `indexes` instead. +@property (nonatomic, strong, readonly) NSData *data; + +// The indexes that this sequence should enumerate. +@property (nonatomic, readonly) const NSUInteger *indexes; + +// The number of indexes to enumerate. +@property (nonatomic, readonly) NSUInteger count; + +@end + +@implementation RACIndexSetSequence + +#pragma mark Lifecycle + ++ (instancetype)sequenceWithIndexSet:(NSIndexSet *)indexSet { + NSUInteger count = indexSet.count; + NSUInteger sizeInBytes = sizeof(NSUInteger) * count; + + NSMutableData *data = [[NSMutableData alloc] initWithCapacity:sizeInBytes]; + [indexSet getIndexes:data.mutableBytes maxCount:count inIndexRange:NULL]; + + RACIndexSetSequence *seq = [[self alloc] init]; + seq->_data = data; + seq->_indexes = data.bytes; + seq->_count = count; + return seq; +} + ++ (instancetype)sequenceWithIndexSetSequence:(RACIndexSetSequence *)indexSetSequence offset:(NSUInteger)offset { + NSCParameterAssert(offset < indexSetSequence.count); + + RACIndexSetSequence *seq = [[self alloc] init]; + seq->_data = indexSetSequence.data; + seq->_indexes = indexSetSequence.indexes + offset; + seq->_count = indexSetSequence.count - offset; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + return @(self.indexes[0]); +} + +- (RACSequence *)tail { + if (self.count <= 1) return [RACSequence empty]; + + return [self.class sequenceWithIndexSetSequence:self offset:1]; +} + +#pragma mark NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len { + NSCParameterAssert(len > 0); + + if (state->state >= self.count) { + // Enumeration has completed. + return 0; + } + + if (state->state == 0) { + // Enumeration begun, mark the mutation flag. + state->mutationsPtr = state->extra; + } + + state->itemsPtr = stackbuf; + + unsigned long index = 0; + while (index < MIN(self.count - state->state, len)) { + stackbuf[index] = @(self.indexes[index + state->state]); + ++index; + } + + state->state += index; + return index; +} + +#pragma mark NSObject + +- (NSString *)description { + NSMutableString *indexesStr = [NSMutableString string]; + + for (unsigned int i = 0; i < self.count; ++i) { + if (i > 0) [indexesStr appendString:@", "]; + + [indexesStr appendFormat:@"%lu", (unsigned long)self.indexes[i]]; + } + + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, indexes = %@ }", self.class, self, self.name, indexesStr]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.h b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.h new file mode 100644 index 0000000..0f72995 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.h
@@ -0,0 +1,97 @@ +// +// RACKVOChannel.h +// ReactiveCocoa +// +// Created by Uri Baghin on 27/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACChannel.h" +#import "EXTKeyPathCoding.h" +#import "metamacros.h" + +/// Creates a RACKVOChannel to the given key path. When the targeted object +/// deallocates, the channel will complete. +/// +/// If RACChannelTo() is used as an expression, it returns a RACChannelTerminal that +/// can be used to watch the specified property for changes, and set new values +/// for it. The terminal will start with the property's current value upon +/// subscription. +/// +/// If RACChannelTo() is used on the left-hand side of an assignment, there must a +/// RACChannelTerminal on the right-hand side of the assignment. The two will be +/// subscribed to one another: the property's value is immediately set to the +/// value of the channel terminal on the right-hand side, and subsequent changes +/// to either terminal will be reflected on the other. +/// +/// There are two different versions of this macro: +/// +/// - RACChannelTo(TARGET, KEYPATH, NILVALUE) will create a channel to the `KEYPATH` +/// of `TARGET`. If the terminal is ever sent a `nil` value, the property will +/// be set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object +/// properties, but an NSValue should be used for primitive properties, to +/// avoid an exception if `nil` is sent (which might occur if an intermediate +/// object is set to `nil`). +/// - RACChannelTo(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to +/// `nil`. +/// +/// Examples +/// +/// RACChannelTerminal *integerChannel = RACChannelTo(self, integerProperty, @42); +/// +/// // Sets self.integerProperty to 5. +/// [integerChannel sendNext:@5]; +/// +/// // Logs the current value of self.integerProperty, and all future changes. +/// [integerChannel subscribeNext:^(id value) { +/// NSLog(@"value: %@", value); +/// }]; +/// +/// // Binds properties to each other, taking the initial value from the right +/// side. +/// RACChannelTo(view, objectProperty) = RACChannelTo(model, objectProperty); +/// RACChannelTo(view, integerProperty, @2) = RACChannelTo(model, integerProperty, @10); +#define RACChannelTo(TARGET, ...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ + (RACChannelTo_(TARGET, __VA_ARGS__, nil)) \ + (RACChannelTo_(TARGET, __VA_ARGS__)) + +/// Do not use this directly. Use the RACChannelTo macro above. +#define RACChannelTo_(TARGET, KEYPATH, NILVALUE) \ + [[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)] + +/// A RACChannel that observes a KVO-compliant key path for changes. +@interface RACKVOChannel : RACChannel + +/// Initializes a channel that will observe the given object and key path. +/// +/// The current value of the key path, and future KVO notifications for the given +/// key path, will be sent to subscribers of the channel's `followingTerminal`. +/// Values sent to the `followingTerminal` will be set at the given key path using +/// key-value coding. +/// +/// When the target object deallocates, the channel will complete. Signal errors +/// are considered undefined behavior. +/// +/// This is the designated initializer for this class. +/// +/// target - The object to bind to. +/// keyPath - The key path to observe and set the value of. +/// nilValue - The value to set at the key path whenever a `nil` value is +/// received. This may be nil when connecting to object properties, but +/// an NSValue should be used for primitive properties, to avoid an +/// exception if `nil` is received (which might occur if an intermediate +/// object is set to `nil`). +- (id)initWithTarget:(NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue; + +- (id)init __attribute__((unavailable("Use -initWithTarget:keyPath:nilValue: instead"))); + +@end + +/// Methods needed for the convenience macro. Do not call explicitly. +@interface RACKVOChannel (RACChannelTo) + +- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key; +- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.m b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.m new file mode 100644 index 0000000..7292a74 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOChannel.m
@@ -0,0 +1,206 @@ +// +// RACKVOChannel.m +// ReactiveCocoa +// +// Created by Uri Baghin on 27/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACKVOChannel.h" +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACKVOWrapper.h" +#import "NSObject+RACPropertySubscribing.h" +#import "NSString+RACKeyPathUtilities.h" +#import "RACChannel.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACReplaySubject.h" +#import "RACSignal+Operations.h" +#import "RACSubscriber+Private.h" +#import "RACSubject.h" + +// Key for the array of RACKVOChannel's additional thread local +// data in the thread dictionary. +static NSString * const RACKVOChannelDataDictionaryKey = @"RACKVOChannelKey"; + +// Wrapper class for additional thread local data. +@interface RACKVOChannelData : NSObject + +// The flag used to ignore updates the channel itself has triggered. +@property (nonatomic, assign) BOOL ignoreNextUpdate; + +// A pointer to the owner of the data. Only use this for pointer comparison, +// never as an object reference. +@property (nonatomic, assign) void *owner; + ++ (instancetype)dataForChannel:(RACKVOChannel *)channel; + +@end + +@interface RACKVOChannel () + +// The object whose key path the channel is wrapping. +@property (atomic, unsafe_unretained) NSObject *target; + +// The key path the channel is wrapping. +@property (nonatomic, copy, readonly) NSString *keyPath; + +// Returns the existing thread local data container or nil if none exists. +@property (nonatomic, strong, readonly) RACKVOChannelData *currentThreadData; + +// Creates the thread local data container for the channel. +- (void)createCurrentThreadData; + +// Destroy the thread local data container for the channel. +- (void)destroyCurrentThreadData; + +@end + +@implementation RACKVOChannel + +#pragma mark Properties + +- (RACKVOChannelData *)currentThreadData { + NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; + + for (RACKVOChannelData *data in dataArray) { + if (data.owner == (__bridge void *)self) return data; + } + + return nil; +} + +#pragma mark Lifecycle + +- (id)initWithTarget:(NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue { + NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); + + self = [super init]; + if (self == nil) return nil; + + _target = target; + _keyPath = [keyPath copy]; + + [self.leadingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -leadingTerminal", target, keyPath, nilValue]; + [self.followingTerminal setNameWithFormat:@"[-initWithTarget: %@ keyPath: %@ nilValue: %@] -followingTerminal", target, keyPath, nilValue]; + + // Observe the key path on target for changes and forward the changes to the + // terminal. + // + // Intentionally capturing `self` strongly in the blocks below, so the + // channel object stays alive while observing. + RACDisposable *observationDisposable = [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change) { + // If the change wasn't triggered by deallocation, only affects the last + // path component, and ignoreNextUpdate is set, then it was triggered by + // this channel and should not be forwarded. + if (![change[RACKeyValueChangeCausedByDeallocationKey] boolValue] && [change[RACKeyValueChangeAffectedOnlyLastComponentKey] boolValue] && self.currentThreadData.ignoreNextUpdate) { + [self destroyCurrentThreadData]; + return; + } + + [self.leadingTerminal sendNext:value]; + }]; + + NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent; + NSArray *keyPathComponents = keyPath.rac_keyPathComponents; + NSUInteger keyPathComponentsCount = keyPathComponents.count; + NSString *lastKeyPathComponent = keyPathComponents.lastObject; + + // Update the value of the property with the values received. + [[self.leadingTerminal + finally:^{ + [observationDisposable dispose]; + }] + subscribeNext:^(id x) { + // Check the value of the second to last key path component. Since the + // channel can only update the value of a property on an object, and not + // update intermediate objects, it can only update the value of the whole + // key path if this object is not nil. + NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target); + if (object == nil) return; + + // Set the ignoreNextUpdate flag before setting the value so this channel + // ignores the value in the subsequent -didChangeValueForKey: callback. + [self createCurrentThreadData]; + self.currentThreadData.ignoreNextUpdate = YES; + + [object setValue:x ?: nilValue forKey:lastKeyPathComponent]; + } error:^(NSError *error) { + NSCAssert(NO, @"Received error in %@: %@", self, error); + + // Log the error if we're running with assertions disabled. + NSLog(@"Received error in %@: %@", self, error); + }]; + + // Capture `self` weakly for the target's deallocation disposable, so we can + // freely deallocate if we complete before then. + @weakify(self); + + [target.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + @strongify(self); + [self.leadingTerminal sendCompleted]; + self.target = nil; + }]]; + + return self; +} + +- (void)createCurrentThreadData { + NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; + if (dataArray == nil) { + dataArray = [NSMutableArray array]; + NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey] = dataArray; + [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; + return; + } + + for (RACKVOChannelData *data in dataArray) { + if (data.owner == (__bridge void *)self) return; + } + + [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; +} + +- (void)destroyCurrentThreadData { + NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; + NSUInteger index = [dataArray indexOfObjectPassingTest:^ BOOL (RACKVOChannelData *data, NSUInteger idx, BOOL *stop) { + return data.owner == (__bridge void *)self; + }]; + + if (index != NSNotFound) [dataArray removeObjectAtIndex:index]; +} + +@end + +@implementation RACKVOChannel (RACChannelTo) + +- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key { + NSCParameterAssert(key != nil); + + RACChannelTerminal *terminal = [self valueForKey:key]; + NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key); + + return terminal; +} + +- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key { + NSCParameterAssert(otherTerminal != nil); + + RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key]; + [otherTerminal subscribe:selfTerminal]; + [[selfTerminal skip:1] subscribe:otherTerminal]; +} + +@end + +@implementation RACKVOChannelData + ++ (instancetype)dataForChannel:(RACKVOChannel *)channel { + RACKVOChannelData *data = [[self alloc] init]; + data->_owner = (__bridge void *)channel; + return data; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.h b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.h new file mode 100644 index 0000000..5701486 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.h
@@ -0,0 +1,31 @@ +// +// RACKVOTrampoline.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 1/15/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "NSObject+RACKVOWrapper.h" +#import "RACDisposable.h" + +// A private trampoline object that represents a KVO observation. +// +// Disposing of the trampoline will stop observation. +@interface RACKVOTrampoline : RACDisposable + +// Initializes the receiver with the given parameters. +// +// target - The object whose key path should be observed. Cannot be nil. +// observer - The object that gets notified when the value at the key path +// changes. Can be nil. +// keyPath - The key path on `target` to observe. Cannot be nil. +// options - Any key value observing options to use in the observation. +// block - The block to call when the value at the observed key path changes. +// Cannot be nil. +// +// Returns the initialized object. +- (id)initWithTarget:(NSObject *)target observer:(NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.m b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.m new file mode 100644 index 0000000..1e4feae --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACKVOTrampoline.m
@@ -0,0 +1,100 @@ +// +// RACKVOTrampoline.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 1/15/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACKVOTrampoline.h" +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" + +static void *RACKVOWrapperContext = &RACKVOWrapperContext; + +@interface RACKVOTrampoline () + +// The keypath which the trampoline is observing. +@property (nonatomic, readonly, copy) NSString *keyPath; + +// These properties should only be manipulated while synchronized on the +// receiver. +@property (nonatomic, readonly, copy) RACKVOBlock block; +@property (nonatomic, readonly, unsafe_unretained) NSObject *target; +@property (nonatomic, readonly, unsafe_unretained) NSObject *observer; + +@end + +@implementation RACKVOTrampoline + +#pragma mark Lifecycle + +- (id)initWithTarget:(NSObject *)target observer:(NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { + NSCParameterAssert(target != nil); + NSCParameterAssert(keyPath != nil); + NSCParameterAssert(block != nil); + + self = [super init]; + if (self == nil) return nil; + + _keyPath = [keyPath copy]; + + _block = [block copy]; + _target = target; + _observer = observer; + + [self.target addObserver:self forKeyPath:self.keyPath options:options context:&RACKVOWrapperContext]; + [self.target.rac_deallocDisposable addDisposable:self]; + [self.observer.rac_deallocDisposable addDisposable:self]; + + return self; +} + +- (void)dealloc { + [self dispose]; +} + +#pragma mark Observation + +- (void)dispose { + NSObject *target; + NSObject *observer; + + @synchronized (self) { + _block = nil; + + target = self.target; + observer = self.observer; + + _target = nil; + _observer = nil; + } + + [target.rac_deallocDisposable removeDisposable:self]; + [observer.rac_deallocDisposable removeDisposable:self]; + + [target removeObserver:self forKeyPath:self.keyPath context:&RACKVOWrapperContext]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context != &RACKVOWrapperContext) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + + RACKVOBlock block; + id observer; + id target; + + @synchronized (self) { + block = self.block; + observer = self.observer; + target = self.target; + } + + if (block == nil) return; + + block(target, observer, change); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection+Private.h new file mode 100644 index 0000000..43931f8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection+Private.h
@@ -0,0 +1,17 @@ +// +// RACMulticastConnection+Private.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACMulticastConnection.h" + +@class RACSubject; + +@interface RACMulticastConnection () + +- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h new file mode 100644 index 0000000..5361f5d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.h
@@ -0,0 +1,46 @@ +// +// RACMulticastConnection.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +@class RACSignal; +@class RACDisposable; + +/// A multicast connection encapsulates the idea of sharing one subscription to a +/// signal to many subscribers. This is most often needed if the subscription to +/// the underlying signal involves side-effects or shouldn't be called more than +/// once. +/// +/// The multicasted signal is only subscribed to when +/// -[RACMulticastConnection connect] is called. Until that happens, no values +/// will be sent on `signal`. See -[RACMulticastConnection autoconnect] for how +/// -[RACMulticastConnection connect] can be called automatically. +/// +/// Note that you shouldn't create RACMulticastConnection manually. Instead use +/// -[RACSignal publish] or -[RACSignal multicast:]. +@interface RACMulticastConnection : NSObject + +/// The multicasted signal. +@property (nonatomic, strong, readonly) RACSignal *signal; + +/// Connect to the underlying signal by subscribing to it. Calling this multiple +/// times does nothing but return the existing connection's disposable. +/// +/// Returns the disposable for the subscription to the multicasted signal. +- (RACDisposable *)connect; + +/// Connects to the underlying signal when the returned signal is first +/// subscribed to, and disposes of the subscription to the multicasted signal +/// when the returned signal has no subscribers. +/// +/// If new subscribers show up after being disposed, they'll subscribe and then +/// be immediately disposed of. The returned signal will never re-connect to the +/// multicasted signal. +/// +/// Returns the autoconnecting signal. +- (RACSignal *)autoconnect; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.m b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.m new file mode 100644 index 0000000..1534d96 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACMulticastConnection.m
@@ -0,0 +1,85 @@ +// +// RACMulticastConnection.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/11/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACMulticastConnection.h" +#import "RACMulticastConnection+Private.h" +#import "RACDisposable.h" +#import "RACSerialDisposable.h" +#import "RACSubject.h" +#import <libkern/OSAtomic.h> + +@interface RACMulticastConnection () { + RACSubject *_signal; + + // When connecting, a caller should attempt to atomically swap the value of this + // from `0` to `1`. + // + // If the swap is successful the caller is resposible for subscribing `_signal` + // to `sourceSignal` and storing the returned disposable in `serialDisposable`. + // + // If the swap is unsuccessful it means that `_sourceSignal` has already been + // connected and the caller has no action to take. + int32_t volatile _hasConnected; +} + +@property (nonatomic, readonly, strong) RACSignal *sourceSignal; +@property (strong) RACSerialDisposable *serialDisposable; +@end + +@implementation RACMulticastConnection + +#pragma mark Lifecycle + +- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject { + NSCParameterAssert(source != nil); + NSCParameterAssert(subject != nil); + + self = [super init]; + if (self == nil) return nil; + + _sourceSignal = source; + _serialDisposable = [[RACSerialDisposable alloc] init]; + _signal = subject; + + return self; +} + +#pragma mark Connecting + +- (RACDisposable *)connect { + BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected); + + if (shouldConnect) { + self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; + } + + return self.serialDisposable; +} + +- (RACSignal *)autoconnect { + __block volatile int32_t subscriberCount = 0; + + return [[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + OSAtomicIncrement32Barrier(&subscriberCount); + + RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber]; + RACDisposable *connectionDisposable = [self connect]; + + return [RACDisposable disposableWithBlock:^{ + [subscriptionDisposable dispose]; + + if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) { + [connectionDisposable dispose]; + } + }]; + }] + setNameWithFormat:@"[%@] -autoconnect", self.signal.name]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.h b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.h new file mode 100644 index 0000000..fbec61c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.h
@@ -0,0 +1,17 @@ +// +// RACObjCRuntime.h +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +// A private class containing wrappers to runtime functions. +@interface RACObjCRuntime : NSObject + +// Invokes objc_allocateClassPair(). Can be called from ARC code. ++ (Class)createClass:(const char *)className inheritingFromClass:(Class)superclass; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.m b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.m new file mode 100644 index 0000000..43cfc19 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACObjCRuntime.m
@@ -0,0 +1,22 @@ +// +// RACObjCRuntime.m +// ReactiveCocoa +// +// Created by Cody Krieger on 5/19/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACObjCRuntime.h" +#import <objc/runtime.h> + +#if __has_feature(objc_arc) +#error "This file must be compiled without ARC." +#endif + +@implementation RACObjCRuntime + ++ (Class)createClass:(const char *)className inheritingFromClass:(Class)superclass { + return objc_allocateClassPair(superclass, className, 0); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.h b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.h new file mode 100644 index 0000000..9ae547b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.h
@@ -0,0 +1,29 @@ +// +// RACPassthroughSubscriber.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACSubscriber.h" + +@class RACCompoundDisposable; +@class RACSignal; + +// A private subscriber that passes through all events to another subscriber +// while not disposed. +@interface RACPassthroughSubscriber : NSObject <RACSubscriber> + +// Initializes the receiver to pass through events until disposed. +// +// subscriber - The subscriber to forward events to. This must not be nil. +// signal - The signal that will be sending events to the receiver. +// disposable - When this disposable is disposed, no more events will be +// forwarded. This must not be nil. +// +// Returns an initialized passthrough subscriber. +- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.m b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.m new file mode 100644 index 0000000..614d7d5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACPassthroughSubscriber.m
@@ -0,0 +1,103 @@ +// +// RACPassthroughSubscriber.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACPassthroughSubscriber.h" +#import "RACCompoundDisposable.h" +#import "RACSignal.h" +#import "RACSignalProvider.h" + +static const char *cleanedDTraceString(NSString *original) { + return [original stringByReplacingOccurrencesOfString:@"\\s+" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, original.length)].UTF8String; +} + +static const char *cleanedSignalDescription(RACSignal *signal) { + NSString *desc = signal.description; + + NSRange range = [desc rangeOfString:@" name:"]; + if (range.location != NSNotFound) { + desc = [desc stringByReplacingCharactersInRange:range withString:@""]; + } + + return cleanedDTraceString(desc); +} + +@interface RACPassthroughSubscriber () + +// The subscriber to which events should be forwarded. +@property (nonatomic, strong, readonly) id<RACSubscriber> innerSubscriber; + +// The signal sending events to this subscriber. +// +// This property isn't `weak` because it's only used for DTrace probes, so +// a zeroing weak reference would incur an unnecessary performance penalty in +// normal usage. +@property (nonatomic, unsafe_unretained, readonly) RACSignal *signal; + +// A disposable representing the subscription. When disposed, no further events +// should be sent to the `innerSubscriber`. +@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; + +@end + +@implementation RACPassthroughSubscriber + +#pragma mark Lifecycle + +- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable { + NSCParameterAssert(subscriber != nil); + + self = [super init]; + if (self == nil) return nil; + + _innerSubscriber = subscriber; + _signal = signal; + _disposable = disposable; + + [self.innerSubscriber didSubscribeWithDisposable:self.disposable]; + return self; +} + +#pragma mark RACSubscriber + +- (void)sendNext:(id)value { + if (self.disposable.disposed) return; + + if (RACSIGNAL_NEXT_ENABLED()) { + RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description])); + } + + [self.innerSubscriber sendNext:value]; +} + +- (void)sendError:(NSError *)error { + if (self.disposable.disposed) return; + + if (RACSIGNAL_ERROR_ENABLED()) { + RACSIGNAL_ERROR(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString(error.description)); + } + + [self.innerSubscriber sendError:error]; +} + +- (void)sendCompleted { + if (self.disposable.disposed) return; + + if (RACSIGNAL_COMPLETED_ENABLED()) { + RACSIGNAL_COMPLETED(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description)); + } + + [self.innerSubscriber sendCompleted]; +} + +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { + if (disposable != self.disposable) { + [self.disposable addDisposable:disposable]; + } +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler+Subclass.h b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler+Subclass.h new file mode 100644 index 0000000..48ef636 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler+Subclass.h
@@ -0,0 +1,46 @@ +// +// RACQueueScheduler+Subclass.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/6/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACQueueScheduler.h" + +/// An interface for use by subclasses. +/// +/// Subclasses should use `-performAsCurrentScheduler:` to do the actual block +/// invocation so that +[RACScheduler currentScheduler] behaves as expected. +/// +/// **Note that RACSchedulers are expected to be serial**. Subclasses must honor +/// that contract. See `RACTargetQueueScheduler` for a queue-based scheduler +/// which will enforce the serialization guarantee. +@interface RACQueueScheduler () + +/// The queue on which blocks are enqueued. +@property (nonatomic, readonly) dispatch_queue_t queue; + +/// Initializes the receiver with the name of the scheduler and the queue which +/// the scheduler should use. +/// +/// name - The name of the scheduler. If nil, a default name will be used. +/// queue - The queue upon which the receiver should enqueue scheduled blocks. +/// This argument must not be NULL. +/// +/// Returns the initialized object. +- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue; + +/// Performs the given block with the receiver as the current scheduler for +/// `queue`. This should only be called by subclasses to perform scheduled blocks +/// on their queue. +/// +/// block - The block to execute. Cannot be NULL. +- (void)performAsCurrentScheduler:(void (^)(void))block; + +/// Converts a date into a GCD time using dispatch_walltime(). +/// +/// date - The date to convert. This must not be nil. ++ (dispatch_time_t)wallTimeWithDate:(NSDate *)date; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.h new file mode 100644 index 0000000..ef42512 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.h
@@ -0,0 +1,18 @@ +// +// RACQueueScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +/// An abstract scheduler which asynchronously enqueues all its work to a Grand +/// Central Dispatch queue. +/// +/// Because RACQueueScheduler is abstract, it should not be instantiated +/// directly. Create a subclass using the `RACQueueScheduler+Subclass.h` +/// interface and use that instead. +@interface RACQueueScheduler : RACScheduler +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.m new file mode 100644 index 0000000..7ae8202 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACQueueScheduler.m
@@ -0,0 +1,120 @@ +// +// RACQueueScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACQueueScheduler.h" +#import "RACBacktrace.h" +#import "RACDisposable.h" +#import "RACQueueScheduler+Subclass.h" +#import "RACScheduler+Private.h" + +@implementation RACQueueScheduler + +#pragma mark Lifecycle + +- (void)dealloc { + dispatch_release(_queue); +} + +- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue { + NSCParameterAssert(queue != NULL); + + self = [super initWithName:name]; + if (self == nil) return nil; + + dispatch_retain(queue); + _queue = queue; + + return self; +} + +#pragma mark Date Conversions + ++ (dispatch_time_t)wallTimeWithDate:(NSDate *)date { + NSCParameterAssert(date != nil); + + double seconds = 0; + double frac = modf(date.timeIntervalSince1970, &seconds); + + struct timespec walltime = { + .tv_sec = (time_t)fmin(fmax(seconds, LONG_MIN), LONG_MAX), + .tv_nsec = (long)fmin(fmax(frac * NSEC_PER_SEC, LONG_MIN), LONG_MAX) + }; + + return dispatch_walltime(&walltime, 0); +} + +#pragma mark RACScheduler + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + RACDisposable *disposable = [[RACDisposable alloc] init]; + + dispatch_async(self.queue, ^{ + if (disposable.disposed) return; + [self performAsCurrentScheduler:block]; + }); + + return disposable; +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != NULL); + + RACDisposable *disposable = [[RACDisposable alloc] init]; + + dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{ + if (disposable.disposed) return; + [self performAsCurrentScheduler:block]; + }); + + return disposable; +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC); + NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC); + NSCParameterAssert(block != NULL); + + uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC); + uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC); + + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); + dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs); + dispatch_source_set_event_handler(timer, block); + dispatch_resume(timer); + + return [RACDisposable disposableWithBlock:^{ + dispatch_source_cancel(timer); + dispatch_release(timer); + }]; +} + +- (void)performAsCurrentScheduler:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + // If we're using a concurrent queue, we could end up in here concurrently, + // in which case we *don't* want to clear the current scheduler immediately + // after our block is done executing, but only *after* all our concurrent + // invocations are done. + + RACScheduler *previousScheduler = RACScheduler.currentScheduler; + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; + + block(); + + if (previousScheduler != nil) { + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; + } else { + [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; + } +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.h b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.h new file mode 100644 index 0000000..cd6f2ec --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.h
@@ -0,0 +1,23 @@ +// +// RACReplaySubject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/14/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" + +extern const NSUInteger RACReplaySubjectUnlimitedCapacity; + + +/// A replay subject saves the values it is sent (up to its defined capacity) +/// and resends those to new subscribers. It will also replay an error or +/// completion. +@interface RACReplaySubject : RACSubject + +/// Creates a new replay subject with the given capacity. A capacity of +/// RACReplaySubjectUnlimitedCapacity means values are never trimmed. ++ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.m b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.m new file mode 100644 index 0000000..bca008e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReplaySubject.m
@@ -0,0 +1,112 @@ +// +// RACReplaySubject.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/14/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACReplaySubject.h" +#import "RACDisposable.h" +#import "RACScheduler+Private.h" +#import "RACSubscriber.h" +#import "RACTuple.h" +#import "RACCompoundDisposable.h" + +const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax; + +@interface RACReplaySubject () + +@property (nonatomic, assign, readonly) NSUInteger capacity; + +// These properties should only be modified while synchronized on self. +@property (nonatomic, strong, readonly) NSMutableArray *valuesReceived; +@property (nonatomic, assign) BOOL hasCompleted; +@property (nonatomic, assign) BOOL hasError; +@property (nonatomic, strong) NSError *error; + +@end + + +@implementation RACReplaySubject + +#pragma mark Lifecycle + ++ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity { + return [[self alloc] initWithCapacity:capacity]; +} + +- (instancetype)init { + return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity]; +} + +- (instancetype)initWithCapacity:(NSUInteger)capacity { + self = [super init]; + if (self == nil) return nil; + + _capacity = capacity; + _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]); + + return self; +} + +#pragma mark RACSignal + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ + @synchronized (self) { + for (id value in self.valuesReceived) { + if (compoundDisposable.disposed) return; + + [subscriber sendNext:([value isKindOfClass:RACTupleNil.class] ? nil : value)]; + } + + if (compoundDisposable.disposed) return; + + if (self.hasCompleted) { + [subscriber sendCompleted]; + } else if (self.hasError) { + [subscriber sendError:self.error]; + } else { + RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; + [compoundDisposable addDisposable:subscriptionDisposable]; + } + } + }]; + + [compoundDisposable addDisposable:schedulingDisposable]; + + return compoundDisposable; +} + +#pragma mark RACSubscriber + +- (void)sendNext:(id)value { + @synchronized (self) { + [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil]; + [super sendNext:value]; + + if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) { + [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)]; + } + } +} + +- (void)sendCompleted { + @synchronized (self) { + self.hasCompleted = YES; + [super sendCompleted]; + } +} + +- (void)sendError:(NSError *)e { + @synchronized (self) { + self.hasError = YES; + self.error = e; + [super sendError:e]; + } +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.h new file mode 100644 index 0000000..73e5674 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.h
@@ -0,0 +1,17 @@ +// +// RACReturnSignal.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" + +// A private `RACSignal` subclasses that synchronously sends a value to any +// subscribers, then completes. +@interface RACReturnSignal : RACSignal + ++ (RACSignal *)return:(id)value; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.m new file mode 100644 index 0000000..3006634 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACReturnSignal.m
@@ -0,0 +1,90 @@ +// +// RACReturnSignal.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-10. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACReturnSignal.h" +#import "RACScheduler+Private.h" +#import "RACSubscriber.h" +#import "RACUnit.h" + +@interface RACReturnSignal () + +// The value to send upon subscription. +@property (nonatomic, strong, readonly) id value; + +@end + +@implementation RACReturnSignal + +#pragma mark Properties + +// Only allow this signal's name to be customized in DEBUG, since it's +// potentially a singleton in release builds (see +return:). +- (void)setName:(NSString *)name { +#ifdef DEBUG + [super setName:name]; +#endif +} + +- (NSString *)name { +#ifdef DEBUG + return super.name; +#else + return @"+return:"; +#endif +} + +#pragma mark Lifecycle + ++ (RACSignal *)return:(id)value { +#ifndef DEBUG + // In release builds, use singletons for two very common cases. + if (value == RACUnit.defaultUnit) { + static RACReturnSignal *unitSingleton; + static dispatch_once_t unitPred; + + dispatch_once(&unitPred, ^{ + unitSingleton = [[self alloc] init]; + unitSingleton->_value = RACUnit.defaultUnit; + }); + + return unitSingleton; + } else if (value == nil) { + static RACReturnSignal *nilSingleton; + static dispatch_once_t nilPred; + + dispatch_once(&nilPred, ^{ + nilSingleton = [[self alloc] init]; + nilSingleton->_value = nil; + }); + + return nilSingleton; + } +#endif + + RACReturnSignal *signal = [[self alloc] init]; + signal->_value = value; + +#ifdef DEBUG + [signal setNameWithFormat:@"+return: %@", value]; +#endif + + return signal; +} + +#pragma mark Subscription + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCParameterAssert(subscriber != nil); + + return [RACScheduler.subscriptionScheduler schedule:^{ + [subscriber sendNext:self.value]; + [subscriber sendCompleted]; + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler+Private.h new file mode 100644 index 0000000..2c91e66 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler+Private.h
@@ -0,0 +1,34 @@ +// +// RACScheduler+Private.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/29/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +// The thread-specific current scheduler key. +extern NSString * const RACSchedulerCurrentSchedulerKey; + +// A private interface for internal RAC use only. +@interface RACScheduler () + +// A dedicated scheduler that fills two requirements: +// +// 1. By the time subscription happens, we need a valid +currentScheduler. +// 2. Subscription should happen as soon as possible. +// +// To fulfill those two, if we already have a valid +currentScheduler, it +// immediately executes scheduled blocks. If we don't, it will execute scheduled +// blocks with a private background scheduler. ++ (instancetype)subscriptionScheduler; + +// Initializes the receiver with the given name. +// +// name - The name of the scheduler. If nil, a default name will be used. +// +// Returns the initialized object. +- (id)initWithName:(NSString *)name; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h new file mode 100644 index 0000000..ce1ee32 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.h
@@ -0,0 +1,148 @@ +// +// RACScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +/// The priority for the scheduler. +/// +/// RACSchedulerPriorityHigh - High priority. +/// RACSchedulerPriorityDefault - Default priority. +/// RACSchedulerPriorityLow - Low priority. +/// RACSchedulerPriorityBackground - Background priority. +typedef enum : long { + RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH, + RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT, + RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW, + RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND, +} RACSchedulerPriority; + +/// Scheduled with -scheduleRecursiveBlock:, this type of block is passed a block +/// with which it can call itself recursively. +typedef void (^RACSchedulerRecursiveBlock)(void (^reschedule)(void)); + +@class RACDisposable; + +/// Schedulers are used to control when and where work is performed. +@interface RACScheduler : NSObject + +/// A singleton scheduler that immediately executes the blocks it is given. +/// +/// **Note:** Unlike most other schedulers, this does not set the current +/// scheduler. There may still be a valid +currentScheduler if this is used +/// within a block scheduled on a different scheduler. ++ (RACScheduler *)immediateScheduler; + +/// A singleton scheduler that executes blocks in the main thread. ++ (RACScheduler *)mainThreadScheduler; + +/// Creates and returns a new background scheduler with the given priority and +/// name. The name is for debug and instrumentation purposes only. +/// +/// Scheduler creation is cheap. It's unnecessary to save the result of this +/// method call unless you want to serialize some actions on the same background +/// scheduler. ++ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name; + +/// Invokes +schedulerWithPriority:name: with a default name. ++ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority; + +/// Invokes +schedulerWithPriority: with RACSchedulerPriorityDefault. ++ (RACScheduler *)scheduler; + +/// The current scheduler. This will only be valid when used from within a +/// -[RACScheduler schedule:] block or when on the main thread. ++ (RACScheduler *)currentScheduler; + +/// Schedule the given block for execution on the scheduler. +/// +/// Scheduled blocks will be executed in the order in which they were scheduled. +/// +/// block - The block to schedule for execution. Cannot be nil. +/// +/// Returns a disposable which can be used to cancel the scheduled block before +/// it begins executing, or nil if cancellation is not supported. +- (RACDisposable *)schedule:(void (^)(void))block; + +/// Schedule the given block for execution on the scheduler at or after +/// a specific time. +/// +/// Note that blocks scheduled for a certain time will not preempt any other +/// scheduled work that is executing at the time. +/// +/// When invoked on the +immediateScheduler, the calling thread **will block** +/// until the specified time. +/// +/// date - The earliest time at which `block` should begin executing. The block +/// may not execute immediately at this time, whether due to system load +/// or another block on the scheduler currently being run. Cannot be nil. +/// block - The block to schedule for execution. Cannot be nil. +/// +/// Returns a disposable which can be used to cancel the scheduled block before +/// it begins executing, or nil if cancellation is not supported. +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block; + +/// Schedule the given block for execution on the scheduler after the delay. +/// +/// Converts the delay into an NSDate, then invokes `-after:schedule:`. +- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block; + +/// Reschedule the given block at a particular interval, starting at a specific +/// time, and with a given leeway for deferral. +/// +/// Note that blocks scheduled for a certain time will not preempt any other +/// scheduled work that is executing at the time. +/// +/// Regardless of the value of `leeway`, the given block may not execute exactly +/// at `when` or exactly on successive intervals, whether due to system load or +/// because another block is currently being run on the scheduler. +/// +/// It is considered undefined behavior to invoke this method on the +/// +immediateScheduler. +/// +/// date - The earliest time at which `block` should begin executing. The +/// block may not execute immediately at this time, whether due to +/// system load or another block on the scheduler currently being +/// run. Cannot be nil. +/// interval - The interval at which the block should be rescheduled, starting +/// from `date`. This will use the system wall clock, to avoid +/// skew when the computer goes to sleep. +/// leeway - A hint to the system indicating the number of seconds that each +/// scheduling can be deferred. Note that this is just a hint, and +/// there may be some additional latency no matter what. +/// block - The block to repeatedly schedule for execution. Cannot be nil. +/// +/// Returns a disposable which can be used to cancel the automatic scheduling and +/// rescheduling, or nil if cancellation is not supported. +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block; + +/// Schedule the given recursive block for execution on the scheduler. The +/// scheduler will automatically flatten any recursive scheduling into iteration +/// instead, so this can be used without issue for blocks that may keep invoking +/// themselves forever. +/// +/// Scheduled blocks will be executed in the order in which they were scheduled. +/// +/// recursiveBlock - The block to schedule for execution. When invoked, the +/// recursive block will be passed a `void (^)(void)` block +/// which will reschedule the recursive block at the end of the +/// receiver's queue. This passed-in block will automatically +/// skip scheduling if the scheduling of the `recursiveBlock` +/// was disposed in the meantime. +/// +/// Returns a disposable which can be used to cancel the scheduled block before +/// it begins executing, or to stop it from rescheduling if it's already begun +/// execution. +- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock; + +@end + +@interface RACScheduler (Deprecated) + ++ (RACScheduler *)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name __attribute__((deprecated("Use -[RACScheduler initWithName:targetQueue:] instead."))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.m new file mode 100644 index 0000000..c829896 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScheduler.m
@@ -0,0 +1,206 @@ +// +// RACScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/16/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" +#import "RACBacktrace.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACImmediateScheduler.h" +#import "RACScheduler+Private.h" +#import "RACSubscriptionScheduler.h" +#import "RACTargetQueueScheduler.h" + +// The key for the thread-specific current scheduler. +NSString * const RACSchedulerCurrentSchedulerKey = @"RACSchedulerCurrentSchedulerKey"; + +@interface RACScheduler () +@property (nonatomic, readonly, copy) NSString *name; +@end + +@implementation RACScheduler + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.name]; +} + +#pragma mark Initializers + +- (id)initWithName:(NSString *)name { + self = [super init]; + if (self == nil) return nil; + + if (name == nil) { + _name = [NSString stringWithFormat:@"com.ReactiveCocoa.%@.anonymousScheduler", self.class]; + } else { + _name = [name copy]; + } + + return self; +} + +#pragma mark Schedulers + ++ (instancetype)immediateScheduler { + static dispatch_once_t onceToken; + static RACScheduler *immediateScheduler; + dispatch_once(&onceToken, ^{ + immediateScheduler = [[RACImmediateScheduler alloc] init]; + }); + + return immediateScheduler; +} + ++ (instancetype)mainThreadScheduler { + static dispatch_once_t onceToken; + static RACScheduler *mainThreadScheduler; + dispatch_once(&onceToken, ^{ + mainThreadScheduler = [[RACTargetQueueScheduler alloc] initWithName:@"com.ReactiveCocoa.RACScheduler.mainThreadScheduler" targetQueue:dispatch_get_main_queue()]; + }); + + return mainThreadScheduler; +} + ++ (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name { + return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)]; +} + ++ (instancetype)schedulerWithPriority:(RACSchedulerPriority)priority { + return [self schedulerWithPriority:priority name:@"com.ReactiveCocoa.RACScheduler.backgroundScheduler"]; +} + ++ (instancetype)scheduler { + return [self schedulerWithPriority:RACSchedulerPriorityDefault]; +} + ++ (instancetype)subscriptionScheduler { + static dispatch_once_t onceToken; + static RACScheduler *subscriptionScheduler; + dispatch_once(&onceToken, ^{ + subscriptionScheduler = [[RACSubscriptionScheduler alloc] init]; + }); + + return subscriptionScheduler; +} + ++ (BOOL)isOnMainThread { + return [NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue] || [NSThread isMainThread]; +} + ++ (instancetype)currentScheduler { + RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey]; + if (scheduler != nil) return scheduler; + if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler; + + return nil; +} + +#pragma mark Scheduling + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; +} + +- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block { + return [self after:[NSDate dateWithTimeIntervalSinceNow:delay] schedule:block]; +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd)); + return nil; +} + +- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + [self scheduleRecursiveBlock:[recursiveBlock copy] addingToDisposable:disposable]; + return disposable; +} + +- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable { + @autoreleasepool { + RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; + [disposable addDisposable:selfDisposable]; + + __weak RACDisposable *weakSelfDisposable = selfDisposable; + + RACDisposable *schedulingDisposable = [self schedule:^{ + @autoreleasepool { + // At this point, we've been invoked, so our disposable is now useless. + [disposable removeDisposable:weakSelfDisposable]; + } + + if (disposable.disposed) return; + + void (^reallyReschedule)(void) = ^{ + if (disposable.disposed) return; + [self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable]; + }; + + // Protects the variables below. + // + // This doesn't actually need to be __block qualified, but Clang + // complains otherwise. :C + __block NSLock *lock = [[NSLock alloc] init]; + lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)]; + + __block NSUInteger rescheduleCount = 0; + + // Set to YES once synchronous execution has finished. Further + // rescheduling should occur immediately (rather than being + // flattened). + __block BOOL rescheduleImmediately = NO; + + @autoreleasepool { + recursiveBlock(^{ + [lock lock]; + BOOL immediate = rescheduleImmediately; + if (!immediate) ++rescheduleCount; + [lock unlock]; + + if (immediate) reallyReschedule(); + }); + } + + [lock lock]; + NSUInteger synchronousCount = rescheduleCount; + rescheduleImmediately = YES; + [lock unlock]; + + for (NSUInteger i = 0; i < synchronousCount; i++) { + reallyReschedule(); + } + }]; + + [selfDisposable addDisposable:schedulingDisposable]; + } +} + +@end + +@implementation RACScheduler (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + ++ (instancetype)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name { + NSCParameterAssert(queue != NULL); + + return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:queue]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.h new file mode 100644 index 0000000..03da6a2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.h
@@ -0,0 +1,19 @@ +// +// RACScopedDisposable.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" + + +/// A disposable that calls its own -dispose when it is dealloc'd. +@interface RACScopedDisposable : RACDisposable + +/// Creates a new scoped disposable that will also dispose of the given +/// disposable when it is dealloc'd. ++ (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.m new file mode 100644 index 0000000..91115be --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACScopedDisposable.m
@@ -0,0 +1,32 @@ +// +// RACScopedDisposable.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScopedDisposable.h" + +@implementation RACScopedDisposable + +#pragma mark Lifecycle + ++ (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable { + return [self disposableWithBlock:^{ + [disposable dispose]; + }]; +} + +- (void)dealloc { + [self dispose]; +} + +#pragma mark RACDisposable + +- (RACScopedDisposable *)asScopedDisposable { + // totally already are + return self; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h new file mode 100644 index 0000000..a39f840 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.h
@@ -0,0 +1,154 @@ +// +// RACSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACStream.h" + +@class RACScheduler; +@class RACSignal; + +/// Represents an immutable sequence of values. Unless otherwise specified, the +/// sequences' values are evaluated lazily on demand. Like Cocoa collections, +/// sequences cannot contain nil. +/// +/// Most inherited RACStream methods that accept a block will execute the block +/// _at most_ once for each value that is evaluated in the returned sequence. +/// Side effects are subject to the behavior described in +/// +sequenceWithHeadBlock:tailBlock:. +/// +/// Implemented as a class cluster. A minimal implementation for a subclass +/// consists simply of -head and -tail. +@interface RACSequence : RACStream <NSCoding, NSCopying, NSFastEnumeration> + +/// The first object in the sequence, or nil if the sequence is empty. +/// +/// Subclasses must provide an implementation of this method. +@property (nonatomic, strong, readonly) id head; + +/// All but the first object in the sequence, or nil if the sequence is empty. +/// +/// Subclasses must provide an implementation of this method. +@property (nonatomic, strong, readonly) RACSequence *tail; + +/// Evaluates the full sequence to produce an equivalently-sized array. +@property (nonatomic, copy, readonly) NSArray *array; + +/// Returns an enumerator of all objects in the sequence. +@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator; + +/// Converts a sequence into an eager sequence. +/// +/// An eager sequence fully evaluates all of its values immediately. Sequences +/// derived from an eager sequence will also be eager. +/// +/// Returns a new eager sequence, or the receiver if the sequence is already +/// eager. +@property (nonatomic, copy, readonly) RACSequence *eagerSequence; + +/// Converts a sequence into a lazy sequence. +/// +/// A lazy sequence evaluates its values on demand, as they are accessed. +/// Sequences derived from a lazy sequence will also be lazy. +/// +/// Returns a new lazy sequence, or the receiver if the sequence is already lazy. +@property (nonatomic, copy, readonly) RACSequence *lazySequence; + +/// Invokes -signalWithScheduler: with a new RACScheduler. +- (RACSignal *)signal; + +/// Evaluates the full sequence on the given scheduler. +/// +/// Each item is evaluated in its own scheduled block, such that control of the +/// scheduler is yielded between each value. +/// +/// Returns a signal which sends the receiver's values on the given scheduler as +/// they're evaluated. +- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler; + +/// Applies a left fold to the sequence. +/// +/// This is the same as iterating the sequence along with a provided start value. +/// This uses a constant amount of memory. A left fold is left-associative so in +/// the sequence [1,2,3] the block would applied in the following order: +/// reduce(reduce(reduce(start, 1), 2), 3) +/// +/// start - The starting value for the fold. Used as `accumulator` for the +/// first fold. +/// reduce - The block used to combine the accumulated value and the next value. +/// Cannot be nil. +/// +/// Returns a reduced value. +- (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce; + +/// Applies a right fold to the sequence. +/// +/// A right fold is equivalent to recursion on the list. The block is evaluated +/// from the right to the left in list. It is right associative so it's applied +/// to the rightmost elements first. For example, in the sequence [1,2,3] the +/// block is applied in the order: +/// reduce(1, reduce(2, reduce(3, start))) +/// +/// start - The starting value for the fold. +/// reduce - The block used to combine the accumulated value and the next head. +/// The block is given the accumulated value and the value of the rest +/// of the computation (result of the recursion). This is computed when +/// you retrieve its value using `rest.head`. This allows you to +/// prevent unnecessary computation by not accessing `rest.head` if you +/// don't need to. +/// +/// Returns a reduced value. +- (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce; + +/// Check if any value in sequence passes the block. +/// +/// block - The block predicate used to check each item. Cannot be nil. +/// +/// Returns a boolean indiciating if any value in the sequence passed. +- (BOOL)any:(BOOL (^)(id value))block; + +/// Check if all values in the sequence pass the block. +/// +/// block - The block predicate used to check each item. Cannot be nil. +/// +/// Returns a boolean indicating if all values in the sequence passed. +- (BOOL)all:(BOOL (^)(id value))block; + +/// Returns the first object that passes the block. +/// +/// block - The block predicate used to check each item. Cannot be nil. +/// +/// Returns an object that passes the block or nil if no objects passed. +- (id)objectPassingTest:(BOOL (^)(id value))block; + +/// Creates a sequence that dynamically generates its values. +/// +/// headBlock - Invoked the first time -head is accessed. +/// tailBlock - Invoked the first time -tail is accessed. +/// +/// The results from each block are memoized, so each block will be invoked at +/// most once, no matter how many times the head and tail properties of the +/// sequence are accessed. +/// +/// Any side effects in `headBlock` or `tailBlock` should be thread-safe, since +/// the sequence may be evaluated at any time from any thread. Not only that, but +/// -tail may be accessed before -head, or both may be accessed simultaneously. +/// As noted above, side effects will only be triggered the _first_ time -head or +/// -tail is invoked. +/// +/// Returns a sequence that lazily invokes the given blocks to provide head and +/// tail. `headBlock` must not be nil. ++ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock; + +@end + +@interface RACSequence (Deprecated) + +- (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine __attribute__((deprecated("Renamed to -foldLeftWithStart:reduce:"))); +- (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine __attribute__((deprecated("Renamed to -foldRightWithStart:reduce:"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.m new file mode 100644 index 0000000..9567ea8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSequence.m
@@ -0,0 +1,384 @@ +// +// RACSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACSequence.h" +#import "RACArraySequence.h" +#import "RACDynamicSequence.h" +#import "RACEagerSequence.h" +#import "RACEmptySequence.h" +#import "RACScheduler.h" +#import "RACSignal.h" +#import "RACSubscriber.h" +#import "RACTuple.h" +#import "RACUnarySequence.h" + +// An enumerator over sequences. +@interface RACSequenceEnumerator : NSEnumerator + +// The sequence the enumerator is enumerating. +// +// This will change as the enumerator is exhausted. This property should only be +// accessed while synchronized on self. +@property (nonatomic, strong) RACSequence *sequence; + +@end + +@interface RACSequence () + +// Performs one iteration of lazy binding, passing through values from `current` +// until the sequence is exhausted, then recursively binding the remaining +// values in the receiver. +// +// Returns a new sequence which contains `current`, followed by the combined +// result of all applications of `block` to the remaining values in the receiver. +- (instancetype)bind:(RACStreamBindBlock)block passingThroughValuesFromSequence:(RACSequence *)current; + +@end + +@implementation RACSequenceEnumerator + +- (id)nextObject { + id object = nil; + + @synchronized (self) { + object = self.sequence.head; + self.sequence = self.sequence.tail; + } + + return object; +} + +@end + +@implementation RACSequence + +#pragma mark Lifecycle + ++ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock { + return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"]; +} + +#pragma mark Class cluster primitives + +- (id)head { + NSCAssert(NO, @"%s must be overridden by subclasses", __func__); + return nil; +} + +- (RACSequence *)tail { + NSCAssert(NO, @"%s must be overridden by subclasses", __func__); + return nil; +} + +#pragma mark RACStream + ++ (instancetype)empty { + return RACEmptySequence.empty; +} + ++ (instancetype)return:(id)value { + return [RACUnarySequence return:value]; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(void))block { + RACStreamBindBlock bindBlock = block(); + return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name]; +} + +- (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence { + // Store values calculated in the dependency here instead, avoiding any kind + // of temporary collection and boxing. + // + // This relies on the implementation of RACDynamicSequence synchronizing + // access to its head, tail, and dependency, and we're only doing it because + // we really need the performance. + __block RACSequence *valuesSeq = self; + __block RACSequence *current = passthroughSequence; + __block BOOL stop = NO; + + RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id { + while (current.head == nil) { + if (stop) return nil; + + // We've exhausted the current sequence, create a sequence from the + // next value. + id value = valuesSeq.head; + + if (value == nil) { + // We've exhausted all the sequences. + stop = YES; + return nil; + } + + current = (id)bindBlock(value, &stop); + if (current == nil) { + stop = YES; + return nil; + } + + valuesSeq = valuesSeq.tail; + } + + NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current); + return nil; + } headBlock:^(id _) { + return current.head; + } tailBlock:^ id (id _) { + if (stop) return nil; + + return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail]; + }]; + + sequence.name = self.name; + return sequence; +} + +- (instancetype)concat:(RACStream *)stream { + NSCParameterAssert(stream != nil); + + return [[[RACArraySequence sequenceWithArray:@[ self, stream ] offset:0] + flatten] + setNameWithFormat:@"[%@] -concat: %@", self.name, stream]; +} + +- (instancetype)zipWith:(RACSequence *)sequence { + NSCParameterAssert(sequence != nil); + + return [[RACSequence + sequenceWithHeadBlock:^ id { + if (self.head == nil || sequence.head == nil) return nil; + return RACTuplePack(self.head, sequence.head); + } tailBlock:^ id { + if (self.tail == nil || [[RACSequence empty] isEqual:self.tail]) return nil; + if (sequence.tail == nil || [[RACSequence empty] isEqual:sequence.tail]) return nil; + + return [self.tail zipWith:sequence.tail]; + }] + setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence]; +} + +#pragma mark Extended methods + +- (NSArray *)array { + NSMutableArray *array = [NSMutableArray array]; + for (id obj in self) { + [array addObject:obj]; + } + + return [array copy]; +} + +- (NSEnumerator *)objectEnumerator { + RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init]; + enumerator.sequence = self; + return enumerator; +} + +- (RACSignal *)signal { + return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name]; +} + +- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block RACSequence *sequence = self; + + return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) { + if (sequence.head == nil) { + [subscriber sendCompleted]; + return; + } + + [subscriber sendNext:sequence.head]; + + sequence = sequence.tail; + reschedule(); + }]; + }] setNameWithFormat:@"[%@] -signalWithScheduler:", self.name]; +} + +- (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce { + NSCParameterAssert(reduce != NULL); + + if (self.head == nil) return start; + + for (id value in self) { + start = reduce(start, value); + } + + return start; +} + +- (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *))reduce { + NSCParameterAssert(reduce != NULL); + + if (self.head == nil) return start; + + RACSequence *rest = [RACSequence sequenceWithHeadBlock:^{ + return [self.tail foldRightWithStart:start reduce:reduce]; + } tailBlock:nil]; + + return reduce(self.head, rest); +} + +- (BOOL)any:(BOOL (^)(id))block { + NSCParameterAssert(block != NULL); + + return [self objectPassingTest:block] != nil; +} + +- (BOOL)all:(BOOL (^)(id))block { + NSCParameterAssert(block != NULL); + + NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) { + return @(accumulator.boolValue && block(value)); + }]; + + return result.boolValue; +} + +- (id)objectPassingTest:(BOOL (^)(id))block { + NSCParameterAssert(block != NULL); + + return [self filter:block].head; +} + +- (RACSequence *)eagerSequence { + return [RACEagerSequence sequenceWithArray:self.array offset:0]; +} + +- (RACSequence *)lazySequence { + return self; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +#pragma mark NSCoding + +- (Class)classForCoder { + // Most sequences should be archived as RACArraySequences. + return RACArraySequence.class; +} + +- (id)initWithCoder:(NSCoder *)coder { + if (![self isKindOfClass:RACArraySequence.class]) return [[RACArraySequence alloc] initWithCoder:coder]; + + // Decoding is handled in RACArraySequence. + return [super init]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:self.array forKey:@"array"]; +} + +#pragma mark NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len { + if (state->state == ULONG_MAX) { + // Enumeration has completed. + return 0; + } + + // We need to traverse the sequence itself on repeated calls to this + // method, so use the 'state' field to track the current head. + RACSequence *(^getSequence)(void) = ^{ + return (__bridge RACSequence *)(void *)state->state; + }; + + void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) { + // Release the old sequence and retain the new one. + CFBridgingRelease((void *)state->state); + + state->state = (unsigned long)CFBridgingRetain(sequence); + }; + + void (^complete)(void) = ^{ + // Release any stored sequence. + setSequence(nil); + state->state = ULONG_MAX; + }; + + if (state->state == 0) { + // Since a sequence doesn't mutate, this just needs to be set to + // something non-NULL. + state->mutationsPtr = state->extra; + + setSequence(self); + } + + state->itemsPtr = stackbuf; + + NSUInteger enumeratedCount = 0; + while (enumeratedCount < len) { + RACSequence *seq = getSequence(); + + // Because the objects in a sequence may be generated lazily, we want to + // prevent them from being released until the enumerator's used them. + __autoreleasing id obj = seq.head; + if (obj == nil) { + complete(); + break; + } + + stackbuf[enumeratedCount++] = obj; + + if (seq.tail == nil) { + complete(); + break; + } + + setSequence(seq.tail); + } + + return enumeratedCount; +} + +#pragma mark NSObject + +- (NSUInteger)hash { + return [self.head hash]; +} + +- (BOOL)isEqual:(RACSequence *)seq { + if (self == seq) return YES; + if (![seq isKindOfClass:RACSequence.class]) return NO; + + for (id<NSObject> selfObj in self) { + id<NSObject> seqObj = seq.head; + + // Handles the nil case too. + if (![seqObj isEqual:selfObj]) return NO; + + seq = seq.tail; + } + + // self is now depleted -- the argument should be too. + return (seq.head == nil); +} + +@end + +@implementation RACSequence (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine { + return [self foldLeftWithStart:start reduce:combine]; +} + +- (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine { + return [self foldRightWithStart:start reduce:combine]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.h new file mode 100644 index 0000000..a3fc1d4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.h
@@ -0,0 +1,43 @@ +// +// RACSerialDisposable.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" + +/// A disposable that contains exactly one other disposable and allows it to be +/// swapped out atomically. +@interface RACSerialDisposable : RACDisposable + +/// The inner disposable managed by the serial disposable. +/// +/// This property is thread-safe for reading and writing. However, if you want to +/// read the current value _and_ write a new one atomically, use +/// -swapInDisposable: instead. +/// +/// Disposing of the receiver will also dispose of the current disposable set for +/// this property, then set the property to nil. If any new disposable is set +/// after the receiver is disposed, it will be disposed immediately and this +/// property will remain set to nil. +@property (atomic, strong) RACDisposable *disposable; + +/// Creates a serial disposable which will wrap the given disposable. +/// +/// disposable - The value to set for `disposable`. This may be nil. +/// +/// Returns a RACSerialDisposable, or nil if an error occurs. ++ (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable; + +/// Atomically swaps the receiver's `disposable` for `newDisposable`. +/// +/// newDisposable - The new value for `disposable`. If the receiver has already +/// been disposed, this disposable will be too, and `disposable` +/// will remain set to nil. This argument may be nil. +/// +/// Returns the previous value for the `disposable` property. +- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.m new file mode 100644 index 0000000..975afbf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSerialDisposable.m
@@ -0,0 +1,133 @@ +// +// RACSerialDisposable.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSerialDisposable.h" +#import <libkern/OSAtomic.h> + +@interface RACSerialDisposable () { + // A reference to the receiver's `disposable`. This variable must only be + // modified atomically. + // + // If this is `self`, no `disposable` has been set, but the receiver has not + // been disposed of yet. `self` is never stored retained. + // + // If this is `nil`, the receiver has been disposed. + // + // Otherwise, this is a retained reference to the inner disposable and the + // receiver has not been disposed of yet. + void * volatile _disposablePtr; +} + +@end + +@implementation RACSerialDisposable + +#pragma mark Properties + +- (BOOL)isDisposed { + return _disposablePtr == nil; +} + +- (RACDisposable *)disposable { + RACDisposable *disposable = (__bridge id)_disposablePtr; + return (disposable == self ? nil : disposable); +} + +- (void)setDisposable:(RACDisposable *)disposable { + [self swapInDisposable:disposable]; +} + +#pragma mark Lifecycle + ++ (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable { + RACSerialDisposable *serialDisposable = [[self alloc] init]; + serialDisposable.disposable = disposable; + return serialDisposable; +} + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + _disposablePtr = (__bridge void *)self; + OSMemoryBarrier(); + + return self; +} + +- (id)initWithBlock:(void (^)(void))block { + self = [self init]; + if (self == nil) return nil; + + self.disposable = [RACDisposable disposableWithBlock:block]; + + return self; +} + +- (void)dealloc { + self.disposable = nil; +} + +#pragma mark Inner Disposable + +- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable { + void * const selfPtr = (__bridge void *)self; + + // Only retain the new disposable if it's not `self`. + // Take ownership before attempting the swap so that a subsequent swap + // receives an owned reference. + void *newDisposablePtr = selfPtr; + if (newDisposable != nil) { + newDisposablePtr = (void *)CFBridgingRetain(newDisposable); + } + + void *existingDisposablePtr; + // Keep trying while we're not disposed. + while ((existingDisposablePtr = _disposablePtr) != NULL) { + if (!OSAtomicCompareAndSwapPtrBarrier(existingDisposablePtr, newDisposablePtr, &_disposablePtr)) { + continue; + } + + // Return nil if _disposablePtr was set to self. Otherwise, release + // the old value and return it as an object. + if (existingDisposablePtr == selfPtr) { + return nil; + } else { + return CFBridgingRelease(existingDisposablePtr); + } + } + + // At this point, we've found out that we were already disposed. + [newDisposable dispose]; + + // Failed to swap, clean up the ownership we took prior to the swap. + if (newDisposable != nil) { + CFRelease(newDisposablePtr); + } + + return nil; +} + +#pragma mark Disposal + +- (void)dispose { + void *existingDisposablePtr; + + while ((existingDisposablePtr = _disposablePtr) != NULL) { + if (OSAtomicCompareAndSwapPtrBarrier(existingDisposablePtr, NULL, &_disposablePtr)) { + if (existingDisposablePtr != (__bridge void *)self) { + RACDisposable *existingDisposable = CFBridgingRelease(existingDisposablePtr); + [existingDisposable dispose]; + } + + break; + } + } +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h new file mode 100644 index 0000000..53b10d6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.h
@@ -0,0 +1,625 @@ +// +// RACSignal+Operations.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-09-06. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACSignal.h" + +/// The domain for errors originating in RACSignal operations. +extern NSString * const RACSignalErrorDomain; + +/// The error code used with -timeout:. +extern const NSInteger RACSignalErrorTimedOut; + +/// The error code used when a value passed into +switch:cases:default: does not +/// match any of the cases, and no default was given. +extern const NSInteger RACSignalErrorNoMatchingCase; + +@class RACMulticastConnection; +@class RACDisposable; +@class RACScheduler; +@class RACSequence; +@class RACSubject; +@class RACTuple; +@class RACCommand; +@protocol RACSubscriber; + +@interface RACSignal (Operations) + +/// Do the given block on `next`. This should be used to inject side effects into +/// the signal. +- (RACSignal *)doNext:(void (^)(id x))block; + +/// Do the given block on `error`. This should be used to inject side effects +/// into the signal. +- (RACSignal *)doError:(void (^)(NSError *error))block; + +/// Do the given block on `completed`. This should be used to inject side effects +/// into the signal. +- (RACSignal *)doCompleted:(void (^)(void))block; + +/// Send `next`s only if we don't receive another `next` in `interval` seconds. +/// +/// If a `next` is received, and then another `next` is received before +/// `interval` seconds have passed, the first value is discarded. +/// +/// After `interval` seconds have passed since the most recent `next` was sent, +/// the most recent `next` is forwarded on the scheduler that the value was +/// originally received on. If +[RACScheduler currentScheduler] was nil at the +/// time, a private background scheduler is used. +/// +/// Returns a signal which sends throttled and delayed `next` events. Completion +/// and errors are always forwarded immediately. +- (RACSignal *)throttle:(NSTimeInterval)interval; + +/// Throttles `next`s for which `predicate` returns YES. +/// +/// When `predicate` returns YES for a `next`: +/// +/// 1. If another `next` is received before `interval` seconds have passed, the +/// prior value is discarded. This happens regardless of whether the new +/// value will be throttled. +/// 2. After `interval` seconds have passed since the value was originally +/// received, it will be forwarded on the scheduler that it was received +/// upon. If +[RACScheduler currentScheduler] was nil at the time, a private +/// background scheduler is used. +/// +/// When `predicate` returns NO for a `next`, it is forwarded immediately, +/// without any throttling. +/// +/// interval - The number of seconds for which to buffer the latest value that +/// passes `predicate`. +/// predicate - Passed each `next` from the receiver, this block returns +/// whether the given value should be throttled. This argument must +/// not be nil. +/// +/// Returns a signal which sends `next` events, throttled when `predicate` +/// returns YES. Completion and errors are always forwarded immediately. +- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate; + +/// Forwards `next` and `completed` events after delaying for `interval` seconds +/// on the current scheduler (on which the events were delivered). +/// +/// If +[RACScheduler currentScheduler] is nil when `next` or `completed` is +/// received, a private background scheduler is used. +/// +/// Returns a signal which sends delayed `next` and `completed` events. Errors +/// are always forwarded immediately. +- (RACSignal *)delay:(NSTimeInterval)interval; + +/// Resubscribes when the signal completes. +- (RACSignal *)repeat; + +/// Execute the given block each time a subscription is created. +/// +/// block - A block which defines the subscription side effects. Cannot be `nil`. +/// +/// Example: +/// +/// // Write new file, with backup. +/// [[[[fileManager +/// rac_createFileAtPath:path contents:data] +/// initially:^{ +/// // 2. Second, backup current file +/// [fileManager moveItemAtPath:path toPath:backupPath error:nil]; +/// }] +/// initially:^{ +/// // 1. First, acquire write lock. +/// [writeLock lock]; +/// }] +/// finally:^{ +/// [writeLock unlock]; +/// }]; +/// +/// Returns a signal that passes through all events of the receiver, plus +/// introduces side effects which occur prior to any subscription side effects +/// of the receiver. +- (RACSignal *)initially:(void (^)(void))block; + +/// Execute the given block when the signal completes or errors. +- (RACSignal *)finally:(void (^)(void))block; + +/// Divides the receiver's `next`s into buffers which deliver every `interval` +/// seconds. +/// +/// interval - The interval in which values are grouped into one buffer. +/// scheduler - The scheduler upon which the returned signal will deliver its +/// values. This must not be nil or +[RACScheduler +/// immediateScheduler]. +/// +/// Returns a signal which sends RACTuples of the buffered values at each +/// interval on `scheduler`. When the receiver completes, any currently-buffered +/// values will be sent immediately. +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; + +/// Collect all receiver's `next`s into a NSArray. nil values will be converted +/// to NSNull. +/// +/// This corresponds to the `ToArray` method in Rx. +/// +/// Returns a signal which sends a single NSArray when the receiver completes +/// successfully. +- (RACSignal *)collect; + +/// Takes the last `count` `next`s after the receiving signal completes. +- (RACSignal *)takeLast:(NSUInteger)count; + +/// Combines the latest values from the receiver and the given signal into +/// RACTuples, once both have sent at least one `next`. +/// +/// Any additional `next`s will result in a new RACTuple with the latest values +/// from both signals. +/// +/// signal - The signal to combine with. This argument must not be nil. +/// +/// Returns a signal which sends RACTuples of the combined values, forwards any +/// `error` events, and completes when both input signals complete. +- (RACSignal *)combineLatestWith:(RACSignal *)signal; + +/// Combines the latest values from the given signals into RACTuples, once all +/// the signals have sent at least one `next`. +/// +/// Any additional `next`s will result in a new RACTuple with the latest values +/// from all signals. +/// +/// signals - The signals to combine. If this collection is empty, the returned +/// signal will immediately complete upon subscription. +/// +/// Returns a signal which sends RACTuples of the combined values, forwards any +/// `error` events, and completes when all input signals complete. ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals; + +/// Combines signals using +combineLatest:, then reduces the resulting tuples +/// into a single value using -reduceEach:. +/// +/// signals - The signals to combine. If this collection is empty, the +/// returned signal will immediately complete upon subscription. +/// reduceBlock - The block which reduces the latest values from all the +/// signals into one value. It must take as many arguments as the +/// number of signals given. Each argument will be an object +/// argument. The return value must be an object. This argument +/// must not be nil. +/// +/// Example: +/// +/// [RACSignal combineLatest:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) { +/// return [NSString stringWithFormat:@"%@: %@", string, number]; +/// }]; +/// +/// Returns a signal which sends the results from each invocation of +/// `reduceBlock`. ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock; + +/// Merges the receiver and the given signal with `+merge:` and returns the +/// resulting signal. +- (RACSignal *)merge:(RACSignal *)signal; + +/// Sends the latest `next` from any of the signals. +/// +/// Returns a signal that passes through values from each of the given signals, +/// and sends `completed` when all of them complete. If any signal sends an error, +/// the returned signal sends `error` immediately. ++ (RACSignal *)merge:(id<NSFastEnumeration>)signals; + +/// Merges the signals sent by the receiver into a flattened signal, but only +/// subscribes to `maxConcurrent` number of signals at a time. New signals are +/// queued and subscribed to as other signals complete. +/// +/// If an error occurs on any of the signals, it is sent on the returned signal. +/// It completes only after the receiver and all sent signals have completed. +/// +/// This corresponds to `Merge<TSource>(IObservable<IObservable<TSource>>, Int32)` +/// in Rx. +/// +/// maxConcurrent - the maximum number of signals to subscribe to at a +/// time. If 0, it subscribes to an unlimited number of +/// signals. +- (RACSignal *)flatten:(NSUInteger)maxConcurrent; + +/// Ignores all `next`s from the receiver, waits for the receiver to complete, +/// then subscribes to a new signal. +/// +/// block - A block which will create or obtain a new signal to subscribe to, +/// executed only after the receiver completes. This block must not be +/// nil, and it must not return a nil signal. +/// +/// Returns a signal which will pass through the events of the signal created in +/// `block`. If the receiver errors out, the returned signal will error as well. +- (RACSignal *)then:(RACSignal * (^)(void))block; + +/// Concats the inner signals of a signal of signals. +- (RACSignal *)concat; + +/// Aggregate `next`s with the given start and combination. +- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock; + +/// Aggregate `next`s with the given start and combination. The start factory +/// block is called to get a new start object for each subscription. +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock; + +/// Invokes -setKeyPath:onObject:nilValue: with `nil` for the nil value. +/// +/// WARNING: Under certain conditions, this method is known to be thread-unsafe. +/// See the description in -setKeyPath:onObject:nilValue:. +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object; + +/// Binds the receiver to an object, automatically setting the given key path on +/// every `next`. When the signal completes, the binding is automatically +/// disposed of. +/// +/// WARNING: Under certain conditions, this method is known to be thread-unsafe. +/// A crash can result if `object` is deallocated concurrently on +/// another thread within a window of time between a value being sent +/// on this signal and immediately prior to the invocation of +/// -setValue:forKeyPath:, which sets the property. To prevent this, +/// ensure `object` is deallocated on the same thread the receiver +/// sends on, or ensure that the returned disposable is disposed of +/// before `object` deallocates. +/// See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1184 +/// +/// Sending an error on the signal is considered undefined behavior, and will +/// generate an assertion failure in Debug builds. +/// +/// A given key on an object should only have one active signal bound to it at any +/// given time. Binding more than one signal to the same property is considered +/// undefined behavior. +/// +/// keyPath - The key path to update with `next`s from the receiver. +/// object - The object that `keyPath` is relative to. +/// nilValue - The value to set at the key path whenever `nil` is sent by the +/// receiver. This may be nil when binding to object properties, but +/// an NSValue should be used for primitive properties, to avoid an +/// exception if `nil` is sent (which might occur if an intermediate +/// object is set to `nil`). +/// +/// Returns a disposable which can be used to terminate the binding. +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue; + +/// Sends NSDate.date every `interval` seconds. +/// +/// interval - The time interval in seconds at which the current time is sent. +/// scheduler - The scheduler upon which the current NSDate should be sent. This +/// must not be nil or +[RACScheduler immediateScheduler]. +/// +/// Returns a signal that sends the current date/time every `interval` on +/// `scheduler`. ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; + +/// Sends NSDate.date at intervals of at least `interval` seconds, up to +/// approximately `interval` + `leeway` seconds. +/// +/// The created signal will defer sending each `next` for at least `interval` +/// seconds, and for an additional amount of time up to `leeway` seconds in the +/// interest of performance or power consumption. Note that some additional +/// latency is to be expected, even when specifying a `leeway` of 0. +/// +/// interval - The base interval between `next`s. +/// scheduler - The scheduler upon which the current NSDate should be sent. This +/// must not be nil or +[RACScheduler immediateScheduler]. +/// leeway - The maximum amount of additional time the `next` can be deferred. +/// +/// Returns a signal that sends the current date/time at intervals of at least +/// `interval seconds` up to approximately `interval` + `leeway` seconds on +/// `scheduler`. ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway; + +/// Take `next`s until the `signalTrigger` sends `next` or `completed`. +/// +/// Returns a signal which passes through all events from the receiver until +/// `signalTrigger` sends `next` or `completed`, at which point the returned signal +/// will send `completed`. +- (RACSignal *)takeUntil:(RACSignal *)signalTrigger; + +/// Take `next`s until the `replacement` sends an event. +/// +/// replacement - The signal which replaces the receiver as soon as it sends an +/// event. +/// +/// Returns a signal which passes through `next`s and `error` from the receiver +/// until `replacement` sends an event, at which point the returned signal will +/// send that event and switch to passing through events from `replacement` +/// instead, regardless of whether the receiver has sent events already. +- (RACSignal *)takeUntilReplacement:(RACSignal *)replacement; + +/// Subscribe to the returned signal when an error occurs. +- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock; + +/// Subscribe to the given signal when an error occurs. +- (RACSignal *)catchTo:(RACSignal *)signal; + +/// Runs `tryBlock` against each of the receiver's values, passing values +/// until `tryBlock` returns NO, or the receiver completes. +/// +/// tryBlock - An action to run against each of the receiver's values. +/// The block should return YES to indicate that the action was +/// successful. This block must not be nil. +/// +/// Example: +/// +/// // The returned signal will send an error if data values cannot be +/// // written to `someFileURL`. +/// [signal try:^(NSData *data, NSError **errorPtr) { +/// return [data writeToURL:someFileURL options:NSDataWritingAtomic error:errorPtr]; +/// }]; +/// +/// Returns a signal which passes through all the values of the receiver. If +/// `tryBlock` fails for any value, the returned signal will error using the +/// `NSError` passed out from the block. +- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock; + +/// Runs `mapBlock` against each of the receiver's values, mapping values until +/// `mapBlock` returns nil, or the receiver completes. +/// +/// mapBlock - An action to map each of the receiver's values. The block should +/// return a non-nil value to indicate that the action was successful. +/// This block must not be nil. +/// +/// Example: +/// +/// // The returned signal will send an error if data cannot be read from +/// // `fileURL`. +/// [signal tryMap:^(NSURL *fileURL, NSError **errorPtr) { +/// return [NSData dataWithContentsOfURL:fileURL options:0 error:errorPtr]; +/// }]; +/// +/// Returns a signal which transforms all the values of the receiver. If +/// `mapBlock` returns nil for any value, the returned signal will error using +/// the `NSError` passed out from the block. +- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock; + +/// Returns the first `next`. Note that this is a blocking call. +- (id)first; + +/// Returns the first `next` or `defaultValue` if the signal completes or errors +/// without sending a `next`. Note that this is a blocking call. +- (id)firstOrDefault:(id)defaultValue; + +/// Returns the first `next` or `defaultValue` if the signal completes or errors +/// without sending a `next`. If an error occurs success will be NO and error +/// will be populated. Note that this is a blocking call. +/// +/// Both success and error may be NULL. +- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error; + +/// Blocks the caller and waits for the signal to complete. +/// +/// error - If not NULL, set to any error that occurs. +/// +/// Returns whether the signal completed successfully. If NO, `error` will be set +/// to the error that occurred. +- (BOOL)waitUntilCompleted:(NSError **)error; + +/// Defer creation of a signal until the signal's actually subscribed to. +/// +/// This can be used to effectively turn a hot signal into a cold signal. ++ (RACSignal *)defer:(RACSignal * (^)(void))block; + +/// Every time the receiver sends a new RACSignal, subscribes and sends `next`s and +/// `error`s only for that signal. +/// +/// The receiver must be a signal of signals. +/// +/// Returns a signal which passes through `next`s and `error`s from the latest +/// signal sent by the receiver, and sends `completed` when both the receiver and +/// the last sent signal complete. +- (RACSignal *)switchToLatest; + +/// Switches between the signals in `cases` as well as `defaultSignal` based on +/// the latest value sent by `signal`. +/// +/// signal - A signal of objects used as keys in the `cases` dictionary. +/// This argument must not be nil. +/// cases - A dictionary that has signals as values. This argument must +/// not be nil. A RACTupleNil key in this dictionary will match +/// nil `next` events that are received on `signal`. +/// defaultSignal - The signal to pass through after `signal` sends a value for +/// which `cases` does not contain a signal. If nil, any +/// unmatched values will result in +/// a RACSignalErrorNoMatchingCase error. +/// +/// Returns a signal which passes through `next`s and `error`s from one of the +/// the signals in `cases` or `defaultSignal`, and sends `completed` when both +/// `signal` and the last used signal complete. If no `defaultSignal` is given, +/// an unmatched `next` will result in an error on the returned signal. ++ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal; + +/// Switches between `trueSignal` and `falseSignal` based on the latest value +/// sent by `boolSignal`. +/// +/// boolSignal - A signal of BOOLs determining whether `trueSignal` or +/// `falseSignal` should be active. This argument must not be nil. +/// trueSignal - The signal to pass through after `boolSignal` has sent YES. +/// This argument must not be nil. +/// falseSignal - The signal to pass through after `boolSignal` has sent NO. This +/// argument must not be nil. +/// +/// Returns a signal which passes through `next`s and `error`s from `trueSignal` +/// and/or `falseSignal`, and sends `completed` when both `boolSignal` and the +/// last switched signal complete. ++ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal; + +/// Add every `next` to an array. Nils are represented by NSNulls. Note that this +/// is a blocking call. +/// +/// **This is not the same as the `ToArray` method in Rx.** See -collect for +/// that behavior instead. +/// +/// Returns the array of `next` values, or nil if an error occurs. +- (NSArray *)toArray; + +/// Add every `next` to a sequence. Nils are represented by NSNulls. +/// +/// This corresponds to the `ToEnumerable` method in Rx. +/// +/// Returns a sequence which provides values from the signal as they're sent. +/// Trying to retrieve a value from the sequence which has not yet been sent will +/// block. +@property (nonatomic, strong, readonly) RACSequence *sequence; + +/// Creates and returns a multicast connection. This allows you to share a single +/// subscription to the underlying signal. +- (RACMulticastConnection *)publish; + +/// Creates and returns a multicast connection that pushes values into the given +/// subject. This allows you to share a single subscription to the underlying +/// signal. +- (RACMulticastConnection *)multicast:(RACSubject *)subject; + +/// Multicasts the signal to a RACReplaySubject of unlimited capacity, and +/// immediately connects to the resulting RACMulticastConnection. +/// +/// Returns the connected, multicasted signal. +- (RACSignal *)replay; + +/// Multicasts the signal to a RACReplaySubject of capacity 1, and immediately +/// connects to the resulting RACMulticastConnection. +/// +/// Returns the connected, multicasted signal. +- (RACSignal *)replayLast; + +/// Multicasts the signal to a RACReplaySubject of unlimited capacity, and +/// lazily connects to the resulting RACMulticastConnection. +/// +/// This means the returned signal will subscribe to the multicasted signal only +/// when the former receives its first subscription. +/// +/// Returns the lazily connected, multicasted signal. +- (RACSignal *)replayLazily; + +/// Sends an error after `interval` seconds if the source doesn't complete +/// before then. +/// +/// The error will be in the RACSignalErrorDomain and have a code of +/// RACSignalErrorTimedOut. +/// +/// interval - The number of seconds after which the signal should error out. +/// scheduler - The scheduler upon which any timeout error should be sent. This +/// must not be nil or +[RACScheduler immediateScheduler]. +/// +/// Returns a signal that passes through the receiver's events, until the stream +/// finishes or times out, at which point an error will be sent on `scheduler`. +- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; + +/// Creates and returns a signal that delivers its events on the given scheduler. +/// Any side effects of the receiver will still be performed on the original +/// thread. +/// +/// This is ideal when the signal already performs its work on the desired +/// thread, but you want to handle its events elsewhere. +/// +/// This corresponds to the `ObserveOn` method in Rx. +- (RACSignal *)deliverOn:(RACScheduler *)scheduler; + +/// Creates and returns a signal that executes its side effects and delivers its +/// events on the given scheduler. +/// +/// Use of this operator should be avoided whenever possible, because the +/// receiver's side effects may not be safe to run on another thread. If you just +/// want to receive the signal's events on `scheduler`, use -deliverOn: instead. +- (RACSignal *)subscribeOn:(RACScheduler *)scheduler; + +/// Groups each received object into a group, as determined by calling `keyBlock` +/// with that object. The object sent is transformed by calling `transformBlock` +/// with the object. If `transformBlock` is nil, it sends the original object. +/// +/// The returned signal is a signal of RACGroupedSignal. +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock; + +/// Calls -[RACSignal groupBy:keyBlock transform:nil]. +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock; + +/// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any +/// objects. +- (RACSignal *)any; + +/// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any +/// objects that pass `predicateBlock`. +/// +/// predicateBlock - cannot be nil. +- (RACSignal *)any:(BOOL (^)(id object))predicateBlock; + +/// Sends an [NSNumber numberWithBool:YES] if all the objects the receiving +/// signal sends pass `predicateBlock`. +/// +/// predicateBlock - cannot be nil. +- (RACSignal *)all:(BOOL (^)(id object))predicateBlock; + +/// Resubscribes to the receiving signal if an error occurs, up until it has +/// retried the given number of times. +/// +/// retryCount - if 0, it keeps retrying until it completes. +- (RACSignal *)retry:(NSInteger)retryCount; + +/// Resubscribes to the receiving signal if an error occurs. +- (RACSignal *)retry; + +/// Sends the latest value from the receiver only when `sampler` sends a value. +/// The returned signal could repeat values if `sampler` fires more often than +/// the receiver. Values from `sampler` are ignored before the receiver sends +/// its first value. +/// +/// sampler - The signal that controls when the latest value from the receiver +/// is sent. Cannot be nil. +- (RACSignal *)sample:(RACSignal *)sampler; + +/// Ignores all `next`s from the receiver. +/// +/// Returns a signal which only passes through `error` or `completed` events from +/// the receiver. +- (RACSignal *)ignoreValues; + +/// Converts each of the receiver's events into a RACEvent object. +/// +/// Returns a signal which sends the receiver's events as RACEvents, and +/// completes after the receiver sends `completed` or `error`. +- (RACSignal *)materialize; + +/// Converts each RACEvent in the receiver back into "real" RACSignal events. +/// +/// Returns a signal which sends `next` for each value RACEvent, `error` for each +/// error RACEvent, and `completed` for each completed RACEvent. +- (RACSignal *)dematerialize; + +/// Inverts each NSNumber-wrapped BOOL sent by the receiver. It will assert if +/// the receiver sends anything other than NSNumbers. +/// +/// Returns a signal of inverted NSNumber-wrapped BOOLs. +- (RACSignal *)not; + +/// Performs a boolean AND on all of the RACTuple of NSNumbers in sent by the receiver. +/// +/// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers. +/// +/// Returns a signal that applies AND to each NSNumber in the tuple. +- (RACSignal *)and; + +/// Performs a boolean OR on all of the RACTuple of NSNumbers in sent by the receiver. +/// +/// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers. +/// +/// Returns a signal that applies OR to each NSNumber in the tuple. +- (RACSignal *)or; + +@end + +@interface RACSignal (OperationsDeprecated) + +- (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); +- (RACSignal *)buffer:(NSUInteger)bufferCount __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); +- (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock __attribute__((deprecated("Use -publish instead"))); ++ (RACSignal *)interval:(NSTimeInterval)interval __attribute__((deprecated("Use +interval:onScheduler: instead"))); ++ (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway __attribute__((deprecated("Use +interval:onScheduler:withLeeway: instead"))); +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval __attribute__((deprecated("Use -bufferWithTime:onScheduler: instead"))); +- (RACSignal *)timeout:(NSTimeInterval)interval __attribute__((deprecated("Use -timeout:onScheduler: instead"))); +- (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object __attribute__((deprecated("Renamed to -setKeyPath:onObject:"))); +- (RACSignal *)ignoreElements __attribute__((deprecated("Renamed to -ignoreValues"))); +- (RACSignal *)sequenceNext:(RACSignal * (^)(void))block __attribute__((deprecated("Renamed to -then:"))); +- (RACSignal *)aggregateWithStart:(id)start combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStart:reduce:"))); +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStartFactory:reduce:"))); +- (RACDisposable *)executeCommand:(RACCommand *)command __attribute__((deprecated("Use -flattenMap: or -subscribeNext: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.m new file mode 100644 index 0000000..6e36dd6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal+Operations.m
@@ -0,0 +1,1436 @@ +// +// RACSignal+Operations.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-09-06. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignal+Operations.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACEvent.h" +#import "RACGroupedSignal.h" +#import "RACMulticastConnection+Private.h" +#import "RACReplaySubject.h" +#import "RACScheduler+Private.h" +#import "RACScheduler.h" +#import "RACSerialDisposable.h" +#import "RACSignalSequence.h" +#import "RACStream+Private.h" +#import "RACSubject.h" +#import "RACSubscriber+Private.h" +#import "RACSubscriber.h" +#import "RACTuple.h" +#import "RACUnit.h" +#import <libkern/OSAtomic.h> +#import <objc/runtime.h> + +NSString * const RACSignalErrorDomain = @"RACSignalErrorDomain"; + +const NSInteger RACSignalErrorTimedOut = 1; +const NSInteger RACSignalErrorNoMatchingCase = 2; + +// Subscribes to the given signal with the given blocks. +// +// If the signal errors or completes, the corresponding block is invoked. If the +// disposable passed to the block is _not_ disposed, then the signal is +// subscribed to again. +static RACDisposable *subscribeForever (RACSignal *signal, void (^next)(id), void (^error)(NSError *, RACDisposable *), void (^completed)(RACDisposable *)) { + next = [next copy]; + error = [error copy]; + completed = [completed copy]; + + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + RACSchedulerRecursiveBlock recursiveBlock = ^(void (^recurse)(void)) { + RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable]; + [compoundDisposable addDisposable:selfDisposable]; + + __weak RACDisposable *weakSelfDisposable = selfDisposable; + + RACDisposable *subscriptionDisposable = [signal subscribeNext:next error:^(NSError *e) { + @autoreleasepool { + error(e, compoundDisposable); + [compoundDisposable removeDisposable:weakSelfDisposable]; + } + + recurse(); + } completed:^{ + @autoreleasepool { + completed(compoundDisposable); + [compoundDisposable removeDisposable:weakSelfDisposable]; + } + + recurse(); + }]; + + [selfDisposable addDisposable:subscriptionDisposable]; + }; + + // Subscribe once immediately, and then use recursive scheduling for any + // further resubscriptions. + recursiveBlock(^{ + RACScheduler *recursiveScheduler = RACScheduler.currentScheduler ?: [RACScheduler scheduler]; + + RACDisposable *schedulingDisposable = [recursiveScheduler scheduleRecursiveBlock:recursiveBlock]; + [compoundDisposable addDisposable:schedulingDisposable]; + }); + + return compoundDisposable; +} + +@implementation RACSignal (Operations) + +- (RACSignal *)doNext:(void (^)(id x))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + block(x); + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -doNext:", self.name]; +} + +- (RACSignal *)doError:(void (^)(NSError *error))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + block(error); + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -doError:", self.name]; +} + +- (RACSignal *)doCompleted:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + block(); + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -doCompleted:", self.name]; +} + +- (RACSignal *)throttle:(NSTimeInterval)interval { + return [[self throttle:interval valuesPassingTest:^(id _) { + return YES; + }] setNameWithFormat:@"[%@] -throttle: %f", self.name, (double)interval]; +} + +- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate { + NSCParameterAssert(interval >= 0); + NSCParameterAssert(predicate != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + // We may never use this scheduler, but we need to set it up ahead of + // time so that our scheduled blocks are run serially if we do. + RACScheduler *scheduler = [RACScheduler scheduler]; + + // Information about any currently-buffered `next` event. + __block id nextValue = nil; + __block BOOL hasNextValue = NO; + RACSerialDisposable *nextDisposable = [[RACSerialDisposable alloc] init]; + + void (^flushNext)(BOOL send) = ^(BOOL send) { + @synchronized (compoundDisposable) { + [nextDisposable.disposable dispose]; + + if (!hasNextValue) return; + if (send) [subscriber sendNext:nextValue]; + + nextValue = nil; + hasNextValue = NO; + } + }; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler; + BOOL shouldThrottle = predicate(x); + + @synchronized (compoundDisposable) { + flushNext(NO); + if (!shouldThrottle) { + [subscriber sendNext:x]; + return; + } + + nextValue = x; + hasNextValue = YES; + nextDisposable.disposable = [delayScheduler afterDelay:interval schedule:^{ + flushNext(YES); + }]; + } + } error:^(NSError *error) { + [compoundDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + flushNext(YES); + [subscriber sendCompleted]; + }]; + + [compoundDisposable addDisposable:subscriptionDisposable]; + return compoundDisposable; + }] setNameWithFormat:@"[%@] -throttle: %f valuesPassingTest:", self.name, (double)interval]; +} + +- (RACSignal *)delay:(NSTimeInterval)interval { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + // We may never use this scheduler, but we need to set it up ahead of + // time so that our scheduled blocks are run serially if we do. + RACScheduler *scheduler = [RACScheduler scheduler]; + + void (^schedule)(dispatch_block_t) = ^(dispatch_block_t block) { + RACScheduler *delayScheduler = RACScheduler.currentScheduler ?: scheduler; + RACDisposable *schedulerDisposable = [delayScheduler afterDelay:interval schedule:block]; + [disposable addDisposable:schedulerDisposable]; + }; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + schedule(^{ + [subscriber sendNext:x]; + }); + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + schedule(^{ + [subscriber sendCompleted]; + }); + }]; + + [disposable addDisposable:subscriptionDisposable]; + return disposable; + }] setNameWithFormat:@"[%@] -delay: %f", self.name, (double)interval]; +} + +- (RACSignal *)repeat { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return subscribeForever(self, + ^(id x) { + [subscriber sendNext:x]; + }, + ^(NSError *error, RACDisposable *disposable) { + [disposable dispose]; + [subscriber sendError:error]; + }, + ^(RACDisposable *disposable) { + // Resubscribe. + }); + }] setNameWithFormat:@"[%@] -repeat", self.name]; +} + +- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock { + NSCParameterAssert(catchBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *catchDisposable = [[RACSerialDisposable alloc] init]; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + RACSignal *signal = catchBlock(error); + NSCAssert(signal != nil, @"Expected non-nil signal from catch block on %@", self); + catchDisposable.disposable = [signal subscribe:subscriber]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [catchDisposable dispose]; + [subscriptionDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -catch:", self.name]; +} + +- (RACSignal *)catchTo:(RACSignal *)signal { + return [[self catch:^(NSError *error) { + return signal; + }] setNameWithFormat:@"[%@] -catchTo: %@", self.name, signal]; +} + +- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock { + NSCParameterAssert(tryBlock != NULL); + + return [[self flattenMap:^(id value) { + NSError *error = nil; + BOOL passed = tryBlock(value, &error); + return (passed ? [RACSignal return:value] : [RACSignal error:error]); + }] setNameWithFormat:@"[%@] -try:", self.name]; +} + +- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock { + NSCParameterAssert(mapBlock != NULL); + + return [[self flattenMap:^(id value) { + NSError *error = nil; + id mappedValue = mapBlock(value, &error); + return (mappedValue == nil ? [RACSignal error:error] : [RACSignal return:mappedValue]); + }] setNameWithFormat:@"[%@] -tryMap:", self.name]; +} + +- (RACSignal *)initially:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal defer:^{ + block(); + return self; + }] setNameWithFormat:@"[%@] -initially:", self.name]; +} + +- (RACSignal *)finally:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[[self + doError:^(NSError *error) { + block(); + }] + doCompleted:^{ + block(); + }] + setNameWithFormat:@"[%@] -finally:", self.name]; +} + +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *timerDisposable = [[RACSerialDisposable alloc] init]; + NSMutableArray *values = [NSMutableArray array]; + + void (^flushValues)() = ^{ + @synchronized (values) { + [timerDisposable.disposable dispose]; + + if (values.count == 0) return; + + RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:values convertNullsToNils:NO]; + [values removeAllObjects]; + [subscriber sendNext:tuple]; + } + }; + + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + @synchronized (values) { + if (values.count == 0) { + timerDisposable.disposable = [scheduler afterDelay:interval schedule:flushValues]; + } + + [values addObject:x ?: RACTupleNil.tupleNil]; + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + flushValues(); + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [selfDisposable dispose]; + [timerDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -bufferWithTime: %f", self.name, (double)interval]; +} + +- (RACSignal *)collect { + return [[self aggregateWithStartFactory:^{ + return [[NSMutableArray alloc] init]; + } reduce:^(NSMutableArray *collectedValues, id x) { + [collectedValues addObject:(x ?: NSNull.null)]; + return collectedValues; + }] setNameWithFormat:@"[%@] -collect", self.name]; +} + +- (RACSignal *)takeLast:(NSUInteger)count { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSMutableArray *valuesTaken = [NSMutableArray arrayWithCapacity:count]; + return [self subscribeNext:^(id x) { + [valuesTaken addObject:x ? : [RACTupleNil tupleNil]]; + + while(valuesTaken.count > count) { + [valuesTaken removeObjectAtIndex:0]; + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + for(id value in valuesTaken) { + [subscriber sendNext:[value isKindOfClass:[RACTupleNil class]] ? nil : value]; + } + + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -takeLast: %lu", self.name, (unsigned long)count]; +} + +- (RACSignal *)combineLatestWith:(RACSignal *)signal { + NSCParameterAssert(signal != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + __block id lastSelfValue = nil; + __block BOOL selfCompleted = NO; + + __block id lastOtherValue = nil; + __block BOOL otherCompleted = NO; + + void (^sendNext)(void) = ^{ + @synchronized (disposable) { + if (lastSelfValue == nil || lastOtherValue == nil) return; + [subscriber sendNext:[RACTuple tupleWithObjects:lastSelfValue, lastOtherValue, nil]]; + } + }; + + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + @synchronized (disposable) { + lastSelfValue = x ?: RACTupleNil.tupleNil; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (disposable) { + selfCompleted = YES; + if (otherCompleted) [subscriber sendCompleted]; + } + }]; + + [disposable addDisposable:selfDisposable]; + + RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { + @synchronized (disposable) { + lastOtherValue = x ?: RACTupleNil.tupleNil; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (disposable) { + otherCompleted = YES; + if (selfCompleted) [subscriber sendCompleted]; + } + }]; + + [disposable addDisposable:otherDisposable]; + + return disposable; + }] setNameWithFormat:@"[%@] -combineLatestWith: %@", self.name, signal]; +} + ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals { + return [[self join:signals block:^(RACSignal *left, RACSignal *right) { + return [left combineLatestWith:right]; + }] setNameWithFormat:@"+combineLatest: %@", signals]; +} + ++ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock { + NSCParameterAssert(reduceBlock != nil); + + RACSignal *result = [self combineLatest:signals]; + + // Although we assert this condition above, older versions of this method + // supported this argument being nil. Avoid crashing Release builds of + // apps that depended on that. + if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; + + return [result setNameWithFormat:@"+combineLatest: %@ reduce:", signals]; +} + +- (RACSignal *)merge:(RACSignal *)signal { + return [[RACSignal + merge:@[ self, signal ]] + setNameWithFormat:@"[%@] -merge: %@", self.name, signal]; +} + ++ (RACSignal *)merge:(id<NSFastEnumeration>)signals { + NSMutableArray *copiedSignals = [[NSMutableArray alloc] init]; + for (RACSignal *signal in signals) { + [copiedSignals addObject:signal]; + } + + return [[[RACSignal + createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + for (RACSignal *signal in copiedSignals) { + [subscriber sendNext:signal]; + } + + [subscriber sendCompleted]; + return nil; + }] + flatten] + setNameWithFormat:@"+merge: %@", copiedSignals]; +} + +- (RACSignal *)flatten:(NSUInteger)maxConcurrent { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init]; + + // Contains disposables for the currently active subscriptions. + // + // This should only be used while synchronized on `subscriber`. + NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent]; + + // Whether the signal-of-signals has completed yet. + // + // This should only be used while synchronized on `subscriber`. + __block BOOL selfCompleted = NO; + + // Subscribes to the given signal. + // + // This will be set to nil once all signals have completed (to break + // a retain cycle in the recursive block). + __block void (^subscribeToSignal)(RACSignal *); + + // Sends completed to the subscriber if all signals are finished. + // + // This should only be used while synchronized on `subscriber`. + void (^completeIfAllowed)(void) = ^{ + if (selfCompleted && activeDisposables.count == 0) { + [subscriber sendCompleted]; + subscribeToSignal = nil; + } + }; + + // The signals waiting to be started. + // + // This array should only be used while synchronized on `subscriber`. + NSMutableArray *queuedSignals = [NSMutableArray array]; + + subscribeToSignal = ^(RACSignal *signal) { + RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; + + @synchronized (subscriber) { + [compoundDisposable addDisposable:serialDisposable]; + [activeDisposables addObject:serialDisposable]; + } + + serialDisposable.disposable = [signal subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + RACSignal *nextSignal; + + @synchronized (subscriber) { + [compoundDisposable removeDisposable:serialDisposable]; + [activeDisposables removeObjectIdenticalTo:serialDisposable]; + + if (queuedSignals.count == 0) { + completeIfAllowed(); + return; + } + + nextSignal = queuedSignals[0]; + [queuedSignals removeObjectAtIndex:0]; + } + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-retain-cycles" + // This retain cycle is broken in `completeIfAllowed`. + subscribeToSignal(nextSignal); + #pragma clang diagnostic pop + }]; + }; + + [compoundDisposable addDisposable:[self subscribeNext:^(RACSignal *signal) { + if (signal == nil) return; + + NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal); + + @synchronized (subscriber) { + if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) { + [queuedSignals addObject:signal]; + + // If we need to wait, skip subscribing to this + // signal. + return; + } + } + + subscribeToSignal(signal); + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (subscriber) { + selfCompleted = YES; + completeIfAllowed(); + } + }]]; + + return compoundDisposable; + }] setNameWithFormat:@"[%@] -flatten: %lu", self.name, (unsigned long)maxConcurrent]; +} + +- (RACSignal *)then:(RACSignal * (^)(void))block { + NSCParameterAssert(block != nil); + + return [[[self + ignoreValues] + concat:[RACSignal defer:block]] + setNameWithFormat:@"[%@] -then:", self.name]; +} + +- (RACSignal *)concat { + return [[self flatten:1] setNameWithFormat:@"[%@] -concat", self.name]; +} + +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock { + NSCParameterAssert(startFactory != NULL); + NSCParameterAssert(reduceBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block id runningValue = startFactory(); + return [self subscribeNext:^(id x) { + runningValue = reduceBlock(runningValue, x); + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendNext:runningValue]; + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -aggregateWithStartFactory:reduce:", self.name]; +} + +- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock { + RACSignal *signal = [self aggregateWithStartFactory:^{ + return start; + } reduce:reduceBlock]; + + return [signal setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduce:", self.name, [start rac_description]]; +} + +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object { + return [self setKeyPath:keyPath onObject:object nilValue:nil]; +} + +- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue { + NSCParameterAssert(keyPath != nil); + NSCParameterAssert(object != nil); + + keyPath = [keyPath copy]; + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + // Purposely not retaining 'object', since we want to tear down the binding + // when it deallocates normally. + __block void * volatile objectPtr = (__bridge void *)object; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + // Possibly spec, possibly compiler bug, but this __bridge cast does not + // result in a retain here, effectively an invisible __unsafe_unretained + // qualifier. Using objc_precise_lifetime gives the __strong reference + // desired. The explicit use of __strong is strictly defensive. + __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr; + [object setValue:x ?: nilValue forKeyPath:keyPath]; + } error:^(NSError *error) { + __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr; + + NSCAssert(NO, @"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error); + + // Log the error if we're running with assertions disabled. + NSLog(@"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error); + + [disposable dispose]; + } completed:^{ + [disposable dispose]; + }]; + + [disposable addDisposable:subscriptionDisposable]; + + #if DEBUG + static void *bindingsKey = &bindingsKey; + NSMutableDictionary *bindings; + + @synchronized (object) { + bindings = objc_getAssociatedObject(object, bindingsKey); + if (bindings == nil) { + bindings = [NSMutableDictionary dictionary]; + objc_setAssociatedObject(object, bindingsKey, bindings, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + } + + @synchronized (bindings) { + NSCAssert(bindings[keyPath] == nil, @"Signal %@ is already bound to key path \"%@\" on object %@, adding signal %@ is undefined behavior", [bindings[keyPath] nonretainedObjectValue], keyPath, object, self); + + bindings[keyPath] = [NSValue valueWithNonretainedObject:self]; + } + #endif + + RACDisposable *clearPointerDisposable = [RACDisposable disposableWithBlock:^{ + #if DEBUG + @synchronized (bindings) { + [bindings removeObjectForKey:keyPath]; + } + #endif + + while (YES) { + void *ptr = objectPtr; + if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr)) { + break; + } + } + }]; + + [disposable addDisposable:clearPointerDisposable]; + + [object.rac_deallocDisposable addDisposable:disposable]; + + RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; + return [RACDisposable disposableWithBlock:^{ + [objectDisposable removeDisposable:disposable]; + [disposable dispose]; + }]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { + return [[RACSignal interval:interval onScheduler:scheduler withLeeway:0.0] setNameWithFormat:@"+interval: %f onScheduler: %@", (double)interval, scheduler]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [scheduler after:[NSDate dateWithTimeIntervalSinceNow:interval] repeatingEvery:interval withLeeway:leeway schedule:^{ + [subscriber sendNext:[NSDate date]]; + }]; + }] setNameWithFormat:@"+interval: %f onScheduler: %@ withLeeway: %f", (double)interval, scheduler, (double)leeway]; +} + +- (RACSignal *)takeUntil:(RACSignal *)signalTrigger { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + void (^triggerCompletion)(void) = ^{ + [disposable dispose]; + [subscriber sendCompleted]; + }; + + RACDisposable *triggerDisposable = [signalTrigger subscribeNext:^(id _) { + triggerCompletion(); + } completed:^{ + triggerCompletion(); + }]; + + [disposable addDisposable:triggerDisposable]; + + if (!disposable.disposed) { + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [disposable dispose]; + [subscriber sendCompleted]; + }]; + + [disposable addDisposable:selfDisposable]; + } + + return disposable; + }] setNameWithFormat:@"[%@] -takeUntil: %@", self.name, signalTrigger]; +} + +- (RACSignal *)takeUntilReplacement:(RACSignal *)replacement { + return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; + + RACDisposable *replacementDisposable = [replacement subscribeNext:^(id x) { + [selfDisposable dispose]; + [subscriber sendNext:x]; + } error:^(NSError *error) { + [selfDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [selfDisposable dispose]; + [subscriber sendCompleted]; + }]; + + if (!selfDisposable.disposed) { + selfDisposable.disposable = [[self + concat:[RACSignal never]] + subscribe:subscriber]; + } + + return [RACDisposable disposableWithBlock:^{ + [selfDisposable dispose]; + [replacementDisposable dispose]; + }]; + }]; +} + +- (RACSignal *)switchToLatest { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACMulticastConnection *connection = [self publish]; + + RACDisposable *subscriptionDisposable = [[connection.signal + flattenMap:^(RACSignal *x) { + NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x); + + // -concat:[RACSignal never] prevents completion of the receiver from + // prematurely terminating the inner signal. + return [x takeUntil:[connection.signal concat:[RACSignal never]]]; + }] + subscribe:subscriber]; + + RACDisposable *connectionDisposable = [connection connect]; + return [RACDisposable disposableWithBlock:^{ + [subscriptionDisposable dispose]; + [connectionDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -switchToLatest", self.name]; +} + ++ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal { + NSCParameterAssert(signal != nil); + NSCParameterAssert(cases != nil); + + for (id key in cases) { + id value __attribute__((unused)) = cases[key]; + NSCAssert([value isKindOfClass:RACSignal.class], @"Expected all cases to be RACSignals, %@ isn't", value); + } + + NSDictionary *copy = [cases copy]; + + return [[[signal + map:^(id key) { + if (key == nil) key = RACTupleNil.tupleNil; + + RACSignal *signal = copy[key] ?: defaultSignal; + if (signal == nil) { + NSString *description = [NSString stringWithFormat:NSLocalizedString(@"No matching signal found for value %@", @""), key]; + return [RACSignal error:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorNoMatchingCase userInfo:@{ NSLocalizedDescriptionKey: description }]]; + } + + return signal; + }] + switchToLatest] + setNameWithFormat:@"+switch: %@ cases: %@ default: %@", signal, cases, defaultSignal]; +} + ++ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal { + NSCParameterAssert(boolSignal != nil); + NSCParameterAssert(trueSignal != nil); + NSCParameterAssert(falseSignal != nil); + + return [[[boolSignal + map:^(NSNumber *value) { + NSCAssert([value isKindOfClass:NSNumber.class], @"Expected %@ to send BOOLs, not %@", boolSignal, value); + + return (value.boolValue ? trueSignal : falseSignal); + }] + switchToLatest] + setNameWithFormat:@"+if: %@ then: %@ else: %@", boolSignal, trueSignal, falseSignal]; +} + +- (id)first { + return [self firstOrDefault:nil]; +} + +- (id)firstOrDefault:(id)defaultValue { + return [self firstOrDefault:defaultValue success:NULL error:NULL]; +} + +- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error { + NSCondition *condition = [[NSCondition alloc] init]; + condition.name = [NSString stringWithFormat:@"[%@] -firstOrDefault: %@ success:error:", self.name, defaultValue]; + + __block id value = defaultValue; + __block BOOL done = NO; + + // Ensures that we don't pass values across thread boundaries by reference. + __block NSError *localError; + __block BOOL localSuccess; + + [[self take:1] subscribeNext:^(id x) { + [condition lock]; + + value = x; + localSuccess = YES; + + done = YES; + [condition broadcast]; + [condition unlock]; + } error:^(NSError *e) { + [condition lock]; + + if (!done) { + localSuccess = NO; + localError = e; + + done = YES; + [condition broadcast]; + } + + [condition unlock]; + } completed:^{ + [condition lock]; + + localSuccess = YES; + + done = YES; + [condition broadcast]; + [condition unlock]; + }]; + + [condition lock]; + while (!done) { + [condition wait]; + } + + if (success != NULL) *success = localSuccess; + if (error != NULL) *error = localError; + + [condition unlock]; + return value; +} + +- (BOOL)waitUntilCompleted:(NSError **)error { + BOOL success = NO; + + [[[self + ignoreValues] + setNameWithFormat:@"[%@] -waitUntilCompleted:", self.name] + firstOrDefault:nil success:&success error:error]; + + return success; +} + ++ (RACSignal *)defer:(RACSignal * (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [block() subscribe:subscriber]; + }] setNameWithFormat:@"+defer:"]; +} + +- (NSArray *)toArray { + return [[[self collect] first] copy]; +} + +- (RACSequence *)sequence { + return [[RACSignalSequence sequenceWithSignal:self] setNameWithFormat:@"[%@] -sequence", self.name]; +} + +- (RACMulticastConnection *)publish { + RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name]; + RACMulticastConnection *connection = [self multicast:subject]; + return connection; +} + +- (RACMulticastConnection *)multicast:(RACSubject *)subject { + [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name]; + RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject]; + return connection; +} + +- (RACSignal *)replay { + RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name]; + + RACMulticastConnection *connection = [self multicast:subject]; + [connection connect]; + + return connection.signal; +} + +- (RACSignal *)replayLast { + RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name]; + + RACMulticastConnection *connection = [self multicast:subject]; + [connection connect]; + + return connection.signal; +} + +- (RACSignal *)replayLazily { + RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]]; + return [[RACSignal + defer:^{ + [connection connect]; + return connection.signal; + }] + setNameWithFormat:@"[%@] -replayLazily", self.name]; +} + +- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(scheduler != RACScheduler.immediateScheduler); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + RACDisposable *timeoutDisposable = [scheduler afterDelay:interval schedule:^{ + [disposable dispose]; + [subscriber sendError:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorTimedOut userInfo:nil]]; + }]; + + [disposable addDisposable:timeoutDisposable]; + + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [disposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [disposable dispose]; + [subscriber sendCompleted]; + }]; + + [disposable addDisposable:subscriptionDisposable]; + return disposable; + }] setNameWithFormat:@"[%@] -timeout: %f", self.name, (double)interval]; +} + +- (RACSignal *)deliverOn:(RACScheduler *)scheduler { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [scheduler schedule:^{ + [subscriber sendNext:x]; + }]; + } error:^(NSError *error) { + [scheduler schedule:^{ + [subscriber sendError:error]; + }]; + } completed:^{ + [scheduler schedule:^{ + [subscriber sendCompleted]; + }]; + }]; + }] setNameWithFormat:@"[%@] -deliverOn: %@", self.name, scheduler]; +} + +- (RACSignal *)subscribeOn:(RACScheduler *)scheduler { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + + RACDisposable *schedulingDisposable = [scheduler schedule:^{ + RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + [disposable addDisposable:subscriptionDisposable]; + }]; + + [disposable addDisposable:schedulingDisposable]; + return disposable; + }] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler]; +} + +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock { + NSCParameterAssert(keyBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSMutableDictionary *groups = [NSMutableDictionary dictionary]; + + return [self subscribeNext:^(id x) { + id<NSCopying> key = keyBlock(x); + RACGroupedSignal *groupSubject = nil; + @synchronized(groups) { + groupSubject = [groups objectForKey:key]; + if(groupSubject == nil) { + groupSubject = [RACGroupedSignal signalWithKey:key]; + [groups setObject:groupSubject forKey:key]; + [subscriber sendNext:groupSubject]; + } + } + + [groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + + [groups.allValues makeObjectsPerformSelector:@selector(sendError:) withObject:error]; + } completed:^{ + [subscriber sendCompleted]; + + [groups.allValues makeObjectsPerformSelector:@selector(sendCompleted)]; + }]; + }] setNameWithFormat:@"[%@] -groupBy:transform:", self.name]; +} + +- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock { + return [[self groupBy:keyBlock transform:nil] setNameWithFormat:@"[%@] -groupBy:", self.name]; +} + +- (RACSignal *)any { + return [[self any:^(id x) { + return YES; + }] setNameWithFormat:@"[%@] -any", self.name]; +} + +- (RACSignal *)any:(BOOL (^)(id object))predicateBlock { + NSCParameterAssert(predicateBlock != NULL); + + return [[[self materialize] bind:^{ + return ^(RACEvent *event, BOOL *stop) { + if (event.finished) { + *stop = YES; + return [RACSignal return:@NO]; + } + + if (predicateBlock(event.value)) { + *stop = YES; + return [RACSignal return:@YES]; + } + + return [RACSignal empty]; + }; + }] setNameWithFormat:@"[%@] -any:", self.name]; +} + +- (RACSignal *)all:(BOOL (^)(id object))predicateBlock { + NSCParameterAssert(predicateBlock != NULL); + + return [[[self materialize] bind:^{ + return ^(RACEvent *event, BOOL *stop) { + if (event.eventType == RACEventTypeCompleted) { + *stop = YES; + return [RACSignal return:@YES]; + } + + if (event.eventType == RACEventTypeError || !predicateBlock(event.value)) { + *stop = YES; + return [RACSignal return:@NO]; + } + + return [RACSignal empty]; + }; + }] setNameWithFormat:@"[%@] -all:", self.name]; +} + +- (RACSignal *)retry:(NSInteger)retryCount { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block NSInteger currentRetryCount = 0; + return subscribeForever(self, + ^(id x) { + [subscriber sendNext:x]; + }, + ^(NSError *error, RACDisposable *disposable) { + if (retryCount == 0 || currentRetryCount < retryCount) { + // Resubscribe. + currentRetryCount++; + return; + } + + [disposable dispose]; + [subscriber sendError:error]; + }, + ^(RACDisposable *disposable) { + [disposable dispose]; + [subscriber sendCompleted]; + }); + }] setNameWithFormat:@"[%@] -retry: %lu", self.name, (unsigned long)retryCount]; +} + +- (RACSignal *)retry { + return [[self retry:0] setNameWithFormat:@"[%@] -retry", self.name]; +} + +- (RACSignal *)sample:(RACSignal *)sampler { + NSCParameterAssert(sampler != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSLock *lock = [[NSLock alloc] init]; + __block id lastValue; + __block BOOL hasValue = NO; + + RACSerialDisposable *samplerDisposable = [[RACSerialDisposable alloc] init]; + RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { + [lock lock]; + hasValue = YES; + lastValue = x; + [lock unlock]; + } error:^(NSError *error) { + [samplerDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [samplerDisposable dispose]; + [subscriber sendCompleted]; + }]; + + samplerDisposable.disposable = [sampler subscribeNext:^(id _) { + BOOL shouldSend = NO; + id value; + [lock lock]; + shouldSend = hasValue; + value = lastValue; + [lock unlock]; + + if (shouldSend) { + [subscriber sendNext:value]; + } + } error:^(NSError *error) { + [sourceDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + [sourceDisposable dispose]; + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [samplerDisposable dispose]; + [sourceDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -sample: %@", self.name, sampler]; +} + +- (RACSignal *)ignoreValues { + return [[self filter:^(id _) { + return NO; + }] setNameWithFormat:@"[%@] -ignoreValues", self.name]; +} + +- (RACSignal *)materialize { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [self subscribeNext:^(id x) { + [subscriber sendNext:[RACEvent eventWithValue:x]]; + } error:^(NSError *error) { + [subscriber sendNext:[RACEvent eventWithError:error]]; + [subscriber sendCompleted]; + } completed:^{ + [subscriber sendNext:RACEvent.completedEvent]; + [subscriber sendCompleted]; + }]; + }] setNameWithFormat:@"[%@] -materialize", self.name]; +} + +- (RACSignal *)dematerialize { + return [[self bind:^{ + return ^(RACEvent *event, BOOL *stop) { + switch (event.eventType) { + case RACEventTypeCompleted: + *stop = YES; + return [RACSignal empty]; + + case RACEventTypeError: + *stop = YES; + return [RACSignal error:event.error]; + + case RACEventTypeNext: + return [RACSignal return:event.value]; + } + }; + }] setNameWithFormat:@"[%@] -dematerialize", self.name]; +} + +- (RACSignal *)not { + return [[self map:^(NSNumber *value) { + NSCAssert([value isKindOfClass:NSNumber.class], @"-not must only be used on a signal of NSNumbers. Instead, got: %@", value); + + return @(!value.boolValue); + }] setNameWithFormat:@"[%@] -not", self.name]; +} + +- (RACSignal *)and { + return [[self map:^(RACTuple *tuple) { + NSCAssert([tuple isKindOfClass:RACTuple.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple); + NSCAssert(tuple.count > 0, @"-and must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple"); + + return @([tuple.rac_sequence all:^(NSNumber *number) { + NSCAssert([number isKindOfClass:NSNumber.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple); + + return number.boolValue; + }]); + }] setNameWithFormat:@"[%@] -and", self.name]; +} + +- (RACSignal *)or { + return [[self map:^(RACTuple *tuple) { + NSCAssert([tuple isKindOfClass:RACTuple.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple); + NSCAssert(tuple.count > 0, @"-or must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple"); + + return @([tuple.rac_sequence any:^(NSNumber *number) { + NSCAssert([number isKindOfClass:NSNumber.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple); + + return number.boolValue; + }]); + }] setNameWithFormat:@"[%@] -or", self.name]; +} + +@end + +@implementation RACSignal (OperationsDeprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock { + NSCParameterAssert(openSignal != nil); + NSCParameterAssert(closeBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block RACSubject *currentWindow = nil; + __block RACSignal *currentCloseWindow = nil; + __block RACDisposable *closeObserverDisposable = NULL; + + void (^closeCurrentWindow)(void) = ^{ + [currentWindow sendCompleted]; + currentWindow = nil; + currentCloseWindow = nil; + [closeObserverDisposable dispose], closeObserverDisposable = nil; + }; + + RACDisposable *openObserverDisposable = [openSignal subscribe:[RACSubscriber subscriberWithNext:^(id x) { + if(currentWindow == nil) { + currentWindow = [RACSubject subject]; + [subscriber sendNext:currentWindow]; + + currentCloseWindow = closeBlock(currentWindow); + closeObserverDisposable = [currentCloseWindow subscribe:[RACSubscriber subscriberWithNext:^(id x) { + closeCurrentWindow(); + } error:^(NSError *error) { + closeCurrentWindow(); + } completed:^{ + closeCurrentWindow(); + }]]; + } + } error:^(NSError *error) { + + } completed:^{ + + }]]; + + RACDisposable *selfObserverDisposable = [self subscribeNext:^(id x) { + [currentWindow sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [closeObserverDisposable dispose]; + [openObserverDisposable dispose]; + [selfObserverDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -windowWithStart: %@ close:", self.name, openSignal]; +} + +- (RACSignal *)buffer:(NSUInteger)bufferCount { + NSCParameterAssert(bufferCount > 0); + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + NSMutableArray *values = [NSMutableArray arrayWithCapacity:bufferCount]; + RACSubject *windowCloseSubject = [RACSubject subject]; + + RACDisposable *closeDisposable = [windowCloseSubject subscribeNext:^(id x) { + [subscriber sendNext:[RACTuple tupleWithObjectsFromArray:values convertNullsToNils:NO]]; + [values removeAllObjects]; + }]; + + __block RACDisposable *innerDisposable = nil; + RACDisposable *outerDisposable = [[self windowWithStart:self close:^(RACSignal *start) { + return windowCloseSubject; + }] subscribeNext:^(id x) { + innerDisposable = [x subscribeNext:^(id x) { + [values addObject:x ? : [RACTupleNil tupleNil]]; + if(values.count % bufferCount == 0) { + [windowCloseSubject sendNext:[RACUnit defaultUnit]]; + } + }]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [innerDisposable dispose]; + [outerDisposable dispose]; + [closeDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -buffer: %lu", self.name, (unsigned long)bufferCount]; +} + +- (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock { + NSCParameterAssert(letBlock != NULL); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACMulticastConnection *connection = [self publish]; + RACDisposable *finalDisposable = [letBlock(connection.signal) subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + [subscriber sendCompleted]; + }]; + + RACDisposable *connectionDisposable = [connection connect]; + + return [RACDisposable disposableWithBlock:^{ + [connectionDisposable dispose]; + [finalDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -let:", self.name]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval { + return [RACSignal interval:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; +} + ++ (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway { + return [RACSignal interval:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh] withLeeway:leeway]; +} + +- (RACSignal *)timeout:(NSTimeInterval)interval { + return [self timeout:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; +} + +- (RACSignal *)bufferWithTime:(NSTimeInterval)interval { + return [self bufferWithTime:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]]; +} + +- (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object { + return [self setKeyPath:keyPath onObject:object]; +} + +- (RACSignal *)ignoreElements { + return [self ignoreValues]; +} + +- (RACSignal *)sequenceNext:(RACSignal * (^)(void))block { + return [self then:block]; +} + +- (RACSignal *)aggregateWithStart:(id)start combine:(id (^)(id running, id next))combineBlock { + return [self aggregateWithStart:start reduce:combineBlock]; +} + +- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory combine:(id (^)(id running, id next))combineBlock { + return [self aggregateWithStartFactory:startFactory reduce:combineBlock]; +} + +- (RACDisposable *)executeCommand:(RACCommand *)command { + NSCParameterAssert(command != nil); + + return [self subscribeNext:^(id x) { + [command execute:x]; + }]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h new file mode 100644 index 0000000..ed644cf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.h
@@ -0,0 +1,219 @@ +// +// RACSignal.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/1/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "RACStream.h" + +@class RACDisposable; +@class RACScheduler; +@class RACSubject; +@protocol RACSubscriber; + +@interface RACSignal : RACStream + +/// Creates a new signal. This is the preferred way to create a new signal +/// operation or behavior. +/// +/// Events can be sent to new subscribers immediately in the `didSubscribe` +/// block, but the subscriber will not be able to dispose of the signal until +/// a RACDisposable is returned from `didSubscribe`. In the case of infinite +/// signals, this won't _ever_ happen if events are sent immediately. +/// +/// To ensure that the signal is disposable, events can be scheduled on the +/// +[RACScheduler currentScheduler] (so that they're deferred, not sent +/// immediately), or they can be sent in the background. The RACDisposable +/// returned by the `didSubscribe` block should cancel any such scheduling or +/// asynchronous work. +/// +/// didSubscribe - Called when the signal is subscribed to. The new subscriber is +/// passed in. You can then manually control the <RACSubscriber> by +/// sending it -sendNext:, -sendError:, and -sendCompleted, +/// as defined by the operation you're implementing. This block +/// should return a RACDisposable which cancels any ongoing work +/// triggered by the subscription, and cleans up any resources or +/// disposables created as part of it. When the disposable is +/// disposed of, the signal must not send any more events to the +/// `subscriber`. If no cleanup is necessary, return nil. +/// +/// **Note:** The `didSubscribe` block is called every time a new subscriber +/// subscribes. Any side effects within the block will thus execute once for each +/// subscription, not necessarily on one thread, and possibly even +/// simultaneously! ++ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe; + +/// Returns a signal that immediately sends the given error. ++ (RACSignal *)error:(NSError *)error; + +/// Returns a signal that never completes. ++ (RACSignal *)never; + +/// Immediately schedules the given block on the given scheduler. The block is +/// given a subscriber to which it can send events. +/// +/// scheduler - The scheduler on which `block` will be scheduled and results +/// delivered. Cannot be nil. +/// block - The block to invoke. Cannot be NULL. +/// +/// Returns a signal which will send all events sent on the subscriber given to +/// `block`. All events will be sent on `scheduler` and it will replay any missed +/// events to new subscribers. ++ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block; + +/// Invokes the given block only on the first subscription. The block is given a +/// subscriber to which it can send events. +/// +/// Note that disposing of the subscription to the returned signal will *not* +/// dispose of the underlying subscription. If you need that behavior, see +/// -[RACMulticastConnection autoconnect]. The underlying subscription will never +/// be disposed of. Because of this, `block` should never return an infinite +/// signal since there would be no way of ending it. +/// +/// scheduler - The scheduler on which the block should be scheduled. Note that +/// if given +[RACScheduler immediateScheduler], the block will be +/// invoked synchronously on the first subscription. Cannot be nil. +/// block - The block to invoke on the first subscription. Cannot be NULL. +/// +/// Returns a signal which will pass through the events sent to the subscriber +/// given to `block` and replay any missed events to new subscribers. ++ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block; + +@end + +@interface RACSignal (RACStream) + +/// Returns a signal that immediately sends the given value and then completes. ++ (RACSignal *)return:(id)value; + +/// Returns a signal that immediately completes. ++ (RACSignal *)empty; + +/// Subscribes to `signal` when the source signal completes. +- (RACSignal *)concat:(RACSignal *)signal; + +/// Zips the values in the receiver with those of the given signal to create +/// RACTuples. +/// +/// The first `next` of each stream will be combined, then the second `next`, and +/// so forth, until either signal completes or errors. +/// +/// signal - The signal to zip with. This must not be `nil`. +/// +/// Returns a new signal of RACTuples, representing the combined values of the +/// two signals. Any error from one of the original signals will be forwarded on +/// the returned signal. +- (RACSignal *)zipWith:(RACSignal *)signal; + +@end + +@interface RACSignal (Subscription) + +/// Subscribes `subscriber` to changes on the receiver. The receiver defines which +/// events it actually sends and in what situations the events are sent. +/// +/// Subscription will always happen on a valid RACScheduler. If the +/// +[RACScheduler currentScheduler] cannot be determined at the time of +/// subscription (e.g., because the calling code is running on a GCD queue or +/// NSOperationQueue), subscription will occur on a private background scheduler. +/// On the main thread, subscriptions will always occur immediately, with a +/// +[RACScheduler currentScheduler] of +[RACScheduler mainThreadScheduler]. +/// +/// This method must be overridden by any subclasses. +/// +/// Returns nil or a disposable. You can call -[RACDisposable dispose] if you +/// need to end your subscription before it would "naturally" end, either by +/// completing or erroring. Once the disposable has been disposed, the subscriber +/// won't receive any more events from the subscription. +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber; + +/// Convenience method to subscribe to the `next` event. +/// +/// This corresponds to `IObserver<T>.OnNext` in Rx. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock; + +/// Convenience method to subscribe to the `next` and `completed` events. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock; + +/// Convenience method to subscribe to the `next`, `completed`, and `error` events. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; + +/// Convenience method to subscribe to `error` events. +/// +/// This corresponds to the `IObserver<T>.OnError` in Rx. +- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock; + +/// Convenience method to subscribe to `completed` events. +/// +/// This corresponds to the `IObserver<T>.OnCompleted` in Rx. +- (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock; + +/// Convenience method to subscribe to `next` and `error` events. +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock; + +/// Convenience method to subscribe to `error` and `completed` events. +- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock; + +@end + +/// Additional methods to assist with debugging. +@interface RACSignal (Debugging) + +/// Logs all events that the receiver sends. +- (RACSignal *)logAll; + +/// Logs each `next` that the receiver sends. +- (RACSignal *)logNext; + +/// Logs any error that the receiver sends. +- (RACSignal *)logError; + +/// Logs any `completed` event that the receiver sends. +- (RACSignal *)logCompleted; + +@end + +/// Additional methods to assist with unit testing. +/// +/// **These methods should never ship in production code.** +@interface RACSignal (Testing) + +/// Spins the main run loop for a short while, waiting for the receiver to send a `next`. +/// +/// **Because this method executes the run loop recursively, it should only be used +/// on the main thread, and only from a unit test.** +/// +/// defaultValue - Returned if the receiver completes or errors before sending +/// a `next`, or if the method times out. This argument may be +/// nil. +/// success - If not NULL, set to whether the receiver completed +/// successfully. +/// error - If not NULL, set to any error that occurred. +/// +/// Returns the first value received, or `defaultValue` if no value is received +/// before the signal finishes or the method times out. +- (id)asynchronousFirstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error; + +/// Spins the main run loop for a short while, waiting for the receiver to complete. +/// +/// **Because this method executes the run loop recursively, it should only be used +/// on the main thread, and only from a unit test.** +/// +/// error - If not NULL, set to any error that occurs. +/// +/// Returns whether the signal completed successfully before timing out. If NO, +/// `error` will be set to any error that occurred. +- (BOOL)asynchronouslyWaitUntilCompleted:(NSError **)error; + +@end + +@interface RACSignal (Deprecated) + ++ (RACSignal *)start:(id (^)(BOOL *success, NSError **error))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler subjectBlock:(void (^)(RACSubject *subject))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler block:(id (^)(BOOL *success, NSError **error))block __attribute__((deprecated("Use +startEagerlyWithScheduler:block: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.m new file mode 100644 index 0000000..69d9700 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignal.m
@@ -0,0 +1,447 @@ +// +// RACSignal.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/15/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACDynamicSignal.h" +#import "RACEmptySignal.h" +#import "RACErrorSignal.h" +#import "RACMulticastConnection.h" +#import "RACReplaySubject.h" +#import "RACReturnSignal.h" +#import "RACScheduler.h" +#import "RACSerialDisposable.h" +#import "RACSignal+Operations.h" +#import "RACSubject.h" +#import "RACSubscriber+Private.h" +#import "RACTuple.h" + +@implementation RACSignal + +#pragma mark Lifecycle + ++ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { + return [RACDynamicSignal createSignal:didSubscribe]; +} + ++ (RACSignal *)error:(NSError *)error { + return [RACErrorSignal error:error]; +} + ++ (RACSignal *)never { + return [[self createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + return nil; + }] setNameWithFormat:@"+never"]; +} + ++ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(block != NULL); + + RACSignal *signal = [self startLazilyWithScheduler:scheduler block:block]; + // Subscribe to force the lazy signal to call its block. + [[signal publish] connect]; + return [signal setNameWithFormat:@"+startEagerlyWithScheduler:%@ block:", scheduler]; +} + ++ (RACSignal *)startLazilyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id<RACSubscriber> subscriber))block { + NSCParameterAssert(scheduler != nil); + NSCParameterAssert(block != NULL); + + RACMulticastConnection *connection = [[RACSignal + createSignal:^ id (id<RACSubscriber> subscriber) { + block(subscriber); + return nil; + }] + multicast:[RACReplaySubject subject]]; + + return [[[RACSignal + createSignal:^ id (id<RACSubscriber> subscriber) { + [connection.signal subscribe:subscriber]; + [connection connect]; + return nil; + }] + subscribeOn:scheduler] + setNameWithFormat:@"+startLazilyWithScheduler:%@ block:", scheduler]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p> name: %@", self.class, self, self.name]; +} + +@end + +@implementation RACSignal (RACStream) + ++ (RACSignal *)empty { + return [RACEmptySignal empty]; +} + ++ (RACSignal *)return:(id)value { + return [RACReturnSignal return:value]; +} + +- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block { + NSCParameterAssert(block != NULL); + + /* + * -bind: should: + * + * 1. Subscribe to the original signal of values. + * 2. Any time the original signal sends a value, transform it using the binding block. + * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received. + * 4. If the binding block asks the bind to terminate, complete the _original_ signal. + * 5. When _all_ signals complete, send completed to the subscriber. + * + * If any signal sends an error at any point, send that to the subscriber. + */ + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACStreamBindBlock bindingBlock = block(); + + NSMutableArray *signals = [NSMutableArray arrayWithObject:self]; + + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) { + BOOL removeDisposable = NO; + + @synchronized (signals) { + [signals removeObject:signal]; + + if (signals.count == 0) { + [subscriber sendCompleted]; + [compoundDisposable dispose]; + } else { + removeDisposable = YES; + } + } + + if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; + }; + + void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { + @synchronized (signals) { + [signals addObject:signal]; + } + + RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; + [compoundDisposable addDisposable:selfDisposable]; + + RACDisposable *disposable = [signal subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [compoundDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + @autoreleasepool { + completeSignal(signal, selfDisposable); + } + }]; + + selfDisposable.disposable = disposable; + }; + + @autoreleasepool { + RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; + [compoundDisposable addDisposable:selfDisposable]; + + RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { + BOOL stop = NO; + id signal = bindingBlock(x, &stop); + + @autoreleasepool { + if (signal != nil) addSignal(signal); + if (signal == nil || stop) { + [selfDisposable dispose]; + completeSignal(self, selfDisposable); + } + } + } error:^(NSError *error) { + [compoundDisposable dispose]; + [subscriber sendError:error]; + } completed:^{ + @autoreleasepool { + completeSignal(self, selfDisposable); + } + }]; + + selfDisposable.disposable = bindingDisposable; + } + + return compoundDisposable; + }] setNameWithFormat:@"[%@] -bind:", self.name]; +} + +- (RACSignal *)concat:(RACSignal *)signal { + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; + + RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { + [subscriber sendNext:x]; + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + RACDisposable *concattedDisposable = [signal subscribe:subscriber]; + serialDisposable.disposable = concattedDisposable; + }]; + + serialDisposable.disposable = sourceDisposable; + return serialDisposable; + }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal]; +} + +- (RACSignal *)zipWith:(RACSignal *)signal { + NSCParameterAssert(signal != nil); + + return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block BOOL selfCompleted = NO; + NSMutableArray *selfValues = [NSMutableArray array]; + + __block BOOL otherCompleted = NO; + NSMutableArray *otherValues = [NSMutableArray array]; + + void (^sendCompletedIfNecessary)(void) = ^{ + @synchronized (selfValues) { + BOOL selfEmpty = (selfCompleted && selfValues.count == 0); + BOOL otherEmpty = (otherCompleted && otherValues.count == 0); + if (selfEmpty || otherEmpty) [subscriber sendCompleted]; + } + }; + + void (^sendNext)(void) = ^{ + @synchronized (selfValues) { + if (selfValues.count == 0) return; + if (otherValues.count == 0) return; + + RACTuple *tuple = [RACTuple tupleWithObjects:selfValues[0], otherValues[0], nil]; + [selfValues removeObjectAtIndex:0]; + [otherValues removeObjectAtIndex:0]; + + [subscriber sendNext:tuple]; + sendCompletedIfNecessary(); + } + }; + + RACDisposable *selfDisposable = [self subscribeNext:^(id x) { + @synchronized (selfValues) { + [selfValues addObject:x ?: RACTupleNil.tupleNil]; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (selfValues) { + selfCompleted = YES; + sendCompletedIfNecessary(); + } + }]; + + RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { + @synchronized (selfValues) { + [otherValues addObject:x ?: RACTupleNil.tupleNil]; + sendNext(); + } + } error:^(NSError *error) { + [subscriber sendError:error]; + } completed:^{ + @synchronized (selfValues) { + otherCompleted = YES; + sendCompletedIfNecessary(); + } + }]; + + return [RACDisposable disposableWithBlock:^{ + [selfDisposable dispose]; + [otherDisposable dispose]; + }]; + }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal]; +} + +@end + +@implementation RACSignal (Subscription) + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCAssert(NO, @"This method must be overridden by subclasses"); + return nil; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock { + NSCParameterAssert(nextBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock { + NSCParameterAssert(nextBlock != NULL); + NSCParameterAssert(completedBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:completedBlock]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock { + NSCParameterAssert(nextBlock != NULL); + NSCParameterAssert(errorBlock != NULL); + NSCParameterAssert(completedBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock { + NSCParameterAssert(errorBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:errorBlock completed:NULL]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock { + NSCParameterAssert(completedBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:NULL completed:completedBlock]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock { + NSCParameterAssert(nextBlock != NULL); + NSCParameterAssert(errorBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:NULL]; + return [self subscribe:o]; +} + +- (RACDisposable *)subscribeError:(void (^)(NSError *))errorBlock completed:(void (^)(void))completedBlock { + NSCParameterAssert(completedBlock != NULL); + NSCParameterAssert(errorBlock != NULL); + + RACSubscriber *o = [RACSubscriber subscriberWithNext:NULL error:errorBlock completed:completedBlock]; + return [self subscribe:o]; +} + +@end + +@implementation RACSignal (Debugging) + +- (RACSignal *)logAll { + return [[[self logNext] logError] logCompleted]; +} + +- (RACSignal *)logNext { + return [[self doNext:^(id x) { + NSLog(@"%@ next: %@", self, x); + }] setNameWithFormat:@"%@", self.name]; +} + +- (RACSignal *)logError { + return [[self doError:^(NSError *error) { + NSLog(@"%@ error: %@", self, error); + }] setNameWithFormat:@"%@", self.name]; +} + +- (RACSignal *)logCompleted { + return [[self doCompleted:^{ + NSLog(@"%@ completed", self); + }] setNameWithFormat:@"%@", self.name]; +} + +@end + +@implementation RACSignal (Testing) + +static const NSTimeInterval RACSignalAsynchronousWaitTimeout = 10; + +- (id)asynchronousFirstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error { + NSCAssert([NSThread isMainThread], @"%s should only be used from the main thread", __func__); + + __block id result = defaultValue; + __block BOOL done = NO; + + // Ensures that we don't pass values across thread boundaries by reference. + __block NSError *localError; + __block BOOL localSuccess = YES; + + [[[[self + take:1] + timeout:RACSignalAsynchronousWaitTimeout onScheduler:[RACScheduler scheduler]] + deliverOn:RACScheduler.mainThreadScheduler] + subscribeNext:^(id x) { + result = x; + done = YES; + } error:^(NSError *e) { + if (!done) { + localSuccess = NO; + localError = e; + done = YES; + } + } completed:^{ + done = YES; + }]; + + do { + [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + } while (!done); + + if (success != NULL) *success = localSuccess; + if (error != NULL) *error = localError; + + return result; +} + +- (BOOL)asynchronouslyWaitUntilCompleted:(NSError **)error { + BOOL success = NO; + [[self ignoreValues] asynchronousFirstOrDefault:nil success:&success error:error]; + return success; +} + +@end + +@implementation RACSignal (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler subjectBlock:(void (^)(RACSubject *subject))block { + NSCParameterAssert(block != NULL); + + RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"+startWithScheduler:subjectBlock:"]; + + [scheduler schedule:^{ + block(subject); + }]; + + return subject; +} + ++ (RACSignal *)start:(id (^)(BOOL *success, NSError **error))block { + return [[self startWithScheduler:[RACScheduler scheduler] block:block] setNameWithFormat:@"+start:"]; +} + ++ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler block:(id (^)(BOOL *success, NSError **error))block { + return [[self startWithScheduler:scheduler subjectBlock:^(id<RACSubscriber> subscriber) { + BOOL success = YES; + NSError *error = nil; + id returned = block(&success, &error); + + if (!success) { + [subscriber sendError:error]; + } else { + [subscriber sendNext:returned]; + [subscriber sendCompleted]; + } + }] setNameWithFormat:@"+startWithScheduler:block:"]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignalProvider.d b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalProvider.d new file mode 100644 index 0000000..8add9a1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalProvider.d
@@ -0,0 +1,5 @@ +provider RACSignal { + probe next(char *signal, char *subscriber, char *valueDescription); + probe completed(char *signal, char *subscriber); + probe error(char *signal, char *subscriber, char *errorDescription); +};
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.h new file mode 100644 index 0000000..e3ab77d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.h
@@ -0,0 +1,19 @@ +// +// RACSignalSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-09. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSequence.h" + +@class RACSignal; + +// Private class that adapts a RACSignal to the RACSequence interface. +@interface RACSignalSequence : RACSequence + +// Returns a sequence for enumerating over the given signal. ++ (RACSequence *)sequenceWithSignal:(RACSignal *)signal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.m new file mode 100644 index 0000000..52ea1b9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSignalSequence.m
@@ -0,0 +1,79 @@ +// +// RACSignalSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-09. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignalSequence.h" +#import "RACDisposable.h" +#import "RACReplaySubject.h" +#import "RACSignal+Operations.h" + +@interface RACSignalSequence () + +// Replays the signal given on initialization. +@property (nonatomic, strong, readonly) RACReplaySubject *subject; + +@end + +@implementation RACSignalSequence + +#pragma mark Lifecycle + ++ (RACSequence *)sequenceWithSignal:(RACSignal *)signal { + RACSignalSequence *seq = [[self alloc] init]; + + RACReplaySubject *subject = [RACReplaySubject subject]; + [signal subscribeNext:^(id value) { + [subject sendNext:value]; + } error:^(NSError *error) { + [subject sendError:error]; + } completed:^{ + [subject sendCompleted]; + }]; + + seq->_subject = subject; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + id value = [self.subject firstOrDefault:self]; + + if (value == self) { + return nil; + } else { + return value ?: NSNull.null; + } +} + +- (RACSequence *)tail { + RACSequence *sequence = [self.class sequenceWithSignal:[self.subject skip:1]]; + sequence.name = self.name; + return sequence; +} + +- (NSArray *)array { + return self.subject.toArray; +} + +#pragma mark NSObject + +- (NSString *)description { + // Synchronously accumulate the values that have been sent so far. + NSMutableArray *values = [NSMutableArray array]; + RACDisposable *disposable = [self.subject subscribeNext:^(id value) { + @synchronized (values) { + [values addObject:value ?: NSNull.null]; + } + }]; + + [disposable dispose]; + + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, values = %@ … }", self.class, self, self.name, values]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStream+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACStream+Private.h new file mode 100644 index 0000000..f6c0407 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStream+Private.h
@@ -0,0 +1,23 @@ +// +// RACStream+Private.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACStream.h" + +@interface RACStream () + +// Combines a list of streams using the logic of the given block. +// +// streams - The streams to combine. +// block - An operator that combines two streams and returns a new one. The +// returned stream should contain 2-tuples of the streams' combined +// values. +// +// Returns a combined stream. ++ (instancetype)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStream.h b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.h new file mode 100644 index 0000000..8853ffe --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.h
@@ -0,0 +1,321 @@ +// +// RACStream.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-31. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACStream; + +/// A block which accepts a value from a RACStream and returns a new instance +/// of the same stream class. +/// +/// Setting `stop` to `YES` will cause the bind to terminate after the returned +/// value. Returning `nil` will result in immediate termination. +typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop); + +/// An abstract class representing any stream of values. +/// +/// This class represents a monad, upon which many stream-based operations can +/// be built. +/// +/// When subclassing RACStream, only the methods in the main @interface body need +/// to be overridden. +@interface RACStream : NSObject + +/// Returns an empty stream. ++ (instancetype)empty; + +/// Lifts `value` into the stream monad. +/// +/// Returns a stream containing only the given value. ++ (instancetype)return:(id)value; + +/// Lazily binds a block to the values in the receiver. +/// +/// This should only be used if you need to terminate the bind early, or close +/// over some state. -flattenMap: is more appropriate for all other cases. +/// +/// block - A block returning a RACStreamBindBlock. This block will be invoked +/// each time the bound stream is re-evaluated. This block must not be +/// nil or return nil. +/// +/// Returns a new stream which represents the combined result of all lazy +/// applications of `block`. +- (instancetype)bind:(RACStreamBindBlock (^)(void))block; + +/// Appends the values of `stream` to the values in the receiver. +/// +/// stream - A stream to concatenate. This must be an instance of the same +/// concrete class as the receiver, and should not be `nil`. +/// +/// Returns a new stream representing the receiver followed by `stream`. +- (instancetype)concat:(RACStream *)stream; + +/// Zips the values in the receiver with those of the given stream to create +/// RACTuples. +/// +/// The first value of each stream will be combined, then the second value, and +/// so forth, until at least one of the streams is exhausted. +/// +/// stream - The stream to zip with. This must be an instance of the same +/// concrete class as the receiver, and should not be `nil`. +/// +/// Returns a new stream of RACTuples, representing the zipped values of the +/// two streams. +- (instancetype)zipWith:(RACStream *)stream; + +@end + +/// This extension contains functionality to support naming streams for +/// debugging. +/// +/// Subclasses do not need to override the methods here. +@interface RACStream () + +/// The name of the stream. This is for debugging/human purposes only. +@property (copy) NSString *name; + +/// Sets the name of the receiver to the given format string. +/// +/// This is for debugging purposes only, and won't do anything unless the +/// RAC_DEBUG_SIGNAL_NAMES environment variable is set. +/// +/// Returns the receiver, for easy method chaining. +- (instancetype)setNameWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); + +@end + +/// Operations built on the RACStream primitives. +/// +/// These methods do not need to be overridden, although subclasses may +/// occasionally gain better performance from doing so. +@interface RACStream (Operations) + +/// Maps `block` across the values in the receiver and flattens the result. +/// +/// Note that operators applied _after_ -flattenMap: behave differently from +/// operators _within_ -flattenMap:. See the Examples section below. +/// +/// This corresponds to the `SelectMany` method in Rx. +/// +/// block - A block which accepts the values in the receiver and returns a new +/// instance of the receiver's class. Returning `nil` from this block is +/// equivalent to returning an empty signal. +/// +/// Examples +/// +/// [signal flattenMap:^(id x) { +/// // Logs each time a returned signal completes. +/// return [[RACSignal return:x] logCompleted]; +/// }]; +/// +/// [[signal +/// flattenMap:^(id x) { +/// return [RACSignal return:x]; +/// }] +/// // Logs only once, when all of the signals complete. +/// logCompleted]; +/// +/// Returns a new stream which represents the combined streams resulting from +/// mapping `block`. +- (instancetype)flattenMap:(RACStream * (^)(id value))block; + +/// Flattens a stream of streams. +/// +/// This corresponds to the `Merge` method in Rx. +/// +/// Returns a stream consisting of the combined streams obtained from the +/// receiver. +- (instancetype)flatten; + +/// Maps `block` across the values in the receiver. +/// +/// This corresponds to the `Select` method in Rx. +/// +/// Returns a new stream with the mapped values. +- (instancetype)map:(id (^)(id value))block; + +/// Replace each value in the receiver with the given object. +/// +/// Returns a new stream which includes the given object once for each value in +/// the receiver. +- (instancetype)mapReplace:(id)object; + +/// Filters out values in the receiver that don't pass the given test. +/// +/// This corresponds to the `Where` method in Rx. +/// +/// Returns a new stream with only those values that passed. +- (instancetype)filter:(BOOL (^)(id value))block; + +/// Filters out values in the receiver that equal (via -isEqual:) the provided value. +/// +/// value - The value can be `nil`, in which case it ignores `nil` values. +/// +/// Returns a new stream containing only the values which did not compare equal +/// to `value`. +- (instancetype)ignore:(id)value; + +/// Unpacks each RACTuple in the receiver and maps the values to a new value. +/// +/// reduceBlock - The block which reduces each RACTuple's values into one value. +/// It must take as many arguments as the number of tuple elements +/// to process. Each argument will be an object argument. The +/// return value must be an object. This argument cannot be nil. +/// +/// Returns a new stream of reduced tuple values. +- (instancetype)reduceEach:(id (^)())reduceBlock; + +/// Returns a stream consisting of `value`, followed by the values in the +/// receiver. +- (instancetype)startWith:(id)value; + +/// Skips the first `skipCount` values in the receiver. +/// +/// Returns the receiver after skipping the first `skipCount` values. If +/// `skipCount` is greater than the number of values in the stream, an empty +/// stream is returned. +- (instancetype)skip:(NSUInteger)skipCount; + +/// Returns a stream of the first `count` values in the receiver. If `count` is +/// greater than or equal to the number of values in the stream, a stream +/// equivalent to the receiver is returned. +- (instancetype)take:(NSUInteger)count; + +/// Zips the values in the given streams to create RACTuples. +/// +/// The first value of each stream will be combined, then the second value, and +/// so forth, until at least one of the streams is exhausted. +/// +/// streams - The streams to combine. These must all be instances of the same +/// concrete class implementing the protocol. If this collection is +/// empty, the returned stream will be empty. +/// +/// Returns a new stream containing RACTuples of the zipped values from the +/// streams. ++ (instancetype)zip:(id<NSFastEnumeration>)streams; + +/// Zips streams using +zip:, then reduces the resulting tuples into a single +/// value using -reduceEach: +/// +/// streams - The streams to combine. These must all be instances of the +/// same concrete class implementing the protocol. If this +/// collection is empty, the returned stream will be empty. +/// reduceBlock - The block which reduces the values from all the streams +/// into one value. It must take as many arguments as the +/// number of streams given. Each argument will be an object +/// argument. The return value must be an object. This argument +/// must not be nil. +/// +/// Example: +/// +/// [RACStream zip:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) { +/// return [NSString stringWithFormat:@"%@: %@", string, number]; +/// }]; +/// +/// Returns a new stream containing the results from each invocation of +/// `reduceBlock`. ++ (instancetype)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock; + +/// Returns a stream obtained by concatenating `streams` in order. ++ (instancetype)concat:(id<NSFastEnumeration>)streams; + +/// Combines values in the receiver from left to right using the given block. +/// +/// The algorithm proceeds as follows: +/// +/// 1. `startingValue` is passed into the block as the `running` value, and the +/// first element of the receiver is passed into the block as the `next` value. +/// 2. The result of the invocation is added to the returned stream. +/// 3. The result of the invocation (`running`) and the next element of the +/// receiver (`next`) is passed into `block`. +/// 4. Steps 2 and 3 are repeated until all values have been processed. +/// +/// startingValue - The value to be combined with the first element of the +/// receiver. This value may be `nil`. +/// block - A block that describes how to combine values of the +/// receiver. If the receiver is empty, this block will never be +/// invoked. +/// +/// Examples +/// +/// RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence; +/// +/// // Contains 1, 3, 6, 10 +/// RACSequence *sums = [numbers scanWithStart:@0 reduce:^(NSNumber *sum, NSNumber *next) { +/// return @(sum.integerValue + next.integerValue); +/// }]; +/// +/// Returns a new stream that consists of each application of `block`. If the +/// receiver is empty, an empty stream is returned. +- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))block; + +/// Combines each previous and current value into one object. +/// +/// This method is similar to -scanWithStart:reduce:, but only ever operates on +/// the previous and current values (instead of the whole stream), and does not +/// pass the return value of `reduceBlock` into the next invocation of it. +/// +/// start - The value passed into `reduceBlock` as `previous` for the +/// first value. +/// reduceBlock - The block that combines the previous value and the current +/// value to create the reduced value. Cannot be nil. +/// +/// Examples +/// +/// RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence; +/// +/// // Contains 1, 3, 5, 7 +/// RACSequence *sums = [numbers combinePreviousWithStart:@0 reduce:^(NSNumber *previous, NSNumber *next) { +/// return @(previous.integerValue + next.integerValue); +/// }]; +/// +/// Returns a new stream consisting of the return values from each application of +/// `reduceBlock`. +- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id current))reduceBlock; + +/// Takes values until the given block returns `YES`. +/// +/// Returns a stream of the initial values in the receiver that fail `predicate`. +/// If `predicate` never returns `YES`, a stream equivalent to the receiver is +/// returned. +- (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate; + +/// Takes values until the given block returns `NO`. +/// +/// Returns a stream of the initial values in the receiver that pass `predicate`. +/// If `predicate` never returns `NO`, a stream equivalent to the receiver is +/// returned. +- (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate; + +/// Skips values until the given block returns `YES`. +/// +/// Returns a stream containing the values of the receiver that follow any +/// initial values failing `predicate`. If `predicate` never returns `YES`, +/// an empty stream is returned. +- (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate; + +/// Skips values until the given block returns `NO`. +/// +/// Returns a stream containing the values of the receiver that follow any +/// initial values passing `predicate`. If `predicate` never returns `NO`, an +/// empty stream is returned. +- (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate; + +/// Returns a stream of values for which -isEqual: returns NO when compared to the +/// previous value. +- (instancetype)distinctUntilChanged; + +@end + +@interface RACStream (Deprecated) + +- (instancetype)sequenceMany:(RACStream * (^)(void))block __attribute__((deprecated("Use -flattenMap: instead"))); +- (instancetype)scanWithStart:(id)startingValue combine:(id (^)(id running, id next))block __attribute__((deprecated("Renamed to -scanWithStart:reduce:"))); +- (instancetype)mapPreviousWithStart:(id)start reduce:(id (^)(id previous, id current))combineBlock __attribute__((deprecated("Renamed to -combinePreviousWithStart:reduce:"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStream.m b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.m new file mode 100644 index 0000000..7c37c69 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStream.m
@@ -0,0 +1,362 @@ +// +// RACStream.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-31. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACStream.h" +#import "NSObject+RACDescription.h" +#import "RACBlockTrampoline.h" +#import "RACTuple.h" + +@implementation RACStream + +#pragma mark Lifecycle + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + self.name = @""; + return self; +} + +#pragma mark Abstract methods + ++ (instancetype)empty { + return nil; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(void))block { + return nil; +} + ++ (instancetype)return:(id)value { + return nil; +} + +- (instancetype)concat:(RACStream *)stream { + return nil; +} + +- (instancetype)zipWith:(RACStream *)stream { + return nil; +} + +#pragma mark Naming + +- (instancetype)setNameWithFormat:(NSString *)format, ... { + if (getenv("RAC_DEBUG_SIGNAL_NAMES") == NULL) return self; + + NSCParameterAssert(format != nil); + + va_list args; + va_start(args, format); + + NSString *str = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + self.name = str; + return self; +} + +@end + +@implementation RACStream (Operations) + +- (instancetype)flattenMap:(RACStream * (^)(id value))block { + Class class = self.class; + + return [[self bind:^{ + return ^(id value, BOOL *stop) { + id stream = block(value) ?: [class empty]; + NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream); + + return stream; + }; + }] setNameWithFormat:@"[%@] -flattenMap:", self.name]; +} + +- (instancetype)flatten { + __weak RACStream *stream __attribute__((unused)) = self; + return [[self flattenMap:^(id value) { + return value; + }] setNameWithFormat:@"[%@] -flatten", self.name]; +} + +- (instancetype)map:(id (^)(id value))block { + NSCParameterAssert(block != nil); + + Class class = self.class; + + return [[self flattenMap:^(id value) { + return [class return:block(value)]; + }] setNameWithFormat:@"[%@] -map:", self.name]; +} + +- (instancetype)mapReplace:(id)object { + return [[self map:^(id _) { + return object; + }] setNameWithFormat:@"[%@] -mapReplace: %@", self.name, [object rac_description]]; +} + +- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock { + NSCParameterAssert(reduceBlock != NULL); + return [[[self + scanWithStart:[RACTuple tupleWithObjects:start, nil] + reduce:^(RACTuple *previousTuple, id next) { + id value = reduceBlock(previousTuple[0], next); + return [RACTuple tupleWithObjects:next ?: RACTupleNil.tupleNil, value ?: RACTupleNil.tupleNil, nil]; + }] + map:^(RACTuple *tuple) { + return tuple[1]; + }] + setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, [start rac_description]]; +} + +- (instancetype)filter:(BOOL (^)(id value))block { + NSCParameterAssert(block != nil); + + Class class = self.class; + + return [[self flattenMap:^ id (id value) { + if (block(value)) { + return [class return:value]; + } else { + return class.empty; + } + }] setNameWithFormat:@"[%@] -filter:", self.name]; +} + +- (instancetype)ignore:(id)value { + return [[self filter:^ BOOL (id innerValue) { + return innerValue != value && ![innerValue isEqual:value]; + }] setNameWithFormat:@"[%@] -ignore: %@", self.name, [value rac_description]]; +} + +- (instancetype)reduceEach:(id (^)())reduceBlock { + NSCParameterAssert(reduceBlock != nil); + + __weak RACStream *stream __attribute__((unused)) = self; + return [[self map:^(RACTuple *t) { + NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t); + return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t]; + }] setNameWithFormat:@"[%@] -reduceEach:", self.name]; +} + +- (instancetype)startWith:(id)value { + return [[[self.class return:value] + concat:self] + setNameWithFormat:@"[%@] -startWith: %@", self.name, [value rac_description]]; +} + +- (instancetype)skip:(NSUInteger)skipCount { + Class class = self.class; + + return [[self bind:^{ + __block NSUInteger skipped = 0; + + return ^(id value, BOOL *stop) { + if (skipped >= skipCount) return [class return:value]; + + skipped++; + return class.empty; + }; + }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount]; +} + +- (instancetype)take:(NSUInteger)count { + Class class = self.class; + + return [[self bind:^{ + __block NSUInteger taken = 0; + + return ^ id (id value, BOOL *stop) { + RACStream *result = class.empty; + + if (taken < count) result = [class return:value]; + if (++taken >= count) *stop = YES; + + return result; + }; + }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count]; +} + ++ (instancetype)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block { + RACStream *current = nil; + + // Creates streams of successively larger tuples by combining the input + // streams one-by-one. + for (RACStream *stream in streams) { + // For the first stream, just wrap its values in a RACTuple. That way, + // if only one stream is given, the result is still a stream of tuples. + if (current == nil) { + current = [stream map:^(id x) { + return RACTuplePack(x); + }]; + + continue; + } + + current = block(current, stream); + } + + if (current == nil) return [self empty]; + + return [current map:^(RACTuple *xs) { + // Right now, each value is contained in its own tuple, sorta like: + // + // (((1), 2), 3) + // + // We need to unwrap all the layers and create a tuple out of the result. + NSMutableArray *values = [[NSMutableArray alloc] init]; + + while (xs != nil) { + [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0]; + xs = (xs.count > 1 ? xs.first : nil); + } + + return [RACTuple tupleWithObjectsFromArray:values]; + }]; +} + ++ (instancetype)zip:(id<NSFastEnumeration>)streams { + return [[self join:streams block:^(RACStream *left, RACStream *right) { + return [left zipWith:right]; + }] setNameWithFormat:@"+zip: %@", streams]; +} + ++ (instancetype)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock { + NSCParameterAssert(reduceBlock != nil); + + RACStream *result = [self zip:streams]; + + // Although we assert this condition above, older versions of this method + // supported this argument being nil. Avoid crashing Release builds of + // apps that depended on that. + if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; + + return [result setNameWithFormat:@"+zip: %@ reduce:", streams]; +} + ++ (instancetype)concat:(id<NSFastEnumeration>)streams { + RACStream *result = self.empty; + for (RACStream *stream in streams) { + result = [result concat:stream]; + } + + return [result setNameWithFormat:@"+concat: %@", streams]; +} + +- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))block { + NSCParameterAssert(block != nil); + + Class class = self.class; + + return [[self bind:^{ + __block id running = startingValue; + + return ^(id value, BOOL *stop) { + running = block(running, value); + return [class return:running]; + }; + }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, [startingValue rac_description]]; +} + +- (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + Class class = self.class; + + return [[self bind:^{ + return ^ id (id value, BOOL *stop) { + if (predicate(value)) return nil; + + return [class return:value]; + }; + }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name]; +} + +- (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + return [[self takeUntilBlock:^ BOOL (id x) { + return !predicate(x); + }] setNameWithFormat:@"[%@] -takeWhileBlock:", self.name]; +} + +- (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + Class class = self.class; + + return [[self bind:^{ + __block BOOL skipping = YES; + + return ^ id (id value, BOOL *stop) { + if (skipping) { + if (predicate(value)) { + skipping = NO; + } else { + return class.empty; + } + } + + return [class return:value]; + }; + }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; +} + +- (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate { + NSCParameterAssert(predicate != nil); + + return [[self skipUntilBlock:^ BOOL (id x) { + return !predicate(x); + }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; +} + +- (instancetype)distinctUntilChanged { + Class class = self.class; + + return [[self bind:^{ + __block id lastValue = nil; + __block BOOL initial = YES; + + return ^(id x, BOOL *stop) { + if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty]; + + initial = NO; + lastValue = x; + return [class return:x]; + }; + }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name]; +} + +@end + +@implementation RACStream (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" + +- (instancetype)sequenceMany:(RACStream * (^)(void))block { + NSCParameterAssert(block != NULL); + + return [[self flattenMap:^(id _) { + return block(); + }] setNameWithFormat:@"[%@] -sequenceMany:", self.name]; +} + +- (instancetype)scanWithStart:(id)startingValue combine:(id (^)(id running, id next))block { + return [self scanWithStart:startingValue reduce:block]; +} + +- (instancetype)mapPreviousWithStart:(id)start reduce:(id (^)(id previous, id current))combineBlock { + return [self combinePreviousWithStart:start reduce:combineBlock]; +} + +#pragma clang diagnostic pop + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.h new file mode 100644 index 0000000..b37ddea --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.h
@@ -0,0 +1,18 @@ +// +// RACStringSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACSequence.h" + +// Private class that adapts a string to the RACSequence interface. +@interface RACStringSequence : RACSequence + +// Returns a sequence for enumerating over the given string, starting from the +// given character offset. The string will be copied to prevent mutation. ++ (RACSequence *)sequenceWithString:(NSString *)string offset:(NSUInteger)offset; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.m new file mode 100644 index 0000000..dd10f3c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACStringSequence.m
@@ -0,0 +1,65 @@ +// +// RACStringSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-10-29. +// Copyright (c) 2012 GitHub. All rights reserved. +// + +#import "RACStringSequence.h" + +@interface RACStringSequence () + +// The string being sequenced. +@property (nonatomic, copy, readonly) NSString *string; + +// The index in the string from which the sequence starts. +@property (nonatomic, assign, readonly) NSUInteger offset; + +@end + +@implementation RACStringSequence + +#pragma mark Lifecycle + ++ (RACSequence *)sequenceWithString:(NSString *)string offset:(NSUInteger)offset { + NSCParameterAssert(offset <= string.length); + + if (offset == string.length) return self.empty; + + RACStringSequence *seq = [[self alloc] init]; + seq->_string = [string copy]; + seq->_offset = offset; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + return [self.string substringWithRange:NSMakeRange(self.offset, 1)]; +} + +- (RACSequence *)tail { + RACSequence *sequence = [self.class sequenceWithString:self.string offset:self.offset + 1]; + sequence.name = self.name; + return sequence; +} + +- (NSArray *)array { + NSUInteger substringLength = self.string.length - self.offset; + NSMutableArray *array = [NSMutableArray arrayWithCapacity:substringLength]; + + [self.string enumerateSubstringsInRange:NSMakeRange(self.offset, substringLength) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + [array addObject:substring]; + }]; + + return [array copy]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, string = %@ }", self.class, self, self.name, [self.string substringFromIndex:self.offset]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.h new file mode 100644 index 0000000..30c100b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.h
@@ -0,0 +1,22 @@ +// +// RACSubject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/9/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACSubscriber.h" + +/// A subject can be thought of as a signal that you can manually control by +/// sending next, completed, and error. +/// +/// They're most helpful in bridging the non-RAC world to RAC, since they let you +/// manually control the sending of events. +@interface RACSubject : RACSignal <RACSubscriber> + +/// Returns a new subject. ++ (instancetype)subject; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.m new file mode 100644 index 0000000..fa9ba7d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubject.m
@@ -0,0 +1,124 @@ +// +// RACSubject.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/9/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubject.h" +#import "EXTScope.h" +#import "RACCompoundDisposable.h" +#import "RACPassthroughSubscriber.h" + +@interface RACSubject () + +// Contains all current subscribers to the receiver. +// +// This should only be used while synchronized on `self`. +@property (nonatomic, strong, readonly) NSMutableArray *subscribers; + +// Contains all of the receiver's subscriptions to other signals. +@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; + +// Enumerates over each of the receiver's `subscribers` and invokes `block` for +// each. +- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block; + +@end + +@implementation RACSubject + +#pragma mark Lifecycle + ++ (instancetype)subject { + return [[self alloc] init]; +} + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + _disposable = [RACCompoundDisposable compoundDisposable]; + _subscribers = [[NSMutableArray alloc] initWithCapacity:1]; + + return self; +} + +- (void)dealloc { + [self.disposable dispose]; +} + +#pragma mark Subscription + +- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { + NSCParameterAssert(subscriber != nil); + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; + + NSMutableArray *subscribers = self.subscribers; + @synchronized (subscribers) { + [subscribers addObject:subscriber]; + } + + return [RACDisposable disposableWithBlock:^{ + @synchronized (subscribers) { + // Since newer subscribers are generally shorter-lived, search + // starting from the end of the list. + NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) { + return obj == subscriber; + }]; + + if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; + } + }]; +} + +- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block { + NSArray *subscribers; + @synchronized (self.subscribers) { + subscribers = [self.subscribers copy]; + } + + for (id<RACSubscriber> subscriber in subscribers) { + block(subscriber); + } +} + +#pragma mark RACSubscriber + +- (void)sendNext:(id)value { + [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { + [subscriber sendNext:value]; + }]; +} + +- (void)sendError:(NSError *)error { + [self.disposable dispose]; + + [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { + [subscriber sendError:error]; + }]; +} + +- (void)sendCompleted { + [self.disposable dispose]; + + [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { + [subscriber sendCompleted]; + }]; +} + +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d { + if (d.disposed) return; + [self.disposable addDisposable:d]; + + @weakify(self, d); + [d addDisposable:[RACDisposable disposableWithBlock:^{ + @strongify(self, d); + [self.disposable removeDisposable:d]; + }]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber+Private.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber+Private.h new file mode 100644 index 0000000..0ad81d2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber+Private.h
@@ -0,0 +1,17 @@ +// +// RACSubscriber+Private.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriber.h" + +// A simple block-based subscriber. +@interface RACSubscriber : NSObject <RACSubscriber> + +// Creates a new subscriber with the given blocks. ++ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h new file mode 100644 index 0000000..173e6dc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.h
@@ -0,0 +1,51 @@ +// +// RACSubscriber.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/1/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class RACCompoundDisposable; + +/// Represents any object which can directly receive values from a RACSignal. +/// +/// You generally shouldn't need to implement this protocol. +[RACSignal +/// createSignal:], RACSignal's subscription methods, or RACSubject should work +/// for most uses. +/// +/// Implementors of this protocol may receive messages and values from multiple +/// threads simultaneously, and so should be thread-safe. Subscribers will also +/// be weakly referenced so implementations must allow that. +@protocol RACSubscriber <NSObject> +@required + +/// Send the next value to subscribers. +/// +/// value - The value to send. This can be `nil`. +- (void)sendNext:(id)value; + +/// Send the error to subscribers. +/// +/// error - The error to send. This can be `nil`. +/// +/// This terminates the subscription, and invalidates the subscriber (such that +/// it cannot subscribe to anything else in the future). +- (void)sendError:(NSError *)error; + +/// Send completed to subscribers. +/// +/// This terminates the subscription, and invalidates the subscriber (such that +/// it cannot subscribe to anything else in the future). +- (void)sendCompleted; + +/// Sends the subscriber a disposable that represents one of its subscriptions. +/// +/// A subscriber may receive multiple disposables if it gets subscribed to +/// multiple signals; however, any error or completed events must terminate _all_ +/// subscriptions. +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.m new file mode 100644 index 0000000..b9e83f0 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriber.m
@@ -0,0 +1,108 @@ +// +// RACSubscriber.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/1/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriber.h" +#import "RACSubscriber+Private.h" +#import "EXTScope.h" +#import "RACCompoundDisposable.h" + +@interface RACSubscriber () + +// These callbacks should only be accessed while synchronized on self. +@property (nonatomic, copy) void (^next)(id value); +@property (nonatomic, copy) void (^error)(NSError *error); +@property (nonatomic, copy) void (^completed)(void); + +@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable; + +@end + +@implementation RACSubscriber + +#pragma mark Lifecycle + ++ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { + RACSubscriber *subscriber = [[self alloc] init]; + + subscriber->_next = [next copy]; + subscriber->_error = [error copy]; + subscriber->_completed = [completed copy]; + + return subscriber; +} + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + @weakify(self); + + RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{ + @strongify(self); + if (self == nil) return; + + @synchronized (self) { + self.next = nil; + self.error = nil; + self.completed = nil; + } + }]; + + _disposable = [RACCompoundDisposable compoundDisposable]; + [_disposable addDisposable:selfDisposable]; + + return self; +} + +- (void)dealloc { + [self.disposable dispose]; +} + +#pragma mark RACSubscriber + +- (void)sendNext:(id)value { + @synchronized (self) { + void (^nextBlock)(id) = [self.next copy]; + if (nextBlock == nil) return; + + nextBlock(value); + } +} + +- (void)sendError:(NSError *)e { + @synchronized (self) { + void (^errorBlock)(NSError *) = [self.error copy]; + [self.disposable dispose]; + + if (errorBlock == nil) return; + errorBlock(e); + } +} + +- (void)sendCompleted { + @synchronized (self) { + void (^completedBlock)(void) = [self.completed copy]; + [self.disposable dispose]; + + if (completedBlock == nil) return; + completedBlock(); + } +} + +- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d { + if (d.disposed) return; + [self.disposable addDisposable:d]; + + @weakify(self, d); + [d addDisposable:[RACDisposable disposableWithBlock:^{ + @strongify(self, d); + [self.disposable removeDisposable:d]; + }]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h new file mode 100644 index 0000000..8987731 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.h
@@ -0,0 +1,54 @@ +// +// RACSubscriptingAssignmentTrampoline.h +// iOSDemo +// +// Created by Josh Abernathy on 9/24/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "EXTKeyPathCoding.h" + +@class RACSignal; + +/// Assigns a signal to an object property, automatically setting the given key +/// path on every `next`. When the signal completes, the binding is automatically +/// disposed of. +/// +/// There are two different versions of this macro: +/// +/// - RAC(TARGET, KEYPATH, NILVALUE) will bind the `KEYPATH` of `TARGET` to the +/// given signal. If the signal ever sends a `nil` value, the property will be +/// set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object +/// properties, but an NSValue should be used for primitive properties, to +/// avoid an exception if `nil` is sent (which might occur if an intermediate +/// object is set to `nil`). +/// - RAC(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to +/// `nil`. +/// +/// See -[RACSignal setKeyPath:onObject:nilValue:] for more information about the +/// binding's semantics. +/// +/// Examples +/// +/// RAC(self, objectProperty) = objectSignal; +/// RAC(self, stringProperty, @"foobar") = stringSignal; +/// RAC(self, integerProperty, @42) = integerSignal; +/// +/// WARNING: Under certain conditions, use of this macro can be thread-unsafe. +/// See the documentation of -setKeyPath:onObject:nilValue:. +#define RAC(TARGET, ...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \ + (RAC_(TARGET, __VA_ARGS__, nil)) \ + (RAC_(TARGET, __VA_ARGS__)) + +/// Do not use this directly. Use the RAC macro above. +#define RAC_(TARGET, KEYPATH, NILVALUE) \ + [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)] + +@interface RACSubscriptingAssignmentTrampoline : NSObject + +- (id)initWithTarget:(id)target nilValue:(id)nilValue; +- (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.m new file mode 100644 index 0000000..2ab8cd3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptingAssignmentTrampoline.m
@@ -0,0 +1,42 @@ +// +// RACSubscriptingAssignmentTrampoline.m +// iOSDemo +// +// Created by Josh Abernathy on 9/24/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriptingAssignmentTrampoline.h" +#import "RACSignal+Operations.h" + +@interface RACSubscriptingAssignmentTrampoline () + +// The object to bind to. +@property (nonatomic, strong, readonly) id target; + +// A value to use when `nil` is sent on the bound signal. +@property (nonatomic, strong, readonly) id nilValue; + +@end + +@implementation RACSubscriptingAssignmentTrampoline + +- (id)initWithTarget:(id)target nilValue:(id)nilValue { + // This is often a programmer error, but this prevents crashes if the target + // object has unexpectedly deallocated. + if (target == nil) return nil; + + self = [super init]; + if (self == nil) return nil; + + _target = target; + _nilValue = nilValue; + + return self; +} + +- (void)setObject:(RACSignal *)signal forKeyedSubscript:(NSString *)keyPath { + [signal setKeyPath:keyPath onObject:self.target nilValue:self.nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.h new file mode 100644 index 0000000..34f5dc5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.h
@@ -0,0 +1,15 @@ +// +// RACSubscriptionScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +// A private scheduler used only for subscriptions. See the private +// +[RACScheduler subscriptionScheduler] method for more information. +@interface RACSubscriptionScheduler : RACScheduler + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.m new file mode 100644 index 0000000..2aab9c3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACSubscriptionScheduler.m
@@ -0,0 +1,54 @@ +// +// RACSubscriptionScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriptionScheduler.h" +#import "RACScheduler+Private.h" + +@interface RACSubscriptionScheduler () + +// A private background scheduler on which to subscribe if the +currentScheduler +// is unknown. +@property (nonatomic, strong, readonly) RACScheduler *backgroundScheduler; + +@end + +@implementation RACSubscriptionScheduler + +#pragma mark Lifecycle + +- (id)init { + self = [super initWithName:@"com.ReactiveCocoa.RACScheduler.subscriptionScheduler"]; + if (self == nil) return nil; + + _backgroundScheduler = [RACScheduler scheduler]; + + return self; +} + +#pragma mark RACScheduler + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCParameterAssert(block != NULL); + + if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block]; + + block(); + return nil; +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; + return [scheduler after:date schedule:block]; +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + RACScheduler *scheduler = RACScheduler.currentScheduler ?: self.backgroundScheduler; + return [scheduler after:date repeatingEvery:interval withLeeway:leeway schedule:block]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.h new file mode 100644 index 0000000..429e595 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.h
@@ -0,0 +1,24 @@ +// +// RACTargetQueueScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/6/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACQueueScheduler.h" + +/// A scheduler that enqueues blocks on a private serial queue, targeting an +/// arbitrary GCD queue. +@interface RACTargetQueueScheduler : RACQueueScheduler + +/// Initializes the receiver with a serial queue that will target the given +/// `targetQueue`. +/// +/// name - The name of the scheduler. If nil, a default name will be used. +/// targetQueue - The queue to target. Cannot be NULL. +/// +/// Returns the initialized object. +- (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.m new file mode 100644 index 0000000..65114d3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTargetQueueScheduler.m
@@ -0,0 +1,37 @@ +// +// RACTargetQueueScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/6/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTargetQueueScheduler.h" +#import "RACBacktrace.h" +#import "RACQueueScheduler+Subclass.h" + +@implementation RACTargetQueueScheduler + +#pragma mark Lifecycle + +- (id)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue { + NSCParameterAssert(targetQueue != NULL); + + if (name == nil) { + name = [NSString stringWithFormat:@"com.ReactiveCocoa.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)]; + } + + dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL); + if (queue == NULL) return nil; + + dispatch_set_target_queue(queue, targetQueue); + + self = [super initWithName:name queue:queue]; + if (self == nil) return nil; + + dispatch_release(queue); + + return self; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.h new file mode 100644 index 0000000..a790f5b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.h
@@ -0,0 +1,42 @@ +// +// RACTestScheduler.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" + +/// A special kind of scheduler that steps through virtualized time. +/// +/// This scheduler class can be used in unit tests to verify asynchronous +/// behaviors without spending significant time waiting. +/// +/// This class can be used from multiple threads, but only one thread can `step` +/// through the enqueued actions at a time. Other threads will wait while the +/// scheduled blocks are being executed. +@interface RACTestScheduler : RACScheduler + +/// Initializes a new test scheduler. +- (instancetype)init; + +/// Executes the next scheduled block, if any. +/// +/// This method will block until the scheduled action has completed. +- (void)step; + +/// Executes up to the next `ticks` scheduled blocks. +/// +/// This method will block until the scheduled actions have completed. +/// +/// ticks - The number of scheduled blocks to execute. If there aren't this many +/// blocks enqueued, all scheduled blocks are executed. +- (void)step:(NSUInteger)ticks; + +/// Executes all of the scheduled blocks on the receiver. +/// +/// This method will block until the scheduled actions have completed. +- (void)stepAll; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.m new file mode 100644 index 0000000..b6997b2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTestScheduler.m
@@ -0,0 +1,223 @@ +// +// RACTestScheduler.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestScheduler.h" +#import "EXTScope.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACScheduler+Private.h" + +@interface RACTestSchedulerAction : NSObject + +// The date at which the action should be executed. +// +// This absolute time will not actually be honored. This date is only used for +// comparison, to determine which block should be run _next_. +@property (nonatomic, copy, readonly) NSDate *date; + +// The scheduled block. +@property (nonatomic, copy, readonly) void (^block)(void); + +// A disposable for this action. +// +// When disposed, the action should not start executing if it hasn't already. +@property (nonatomic, strong, readonly) RACDisposable *disposable; + +// Initializes a new scheduler action. +- (id)initWithDate:(NSDate *)date block:(void (^)(void))block; + +@end + +static CFComparisonResult RACCompareScheduledActions(const void *ptr1, const void *ptr2, void *info) { + RACTestSchedulerAction *action1 = (__bridge id)ptr1; + RACTestSchedulerAction *action2 = (__bridge id)ptr2; + return CFDateCompare((__bridge CFDateRef)action1.date, (__bridge CFDateRef)action2.date, NULL); +} + +static const void *RACRetainScheduledAction(CFAllocatorRef allocator, const void *ptr) { + return CFRetain(ptr); +} + +static void RACReleaseScheduledAction(CFAllocatorRef allocator, const void *ptr) { + CFRelease(ptr); +} + +@interface RACTestScheduler () + +// All of the RACTestSchedulerActions that have been enqueued and not yet +// executed. +// +// The minimum value in the heap represents the action to execute next. +// +// This property should only be used while synchronized on self. +@property (nonatomic, assign, readonly) CFBinaryHeapRef scheduledActions; + +// The number of blocks that have been directly enqueued with -schedule: so +// far. +// +// This is used to ensure unique dates when two blocks are enqueued +// simultaneously. +// +// This property should only be used while synchronized on self. +@property (nonatomic, assign) NSUInteger numberOfDirectlyScheduledBlocks; + +@end + +@implementation RACTestScheduler + +#pragma mark Lifecycle + +- (instancetype)init { + self = [super initWithName:@"com.github.ReactiveCocoa.RACTestScheduler"]; + if (self == nil) return nil; + + CFBinaryHeapCallBacks callbacks = (CFBinaryHeapCallBacks){ + .version = 0, + .retain = &RACRetainScheduledAction, + .release = &RACReleaseScheduledAction, + .copyDescription = &CFCopyDescription, + .compare = &RACCompareScheduledActions + }; + + _scheduledActions = CFBinaryHeapCreate(NULL, 0, &callbacks, NULL); + return self; +} + +- (void)dealloc { + [self stepAll]; + + if (_scheduledActions != NULL) { + CFRelease(_scheduledActions); + _scheduledActions = NULL; + } +} + +#pragma mark Execution + +- (void)step { + [self step:1]; +} + +- (void)step:(NSUInteger)ticks { + @synchronized (self) { + for (NSUInteger i = 0; i < ticks; i++) { + const void *actionPtr = NULL; + if (!CFBinaryHeapGetMinimumIfPresent(self.scheduledActions, &actionPtr)) break; + + RACTestSchedulerAction *action = (__bridge id)actionPtr; + CFBinaryHeapRemoveMinimumValue(self.scheduledActions); + + if (action.disposable.disposed) continue; + + RACScheduler *previousScheduler = RACScheduler.currentScheduler; + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = self; + + action.block(); + + if (previousScheduler != nil) { + NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; + } else { + [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; + } + } + } +} + +- (void)stepAll { + [self step:NSUIntegerMax]; +} + +#pragma mark RACScheduler + +- (RACDisposable *)schedule:(void (^)(void))block { + NSCParameterAssert(block != nil); + + @synchronized (self) { + NSDate *uniqueDate = [NSDate dateWithTimeIntervalSinceReferenceDate:self.numberOfDirectlyScheduledBlocks]; + self.numberOfDirectlyScheduledBlocks++; + + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:uniqueDate block:block]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + + return action.disposable; + } +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); + + @synchronized (self) { + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:block]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + + return action.disposable; + } +} + +- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); + NSCParameterAssert(interval >= 0); + NSCParameterAssert(leeway >= 0); + + RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; + + @weakify(self); + @synchronized (self) { + __block RACDisposable *thisDisposable = nil; + + void (^reschedulingBlock)(void) = ^{ + @strongify(self); + + [compoundDisposable removeDisposable:thisDisposable]; + + // Schedule the next interval. + RACDisposable *schedulingDisposable = [self after:[date dateByAddingTimeInterval:interval] repeatingEvery:interval withLeeway:leeway schedule:block]; + [compoundDisposable addDisposable:schedulingDisposable]; + + block(); + }; + + RACTestSchedulerAction *action = [[RACTestSchedulerAction alloc] initWithDate:date block:reschedulingBlock]; + CFBinaryHeapAddValue(self.scheduledActions, (__bridge void *)action); + + thisDisposable = action.disposable; + [compoundDisposable addDisposable:thisDisposable]; + } + + return compoundDisposable; +} + +@end + +@implementation RACTestSchedulerAction + +#pragma mark Lifecycle + +- (id)initWithDate:(NSDate *)date block:(void (^)(void))block { + NSCParameterAssert(date != nil); + NSCParameterAssert(block != nil); + + self = [super init]; + if (self == nil) return nil; + + _date = [date copy]; + _block = [block copy]; + _disposable = [[RACDisposable alloc] init]; + + return self; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ date: %@ }", self.class, self, self.date]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h new file mode 100644 index 0000000..647b42c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.h
@@ -0,0 +1,159 @@ +// +// RACTuple.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/12/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "metamacros.h" + +@class RACSequence; + +/// Creates a new tuple with the given values. At least one value must be given. +/// Values can be nil. +#define RACTuplePack(...) \ + RACTuplePack_(__VA_ARGS__) + +/// Declares new object variables and unpacks a RACTuple into them. +/// +/// This macro should be used on the left side of an assignment, with the +/// tuple on the right side. Nothing else should appear on the same line, and the +/// macro should not be the only statement in a conditional or loop body. +/// +/// If the tuple has more values than there are variables listed, the excess +/// values are ignored. +/// +/// If the tuple has fewer values than there are variables listed, the excess +/// variables are initialized to nil. +/// +/// Examples +/// +/// RACTupleUnpack(NSString *string, NSNumber *num) = [RACTuple tupleWithObjects:@"foo", @5, nil]; +/// NSLog(@"string: %@", string); +/// NSLog(@"num: %@", num); +/// +/// /* The above is equivalent to: */ +/// RACTuple *t = [RACTuple tupleWithObjects:@"foo", @5, nil]; +/// NSString *string = t[0]; +/// NSNumber *num = t[1]; +/// NSLog(@"string: %@", string); +/// NSLog(@"num: %@", num); +#define RACTupleUnpack(...) \ + RACTupleUnpack_(__VA_ARGS__) + +/// A sentinel object that represents nils in the tuple. +/// +/// It should never be necessary to create a tuple nil yourself. Just use +/// +tupleNil. +@interface RACTupleNil : NSObject <NSCopying, NSCoding> +/// A singleton instance. ++ (RACTupleNil *)tupleNil; +@end + + +/// A tuple is an ordered collection of objects. It may contain nils, represented +/// by RACTupleNil. +@interface RACTuple : NSObject <NSCoding, NSCopying, NSFastEnumeration> + +@property (nonatomic, readonly) NSUInteger count; + +/// These properties all return the object at that index or nil if the number of +/// objects is less than the index. +@property (nonatomic, readonly) id first; +@property (nonatomic, readonly) id second; +@property (nonatomic, readonly) id third; +@property (nonatomic, readonly) id fourth; +@property (nonatomic, readonly) id fifth; +@property (nonatomic, readonly) id last; + +/// Creates a new tuple out of the array. Does not convert nulls to nils. ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array; + +/// Creates a new tuple out of the array. If `convert` is YES, it also converts +/// every NSNull to RACTupleNil. ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert; + +/// Creates a new tuple with the given objects. Use RACTupleNil to represent +/// nils. ++ (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION; + +/// Returns the object at `index` or nil if the object is a RACTupleNil. Unlike +/// NSArray and friends, it's perfectly fine to ask for the object at an index +/// past the tuple's count - 1. It will simply return nil. +- (id)objectAtIndex:(NSUInteger)index; + +/// Returns an array of all the objects. RACTupleNils are converted to NSNulls. +- (NSArray *)allObjects; + +/// Appends `obj` to the receiver. +/// +/// obj - The object to add to the tuple. This argument may be nil. +/// +/// Returns a new tuple. +- (instancetype)tupleByAddingObject:(id)obj; + +@end + +@interface RACTuple (RACSequenceAdditions) + +/// Returns a sequence of all the objects. RACTupleNils are converted to NSNulls. +@property (nonatomic, copy, readonly) RACSequence *rac_sequence; + +@end + +@interface RACTuple (ObjectSubscripting) +/// Returns the object at that index or nil if the number of objects is less +/// than the index. +- (id)objectAtIndexedSubscript:(NSUInteger)idx; +@end + +/// This and everything below is for internal use only. +/// +/// See RACTuplePack() and RACTupleUnpack() instead. +#define RACTuplePack_(...) \ + ([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]]) + +#define RACTuplePack_object_or_ractuplenil(INDEX, ARG) \ + (ARG) ?: RACTupleNil.tupleNil, + +#define RACTupleUnpack_(...) \ + metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \ + \ + int RACTupleUnpack_state = 0; \ + \ + RACTupleUnpack_after: \ + ; \ + metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \ + if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \ + \ + while (RACTupleUnpack_state != 2) \ + if (RACTupleUnpack_state == 1) { \ + goto RACTupleUnpack_after; \ + } else \ + for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \ + [RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ] + +#define RACTupleUnpack_state metamacro_concat(RACTupleUnpack_state, __LINE__) +#define RACTupleUnpack_after metamacro_concat(RACTupleUnpack_after, __LINE__) +#define RACTupleUnpack_loop metamacro_concat(RACTupleUnpack_loop, __LINE__) + +#define RACTupleUnpack_decl_name(INDEX) \ + metamacro_concat(metamacro_concat(RACTupleUnpack, __LINE__), metamacro_concat(_var, INDEX)) + +#define RACTupleUnpack_decl(INDEX, ARG) \ + __strong id RACTupleUnpack_decl_name(INDEX); + +#define RACTupleUnpack_assign(INDEX, ARG) \ + __strong ARG = RACTupleUnpack_decl_name(INDEX); + +#define RACTupleUnpack_value(INDEX, ARG) \ + [NSValue valueWithPointer:&RACTupleUnpack_decl_name(INDEX)], + +@interface RACTupleUnpackingTrampoline : NSObject + ++ (instancetype)trampoline; +- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m new file mode 100644 index 0000000..9d338db --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTuple.m
@@ -0,0 +1,252 @@ +// +// RACTuple.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/12/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACTuple.h" +#import "EXTKeyPathCoding.h" +#import "RACTupleSequence.h" + +@implementation RACTupleNil + ++ (RACTupleNil *)tupleNil { + static dispatch_once_t onceToken; + static RACTupleNil *tupleNil = nil; + dispatch_once(&onceToken, ^{ + tupleNil = [[self alloc] init]; + }); + + return tupleNil; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder { + // Always return the singleton. + return self.class.tupleNil; +} + +- (void)encodeWithCoder:(NSCoder *)coder { +} + +@end + + +@interface RACTuple () +@property (nonatomic, strong) NSArray *backingArray; +@end + + +@implementation RACTuple + +- (instancetype)init { + self = [super init]; + if (self == nil) return nil; + + self.backingArray = [NSArray array]; + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects]; +} + +- (BOOL)isEqual:(RACTuple *)object { + if (object == self) return YES; + if (![object isKindOfClass:self.class]) return NO; + + return [self.backingArray isEqual:object.backingArray]; +} + +- (NSUInteger)hash { + return self.backingArray.hash; +} + + +#pragma mark NSFastEnumeration + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { + return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len]; +} + + +#pragma mark NSCopying + +- (instancetype)copyWithZone:(NSZone *)zone { + // we're immutable, bitches! + return self; +} + + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder { + self = [self init]; + if (self == nil) return nil; + + self.backingArray = [coder decodeObjectForKey:@keypath(self.backingArray)]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + if (self.backingArray != nil) [coder encodeObject:self.backingArray forKey:@keypath(self.backingArray)]; +} + + +#pragma mark API + ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array { + return [self tupleWithObjectsFromArray:array convertNullsToNils:NO]; +} + ++ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert { + RACTuple *tuple = [[self alloc] init]; + + if (convert) { + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + for (id object in array) { + [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)]; + } + + tuple.backingArray = newArray; + } else { + tuple.backingArray = [array copy]; + } + + return tuple; +} + ++ (instancetype)tupleWithObjects:(id)object, ... { + RACTuple *tuple = [[self alloc] init]; + + va_list args; + va_start(args, object); + + NSUInteger count = 0; + for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { + ++count; + } + + va_end(args); + + if (count == 0) { + tuple.backingArray = @[]; + return tuple; + } + + NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count]; + + va_start(args, object); + for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) { + [objects addObject:currentObject]; + } + + va_end(args); + + tuple.backingArray = objects; + return tuple; +} + +- (id)objectAtIndex:(NSUInteger)index { + if (index >= self.count) return nil; + + id object = [self.backingArray objectAtIndex:index]; + return (object == RACTupleNil.tupleNil ? nil : object); +} + +- (NSArray *)allObjects { + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count]; + for (id object in self.backingArray) { + [newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)]; + } + + return newArray; +} + +- (instancetype)tupleByAddingObject:(id)obj { + NSArray *newArray = [self.backingArray arrayByAddingObject:obj ?: RACTupleNil.tupleNil]; + return [self.class tupleWithObjectsFromArray:newArray convertNullsToNils:NO]; +} + +- (NSUInteger)count { + return self.backingArray.count; +} + +- (id)first { + return [self objectAtIndex:0]; +} + +- (id)second { + return [self objectAtIndex:1]; +} + +- (id)third { + return [self objectAtIndex:2]; +} + +- (id)fourth { + return [self objectAtIndex:3]; +} + +- (id)fifth { + return [self objectAtIndex:4]; +} + +- (id)last { + return [self objectAtIndex:self.count - 1]; +} + +@end + + +@implementation RACTuple (RACSequenceAdditions) + +- (RACSequence *)rac_sequence { + return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0]; +} + +@end + +@implementation RACTuple (ObjectSubscripting) + +- (id)objectAtIndexedSubscript:(NSUInteger)idx { + return [self objectAtIndex:idx]; +} + +@end + + +@implementation RACTupleUnpackingTrampoline + +#pragma mark Lifecycle + ++ (instancetype)trampoline { + static dispatch_once_t onceToken; + static id trampoline = nil; + dispatch_once(&onceToken, ^{ + trampoline = [[self alloc] init]; + }); + + return trampoline; +} + +- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables { + NSCParameterAssert(variables != nil); + + [variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) { + __strong id *ptr = (__strong id *)value.pointerValue; + *ptr = tuple[index]; + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.h new file mode 100644 index 0000000..f6cd021 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.h
@@ -0,0 +1,18 @@ +// +// RACTupleSequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSequence.h" + +// Private class that adapts a RACTuple to the RACSequence interface. +@interface RACTupleSequence : RACSequence + +// Returns a sequence for enumerating over the given backing array (from a +// RACTuple), starting from the given offset. ++ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.m new file mode 100644 index 0000000..ba268f5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACTupleSequence.m
@@ -0,0 +1,68 @@ +// +// RACTupleSequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTupleSequence.h" +#import "RACTuple.h" + +@interface RACTupleSequence () + +// The array being sequenced, as taken from RACTuple.backingArray. +@property (nonatomic, strong, readonly) NSArray *tupleBackingArray; + +// The index in the array from which the sequence starts. +@property (nonatomic, assign, readonly) NSUInteger offset; + +@end + +@implementation RACTupleSequence + +#pragma mark Lifecycle + ++ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset { + NSCParameterAssert(offset <= backingArray.count); + + if (offset == backingArray.count) return self.empty; + + RACTupleSequence *seq = [[self alloc] init]; + seq->_tupleBackingArray = backingArray; + seq->_offset = offset; + return seq; +} + +#pragma mark RACSequence + +- (id)head { + id object = [self.tupleBackingArray objectAtIndex:self.offset]; + return (object == RACTupleNil.tupleNil ? NSNull.null : object); +} + +- (RACSequence *)tail { + RACSequence *sequence = [self.class sequenceWithTupleBackingArray:self.tupleBackingArray offset:self.offset + 1]; + sequence.name = self.name; + return sequence; +} + +- (NSArray *)array { + NSRange range = NSMakeRange(self.offset, self.tupleBackingArray.count - self.offset); + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:range.length]; + + [self.tupleBackingArray enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:range] options:0 usingBlock:^(id object, NSUInteger index, BOOL *stop) { + id mappedObject = (object == RACTupleNil.tupleNil ? NSNull.null : object); + [array addObject:mappedObject]; + }]; + + return array; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, tuple = %@ }", self.class, self, self.name, self.tupleBackingArray]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.h b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.h new file mode 100644 index 0000000..86a514b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.h
@@ -0,0 +1,14 @@ +// +// RACUnarySequence.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSequence.h" + +// Private class representing a sequence of exactly one value. +@interface RACUnarySequence : RACSequence + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.m b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.m new file mode 100644 index 0000000..f72bf09 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnarySequence.m
@@ -0,0 +1,81 @@ +// +// RACUnarySequence.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-05-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACUnarySequence.h" +#import "EXTKeyPathCoding.h" +#import "NSObject+RACDescription.h" + +@interface RACUnarySequence () + +// The single value stored in this sequence. +@property (nonatomic, strong, readwrite) id head; + +@end + +@implementation RACUnarySequence + +#pragma mark Properties + +@synthesize head = _head; + +#pragma mark Lifecycle + ++ (instancetype)return:(id)value { + RACUnarySequence *sequence = [[self alloc] init]; + sequence.head = value; + return [sequence setNameWithFormat:@"+return: %@", [value rac_description]]; +} + +#pragma mark RACSequence + +- (RACSequence *)tail { + return nil; +} + +- (instancetype)bind:(RACStreamBindBlock (^)(void))block { + RACStreamBindBlock bindBlock = block(); + BOOL stop = NO; + + RACSequence *result = (id)[bindBlock(self.head, &stop) setNameWithFormat:@"[%@] -bind:", self.name]; + return result ?: self.class.empty; +} + +#pragma mark NSCoding + +- (Class)classForCoder { + // Unary sequences should be encoded as themselves, not array sequences. + return self.class; +} + +- (id)initWithCoder:(NSCoder *)coder { + id value = [coder decodeObjectForKey:@keypath(self.head)]; + return [self.class return:value]; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + if (self.head != nil) [coder encodeObject:self.head forKey:@keypath(self.head)]; +} + +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ name = %@, head = %@ }", self.class, self, self.name, self.head]; +} + +- (NSUInteger)hash { + return [self.head hash]; +} + +- (BOOL)isEqual:(RACUnarySequence *)seq { + if (self == seq) return YES; + if (![seq isKindOfClass:RACUnarySequence.class]) return NO; + + return self.head == seq.head || [(NSObject *)self.head isEqual:seq.head]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.h b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.h new file mode 100644 index 0000000..7713e1d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.h
@@ -0,0 +1,20 @@ +// +// RACUnit.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/27/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + + +/// A unit represents an empty value. +/// +/// It should never be necessary to create a unit yourself. Just use +defaultUnit. +@interface RACUnit : NSObject + +/// A singleton instance. ++ (RACUnit *)defaultUnit; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.m b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.m new file mode 100644 index 0000000..76b8425 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACUnit.m
@@ -0,0 +1,27 @@ +// +// RACUnit.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/27/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACUnit.h" + + +@implementation RACUnit + + +#pragma mark API + ++ (RACUnit *)defaultUnit { + static dispatch_once_t onceToken; + static RACUnit *defaultUnit = nil; + dispatch_once(&onceToken, ^{ + defaultUnit = [[self alloc] init]; + }); + + return defaultUnit; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.h b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.h new file mode 100644 index 0000000..740b861 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.h
@@ -0,0 +1,16 @@ +// +// RACValueTransformer.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/6/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +// A private block based transformer. +@interface RACValueTransformer : NSValueTransformer + ++ (instancetype)transformerWithBlock:(id (^)(id value))block; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.m b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.m new file mode 100644 index 0000000..ccec07d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/RACValueTransformer.m
@@ -0,0 +1,42 @@ +// +// RACValueTransformer.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/6/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACValueTransformer.h" + +@interface RACValueTransformer () +@property (nonatomic, copy) id (^transformBlock)(id value); +@end + + +@implementation RACValueTransformer + + +#pragma mark NSValueTransformer + ++ (BOOL)allowsReverseTransformation { + return NO; +} + +- (id)transformedValue:(id)value { + return self.transformBlock(value); +} + + +#pragma mark API + +@synthesize transformBlock; + ++ (instancetype)transformerWithBlock:(id (^)(id value))block { + NSCParameterAssert(block != NULL); + + RACValueTransformer *transformer = [[self alloc] init]; + transformer.transformBlock = block; + return transformer; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa-Info.plist b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa-Info.plist new file mode 100644 index 0000000..4e63eab --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa-Info.plist
@@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string></string> + <key>CFBundleIdentifier</key> + <string>com.github.${PRODUCT_NAME:rfc1034identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>NSHumanReadableCopyright</key> + <string>Copyright © 2012 GitHub, Inc. All rights reserved.</string> + <key>NSPrincipalClass</key> + <string></string> +</dict> +</plist>
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa-Prefix.pch b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa-Prefix.pch new file mode 100644 index 0000000..aa9849d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa-Prefix.pch
@@ -0,0 +1,13 @@ +// +// Prefix header for all source files of the 'ReactiveCocoa' target in the 'ReactiveCocoa' project +// + +#ifdef __OBJC__ + #import <Foundation/Foundation.h> +#endif + +#undef NSAssert +#undef NSParameterAssert + +extern void NSAssert(int condition, ...) __attribute__((unavailable("Use NSCAssert instead."))); +extern void NSParameterAssert(int condition, ...) __attribute__((unavailable("Use NSCParameterAssert instead.")));
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h new file mode 100644 index 0000000..41f32d9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/ReactiveCocoa.h
@@ -0,0 +1,74 @@ +// +// ReactiveCocoa.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/5/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "EXTKeyPathCoding.h" +#import "NSArray+RACSequenceAdditions.h" +#import "NSData+RACSupport.h" +#import "NSDictionary+RACSequenceAdditions.h" +#import "NSEnumerator+RACSequenceAdditions.h" +#import "NSFileHandle+RACSupport.h" +#import "NSNotificationCenter+RACSupport.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACLifting.h" +#import "NSObject+RACPropertySubscribing.h" +#import "NSObject+RACSelectorSignal.h" +#import "NSOrderedSet+RACSequenceAdditions.h" +#import "NSSet+RACSequenceAdditions.h" +#import "NSString+RACSequenceAdditions.h" +#import "NSString+RACSupport.h" +#import "NSIndexSet+RACSequenceAdditions.h" +#import "NSURLConnection+RACSupport.h" +#import "NSUserDefaults+RACSupport.h" +#import "RACBehaviorSubject.h" +#import "RACChannel.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACEvent.h" +#import "RACGroupedSignal.h" +#import "RACKVOChannel.h" +#import "RACMulticastConnection.h" +#import "RACQueueScheduler.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" +#import "RACScopedDisposable.h" +#import "RACSequence.h" +#import "RACSerialDisposable.h" +#import "RACSignal+Operations.h" +#import "RACSignal.h" +#import "RACStream.h" +#import "RACSubject.h" +#import "RACSubscriber.h" +#import "RACSubscriptingAssignmentTrampoline.h" +#import "RACTargetQueueScheduler.h" +#import "RACTestScheduler.h" +#import "RACTuple.h" +#import "RACUnit.h" + +#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED + #import "UIActionSheet+RACSignalSupport.h" + #import "UIAlertView+RACSignalSupport.h" + #import "UIBarButtonItem+RACCommandSupport.h" + #import "UIButton+RACCommandSupport.h" + #import "UICollectionReusableView+RACSignalSupport.h" + #import "UIControl+RACSignalSupport.h" + #import "UIDatePicker+RACSignalSupport.h" + #import "UIGestureRecognizer+RACSignalSupport.h" + #import "UISegmentedControl+RACSignalSupport.h" + #import "UISlider+RACSignalSupport.h" + #import "UIStepper+RACSignalSupport.h" + #import "UISwitch+RACSignalSupport.h" + #import "UITableViewCell+RACSignalSupport.h" + #import "UITextField+RACSignalSupport.h" + #import "UITextView+RACSignalSupport.h" +#elif TARGET_OS_MAC + #import "NSControl+RACCommandSupport.h" + #import "NSControl+RACTextSignalSupport.h" + #import "NSObject+RACAppKitBindings.h" + #import "NSText+RACSignalSupport.h" +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.h new file mode 100644 index 0000000..3d667e9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.h
@@ -0,0 +1,32 @@ +// +// UIActionSheet+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Dave Lee on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACDelegateProxy; +@class RACSignal; + +@interface UIActionSheet (RACSignalSupport) + +/// A delegate proxy which will be set as the receiver's delegate when any of the +/// methods in this category are used. +@property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; + +/// Creates a signal for button clicks on the receiver. +/// +/// When this method is invoked, the `rac_delegateProxy` will become the +/// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy +/// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't +/// know how to handle. Setting the receiver's `delegate` afterward is +/// considered undefined behavior. +/// +/// Returns a signal which will send the index of the specific button clicked. +/// The signal will complete when the receiver is deallocated. +- (RACSignal *)rac_buttonClickedSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.m new file mode 100644 index 0000000..6198797 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIActionSheet+RACSignalSupport.m
@@ -0,0 +1,50 @@ +// +// UIActionSheet+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Dave Lee on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIActionSheet+RACSignalSupport.h" +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "NSObject+RACDeallocating.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" +#import <objc/runtime.h> + +@implementation UIActionSheet (RACSignalSupport) + +static void RACUseDelegateProxy(UIActionSheet *self) { + if (self.delegate == self.rac_delegateProxy) return; + + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; +} + +- (RACDelegateProxy *)rac_delegateProxy { + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIActionSheetDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + return proxy; +} + +- (RACSignal *)rac_buttonClickedSignal { + RACSignal *signal = [[[[self.rac_delegateProxy + signalForSelector:@selector(actionSheet:clickedButtonAtIndex:)] + reduceEach:^(UIActionSheet *actionSheet, NSNumber *buttonIndex) { + return buttonIndex; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_buttonClickedSignal", [self rac_description]]; + + RACUseDelegateProxy(self); + + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.h new file mode 100644 index 0000000..4586460 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.h
@@ -0,0 +1,32 @@ +// +// UIAlertView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Henrik Hodne on 6/16/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACDelegateProxy; +@class RACSignal; + +@interface UIAlertView (RACSignalSupport) + +/// A delegate proxy which will be set as the receiver's delegate when any of the +/// methods in this category are used. +@property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; + +/// Creates a signal for button clicks on the receiver. +/// +/// When this method is invoked, the `rac_delegateProxy` will become the +/// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy +/// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't +/// know how to handle. Setting the receiver's `delegate` afterward is considered +/// undefined behavior. +/// +/// Returns a signal which will send the index of the specific button clicked. +/// The signal will complete itself when the receiver is deallocated. +- (RACSignal *)rac_buttonClickedSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.m new file mode 100644 index 0000000..bf15b27 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIAlertView+RACSignalSupport.m
@@ -0,0 +1,50 @@ +// +// UIAlertView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Henrik Hodne on 6/16/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIAlertView+RACSignalSupport.h" +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "RACTuple.h" +#import <objc/runtime.h> + +@implementation UIAlertView (RACSignalSupport) + +static void RACUseDelegateProxy(UIAlertView *self) { + if (self.delegate == self.rac_delegateProxy) return; + + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; +} + +- (RACDelegateProxy *)rac_delegateProxy { + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UIAlertViewDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + return proxy; +} + +- (RACSignal *)rac_buttonClickedSignal { + RACSignal *signal = [[[[self.rac_delegateProxy + signalForSelector:@selector(alertView:clickedButtonAtIndex:)] + reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { + return buttonIndex; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_buttonClickedSignal", [self rac_description]]; + + RACUseDelegateProxy(self); + + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h new file mode 100644 index 0000000..c04648a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.h
@@ -0,0 +1,22 @@ +// +// UIBarButtonItem+RACCommandSupport.h +// ReactiveCocoa +// +// Created by Kyle LeNeau on 3/27/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACCommand; + +@interface UIBarButtonItem (RACCommandSupport) + +/// Sets the control's command. When the control is clicked, the command is +/// executed with the sender of the event. The control's enabledness is bound +/// to the command's `canExecute`. +/// +/// Note: this will reset the control's target and action. +@property (nonatomic, strong) RACCommand *rac_command; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m new file mode 100644 index 0000000..91b7d19 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIBarButtonItem+RACCommandSupport.m
@@ -0,0 +1,55 @@ +// +// UIBarButtonItem+RACCommandSupport.m +// ReactiveCocoa +// +// Created by Kyle LeNeau on 3/27/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIBarButtonItem+RACCommandSupport.h" +#import "EXTKeyPathCoding.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCommand.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import <objc/runtime.h> + +static void *UIControlRACCommandKey = &UIControlRACCommandKey; +static void *UIControlEnabledDisposableKey = &UIControlEnabledDisposableKey; + +@implementation UIBarButtonItem (RACCommandSupport) + +- (RACCommand *)rac_command { + return objc_getAssociatedObject(self, UIControlRACCommandKey); +} + +- (void)setRac_command:(RACCommand *)command { + objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Check for stored signal in order to remove it and add a new one + RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey); + [disposable dispose]; + + if (command == nil) return; + + disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; + objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [self rac_hijackActionAndTargetIfNeeded]; +} + +- (void)rac_hijackActionAndTargetIfNeeded { + SEL hijackSelector = @selector(rac_commandPerformAction:); + if (self.target == self && self.action == hijackSelector) return; + + if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action."); + + self.target = self; + self.action = hijackSelector; +} + +- (void)rac_commandPerformAction:(id)sender { + [self.rac_command execute:sender]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.h new file mode 100644 index 0000000..642ba8e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.h
@@ -0,0 +1,20 @@ +// +// UIButton+RACCommandSupport.h +// ReactiveCocoa +// +// Created by Ash Furrow on 2013-06-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACCommand; + +@interface UIButton (RACCommandSupport) + +/// Sets the button's command. When the button is clicked, the command is +/// executed with the sender of the event. The button's enabledness is bound +/// to the command's `canExecute`. +@property (nonatomic, strong) RACCommand *rac_command; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.m new file mode 100644 index 0000000..2f5f1a0 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIButton+RACCommandSupport.m
@@ -0,0 +1,57 @@ +// +// UIButton+RACCommandSupport.m +// ReactiveCocoa +// +// Created by Ash Furrow on 2013-06-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIButton+RACCommandSupport.h" +#import "EXTKeyPathCoding.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCommand.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import <objc/runtime.h> + +static void *UIButtonRACCommandKey = &UIButtonRACCommandKey; +static void *UIButtonEnabledDisposableKey = &UIButtonEnabledDisposableKey; + +@implementation UIButton (RACCommandSupport) + +- (RACCommand *)rac_command { + return objc_getAssociatedObject(self, UIButtonRACCommandKey); +} + +- (void)setRac_command:(RACCommand *)command { + objc_setAssociatedObject(self, UIButtonRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Check for stored signal in order to remove it and add a new one + RACDisposable *disposable = objc_getAssociatedObject(self, UIButtonEnabledDisposableKey); + [disposable dispose]; + + if (command == nil) return; + + disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; + objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [self rac_hijackActionAndTargetIfNeeded]; +} + +- (void)rac_hijackActionAndTargetIfNeeded { + SEL hijackSelector = @selector(rac_commandPerformAction:); + + for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) { + if (hijackSelector == NSSelectorFromString(selector)) { + return; + } + } + + [self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside]; +} + +- (void)rac_commandPerformAction:(id)sender { + [self.rac_command execute:sender]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.h new file mode 100644 index 0000000..96b3dfe --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.h
@@ -0,0 +1,29 @@ +// +// UICollectionReusableView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Kent Wong on 2013-10-04. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +// This category is only applicable to iOS >= 6.0. +@interface UICollectionReusableView (RACSignalSupport) + +/// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon +/// the receiver. +/// +/// Examples +/// +/// [[[self.cancelButton +/// rac_signalForControlEvents:UIControlEventTouchUpInside] +/// takeUntil:self.rac_prepareForReuseSignal] +/// subscribeNext:^(UIButton *x) { +/// // do other things +/// }]; +@property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.m new file mode 100644 index 0000000..7e8680c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UICollectionReusableView+RACSignalSupport.m
@@ -0,0 +1,31 @@ +// +// UICollectionReusableView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Kent Wong on 2013-10-04. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UICollectionReusableView+RACSignalSupport.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACSignal+Operations.h" +#import "RACUnit.h" +#import <objc/runtime.h> + +@implementation UICollectionReusableView (RACSignalSupport) + +- (RACSignal *)rac_prepareForReuseSignal { + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; + + signal = [[[self + rac_signalForSelector:@selector(prepareForReuse)] + mapReplace:RACUnit.defaultUnit] + setNameWithFormat:@"%@ -rac_prepareForReuseSignal", self.rac_description]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.h new file mode 100644 index 0000000..2de86cf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.h
@@ -0,0 +1,19 @@ +// +// UIControl+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +@interface UIControl (RACSignalSupport) + +/// Creates and returns a signal that sends the sender of the control event +/// whenever one of the control events is triggered. +- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.m new file mode 100644 index 0000000..9e818ca --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupport.m
@@ -0,0 +1,41 @@ +// +// UIControl+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "UIControl+RACSignalSupport.h" +#import "EXTScope.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "RACSubscriber+Private.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" + +@implementation UIControl (RACSignalSupport) + +- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents { + @weakify(self); + + return [[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + @strongify(self); + + [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subscriber sendCompleted]; + }]]; + + return [RACDisposable disposableWithBlock:^{ + @strongify(self); + [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; + }]; + }] + setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", [self rac_description], (unsigned long)controlEvents]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.h b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.h new file mode 100644 index 0000000..7f778a4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.h
@@ -0,0 +1,29 @@ +// +// UIControl+RACSignalSupportPrivate.h +// ReactiveCocoa +// +// Created by Uri Baghin on 06/08/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UIControl (RACSignalSupportPrivate) + +// Adds a RACChannel-based interface to the receiver for the given +// UIControlEvents and exposes it. +// +// controlEvents - A mask of UIControlEvents on which to send new values. +// key - The key whose value should be read and set when a control +// event fires and when a value is sent to the +// RACChannelTerminal respectively. +// nilValue - The value to be assigned to the key when `nil` is sent to the +// RACChannelTerminal. +// +// Returns a RACChannelTerminal which will send future values from the receiver, +// and update the receiver when values are sent to the terminal. +- (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.m b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.m new file mode 100644 index 0000000..b73f817 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIControl+RACSignalSupportPrivate.m
@@ -0,0 +1,50 @@ +// +// UIControl+RACSignalSupportPrivate.m +// ReactiveCocoa +// +// Created by Uri Baghin on 06/08/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIControl+RACSignalSupportPrivate.h" +#import "EXTScope.h" +#import "NSInvocation+RACTypeParsing.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACLifting.h" +#import "RACChannel.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import "UIControl+RACSignalSupport.h" + +@implementation UIControl (RACSignalSupportPrivate) + +- (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue { + NSCParameterAssert(key.length > 0); + key = [key copy]; + RACChannel *channel = [[RACChannel alloc] init]; + + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [channel.followingTerminal sendCompleted]; + }]]; + + RACSignal *eventSignal = [[[self + rac_signalForControlEvents:controlEvents] + mapReplace:key] + takeUntil:[[channel.followingTerminal + ignoreValues] + catchTo:RACSignal.empty]]; + [[self + rac_liftSelector:@selector(valueForKey:) withSignals:eventSignal, nil] + subscribe:channel.followingTerminal]; + + RACSignal *valuesSignal = [channel.followingTerminal + map:^(id value) { + return value ?: nilValue; + }]; + [self rac_liftSelector:@selector(setValue:forKey:) withSignals:valuesSignal, [RACSignal return:key], nil]; + + return channel.leadingTerminal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.h new file mode 100644 index 0000000..e620dfc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UIDatePicker+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UIDatePicker (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The date to set when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's date whenever the +/// UIControlEventValueChanged control event is fired, and sets the date to the +/// values it receives. +- (RACChannelTerminal *)rac_newDateChannelWithNilValue:(NSDate *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.m new file mode 100644 index 0000000..fbae59c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIDatePicker+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UIDatePicker+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIDatePicker+RACSignalSupport.h" +#import "EXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UIDatePicker (RACSignalSupport) + +- (RACChannelTerminal *)rac_newDateChannelWithNilValue:(NSDate *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.date) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.h new file mode 100644 index 0000000..ee774fe --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.h
@@ -0,0 +1,18 @@ +// +// UIGestureRecognizer+RACSignalSupport.h +// Talks +// +// Created by Josh Vera on 5/5/13. +// Copyright (c) 2013 GitHub. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +@interface UIGestureRecognizer (RACSignalSupport) + +/// Returns a signal that sends the receiver when its gesture occurs. +- (RACSignal *)rac_gestureSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.m new file mode 100644 index 0000000..afd5127 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIGestureRecognizer+RACSignalSupport.m
@@ -0,0 +1,40 @@ +// +// UIGestureRecognizer+RACSignalSupport.m +// Talks +// +// Created by Josh Vera on 5/5/13. +// Copyright (c) 2013 GitHub. All rights reserved. +// + +#import "UIGestureRecognizer+RACSignalSupport.h" +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "RACSubscriber.h" +#import "NSObject+RACDescription.h" + +@implementation UIGestureRecognizer (RACSignalSupport) + +- (RACSignal *)rac_gestureSignal { + @weakify(self); + + return [[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + @strongify(self); + + [self addTarget:subscriber action:@selector(sendNext:)]; + [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + [subscriber sendCompleted]; + }]]; + + return [RACDisposable disposableWithBlock:^{ + @strongify(self); + [self removeTarget:subscriber action:@selector(sendNext:)]; + }]; + }] + setNameWithFormat:@"%@ -rac_gestureSignal", [self rac_description]]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.h new file mode 100644 index 0000000..0bd1bf1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.h
@@ -0,0 +1,22 @@ +// +// UIRefreshControl+RACCommandSupport.h +// ReactiveCocoa +// +// Created by Dave Lee on 2013-10-17. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACCommand; + +@interface UIRefreshControl (RACCommandSupport) + +/// Manipulate the RACCommand property associated with this refresh control. +/// +/// When this refresh control is activated by the user, the command will be +/// executed. Upon completion or error of the execution signal, -endRefreshing +/// will be invoked. +@property (nonatomic, strong) RACCommand *rac_command; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.m new file mode 100644 index 0000000..9447f3e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIRefreshControl+RACCommandSupport.m
@@ -0,0 +1,59 @@ +// +// UIRefreshControl+RACCommandSupport.m +// ReactiveCocoa +// +// Created by Dave Lee on 2013-10-17. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIRefreshControl+RACCommandSupport.h" +#import "EXTKeyPathCoding.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACDisposable.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "UIControl+RACSignalSupport.h" +#import <objc/runtime.h> + +static void *UIRefreshControlRACCommandKey = &UIRefreshControlRACCommandKey; +static void *UIRefreshControlDisposableKey = &UIRefreshControlDisposableKey; + +@implementation UIRefreshControl (RACCommandSupport) + +- (RACCommand *)rac_command { + return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey); +} + +- (void)setRac_command:(RACCommand *)command { + objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Dispose of any active command associations. + [objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose]; + + if (command == nil) return; + + // Like RAC(self, enabled) = command.enabled; but with access to disposable. + RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; + + RACDisposable *executionDisposable = [[[[self + rac_signalForControlEvents:UIControlEventValueChanged] + map:^(UIRefreshControl *x) { + return [[[command + execute:x] + catchTo:[RACSignal empty]] + then:^{ + return [RACSignal return:x]; + }]; + }] + concat] + subscribeNext:^(UIRefreshControl *x) { + [x endRefreshing]; + }]; + + RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]]; + objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.h new file mode 100644 index 0000000..2d3c3e7 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UISegmentedControl+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UISegmentedControl (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The segment to select when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's currently selected +/// segment's index whenever the UIControlEventValueChanged control event is +/// fired, and sets the selected segment index to the values it receives. +- (RACChannelTerminal *)rac_newSelectedSegmentIndexChannelWithNilValue:(NSNumber *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.m new file mode 100644 index 0000000..9ccea2b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISegmentedControl+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UISegmentedControl+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UISegmentedControl+RACSignalSupport.h" +#import "EXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UISegmentedControl (RACSignalSupport) + +- (RACChannelTerminal *)rac_newSelectedSegmentIndexChannelWithNilValue:(NSNumber *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.selectedSegmentIndex) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.h new file mode 100644 index 0000000..75626ad --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UISlider+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UISlider (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The value to set when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's value whenever the +/// UIControlEventValueChanged control event is fired, and sets the value to the +/// values it receives. +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.m new file mode 100644 index 0000000..2d37a3b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISlider+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UISlider+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UISlider+RACSignalSupport.h" +#import "EXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UISlider (RACSignalSupport) + +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.value) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.h new file mode 100644 index 0000000..da6d97b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.h
@@ -0,0 +1,24 @@ +// +// UIStepper+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UIStepper (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// nilValue - The value to set when the terminal receives `nil`. +/// +/// Returns a RACChannelTerminal that sends the receiver's value whenever the +/// UIControlEventValueChanged control event is fired, and sets the value to the +/// values it receives. +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.m new file mode 100644 index 0000000..d8c5e81 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UIStepper+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UIStepper+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIStepper+RACSignalSupport.h" +#import "EXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UIStepper (RACSignalSupport) + +- (RACChannelTerminal *)rac_newValueChannelWithNilValue:(NSNumber *)nilValue { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.value) nilValue:nilValue]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.h new file mode 100644 index 0000000..1313a2c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.h
@@ -0,0 +1,22 @@ +// +// UISwitch+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACChannelTerminal; + +@interface UISwitch (RACSignalSupport) + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// Returns a RACChannelTerminal that sends whether the receiver is on whenever +/// the UIControlEventValueChanged control event is fired, and sets it on or off +/// when it receives @YES or @NO respectively. +- (RACChannelTerminal *)rac_newOnChannel; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.m new file mode 100644 index 0000000..1733fc5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UISwitch+RACSignalSupport.m
@@ -0,0 +1,20 @@ +// +// UISwitch+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Uri Baghin on 20/07/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UISwitch+RACSignalSupport.h" +#import "EXTKeyPathCoding.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UISwitch (RACSignalSupport) + +- (RACChannelTerminal *)rac_newOnChannel { + return [self rac_channelForControlEvents:UIControlEventValueChanged key:@keypath(self.on) nilValue:@NO]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.h new file mode 100644 index 0000000..c29d47c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.h
@@ -0,0 +1,28 @@ +// +// UITableViewCell+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +@interface UITableViewCell (RACSignalSupport) + +/// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon +/// the receiver. +/// +/// Examples +/// +/// [[[self.cancelButton +/// rac_signalForControlEvents:UIControlEventTouchUpInside] +/// takeUntil:self.rac_prepareForReuseSignal] +/// subscribeNext:^(UIButton *x) { +/// // do other things +/// }]; +@property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.m new file mode 100644 index 0000000..8a81c30 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewCell+RACSignalSupport.m
@@ -0,0 +1,31 @@ +// +// UITableViewCell+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UITableViewCell+RACSignalSupport.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACSignal+Operations.h" +#import "RACUnit.h" +#import <objc/runtime.h> + +@implementation UITableViewCell (RACSignalSupport) + +- (RACSignal *)rac_prepareForReuseSignal { + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; + + signal = [[[self + rac_signalForSelector:@selector(prepareForReuse)] + mapReplace:RACUnit.defaultUnit] + setNameWithFormat:@"%@ -rac_prepareForReuseSignal", self.rac_description]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.h new file mode 100644 index 0000000..6c5d6b8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.h
@@ -0,0 +1,29 @@ +// +// UITableViewHeaderFooterView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Syo Ikeda on 12/30/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal; + +// This category is only applicable to iOS >= 6.0. +@interface UITableViewHeaderFooterView (RACSignalSupport) + +/// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon +/// the receiver. +/// +/// Examples +/// +/// [[[self.cancelButton +/// rac_signalForControlEvents:UIControlEventTouchUpInside] +/// takeUntil:self.rac_prepareForReuseSignal] +/// subscribeNext:^(UIButton *x) { +/// // do other things +/// }]; +@property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.m new file mode 100644 index 0000000..0ecfc37 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITableViewHeaderFooterView+RACSignalSupport.m
@@ -0,0 +1,31 @@ +// +// UITableViewHeaderFooterView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Syo Ikeda on 12/30/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UITableViewHeaderFooterView+RACSignalSupport.h" +#import "NSObject+RACDescription.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACSignal+Operations.h" +#import "RACUnit.h" +#import <objc/runtime.h> + +@implementation UITableViewHeaderFooterView (RACSignalSupport) + +- (RACSignal *)rac_prepareForReuseSignal { + RACSignal *signal = objc_getAssociatedObject(self, _cmd); + if (signal != nil) return signal; + + signal = [[[self + rac_signalForSelector:@selector(prepareForReuse)] + mapReplace:RACUnit.defaultUnit] + setNameWithFormat:@"%@ -rac_prepareForReuseSignal", self.rac_description]; + + objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.h new file mode 100644 index 0000000..4ff6ee4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.h
@@ -0,0 +1,27 @@ +// +// UITextField+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACSignal, RACChannelTerminal; + +@interface UITextField (RACSignalSupport) + +/// Creates and returns a signal for the text of the field. It always starts with +/// the current text. The signal sends next when the UIControlEventEditingChanged +/// or UIControlEventEditingDidBegin control event is fired on the control. +- (RACSignal *)rac_textSignal; + +/// Creates a new RACChannel-based binding to the receiver. +/// +/// Returns a RACChannelTerminal that sends the receiver's text whenever the +/// UIControlEventEditingChanged or UIControlEventEditingDidBegin control event +/// is fired, and sets the text to the values it receives. +- (RACChannelTerminal *)rac_newTextChannel; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.m new file mode 100644 index 0000000..ca2c68c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextField+RACSignalSupport.m
@@ -0,0 +1,39 @@ +// +// UITextField+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 4/17/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "UITextField+RACSignalSupport.h" +#import "EXTKeyPathCoding.h" +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACDescription.h" +#import "RACSignal+Operations.h" +#import "UIControl+RACSignalSupport.h" +#import "UIControl+RACSignalSupportPrivate.h" + +@implementation UITextField (RACSignalSupport) + +- (RACSignal *)rac_textSignal { + @weakify(self); + return [[[[[RACSignal + defer:^{ + @strongify(self); + return [RACSignal return:self]; + }] + concat:[self rac_signalForControlEvents:UIControlEventEditingChanged | UIControlEventEditingDidBegin]] + map:^(UITextField *x) { + return x.text; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_textSignal", [self rac_description]]; +} + +- (RACChannelTerminal *)rac_newTextChannel { + return [self rac_channelForControlEvents:UIControlEventEditingChanged | UIControlEventEditingDidBegin key:@keypath(self.text) nilValue:@""]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.h b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.h new file mode 100644 index 0000000..174b1ba --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.h
@@ -0,0 +1,39 @@ +// +// UITextView+RACSignalSupport.h +// ReactiveCocoa +// +// Created by Cody Krieger on 5/18/12. +// Copyright (c) 2012 Cody Krieger. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@class RACDelegateProxy; +@class RACSignal; + +@interface UITextView (RACSignalSupport) + +/// A delegate proxy which will be set as the receiver's delegate when any of the +/// methods in this category are used. +@property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; + +/// Creates a signal for the text of the receiver. +/// +/// When this method is invoked, the `rac_delegateProxy` will become the +/// receiver's delegate. Any previous delegate will become the -[RACDelegateProxy +/// rac_proxiedDelegate], so that it receives any messages that the proxy doesn't +/// know how to handle. Setting the receiver's `delegate` afterward is +/// considered undefined behavior. +/// +/// Returns a signal which will send the current text upon subscription, then +/// again whenever the receiver's text is changed. The signal will complete when +/// the receiver is deallocated. +- (RACSignal *)rac_textSignal; + +@end + +@interface UITextView (RACSignalSupportUnavailable) + +- (RACSignal *)rac_signalForDelegateMethod:(SEL)method __attribute__((unavailable("Use -rac_signalForSelector:fromProtocol: instead"))); + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.m b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.m new file mode 100644 index 0000000..aa1cf12 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/UITextView+RACSignalSupport.m
@@ -0,0 +1,56 @@ +// +// UITextView+RACSignalSupport.m +// ReactiveCocoa +// +// Created by Cody Krieger on 5/18/12. +// Copyright (c) 2012 Cody Krieger. All rights reserved. +// + +#import "UITextView+RACSignalSupport.h" +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "RACDelegateProxy.h" +#import "RACSignal+Operations.h" +#import "RACTuple.h" +#import "NSObject+RACDescription.h" +#import <objc/runtime.h> + +@implementation UITextView (RACSignalSupport) + +static void RACUseDelegateProxy(UITextView *self) { + if (self.delegate == self.rac_delegateProxy) return; + + self.rac_delegateProxy.rac_proxiedDelegate = self.delegate; + self.delegate = (id)self.rac_delegateProxy; +} + +- (RACDelegateProxy *)rac_delegateProxy { + RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); + if (proxy == nil) { + proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UITextViewDelegate)]; + objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + return proxy; +} + +- (RACSignal *)rac_textSignal { + @weakify(self); + RACSignal *signal = [[[[[RACSignal + defer:^{ + @strongify(self); + return [RACSignal return:RACTuplePack(self)]; + }] + concat:[self.rac_delegateProxy signalForSelector:@selector(textViewDidChange:)]] + reduceEach:^(UITextView *x) { + return x.text; + }] + takeUntil:self.rac_willDeallocSignal] + setNameWithFormat:@"%@ -rac_textSignal", [self rac_description]]; + + RACUseDelegateProxy(self); + + return signal; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/en.lproj/InfoPlist.strings b/ReactiveCocoaFramework/ReactiveCocoa/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ +
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTKeyPathCoding.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTKeyPathCoding.h new file mode 100644 index 0000000..f34dc4a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTKeyPathCoding.h
@@ -0,0 +1,68 @@ +// +// EXTKeyPathCoding.h +// extobjc +// +// Created by Justin Spahr-Summers on 19.06.12. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import <Foundation/Foundation.h> +#import "metamacros.h" + +/** + * \@keypath allows compile-time verification of key paths. Given a real object + * receiver and key path: + * + * @code + +NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String); +// => @"lowercaseString.UTF8String" + +NSString *versionPath = @keypath(NSObject, version); +// => @"version" + +NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString); +// => @"lowercaseString" + + * @endcode + * + * ... the macro returns an \c NSString containing all but the first path + * component or argument (e.g., @"lowercaseString.UTF8String", @"version"). + * + * In addition to simply creating a key path, this macro ensures that the key + * path is valid at compile-time (causing a syntax error if not), and supports + * refactoring, such that changing the name of the property will also update any + * uses of \@keypath. + */ +#define keypath(...) \ + metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__)) + +#define keypath1(PATH) \ + (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1)) + +#define keypath2(OBJ, PATH) \ + (((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) + +/** + * \@collectionKeypath allows compile-time verification of key paths across collections NSArray/NSSet etc. Given a real object + * receiver, collection object receiver and related keypaths: + * + * @code + + NSString *employessFirstNamePath = @collectionKeypath(department.employees, Employee.new, firstName) + // => @"employees.firstName" + + NSString *employessFirstNamePath = @collectionKeypath(Department.new, employees, Employee.new, firstName) + // => @"employees.firstName" + + * @endcode + * + */ +#define collectionKeypath(...) \ + metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__)) + +#define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) + +#define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(OBJ, PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]) +
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTRuntimeExtensions.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTRuntimeExtensions.h new file mode 100644 index 0000000..ab4e11d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTRuntimeExtensions.h
@@ -0,0 +1,122 @@ +// +// EXTRuntimeExtensions.h +// extobjc +// +// Created by Justin Spahr-Summers on 2011-03-05. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import <objc/runtime.h> + +/** + * Describes the memory management policy of a property. + */ +typedef enum { + /** + * The value is assigned. + */ + rac_propertyMemoryManagementPolicyAssign = 0, + + /** + * The value is retained. + */ + rac_propertyMemoryManagementPolicyRetain, + + /** + * The value is copied. + */ + rac_propertyMemoryManagementPolicyCopy +} rac_propertyMemoryManagementPolicy; + +/** + * Describes the attributes and type information of a property. + */ +typedef struct { + /** + * Whether this property was declared with the \c readonly attribute. + */ + BOOL readonly; + + /** + * Whether this property was declared with the \c nonatomic attribute. + */ + BOOL nonatomic; + + /** + * Whether the property is a weak reference. + */ + BOOL weak; + + /** + * Whether the property is eligible for garbage collection. + */ + BOOL canBeCollected; + + /** + * Whether this property is defined with \c \@dynamic. + */ + BOOL dynamic; + + /** + * The memory management policy for this property. This will always be + * #rac_propertyMemoryManagementPolicyAssign if #readonly is \c YES. + */ + rac_propertyMemoryManagementPolicy memoryManagementPolicy; + + /** + * The selector for the getter of this property. This will reflect any + * custom \c getter= attribute provided in the property declaration, or the + * inferred getter name otherwise. + */ + SEL getter; + + /** + * The selector for the setter of this property. This will reflect any + * custom \c setter= attribute provided in the property declaration, or the + * inferred setter name otherwise. + * + * @note If #readonly is \c YES, this value will represent what the setter + * \e would be, if the property were writable. + */ + SEL setter; + + /** + * The backing instance variable for this property, or \c NULL if \c + * \c @synthesize was not used, and therefore no instance variable exists. This + * would also be the case if the property is implemented dynamically. + */ + const char *ivar; + + /** + * If this property is defined as being an instance of a specific class, + * this will be the class object representing it. + * + * This will be \c nil if the property was defined as type \c id, if the + * property is not of an object type, or if the class could not be found at + * runtime. + */ + Class objectClass; + + /** + * The type encoding for the value of this property. This is the type as it + * would be returned by the \c \@encode() directive. + */ + char type[]; +} rac_propertyAttributes; + +/** + * Finds the instance method named \a aSelector on \a aClass and returns it, or + * returns \c NULL if no such instance method exists. Unlike \c + * class_getInstanceMethod(), this does not search superclasses. + * + * @note To get class methods in this manner, use a metaclass for \a aClass. + */ +Method rac_getImmediateInstanceMethod (Class aClass, SEL aSelector); + +/** + * Returns a pointer to a structure containing information about \a property. + * You must \c free() the returned pointer. Returns \c NULL if there is an error + * obtaining information from \a property. + */ +rac_propertyAttributes *rac_copyPropertyAttributes (objc_property_t property);
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTRuntimeExtensions.m b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTRuntimeExtensions.m new file mode 100644 index 0000000..4d35a3e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTRuntimeExtensions.m
@@ -0,0 +1,232 @@ +// +// EXTRuntimeExtensions.m +// extobjc +// +// Created by Justin Spahr-Summers on 2011-03-05. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import "EXTRuntimeExtensions.h" +#import <ctype.h> +#import <libkern/OSAtomic.h> +#import <objc/message.h> +#import <pthread.h> +#import <stdio.h> +#import <stdlib.h> +#import <string.h> + +rac_propertyAttributes *rac_copyPropertyAttributes (objc_property_t property) { + const char * const attrString = property_getAttributes(property); + if (!attrString) { + fprintf(stderr, "ERROR: Could not get attribute string from property %s\n", property_getName(property)); + return NULL; + } + + if (attrString[0] != 'T') { + fprintf(stderr, "ERROR: Expected attribute string \"%s\" for property %s to start with 'T'\n", attrString, property_getName(property)); + return NULL; + } + + const char *typeString = attrString + 1; + const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL); + if (!next) { + fprintf(stderr, "ERROR: Could not read past type in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + size_t typeLength = (size_t)(next - typeString); + if (!typeLength) { + fprintf(stderr, "ERROR: Invalid type in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + // allocate enough space for the structure and the type string (plus a NUL) + rac_propertyAttributes *attributes = calloc(1, sizeof(rac_propertyAttributes) + typeLength + 1); + if (!attributes) { + fprintf(stderr, "ERROR: Could not allocate rac_propertyAttributes structure for attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + // copy the type string + strncpy(attributes->type, typeString, typeLength); + attributes->type[typeLength] = '\0'; + + // if this is an object type, and immediately followed by a quoted string... + if (typeString[0] == *(@encode(id)) && typeString[1] == '"') { + // we should be able to extract a class name + const char *className = typeString + 2; + next = strchr(className, '"'); + + if (!next) { + fprintf(stderr, "ERROR: Could not read class name in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + return NULL; + } + + if (className != next) { + size_t classNameLength = (size_t)(next - className); + char trimmedName[classNameLength + 1]; + + strncpy(trimmedName, className, classNameLength); + trimmedName[classNameLength] = '\0'; + + // attempt to look up the class in the runtime + attributes->objectClass = objc_getClass(trimmedName); + } + } + + if (*next != '\0') { + // skip past any junk before the first flag + next = strchr(next, ','); + } + + while (next && *next == ',') { + char flag = next[1]; + next += 2; + + switch (flag) { + case '\0': + break; + + case 'R': + attributes->readonly = YES; + break; + + case 'C': + attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyCopy; + break; + + case '&': + attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyRetain; + break; + + case 'N': + attributes->nonatomic = YES; + break; + + case 'G': + case 'S': + { + const char *nextFlag = strchr(next, ','); + SEL name = NULL; + + if (!nextFlag) { + // assume that the rest of the string is the selector + const char *selectorString = next; + next = ""; + + name = sel_registerName(selectorString); + } else { + size_t selectorLength = (size_t)(nextFlag - next); + if (!selectorLength) { + fprintf(stderr, "ERROR: Found zero length selector name in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + goto errorOut; + } + + char selectorString[selectorLength + 1]; + + strncpy(selectorString, next, selectorLength); + selectorString[selectorLength] = '\0'; + + name = sel_registerName(selectorString); + next = nextFlag; + } + + if (flag == 'G') + attributes->getter = name; + else + attributes->setter = name; + } + + break; + + case 'D': + attributes->dynamic = YES; + attributes->ivar = NULL; + break; + + case 'V': + // assume that the rest of the string (if present) is the ivar name + if (*next == '\0') { + // if there's nothing there, let's assume this is dynamic + attributes->ivar = NULL; + } else { + attributes->ivar = next; + next = ""; + } + + break; + + case 'W': + attributes->weak = YES; + break; + + case 'P': + attributes->canBeCollected = YES; + break; + + case 't': + fprintf(stderr, "ERROR: Old-style type encoding is unsupported in attribute string \"%s\" for property %s\n", attrString, property_getName(property)); + + // skip over this type encoding + while (*next != ',' && *next != '\0') + ++next; + + break; + + default: + fprintf(stderr, "ERROR: Unrecognized attribute string flag '%c' in attribute string \"%s\" for property %s\n", flag, attrString, property_getName(property)); + } + } + + if (next && *next != '\0') { + fprintf(stderr, "Warning: Unparsed data \"%s\" in attribute string \"%s\" for property %s\n", next, attrString, property_getName(property)); + } + + if (!attributes->getter) { + // use the property name as the getter by default + attributes->getter = sel_registerName(property_getName(property)); + } + + if (!attributes->setter) { + const char *propertyName = property_getName(property); + size_t propertyNameLength = strlen(propertyName); + + // we want to transform the name to setProperty: style + size_t setterLength = propertyNameLength + 4; + + char setterName[setterLength + 1]; + strncpy(setterName, "set", 3); + strncpy(setterName + 3, propertyName, propertyNameLength); + + // capitalize property name for the setter + setterName[3] = (char)toupper(setterName[3]); + + setterName[setterLength - 1] = ':'; + setterName[setterLength] = '\0'; + + attributes->setter = sel_registerName(setterName); + } + + return attributes; + +errorOut: + free(attributes); + return NULL; +} + +Method rac_getImmediateInstanceMethod (Class aClass, SEL aSelector) { + unsigned methodCount = 0; + Method *methods = class_copyMethodList(aClass, &methodCount); + Method foundMethod = NULL; + + for (unsigned methodIndex = 0;methodIndex < methodCount;++methodIndex) { + if (method_getName(methods[methodIndex]) == aSelector) { + foundMethod = methods[methodIndex]; + break; + } + } + + free(methods); + return foundMethod; +}
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTScope.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTScope.h new file mode 100644 index 0000000..2e51747 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/EXTScope.h
@@ -0,0 +1,101 @@ +// +// EXTScope.h +// extobjc +// +// Created by Justin Spahr-Summers on 2011-05-04. +// Copyright (C) 2012 Justin Spahr-Summers. +// Released under the MIT license. +// + +#import "metamacros.h" + +/** + * \@onExit defines some code to be executed when the current scope exits. The + * code must be enclosed in braces and terminated with a semicolon, and will be + * executed regardless of how the scope is exited, including from exceptions, + * \c goto, \c return, \c break, and \c continue. + * + * Provided code will go into a block to be executed later. Keep this in mind as + * it pertains to memory management, restrictions on assignment, etc. Because + * the code is used within a block, \c return is a legal (though perhaps + * confusing) way to exit the cleanup block early. + * + * Multiple \@onExit statements in the same scope are executed in reverse + * lexical order. This helps when pairing resource acquisition with \@onExit + * statements, as it guarantees teardown in the opposite order of acquisition. + * + * @note This statement cannot be used within scopes defined without braces + * (like a one line \c if). In practice, this is not an issue, since \@onExit is + * a useless construct in such a case anyways. + */ +#define onExit \ + autoreleasepool {} \ + __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^ + +/** + * Creates \c __weak shadow variables for each of the variables provided as + * arguments, which can later be made strong again with #strongify. + * + * This is typically used to weakly reference variables in a block, but then + * ensure that the variables stay alive during the actual execution of the block + * (if they were live upon entry). + * + * See #strongify for an example of usage. + */ +#define weakify(...) \ + autoreleasepool {} \ + metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__) + +/** + * Like #weakify, but uses \c __unsafe_unretained instead, for targets or + * classes that do not support weak references. + */ +#define unsafeify(...) \ + autoreleasepool {} \ + metamacro_foreach_cxt(rac_weakify_,, __unsafe_unretained, __VA_ARGS__) + +/** + * Strongly references each of the variables provided as arguments, which must + * have previously been passed to #weakify. + * + * The strong references created will shadow the original variable names, such + * that the original names can be used without issue (and a significantly + * reduced risk of retain cycles) in the current scope. + * + * @code + + id foo = [[NSObject alloc] init]; + id bar = [[NSObject alloc] init]; + + @weakify(foo, bar); + + // this block will not keep 'foo' or 'bar' alive + BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){ + // but now, upon entry, 'foo' and 'bar' will stay alive until the block has + // finished executing + @strongify(foo, bar); + + return [foo isEqual:obj] || [bar isEqual:obj]; + }; + + * @endcode + */ +#define strongify(...) \ + try {} @finally {} \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wshadow\"") \ + metamacro_foreach(rac_strongify_,, __VA_ARGS__) \ + _Pragma("clang diagnostic pop") + +/*** implementation details follow ***/ +typedef void (^rac_cleanupBlock_t)(); + +static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) { + (*block)(); +} + +#define rac_weakify_(INDEX, CONTEXT, VAR) \ + CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR); + +#define rac_strongify_(INDEX, VAR) \ + __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
diff --git a/ReactiveCocoaFramework/ReactiveCocoa/extobjc/metamacros.h b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/metamacros.h new file mode 100644 index 0000000..77a77b5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoa/extobjc/metamacros.h
@@ -0,0 +1,666 @@ +/** + * Macros for metaprogramming + * ExtendedC + * + * Copyright (C) 2012 Justin Spahr-Summers + * Released under the MIT license + */ + +#ifndef EXTC_METAMACROS_H +#define EXTC_METAMACROS_H + +/** + * Executes one or more expressions (which may have a void type, such as a call + * to a function that returns no value) and always returns true. + */ +#define metamacro_exprify(...) \ + ((__VA_ARGS__), true) + +/** + * Returns a string representation of VALUE after full macro expansion. + */ +#define metamacro_stringify(VALUE) \ + metamacro_stringify_(VALUE) + +/** + * Returns A and B concatenated after full macro expansion. + */ +#define metamacro_concat(A, B) \ + metamacro_concat_(A, B) + +/** + * Returns the Nth variadic argument (starting from zero). At least + * N + 1 variadic arguments must be given. N must be between zero and twenty, + * inclusive. + */ +#define metamacro_at(N, ...) \ + metamacro_concat(metamacro_at, N)(__VA_ARGS__) + +/** + * Returns the number of arguments (up to twenty) provided to the macro. At + * least one argument must be provided. + * + * Inspired by P99: http://p99.gforge.inria.fr + */ +#define metamacro_argcount(...) \ + metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) + +/** + * Identical to #metamacro_foreach_cxt, except that no CONTEXT argument is + * given. Only the index and current argument will thus be passed to MACRO. + */ +#define metamacro_foreach(MACRO, SEP, ...) \ + metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__) + +/** + * For each consecutive variadic argument (up to twenty), MACRO is passed the + * zero-based index of the current argument, CONTEXT, and then the argument + * itself. The results of adjoining invocations of MACRO are then separated by + * SEP. + * + * Inspired by P99: http://p99.gforge.inria.fr + */ +#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \ + metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) + +/** + * Identical to #metamacro_foreach_cxt. This can be used when the former would + * fail due to recursive macro expansion. + */ +#define metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...) \ + metamacro_concat(metamacro_foreach_cxt_recursive, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__) + +/** + * In consecutive order, appends each variadic argument (up to twenty) onto + * BASE. The resulting concatenations are then separated by SEP. + * + * This is primarily useful to manipulate a list of macro invocations into instead + * invoking a different, possibly related macro. + */ +#define metamacro_foreach_concat(BASE, SEP, ...) \ + metamacro_foreach_cxt(metamacro_foreach_concat_iter, SEP, BASE, __VA_ARGS__) + +/** + * Iterates COUNT times, each time invoking MACRO with the current index + * (starting at zero) and CONTEXT. The results of adjoining invocations of MACRO + * are then separated by SEP. + * + * COUNT must be an integer between zero and twenty, inclusive. + */ +#define metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT) \ + metamacro_concat(metamacro_for_cxt, COUNT)(MACRO, SEP, CONTEXT) + +/** + * Returns the first argument given. At least one argument must be provided. + * + * This is useful when implementing a variadic macro, where you may have only + * one variadic argument, but no way to retrieve it (for example, because \c ... + * always needs to match at least one argument). + * + * @code + +#define varmacro(...) \ + metamacro_head(__VA_ARGS__) + + * @endcode + */ +#define metamacro_head(...) \ + metamacro_head_(__VA_ARGS__, 0) + +/** + * Returns every argument except the first. At least two arguments must be + * provided. + */ +#define metamacro_tail(...) \ + metamacro_tail_(__VA_ARGS__) + +/** + * Returns the first N (up to twenty) variadic arguments as a new argument list. + * At least N variadic arguments must be provided. + */ +#define metamacro_take(N, ...) \ + metamacro_concat(metamacro_take, N)(__VA_ARGS__) + +/** + * Removes the first N (up to twenty) variadic arguments from the given argument + * list. At least N variadic arguments must be provided. + */ +#define metamacro_drop(N, ...) \ + metamacro_concat(metamacro_drop, N)(__VA_ARGS__) + +/** + * Decrements VAL, which must be a number between zero and twenty, inclusive. + * + * This is primarily useful when dealing with indexes and counts in + * metaprogramming. + */ +#define metamacro_dec(VAL) \ + metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19) + +/** + * Increments VAL, which must be a number between zero and twenty, inclusive. + * + * This is primarily useful when dealing with indexes and counts in + * metaprogramming. + */ +#define metamacro_inc(VAL) \ + metamacro_at(VAL, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21) + +/** + * If A is equal to B, the next argument list is expanded; otherwise, the + * argument list after that is expanded. A and B must be numbers between zero + * and twenty, inclusive. Additionally, B must be greater than or equal to A. + * + * @code + +// expands to true +metamacro_if_eq(0, 0)(true)(false) + +// expands to false +metamacro_if_eq(0, 1)(true)(false) + + * @endcode + * + * This is primarily useful when dealing with indexes and counts in + * metaprogramming. + */ +#define metamacro_if_eq(A, B) \ + metamacro_concat(metamacro_if_eq, A)(B) + +/** + * Identical to #metamacro_if_eq. This can be used when the former would fail + * due to recursive macro expansion. + */ +#define metamacro_if_eq_recursive(A, B) \ + metamacro_concat(metamacro_if_eq_recursive, A)(B) + +/** + * Returns 1 if N is an even number, or 0 otherwise. N must be between zero and + * twenty, inclusive. + * + * For the purposes of this test, zero is considered even. + */ +#define metamacro_is_even(N) \ + metamacro_at(N, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) + +/** + * Returns the logical NOT of B, which must be the number zero or one. + */ +#define metamacro_not(B) \ + metamacro_at(B, 1, 0) + +// IMPLEMENTATION DETAILS FOLLOW! +// Do not write code that depends on anything below this line. +#define metamacro_stringify_(VALUE) # VALUE +#define metamacro_concat_(A, B) A ## B +#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG) +#define metamacro_head_(FIRST, ...) FIRST +#define metamacro_tail_(FIRST, ...) __VA_ARGS__ +#define metamacro_consume_(...) +#define metamacro_expand_(...) __VA_ARGS__ + +// implemented from scratch so that metamacro_concat() doesn't end up nesting +#define metamacro_foreach_concat_iter(INDEX, BASE, ARG) metamacro_foreach_concat_iter_(BASE, ARG) +#define metamacro_foreach_concat_iter_(BASE, ARG) BASE ## ARG + +// metamacro_at expansions +#define metamacro_at0(...) metamacro_head(__VA_ARGS__) +#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__) +#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__) + +// metamacro_foreach_cxt expansions +#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT) +#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) + +#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ + metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \ + SEP \ + MACRO(1, CONTEXT, _1) + +#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \ + SEP \ + MACRO(2, CONTEXT, _2) + +#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + SEP \ + MACRO(3, CONTEXT, _3) + +#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + SEP \ + MACRO(4, CONTEXT, _4) + +#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + SEP \ + MACRO(5, CONTEXT, _5) + +#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + SEP \ + MACRO(6, CONTEXT, _6) + +#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + SEP \ + MACRO(7, CONTEXT, _7) + +#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + SEP \ + MACRO(8, CONTEXT, _8) + +#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + SEP \ + MACRO(9, CONTEXT, _9) + +#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + SEP \ + MACRO(10, CONTEXT, _10) + +#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + SEP \ + MACRO(11, CONTEXT, _11) + +#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + SEP \ + MACRO(12, CONTEXT, _12) + +#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + SEP \ + MACRO(13, CONTEXT, _13) + +#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + SEP \ + MACRO(14, CONTEXT, _14) + +#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + SEP \ + MACRO(15, CONTEXT, _15) + +#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + SEP \ + MACRO(16, CONTEXT, _16) + +#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + SEP \ + MACRO(17, CONTEXT, _17) + +#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + SEP \ + MACRO(18, CONTEXT, _18) + +#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ + metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + SEP \ + MACRO(19, CONTEXT, _19) + +// metamacro_foreach_cxt_recursive expansions +#define metamacro_foreach_cxt_recursive0(MACRO, SEP, CONTEXT) +#define metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0) + +#define metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ + metamacro_foreach_cxt_recursive1(MACRO, SEP, CONTEXT, _0) \ + SEP \ + MACRO(1, CONTEXT, _1) + +#define metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + metamacro_foreach_cxt_recursive2(MACRO, SEP, CONTEXT, _0, _1) \ + SEP \ + MACRO(2, CONTEXT, _2) + +#define metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + metamacro_foreach_cxt_recursive3(MACRO, SEP, CONTEXT, _0, _1, _2) \ + SEP \ + MACRO(3, CONTEXT, _3) + +#define metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + metamacro_foreach_cxt_recursive4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \ + SEP \ + MACRO(4, CONTEXT, _4) + +#define metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + metamacro_foreach_cxt_recursive5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \ + SEP \ + MACRO(5, CONTEXT, _5) + +#define metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + metamacro_foreach_cxt_recursive6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \ + SEP \ + MACRO(6, CONTEXT, _6) + +#define metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + metamacro_foreach_cxt_recursive7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \ + SEP \ + MACRO(7, CONTEXT, _7) + +#define metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + metamacro_foreach_cxt_recursive8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \ + SEP \ + MACRO(8, CONTEXT, _8) + +#define metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + metamacro_foreach_cxt_recursive9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \ + SEP \ + MACRO(9, CONTEXT, _9) + +#define metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + metamacro_foreach_cxt_recursive10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + SEP \ + MACRO(10, CONTEXT, _10) + +#define metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + metamacro_foreach_cxt_recursive11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + SEP \ + MACRO(11, CONTEXT, _11) + +#define metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + metamacro_foreach_cxt_recursive12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + SEP \ + MACRO(12, CONTEXT, _12) + +#define metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + metamacro_foreach_cxt_recursive13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + SEP \ + MACRO(13, CONTEXT, _13) + +#define metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + metamacro_foreach_cxt_recursive14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + SEP \ + MACRO(14, CONTEXT, _14) + +#define metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + metamacro_foreach_cxt_recursive15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + SEP \ + MACRO(15, CONTEXT, _15) + +#define metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + metamacro_foreach_cxt_recursive16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + SEP \ + MACRO(16, CONTEXT, _16) + +#define metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + metamacro_foreach_cxt_recursive17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + SEP \ + MACRO(17, CONTEXT, _17) + +#define metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + metamacro_foreach_cxt_recursive18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \ + SEP \ + MACRO(18, CONTEXT, _18) + +#define metamacro_foreach_cxt_recursive20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \ + metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \ + SEP \ + MACRO(19, CONTEXT, _19) + +// metamacro_for_cxt expansions +#define metamacro_for_cxt0(MACRO, SEP, CONTEXT) +#define metamacro_for_cxt1(MACRO, SEP, CONTEXT) MACRO(0, CONTEXT) + +#define metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt1(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(1, CONTEXT) + +#define metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt2(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(2, CONTEXT) + +#define metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt3(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(3, CONTEXT) + +#define metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt4(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(4, CONTEXT) + +#define metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt5(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(5, CONTEXT) + +#define metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt6(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(6, CONTEXT) + +#define metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt7(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(7, CONTEXT) + +#define metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt8(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(8, CONTEXT) + +#define metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt9(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(9, CONTEXT) + +#define metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt10(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(10, CONTEXT) + +#define metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt11(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(11, CONTEXT) + +#define metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt12(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(12, CONTEXT) + +#define metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt13(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(13, CONTEXT) + +#define metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt14(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(14, CONTEXT) + +#define metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt15(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(15, CONTEXT) + +#define metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt16(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(16, CONTEXT) + +#define metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt17(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(17, CONTEXT) + +#define metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt18(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(18, CONTEXT) + +#define metamacro_for_cxt20(MACRO, SEP, CONTEXT) \ + metamacro_for_cxt19(MACRO, SEP, CONTEXT) \ + SEP \ + MACRO(19, CONTEXT) + +// metamacro_if_eq expansions +#define metamacro_if_eq0(VALUE) \ + metamacro_concat(metamacro_if_eq0_, VALUE) + +#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_ +#define metamacro_if_eq0_1(...) metamacro_expand_ +#define metamacro_if_eq0_2(...) metamacro_expand_ +#define metamacro_if_eq0_3(...) metamacro_expand_ +#define metamacro_if_eq0_4(...) metamacro_expand_ +#define metamacro_if_eq0_5(...) metamacro_expand_ +#define metamacro_if_eq0_6(...) metamacro_expand_ +#define metamacro_if_eq0_7(...) metamacro_expand_ +#define metamacro_if_eq0_8(...) metamacro_expand_ +#define metamacro_if_eq0_9(...) metamacro_expand_ +#define metamacro_if_eq0_10(...) metamacro_expand_ +#define metamacro_if_eq0_11(...) metamacro_expand_ +#define metamacro_if_eq0_12(...) metamacro_expand_ +#define metamacro_if_eq0_13(...) metamacro_expand_ +#define metamacro_if_eq0_14(...) metamacro_expand_ +#define metamacro_if_eq0_15(...) metamacro_expand_ +#define metamacro_if_eq0_16(...) metamacro_expand_ +#define metamacro_if_eq0_17(...) metamacro_expand_ +#define metamacro_if_eq0_18(...) metamacro_expand_ +#define metamacro_if_eq0_19(...) metamacro_expand_ +#define metamacro_if_eq0_20(...) metamacro_expand_ + +#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE)) +#define metamacro_if_eq2(VALUE) metamacro_if_eq1(metamacro_dec(VALUE)) +#define metamacro_if_eq3(VALUE) metamacro_if_eq2(metamacro_dec(VALUE)) +#define metamacro_if_eq4(VALUE) metamacro_if_eq3(metamacro_dec(VALUE)) +#define metamacro_if_eq5(VALUE) metamacro_if_eq4(metamacro_dec(VALUE)) +#define metamacro_if_eq6(VALUE) metamacro_if_eq5(metamacro_dec(VALUE)) +#define metamacro_if_eq7(VALUE) metamacro_if_eq6(metamacro_dec(VALUE)) +#define metamacro_if_eq8(VALUE) metamacro_if_eq7(metamacro_dec(VALUE)) +#define metamacro_if_eq9(VALUE) metamacro_if_eq8(metamacro_dec(VALUE)) +#define metamacro_if_eq10(VALUE) metamacro_if_eq9(metamacro_dec(VALUE)) +#define metamacro_if_eq11(VALUE) metamacro_if_eq10(metamacro_dec(VALUE)) +#define metamacro_if_eq12(VALUE) metamacro_if_eq11(metamacro_dec(VALUE)) +#define metamacro_if_eq13(VALUE) metamacro_if_eq12(metamacro_dec(VALUE)) +#define metamacro_if_eq14(VALUE) metamacro_if_eq13(metamacro_dec(VALUE)) +#define metamacro_if_eq15(VALUE) metamacro_if_eq14(metamacro_dec(VALUE)) +#define metamacro_if_eq16(VALUE) metamacro_if_eq15(metamacro_dec(VALUE)) +#define metamacro_if_eq17(VALUE) metamacro_if_eq16(metamacro_dec(VALUE)) +#define metamacro_if_eq18(VALUE) metamacro_if_eq17(metamacro_dec(VALUE)) +#define metamacro_if_eq19(VALUE) metamacro_if_eq18(metamacro_dec(VALUE)) +#define metamacro_if_eq20(VALUE) metamacro_if_eq19(metamacro_dec(VALUE)) + +// metamacro_if_eq_recursive expansions +#define metamacro_if_eq_recursive0(VALUE) \ + metamacro_concat(metamacro_if_eq_recursive0_, VALUE) + +#define metamacro_if_eq_recursive0_0(...) __VA_ARGS__ metamacro_consume_ +#define metamacro_if_eq_recursive0_1(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_2(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_3(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_4(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_5(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_6(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_7(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_8(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_9(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_10(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_11(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_12(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_13(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_14(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_15(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_16(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_17(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_18(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_19(...) metamacro_expand_ +#define metamacro_if_eq_recursive0_20(...) metamacro_expand_ + +#define metamacro_if_eq_recursive1(VALUE) metamacro_if_eq_recursive0(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive2(VALUE) metamacro_if_eq_recursive1(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive3(VALUE) metamacro_if_eq_recursive2(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive4(VALUE) metamacro_if_eq_recursive3(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive5(VALUE) metamacro_if_eq_recursive4(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive6(VALUE) metamacro_if_eq_recursive5(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive7(VALUE) metamacro_if_eq_recursive6(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive8(VALUE) metamacro_if_eq_recursive7(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive9(VALUE) metamacro_if_eq_recursive8(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive10(VALUE) metamacro_if_eq_recursive9(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive11(VALUE) metamacro_if_eq_recursive10(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive12(VALUE) metamacro_if_eq_recursive11(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive13(VALUE) metamacro_if_eq_recursive12(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive14(VALUE) metamacro_if_eq_recursive13(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive15(VALUE) metamacro_if_eq_recursive14(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive16(VALUE) metamacro_if_eq_recursive15(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive17(VALUE) metamacro_if_eq_recursive16(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive18(VALUE) metamacro_if_eq_recursive17(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive19(VALUE) metamacro_if_eq_recursive18(metamacro_dec(VALUE)) +#define metamacro_if_eq_recursive20(VALUE) metamacro_if_eq_recursive19(metamacro_dec(VALUE)) + +// metamacro_take expansions +#define metamacro_take0(...) +#define metamacro_take1(...) metamacro_head(__VA_ARGS__) +#define metamacro_take2(...) metamacro_head(__VA_ARGS__), metamacro_take1(metamacro_tail(__VA_ARGS__)) +#define metamacro_take3(...) metamacro_head(__VA_ARGS__), metamacro_take2(metamacro_tail(__VA_ARGS__)) +#define metamacro_take4(...) metamacro_head(__VA_ARGS__), metamacro_take3(metamacro_tail(__VA_ARGS__)) +#define metamacro_take5(...) metamacro_head(__VA_ARGS__), metamacro_take4(metamacro_tail(__VA_ARGS__)) +#define metamacro_take6(...) metamacro_head(__VA_ARGS__), metamacro_take5(metamacro_tail(__VA_ARGS__)) +#define metamacro_take7(...) metamacro_head(__VA_ARGS__), metamacro_take6(metamacro_tail(__VA_ARGS__)) +#define metamacro_take8(...) metamacro_head(__VA_ARGS__), metamacro_take7(metamacro_tail(__VA_ARGS__)) +#define metamacro_take9(...) metamacro_head(__VA_ARGS__), metamacro_take8(metamacro_tail(__VA_ARGS__)) +#define metamacro_take10(...) metamacro_head(__VA_ARGS__), metamacro_take9(metamacro_tail(__VA_ARGS__)) +#define metamacro_take11(...) metamacro_head(__VA_ARGS__), metamacro_take10(metamacro_tail(__VA_ARGS__)) +#define metamacro_take12(...) metamacro_head(__VA_ARGS__), metamacro_take11(metamacro_tail(__VA_ARGS__)) +#define metamacro_take13(...) metamacro_head(__VA_ARGS__), metamacro_take12(metamacro_tail(__VA_ARGS__)) +#define metamacro_take14(...) metamacro_head(__VA_ARGS__), metamacro_take13(metamacro_tail(__VA_ARGS__)) +#define metamacro_take15(...) metamacro_head(__VA_ARGS__), metamacro_take14(metamacro_tail(__VA_ARGS__)) +#define metamacro_take16(...) metamacro_head(__VA_ARGS__), metamacro_take15(metamacro_tail(__VA_ARGS__)) +#define metamacro_take17(...) metamacro_head(__VA_ARGS__), metamacro_take16(metamacro_tail(__VA_ARGS__)) +#define metamacro_take18(...) metamacro_head(__VA_ARGS__), metamacro_take17(metamacro_tail(__VA_ARGS__)) +#define metamacro_take19(...) metamacro_head(__VA_ARGS__), metamacro_take18(metamacro_tail(__VA_ARGS__)) +#define metamacro_take20(...) metamacro_head(__VA_ARGS__), metamacro_take19(metamacro_tail(__VA_ARGS__)) + +// metamacro_drop expansions +#define metamacro_drop0(...) __VA_ARGS__ +#define metamacro_drop1(...) metamacro_tail(__VA_ARGS__) +#define metamacro_drop2(...) metamacro_drop1(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop3(...) metamacro_drop2(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop4(...) metamacro_drop3(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop5(...) metamacro_drop4(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop6(...) metamacro_drop5(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop7(...) metamacro_drop6(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop8(...) metamacro_drop7(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop9(...) metamacro_drop8(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop10(...) metamacro_drop9(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop11(...) metamacro_drop10(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop12(...) metamacro_drop11(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop13(...) metamacro_drop12(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop14(...) metamacro_drop13(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop15(...) metamacro_drop14(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop16(...) metamacro_drop15(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop17(...) metamacro_drop16(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop18(...) metamacro_drop17(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop19(...) metamacro_drop18(metamacro_tail(__VA_ARGS__)) +#define metamacro_drop20(...) metamacro_drop19(metamacro_tail(__VA_ARGS__)) + +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSControlRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSControlRACSupportSpec.m new file mode 100644 index 0000000..1825277 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSControlRACSupportSpec.m
@@ -0,0 +1,106 @@ +// +// NSControlRACSupportSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 9/4/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACControlCommandExamples.h" + +#import "NSControl+RACCommandSupport.h" +#import "NSControl+RACTextSignalSupport.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSubject.h" + +SpecBegin(NSControlRACSupport) + +describe(@"NSButton", ^{ + __block NSButton *button; + + beforeEach(^{ + button = [[NSButton alloc] initWithFrame:NSZeroRect]; + expect(button).notTo.beNil(); + }); + + itShouldBehaveLike(RACControlCommandExamples, ^{ + return @{ + RACControlCommandExampleControl: button, + RACControlCommandExampleActivateBlock: ^(NSButton *button) { + [button performClick:nil]; + } + }; + }); +}); + +describe(@"NSTextField", ^{ + __block NSTextField *field; + __block NSWindow *window; + + beforeEach(^{ + field = [[NSTextField alloc] initWithFrame:NSZeroRect]; + expect(field).notTo.beNil(); + + [field.cell setSendsActionOnEndEditing:YES]; + + window = [[NSWindow alloc] initWithContentRect:NSZeroRect styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]; + expect(window).notTo.beNil(); + + [window.contentView addSubview:field]; + + expect([window makeFirstResponder:field]).to.beTruthy(); + expect(window.firstResponder).notTo.equal(window); + }); + + itShouldBehaveLike(RACControlCommandExamples, ^{ + return @{ + RACControlCommandExampleControl: field, + RACControlCommandExampleActivateBlock: ^(NSTextField *field) { + expect([window makeFirstResponder:nil]).to.beTruthy(); + expect(window.firstResponder).to.equal(window); + } + }; + }); + + describe(@"-rac_textSignal", ^{ + it(@"should send changes", ^{ + NSMutableArray *strings = [NSMutableArray array]; + [field.rac_textSignal subscribeNext:^(NSString *str) { + [strings addObject:str]; + }]; + + expect(strings).to.equal(@[ @"" ]); + + NSText *fieldEditor = (id)window.firstResponder; + expect(fieldEditor).to.beKindOf(NSText.class); + + [fieldEditor insertText:@"f"]; + [fieldEditor insertText:@"o"]; + [fieldEditor insertText:@"b"]; + + NSArray *expected = @[ @"", @"f", @"fo", @"fob" ]; + expect(strings).to.equal(expected); + }); + + it(@"shouldn't give the text field eternal life", ^{ + __block BOOL dealloced = NO; + @autoreleasepool { + NSTextField *field __attribute__((objc_precise_lifetime)) = [[NSTextField alloc] initWithFrame:CGRectZero]; + [field.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + dealloced = YES; + }]]; + [field.rac_textSignal subscribeNext:^(id x) { + + }]; + } + + expect(dealloced).will.beTruthy(); + }); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSControllerRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSControllerRACSupportSpec.m new file mode 100644 index 0000000..d309b25 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSControllerRACSupportSpec.m
@@ -0,0 +1,44 @@ +// +// NSControllerRACSupportSpec.m +// ReactiveCocoa +// +// Created by Uri Baghin on 26/10/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <AppKit/AppKit.h> +#import "RACKVOChannel.h" + +@interface RACTestController : NSController + +@property (nonatomic, strong) id object; + +@end + +@implementation RACTestController + +@end + +SpecBegin(NSControllerRACSupport) + +it(@"RACKVOChannel should support NSController", ^{ + RACTestController *a = [[RACTestController alloc] init]; + RACTestController *b = [[RACTestController alloc] init]; + RACChannelTo(a, object) = RACChannelTo(b, object); + expect(a.object).to.beNil(); + expect(b.object).to.beNil(); + + a.object = a; + expect(a.object).to.equal(a); + expect(b.object).to.equal(a); + + b.object = b; + expect(a.object).to.equal(b); + expect(b.object).to.equal(b); + + a.object = nil; + expect(a.object).to.beNil(); + expect(b.object).to.beNil(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSEnumeratorRACSequenceAdditionsSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSEnumeratorRACSequenceAdditionsSpec.m new file mode 100644 index 0000000..50328bd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSEnumeratorRACSequenceAdditionsSpec.m
@@ -0,0 +1,25 @@ +// +// NSEnumeratorRACSequenceAdditionsSpec.m +// ReactiveCocoa +// +// Created by Uri Baghin on 07/01/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSequenceExamples.h" + +#import "NSEnumerator+RACSequenceAdditions.h" + +SpecBegin(NSEnumeratorRACSequenceAdditions) + +describe(@"-rac_sequence", ^{ + NSArray *values = @[ @0, @1, @2, @3, @4 ]; + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: values.objectEnumerator.rac_sequence, + RACSequenceExampleExpectedValues: values + }; + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSNotificationCenterRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSNotificationCenterRACSupportSpec.m new file mode 100644 index 0000000..26143df --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSNotificationCenterRACSupportSpec.m
@@ -0,0 +1,84 @@ +// +// NSNotificationCenterRACSupportSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-12-07. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSNotificationCenter+RACSupport.h" +#import "RACSignal.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "NSObject+RACDeallocating.h" + +static NSString * const TestNotification = @"TestNotification"; + +SpecBegin(NSNotificationCenterRACSupport) + +__block NSNotificationCenter *notificationCenter; + +beforeEach(^{ + // The compiler gets confused and thinks you might be messaging + // NSDistributedNotificationCenter otherwise. Wtf? + notificationCenter = NSNotificationCenter.defaultCenter; +}); + +it(@"should send the notification when posted by any object", ^{ + RACSignal *signal = [notificationCenter rac_addObserverForName:TestNotification object:nil]; + + __block NSUInteger count = 0; + [signal subscribeNext:^(NSNotification *notification) { + ++count; + + expect(notification).to.beKindOf(NSNotification.class); + expect(notification.name).to.equal(TestNotification); + }]; + + expect(count).to.equal(0); + + [notificationCenter postNotificationName:TestNotification object:nil]; + expect(count).to.equal(1); + + [notificationCenter postNotificationName:TestNotification object:self]; + expect(count).to.equal(2); +}); + +it(@"should send the notification when posted by a specific object", ^{ + RACSignal *signal = [notificationCenter rac_addObserverForName:TestNotification object:self]; + + __block NSUInteger count = 0; + [signal subscribeNext:^(NSNotification *notification) { + ++count; + + expect(notification).to.beKindOf(NSNotification.class); + expect(notification.name).to.equal(TestNotification); + expect(notification.object).to.equal(self); + }]; + + expect(count).to.equal(0); + + [notificationCenter postNotificationName:TestNotification object:nil]; + expect(count).to.equal(0); + + [notificationCenter postNotificationName:TestNotification object:self]; + expect(count).to.equal(1); +}); + +it(@"shouldn't strongly capture the notification object", ^{ + RACSignal *signal __attribute__((objc_precise_lifetime, unused)); + + __block BOOL dealloced = NO; + @autoreleasepool { + NSObject *notificationObject = [[NSObject alloc] init]; + [notificationObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + dealloced = YES; + }]]; + + signal = [notificationCenter rac_addObserverForName:TestNotification object:notificationObject]; + } + + expect(dealloced).to.beTruthy(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACAppKitBindingsSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACAppKitBindingsSpec.m new file mode 100644 index 0000000..fcb4837 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACAppKitBindingsSpec.m
@@ -0,0 +1,36 @@ +// +// NSObjectRACAppKitBindingsSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACChannelExamples.h" + +#import "EXTKeyPathCoding.h" +#import "NSObject+RACAppKitBindings.h" + +SpecBegin(NSObjectRACAppKitBindings) + +itShouldBehaveLike(RACViewChannelExamples, ^{ + return @{ + RACViewChannelExampleCreateViewBlock: ^{ + return [[NSSlider alloc] initWithFrame:NSZeroRect]; + }, + RACViewChannelExampleCreateTerminalBlock: ^(NSSlider *view) { + return [view rac_channelToBinding:NSValueBinding]; + }, + RACViewChannelExampleKeyPath: @keypath(NSSlider.new, objectValue), + RACViewChannelExampleSetViewValueBlock: ^(NSSlider *view, NSNumber *value) { + view.objectValue = value; + + // Bindings don't actually trigger from programmatic modification. Do it + // manually. + NSDictionary *bindingInfo = [view infoForBinding:NSValueBinding]; + [bindingInfo[NSObservedObjectKey] setValue:value forKeyPath:bindingInfo[NSObservedKeyPathKey]]; + } + }; +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACDeallocatingSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACDeallocatingSpec.m new file mode 100644 index 0000000..b5e80b2 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACDeallocatingSpec.m
@@ -0,0 +1,195 @@ +// +// NSObject+RACDeallocating.m +// ReactiveCocoa +// +// Created by Kazuo Koga on 2013/03/15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" + +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import <objc/runtime.h> + +@interface RACDeallocSwizzlingTestClass : NSObject +@end + +@implementation RACDeallocSwizzlingTestClass + +- (void)dealloc { + // Provide an empty implementation just so we can swizzle it. +} + +@end + +@interface RACDeallocSwizzlingTestSubclass : RACDeallocSwizzlingTestClass +@end + +@implementation RACDeallocSwizzlingTestSubclass +@end + +SpecBegin(NSObjectRACDeallocatingSpec) + +describe(@"-dealloc swizzling", ^{ + SEL selector = NSSelectorFromString(@"dealloc"); + + it(@"should not invoke superclass -dealloc method twice", ^{ + __block NSUInteger superclassDeallocatedCount = 0; + __block BOOL subclassDeallocated = NO; + + @autoreleasepool { + RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init]; + + Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector); + void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod); + + id newDealloc = ^(__unsafe_unretained id self) { + superclassDeallocatedCount++; + oldDealloc(self, selector); + }; + + class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod)); + + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + subclassDeallocated = YES; + }]]; + + expect(subclassDeallocated).to.beFalsy(); + expect(superclassDeallocatedCount).to.equal(0); + } + + expect(subclassDeallocated).to.beTruthy(); + expect(superclassDeallocatedCount).to.equal(1); + }); + + it(@"should invoke superclass -dealloc method swizzled in after the subclass", ^{ + __block BOOL superclassDeallocated = NO; + __block BOOL subclassDeallocated = NO; + + @autoreleasepool { + RACDeallocSwizzlingTestSubclass *object __attribute__((objc_precise_lifetime)) = [[RACDeallocSwizzlingTestSubclass alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + subclassDeallocated = YES; + }]]; + + Method oldDeallocMethod = class_getInstanceMethod(RACDeallocSwizzlingTestClass.class, selector); + void (*oldDealloc)(id, SEL) = (__typeof__(oldDealloc))method_getImplementation(oldDeallocMethod); + + id newDealloc = ^(__unsafe_unretained id self) { + superclassDeallocated = YES; + oldDealloc(self, selector); + }; + + class_replaceMethod(RACDeallocSwizzlingTestClass.class, selector, imp_implementationWithBlock(newDealloc), method_getTypeEncoding(oldDeallocMethod)); + + expect(subclassDeallocated).to.beFalsy(); + expect(superclassDeallocated).to.beFalsy(); + } + + expect(subclassDeallocated).to.beTruthy(); + expect(superclassDeallocated).to.beTruthy(); + }); +}); + +describe(@"-rac_deallocDisposable", ^{ + it(@"should dispose of the disposable when it is dealloc'd", ^{ + __block BOOL wasDisposed = NO; + @autoreleasepool { + NSObject *object __attribute__((objc_precise_lifetime)) = [[NSObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + wasDisposed = YES; + }]]; + + expect(wasDisposed).to.beFalsy(); + } + + expect(wasDisposed).to.beTruthy(); + }); + + it(@"should be able to use the object during disposal", ^{ + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + @autoreleasepool { + object.objectValue = [@"foo" mutableCopy]; + } + + __unsafe_unretained RACTestObject *weakObject = object; + + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + expect(weakObject.objectValue).to.equal(@"foo"); + }]]; + } + }); +}); + +describe(@"-rac_willDeallocSignal", ^{ + it(@"should complete on dealloc", ^{ + __block BOOL completed = NO; + @autoreleasepool { + [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeCompleted:^{ + completed = YES; + }]; + } + + expect(completed).to.beTruthy(); + }); + + it(@"should not send anything", ^{ + __block BOOL valueReceived = NO; + __block BOOL completed = NO; + @autoreleasepool { + [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeNext:^(id x) { + valueReceived = YES; + } completed:^{ + completed = YES; + }]; + } + + expect(valueReceived).to.beFalsy(); + expect(completed).to.beTruthy(); + }); + + it(@"should complete upon subscription if already deallocated", ^{ + __block BOOL deallocated = NO; + + RACSignal *signal; + + @autoreleasepool { + RACTestObject *object = [[RACTestObject alloc] init]; + + signal = [object rac_willDeallocSignal]; + [signal subscribeCompleted:^{ + deallocated = YES; + }]; + } + + expect(deallocated).to.beTruthy(); + expect([signal waitUntilCompleted:NULL]).to.beTruthy(); + }); + + it(@"should complete before the object is invalid", ^{ + __block NSString *objectValue; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + @autoreleasepool { + object.objectValue = [@"foo" mutableCopy]; + } + + __unsafe_unretained RACTestObject *weakObject = object; + + [[object rac_willDeallocSignal] subscribeCompleted:^{ + objectValue = [weakObject.objectValue copy]; + }]; + } + + expect(objectValue).to.equal(@"foo"); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACLiftingSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACLiftingSpec.m new file mode 100644 index 0000000..7a2acfc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACLiftingSpec.m
@@ -0,0 +1,407 @@ +// +// NSObjectRACLifting.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 10/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" + +#import "NSObject+RACLifting.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSubject.h" +#import "RACTuple.h" +#import "RACUnit.h" + +SpecBegin(NSObjectRACLifting) + +describe(@"-rac_liftSelector:withSignals:", ^{ + __block RACTestObject *object; + + beforeEach(^{ + object = [[RACTestObject alloc] init]; + }); + + it(@"should call the selector with the value of the signal", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignals:subject, nil]; + + expect(object.objectValue).to.beNil(); + + [subject sendNext:@1]; + expect(object.objectValue).to.equal(@1); + + [subject sendNext:@42]; + expect(object.objectValue).to.equal(@42); + }); +}); + +describe(@"-rac_liftSelector:withSignalsFromArray:", ^{ + __block RACTestObject *object; + + beforeEach(^{ + object = [[RACTestObject alloc] init]; + }); + + it(@"should call the selector with the value of the signal", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.objectValue).to.beNil(); + + [subject sendNext:@1]; + expect(object.objectValue).to.equal(@1); + + [subject sendNext:@42]; + expect(object.objectValue).to.equal(@42); + }); + + it(@"should call the selector with the value of the signal unboxed", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.integerValue).to.equal(0); + + [subject sendNext:@1]; + expect(object.integerValue).to.equal(1); + + [subject sendNext:@42]; + expect(object.integerValue).to.equal(42); + }); + + it(@"should work with multiple arguments", ^{ + RACSubject *objectValueSubject = [RACSubject subject]; + RACSubject *integerValueSubject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectValueSubject, integerValueSubject ]]; + + expect(object.hasInvokedSetObjectValueAndIntegerValue).to.beFalsy(); + expect(object.objectValue).to.beNil(); + expect(object.integerValue).to.equal(0); + + [objectValueSubject sendNext:@1]; + expect(object.hasInvokedSetObjectValueAndIntegerValue).to.beFalsy(); + expect(object.objectValue).to.beNil(); + expect(object.integerValue).to.equal(0); + + [integerValueSubject sendNext:@42]; + expect(object.hasInvokedSetObjectValueAndIntegerValue).to.beTruthy(); + expect(object.objectValue).to.equal(@1); + expect(object.integerValue).to.equal(42); + }); + + it(@"should work with signals that immediately start with a value", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ [subject startWith:@42] ]]; + + expect(object.objectValue).to.equal(@42); + + [subject sendNext:@1]; + expect(object.objectValue).to.equal(@1); + }); + + it(@"should work with signals that send nil", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + + [subject sendNext:nil]; + expect(object.objectValue).to.equal(nil); + + [subject sendNext:RACTupleNil.tupleNil]; + expect(object.objectValue).to.equal(nil); + }); + + it(@"should work with integers", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.integerValue).to.equal(0); + + [subject sendNext:@1]; + expect(object.integerValue).to.equal(@1); + }); + + it(@"should convert between numeric types", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.integerValue).to.equal(0); + + [subject sendNext:@1.0]; + expect(object.integerValue).to.equal(@1); + }); + + it(@"should work with class objects", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.objectValue).to.equal(nil); + + [subject sendNext:self.class]; + expect(object.objectValue).to.equal(self.class); + }); + + it(@"should work for char pointer", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setCharPointerValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.charPointerValue).to.equal(NULL); + + NSString *string = @"blah blah blah"; + [subject sendNext:string]; + expect(@(object.charPointerValue)).to.equal(string); + }); + + it(@"should work for const char pointer", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setConstCharPointerValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.constCharPointerValue).to.equal(NULL); + + NSString *string = @"blah blah blah"; + [subject sendNext:string]; + expect(@(object.constCharPointerValue)).to.equal(string); + }); + + it(@"should work for CGRect", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setRectValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.rectValue).to.equal(CGRectZero); + + CGRect value = CGRectMake(10, 20, 30, 40); + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGRect)]]; + expect(object.rectValue).to.equal(value); + }); + + it(@"should work for CGSize", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setSizeValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.sizeValue).to.equal(CGSizeZero); + + CGSize value = CGSizeMake(10, 20); + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGSize)]]; + expect(object.sizeValue).to.equal(value); + }); + + it(@"should work for CGPoint", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setPointValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.pointValue).to.equal(CGPointZero); + + CGPoint value = CGPointMake(10, 20); + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGPoint)]]; + expect(object.pointValue).to.equal(value); + }); + + it(@"should work for NSRange", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setRangeValue:) withSignalsFromArray:@[ subject ]]; + + expect(NSEqualRanges(object.rangeValue, NSMakeRange(0, 0))).to.beTruthy(); + + NSRange value = NSMakeRange(10, 20); + [subject sendNext:[NSValue valueWithRange:value]]; + expect(NSEqualRanges(object.rangeValue, value)).to.beTruthy(); + }); + + it(@"should work for _Bool", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setC99BoolValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.c99BoolValue).to.beFalsy(); + + _Bool value = true; + [subject sendNext:@(value)]; + expect(object.c99BoolValue).to.beTruthy(); + }); + + it(@"should work for primitive pointers", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(write5ToIntPointer:) withSignalsFromArray:@[ subject ]]; + + int value = 0; + int *valuePointer = &value; + expect(value).to.equal(0); + + [subject sendNext:[NSValue valueWithPointer:valuePointer]]; + expect(value).to.equal(5); + }); + + it(@"should work for custom structs", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setStructValue:) withSignalsFromArray:@[ subject ]]; + + expect(object.structValue.integerField).to.equal(0); + expect(object.structValue.doubleField).to.equal(0.0); + + RACTestStruct value = (RACTestStruct){7, 1.23}; + [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(typeof(value))]]; + expect(object.structValue.integerField).to.equal(value.integerField); + expect(object.structValue.doubleField).to.equal(value.doubleField); + }); + + it(@"should send the latest value of the signal as the right argument", ^{ + RACSubject *subject = [RACSubject subject]; + [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ [RACSignal return:@"object"], subject ]]; + [subject sendNext:@1]; + + expect(object.objectValue).to.equal(@"object"); + expect(object.integerValue).to.equal(1); + }); + + describe(@"the returned signal", ^{ + it(@"should send the return value of the method invocation", ^{ + RACSubject *objectSubject = [RACSubject subject]; + RACSubject *integerSubject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; + + __block NSString *result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [objectSubject sendNext:@"Magic number"]; + expect(result).to.beNil(); + + [integerSubject sendNext:@42]; + expect(result).to.equal(@"Magic number: 42"); + }); + + it(@"should send RACUnit.defaultUnit for void-returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:@1]; + + expect(result).to.equal(RACUnit.defaultUnit); + }); + + it(@"should support integer returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleInteger:) withSignalsFromArray:@[ subject ]]; + + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:@1]; + + expect(result).to.equal(@2); + }); + + it(@"should support char * returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleString:) withSignalsFromArray:@[ subject ]]; + + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:@"test"]; + + expect(result).to.equal(@"testtest"); + }); + + it(@"should support const char * returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleConstString:) withSignalsFromArray:@[ subject ]]; + + __block id result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:@"test"]; + + expect(result).to.equal(@"testtest"); + }); + + it(@"should support struct returning methods", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(doubleStruct:) withSignalsFromArray:@[ subject ]]; + + __block NSValue *boxedResult; + [signal subscribeNext:^(id x) { + boxedResult = x; + }]; + + RACTestStruct value = {4, 12.3}; + NSValue *boxedValue = [NSValue valueWithBytes:&value objCType:@encode(typeof(value))]; + [subject sendNext:boxedValue]; + + RACTestStruct result = {0, 0.0}; + [boxedResult getValue:&result]; + expect(result.integerField).to.equal(8); + expect(result.doubleField).to.equal(24.6); + }); + + it(@"should support block arguments and returns", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(wrapBlock:) withSignalsFromArray:@[ subject ]]; + + __block BOOL blockInvoked = NO; + dispatch_block_t testBlock = ^{ + blockInvoked = YES; + }; + + __block dispatch_block_t result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + [subject sendNext:testBlock]; + expect(result).notTo.beNil(); + + result(); + expect(blockInvoked).to.beTruthy(); + }); + + it(@"should replay the last value", ^{ + RACSubject *objectSubject = [RACSubject subject]; + RACSubject *integerSubject = [RACSubject subject]; + RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; + + [objectSubject sendNext:@"Magic number"]; + [integerSubject sendNext:@42]; + [integerSubject sendNext:@43]; + + __block NSString *result; + [signal subscribeNext:^(id x) { + result = x; + }]; + + expect(result).to.equal(@"Magic number: 43"); + }); + }); + + it(@"shouldn't strongly capture the receiver", ^{ + __block BOOL dealloced = NO; + @autoreleasepool { + RACTestObject *testObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [testObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + dealloced = YES; + }]]; + + RACSubject *subject = [RACSubject subject]; + [testObject rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; + [subject sendNext:@1]; + } + + expect(dealloced).to.beTruthy(); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingExamples.h new file mode 100644 index 0000000..694ce59 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingExamples.h
@@ -0,0 +1,16 @@ +// +// NSObjectRACPropertySubscribingExamples.h +// ReactiveCocoa +// +// Created by Josh Vera on 4/10/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +// The name of the shared examples for a signal-driven observation. +extern NSString * const RACPropertySubscribingExamples; + +// The block should have the signature: +// RACSignal * (^)(RACTestObject *testObject, NSString *keyPath, id observer) +// and should observe the value of the key path on testObject with observer. The value +// for this key should not be nil. +extern NSString * const RACPropertySubscribingExamplesSetupBlock;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingExamples.m new file mode 100644 index 0000000..1dae113 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingExamples.m
@@ -0,0 +1,279 @@ +// +// NSObjectRACPropertySubscribingExamples.m +// ReactiveCocoa +// +// Created by Josh Vera on 4/10/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" +#import "NSObjectRACPropertySubscribingExamples.h" + +#import "EXTScope.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal.h" + +NSString * const RACPropertySubscribingExamples = @"RACPropertySubscribingExamples"; +NSString * const RACPropertySubscribingExamplesSetupBlock = @"RACPropertySubscribingExamplesSetupBlock"; + +SharedExamplesBegin(NSObjectRACPropertySubscribingExamples) + +sharedExamples(RACPropertySubscribingExamples, ^(NSDictionary *data) { + __block RACSignal *(^signalBlock)(RACTestObject *object, NSString *keyPath, id observer); + + before(^{ + signalBlock = data[RACPropertySubscribingExamplesSetupBlock]; + }); + + it(@"should send the current value once on subscription", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self); + NSMutableArray *values = [NSMutableArray array]; + + object.objectValue = @0; + [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + + expect(values).to.equal((@[ @0 ])); + }); + + it(@"should send the new value when it changes", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self); + NSMutableArray *values = [NSMutableArray array]; + + object.objectValue = @0; + [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + + expect(values).to.equal((@[ @0 ])); + + object.objectValue = @1; + expect(values).to.equal((@[ @0, @1 ])); + + }); + + it(@"should stop observing when disposed", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self); + NSMutableArray *values = [NSMutableArray array]; + + object.objectValue = @0; + RACDisposable *disposable = [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + + object.objectValue = @1; + NSArray *expected = @[ @0, @1 ]; + expect(values).to.equal(expected); + + [disposable dispose]; + object.objectValue = @2; + expect(values).to.equal(expected); + }); + + it(@"shouldn't send any more values after the observer is gone", ^{ + __block BOOL observerDealloced = NO; + RACTestObject *object = [[RACTestObject alloc] init]; + NSMutableArray *values = [NSMutableArray array]; + @autoreleasepool { + RACTestObject *observer __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + observerDealloced = YES; + }]]; + + RACSignal *signal = signalBlock(object, @keypath(object, objectValue), observer); + object.objectValue = @1; + [signal subscribeNext:^(id x) { + [values addObject:x]; + }]; + } + + expect(observerDealloced).to.beTruthy(); + + NSArray *expected = @[ @1 ]; + expect(values).to.equal(expected); + + object.objectValue = @2; + expect(values).to.equal(expected); + }); + + it(@"shouldn't keep either object alive unnaturally long", ^{ + __block BOOL objectDealloced = NO; + __block BOOL scopeObjectDealloced = NO; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDealloced = YES; + }]]; + RACTestObject *scopeObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [scopeObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + scopeObjectDealloced = YES; + }]]; + + RACSignal *signal = signalBlock(object, @keypath(object, objectValue), scopeObject); + + [signal subscribeNext:^(id _) { + + }]; + } + + expect(objectDealloced).to.beTruthy(); + expect(scopeObjectDealloced).to.beTruthy(); + }); + + it(@"shouldn't keep the signal alive past the lifetime of the object", ^{ + __block BOOL objectDealloced = NO; + __block BOOL signalDealloced = NO; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDealloced = YES; + }]]; + + RACSignal *signal = [signalBlock(object, @keypath(object, objectValue), self) map:^(id value) { + return value; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + signalDealloced = YES; + }]]; + + [signal subscribeNext:^(id _) { + + }]; + } + + expect(signalDealloced).will.beTruthy(); + expect(objectDealloced).to.beTruthy(); + }); + + it(@"should not resurrect a deallocated object upon subscription", ^{ + dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT); + @onExit { + dispatch_release(queue); + }; + + dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); + + // Fuzz out race conditions. + for (unsigned i = 0; i < 100; i++) { + dispatch_suspend(queue); + + __block CFTypeRef object; + __block BOOL deallocated; + + RACSignal *signal; + + @autoreleasepool { + RACTestObject *testObject = [[RACTestObject alloc] init]; + [testObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + signal = signalBlock(testObject, @keypath(testObject, objectValue), nil); + object = CFBridgingRetain(testObject); + } + + dispatch_block_t testSubscription = ^{ + RACDisposable *disposable = [signal subscribeCompleted:^{}]; + expect(disposable).notTo.beNil(); + }; + + unsigned beforeCount = arc4random_uniform(20); + for (unsigned j = 0; j < beforeCount; j++) { + dispatch_async(queue, testSubscription); + } + + dispatch_async(queue, ^{ + CFRelease(object); + + // expect() is a bit finicky on background threads. + STAssertTrue(deallocated, @"Object did not deallocate after being released"); + }); + + unsigned afterCount = arc4random_uniform(20); + for (unsigned j = 0; j < afterCount; j++) { + dispatch_async(queue, testSubscription); + } + + dispatch_barrier_async(queue, testSubscription); + + // Start everything and wait for it all to complete. + dispatch_resume(queue); + + expect(deallocated).will.beTruthy(); + dispatch_barrier_sync(queue, ^{}); + } + }); + + it(@"shouldn't crash when the value is changed on a different queue", ^{ + __block id value; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + RACSignal *signal = signalBlock(object, @keypath(object, objectValue), self); + + [signal subscribeNext:^(id x) { + value = x; + }]; + + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + [queue addOperationWithBlock:^{ + object.objectValue = @1; + }]; + + [queue waitUntilAllOperationsAreFinished]; + } + + expect(value).will.equal(@1); + }); + + describe(@"mutating collections", ^{ + __block RACTestObject *object; + __block NSMutableOrderedSet *lastValue; + __block NSMutableOrderedSet *proxySet; + + before(^{ + object = [[RACTestObject alloc] init]; + object.objectValue = [NSMutableOrderedSet orderedSetWithObject:@1]; + + NSString *keyPath = @keypath(object, objectValue); + + [signalBlock(object, keyPath, self) subscribeNext:^(NSMutableOrderedSet *x) { + lastValue = x; + }]; + + proxySet = [object mutableOrderedSetValueForKey:keyPath]; + }); + + it(@"sends the newest object when inserting values into an observed object", ^{ + NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @1, @2, nil]; + + [proxySet addObject:@2]; + expect(lastValue).to.equal(expected); + }); + + it(@"sends the newest object when removing values in an observed object", ^{ + NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSet]; + + [proxySet removeAllObjects]; + expect(lastValue).to.equal(expected); + }); + + it(@"sends the newest object when replacing values in an observed object", ^{ + NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSetWithObjects: @2, nil]; + + [proxySet replaceObjectAtIndex:0 withObject:@2]; + expect(lastValue).to.equal(expected); + }); + }); + +}); + +SharedExamplesEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingSpec.m new file mode 100644 index 0000000..1b69c2a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACPropertySubscribingSpec.m
@@ -0,0 +1,155 @@ +// +// NSObjectRACPropertySubscribingSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 9/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSObjectRACPropertySubscribingExamples.h" +#import "RACTestObject.h" + +#import "NSObject+RACPropertySubscribing.h" +#import "RACDisposable.h" +#import "RACSignal.h" + +SpecBegin(NSObjectRACPropertySubscribing) + +describe(@"-rac_valuesForKeyPath:observer:", ^{ + id (^setupBlock)(id, id, id) = ^(RACTestObject *object, NSString *keyPath, id observer) { + return [object rac_valuesForKeyPath:keyPath observer:observer]; + }; + + itShouldBehaveLike(RACPropertySubscribingExamples, ^{ + return @{ RACPropertySubscribingExamplesSetupBlock: setupBlock }; + }); + +}); + +describe(@"+rac_signalWithChangesFor:keyPath:options:observer:", ^{ + describe(@"KVO options argument", ^{ + __block RACTestObject *object; + __block id actual; + __block RACSignal *(^objectValueSignal)(NSKeyValueObservingOptions); + + before(^{ + object = [[RACTestObject alloc] init]; + + objectValueSignal = ^(NSKeyValueObservingOptions options) { + return [[object rac_valuesAndChangesForKeyPath:@keypath(object, objectValue) options:options observer:self] reduceEach:^(id value, NSDictionary *change) { + return change; + }]; + }; + }); + + it(@"sends a KVO dictionary", ^{ + [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { + actual = x; + }]; + + object.objectValue = @1; + + expect(actual).to.beKindOf(NSDictionary.class); + }); + + it(@"sends a kind key by default", ^{ + [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeKindKey]; + }]; + + object.objectValue = @1; + + expect(actual).notTo.beNil(); + }); + + it(@"sends the newest changes with NSKeyValueObservingOptionNew", ^{ + [objectValueSignal(NSKeyValueObservingOptionNew) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeNewKey]; + }]; + + object.objectValue = @1; + expect(actual).to.equal(@1); + + object.objectValue = @2; + expect(actual).to.equal(@2); + }); + + it(@"sends an additional change value with NSKeyValueObservingOptionPrior", ^{ + NSMutableArray *values = [NSMutableArray new]; + NSArray *expected = @[ @(YES), @(NO) ]; + + [objectValueSignal(NSKeyValueObservingOptionPrior) subscribeNext:^(NSDictionary *x) { + BOOL isPrior = [x[NSKeyValueChangeNotificationIsPriorKey] boolValue]; + [values addObject:@(isPrior)]; + }]; + + object.objectValue = @[ @1 ]; + + expect(values).to.equal(expected); + }); + + it(@"sends index changes when adding, inserting or removing a value from an observed object", ^{ + __block NSUInteger hasIndexesCount = 0; + + [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { + if (x[NSKeyValueChangeIndexesKey] != nil) { + hasIndexesCount += 1; + } + }]; + + object.objectValue = [NSMutableOrderedSet orderedSet]; + expect(hasIndexesCount).to.equal(0); + + NSMutableOrderedSet *objectValue = [object mutableOrderedSetValueForKey:@"objectValue"]; + + [objectValue addObject:@1]; + expect(hasIndexesCount).to.equal(1); + + [objectValue replaceObjectAtIndex:0 withObject:@2]; + expect(hasIndexesCount).to.equal(2); + + [objectValue removeObject:@2]; + expect(hasIndexesCount).to.equal(3); + }); + + it(@"sends the previous value with NSKeyValueObservingOptionOld", ^{ + [objectValueSignal(NSKeyValueObservingOptionOld) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeOldKey]; + }]; + + object.objectValue = @1; + expect(actual).to.equal(NSNull.null); + + object.objectValue = @2; + expect(actual).to.equal(@1); + }); + + it(@"sends the initial value with NSKeyValueObservingOptionInitial", ^{ + [objectValueSignal(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) subscribeNext:^(NSDictionary *x) { + actual = x[NSKeyValueChangeNewKey]; + }]; + + expect(actual).to.equal(NSNull.null); + }); + }); +}); + +describe(@"-rac_valuesAndChangesForKeyPath:options:observer:", ^{ + it(@"should complete immediately if the receiver or observer have deallocated", ^{ + RACSignal *signal; + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + RACTestObject *observer __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + signal = [object rac_valuesAndChangesForKeyPath:@keypath(object, stringValue) options:0 observer:observer]; + } + + __block BOOL completed = NO; + [signal subscribeCompleted:^{ + completed = YES; + }]; + + expect(completed).to.beTruthy(); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACSelectorSignalSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACSelectorSignalSpec.m new file mode 100644 index 0000000..b5b1839 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSObjectRACSelectorSignalSpec.m
@@ -0,0 +1,434 @@ +// +// NSObjectRACSelectorSignalSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" +#import "RACSubclassObject.h" + +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACMulticastConnection.h" +#import "RACSignal+Operations.h" +#import "RACSignal.h" +#import "RACTuple.h" + +@protocol TestProtocol + +@required +- (BOOL)requiredMethod:(NSUInteger)number; +- (void)lifeIsGood:(id)sender; + +@optional +- (NSUInteger)optionalMethodWithObject:(id)object flag:(BOOL)flag; +- (id)objectValue; + +@end + +SpecBegin(NSObjectRACSelectorSignal) + +describe(@"RACTestObject", ^{ + it(@"should send the argument for each invocation", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + [object lifeIsGood:@42]; + + expect(value).to.equal(@42); + }); + + it(@"should send completed on deallocation", ^{ + __block BOOL completed = NO; + __block BOOL deallocated = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeCompleted:^{ + completed = YES; + }]; + + expect(deallocated).to.beFalsy(); + expect(completed).to.beFalsy(); + } + + expect(deallocated).to.beTruthy(); + expect(completed).to.beTruthy(); + }); + + it(@"should send for a zero-argument method", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block RACTuple *value; + [[object rac_signalForSelector:@selector(objectValue)] subscribeNext:^(RACTuple *x) { + value = x; + }]; + + [object objectValue]; + expect(value).to.equal([RACTuple tupleWithObjectsFromArray:@[]]); + }); + + it(@"should send the argument for each invocation to the instance's own signal", ^{ + RACTestObject *object1 = [[RACTestObject alloc] init]; + __block id value1; + [[object1 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value1 = x.first; + }]; + + RACTestObject *object2 = [[RACTestObject alloc] init]; + __block id value2; + [[object2 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value2 = x.first; + }]; + + [object1 lifeIsGood:@42]; + [object2 lifeIsGood:@"Carpe diem"]; + + expect(value1).to.equal(@42); + expect(value2).to.equal(@"Carpe diem"); + }); + + it(@"should send multiple arguments for each invocation", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id value1; + __block id value2; + [[object rac_signalForSelector:@selector(combineObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + value1 = x.first; + value2 = x.second; + }]; + + expect([object combineObjectValue:@42 andSecondObjectValue:@"foo"]).to.equal(@"42: foo"); + expect(value1).to.equal(@42); + expect(value2).to.equal(@"foo"); + }); + + it(@"should send arguments for invocation of non-existant methods", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + __block id key; + __block id value; + [[object rac_signalForSelector:@selector(setObject:forKey:)] subscribeNext:^(RACTuple *x) { + value = x.first; + key = x.second; + }]; + + [object performSelector:@selector(setObject:forKey:) withObject:@YES withObject:@"Winner"]; + + expect(value).to.equal(@YES); + expect(key).to.equal(@"Winner"); + }); + + it(@"should send arguments for invocation and invoke the original method on previously KVO'd receiver", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + [[RACObserve(object, objectValue) publish] connect]; + + __block id key; + __block id value; + [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + key = x.second; + }]; + + [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; + + expect(object.hasInvokedSetObjectValueAndSecondObjectValue).to.beTruthy(); + expect(object.objectValue).to.equal(@YES); + expect(object.secondObjectValue).to.equal(@"Winner"); + + expect(value).to.equal(@YES); + expect(key).to.equal(@"Winner"); + }); + + it(@"should send arguments for invocation and invoke the original method when receiver is subsequently KVO'd", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id key; + __block id value; + [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + key = x.second; + }]; + + [[RACObserve(object, objectValue) publish] connect]; + + [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; + + expect(object.hasInvokedSetObjectValueAndSecondObjectValue).to.beTruthy(); + expect(object.objectValue).to.equal(@YES); + expect(object.secondObjectValue).to.equal(@"Winner"); + + expect(value).to.equal(@YES); + expect(key).to.equal(@"Winner"); + }); + + it(@"should properly implement -respondsToSelector: when called on KVO'd receiver", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + // First, setup KVO on `object`, which gives us the desired side-effect + // of `object` taking on a KVO-custom subclass. + [[RACObserve(object, objectValue) publish] connect]; + + SEL selector = NSSelectorFromString(@"anyOldSelector:"); + + // With the KVO subclass in place, call -rac_signalForSelector: to + // implement -anyOldSelector: directly on the KVO subclass. + [object rac_signalForSelector:selector]; + + expect([object respondsToSelector:selector]).to.beTruthy(); + }); + + it(@"should properly implement -respondsToSelector: for optional method from a protocol", ^{ + // Selector for the targeted optional method from a protocol. + SEL selector = @selector(optionalProtocolMethodWithObjectValue:); + + RACTestObject *object1 = [[RACTestObject alloc] init]; + + // Method implementation of the selector is added to its swizzled class. + [object1 rac_signalForSelector:selector fromProtocol:@protocol(RACTestProtocol)]; + + expect([object1 respondsToSelector:selector]).to.beTruthy(); + + RACTestObject *object2 = [[RACTestObject alloc] init]; + + // Call -rac_signalForSelector: to swizzle this instance's class, + // method implementations of -respondsToSelector: and + // -forwardInvocation:. + [object2 rac_signalForSelector:@selector(lifeIsGood:)]; + + // This instance should not respond to the selector because of not + // calling -rac_signalForSelector: with the selector. + expect([object2 respondsToSelector:selector]).to.beFalsy(); + }); + + it(@"should send non-object arguments", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id value; + [[object rac_signalForSelector:@selector(setIntegerValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + object.integerValue = 42; + expect(value).to.equal(@42); + }); + + it(@"should send on signal after the original method is invoked", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block BOOL invokedMethodBefore = NO; + [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) { + invokedMethodBefore = object.hasInvokedSetObjectValueAndSecondObjectValue; + }]; + + [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; + expect(invokedMethodBefore).to.beTruthy(); + }); +}); + +it(@"should swizzle an NSObject method", ^{ + NSObject *object = [[NSObject alloc] init]; + + __block RACTuple *value; + [[object rac_signalForSelector:@selector(description)] subscribeNext:^(RACTuple *x) { + value = x; + }]; + + expect([object description]).notTo.beNil(); + expect(value).to.equal([RACTuple tupleWithObjectsFromArray:@[]]); +}); + +describe(@"a class that already overrides -forwardInvocation:", ^{ + it(@"should invoke the superclass' implementation", ^{ + RACSubclassObject *object = [[RACSubclassObject alloc] init]; + + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + [object lifeIsGood:@42]; + expect(value).to.equal(@42); + + expect(object.forwardedSelector).to.beNil(); + + [object performSelector:@selector(allObjects)]; + + expect(value).to.equal(@42); + expect(object.forwardedSelector).to.equal(@selector(allObjects)); + }); + + it(@"should not infinite recurse when KVO'd after RAC swizzled", ^{ + RACSubclassObject *object = [[RACSubclassObject alloc] init]; + + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + [[RACObserve(object, objectValue) publish] connect]; + + [object lifeIsGood:@42]; + expect(value).to.equal(@42); + + expect(object.forwardedSelector).to.beNil(); + [object performSelector:@selector(allObjects)]; + expect(object.forwardedSelector).to.equal(@selector(allObjects)); + }); +}); + +describe(@"two classes in the same hierarchy", ^{ + __block RACTestObject *superclassObj; + __block RACTuple *superclassTuple; + + __block RACSubclassObject *subclassObj; + __block RACTuple *subclassTuple; + + beforeEach(^{ + superclassObj = [[RACTestObject alloc] init]; + expect(superclassObj).notTo.beNil(); + + subclassObj = [[RACSubclassObject alloc] init]; + expect(subclassObj).notTo.beNil(); + }); + + it(@"should not collide", ^{ + [[superclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) { + superclassTuple = t; + }]; + + [[subclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) { + subclassTuple = t; + }]; + + expect([superclassObj combineObjectValue:@"foo" andIntegerValue:42]).to.equal(@"foo: 42"); + + NSArray *expectedValues = @[ @"foo", @42 ]; + expect(superclassTuple.allObjects).to.equal(expectedValues); + + expect([subclassObj combineObjectValue:@"foo" andIntegerValue:42]).to.equal(@"fooSUBCLASS: 42"); + + expectedValues = @[ @"foo", @42 ]; + expect(subclassTuple.allObjects).to.equal(expectedValues); + }); + + it(@"should not collide when the superclass is invoked asynchronously", ^{ + [[superclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) { + superclassTuple = t; + }]; + + [[subclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) { + subclassTuple = t; + }]; + + [superclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"]; + expect(superclassObj.hasInvokedSetObjectValueAndSecondObjectValue).to.beTruthy(); + + NSArray *expectedValues = @[ @"foo", @"42" ]; + expect(superclassTuple.allObjects).to.equal(expectedValues); + + [subclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"]; + expect(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue).to.beFalsy(); + expect(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue).will.beTruthy(); + + expectedValues = @[ @"foo", @"42" ]; + expect(subclassTuple.allObjects).to.equal(expectedValues); + }); +}); + +describe(@"-rac_signalForSelector:fromProtocol", ^{ + __block RACTestObject<TestProtocol> *object; + __block Protocol *protocol; + + beforeEach(^{ + object = (id)[[RACTestObject alloc] init]; + expect(object).notTo.beNil(); + + protocol = @protocol(TestProtocol); + expect(protocol).notTo.beNil(); + }); + + it(@"should not clobber a required method already implemented", ^{ + __block id value; + [[object rac_signalForSelector:@selector(lifeIsGood:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + [object lifeIsGood:@42]; + expect(value).to.equal(@42); + }); + + it(@"should not clobber an optional method already implemented", ^{ + object.objectValue = @"foo"; + + __block id value; + [[object rac_signalForSelector:@selector(objectValue) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x; + }]; + + expect([object objectValue]).to.equal(@"foo"); + expect(value).to.equal([RACTuple tupleWithObjectsFromArray:@[]]); + }); + + it(@"should inject a required method", ^{ + __block id value; + [[object rac_signalForSelector:@selector(requiredMethod:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + expect([object requiredMethod:42]).to.beFalsy(); + expect(value).to.equal(42); + }); + + it(@"should inject an optional method", ^{ + __block id value; + [[object rac_signalForSelector:@selector(optionalMethodWithObject:flag:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) { + value = x; + }]; + + expect([object optionalMethodWithObject:@"foo" flag:YES]).to.equal(0); + expect(value).to.equal(RACTuplePack(@"foo", @YES)); + }); +}); + +describe(@"class reporting", ^{ + __block RACTestObject *object; + __block Class originalClass; + + beforeEach(^{ + object = [[RACTestObject alloc] init]; + originalClass = object.class; + }); + + it(@"should report the original class", ^{ + [object rac_signalForSelector:@selector(lifeIsGood:)]; + expect(object.class).to.beIdenticalTo(originalClass); + }); + + it(@"should report the original class when it's KVO'd after dynamically subclassing", ^{ + [object rac_signalForSelector:@selector(lifeIsGood:)]; + [[RACObserve(object, objectValue) publish] connect]; + expect(object.class).to.beIdenticalTo(originalClass); + }); + + it(@"should report the original class when it's KVO'd before dynamically subclassing", ^{ + [[RACObserve(object, objectValue) publish] connect]; + [object rac_signalForSelector:@selector(lifeIsGood:)]; + expect(object.class).to.beIdenticalTo(originalClass); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSStringRACKeyPathUtilitiesSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSStringRACKeyPathUtilitiesSpec.m new file mode 100644 index 0000000..72e743a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSStringRACKeyPathUtilitiesSpec.m
@@ -0,0 +1,51 @@ +// +// NSStringRACKeyPathUtilitiesSpec.m +// ReactiveCocoa +// +// Created by Uri Baghin on 05/05/2013. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSString+RACKeyPathUtilities.h" + +SpecBegin(NSStringRACKeyPathUtilities) + +describe(@"-keyPathComponents", ^{ + it(@"should return components in the key path", ^{ + expect(@"self.test.key.path".rac_keyPathComponents).to.equal((@[@"self", @"test", @"key", @"path"])); + }); + + it(@"should return nil if given an empty string", ^{ + expect(@"".rac_keyPathComponents).to.beNil(); + }); +}); + +describe(@"-keyPathByDeletingLastKeyPathComponent", ^{ + it(@"should return the parent key path", ^{ + expect(@"grandparent.parent.child".rac_keyPathByDeletingLastKeyPathComponent).to.equal(@"grandparent.parent"); + }); + + it(@"should return nil if given an empty string", ^{ + expect(@"".rac_keyPathByDeletingLastKeyPathComponent).to.beNil(); + }); + + it(@"should return nil if given a key path with only one component", ^{ + expect(@"self".rac_keyPathByDeletingLastKeyPathComponent).to.beNil(); + }); +}); + +describe(@"-keyPathByDeletingFirstKeyPathComponent", ^{ + it(@"should return the remaining key path", ^{ + expect(@"first.second.third".rac_keyPathByDeletingFirstKeyPathComponent).to.equal(@"second.third"); + }); + + it(@"should return nil if given an empty string", ^{ + expect(@"".rac_keyPathByDeletingFirstKeyPathComponent).to.beNil(); + }); + + it(@"should return nil if given a key path with only one component", ^{ + expect(@"self".rac_keyPathByDeletingFirstKeyPathComponent).to.beNil(); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSTextRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSTextRACSupportSpec.m new file mode 100644 index 0000000..364d0e4 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSTextRACSupportSpec.m
@@ -0,0 +1,33 @@ +// +// NSTextRACSupportSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-03-08. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSText+RACSignalSupport.h" +#import "RACSignal.h" + +SpecBegin(NSTextRACSupport) + +it(@"NSTextView should send changes on rac_textSignal", ^{ + NSTextView *textView = [[NSTextView alloc] initWithFrame:NSZeroRect]; + expect(textView).notTo.beNil(); + + NSMutableArray *strings = [NSMutableArray array]; + [textView.rac_textSignal subscribeNext:^(NSString *str) { + [strings addObject:str]; + }]; + + expect(strings).to.equal(@[ @"" ]); + + [textView insertText:@"f"]; + [textView insertText:@"o"]; + [textView insertText:@"b"]; + + NSArray *expected = @[ @"", @"f", @"fo", @"fob" ]; + expect(strings).to.equal(expected); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSURLConnectionRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSURLConnectionRACSupportSpec.m new file mode 100644 index 0000000..e7bc8de --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSURLConnectionRACSupportSpec.m
@@ -0,0 +1,36 @@ +// +// NSURLConnectionRACSupportSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-10-01. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSURLConnection+RACSupport.h" +#import "RACSignal+Operations.h" +#import "RACTuple.h" + +SpecBegin(NSURLConnectionRACSupport) + +it(@"should fetch a JSON file", ^{ + NSURL *fileURL = [[NSBundle bundleForClass:self.class] URLForResource:@"test-data" withExtension:@"json"]; + expect(fileURL).notTo.beNil(); + + NSURLRequest *request = [NSURLRequest requestWithURL:fileURL]; + + BOOL success = NO; + NSError *error = nil; + RACTuple *result = [[NSURLConnection rac_sendAsynchronousRequest:request] firstOrDefault:nil success:&success error:&error]; + expect(success).to.beTruthy(); + expect(error).to.beNil(); + expect(result).to.beKindOf(RACTuple.class); + + NSURLResponse *response = result.first; + expect(response).to.beKindOf(NSURLResponse.class); + + NSData *data = result.second; + expect(data).to.beKindOf(NSData.class); + expect(data).to.equal([NSData dataWithContentsOfURL:fileURL]); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/NSUserDefaultsRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/NSUserDefaultsRACSupportSpec.m new file mode 100644 index 0000000..3f0866c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/NSUserDefaultsRACSupportSpec.m
@@ -0,0 +1,133 @@ +// +// NSUserDefaultsRACSupportSpec.m +// ReactiveCocoa +// +// Created by Matt Diephouse on 12/19/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSUserDefaults+RACSupport.h" + +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACKVOChannel.h" +#import "NSObject+RACDeallocating.h" +#import "RACSignal+Operations.h" + +static NSString * const NSUserDefaultsRACSupportSpecStringDefault = @"NSUserDefaultsRACSupportSpecStringDefault"; +static NSString * const NSUserDefaultsRACSupportSpecBoolDefault = @"NSUserDefaultsRACSupportSpecBoolDefault"; + +@interface TestObserver : NSObject + +@property (copy, atomic) NSString *string1; +@property (copy, atomic) NSString *string2; + +@property (assign, atomic) BOOL bool1; + +@end + +@implementation TestObserver + +@end + +SpecBegin(NSUserDefaultsRACSupportSpec) + +__block NSUserDefaults *defaults = nil; +__block TestObserver *observer = nil; + +beforeEach(^{ + defaults = NSUserDefaults.standardUserDefaults; + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecStringDefault]; + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + observer = [TestObserver new]; +}); + +afterEach(^{ + observer = nil; +}); + +it(@"should set defaults", ^{ + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + observer.string1 = @"A string"; + observer.bool1 = YES; + + expect([defaults objectForKey:NSUserDefaultsRACSupportSpecStringDefault]).will.equal(@"A string"); + expect([defaults objectForKey:NSUserDefaultsRACSupportSpecBoolDefault]).will.equal(@YES); +}); + +it(@"should read defaults", ^{ + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + expect(observer.string1).to.beNil(); + expect(observer.bool1).to.equal(NO); + + [defaults setObject:@"Another string" forKey:NSUserDefaultsRACSupportSpecStringDefault]; + [defaults setBool:YES forKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + expect(observer.string1).to.equal(@"Another string"); + expect(observer.bool1).to.equal(YES); +}); + +it(@"should be okay to create 2 terminals", ^{ + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, string2) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + + [defaults setObject:@"String 3" forKey:NSUserDefaultsRACSupportSpecStringDefault]; + + expect(observer.string1).to.equal(@"String 3"); + expect(observer.string2).to.equal(@"String 3"); +}); + +it(@"should handle removed defaults", ^{ + observer.string1 = @"Some string"; + observer.bool1 = YES; + + RACChannelTo(observer, string1) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTo(observer, bool1, @NO) = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecStringDefault]; + [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecBoolDefault]; + + expect(observer.string1).to.beNil(); + expect(observer.bool1).to.equal(NO); +}); + +it(@"shouldn't resend values", ^{ + RACChannelTerminal *terminal = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + + RACChannelTo(observer, string1) = terminal; + + RACSignal *sentValue = [terminal replayLast]; + observer.string1 = @"Test value"; + id value = [sentValue asynchronousFirstOrDefault:nil success:NULL error:NULL]; + expect(value).to.beNil(); +}); + +it(@"should complete when the NSUserDefaults deallocates", ^{ + __block RACChannelTerminal *terminal; + __block BOOL deallocated = NO; + + @autoreleasepool { + NSUserDefaults *customDefaults __attribute__((objc_precise_lifetime)) = [NSUserDefaults new]; + [customDefaults.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + terminal = [customDefaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + } + + expect(deallocated).to.beTruthy(); + expect([terminal asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); +}); + +it(@"should send an initial value", ^{ + [defaults setObject:@"Initial" forKey:NSUserDefaultsRACSupportSpecStringDefault]; + RACChannelTerminal *terminal = [defaults rac_channelTerminalForKey:NSUserDefaultsRACSupportSpecStringDefault]; + expect([terminal asynchronousFirstOrDefault:nil success:NULL error:NULL]).to.equal(@"Initial"); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACBacktraceSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACBacktraceSpec.m new file mode 100644 index 0000000..9ad4c3b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACBacktraceSpec.m
@@ -0,0 +1,148 @@ +// +// RACBacktraceSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-12-24. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACBacktrace.h" + +#import "NSArray+RACSequenceAdditions.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" +#import "RACSequence.h" +#import "RACSignal+Operations.h" + +#ifdef DEBUG + +static RACBacktrace *previousBacktrace; + +static void capturePreviousBacktrace(void *context) { + previousBacktrace = [RACBacktrace backtrace].previousThreadBacktrace; +} + +typedef struct { + dispatch_queue_t queue; + NSUInteger i; + __unsafe_unretained RACSubject *doneSubject; +} RACDeepRecursionContext; + +static void recurseDeeply(void *ptr) { + RACDeepRecursionContext *context = ptr; + + if (context->i++ < 10000) { + rac_dispatch_async_f(context->queue, context, recurseDeeply); + } else { + [context->doneSubject sendCompleted]; + } +} + +SpecBegin(RACBacktrace) + +__block dispatch_block_t block; + +beforeEach(^{ + expect([RACBacktrace backtrace].previousThreadBacktrace).to.beNil(); + previousBacktrace = nil; + + block = ^{ + capturePreviousBacktrace(NULL); + }; +}); + +it(@"should capture the current backtrace", ^{ + RACBacktrace *backtrace = [RACBacktrace backtrace]; + expect(backtrace).notTo.beNil(); +}); + +describe(@"with a GCD queue", ^{ + __block dispatch_queue_t queue; + + beforeEach(^{ + queue = dispatch_queue_create("com.github.ReactiveCocoa.RACBacktraceSpec", DISPATCH_QUEUE_SERIAL); + }); + + afterEach(^{ + dispatch_barrier_sync(queue, ^{}); + dispatch_release(queue); + }); + + it(@"should trace across dispatch_async", ^{ + rac_dispatch_async(queue, block); + expect(previousBacktrace).willNot.beNil(); + }); + + it(@"should trace across dispatch_async to the main thread", ^{ + rac_dispatch_async(queue, ^{ + rac_dispatch_async(dispatch_get_main_queue(), block); + }); + + expect(previousBacktrace).willNot.beNil(); + }); + + it(@"should trace across dispatch_async_f", ^{ + rac_dispatch_async_f(queue, NULL, &capturePreviousBacktrace); + expect(previousBacktrace).willNot.beNil(); + }); + + it(@"should trace across dispatch_barrier_async", ^{ + rac_dispatch_barrier_async(queue, block); + expect(previousBacktrace).willNot.beNil(); + }); + + it(@"should trace across dispatch_barrier_async_f", ^{ + rac_dispatch_barrier_async_f(queue, NULL, &capturePreviousBacktrace); + expect(previousBacktrace).willNot.beNil(); + }); + + it(@"should trace across dispatch_after", ^{ + rac_dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1), queue, block); + expect(previousBacktrace).willNot.beNil(); + }); + + it(@"should trace across dispatch_after_f", ^{ + rac_dispatch_after_f(dispatch_time(DISPATCH_TIME_NOW, 1), queue, NULL, &capturePreviousBacktrace); + expect(previousBacktrace).willNot.beNil(); + }); + + it(@"shouldn't overflow the stack when deallocating a huge backtrace list", ^{ + RACSubject *doneSubject = [RACReplaySubject subject]; + RACDeepRecursionContext context = { + .queue = queue, + .i = 0, + .doneSubject = doneSubject + }; + + rac_dispatch_async_f(queue, &context, &recurseDeeply); + [doneSubject waitUntilCompleted:NULL]; + }); +}); + +it(@"should trace across a RACScheduler", ^{ + [[RACScheduler scheduler] schedule:block]; + expect(previousBacktrace).willNot.beNil(); +}); + +it(@"shouldn't go bonkers with RACScheduler", ^{ + NSMutableArray *a = [NSMutableArray array]; + for (NSUInteger i = 0; i < 5000; i++) { + [a addObject:@(i)]; + } + + [[a.rac_sequence signalWithScheduler:[RACScheduler scheduler]] subscribeCompleted:^{}]; +}); + +// Tracing across NSOperationQueue only works on OS X because it depends on +// interposing through dynamic linking +#ifndef __IPHONE_OS_VERSION_MIN_REQUIRED + it(@"should trace across an NSOperationQueue", ^{ + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + [queue addOperationWithBlock:block]; + expect(previousBacktrace).willNot.beNil(); + }); +#endif + +SpecEnd + +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACBlockTrampolineSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACBlockTrampolineSpec.m new file mode 100644 index 0000000..72570fe --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACBlockTrampolineSpec.m
@@ -0,0 +1,48 @@ +// +// RACBlockTrampolineSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 10/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACBlockTrampoline.h" +#import "RACTuple.h" + +SpecBegin(RACBlockTrampoline) + +it(@"should invoke the block with the given arguments", ^{ + __block NSString *stringArg; + __block NSNumber *numberArg; + id (^block)(NSString *, NSNumber *) = ^ id (NSString *string, NSNumber *number) { + stringArg = string; + numberArg = number; + return nil; + }; + + [RACBlockTrampoline invokeBlock:block withArguments:RACTuplePack(@"hi", @1)]; + expect(stringArg).to.equal(@"hi"); + expect(numberArg).to.equal(@1); +}); + +it(@"should return the result of the block invocation", ^{ + NSString * (^block)(NSString *) = ^(NSString *string) { + return string.uppercaseString; + }; + + NSString *result = [RACBlockTrampoline invokeBlock:block withArguments:RACTuplePack(@"hi")]; + expect(result).to.equal(@"HI"); +}); + +it(@"should pass RACTupleNils as nil", ^{ + __block id arg; + id (^block)(id) = ^ id (id obj) { + arg = obj; + return nil; + }; + + [RACBlockTrampoline invokeBlock:block withArguments:RACTuplePack(nil)]; + expect(arg).to.beNil(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelExamples.h new file mode 100644 index 0000000..0952fed --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelExamples.h
@@ -0,0 +1,34 @@ +// +// RACChannelExamples.h +// ReactiveCocoa +// +// Created by Uri Baghin on 30/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +// The name of the shared examples for RACChannel and its subclasses. +extern NSString * const RACChannelExamples; + +// A block of type `RACChannel * (^)(void)`, which should return a new +// RACChannel. +extern NSString * const RACChannelExampleCreateBlock; + +// The name of the shared examples for any RACChannel class that gets and sets +// a property. +extern NSString * const RACViewChannelExamples; + +// A block of type `NSObject * (^)(void)`, which should create a new test view +// and return it. +extern NSString * const RACViewChannelExampleCreateViewBlock; + +// A block of type `RACChannelTerminal * (^)(NSObject *view)`, which should +// create a new RACChannel to the given test view and return an terminal. +extern NSString * const RACViewChannelExampleCreateTerminalBlock; + +// The key path that will be read/written in RACViewChannelExamples. This +// must lead to an NSNumber or numeric primitive property. +extern NSString * const RACViewChannelExampleKeyPath; + +// A block of type `void (^)(NSObject *view, NSNumber *value)`, which should +// change the given test view's value to the given one. +extern NSString * const RACViewChannelExampleSetViewValueBlock;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelExamples.m new file mode 100644 index 0000000..9fc77fc --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelExamples.m
@@ -0,0 +1,298 @@ +// +// RACChannelExamples.m +// ReactiveCocoa +// +// Created by Uri Baghin on 30/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACChannelExamples.h" + +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACChannel.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" + +NSString * const RACChannelExamples = @"RACChannelExamples"; +NSString * const RACChannelExampleCreateBlock = @"RACChannelExampleCreateBlock"; + +NSString * const RACViewChannelExamples = @"RACViewChannelExamples"; +NSString * const RACViewChannelExampleCreateViewBlock = @"RACViewChannelExampleCreateViewBlock"; +NSString * const RACViewChannelExampleCreateTerminalBlock = @"RACViewChannelExampleCreateTerminalBlock"; +NSString * const RACViewChannelExampleKeyPath = @"RACViewChannelExampleKeyPath"; +NSString * const RACViewChannelExampleSetViewValueBlock = @"RACViewChannelExampleSetViewValueBlock"; + +SharedExampleGroupsBegin(RACChannelExamples) + +sharedExamplesFor(RACChannelExamples, ^(NSDictionary *data) { + __block RACChannel * (^getChannel)(void); + __block RACChannel *channel; + + id value1 = @"test value 1"; + id value2 = @"test value 2"; + id value3 = @"test value 3"; + NSArray *values = @[ value1, value2, value3 ]; + + before(^{ + getChannel = data[RACChannelExampleCreateBlock]; + channel = getChannel(); + }); + + it(@"should not send any leadingTerminal value on subscription", ^{ + __block id receivedValue = nil; + + [channel.followingTerminal sendNext:value1]; + [channel.leadingTerminal subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to.beNil(); + + [channel.followingTerminal sendNext:value2]; + expect(receivedValue).to.equal(value2); + }); + + it(@"should send the latest followingTerminal value on subscription", ^{ + __block id receivedValue = nil; + + [channel.leadingTerminal sendNext:value1]; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to.equal(value1); + + [channel.leadingTerminal sendNext:value2]; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to.equal(value2); + }); + + it(@"should send leadingTerminal values as they change", ^{ + NSMutableArray *receivedValues = [NSMutableArray array]; + [channel.leadingTerminal subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + [channel.followingTerminal sendNext:value1]; + [channel.followingTerminal sendNext:value2]; + [channel.followingTerminal sendNext:value3]; + expect(receivedValues).to.equal(values); + }); + + it(@"should send followingTerminal values as they change", ^{ + [channel.leadingTerminal sendNext:value1]; + + NSMutableArray *receivedValues = [NSMutableArray array]; + [channel.followingTerminal subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + [channel.leadingTerminal sendNext:value2]; + [channel.leadingTerminal sendNext:value3]; + expect(receivedValues).to.equal(values); + }); + + it(@"should complete both signals when the leadingTerminal is completed", ^{ + __block BOOL completedLeft = NO; + [channel.leadingTerminal subscribeCompleted:^{ + completedLeft = YES; + }]; + + __block BOOL completedRight = NO; + [channel.followingTerminal subscribeCompleted:^{ + completedRight = YES; + }]; + + [channel.leadingTerminal sendCompleted]; + expect(completedLeft).to.beTruthy(); + expect(completedRight).to.beTruthy(); + }); + + it(@"should complete both signals when the followingTerminal is completed", ^{ + __block BOOL completedLeft = NO; + [channel.leadingTerminal subscribeCompleted:^{ + completedLeft = YES; + }]; + + __block BOOL completedRight = NO; + [channel.followingTerminal subscribeCompleted:^{ + completedRight = YES; + }]; + + [channel.followingTerminal sendCompleted]; + expect(completedLeft).to.beTruthy(); + expect(completedRight).to.beTruthy(); + }); + + it(@"should replay completion to new subscribers", ^{ + [channel.leadingTerminal sendCompleted]; + + __block BOOL completedLeft = NO; + [channel.leadingTerminal subscribeCompleted:^{ + completedLeft = YES; + }]; + + __block BOOL completedRight = NO; + [channel.followingTerminal subscribeCompleted:^{ + completedRight = YES; + }]; + + expect(completedLeft).to.beTruthy(); + expect(completedRight).to.beTruthy(); + }); +}); + +SharedExampleGroupsEnd + +SharedExampleGroupsBegin(RACViewChannelExamples) + +sharedExamplesFor(RACViewChannelExamples, ^(NSDictionary *data) { + __block NSString *keyPath; + __block NSObject * (^getView)(void); + __block RACChannelTerminal * (^getTerminal)(NSObject *); + __block void (^setViewValue)(NSObject *view, NSNumber *value); + + __block NSObject *testView; + __block RACChannelTerminal *endpoint; + + beforeEach(^{ + keyPath = data[RACViewChannelExampleKeyPath]; + getTerminal = data[RACViewChannelExampleCreateTerminalBlock]; + getView = data[RACViewChannelExampleCreateViewBlock]; + setViewValue = data[RACViewChannelExampleSetViewValueBlock]; + + testView = getView(); + endpoint = getTerminal(testView); + }); + + it(@"should not send changes made by the channel itself", ^{ + __block BOOL receivedNext = NO; + [endpoint subscribeNext:^(id x) { + receivedNext = YES; + }]; + + expect(receivedNext).to.beFalsy(); + + [endpoint sendNext:@0.1]; + expect(receivedNext).to.beFalsy(); + + [endpoint sendNext:@0.2]; + expect(receivedNext).to.beFalsy(); + + [endpoint sendCompleted]; + expect(receivedNext).to.beFalsy(); + }); + + it(@"should not send progammatic changes made to the view", ^{ + __block BOOL receivedNext = NO; + [endpoint subscribeNext:^(id x) { + receivedNext = YES; + }]; + + expect(receivedNext).to.beFalsy(); + + [testView setValue:@0.1 forKeyPath:keyPath]; + expect(receivedNext).to.beFalsy(); + + [testView setValue:@0.2 forKeyPath:keyPath]; + expect(receivedNext).to.beFalsy(); + }); + + it(@"should not have a starting value", ^{ + __block BOOL receivedNext = NO; + [endpoint subscribeNext:^(id x) { + receivedNext = YES; + }]; + + expect(receivedNext).to.beFalsy(); + }); + + it(@"should send view changes", ^{ + __block NSString *received; + [endpoint subscribeNext:^(id x) { + received = x; + }]; + + setViewValue(testView, @0.1); + expect(received).to.equal(@0.1); + + setViewValue(testView, @0.2); + expect(received).to.equal(@0.2); + }); + + it(@"should set values on the view", ^{ + [endpoint sendNext:@0.1]; + expect([testView valueForKeyPath:keyPath]).to.equal(@0.1); + + [endpoint sendNext:@0.2]; + expect([testView valueForKeyPath:keyPath]).to.equal(@0.2); + }); + + it(@"should not echo changes back to the channel", ^{ + __block NSUInteger receivedCount = 0; + [endpoint subscribeNext:^(id _) { + receivedCount++; + }]; + + expect(receivedCount).to.equal(0); + + [endpoint sendNext:@0.1]; + expect(receivedCount).to.equal(0); + + setViewValue(testView, @0.2); + expect(receivedCount).to.equal(1); + }); + + it(@"should complete when the view deallocates", ^{ + __block BOOL deallocated = NO; + __block BOOL completed = NO; + + @autoreleasepool { + NSObject *view __attribute__((objc_precise_lifetime)) = getView(); + [view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + RACChannelTerminal *terminal = getTerminal(view); + [terminal subscribeCompleted:^{ + completed = YES; + }]; + + expect(deallocated).to.beFalsy(); + expect(completed).to.beFalsy(); + } + + expect(deallocated).to.beTruthy(); + expect(completed).to.beTruthy(); + }); + + it(@"should deallocate after the view deallocates", ^{ + __block BOOL viewDeallocated = NO; + __block BOOL terminalDeallocated = NO; + + @autoreleasepool { + NSObject *view __attribute__((objc_precise_lifetime)) = getView(); + [view.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + viewDeallocated = YES; + }]]; + + RACChannelTerminal *terminal = getTerminal(view); + [terminal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + terminalDeallocated = YES; + }]]; + + expect(viewDeallocated).to.beFalsy(); + expect(terminalDeallocated).to.beFalsy(); + } + + expect(viewDeallocated).to.beTruthy(); + expect(terminalDeallocated).will.beTruthy(); + }); +}); + +SharedExampleGroupsEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelSpec.m new file mode 100644 index 0000000..7bfe6af --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACChannelSpec.m
@@ -0,0 +1,71 @@ +// +// RACChannelSpec.m +// ReactiveCocoa +// +// Created by Uri Baghin on 30/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACChannelExamples.h" + +#import "NSObject+RACDeallocating.h" +#import "RACChannel.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal.h" + +SpecBegin(RACChannel) + +describe(@"RACChannel", ^{ + itShouldBehaveLike(RACChannelExamples, @{ + RACChannelExampleCreateBlock: [^{ + return [[RACChannel alloc] init]; + } copy] + }); + + describe(@"memory management", ^{ + it(@"should dealloc when its subscribers are disposed", ^{ + RACDisposable *leadingDisposable = nil; + RACDisposable *followingDisposable = nil; + + __block BOOL deallocated = NO; + + @autoreleasepool { + RACChannel *channel __attribute__((objc_precise_lifetime)) = [[RACChannel alloc] init]; + [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + leadingDisposable = [channel.leadingTerminal subscribeCompleted:^{}]; + followingDisposable = [channel.followingTerminal subscribeCompleted:^{}]; + } + + [leadingDisposable dispose]; + [followingDisposable dispose]; + expect(deallocated).will.beTruthy(); + }); + + it(@"should dealloc when its subscriptions are disposed", ^{ + RACDisposable *leadingDisposable = nil; + RACDisposable *followingDisposable = nil; + + __block BOOL deallocated = NO; + + @autoreleasepool { + RACChannel *channel __attribute__((objc_precise_lifetime)) = [[RACChannel alloc] init]; + [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + leadingDisposable = [[RACSignal never] subscribe:channel.leadingTerminal]; + followingDisposable = [[RACSignal never] subscribe:channel.followingTerminal]; + } + + [leadingDisposable dispose]; + [followingDisposable dispose]; + expect(deallocated).will.beTruthy(); + }); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACCommandSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACCommandSpec.m new file mode 100644 index 0000000..b0c810b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACCommandSpec.m
@@ -0,0 +1,526 @@ +// +// RACCommandSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 8/31/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSArray+RACSequenceAdditions.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACEvent.h" +#import "RACScheduler.h" +#import "RACSequence.h" +#import "RACSignal+Operations.h" +#import "RACSubject.h" +#import "RACUnit.h" + +SpecBegin(RACCommand) + +RACSignal * (^emptySignalBlock)(id) = ^(id _) { + return [RACSignal empty]; +}; + +describe(@"with a simple signal block", ^{ + __block RACCommand *command; + + beforeEach(^{ + command = [[RACCommand alloc] initWithSignalBlock:^(id value) { + return [RACSignal return:value]; + }]; + + expect(command).notTo.beNil(); + expect(command.allowsConcurrentExecution).to.beFalsy(); + }); + + it(@"should be enabled by default", ^{ + expect([command.enabled first]).to.equal(@YES); + }); + + it(@"should not be executing by default", ^{ + expect([command.executing first]).to.equal(@NO); + }); + + it(@"should create an execution signal", ^{ + __block NSUInteger signalsReceived = 0; + __block BOOL completed = NO; + + id value = NSNull.null; + [command.executionSignals subscribeNext:^(RACSignal *signal) { + signalsReceived++; + + [signal subscribeNext:^(id x) { + expect(x).to.equal(value); + } completed:^{ + completed = YES; + }]; + }]; + + expect(signalsReceived).to.equal(0); + + [command execute:value]; + expect(signalsReceived).will.equal(1); + expect(completed).to.beTruthy(); + }); + + it(@"should return the execution signal from -execute:", ^{ + __block BOOL completed = NO; + + id value = NSNull.null; + [[command + execute:value] + subscribeNext:^(id x) { + expect(x).to.equal(value); + } completed:^{ + completed = YES; + }]; + + expect(completed).will.beTruthy(); + }); + + it(@"should always send executionSignals on the main thread", ^{ + __block RACScheduler *receivedScheduler = nil; + [command.executionSignals subscribeNext:^(id _) { + receivedScheduler = RACScheduler.currentScheduler; + }]; + + [[RACScheduler scheduler] schedule:^{ + expect([[command execute:nil] waitUntilCompleted:NULL]).to.beTruthy(); + }]; + + expect(receivedScheduler).to.beNil(); + expect(receivedScheduler).will.equal(RACScheduler.mainThreadScheduler); + }); + + it(@"should not send anything on 'errors' by default", ^{ + __block BOOL receivedError = NO; + [command.errors subscribeNext:^(id _) { + receivedError = YES; + }]; + + expect([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); + expect(receivedError).to.beFalsy(); + }); + + it(@"should be executing while an execution signal is running", ^{ + [command.executionSignals subscribeNext:^(RACSignal *signal) { + [signal subscribeNext:^(id x) { + expect([command.executing first]).to.equal(@YES); + }]; + }]; + + expect([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); + expect([command.executing first]).to.equal(@NO); + }); + + it(@"should always update executing on the main thread", ^{ + __block RACScheduler *updatedScheduler = nil; + [[command.executing skip:1] subscribeNext:^(NSNumber *executing) { + if (!executing.boolValue) return; + + updatedScheduler = RACScheduler.currentScheduler; + }]; + + [[RACScheduler scheduler] schedule:^{ + expect([[command execute:nil] waitUntilCompleted:NULL]).to.beTruthy(); + }]; + + expect([command.executing first]).to.equal(@NO); + expect(updatedScheduler).will.equal(RACScheduler.mainThreadScheduler); + }); + + it(@"should dealloc without subscribers", ^{ + __block BOOL disposed = NO; + + @autoreleasepool { + RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock]; + [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + disposed = YES; + }]]; + } + + expect(disposed).will.beTruthy(); + }); + + it(@"should complete signals on the main thread when deallocated", ^{ + __block RACScheduler *executionSignalsScheduler = nil; + __block RACScheduler *executingScheduler = nil; + __block RACScheduler *enabledScheduler = nil; + __block RACScheduler *errorsScheduler = nil; + + [[RACScheduler scheduler] schedule:^{ + @autoreleasepool { + RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock]; + + [command.executionSignals subscribeCompleted:^{ + executionSignalsScheduler = RACScheduler.currentScheduler; + }]; + + [command.executing subscribeCompleted:^{ + executingScheduler = RACScheduler.currentScheduler; + }]; + + [command.enabled subscribeCompleted:^{ + enabledScheduler = RACScheduler.currentScheduler; + }]; + + [command.errors subscribeCompleted:^{ + errorsScheduler = RACScheduler.currentScheduler; + }]; + } + }]; + + expect(executionSignalsScheduler).will.equal(RACScheduler.mainThreadScheduler); + expect(executingScheduler).will.equal(RACScheduler.mainThreadScheduler); + expect(enabledScheduler).will.equal(RACScheduler.mainThreadScheduler); + expect(errorsScheduler).will.equal(RACScheduler.mainThreadScheduler); + }); +}); + +it(@"should invoke the signalBlock once per execution", ^{ + NSMutableArray *valuesReceived = [NSMutableArray array]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id x) { + [valuesReceived addObject:x]; + return [RACSignal empty]; + }]; + + expect([[command execute:@"foo"] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); + expect(valuesReceived).to.equal((@[ @"foo" ])); + + expect([[command execute:@"bar"] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); + expect(valuesReceived).to.equal((@[ @"foo", @"bar" ])); +}); + +it(@"should send on executionSignals in order of execution", ^{ + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSequence *seq) { + return [seq signalWithScheduler:RACScheduler.immediateScheduler]; + }]; + + NSMutableArray *valuesReceived = [NSMutableArray array]; + [[command.executionSignals + concat] + subscribeNext:^(id x) { + [valuesReceived addObject:x]; + }]; + + RACSequence *first = @[ @"foo", @"bar" ].rac_sequence; + expect([[command execute:first] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); + + RACSequence *second = @[ @"buzz", @"baz" ].rac_sequence; + expect([[command execute:second] asynchronouslyWaitUntilCompleted:NULL]).will.beTruthy(); + + NSArray *expectedValues = @[ @"foo", @"bar", @"buzz", @"baz" ]; + expect(valuesReceived).to.equal(expectedValues); +}); + +it(@"should wait for all signals to complete or error before executing sends NO", ^{ + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { + return signal; + }]; + + command.allowsConcurrentExecution = YES; + + RACSubject *firstSubject = [RACSubject subject]; + expect([command execute:firstSubject]).notTo.beNil(); + + RACSubject *secondSubject = [RACSubject subject]; + expect([command execute:secondSubject]).notTo.beNil(); + + expect([command.executing first]).will.equal(@YES); + + [firstSubject sendError:nil]; + expect([command.executing first]).to.equal(@YES); + + [secondSubject sendNext:nil]; + expect([command.executing first]).to.equal(@YES); + + [secondSubject sendCompleted]; + expect([command.executing first]).will.equal(@NO); +}); + +it(@"should not deliver errors from executionSignals", ^{ + RACSubject *subject = [RACSubject subject]; + NSMutableArray *receivedEvents = [NSMutableArray array]; + + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return subject; + }]; + + [[[command.executionSignals + flatten] + materialize] + subscribeNext:^(RACEvent *event) { + [receivedEvents addObject:event]; + }]; + + expect([command execute:nil]).notTo.beNil(); + expect([command.executing first]).will.equal(@YES); + + [subject sendNext:RACUnit.defaultUnit]; + + NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; + expect(receivedEvents).will.equal(expectedEvents); + expect([command.executing first]).to.equal(@YES); + + [subject sendNext:@"foo"]; + + expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; + expect(receivedEvents).will.equal(expectedEvents); + expect([command.executing first]).to.equal(@YES); + + NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + [subject sendError:error]; + + expect([command.executing first]).will.equal(@NO); + expect(receivedEvents).to.equal(expectedEvents); +}); + +it(@"should deliver errors from -execute:", ^{ + RACSubject *subject = [RACSubject subject]; + NSMutableArray *receivedEvents = [NSMutableArray array]; + + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return subject; + }]; + + [[[command + execute:nil] + materialize] + subscribeNext:^(RACEvent *event) { + [receivedEvents addObject:event]; + }]; + + expect([command.executing first]).will.equal(@YES); + + [subject sendNext:RACUnit.defaultUnit]; + + NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; + expect(receivedEvents).will.equal(expectedEvents); + expect([command.executing first]).to.equal(@YES); + + [subject sendNext:@"foo"]; + + expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; + expect(receivedEvents).will.equal(expectedEvents); + expect([command.executing first]).to.equal(@YES); + + NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + [subject sendError:error]; + + expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"], [RACEvent eventWithError:error] ]; + expect(receivedEvents).will.equal(expectedEvents); + expect([command.executing first]).will.equal(@NO); +}); + +it(@"should deliver errors onto 'errors'", ^{ + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { + return signal; + }]; + + command.allowsConcurrentExecution = YES; + + RACSubject *firstSubject = [RACSubject subject]; + expect([command execute:firstSubject]).notTo.beNil(); + + RACSubject *secondSubject = [RACSubject subject]; + expect([command execute:secondSubject]).notTo.beNil(); + + NSError *firstError = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + NSError *secondError = [NSError errorWithDomain:@"" code:2 userInfo:nil]; + + // We should receive errors from our previously-started executions. + NSMutableArray *receivedErrors = [NSMutableArray array]; + [command.errors subscribeNext:^(NSError *error) { + [receivedErrors addObject:error]; + }]; + + expect([command.executing first]).will.equal(@YES); + + [firstSubject sendError:firstError]; + expect([command.executing first]).will.equal(@YES); + + NSArray *expected = @[ firstError ]; + expect(receivedErrors).will.equal(expected); + + [secondSubject sendError:secondError]; + expect([command.executing first]).will.equal(@NO); + + expected = @[ firstError, secondError ]; + expect(receivedErrors).will.equal(expected); +}); + +it(@"should not deliver non-error events onto 'errors'", ^{ + RACSubject *subject = [RACSubject subject]; + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return subject; + }]; + + __block BOOL receivedEvent = NO; + [command.errors subscribeNext:^(id _) { + receivedEvent = YES; + }]; + + expect([command execute:nil]).notTo.beNil(); + expect([command.executing first]).will.equal(@YES); + + [subject sendNext:RACUnit.defaultUnit]; + [subject sendCompleted]; + + expect([command.executing first]).will.equal(@NO); + expect(receivedEvent).to.beFalsy(); +}); + +it(@"should send errors on the main thread", ^{ + RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { + return signal; + }]; + + NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; + + __block RACScheduler *receivedScheduler = nil; + [command.errors subscribeNext:^(NSError *e) { + expect(e).to.equal(error); + receivedScheduler = RACScheduler.currentScheduler; + }]; + + RACSignal *errorSignal = [RACSignal error:error]; + + [[RACScheduler scheduler] schedule:^{ + expect([[command execute:errorSignal] waitUntilCompleted:NULL]).to.beTruthy(); + }]; + + expect(receivedScheduler).to.beNil(); + expect(receivedScheduler).will.equal(RACScheduler.mainThreadScheduler); +}); + +describe(@"enabled signal", ^{ + __block RACSubject *enabledSubject; + __block RACCommand *command; + + beforeEach(^{ + enabledSubject = [RACSubject subject]; + command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id _) { + return [RACSignal return:RACUnit.defaultUnit]; + }]; + }); + + it(@"should send YES by default", ^{ + expect([command.enabled first]).to.equal(@YES); + }); + + it(@"should send whatever the enabledSignal has sent most recently", ^{ + [enabledSubject sendNext:@NO]; + expect([command.enabled first]).will.equal(@NO); + + [enabledSubject sendNext:@YES]; + expect([command.enabled first]).will.equal(@YES); + + [enabledSubject sendNext:@NO]; + expect([command.enabled first]).will.equal(@NO); + }); + + it(@"should sample enabledSignal synchronously at initialization time", ^{ + RACCommand *command = [[RACCommand alloc] initWithEnabled:[RACSignal return:@NO] signalBlock:^(id _) { + return [RACSignal empty]; + }]; + expect([command.enabled first]).to.equal(@NO); + }); + + it(@"should send NO while executing is YES and allowsConcurrentExecution is NO", ^{ + [[command.executionSignals flatten] subscribeNext:^(id _) { + expect([command.executing first]).to.equal(@YES); + expect([command.enabled first]).to.equal(@NO); + }]; + + expect([command.enabled first]).to.equal(@YES); + expect([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); + expect([command.enabled first]).to.equal(@YES); + }); + + it(@"should send YES while executing is YES and allowsConcurrentExecution is YES", ^{ + command.allowsConcurrentExecution = YES; + + __block BOOL outerExecuted = NO; + __block BOOL innerExecuted = NO; + + // Prevent infinite recursion by only responding to the first value. + [[[command.executionSignals + take:1] + flatten] + subscribeNext:^(id _) { + outerExecuted = YES; + + expect([command.executing first]).to.equal(@YES); + expect([command.enabled first]).to.equal(@YES); + + [[command execute:nil] subscribeCompleted:^{ + innerExecuted = YES; + }]; + }]; + + expect([command.enabled first]).to.equal(@YES); + + expect([command execute:nil]).notTo.beNil(); + expect(outerExecuted).will.beTruthy(); + expect(innerExecuted).will.beTruthy(); + + expect([command.enabled first]).to.equal(@YES); + }); + + it(@"should send an error from -execute: when NO", ^{ + [enabledSubject sendNext:@NO]; + + RACSignal *signal = [command execute:nil]; + expect(signal).notTo.beNil(); + + __block BOOL success = NO; + __block NSError *error = nil; + expect([signal firstOrDefault:nil success:&success error:&error]).to.beNil(); + expect(success).to.beFalsy(); + + expect(error).notTo.beNil(); + expect(error.domain).to.equal(RACCommandErrorDomain); + expect(error.code).to.equal(RACCommandErrorNotEnabled); + expect(error.userInfo[RACUnderlyingCommandErrorKey]).to.beIdenticalTo(command); + }); + + it(@"should always update on the main thread", ^{ + __block RACScheduler *updatedScheduler = nil; + [[command.enabled skip:1] subscribeNext:^(id _) { + updatedScheduler = RACScheduler.currentScheduler; + }]; + + [[RACScheduler scheduler] schedule:^{ + [enabledSubject sendNext:@NO]; + }]; + + expect([command.enabled first]).to.equal(@YES); + expect([command.enabled first]).will.equal(@NO); + expect(updatedScheduler).to.equal(RACScheduler.mainThreadScheduler); + }); + + it(@"should complete when the command is deallocated even if the input signal hasn't", ^{ + __block BOOL deallocated = NO; + __block BOOL completed = NO; + + @autoreleasepool { + RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:emptySignalBlock]; + [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + [command.enabled subscribeCompleted:^{ + completed = YES; + }]; + } + + expect(deallocated).will.beTruthy(); + expect(completed).will.beTruthy(); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACCompoundDisposableSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACCompoundDisposableSpec.m new file mode 100644 index 0000000..faad3bb --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACCompoundDisposableSpec.m
@@ -0,0 +1,109 @@ +// +// RACCompoundDisposableSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/30/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACCompoundDisposable.h" + +SpecBegin(RACCompoundDisposable) + +it(@"should dispose of all its contained disposables", ^{ + __block BOOL d1Disposed = NO; + RACDisposable *d1 = [RACDisposable disposableWithBlock:^{ + d1Disposed = YES; + }]; + + __block BOOL d2Disposed = NO; + RACDisposable *d2 = [RACDisposable disposableWithBlock:^{ + d2Disposed = YES; + }]; + + __block BOOL d3Disposed = NO; + RACDisposable *d3 = [RACDisposable disposableWithBlock:^{ + d3Disposed = YES; + }]; + + __block BOOL d4Disposed = NO; + RACDisposable *d4 = [RACDisposable disposableWithBlock:^{ + d4Disposed = YES; + }]; + + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ d1, d2, d3 ]]; + [disposable addDisposable:d4]; + + expect(d1Disposed).to.beFalsy(); + expect(d2Disposed).to.beFalsy(); + expect(d3Disposed).to.beFalsy(); + expect(d4Disposed).to.beFalsy(); + expect(disposable.disposed).to.beFalsy(); + + [disposable dispose]; + + expect(d1Disposed).to.beTruthy(); + expect(d2Disposed).to.beTruthy(); + expect(d3Disposed).to.beTruthy(); + expect(d4Disposed).to.beTruthy(); + expect(disposable.disposed).to.beTruthy(); +}); + +it(@"should dispose of any added disposables immediately if it's already been disposed", ^{ + RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; + [disposable dispose]; + + RACDisposable *d = [[RACDisposable alloc] init]; + + expect(d.disposed).to.beFalsy(); + [disposable addDisposable:d]; + expect(d.disposed).to.beTruthy(); +}); + +it(@"should work when initialized with -init", ^{ + RACCompoundDisposable *disposable = [[RACCompoundDisposable alloc] init]; + + __block BOOL disposed = NO; + RACDisposable *d = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + [disposable addDisposable:d]; + expect(disposed).to.beFalsy(); + + [disposable dispose]; + expect(disposed).to.beTruthy(); +}); + +it(@"should work when initialized with +disposableWithBlock:", ^{ + __block BOOL compoundDisposed = NO; + RACCompoundDisposable *disposable = [RACCompoundDisposable disposableWithBlock:^{ + compoundDisposed = YES; + }]; + + __block BOOL disposed = NO; + RACDisposable *d = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + [disposable addDisposable:d]; + expect(disposed).to.beFalsy(); + expect(compoundDisposed).to.beFalsy(); + + [disposable dispose]; + expect(disposed).to.beTruthy(); + expect(compoundDisposed).to.beTruthy(); +}); + +it(@"should allow disposables to be removed", ^{ + RACCompoundDisposable *disposable = [[RACCompoundDisposable alloc] init]; + RACDisposable *d = [[RACDisposable alloc] init]; + + [disposable addDisposable:d]; + [disposable removeDisposable:d]; + + [disposable dispose]; + expect(d.disposed).to.beFalsy(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACControlCommandExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACControlCommandExamples.h new file mode 100644 index 0000000..3fbaa34 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACControlCommandExamples.h
@@ -0,0 +1,18 @@ +// +// RACControlCommandExamples.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-08-15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +// The name of the shared examples for any control class that has +// `rac_command` and `isEnabled` properties. +extern NSString * const RACControlCommandExamples; + +// The control to test. +extern NSString * const RACControlCommandExampleControl; + +// A block of type `void (^)(id control)` which should activate the +// `rac_command` of the `control` by manipulating the control itself. +extern NSString * const RACControlCommandExampleActivateBlock;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACControlCommandExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACControlCommandExamples.m new file mode 100644 index 0000000..034cc81 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACControlCommandExamples.m
@@ -0,0 +1,81 @@ +// +// RACControlCommandExamples.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-08-15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACControlCommandExamples.h" + +#import "RACCommand.h" +#import "RACSubject.h" +#import "RACUnit.h" + +NSString * const RACControlCommandExamples = @"RACControlCommandExamples"; +NSString * const RACControlCommandExampleControl = @"RACControlCommandExampleControl"; +NSString * const RACControlCommandExampleActivateBlock = @"RACControlCommandExampleActivateBlock"; + +// Methods used by the unit test that would otherwise require platform-specific +// imports. +@interface NSObject (RACControlCommandExamples) + +@property (nonatomic, strong) RACCommand *rac_command; + +- (BOOL)isEnabled; + +@end + +SharedExampleGroupsBegin(RACControlCommandExamples) + +sharedExamplesFor(RACControlCommandExamples, ^(NSDictionary *data) { + __block id control; + __block void (^activate)(id); + + __block RACSubject *enabledSubject; + __block RACCommand *command; + + beforeEach(^{ + control = data[RACControlCommandExampleControl]; + activate = [data[RACControlCommandExampleActivateBlock] copy]; + + enabledSubject = [RACSubject subject]; + command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id sender) { + return [RACSignal return:sender]; + }]; + + [control setRac_command:command]; + }); + + it(@"should bind the control's enabledness to the command", ^{ + expect([control isEnabled]).will.beTruthy(); + + [enabledSubject sendNext:@NO]; + expect([control isEnabled]).will.beFalsy(); + + [enabledSubject sendNext:@YES]; + expect([control isEnabled]).will.beTruthy(); + }); + + it(@"should execute the control's command when activated", ^{ + __block BOOL executed = NO; + [[command.executionSignals flatten] subscribeNext:^(id sender) { + expect(sender).to.equal(control); + executed = YES; + }]; + + activate(control); + expect(executed).will.beTruthy(); + }); + + it(@"should overwrite an existing command when setting a new one", ^{ + RACCommand *secondCommand = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return [RACSignal return:RACUnit.defaultUnit]; + }]; + + [control setRac_command:secondCommand]; + expect([control rac_command]).to.beIdenticalTo(secondCommand); + }); +}); + +SharedExampleGroupsEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACDelegateProxySpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACDelegateProxySpec.m new file mode 100644 index 0000000..831c3a3 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACDelegateProxySpec.m
@@ -0,0 +1,89 @@ +// +// RACDelegateProxySpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACSelectorSignal.h" +#import "RACDelegateProxy.h" +#import "RACSignal.h" +#import "RACTuple.h" +#import "RACCompoundDisposable.h" +#import "NSObject+RACDeallocating.h" + +@protocol TestDelegateProtocol +- (NSUInteger)lengthOfString:(NSString *)str; +@end + +@interface TestDelegate : NSObject <TestDelegateProtocol> +@property (nonatomic, assign) BOOL lengthOfStringInvoked; +@end + +SpecBegin(RACDelegateProxy) + +__block id proxy; +__block TestDelegate *delegate; +__block Protocol *protocol; + +beforeEach(^{ + protocol = @protocol(TestDelegateProtocol); + expect(protocol).notTo.beNil(); + + proxy = [[RACDelegateProxy alloc] initWithProtocol:protocol]; + expect(proxy).notTo.beNil(); + expect([proxy rac_proxiedDelegate]).to.beNil(); + + delegate = [[TestDelegate alloc] init]; + expect(delegate).notTo.beNil(); +}); + +it(@"should not respond to selectors at first", ^{ + expect([proxy respondsToSelector:@selector(lengthOfString:)]).to.beFalsy(); +}); + +it(@"should send on a signal for a protocol method", ^{ + __block RACTuple *tuple; + [[proxy signalForSelector:@selector(lengthOfString:)] subscribeNext:^(RACTuple *t) { + tuple = t; + }]; + + expect([proxy respondsToSelector:@selector(lengthOfString:)]).to.beTruthy(); + expect([proxy lengthOfString:@"foo"]).to.equal(0); + expect(tuple).to.equal(RACTuplePack(@"foo")); +}); + +it(@"should forward to the proxied delegate", ^{ + [proxy setRac_proxiedDelegate:delegate]; + + expect([proxy respondsToSelector:@selector(lengthOfString:)]).to.beTruthy(); + expect([proxy lengthOfString:@"foo"]).to.equal(3); + expect(delegate.lengthOfStringInvoked).to.beTruthy(); +}); + +it(@"should not send to the delegate when signals are applied", ^{ + [proxy setRac_proxiedDelegate:delegate]; + + __block RACTuple *tuple; + [[proxy signalForSelector:@selector(lengthOfString:)] subscribeNext:^(RACTuple *t) { + tuple = t; + }]; + + expect([proxy respondsToSelector:@selector(lengthOfString:)]).to.beTruthy(); + expect([proxy lengthOfString:@"foo"]).to.equal(0); + + expect(tuple).to.equal(RACTuplePack(@"foo")); + expect(delegate.lengthOfStringInvoked).to.beFalsy(); +}); + +SpecEnd + +@implementation TestDelegate + +- (NSUInteger)lengthOfString:(NSString *)str { + self.lengthOfStringInvoked = YES; + return str.length; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACDisposableSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACDisposableSpec.m new file mode 100644 index 0000000..8cde917 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACDisposableSpec.m
@@ -0,0 +1,73 @@ +// +// RACDisposableSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACDisposable.h" +#import "RACScopedDisposable.h" + +SpecBegin(RACDisposable) + +it(@"should initialize without a block", ^{ + RACDisposable *disposable = [[RACDisposable alloc] init]; + expect(disposable).notTo.beNil(); + expect(disposable.disposed).to.beFalsy(); + + [disposable dispose]; + expect(disposable.disposed).to.beTruthy(); +}); + +it(@"should execute a block upon disposal", ^{ + __block BOOL disposed = NO; + RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + expect(disposable).notTo.beNil(); + expect(disposed).to.beFalsy(); + expect(disposable.disposed).to.beFalsy(); + + [disposable dispose]; + expect(disposed).to.beTruthy(); + expect(disposable.disposed).to.beTruthy(); +}); + +it(@"should not dispose upon deallocation", ^{ + __block BOOL disposed = NO; + __weak RACDisposable *weakDisposable = nil; + + @autoreleasepool { + RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + weakDisposable = disposable; + expect(weakDisposable).notTo.beNil(); + } + + expect(weakDisposable).to.beNil(); + expect(disposed).to.beFalsy(); +}); + +it(@"should create a scoped disposable", ^{ + __block BOOL disposed = NO; + __weak RACScopedDisposable *weakDisposable = nil; + + @autoreleasepool { + RACScopedDisposable *disposable __attribute__((objc_precise_lifetime)) = [RACScopedDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + weakDisposable = disposable; + expect(weakDisposable).notTo.beNil(); + expect(disposed).to.beFalsy(); + } + + expect(weakDisposable).to.beNil(); + expect(disposed).to.beTruthy(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACEventSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACEventSpec.m new file mode 100644 index 0000000..9545fb6 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACEventSpec.m
@@ -0,0 +1,80 @@ +// +// RACEventSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-01-07. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACEvent.h" + +SpecBegin(RACEvent) + +it(@"should return the singleton completed event", ^{ + RACEvent *event = RACEvent.completedEvent; + expect(event).notTo.beNil(); + + expect(event).to.beIdenticalTo(RACEvent.completedEvent); + expect([event copy]).to.beIdenticalTo(event); + + expect(event.eventType).to.equal(RACEventTypeCompleted); + expect(event.finished).to.beTruthy(); + expect(event.error).to.beNil(); + expect(event.value).to.beNil(); +}); + +it(@"should return an error event", ^{ + NSError *error = [NSError errorWithDomain:@"foo" code:1 userInfo:nil]; + RACEvent *event = [RACEvent eventWithError:error]; + expect(event).notTo.beNil(); + + expect(event).to.equal([RACEvent eventWithError:error]); + expect([event copy]).to.equal(event); + + expect(event.eventType).to.equal(RACEventTypeError); + expect(event.finished).to.beTruthy(); + expect(event.error).to.equal(error); + expect(event.value).to.beNil(); +}); + +it(@"should return an error event with a nil error", ^{ + RACEvent *event = [RACEvent eventWithError:nil]; + expect(event).notTo.beNil(); + + expect(event).to.equal([RACEvent eventWithError:nil]); + expect([event copy]).to.equal(event); + + expect(event.eventType).to.equal(RACEventTypeError); + expect(event.finished).to.beTruthy(); + expect(event.error).to.beNil(); + expect(event.value).to.beNil(); +}); + +it(@"should return a next event", ^{ + NSString *value = @"foo"; + RACEvent *event = [RACEvent eventWithValue:value]; + expect(event).notTo.beNil(); + + expect(event).to.equal([RACEvent eventWithValue:value]); + expect([event copy]).to.equal(event); + + expect(event.eventType).to.equal(RACEventTypeNext); + expect(event.finished).to.beFalsy(); + expect(event.error).to.beNil(); + expect(event.value).to.equal(value); +}); + +it(@"should return a next event with a nil value", ^{ + RACEvent *event = [RACEvent eventWithValue:nil]; + expect(event).notTo.beNil(); + + expect(event).to.equal([RACEvent eventWithValue:nil]); + expect([event copy]).to.equal(event); + + expect(event.eventType).to.equal(RACEventTypeNext); + expect(event.finished).to.beFalsy(); + expect(event.error).to.beNil(); + expect(event.value).to.beNil(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACKVOChannelSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACKVOChannelSpec.m new file mode 100644 index 0000000..8007511 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACKVOChannelSpec.m
@@ -0,0 +1,389 @@ +// +// RACKVOChannelSpec.m +// ReactiveCocoa +// +// Created by Uri Baghin on 16/12/2012. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" +#import "RACChannelExamples.h" +#import "RACPropertySignalExamples.h" + +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACKVOWrapper.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACKVOChannel.h" +#import "RACSignal+Operations.h" + +SpecBegin(RACKVOChannel) + +describe(@"RACKVOChannel", ^{ + __block RACTestObject *object; + __block RACKVOChannel *channel; + id value1 = @"test value 1"; + id value2 = @"test value 2"; + id value3 = @"test value 3"; + NSArray *values = @[ value1, value2, value3 ]; + + before(^{ + object = [[RACTestObject alloc] init]; + channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil]; + }); + + id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { + RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:testObject keyPath:keyPath nilValue:nilValue]; + [signal subscribe:channel.followingTerminal]; + }; + + itShouldBehaveLike(RACPropertySignalExamples, ^{ + return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; + }); + + itShouldBehaveLike(RACChannelExamples, @{ + RACChannelExampleCreateBlock: [^{ + return [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil]; + } copy] + }); + + it(@"should send the object's current value when subscribed to followingTerminal", ^{ + __block id receivedValue = @"received value should not be this"; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to.beNil(); + + object.stringValue = value1; + [[channel.followingTerminal take:1] subscribeNext:^(id x) { + receivedValue = x; + }]; + + expect(receivedValue).to.equal(value1); + }); + + it(@"should send the object's new value on followingTerminal when it's changed", ^{ + object.stringValue = value1; + + NSMutableArray *receivedValues = [NSMutableArray array]; + [channel.followingTerminal subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + object.stringValue = value2; + object.stringValue = value3; + expect(receivedValues).to.equal(values); + }); + + it(@"should set the object's value using values sent to the followingTerminal", ^{ + expect(object.stringValue).to.beNil(); + + [channel.followingTerminal sendNext:value1]; + expect(object.stringValue).to.equal(value1); + + [channel.followingTerminal sendNext:value2]; + expect(object.stringValue).to.equal(value2); + }); + + it(@"should be able to subscribe to signals", ^{ + NSMutableArray *receivedValues = [NSMutableArray array]; + [object rac_observeKeyPath:@keypath(object.stringValue) options:0 observer:self block:^(id value, NSDictionary *change) { + [receivedValues addObject:value]; + }]; + + RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:value1]; + [subscriber sendNext:value2]; + [subscriber sendNext:value3]; + return nil; + }]; + + [signal subscribe:channel.followingTerminal]; + expect(receivedValues).to.equal(values); + }); + + it(@"should complete both terminals when the target deallocates", ^{ + __block BOOL leadingCompleted = NO; + __block BOOL followingCompleted = NO; + __block BOOL deallocated = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil]; + [channel.leadingTerminal subscribeCompleted:^{ + leadingCompleted = YES; + }]; + + [channel.followingTerminal subscribeCompleted:^{ + followingCompleted = YES; + }]; + + expect(deallocated).to.beFalsy(); + expect(leadingCompleted).to.beFalsy(); + expect(followingCompleted).to.beFalsy(); + } + + expect(deallocated).to.beTruthy(); + expect(leadingCompleted).to.beTruthy(); + expect(followingCompleted).to.beTruthy(); + }); + + it(@"should deallocate when the target deallocates", ^{ + __block BOOL targetDeallocated = NO; + __block BOOL channelDeallocated = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + targetDeallocated = YES; + }]]; + + RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil]; + [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + channelDeallocated = YES; + }]]; + + expect(targetDeallocated).to.beFalsy(); + expect(channelDeallocated).to.beFalsy(); + } + + expect(targetDeallocated).to.beTruthy(); + expect(channelDeallocated).to.beTruthy(); + }); +}); + +describe(@"RACChannelTo", ^{ + __block RACTestObject *a; + __block RACTestObject *b; + __block RACTestObject *c; + __block NSString *testName1; + __block NSString *testName2; + __block NSString *testName3; + + before(^{ + a = [[RACTestObject alloc] init]; + b = [[RACTestObject alloc] init]; + c = [[RACTestObject alloc] init]; + testName1 = @"sync it!"; + testName2 = @"sync it again!"; + testName3 = @"sync it once more!"; + }); + + it(@"should keep objects' properties in sync", ^{ + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + expect(a.stringValue).to.beNil(); + expect(b.stringValue).to.beNil(); + + a.stringValue = testName1; + expect(a.stringValue).to.equal(testName1); + expect(b.stringValue).to.equal(testName1); + + b.stringValue = testName2; + expect(a.stringValue).to.equal(testName2); + expect(b.stringValue).to.equal(testName2); + + a.stringValue = nil; + expect(a.stringValue).to.beNil(); + expect(b.stringValue).to.beNil(); + }); + + it(@"should keep properties identified by keypaths in sync", ^{ + RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + a.strongTestObjectValue = [[RACTestObject alloc] init]; + b.strongTestObjectValue = [[RACTestObject alloc] init]; + + a.strongTestObjectValue.stringValue = testName1; + expect(a.strongTestObjectValue.stringValue).to.equal(testName1); + expect(b.strongTestObjectValue.stringValue).to.equal(testName1); + expect(a.strongTestObjectValue).notTo.equal(b.strongTestObjectValue); + + b.strongTestObjectValue = nil; + expect(a.strongTestObjectValue.stringValue).to.beNil(); + + c.stringValue = testName2; + b.strongTestObjectValue = c; + expect(a.strongTestObjectValue.stringValue).to.equal(testName2); + expect(b.strongTestObjectValue.stringValue).to.equal(testName2); + expect(a.strongTestObjectValue).notTo.equal(b.strongTestObjectValue); + }); + + it(@"should update properties identified by keypaths when the intermediate values change", ^{ + RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + a.strongTestObjectValue = [[RACTestObject alloc] init]; + b.strongTestObjectValue = [[RACTestObject alloc] init]; + c.stringValue = testName1; + b.strongTestObjectValue = c; + + expect(a.strongTestObjectValue.stringValue).to.equal(testName1); + expect(a.strongTestObjectValue).notTo.equal(b.strongTestObjectValue); + }); + + it(@"should update properties identified by keypaths when the channel was created when one of the two objects had an intermediate nil value", ^{ + RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + b.strongTestObjectValue = [[RACTestObject alloc] init]; + c.stringValue = testName1; + a.strongTestObjectValue = c; + + expect(a.strongTestObjectValue.stringValue).to.equal(testName1); + expect(b.strongTestObjectValue.stringValue).to.equal(testName1); + expect(a.strongTestObjectValue).notTo.equal(b.strongTestObjectValue); + }); + + it(@"should take the value of the object being bound to at the start", ^{ + a.stringValue = testName1; + b.stringValue = testName2; + + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + expect(a.stringValue).to.equal(testName2); + expect(b.stringValue).to.equal(testName2); + }); + + it(@"should update the value even if it's the same value the object had before it was bound", ^{ + a.stringValue = testName1; + b.stringValue = testName2; + + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + expect(a.stringValue).to.equal(testName2); + expect(b.stringValue).to.equal(testName2); + + b.stringValue = testName1; + expect(a.stringValue).to.equal(testName1); + expect(b.stringValue).to.equal(testName1); + }); + + it(@"should bind transitively", ^{ + a.stringValue = testName1; + b.stringValue = testName2; + c.stringValue = testName3; + + RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue); + RACChannelTo(b, stringValue) = RACChannelTo(c, stringValue); + expect(a.stringValue).to.equal(testName3); + expect(b.stringValue).to.equal(testName3); + expect(c.stringValue).to.equal(testName3); + + c.stringValue = testName1; + expect(a.stringValue).to.equal(testName1); + expect(b.stringValue).to.equal(testName1); + expect(c.stringValue).to.equal(testName1); + + b.stringValue = testName2; + expect(a.stringValue).to.equal(testName2); + expect(b.stringValue).to.equal(testName2); + expect(c.stringValue).to.equal(testName2); + + a.stringValue = testName3; + expect(a.stringValue).to.equal(testName3); + expect(b.stringValue).to.equal(testName3); + expect(c.stringValue).to.equal(testName3); + }); + + it(@"should bind changes made by KVC on arrays", ^{ + b.arrayValue = @[]; + RACChannelTo(a, arrayValue) = RACChannelTo(b, arrayValue); + + [[b mutableArrayValueForKeyPath:@keypath(b.arrayValue)] addObject:@1]; + expect(a.arrayValue).to.equal(b.arrayValue); + }); + + it(@"should bind changes made by KVC on sets", ^{ + b.setValue = [NSSet set]; + RACChannelTo(a, setValue) = RACChannelTo(b, setValue); + + [[b mutableSetValueForKeyPath:@keypath(b.setValue)] addObject:@1]; + expect(a.setValue).to.equal(b.setValue); + }); + + it(@"should bind changes made by KVC on ordered sets", ^{ + b.orderedSetValue = [NSOrderedSet orderedSet]; + RACChannelTo(a, orderedSetValue) = RACChannelTo(b, orderedSetValue); + + [[b mutableOrderedSetValueForKeyPath:@keypath(b.orderedSetValue)] addObject:@1]; + expect(a.orderedSetValue).to.equal(b.orderedSetValue); + }); + + it(@"should handle deallocation of intermediate objects correctly even without support from KVO", ^{ + __block BOOL wasDisposed = NO; + + RACChannelTo(a, weakTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue); + b.strongTestObjectValue = [[RACTestObject alloc] init]; + + @autoreleasepool { + RACTestObject *object = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + wasDisposed = YES; + }]]; + + a.weakTestObjectValue = object; + object.stringValue = testName1; + + expect(wasDisposed).to.beFalsy(); + expect(b.strongTestObjectValue.stringValue).to.equal(testName1); + } + + expect(wasDisposed).will.beTruthy(); + expect(b.strongTestObjectValue.stringValue).to.beNil(); + }); + + it(@"should stop binding when disposed", ^{ + RACChannelTerminal *aTerminal = RACChannelTo(a, stringValue); + RACChannelTerminal *bTerminal = RACChannelTo(b, stringValue); + + a.stringValue = testName1; + RACDisposable *disposable = [aTerminal subscribe:bTerminal]; + + expect(a.stringValue).to.equal(testName1); + expect(b.stringValue).to.equal(testName1); + + a.stringValue = testName2; + expect(a.stringValue).to.equal(testName2); + expect(b.stringValue).to.equal(testName2); + + [disposable dispose]; + + a.stringValue = testName3; + expect(a.stringValue).to.equal(testName3); + expect(b.stringValue).to.equal(testName2); + }); + + it(@"should use the nilValue when sent nil", ^{ + RACChannelTerminal *terminal = RACChannelTo(a, integerValue, @5); + expect(a.integerValue).to.equal(0); + + [terminal sendNext:@2]; + expect(a.integerValue).to.equal(2); + + [terminal sendNext:nil]; + expect(a.integerValue).to.equal(5); + }); + + it(@"should use the nilValue when an intermediate object is nil", ^{ + __block BOOL wasDisposed = NO; + + RACChannelTo(a, weakTestObjectValue.integerValue, @5) = RACChannelTo(b, strongTestObjectValue.integerValue, @5); + b.strongTestObjectValue = [[RACTestObject alloc] init]; + + @autoreleasepool { + RACTestObject *object = [[RACTestObject alloc] init]; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + wasDisposed = YES; + }]]; + + a.weakTestObjectValue = object; + object.integerValue = 2; + + expect(wasDisposed).to.beFalsy(); + expect(b.strongTestObjectValue.integerValue).to.equal(2); + } + + expect(wasDisposed).will.beTruthy(); + expect(b.strongTestObjectValue.integerValue).to.equal(5); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACKVOWrapperSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACKVOWrapperSpec.m new file mode 100644 index 0000000..dc207ef --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACKVOWrapperSpec.m
@@ -0,0 +1,656 @@ +// +// RACKVOWrapperSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-08-07. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "NSObject+RACKVOWrapper.h" + +#import "EXTKeyPathCoding.h" +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACKVOTrampoline.h" +#import "RACTestObject.h" + +@interface RACTestOperation : NSOperation +@end + +// The name of the examples. +static NSString * const RACKVOWrapperExamples = @"RACKVOWrapperExamples"; + +// A block that returns an object to observe in the examples. +static NSString * const RACKVOWrapperExamplesTargetBlock = @"RACKVOWrapperExamplesTargetBlock"; + +// The key path to observe in the examples. +// +// The key path must have at least one weak property in it. +static NSString * const RACKVOWrapperExamplesKeyPath = @"RACKVOWrapperExamplesKeyPath"; + +// A block that changes the value of a weak property in the observed key path. +// The block is passed the object the example is observing and the new value the +// weak property should be changed to. +static NSString * const RACKVOWrapperExamplesChangeBlock = @"RACKVOWrapperExamplesChangeBlock"; + +// A block that returns a valid value for the weak property changed by +// RACKVOWrapperExamplesChangeBlock. The value must deallocate +// normally. +static NSString * const RACKVOWrapperExamplesValueBlock = @"RACKVOWrapperExamplesValueBlock"; + +// Whether RACKVOWrapperExamplesChangeBlock changes the value +// of the last key path component in the key path directly. +static NSString * const RACKVOWrapperExamplesChangesValueDirectly = @"RACKVOWrapperExamplesChangesValueDirectly"; + +// The name of the examples. +static NSString * const RACKVOWrapperCollectionExamples = @"RACKVOWrapperCollectionExamples"; + +// A block that returns an object to observe in the examples. +static NSString * const RACKVOWrapperCollectionExamplesTargetBlock = @"RACKVOWrapperCollectionExamplesTargetBlock"; + +// The key path to observe in the examples. +// +// Must identify a property of type NSOrderedSet. +static NSString * const RACKVOWrapperCollectionExamplesKeyPath = @"RACKVOWrapperCollectionExamplesKeyPath"; + +SharedExampleGroupsBegin(RACKVOWrapperExamples) + +sharedExamplesFor(RACKVOWrapperExamples, ^(NSDictionary *data) { + __block NSObject *target = nil; + __block NSString *keyPath = nil; + __block void (^changeBlock)(NSObject *, id) = nil; + __block id (^valueBlock)(void) = nil; + __block BOOL changesValueDirectly = NO; + + __block NSUInteger priorCallCount = 0; + __block NSUInteger posteriorCallCount = 0; + __block BOOL priorTriggeredByLastKeyPathComponent = NO; + __block BOOL posteriorTriggeredByLastKeyPathComponent = NO; + __block BOOL posteriorTriggeredByDeallocation = NO; + __block void (^callbackBlock)(id, NSDictionary *) = nil; + + beforeEach(^{ + NSObject * (^targetBlock)(void) = data[RACKVOWrapperExamplesTargetBlock]; + target = targetBlock(); + keyPath = data[RACKVOWrapperExamplesKeyPath]; + changeBlock = data[RACKVOWrapperExamplesChangeBlock]; + valueBlock = data[RACKVOWrapperExamplesValueBlock]; + changesValueDirectly = [data[RACKVOWrapperExamplesChangesValueDirectly] boolValue]; + + priorCallCount = 0; + posteriorCallCount = 0; + + callbackBlock = [^(id value, NSDictionary *change) { + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + priorTriggeredByLastKeyPathComponent = [change[RACKeyValueChangeAffectedOnlyLastComponentKey] boolValue]; + ++priorCallCount; + return; + } + posteriorTriggeredByLastKeyPathComponent = [change[RACKeyValueChangeAffectedOnlyLastComponentKey] boolValue]; + posteriorTriggeredByDeallocation = [change[RACKeyValueChangeCausedByDeallocationKey] boolValue]; + ++posteriorCallCount; + } copy]; + }); + + afterEach(^{ + target = nil; + keyPath = nil; + changeBlock = nil; + valueBlock = nil; + changesValueDirectly = NO; + + callbackBlock = nil; + }); + + it(@"should not call the callback block on add if called without NSKeyValueObservingOptionInitial", ^{ + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + expect(priorCallCount).to.equal(0); + expect(posteriorCallCount).to.equal(0); + }); + + it(@"should call the callback block on add if called with NSKeyValueObservingOptionInitial", ^{ + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionInitial observer:nil block:callbackBlock]; + expect(priorCallCount).to.equal(0); + expect(posteriorCallCount).to.equal(1); + }); + + it(@"should call the callback block twice per change, once prior and once posterior", ^{ + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + priorCallCount = 0; + posteriorCallCount = 0; + + id value1 = valueBlock(); + changeBlock(target, value1); + expect(priorCallCount).to.equal(1); + expect(posteriorCallCount).to.equal(1); + expect(priorTriggeredByLastKeyPathComponent).to.equal(changesValueDirectly); + expect(posteriorTriggeredByLastKeyPathComponent).to.equal(changesValueDirectly); + expect(posteriorTriggeredByDeallocation).to.beFalsy(); + + id value2 = valueBlock(); + changeBlock(target, value2); + expect(priorCallCount).to.equal(2); + expect(posteriorCallCount).to.equal(2); + expect(priorTriggeredByLastKeyPathComponent).to.equal(changesValueDirectly); + expect(posteriorTriggeredByLastKeyPathComponent).to.equal(changesValueDirectly); + expect(posteriorTriggeredByDeallocation).to.beFalsy(); + }); + + it(@"should call the callback block with NSKeyValueChangeNotificationIsPriorKey set before the value is changed, and not set after the value is changed", ^{ + __block BOOL priorCalled = NO; + __block BOOL posteriorCalled = NO; + __block id priorValue = nil; + __block id posteriorValue = nil; + + id value1 = valueBlock(); + changeBlock(target, value1); + id oldValue = [target valueForKeyPath:keyPath]; + + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:^(id value, NSDictionary *change) { + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + priorCalled = YES; + priorValue = value; + expect(posteriorCalled).to.beFalsy(); + return; + } + posteriorCalled = YES; + posteriorValue = value; + expect(priorCalled).to.beTruthy(); + }]; + + id value2 = valueBlock(); + changeBlock(target, value2); + id newValue = [target valueForKeyPath:keyPath]; + expect(priorCalled).to.beTruthy(); + expect(priorValue).to.equal(oldValue); + expect(posteriorCalled).to.beTruthy(); + expect(posteriorValue).to.equal(newValue); + }); + + it(@"should not call the callback block after it's been disposed", ^{ + RACDisposable *disposable = [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + priorCallCount = 0; + posteriorCallCount = 0; + + [disposable dispose]; + expect(priorCallCount).to.equal(0); + expect(posteriorCallCount).to.equal(0); + + id value = valueBlock(); + changeBlock(target, value); + expect(priorCallCount).to.equal(0); + expect(posteriorCallCount).to.equal(0); + }); + + it(@"should call the callback block only once with NSKeyValueChangeNotificationIsPriorKey not set when the value is deallocated", ^{ + __block BOOL valueDidDealloc = NO; + + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + + @autoreleasepool { + NSObject *value __attribute__((objc_precise_lifetime)) = valueBlock(); + [value.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + valueDidDealloc = YES; + }]]; + + changeBlock(target, value); + priorCallCount = 0; + posteriorCallCount = 0; + } + + expect(valueDidDealloc).to.beTruthy(); + expect(priorCallCount).to.equal(0); + expect(posteriorCallCount).to.equal(1); + expect(posteriorTriggeredByDeallocation).to.beTruthy(); + }); +}); + +sharedExamplesFor(RACKVOWrapperCollectionExamples, ^(NSDictionary *data) { + __block NSObject *target = nil; + __block NSString *keyPath = nil; + __block NSMutableOrderedSet *mutableKeyPathProxy = nil; + __block void (^callbackBlock)(id, NSDictionary *) = nil; + + __block id priorValue = nil; + __block id posteriorValue = nil; + __block NSDictionary *priorChange = nil; + __block NSDictionary *posteriorChange = nil; + + beforeEach(^{ + NSObject * (^targetBlock)(void) = data[RACKVOWrapperCollectionExamplesTargetBlock]; + target = targetBlock(); + keyPath = data[RACKVOWrapperCollectionExamplesKeyPath]; + + callbackBlock = [^(id value, NSDictionary *change) { + if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { + priorValue = value; + priorChange = change; + return; + } + posteriorValue = value; + posteriorChange = change; + } copy]; + + [target setValue:[NSOrderedSet orderedSetWithObject:@0] forKeyPath:keyPath]; + [target rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior observer:nil block:callbackBlock]; + mutableKeyPathProxy = [target mutableOrderedSetValueForKeyPath:keyPath]; + }); + + afterEach(^{ + target = nil; + keyPath = nil; + callbackBlock = nil; + + priorValue = nil; + priorChange = nil; + posteriorValue = nil; + posteriorChange = nil; + }); + + it(@"should support inserting elements into ordered collections", ^{ + [mutableKeyPathProxy insertObject:@1 atIndex:0]; + + expect(priorValue).to.equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]); + expect(posteriorValue).to.equal([NSOrderedSet orderedSetWithArray:(@[ @1, @0 ])]); + expect(priorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeInsertion); + expect(posteriorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeInsertion); + expect(priorChange[NSKeyValueChangeOldKey]).to.beNil(); + expect(posteriorChange[NSKeyValueChangeNewKey]).to.equal(@[ @1 ]); + expect(priorChange[NSKeyValueChangeIndexesKey]).to.equal([NSIndexSet indexSetWithIndex:0]); + expect(posteriorChange[NSKeyValueChangeIndexesKey]).to.equal([NSIndexSet indexSetWithIndex:0]); + }); + + it(@"should support removing elements from ordered collections", ^{ + [mutableKeyPathProxy removeObjectAtIndex:0]; + + expect(priorValue).to.equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]); + expect(posteriorValue).to.equal([NSOrderedSet orderedSetWithArray:@[]]); + expect(priorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeRemoval); + expect(posteriorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeRemoval); + expect(priorChange[NSKeyValueChangeOldKey]).to.equal(@[ @0 ]); + expect(posteriorChange[NSKeyValueChangeNewKey]).to.beNil(); + expect(priorChange[NSKeyValueChangeIndexesKey]).to.equal([NSIndexSet indexSetWithIndex:0]); + expect(posteriorChange[NSKeyValueChangeIndexesKey]).to.equal([NSIndexSet indexSetWithIndex:0]); + }); + + it(@"should support replacing elements in ordered collections", ^{ + [mutableKeyPathProxy replaceObjectAtIndex:0 withObject:@1]; + + expect(priorValue).to.equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]); + expect(posteriorValue).to.equal([NSOrderedSet orderedSetWithArray:@[ @1 ]]); + expect(priorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeReplacement); + expect(posteriorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeReplacement); + expect(priorChange[NSKeyValueChangeOldKey]).to.equal(@[ @0 ]); + expect(posteriorChange[NSKeyValueChangeNewKey]).to.equal(@[ @1 ]); + expect(priorChange[NSKeyValueChangeIndexesKey]).to.equal([NSIndexSet indexSetWithIndex:0]); + expect(posteriorChange[NSKeyValueChangeIndexesKey]).to.equal([NSIndexSet indexSetWithIndex:0]); + }); + + it(@"should support adding elements to unordered collections", ^{ + [mutableKeyPathProxy unionOrderedSet:[NSOrderedSet orderedSetWithObject:@1]]; + + expect(priorValue).to.equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]); + expect(posteriorValue).to.equal([NSOrderedSet orderedSetWithArray:(@[ @0, @1 ])]); + expect(priorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeInsertion); + expect(posteriorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeInsertion); + expect(priorChange[NSKeyValueChangeOldKey]).to.beNil(); + expect(posteriorChange[NSKeyValueChangeNewKey]).to.equal(@[ @1 ]); + }); + + it(@"should support removing elements from unordered collections", ^{ + [mutableKeyPathProxy minusOrderedSet:[NSOrderedSet orderedSetWithObject:@0]]; + + expect(priorValue).to.equal([NSOrderedSet orderedSetWithArray:@[ @0 ]]); + expect(posteriorValue).to.equal([NSOrderedSet orderedSetWithArray:@[]]); + expect(priorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeRemoval); + expect(posteriorChange[NSKeyValueChangeKindKey]).to.equal(NSKeyValueChangeRemoval); + expect(priorChange[NSKeyValueChangeOldKey]).to.equal(@[ @0 ]); + expect(posteriorChange[NSKeyValueChangeNewKey]).to.beNil(); + }); +}); + +SharedExampleGroupsEnd + +SpecBegin(RACKVOWrapper) + +describe(@"-rac_observeKeyPath:options:observer:block:", ^{ + describe(@"on simple keys", ^{ + NSObject * (^targetBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { + target.weakTestObjectValue = value; + }; + + id (^valueBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + itShouldBehaveLike(RACKVOWrapperExamples, @{ + RACKVOWrapperExamplesTargetBlock: targetBlock, + RACKVOWrapperExamplesKeyPath: @keypath(RACTestObject.new, weakTestObjectValue), + RACKVOWrapperExamplesChangeBlock: changeBlock, + RACKVOWrapperExamplesValueBlock: valueBlock, + RACKVOWrapperExamplesChangesValueDirectly: @YES + }); + + itShouldBehaveLike(RACKVOWrapperCollectionExamples, @{ + RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, + RACKVOWrapperCollectionExamplesKeyPath: @keypath(RACTestObject.new, orderedSetValue) + }); + }); + + describe(@"on composite key paths'", ^{ + describe(@"last key path components", ^{ + NSObject *(^targetBlock)(void) = ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + object.strongTestObjectValue = [[RACTestObject alloc] init]; + return object; + }; + + void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { + target.strongTestObjectValue.weakTestObjectValue = value; + }; + + id (^valueBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + itShouldBehaveLike(RACKVOWrapperExamples, @{ + RACKVOWrapperExamplesTargetBlock: targetBlock, + RACKVOWrapperExamplesKeyPath: @keypath(RACTestObject.new, strongTestObjectValue.weakTestObjectValue), + RACKVOWrapperExamplesChangeBlock: changeBlock, + RACKVOWrapperExamplesValueBlock: valueBlock, + RACKVOWrapperExamplesChangesValueDirectly: @YES + }); + + itShouldBehaveLike(RACKVOWrapperCollectionExamples, @{ + RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, + RACKVOWrapperCollectionExamplesKeyPath: @keypath(RACTestObject.new, strongTestObjectValue.orderedSetValue) + }); + }); + + describe(@"intermediate key path components", ^{ + NSObject *(^targetBlock)(void) = ^{ + return [[RACTestObject alloc] init]; + }; + + void (^changeBlock)(RACTestObject *, id) = ^(RACTestObject *target, id value) { + target.weakTestObjectValue = value; + }; + + id (^valueBlock)(void) = ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + object.strongTestObjectValue = [[RACTestObject alloc] init]; + return object; + }; + + itShouldBehaveLike(RACKVOWrapperExamples, @{ + RACKVOWrapperExamplesTargetBlock: targetBlock, + RACKVOWrapperExamplesKeyPath: @keypath([[RACTestObject alloc] init], weakTestObjectValue.strongTestObjectValue), + RACKVOWrapperExamplesChangeBlock: changeBlock, + RACKVOWrapperExamplesValueBlock: valueBlock, + RACKVOWrapperExamplesChangesValueDirectly: @NO + }); + }); + + it(@"should not notice deallocation of the object returned by a dynamic final property", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id lastValue = nil; + @autoreleasepool { + [object rac_observeKeyPath:@keypath(object.dynamicObjectProperty) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change) { + lastValue = value; + }]; + + expect(lastValue).to.beKindOf(RACTestObject.class); + } + + expect(lastValue).to.beKindOf(RACTestObject.class); + }); + + it(@"should not notice deallocation of the object returned by a dynamic intermediate property", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id lastValue = nil; + @autoreleasepool { + [object rac_observeKeyPath:@keypath(object.dynamicObjectProperty.integerValue) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change) { + lastValue = value; + }]; + + expect(lastValue).to.equal(@42); + } + + expect(lastValue).to.equal(@42); + }); + + it(@"should not notice deallocation of the object returned by a dynamic method", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id lastValue = nil; + @autoreleasepool { + [object rac_observeKeyPath:@keypath(object.dynamicObjectMethod) options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change) { + lastValue = value; + }]; + + expect(lastValue).to.beKindOf(RACTestObject.class); + } + + expect(lastValue).to.beKindOf(RACTestObject.class); + }); + }); + + it(@"should not call the callback block when the value is the observer", ^{ + __block BOOL observerDisposed = NO; + __block BOOL observerDeallocationTriggeredChange = NO; + __block BOOL targetDisposed = NO; + __block BOOL targetDeallocationTriggeredChange = NO; + + @autoreleasepool { + RACTestObject *observer __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + [observer.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + observerDisposed = YES; + }]]; + + RACTestObject *target __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + [target.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + targetDisposed = YES; + }]]; + + observer.weakTestObjectValue = observer; + target.weakTestObjectValue = target; + + // These observations can only result in dealloc triggered callbacks. + [observer rac_observeKeyPath:@keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id _, NSDictionary *__) { + observerDeallocationTriggeredChange = YES; + }]; + + [target rac_observeKeyPath:@keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id _, NSDictionary *__) { + targetDeallocationTriggeredChange = YES; + }]; + } + + expect(observerDisposed).to.beTruthy(); + expect(observerDeallocationTriggeredChange).to.beFalsy(); + + expect(targetDisposed).to.beTruthy(); + expect(targetDeallocationTriggeredChange).to.beTruthy(); + }); + + it(@"should call the callback block for deallocation of the initial value of a single-key key path", ^{ + RACTestObject *target = [RACTestObject new]; + __block BOOL objectDisposed = NO; + __block BOOL objectDeallocationTriggeredChange = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + target.weakTestObjectValue = object; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDisposed = YES; + }]]; + + [target rac_observeKeyPath:@keypath(target.weakTestObjectValue) options:0 observer:target block:^(id _, NSDictionary *__) { + objectDeallocationTriggeredChange = YES; + }]; + } + + expect(objectDisposed).to.beTruthy(); + expect(objectDeallocationTriggeredChange).to.beTruthy(); + }); + + it(@"should call the callback block for deallocation of an object conforming to protocol property", ^{ + RACTestObject *target = [RACTestObject new]; + __block BOOL objectDisposed = NO; + __block BOOL objectDeallocationTriggeredChange = NO; + + @autoreleasepool { + RACTestObject *object __attribute__((objc_precise_lifetime)) = [RACTestObject new]; + target.weakObjectWithProtocol = object; + [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + objectDisposed = YES; + }]]; + + [target rac_observeKeyPath:@keypath(target.weakObjectWithProtocol) options:0 observer:target block:^(id _, NSDictionary *__) { + objectDeallocationTriggeredChange = YES; + }]; + } + + expect(objectDisposed).to.beTruthy(); + expect(objectDeallocationTriggeredChange).to.beTruthy(); + }); +}); + +describe(@"rac_addObserver:forKeyPath:options:block:", ^{ + it(@"should add and remove an observer", ^{ + NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}]; + expect(operation).notTo.beNil(); + + __block BOOL notified = NO; + RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:self block:^(id value, NSDictionary *change) { + expect([change objectForKey:NSKeyValueChangeNewKey]).to.equal(@YES); + + expect(notified).to.beFalsy(); + notified = YES; + }]; + + expect(disposable).notTo.beNil(); + + [operation start]; + [operation waitUntilFinished]; + + expect(notified).will.beTruthy(); + }); + + it(@"should accept a nil observer", ^{ + NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{}]; + RACDisposable *disposable = [operation rac_observeKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change) {}]; + + expect(disposable).notTo.beNil(); + }); + + it(@"automatically stops KVO on subclasses when the target deallocates", ^{ + void (^testKVOOnSubclass)(Class targetClass, id observer) = ^(Class targetClass, id observer) { + __weak id weakTarget = nil; + __weak id identifier = nil; + + @autoreleasepool { + // Create an observable target that we control the memory management of. + CFTypeRef target = CFBridgingRetain([[targetClass alloc] init]); + expect(target).notTo.beNil(); + + weakTarget = (__bridge id)target; + expect(weakTarget).notTo.beNil(); + + identifier = [(__bridge id)target rac_observeKeyPath:@"isFinished" options:0 observer:observer block:^(id value, NSDictionary *change) {}]; + expect(identifier).notTo.beNil(); + + CFRelease(target); + } + + expect(weakTarget).to.beNil(); + expect(identifier).to.beNil(); + }; + + it (@"stops KVO on NSObject subclasses", ^{ + testKVOOnSubclass(NSOperation.class, self); + }); + + it(@"stops KVO on subclasses of already-swizzled classes", ^{ + testKVOOnSubclass(RACTestOperation.class, self); + }); + + it (@"stops KVO on NSObject subclasses even with a nil observer", ^{ + testKVOOnSubclass(NSOperation.class, nil); + }); + + it(@"stops KVO on subclasses of already-swizzled classes even with a nil observer", ^{ + testKVOOnSubclass(RACTestOperation.class, nil); + }); + }); + + it(@"should automatically stop KVO when the observer deallocates", ^{ + __weak id weakObserver = nil; + __weak id identifier = nil; + + NSOperation *operation = [[NSOperation alloc] init]; + + @autoreleasepool { + // Create an observer that we control the memory management of. + CFTypeRef observer = CFBridgingRetain([[NSOperation alloc] init]); + expect(observer).notTo.beNil(); + + weakObserver = (__bridge id)observer; + expect(weakObserver).notTo.beNil(); + + identifier = [operation rac_observeKeyPath:@"isFinished" options:0 observer:(__bridge id)observer block:^(id value, NSDictionary *change) {}]; + expect(identifier).notTo.beNil(); + + CFRelease(observer); + } + + expect(weakObserver).to.beNil(); + }); + + it(@"should stop KVO when the observer is disposed", ^{ + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + __block NSString *name = nil; + + RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change) { + name = queue.name; + }]; + + queue.name = @"1"; + expect(name).to.equal(@"1"); + [disposable dispose]; + queue.name = @"2"; + expect(name).to.equal(@"1"); + }); + + it(@"should distinguish between observers being disposed", ^{ + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + __block NSString *name1 = nil; + __block NSString *name2 = nil; + + RACDisposable *disposable = [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change) { + name1 = queue.name; + }]; + [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change) { + name2 = queue.name; + }]; + + queue.name = @"1"; + expect(name1).to.equal(@"1"); + expect(name2).to.equal(@"1"); + [disposable dispose]; + queue.name = @"2"; + expect(name1).to.equal(@"1"); + expect(name2).to.equal(@"2"); + }); +}); + +SpecEnd + +@implementation RACTestOperation +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACMulticastConnectionSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACMulticastConnectionSpec.m new file mode 100644 index 0000000..b043beb --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACMulticastConnectionSpec.m
@@ -0,0 +1,142 @@ +// +// RACMulticastConnectionSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 10/8/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACMulticastConnection.h" +#import "RACDisposable.h" +#import "RACSignal+Operations.h" +#import "RACSubscriber.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" +#import <libkern/OSAtomic.h> + +SpecBegin(RACMulticastConnection) + +__block NSUInteger subscriptionCount = 0; +__block RACMulticastConnection *connection; +__block BOOL disposed = NO; + +beforeEach(^{ + subscriptionCount = 0; + disposed = NO; + connection = [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { + subscriptionCount++; + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }] publish]; + expect(subscriptionCount).to.equal(0); +}); + +describe(@"-connect", ^{ + it(@"should subscribe to the underlying signal", ^{ + [connection connect]; + expect(subscriptionCount).to.equal(1); + }); + + it(@"should return the same disposable for each invocation", ^{ + RACDisposable *d1 = [connection connect]; + RACDisposable *d2 = [connection connect]; + expect(d1).to.equal(d2); + expect(subscriptionCount).to.equal(1); + }); + + it(@"shouldn't reconnect after disposal", ^{ + RACDisposable *disposable1 = [connection connect]; + expect(subscriptionCount).to.equal(1); + + [disposable1 dispose]; + + RACDisposable *disposable2 = [connection connect]; + expect(subscriptionCount).to.equal(1); + expect(disposable1).to.equal(disposable2); + }); + + it(@"shouldn't race when connecting", ^{ + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + RACMulticastConnection *connection = [[RACSignal + defer:^ id { + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + return nil; + }] + publish]; + + __block RACDisposable *disposable; + [RACScheduler.scheduler schedule:^{ + disposable = [connection connect]; + dispatch_semaphore_signal(semaphore); + }]; + + expect([connection connect]).notTo.beNil(); + dispatch_semaphore_signal(semaphore); + + expect(disposable).willNot.beNil(); + + dispatch_release(semaphore); + }); +}); + +describe(@"-autoconnect", ^{ + __block RACSignal *autoconnectedSignal; + + beforeEach(^{ + autoconnectedSignal = [connection autoconnect]; + }); + + it(@"should subscribe to the multicasted signal on the first subscription", ^{ + expect(subscriptionCount).to.equal(0); + + [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(subscriptionCount).to.equal(1); + + [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(subscriptionCount).to.equal(1); + }); + + it(@"should dispose of the multicasted subscription when the signal has no subscribers", ^{ + RACDisposable *disposable = [autoconnectedSignal subscribeNext:^(id x) {}]; + + expect(disposed).to.beFalsy(); + [disposable dispose]; + expect(disposed).to.beTruthy(); + }); + + it(@"shouldn't reconnect after disposal", ^{ + RACDisposable *disposable = [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(subscriptionCount).to.equal(1); + [disposable dispose]; + + disposable = [autoconnectedSignal subscribeNext:^(id x) {}]; + expect(subscriptionCount).to.equal(1); + [disposable dispose]; + }); + + it(@"should replay values after disposal when multicasted to a replay subject", ^{ + RACSubject *subject = [RACSubject subject]; + RACSignal *signal = [[subject multicast:[RACReplaySubject subject]] autoconnect]; + + NSMutableArray *results1 = [NSMutableArray array]; + RACDisposable *disposable = [signal subscribeNext:^(id x) { + [results1 addObject:x]; + }]; + + [subject sendNext:@1]; + [subject sendNext:@2]; + + expect(results1).to.equal((@[ @1, @2 ])); + [disposable dispose]; + + NSMutableArray *results2 = [NSMutableArray array]; + [signal subscribeNext:^(id x) { + [results2 addObject:x]; + }]; + expect(results2).will.equal((@[ @1, @2 ])); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACPropertySignalExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACPropertySignalExamples.h new file mode 100644 index 0000000..59a2b43 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACPropertySignalExamples.h
@@ -0,0 +1,18 @@ +// +// RACPropertySignalExamples.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 9/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +// The name of the shared examples for a signal-driven property. +extern NSString * const RACPropertySignalExamples; + +// The block should have the signature: +// +// void (^)(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) +// +// and should tie the value of the key path on testObject to signal. `nilValue` +// will be used when the signal sends a `nil` value. +extern NSString * const RACPropertySignalExamplesSetupBlock;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACPropertySignalExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACPropertySignalExamples.m new file mode 100644 index 0000000..627d634 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACPropertySignalExamples.m
@@ -0,0 +1,180 @@ +// +// RACPropertySignalExamples.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 9/28/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" + +#import "EXTKeyPathCoding.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSubject.h" + +NSString * const RACPropertySignalExamples = @"RACPropertySignalExamples"; +NSString * const RACPropertySignalExamplesSetupBlock = @"RACPropertySignalExamplesSetupBlock"; + +SharedExampleGroupsBegin(RACPropertySignalExamples) + +sharedExamplesFor(RACPropertySignalExamples, ^(NSDictionary *data) { + __block RACTestObject *testObject = nil; + __block void (^setupBlock)(RACTestObject *, NSString *keyPath, id nilValue, RACSignal *); + + beforeEach(^{ + setupBlock = data[RACPropertySignalExamplesSetupBlock]; + testObject = [[RACTestObject alloc] init]; + }); + + it(@"should set the value of the property with the latest value from the signal", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @keypath(testObject.objectValue), nil, subject); + expect(testObject.objectValue).to.beNil(); + + [subject sendNext:@1]; + expect(testObject.objectValue).to.equal(@1); + + [subject sendNext:@2]; + expect(testObject.objectValue).to.equal(@2); + + [subject sendNext:nil]; + expect(testObject.objectValue).to.beNil(); + }); + + it(@"should set the given nilValue for an object property", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @keypath(testObject.objectValue), @"foo", subject); + expect(testObject.objectValue).to.beNil(); + + [subject sendNext:@1]; + expect(testObject.objectValue).to.equal(@1); + + [subject sendNext:@2]; + expect(testObject.objectValue).to.equal(@2); + + [subject sendNext:nil]; + expect(testObject.objectValue).to.equal(@"foo"); + }); + + it(@"should leave the value of the property alone after the signal completes", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @keypath(testObject.objectValue), nil, subject); + expect(testObject.objectValue).to.beNil(); + + [subject sendNext:@1]; + expect(testObject.objectValue).to.equal(@1); + + [subject sendCompleted]; + expect(testObject.objectValue).to.equal(@1); + }); + + it(@"should set the value of a non-object property with the latest value from the signal", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @keypath(testObject.integerValue), nil, subject); + expect(testObject.integerValue).to.equal(0); + + [subject sendNext:@1]; + expect(testObject.integerValue).to.equal(1); + + [subject sendNext:@2]; + expect(testObject.integerValue).to.equal(2); + + [subject sendNext:@0]; + expect(testObject.integerValue).to.equal(0); + }); + + it(@"should set the given nilValue for a non-object property", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @keypath(testObject.integerValue), @42, subject); + expect(testObject.integerValue).to.equal(0); + + [subject sendNext:@1]; + expect(testObject.integerValue).to.equal(@1); + + [subject sendNext:@2]; + expect(testObject.integerValue).to.equal(@2); + + [subject sendNext:nil]; + expect(testObject.integerValue).to.equal(@42); + }); + + it(@"should not invoke -setNilValueForKey: with a nilValue", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @keypath(testObject.integerValue), @42, subject); + + __block BOOL setNilValueForKeyInvoked = NO; + [[testObject rac_signalForSelector:@selector(setNilValueForKey:)] subscribeNext:^(NSString *key) { + setNilValueForKeyInvoked = YES; + }]; + + [subject sendNext:nil]; + expect(testObject.integerValue).to.equal(@42); + expect(setNilValueForKeyInvoked).to.beFalsy(); + }); + + it(@"should invoke -setNilValueForKey: without a nilValue", ^{ + RACSubject *subject = [RACSubject subject]; + setupBlock(testObject, @keypath(testObject.integerValue), nil, subject); + + [subject sendNext:@1]; + expect(testObject.integerValue).to.equal(@1); + + testObject.catchSetNilValueForKey = YES; + + __block BOOL setNilValueForKeyInvoked = NO; + [[testObject rac_signalForSelector:@selector(setNilValueForKey:)] subscribeNext:^(NSString *key) { + setNilValueForKeyInvoked = YES; + }]; + + [subject sendNext:nil]; + expect(testObject.integerValue).to.equal(@1); + expect(setNilValueForKeyInvoked).to.beTruthy(); + }); + + it(@"should retain intermediate signals when binding", ^{ + RACSubject *subject = [RACSubject subject]; + expect(subject).notTo.beNil(); + + __block BOOL deallocd = NO; + + @autoreleasepool { + @autoreleasepool { + RACSignal *intermediateSignal = [subject map:^(NSNumber *num) { + return @(num.integerValue + 1); + }]; + + expect(intermediateSignal).notTo.beNil(); + + [intermediateSignal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + + setupBlock(testObject, @keypath(testObject.integerValue), nil, intermediateSignal); + } + + // Spin the run loop to account for RAC magic that retains the + // signal for a single iteration. + [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; + } + + expect(deallocd).to.beFalsy(); + + [subject sendNext:@5]; + expect(testObject.integerValue).to.equal(6); + + [subject sendNext:@6]; + expect(testObject.integerValue).to.equal(7); + + expect(deallocd).to.beFalsy(); + [subject sendCompleted]; + + // Can't test deallocd again, because it's legal for the chain to be + // retained until the object or the original signal is destroyed. + }); +}); + +SharedExampleGroupsEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSchedulerSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSchedulerSpec.m new file mode 100644 index 0000000..738eff8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSchedulerSpec.m
@@ -0,0 +1,424 @@ +// +// RACSchedulerSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 11/29/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACScheduler.h" +#import "RACScheduler+Private.h" +#import "RACQueueScheduler+Subclass.h" +#import "RACDisposable.h" +#import "EXTScope.h" +#import "RACTestExampleScheduler.h" +#import <libkern/OSAtomic.h> + +// This shouldn't be used directly. Use the `expectCurrentSchedulers` block +// below instead. +static void expectCurrentSchedulersInner(NSArray *schedulers, NSMutableArray *currentSchedulerArray) { + if (schedulers.count > 0) { + RACScheduler *topScheduler = schedulers[0]; + [topScheduler schedule:^{ + RACScheduler *currentScheduler = RACScheduler.currentScheduler; + if (currentScheduler != nil) [currentSchedulerArray addObject:currentScheduler]; + expectCurrentSchedulersInner([schedulers subarrayWithRange:NSMakeRange(1, schedulers.count - 1)], currentSchedulerArray); + }]; + } +} + +SpecBegin(RACScheduler) + +it(@"should know its current scheduler", ^{ + // Recursively schedules a block in each of the given schedulers and records + // the +currentScheduler at each step. It then expects the array of + // +currentSchedulers and the expected array to be equal. + // + // schedulers - The array of schedulers to recursively schedule. + // expectedCurrentSchedulers - The array of +currentSchedulers to expect. + void (^expectCurrentSchedulers)(NSArray *, NSArray *) = ^(NSArray *schedulers, NSArray *expectedCurrentSchedulers) { + NSMutableArray *currentSchedulerArray = [NSMutableArray array]; + expectCurrentSchedulersInner(schedulers, currentSchedulerArray); + expect(currentSchedulerArray).will.equal(expectedCurrentSchedulers); + }; + + RACScheduler *backgroundScheduler = [RACScheduler scheduler]; + + expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.immediateScheduler ], @[ backgroundScheduler, backgroundScheduler ]); + expectCurrentSchedulers(@[ backgroundScheduler, RACScheduler.subscriptionScheduler ], @[ backgroundScheduler, backgroundScheduler ]); + + NSArray *mainThreadJumper = @[ RACScheduler.mainThreadScheduler, backgroundScheduler, RACScheduler.mainThreadScheduler ]; + expectCurrentSchedulers(mainThreadJumper, mainThreadJumper); + + NSArray *backgroundJumper = @[ backgroundScheduler, RACScheduler.mainThreadScheduler, backgroundScheduler ]; + expectCurrentSchedulers(backgroundJumper, backgroundJumper); +}); + +describe(@"+mainThreadScheduler", ^{ + it(@"should cancel scheduled blocks when disposed", ^{ + __block BOOL firstBlockRan = NO; + __block BOOL secondBlockRan = NO; + + RACDisposable *disposable = [RACScheduler.mainThreadScheduler schedule:^{ + firstBlockRan = YES; + }]; + + expect(disposable).notTo.beNil(); + + [RACScheduler.mainThreadScheduler schedule:^{ + secondBlockRan = YES; + }]; + + [disposable dispose]; + + expect(secondBlockRan).to.beFalsy(); + expect(secondBlockRan).will.beTruthy(); + expect(firstBlockRan).to.beFalsy(); + }); + + it(@"should schedule future blocks", ^{ + __block BOOL done = NO; + + [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + done = YES; + }]; + + expect(done).to.beFalsy(); + expect(done).will.beTruthy(); + }); + + it(@"should cancel future blocks when disposed", ^{ + __block BOOL firstBlockRan = NO; + __block BOOL secondBlockRan = NO; + + RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + firstBlockRan = YES; + }]; + + expect(disposable).notTo.beNil(); + + [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + secondBlockRan = YES; + }]; + + [disposable dispose]; + + expect(secondBlockRan).to.beFalsy(); + expect(secondBlockRan).will.beTruthy(); + expect(firstBlockRan).to.beFalsy(); + }); + + it(@"should schedule recurring blocks", ^{ + __block NSUInteger count = 0; + + RACDisposable *disposable = [RACScheduler.mainThreadScheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ + count++; + }]; + + expect(count).to.equal(0); + expect(count).will.equal(1); + expect(count).will.equal(2); + expect(count).will.equal(3); + + [disposable dispose]; + [NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + + expect(count).to.equal(3); + }); +}); + +describe(@"+scheduler", ^{ + __block RACScheduler *scheduler; + __block NSDate * (^futureDate)(void); + + beforeEach(^{ + scheduler = [RACScheduler scheduler]; + + futureDate = ^{ + return [NSDate dateWithTimeIntervalSinceNow:0.01]; + }; + }); + + it(@"should cancel scheduled blocks when disposed", ^{ + __block BOOL firstBlockRan = NO; + __block BOOL secondBlockRan = NO; + + // Start off on the scheduler so the enqueued blocks won't run until we + // return. + [scheduler schedule:^{ + RACDisposable *disposable = [scheduler schedule:^{ + firstBlockRan = YES; + }]; + + expect(disposable).notTo.beNil(); + + [scheduler schedule:^{ + secondBlockRan = YES; + }]; + + [disposable dispose]; + }]; + + expect(secondBlockRan).will.beTruthy(); + expect(firstBlockRan).to.beFalsy(); + }); + + it(@"should schedule future blocks", ^{ + __block BOOL done = NO; + + [scheduler after:futureDate() schedule:^{ + done = YES; + }]; + + expect(done).to.beFalsy(); + expect(done).will.beTruthy(); + }); + + it(@"should cancel future blocks when disposed", ^{ + __block BOOL firstBlockRan = NO; + __block BOOL secondBlockRan = NO; + + NSDate *date = futureDate(); + RACDisposable *disposable = [scheduler after:date schedule:^{ + firstBlockRan = YES; + }]; + + expect(disposable).notTo.beNil(); + [disposable dispose]; + + [scheduler after:date schedule:^{ + secondBlockRan = YES; + }]; + + expect(secondBlockRan).to.beFalsy(); + expect(secondBlockRan).will.beTruthy(); + expect(firstBlockRan).to.beFalsy(); + }); + + it(@"should schedule recurring blocks", ^{ + __block NSUInteger count = 0; + + RACDisposable *disposable = [scheduler after:[NSDate date] repeatingEvery:0.05 withLeeway:0 schedule:^{ + count++; + }]; + + expect(count).to.equal(0); + expect(count).will.equal(1); + expect(count).will.equal(2); + expect(count).will.equal(3); + + [disposable dispose]; + [NSThread sleepForTimeInterval:0.1]; + + expect(count).to.equal(3); + }); +}); + +describe(@"+subscriptionScheduler", ^{ + describe(@"setting +currentScheduler", ^{ + __block RACScheduler *currentScheduler; + + beforeEach(^{ + currentScheduler = nil; + }); + + it(@"should be the +mainThreadScheduler when scheduled from the main queue", ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [RACScheduler.subscriptionScheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + }); + + expect(currentScheduler).will.equal(RACScheduler.mainThreadScheduler); + }); + + it(@"should be a +scheduler when scheduled from an unknown queue", ^{ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [RACScheduler.subscriptionScheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + }); + + expect(currentScheduler).willNot.beNil(); + expect(currentScheduler).notTo.equal(RACScheduler.mainThreadScheduler); + }); + + it(@"should equal the background scheduler from which the block was scheduled", ^{ + RACScheduler *backgroundScheduler = [RACScheduler scheduler]; + [backgroundScheduler schedule:^{ + [RACScheduler.subscriptionScheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + }]; + + expect(currentScheduler).will.equal(backgroundScheduler); + }); + }); + + it(@"should execute scheduled blocks immediately if it's in a scheduler already", ^{ + __block BOOL done = NO; + __block BOOL executedImmediately = NO; + + [[RACScheduler scheduler] schedule:^{ + [RACScheduler.subscriptionScheduler schedule:^{ + executedImmediately = YES; + }]; + + done = YES; + }]; + + expect(done).will.beTruthy(); + expect(executedImmediately).to.beTruthy(); + }); +}); + +describe(@"+immediateScheduler", ^{ + it(@"should immediately execute scheduled blocks", ^{ + __block BOOL executed = NO; + RACDisposable *disposable = [RACScheduler.immediateScheduler schedule:^{ + executed = YES; + }]; + + expect(disposable).to.beNil(); + expect(executed).to.beTruthy(); + }); + + it(@"should block for future scheduled blocks", ^{ + __block BOOL executed = NO; + RACDisposable *disposable = [RACScheduler.immediateScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ + executed = YES; + }]; + + expect(executed).to.beTruthy(); + expect(disposable).to.beNil(); + }); +}); + +describe(@"-scheduleRecursiveBlock:", ^{ + describe(@"with a synchronous scheduler", ^{ + it(@"should behave like a normal block when it doesn't invoke itself", ^{ + __block BOOL executed = NO; + [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + expect(executed).to.beFalsy(); + executed = YES; + }]; + + expect(executed).to.beTruthy(); + }); + + it(@"should reschedule itself after the caller completes", ^{ + __block NSUInteger count = 0; + [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + NSUInteger thisCount = ++count; + if (thisCount < 3) { + recurse(); + + // The block shouldn't have been invoked again yet, only + // scheduled. + expect(count).to.equal(thisCount); + } + }]; + + expect(count).to.equal(3); + }); + }); + + describe(@"with an asynchronous scheduler", ^{ + it(@"should behave like a normal block when it doesn't invoke itself", ^{ + __block BOOL executed = NO; + [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + expect(executed).to.beFalsy(); + executed = YES; + }]; + + expect(executed).will.beTruthy(); + }); + + it(@"should reschedule itself after the caller completes", ^{ + __block NSUInteger count = 0; + [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + NSUInteger thisCount = ++count; + if (thisCount < 3) { + recurse(); + + // The block shouldn't have been invoked again yet, only + // scheduled. + expect(count).to.equal(thisCount); + } + }]; + + expect(count).will.equal(3); + }); + + it(@"should reschedule when invoked asynchronously", ^{ + __block NSUInteger count = 0; + + RACScheduler *asynchronousScheduler = [RACScheduler scheduler]; + [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + [asynchronousScheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ + NSUInteger thisCount = ++count; + if (thisCount < 3) { + recurse(); + + // The block shouldn't have been invoked again yet, only + // scheduled. + expect(count).to.equal(thisCount); + } + }]; + }]; + + expect(count).will.equal(3); + }); + + it(@"shouldn't reschedule itself when disposed", ^{ + __block NSUInteger count = 0; + __block RACDisposable *disposable = [RACScheduler.mainThreadScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { + ++count; + + expect(disposable).notTo.beNil(); + [disposable dispose]; + + recurse(); + }]; + + expect(count).will.equal(1); + }); + }); +}); + +describe(@"subclassing", ^{ + __block RACTestExampleScheduler *scheduler; + + beforeEach(^{ + scheduler = [[RACTestExampleScheduler alloc] initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + }); + + it(@"should invoke blocks scheduled with -schedule:", ^{ + __block BOOL invoked = NO; + [scheduler schedule:^{ + invoked = YES; + }]; + + expect(invoked).will.beTruthy(); + }); + + it(@"should invoke blocks scheduled with -after:schedule:", ^{ + __block BOOL invoked = NO; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ + invoked = YES; + }]; + + expect(invoked).will.beTruthy(); + }); + + it(@"should set a valid current scheduler", ^{ + __block RACScheduler *currentScheduler; + [scheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + + expect(currentScheduler).will.equal(scheduler); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceAdditionsSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceAdditionsSpec.m new file mode 100644 index 0000000..e5b5834 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceAdditionsSpec.m
@@ -0,0 +1,338 @@ +// +// RACSequenceAdditionsSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-01. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSequenceExamples.h" + +#import "NSArray+RACSequenceAdditions.h" +#import "NSDictionary+RACSequenceAdditions.h" +#import "NSOrderedSet+RACSequenceAdditions.h" +#import "NSSet+RACSequenceAdditions.h" +#import "NSString+RACSequenceAdditions.h" +#import "NSIndexSet+RACSequenceAdditions.h" +#import "RACSequence.h" +#import "RACTuple.h" + +SpecBegin(RACSequenceAdditions) + +__block NSArray *numbers; + +beforeEach(^{ + NSMutableArray *mutableNumbers = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + [mutableNumbers addObject:@(i)]; + } + + numbers = [mutableNumbers copy]; +}); + +describe(@"NSArray sequences", ^{ + __block NSMutableArray *values; + __block RACSequence *sequence; + + beforeEach(^{ + values = [numbers mutableCopy]; + sequence = values.rac_sequence; + expect(sequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values + }; + }); + + describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + beforeEach(^{ + unchangedValues = [values copy]; + [values addObject:@6]; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); + + it(@"should fast enumerate after zipping", ^{ + // This certain list of values causes issues, for some reason. + NSArray *values = @[ @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0 ]; + RACSequence *zippedSequence = [RACSequence zip:@[ values.rac_sequence, values.rac_sequence ] reduce:^(id obj1, id obj2) { + return obj1; + }]; + + NSMutableArray *collectedValues = [NSMutableArray array]; + for (id value in zippedSequence) { + [collectedValues addObject:value]; + } + + expect(collectedValues).to.equal(values); + }); +}); + +describe(@"NSDictionary sequences", ^{ + __block NSMutableDictionary *dict; + + __block NSMutableArray *tuples; + __block RACSequence *tupleSequence; + + __block NSArray *keys; + __block RACSequence *keySequence; + + __block NSArray *values; + __block RACSequence *valueSequence; + + beforeEach(^{ + dict = [@{ + @"foo": @"bar", + @"baz": @"buzz", + @5: NSNull.null + } mutableCopy]; + + tuples = [NSMutableArray array]; + for (id key in dict) { + RACTuple *tuple = [RACTuple tupleWithObjects:key, dict[key], nil]; + [tuples addObject:tuple]; + } + + tupleSequence = dict.rac_sequence; + expect(tupleSequence).notTo.beNil(); + + keys = [dict.allKeys copy]; + keySequence = dict.rac_keySequence; + expect(keySequence).notTo.beNil(); + + values = [dict.allValues copy]; + valueSequence = dict.rac_valueSequence; + expect(valueSequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: tupleSequence, + RACSequenceExampleExpectedValues: tuples + }; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: keySequence, + RACSequenceExampleExpectedValues: keys + }; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: valueSequence, + RACSequenceExampleExpectedValues: values + }; + }); + + describe(@"should be immutable", ^{ + beforeEach(^{ + dict[@"foo"] = @"rab"; + dict[@6] = @7; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: tupleSequence, + RACSequenceExampleExpectedValues: tuples + }; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: keySequence, + RACSequenceExampleExpectedValues: keys + }; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: valueSequence, + RACSequenceExampleExpectedValues: values + }; + }); + }); +}); + +describe(@"NSOrderedSet sequences", ^{ + __block NSMutableOrderedSet *values; + __block RACSequence *sequence; + + beforeEach(^{ + values = [NSMutableOrderedSet orderedSetWithArray:numbers]; + sequence = values.rac_sequence; + expect(sequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values.array + }; + }); + + describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + beforeEach(^{ + unchangedValues = [values.array copy]; + [values addObject:@6]; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); +}); + +describe(@"NSSet sequences", ^{ + __block NSMutableSet *values; + __block RACSequence *sequence; + + beforeEach(^{ + values = [NSMutableSet setWithArray:numbers]; + sequence = values.rac_sequence; + expect(sequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values.allObjects + }; + }); + + describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + beforeEach(^{ + unchangedValues = [values.allObjects copy]; + [values addObject:@6]; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); +}); + +describe(@"NSString sequences", ^{ + __block NSMutableString *string; + __block NSArray *values; + __block RACSequence *sequence; + + beforeEach(^{ + string = [@"foobar" mutableCopy]; + values = @[ @"f", @"o", @"o", @"b", @"a", @"r" ]; + sequence = string.rac_sequence; + expect(sequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values + }; + }); + + describe(@"should be immutable", ^{ + beforeEach(^{ + [string appendString:@"buzz"]; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: values + }; + }); + }); + + it(@"should work with composed characters", ^{ + NSString *string = @"\u2665\uFE0F\u2666\uFE0F"; + NSArray *expectedSequence = @[ @"\u2665\uFE0F", @"\u2666\uFE0F" ]; + expect(string.rac_sequence.array).to.equal(expectedSequence); + }); +}); + +describe(@"RACTuple sequences", ^{ + __block RACTuple *tuple; + __block RACSequence *sequence; + + beforeEach(^{ + tuple = RACTuplePack(@"foo", nil, @"bar", NSNull.null, RACTupleNil.tupleNil); + + sequence = tuple.rac_sequence; + expect(sequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: @[ @"foo", NSNull.null, @"bar", NSNull.null, NSNull.null ] + }; + }); +}); + +describe(@"NSIndexSet sequences", ^{ + __block NSMutableIndexSet *values; + __block RACSequence *sequence; + + NSArray * (^valuesFromIndexSet)(NSIndexSet *indexSet) = ^NSArray *(NSIndexSet *indexSet) { + NSMutableArray *arr = [NSMutableArray array]; + [values enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [arr addObject:@(idx)]; + }]; + + return [arr copy]; + }; + + beforeEach(^{ + values = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 10)]; + sequence = values.rac_sequence; + expect(sequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: valuesFromIndexSet(values) + }; + }); + + describe(@"should be immutable", ^{ + __block NSArray *unchangedValues; + + beforeEach(^{ + unchangedValues = valuesFromIndexSet(values); + [values addIndex:20]; + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: unchangedValues + }; + }); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceExamples.h new file mode 100644 index 0000000..922b056 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceExamples.h
@@ -0,0 +1,16 @@ +// +// RACSequenceExamples.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-01. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +// The name of the shared examples for RACSequence instances. +extern NSString * const RACSequenceExamples; + +// RACSequence * +extern NSString * const RACSequenceExampleSequence; + +// NSArray * +extern NSString * const RACSequenceExampleExpectedValues;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceExamples.m new file mode 100644 index 0000000..5ca3923 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceExamples.m
@@ -0,0 +1,125 @@ +// +// RACSequenceExamples.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-01. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSequenceExamples.h" + +#import "RACScheduler.h" +#import "RACSequence.h" +#import "RACSignal+Operations.h" + +NSString * const RACSequenceExamples = @"RACSequenceExamples"; +NSString * const RACSequenceExampleSequence = @"RACSequenceExampleSequence"; +NSString * const RACSequenceExampleExpectedValues = @"RACSequenceExampleExpectedValues"; + +SharedExampleGroupsBegin(RACSequenceExamples); + +sharedExamplesFor(RACSequenceExamples, ^(NSDictionary *data) { + __block RACSequence *sequence; + __block NSArray *values; + + beforeEach(^{ + sequence = data[RACSequenceExampleSequence]; + values = [data[RACSequenceExampleExpectedValues] copy]; + }); + + it(@"should implement <NSFastEnumeration>", ^{ + NSMutableArray *collectedValues = [NSMutableArray array]; + for (id value in sequence) { + [collectedValues addObject:value]; + } + + expect(collectedValues).to.equal(values); + }); + + it(@"should return an array", ^{ + expect(sequence.array).to.equal(values); + }); + + describe(@"-signalWithScheduler:", ^{ + it(@"should return an immediately scheduled signal", ^{ + RACSignal *signal = [sequence signalWithScheduler:RACScheduler.immediateScheduler]; + expect(signal.toArray).to.equal(values); + }); + + it(@"should return a background scheduled signal", ^{ + RACSignal *signal = [sequence signalWithScheduler:[RACScheduler scheduler]]; + expect(signal.toArray).to.equal(values); + }); + + it(@"should only evaluate one value per scheduling", ^{ + RACSignal *signal = [sequence signalWithScheduler:RACScheduler.mainThreadScheduler]; + + __block BOOL flag = YES; + __block BOOL completed = NO; + [signal subscribeNext:^(id x) { + expect(flag).to.beTruthy(); + flag = NO; + + [RACScheduler.mainThreadScheduler schedule:^{ + // This should get executed before the next value (which + // verifies that it's YES). + flag = YES; + }]; + } completed:^{ + completed = YES; + }]; + + expect(completed).will.beTruthy(); + }); + }); + + it(@"should be equal to itself", ^{ + expect(sequence).to.equal(sequence); + }); + + it(@"should be equal to the same sequence of values", ^{ + RACSequence *newSequence = RACSequence.empty; + for (id value in values) { + RACSequence *valueSeq = [RACSequence return:value]; + expect(valueSeq).notTo.beNil(); + + newSequence = [newSequence concat:valueSeq]; + } + + expect(sequence).to.equal(newSequence); + expect(sequence.hash).to.equal(newSequence.hash); + }); + + it(@"should not be equal to a different sequence of values", ^{ + RACSequence *anotherSequence = [RACSequence return:@(-1)]; + expect(sequence).notTo.equal(anotherSequence); + }); + + it(@"should return an identical object for -copy", ^{ + expect([sequence copy]).to.beIdenticalTo(sequence); + }); + + it(@"should archive", ^{ + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:sequence]; + expect(data).notTo.beNil(); + + RACSequence *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + expect(unarchived).to.equal(sequence); + }); + + it(@"should fold right", ^{ + RACSequence *result = [sequence foldRightWithStart:[RACSequence empty] reduce:^(id first, RACSequence *rest) { + return [rest.head startWith:first]; + }]; + expect(result.array).to.equal(values); + }); + + it(@"should fold left", ^{ + RACSequence *result = [sequence foldLeftWithStart:[RACSequence empty] reduce:^(RACSequence *first, id rest) { + return [first concat:[RACSequence return:rest]]; + }]; + expect(result.array).to.equal(values); + }); +}); + +SharedExampleGroupsEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceSpec.m new file mode 100644 index 0000000..3bf7dca --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSequenceSpec.m
@@ -0,0 +1,443 @@ +// +// RACSequenceSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-01. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSequenceExamples.h" +#import "RACStreamExamples.h" + +#import "NSArray+RACSequenceAdditions.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSequence.h" +#import "RACUnit.h" + +SpecBegin(RACSequence) + +describe(@"RACStream", ^{ + id verifyValues = ^(RACSequence *sequence, NSArray *expectedValues) { + NSMutableArray *collectedValues = [NSMutableArray array]; + while (sequence.head != nil) { + [collectedValues addObject:sequence.head]; + sequence = sequence.tail; + } + + expect(collectedValues).to.equal(expectedValues); + }; + + __block RACSequence *infiniteSequence = [RACSequence sequenceWithHeadBlock:^{ + return RACUnit.defaultUnit; + } tailBlock:^{ + return infiniteSequence; + }]; + + itShouldBehaveLike(RACStreamExamples, ^{ + return @{ + RACStreamExamplesClass: RACSequence.class, + RACStreamExamplesVerifyValuesBlock: verifyValues, + RACStreamExamplesInfiniteStream: infiniteSequence + }; + }); +}); + +describe(@"+sequenceWithHeadBlock:tailBlock:", ^{ + __block RACSequence *sequence; + __block BOOL headInvoked; + __block BOOL tailInvoked; + + before(^{ + headInvoked = NO; + tailInvoked = NO; + + sequence = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return @0; + } tailBlock:^{ + tailInvoked = YES; + return [RACSequence return:@1]; + }]; + + expect(sequence).notTo.beNil(); + }); + + it(@"should use the values from the head and tail blocks", ^{ + expect(sequence.head).to.equal(@0); + expect(sequence.tail.head).to.equal(@1); + expect(sequence.tail.tail).to.beNil(); + }); + + it(@"should lazily invoke head and tail blocks", ^{ + expect(headInvoked).to.beFalsy(); + expect(tailInvoked).to.beFalsy(); + + expect(sequence.head).to.equal(@0); + expect(headInvoked).to.beTruthy(); + expect(tailInvoked).to.beFalsy(); + + expect(sequence.tail).notTo.beNil(); + expect(tailInvoked).to.beTruthy(); + }); + + after(^{ + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: sequence, + RACSequenceExampleExpectedValues: @[ @0, @1 ] + }; + }); + }); +}); + +describe(@"empty sequences", ^{ + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: [RACSequence empty], + RACSequenceExampleExpectedValues: @[] + }; + }); +}); + +describe(@"non-empty sequences", ^{ + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]], + RACSequenceExampleExpectedValues: @[ @0, @1, @2 ] + }; + }); +}); + +describe(@"eager sequences", ^{ + __block RACSequence *lazySequence; + __block BOOL headInvoked; + __block BOOL tailInvoked; + + NSArray *values = @[ @0, @1 ]; + + before(^{ + headInvoked = NO; + tailInvoked = NO; + + lazySequence = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return @0; + } tailBlock:^{ + tailInvoked = YES; + return [RACSequence return:@1]; + }]; + + expect(lazySequence).notTo.beNil(); + }); + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: lazySequence.eagerSequence, + RACSequenceExampleExpectedValues: values + }; + }); + + it(@"should evaluate all values immediately", ^{ + RACSequence *eagerSequence = lazySequence.eagerSequence; + expect(headInvoked).to.beTruthy(); + expect(tailInvoked).to.beTruthy(); + expect(eagerSequence.array).to.equal(values); + }); +}); + +describe(@"-take:", ^{ + it(@"should complete take: without needing the head of the second item in the sequence", ^{ + __block NSUInteger valuesTaken = 0; + + __block RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^{ + ++valuesTaken; + return RACUnit.defaultUnit; + } tailBlock:^{ + return sequence; + }]; + + NSArray *values = [sequence take:1].array; + expect(values).to.equal(@[ RACUnit.defaultUnit ]); + expect(valuesTaken).to.equal(1); + }); +}); + +describe(@"-bind:", ^{ + it(@"should only evaluate head when the resulting sequence is evaluated", ^{ + __block BOOL headInvoked = NO; + + RACSequence *original = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return RACUnit.defaultUnit; + } tailBlock:^ id { + return nil; + }]; + + RACSequence *bound = [original bind:^{ + return ^(id value, BOOL *stop) { + return [RACSequence return:value]; + }; + }]; + + expect(bound).notTo.beNil(); + expect(headInvoked).to.beFalsy(); + + expect(bound.head).to.equal(RACUnit.defaultUnit); + expect(headInvoked).to.beTruthy(); + }); +}); + +describe(@"-objectEnumerator", ^{ + it(@"should only evaluate head as it's enumerated", ^{ + __block BOOL firstHeadInvoked = NO; + __block BOOL secondHeadInvoked = NO; + __block BOOL thirdHeadInvoked = NO; + + RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^id{ + firstHeadInvoked = YES; + return @1; + } tailBlock:^RACSequence *{ + return [RACSequence sequenceWithHeadBlock:^id{ + secondHeadInvoked = YES; + return @2; + } tailBlock:^RACSequence *{ + return [RACSequence sequenceWithHeadBlock:^id{ + thirdHeadInvoked = YES; + return @3; + } tailBlock:^RACSequence *{ + return RACSequence.empty; + }]; + }]; + }]; + NSEnumerator *enumerator = sequence.objectEnumerator; + + expect(firstHeadInvoked).to.beFalsy(); + expect(secondHeadInvoked).to.beFalsy(); + expect(thirdHeadInvoked).to.beFalsy(); + + expect([enumerator nextObject]).to.equal(@1); + + expect(firstHeadInvoked).to.beTruthy(); + expect(secondHeadInvoked).to.beFalsy(); + expect(thirdHeadInvoked).to.beFalsy(); + + expect([enumerator nextObject]).to.equal(@2); + + expect(secondHeadInvoked).to.beTruthy(); + expect(thirdHeadInvoked).to.beFalsy(); + + expect([enumerator nextObject]).to.equal(@3); + + expect(thirdHeadInvoked).to.beTruthy(); + + expect([enumerator nextObject]).to.beNil(); + }); + + it(@"should let the sequence dealloc as it's enumerated", ^{ + __block BOOL firstSequenceDeallocd = NO; + __block BOOL secondSequenceDeallocd = NO; + __block BOOL thirdSequenceDeallocd = NO; + + NSEnumerator *enumerator = nil; + + @autoreleasepool { + RACSequence *thirdSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ + return @3; + } tailBlock:^RACSequence *{ + return RACSequence.empty; + }]; + [thirdSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + thirdSequenceDeallocd = YES; + }]]; + + RACSequence *secondSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ + return @2; + } tailBlock:^RACSequence *{ + return thirdSequence; + }]; + [secondSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + secondSequenceDeallocd = YES; + }]]; + + RACSequence *firstSequence __attribute__((objc_precise_lifetime)) = [RACSequence sequenceWithHeadBlock:^id{ + return @1; + } tailBlock:^RACSequence *{ + return secondSequence; + }]; + [firstSequence.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + firstSequenceDeallocd = YES; + }]]; + + enumerator = firstSequence.objectEnumerator; + } + + @autoreleasepool { + expect([enumerator nextObject]).to.equal(@1); + } + + @autoreleasepool { + expect([enumerator nextObject]).to.equal(@2); + } + expect(firstSequenceDeallocd).will.beTruthy(); + + @autoreleasepool { + expect([enumerator nextObject]).to.equal(@3); + } + expect(secondSequenceDeallocd).will.beTruthy(); + + @autoreleasepool { + expect([enumerator nextObject]).to.beNil(); + } + expect(thirdSequenceDeallocd).will.beTruthy(); + }); +}); + +it(@"shouldn't overflow the stack when deallocated on a background queue", ^{ + NSUInteger length = 10000; + NSMutableArray *values = [NSMutableArray arrayWithCapacity:length]; + for (NSUInteger i = 0; i < length; ++i) { + [values addObject:@(i)]; + } + + __block BOOL finished = NO; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + @autoreleasepool { + [[values.rac_sequence map:^(id value) { + return value; + }] array]; + } + + finished = YES; + }); + + NSTimeInterval oldTimeout = Expecta.asynchronousTestTimeout; + Expecta.asynchronousTestTimeout = DBL_MAX; + expect(finished).will.beTruthy(); + Expecta.asynchronousTestTimeout = oldTimeout; +}); + +describe(@"-foldLeftWithStart:reduce:", ^{ + it(@"should reduce with start first", ^{ + RACSequence *sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + NSNumber *result = [sequence foldLeftWithStart:@3 reduce:^(NSNumber *first, NSNumber *rest) { + return first; + }]; + expect(result).to.equal(@3); + }); + + it(@"should be left associative", ^{ + RACSequence *sequence = [[[RACSequence return:@1] concat:[RACSequence return:@2]] concat:[RACSequence return:@3]]; + NSNumber *result = [sequence foldLeftWithStart:@0 reduce:^(NSNumber *first, NSNumber *rest) { + int difference = first.intValue - rest.intValue; + return @(difference); + }]; + expect(result).to.equal(@-6); + }); +}); + +describe(@"-foldRightWithStart:reduce:", ^{ + it(@"should be lazy", ^{ + __block BOOL headInvoked = NO; + __block BOOL tailInvoked = NO; + RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^{ + headInvoked = YES; + return @0; + } tailBlock:^{ + tailInvoked = YES; + return [RACSequence return:@1]; + }]; + + NSNumber *result = [sequence foldRightWithStart:@2 reduce:^(NSNumber *first, RACSequence *rest) { + return first; + }]; + + expect(result).to.equal(@0); + expect(headInvoked).to.beTruthy(); + expect(tailInvoked).to.beFalsy(); + }); + + it(@"should reduce with start last", ^{ + RACSequence *sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + NSNumber *result = [sequence foldRightWithStart:@3 reduce:^(NSNumber *first, RACSequence *rest) { + return rest.head; + }]; + expect(result).to.equal(@3); + }); + + it(@"should be right associative", ^{ + RACSequence *sequence = [[[RACSequence return:@1] concat:[RACSequence return:@2]] concat:[RACSequence return:@3]]; + NSNumber *result = [sequence foldRightWithStart:@0 reduce:^(NSNumber *first, RACSequence *rest) { + int difference = first.intValue - [rest.head intValue]; + return @(difference); + }]; + expect(result).to.equal(@2); + }); +}); + +describe(@"-any", ^{ + __block RACSequence *sequence; + beforeEach(^{ + sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + }); + + it(@"should return true when at least one exists", ^{ + BOOL result = [sequence any:^ BOOL (NSNumber *value) { + return value.integerValue > 0; + }]; + expect(result).to.beTruthy(); + }); + + it(@"should return false when no such thing exists", ^{ + BOOL result = [sequence any:^ BOOL (NSNumber *value) { + return value.integerValue == 3; + }]; + expect(result).to.beFalsy(); + }); +}); + +describe(@"-all", ^{ + __block RACSequence *sequence; + beforeEach(^{ + sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + }); + + it(@"should return true when all values pass", ^{ + BOOL result = [sequence all:^ BOOL (NSNumber *value) { + return value.integerValue >= 0; + }]; + expect(result).to.beTruthy(); + }); + + it(@"should return false when at least one value fails", ^{ + BOOL result = [sequence all:^ BOOL (NSNumber *value) { + return value.integerValue < 2; + }]; + expect(result).to.beFalsy(); + }); +}); + +describe(@"-objectPassingTest:", ^{ + __block RACSequence *sequence; + beforeEach(^{ + sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; + }); + + it(@"should return leftmost object that passes the test", ^{ + NSNumber *result = [sequence objectPassingTest:^ BOOL (NSNumber *value) { + return value.intValue > 0; + }]; + expect(result).to.equal(@1); + }); + + it(@"should return nil if no objects pass the test", ^{ + NSNumber *result = [sequence objectPassingTest:^ BOOL (NSNumber *value) { + return value.intValue < 0; + }]; + expect(result).to.beNil(); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSerialDisposableSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSerialDisposableSpec.m new file mode 100644 index 0000000..cdf35f5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSerialDisposableSpec.m
@@ -0,0 +1,137 @@ +// +// RACSerialDisposableSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSerialDisposable.h" + +SpecBegin(RACSerialDisposable) + +it(@"should initialize with -init", ^{ + RACSerialDisposable *serial = [[RACSerialDisposable alloc] init]; + expect(serial).notTo.beNil(); + expect(serial.disposable).to.beNil(); +}); + +it(@"should initialize an inner disposable with -initWithBlock:", ^{ + __block BOOL disposed = NO; + RACSerialDisposable *serial = [RACSerialDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + expect(serial).notTo.beNil(); + expect(serial.disposable).notTo.beNil(); + + [serial.disposable dispose]; + expect(serial.disposed).to.beFalsy(); + expect(disposed).to.beTruthy(); +}); + +it(@"should initialize with a disposable", ^{ + RACDisposable *inner = [[RACDisposable alloc] init]; + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; + expect(serial).notTo.beNil(); + expect(serial.disposable).to.equal(inner); +}); + +it(@"should dispose of the inner disposable", ^{ + __block BOOL disposed = NO; + RACDisposable *inner = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; + expect(serial.disposed).to.beFalsy(); + expect(disposed).to.beFalsy(); + + [serial dispose]; + expect(serial.disposed).to.beTruthy(); + expect(serial.disposable).to.beNil(); + expect(disposed).to.beTruthy(); +}); + +it(@"should dispose of a new inner disposable if it's already been disposed", ^{ + __block BOOL disposed = NO; + RACDisposable *inner = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + RACSerialDisposable *serial = [[RACSerialDisposable alloc] init]; + expect(serial.disposed).to.beFalsy(); + + [serial dispose]; + expect(serial.disposed).to.beTruthy(); + expect(disposed).to.beFalsy(); + + serial.disposable = inner; + expect(disposed).to.beTruthy(); + expect(serial.disposable).to.beNil(); +}); + +it(@"should allow the inner disposable to be set to nil", ^{ + __block BOOL disposed = NO; + RACDisposable *inner = [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:inner]; + expect(disposed).to.beFalsy(); + + serial.disposable = nil; + expect(serial.disposable).to.beNil(); + + serial.disposable = inner; + expect(serial.disposable).to.equal(inner); + + [serial dispose]; + expect(disposed).to.beTruthy(); + expect(serial.disposable).to.beNil(); +}); + +it(@"should swap inner disposables", ^{ + __block BOOL firstDisposed = NO; + RACDisposable *first = [RACDisposable disposableWithBlock:^{ + firstDisposed = YES; + }]; + + __block BOOL secondDisposed = NO; + RACDisposable *second = [RACDisposable disposableWithBlock:^{ + secondDisposed = YES; + }]; + + RACSerialDisposable *serial = [RACSerialDisposable serialDisposableWithDisposable:first]; + expect([serial swapInDisposable:second]).to.equal(first); + + expect(serial.disposed).to.beFalsy(); + expect(firstDisposed).to.beFalsy(); + expect(secondDisposed).to.beFalsy(); + + [serial dispose]; + expect(serial.disposed).to.beTruthy(); + expect(serial.disposable).to.beNil(); + + expect(firstDisposed).to.beFalsy(); + expect(secondDisposed).to.beTruthy(); +}); + +it(@"should release the inner disposable upon deallocation", ^{ + __weak RACDisposable *weakInnerDisposable; + __weak RACSerialDisposable *weakSerialDisposable; + + @autoreleasepool { + RACDisposable *innerDisposable __attribute__((objc_precise_lifetime)) = [[RACDisposable alloc] init]; + weakInnerDisposable = innerDisposable; + + RACSerialDisposable *serialDisposable __attribute__((objc_precise_lifetime)) = [[RACSerialDisposable alloc] init]; + serialDisposable.disposable = innerDisposable; + weakSerialDisposable = serialDisposable; + } + + expect(weakSerialDisposable).to.beNil(); + expect(weakInnerDisposable).to.beNil(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalSpec.m new file mode 100644 index 0000000..21b50dd --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalSpec.m
@@ -0,0 +1,3877 @@ +// +// RACSignalSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/2/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACPropertySignalExamples.h" +#import "RACSequenceExamples.h" +#import "RACStreamExamples.h" +#import "RACTestObject.h" + +#import "EXTKeyPathCoding.h" +#import "NSObject+RACDeallocating.h" +#import "NSObject+RACPropertySubscribing.h" +#import "RACBehaviorSubject.h" +#import "RACCommand.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACEvent.h" +#import "RACGroupedSignal.h" +#import "RACMulticastConnection.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" +#import "RACSignal+Operations.h" +#import "RACSignalStartExamples.h" +#import "RACSubject.h" +#import "RACSubscriber+Private.h" +#import "RACSubscriber.h" +#import "RACTestScheduler.h" +#import "RACTuple.h" +#import "RACUnit.h" +#import <libkern/OSAtomic.h> + +// Set in a beforeAll below. +static NSError *RACSignalTestError; + +static NSString * const RACSignalMergeConcurrentCompletionExampleGroup = @"RACSignalMergeConcurrentCompletionExampleGroup"; +static NSString * const RACSignalMaxConcurrent = @"RACSignalMaxConcurrent"; +SharedExampleGroupsBegin(mergeConcurrentCompletionName); + +sharedExamplesFor(RACSignalMergeConcurrentCompletionExampleGroup, ^(NSDictionary *data) { + it(@"should complete only after the source and all its signals have completed", ^{ + RACSubject *subject1 = [RACSubject subject]; + RACSubject *subject2 = [RACSubject subject]; + RACSubject *subject3 = [RACSubject subject]; + + RACSubject *signalsSubject = [RACSubject subject]; + __block BOOL completed = NO; + [[signalsSubject flatten:[data[RACSignalMaxConcurrent] unsignedIntegerValue]] subscribeCompleted:^{ + completed = YES; + }]; + + [signalsSubject sendNext:subject1]; + [subject1 sendCompleted]; + + expect(completed).to.beFalsy(); + + [signalsSubject sendNext:subject2]; + [signalsSubject sendNext:subject3]; + + [signalsSubject sendCompleted]; + + expect(completed).to.beFalsy(); + + [subject2 sendCompleted]; + + expect(completed).to.beFalsy(); + + [subject3 sendCompleted]; + + expect(completed).to.beTruthy(); + }); +}); + +SharedExampleGroupsEnd + +SpecBegin(RACSignal) + +beforeAll(^{ + // We do this instead of a macro to ensure that to.equal() will work + // correctly (by matching identity), even if -[NSError isEqual:] is broken. + RACSignalTestError = [NSError errorWithDomain:@"foo" code:100 userInfo:nil]; +}); + +describe(@"RACStream", ^{ + id verifyValues = ^(RACSignal *signal, NSArray *expectedValues) { + expect(signal).notTo.beNil(); + + NSMutableArray *collectedValues = [NSMutableArray array]; + + __block BOOL success = NO; + __block NSError *error = nil; + [signal subscribeNext:^(id value) { + [collectedValues addObject:value]; + } error:^(NSError *receivedError) { + error = receivedError; + } completed:^{ + success = YES; + }]; + + expect(success).will.beTruthy(); + expect(error).to.beNil(); + expect(collectedValues).to.equal(expectedValues); + }; + + RACSignal *infiniteSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + __block volatile int32_t done = 0; + + [RACScheduler.mainThreadScheduler schedule:^{ + while (!done) { + [subscriber sendNext:RACUnit.defaultUnit]; + } + }]; + + return [RACDisposable disposableWithBlock:^{ + OSAtomicIncrement32Barrier(&done); + }]; + }]; + + itShouldBehaveLike(RACStreamExamples, ^{ + return @{ + RACStreamExamplesClass: RACSignal.class, + RACStreamExamplesVerifyValuesBlock: verifyValues, + RACStreamExamplesInfiniteStream: infiniteSignal + }; + }); +}); + +describe(@"-bind:", ^{ + __block RACSubject *signals; + __block BOOL disposed; + __block id lastValue; + __block RACSubject *values; + + beforeEach(^{ + // Tests send a (RACSignal, BOOL) pair that are used below in -bind:. + signals = [RACSubject subject]; + + disposed = NO; + RACSignal *source = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + [signals subscribe:subscriber]; + + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + + RACSignal *bind = [source bind:^{ + return ^(RACTuple *x, BOOL *stop) { + RACTupleUnpack(RACSignal *signal, NSNumber *stopValue) = x; + *stop = stopValue.boolValue; + return signal; + }; + }]; + + lastValue = nil; + [bind subscribeNext:^(id x) { + lastValue = x; + }]; + + // Send `bind` an open ended subject to subscribe to. These tests make + // use of this in two ways: + // 1. Used to test a regression bug where -bind: would not actually + // stop when instructed to. This bug manifested itself only when + // there were subscriptions that lived on past the point at which + // -bind: was stopped. This subject represents such a subscription. + // 2. Test that values sent by this subject are received by `bind`'s + // subscriber, even *after* -bind: has been instructed to stop. + values = [RACSubject subject]; + [signals sendNext:RACTuplePack(values, @NO)]; + expect(disposed).to.beFalsy(); + }); + + it(@"should dispose source signal when stopped with nil signal", ^{ + // Tell -bind: to stop by sending it a `nil` signal. + [signals sendNext:RACTuplePack(nil, @NO)]; + expect(disposed).to.beTruthy(); + + // Should still receive values sent after stopping. + expect(lastValue).to.beNil(); + [values sendNext:RACUnit.defaultUnit]; + expect(lastValue).to.equal(RACUnit.defaultUnit); + }); + + it(@"should dispose source signal when stop flag set to YES", ^{ + // Tell -bind: to stop by setting the stop flag to YES. + [signals sendNext:RACTuplePack([RACSignal return:@1], @YES)]; + expect(disposed).to.beTruthy(); + + // Should still recieve last signal sent at the time of setting stop to YES. + expect(lastValue).to.equal(@1); + + // Should still receive values sent after stopping. + [values sendNext:@2]; + expect(lastValue).to.equal(@2); + }); +}); + +describe(@"subscribing", ^{ + __block RACSignal *signal = nil; + id nextValueSent = @"1"; + + beforeEach(^{ + signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:nextValueSent]; + [subscriber sendCompleted]; + return nil; + }]; + }); + + it(@"should get next values", ^{ + __block id nextValueReceived = nil; + [signal subscribeNext:^(id x) { + nextValueReceived = x; + } error:^(NSError *error) { + + } completed:^{ + + }]; + + expect(nextValueReceived).to.equal(nextValueSent); + }); + + it(@"should get completed", ^{ + __block BOOL didGetCompleted = NO; + [signal subscribeNext:^(id x) { + + } error:^(NSError *error) { + + } completed:^{ + didGetCompleted = YES; + }]; + + expect(didGetCompleted).to.beTruthy(); + }); + + it(@"should not get an error", ^{ + __block BOOL didGetError = NO; + [signal subscribeNext:^(id x) { + + } error:^(NSError *error) { + didGetError = YES; + } completed:^{ + + }]; + + expect(didGetError).to.beFalsy(); + }); + + it(@"shouldn't get anything after dispose", ^{ + RACTestScheduler *scheduler = [[RACTestScheduler alloc] init]; + NSMutableArray *receivedValues = [NSMutableArray array]; + + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:@0]; + + [scheduler afterDelay:0 schedule:^{ + [subscriber sendNext:@1]; + }]; + + return nil; + }]; + + RACDisposable *disposable = [signal subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + NSArray *expectedValues = @[ @0 ]; + expect(receivedValues).to.equal(expectedValues); + + [disposable dispose]; + [scheduler stepAll]; + + expect(receivedValues).to.equal(expectedValues); + }); + + it(@"should have a current scheduler in didSubscribe block", ^{ + __block RACScheduler *currentScheduler; + RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + currentScheduler = RACScheduler.currentScheduler; + [subscriber sendCompleted]; + return nil; + }]; + + [signal subscribeNext:^(id x) {}]; + expect(currentScheduler).notTo.beNil(); + + currentScheduler = nil; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [signal subscribeNext:^(id x) {}]; + }); + expect(currentScheduler).willNot.beNil(); + }); + + it(@"should automatically dispose of other subscriptions from +createSignal:", ^{ + __block BOOL innerDisposed = NO; + + RACSignal *innerSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + innerDisposed = YES; + }]; + }]; + + RACSignal *outerSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [innerSignal subscribe:subscriber]; + return nil; + }]; + + RACDisposable *disposable = [outerSignal subscribeCompleted:^{}]; + expect(disposable).notTo.beNil(); + expect(innerDisposed).to.beFalsy(); + + [disposable dispose]; + expect(innerDisposed).to.beTruthy(); + }); +}); + +describe(@"-takeUntil:", ^{ + it(@"should support value as trigger", ^{ + __block BOOL shouldBeGettingItems = YES; + RACSubject *subject = [RACSubject subject]; + RACSubject *cutOffSubject = [RACSubject subject]; + [[subject takeUntil:cutOffSubject] subscribeNext:^(id x) { + expect(shouldBeGettingItems).to.beTruthy(); + }]; + + shouldBeGettingItems = YES; + [subject sendNext:@"test 1"]; + [subject sendNext:@"test 2"]; + + [cutOffSubject sendNext:[RACUnit defaultUnit]]; + + shouldBeGettingItems = NO; + [subject sendNext:@"test 3"]; + }); + + it(@"should support completion as trigger", ^{ + __block BOOL shouldBeGettingItems = YES; + RACSubject *subject = [RACSubject subject]; + RACSubject *cutOffSubject = [RACSubject subject]; + [[subject takeUntil:cutOffSubject] subscribeNext:^(id x) { + expect(shouldBeGettingItems).to.beTruthy(); + }]; + + [cutOffSubject sendCompleted]; + + shouldBeGettingItems = NO; + [subject sendNext:@"should not go through"]; + }); + + it(@"should squelch any values sent immediately upon subscription", ^{ + RACSignal *valueSignal = [RACSignal return:RACUnit.defaultUnit]; + RACSignal *cutOffSignal = [RACSignal empty]; + + __block BOOL gotNext = NO; + __block BOOL completed = NO; + + [[valueSignal takeUntil:cutOffSignal] subscribeNext:^(id _) { + gotNext = YES; + } completed:^{ + completed = YES; + }]; + + expect(gotNext).to.beFalsy(); + expect(completed).to.beTruthy(); + }); +}); + +describe(@"-takeUntilReplacement:", ^{ + it(@"should forward values from the receiver until it's replaced", ^{ + RACSubject *receiver = [RACSubject subject]; + RACSubject *replacement = [RACSubject subject]; + + NSMutableArray *receivedValues = [NSMutableArray array]; + + [[receiver takeUntilReplacement:replacement] subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + expect(receivedValues).to.equal(@[]); + + [receiver sendNext:@1]; + expect(receivedValues).to.equal(@[ @1 ]); + + [receiver sendNext:@2]; + expect(receivedValues).to.equal((@[ @1, @2 ])); + + [replacement sendNext:@3]; + expect(receivedValues).to.equal((@[ @1, @2, @3 ])); + + [receiver sendNext:@4]; + expect(receivedValues).to.equal((@[ @1, @2, @3 ])); + + [replacement sendNext:@5]; + expect(receivedValues).to.equal((@[ @1, @2, @3, @5 ])); + }); + + it(@"should forward error from the receiver", ^{ + RACSubject *receiver = [RACSubject subject]; + __block BOOL receivedError = NO; + + [[receiver takeUntilReplacement:RACSignal.never] subscribeError:^(NSError *error) { + receivedError = YES; + }]; + + [receiver sendError:nil]; + expect(receivedError).to.beTruthy(); + }); + + it(@"should not forward completed from the receiver", ^{ + RACSubject *receiver = [RACSubject subject]; + __block BOOL receivedCompleted = NO; + + [[receiver takeUntilReplacement:RACSignal.never] subscribeCompleted: ^{ + receivedCompleted = YES; + }]; + + [receiver sendCompleted]; + expect(receivedCompleted).to.beFalsy(); + }); + + it(@"should forward error from the replacement signal", ^{ + RACSubject *replacement = [RACSubject subject]; + __block BOOL receivedError = NO; + + [[RACSignal.never takeUntilReplacement:replacement] subscribeError:^(NSError *error) { + receivedError = YES; + }]; + + [replacement sendError:nil]; + expect(receivedError).to.beTruthy(); + }); + + it(@"should forward completed from the replacement signal", ^{ + RACSubject *replacement = [RACSubject subject]; + __block BOOL receivedCompleted = NO; + + [[RACSignal.never takeUntilReplacement:replacement] subscribeCompleted: ^{ + receivedCompleted = YES; + }]; + + [replacement sendCompleted]; + expect(receivedCompleted).to.beTruthy(); + }); + + it(@"should not forward values from the receiver if both send synchronously", ^{ + RACSignal *receiver = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:@2]; + [subscriber sendNext:@3]; + return nil; + }]; + RACSignal *replacement = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@4]; + [subscriber sendNext:@5]; + [subscriber sendNext:@6]; + return nil; + }]; + + NSMutableArray *receivedValues = [NSMutableArray array]; + + [[receiver takeUntilReplacement:replacement] subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + expect(receivedValues).to.equal((@[ @4, @5, @6 ])); + }); + + it(@"should dispose of the receiver when it's disposed of", ^{ + __block BOOL receiverDisposed = NO; + RACSignal *receiver = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + receiverDisposed = YES; + }]; + }]; + + [[[receiver takeUntilReplacement:RACSignal.never] subscribeCompleted:^{}] dispose]; + + expect(receiverDisposed).to.beTruthy(); + }); + + it(@"should dispose of the replacement signal when it's disposed of", ^{ + __block BOOL replacementDisposed = NO; + RACSignal *replacement = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + replacementDisposed = YES; + }]; + }]; + + [[[RACSignal.never takeUntilReplacement:replacement] subscribeCompleted:^{}] dispose]; + + expect(replacementDisposed).to.beTruthy(); + }); + + it(@"should dispose of the receiver when the replacement signal sends an event", ^{ + __block BOOL receiverDisposed = NO; + RACSignal *receiver = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + receiverDisposed = YES; + }]; + }]; + RACSubject *replacement = [RACSubject subject]; + + [[receiver takeUntilReplacement:replacement] subscribeCompleted:^{}]; + + expect(receiverDisposed).to.beFalsy(); + + [replacement sendNext:nil]; + + expect(receiverDisposed).to.beTruthy(); + }); +}); + +describe(@"disposal", ^{ + it(@"should dispose of the didSubscribe disposable", ^{ + __block BOOL innerDisposed = NO; + RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + innerDisposed = YES; + }]; + }]; + + expect(innerDisposed).to.beFalsy(); + + RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; + expect(disposable).notTo.beNil(); + + [disposable dispose]; + expect(innerDisposed).to.beTruthy(); + }); + + it(@"should dispose of the didSubscribe disposable asynchronously", ^{ + __block BOOL innerDisposed = NO; + RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + innerDisposed = YES; + }]; + }]; + + [[RACScheduler scheduler] schedule:^{ + RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; + [disposable dispose]; + }]; + + expect(innerDisposed).will.beTruthy(); + }); +}); + +describe(@"querying", ^{ + __block RACSignal *signal = nil; + id nextValueSent = @"1"; + + beforeEach(^{ + signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:nextValueSent]; + [subscriber sendNext:@"other value"]; + [subscriber sendCompleted]; + return nil; + }]; + }); + + it(@"should return first 'next' value with -firstOrDefault:success:error:", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:@2]; + [subscriber sendNext:@3]; + [subscriber sendCompleted]; + return nil; + }]; + + expect(signal).notTo.beNil(); + + __block BOOL success = NO; + __block NSError *error = nil; + expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@1); + expect(success).to.beTruthy(); + expect(error).to.beNil(); + }); + + it(@"should return first default value with -firstOrDefault:success:error:", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendCompleted]; + return nil; + }]; + + expect(signal).notTo.beNil(); + + __block BOOL success = NO; + __block NSError *error = nil; + expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@5); + expect(success).to.beTruthy(); + expect(error).to.beNil(); + }); + + it(@"should return error with -firstOrDefault:success:error:", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendError:RACSignalTestError]; + return nil; + }]; + + expect(signal).notTo.beNil(); + + __block BOOL success = NO; + __block NSError *error = nil; + expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@5); + expect(success).to.beFalsy(); + expect(error).to.equal(RACSignalTestError); + }); + + it(@"shouldn't crash when returning an error from a background scheduler", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [[RACScheduler scheduler] schedule:^{ + [subscriber sendError:RACSignalTestError]; + }]; + + return nil; + }]; + + expect(signal).notTo.beNil(); + + __block BOOL success = NO; + __block NSError *error = nil; + expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@5); + expect(success).to.beFalsy(); + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should terminate the subscription after returning from -firstOrDefault:success:error:", ^{ + __block BOOL disposed = NO; + RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + [subscriber sendNext:RACUnit.defaultUnit]; + + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + + expect(signal).notTo.beNil(); + expect(disposed).to.beFalsy(); + + expect([signal firstOrDefault:nil success:NULL error:NULL]).to.equal(RACUnit.defaultUnit); + expect(disposed).to.beTruthy(); + }); + + it(@"should return YES from -waitUntilCompleted: when successful", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:RACUnit.defaultUnit]; + [subscriber sendCompleted]; + return nil; + }]; + + __block NSError *error = nil; + expect([signal waitUntilCompleted:&error]).to.beTruthy(); + expect(error).to.beNil(); + }); + + it(@"should return NO from -waitUntilCompleted: upon error", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:RACUnit.defaultUnit]; + [subscriber sendError:RACSignalTestError]; + return nil; + }]; + + __block NSError *error = nil; + expect([signal waitUntilCompleted:&error]).to.beFalsy(); + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should return a delayed value from -asynchronousFirstOrDefault:success:error:", ^{ + RACSignal *signal = [[RACSignal return:RACUnit.defaultUnit] delay:0]; + + __block BOOL scheduledBlockRan = NO; + [RACScheduler.mainThreadScheduler schedule:^{ + scheduledBlockRan = YES; + }]; + + expect(scheduledBlockRan).to.beFalsy(); + + BOOL success = NO; + NSError *error = nil; + id value = [signal asynchronousFirstOrDefault:nil success:&success error:&error]; + + expect(scheduledBlockRan).to.beTruthy(); + + expect(value).to.equal(RACUnit.defaultUnit); + expect(success).to.beTruthy(); + expect(error).to.beNil(); + }); + + it(@"should return a default value from -asynchronousFirstOrDefault:success:error:", ^{ + RACSignal *signal = [[RACSignal error:RACSignalTestError] delay:0]; + + __block BOOL scheduledBlockRan = NO; + [RACScheduler.mainThreadScheduler schedule:^{ + scheduledBlockRan = YES; + }]; + + expect(scheduledBlockRan).to.beFalsy(); + + BOOL success = NO; + NSError *error = nil; + id value = [signal asynchronousFirstOrDefault:RACUnit.defaultUnit success:&success error:&error]; + + expect(scheduledBlockRan).to.beTruthy(); + + expect(value).to.equal(RACUnit.defaultUnit); + expect(success).to.beFalsy(); + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should return a delayed error from -asynchronousFirstOrDefault:success:error:", ^{ + RACSignal *signal = [[RACSignal + createSignal:^(id<RACSubscriber> subscriber) { + return [[RACScheduler scheduler] schedule:^{ + [subscriber sendError:RACSignalTestError]; + }]; + }] + deliverOn:RACScheduler.mainThreadScheduler]; + + __block NSError *error = nil; + __block BOOL success = NO; + expect([signal asynchronousFirstOrDefault:nil success:&success error:&error]).to.beNil(); + + expect(success).to.beFalsy(); + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should terminate the subscription after returning from -asynchronousFirstOrDefault:success:error:", ^{ + __block BOOL disposed = NO; + RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + [[RACScheduler scheduler] schedule:^{ + [subscriber sendNext:RACUnit.defaultUnit]; + }]; + + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + + expect(signal).notTo.beNil(); + expect(disposed).to.beFalsy(); + + expect([signal asynchronousFirstOrDefault:nil success:NULL error:NULL]).to.equal(RACUnit.defaultUnit); + expect(disposed).will.beTruthy(); + }); + + it(@"should return a delayed success from -asynchronouslyWaitUntilCompleted:", ^{ + RACSignal *signal = [[RACSignal return:RACUnit.defaultUnit] delay:0]; + + __block BOOL scheduledBlockRan = NO; + [RACScheduler.mainThreadScheduler schedule:^{ + scheduledBlockRan = YES; + }]; + + expect(scheduledBlockRan).to.beFalsy(); + + NSError *error = nil; + BOOL success = [signal asynchronouslyWaitUntilCompleted:&error]; + + expect(scheduledBlockRan).to.beTruthy(); + + expect(success).to.beTruthy(); + expect(error).to.beNil(); + }); +}); + +describe(@"continuation", ^{ + it(@"should repeat after completion", ^{ + __block NSUInteger numberOfSubscriptions = 0; + RACScheduler *scheduler = [RACScheduler scheduler]; + + RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + return [scheduler schedule:^{ + if (numberOfSubscriptions == 3) { + [subscriber sendError:RACSignalTestError]; + return; + } + + numberOfSubscriptions++; + + [subscriber sendNext:@"1"]; + [subscriber sendCompleted]; + [subscriber sendError:RACSignalTestError]; + }]; + }]; + + __block NSUInteger nextCount = 0; + __block BOOL gotCompleted = NO; + [[signal repeat] subscribeNext:^(id x) { + nextCount++; + } error:^(NSError *error) { + + } completed:^{ + gotCompleted = YES; + }]; + + expect(nextCount).will.equal(3); + expect(gotCompleted).to.beFalsy(); + }); + + it(@"should stop repeating when disposed", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendCompleted]; + return nil; + }]; + + NSMutableArray *values = [NSMutableArray array]; + + __block BOOL completed = NO; + __block RACDisposable *disposable = [[signal repeat] subscribeNext:^(id x) { + [values addObject:x]; + [disposable dispose]; + } completed:^{ + completed = YES; + }]; + + expect(values).will.equal(@[ @1 ]); + expect(completed).to.beFalsy(); + }); + + it(@"should stop repeating when disposed by -take:", ^{ + RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendCompleted]; + return nil; + }]; + + NSMutableArray *values = [NSMutableArray array]; + + __block BOOL completed = NO; + [[[signal repeat] take:1] subscribeNext:^(id x) { + [values addObject:x]; + } completed:^{ + completed = YES; + }]; + + expect(values).will.equal(@[ @1 ]); + expect(completed).to.beTruthy(); + }); +}); + +describe(@"+combineLatestWith:", ^{ + __block RACSubject *subject1 = nil; + __block RACSubject *subject2 = nil; + __block RACSignal *combined = nil; + + beforeEach(^{ + subject1 = [RACSubject subject]; + subject2 = [RACSubject subject]; + combined = [RACSignal combineLatest:@[ subject1, subject2 ]]; + }); + + it(@"should send next only once both signals send next", ^{ + __block RACTuple *tuple; + + [combined subscribeNext:^(id x) { + tuple = x; + }]; + + expect(tuple).to.beNil(); + + [subject1 sendNext:@"1"]; + expect(tuple).to.beNil(); + + [subject2 sendNext:@"2"]; + expect(tuple).to.equal(RACTuplePack(@"1", @"2")); + }); + + it(@"should send nexts when either signal sends multiple times", ^{ + NSMutableArray *results = [NSMutableArray array]; + [combined subscribeNext:^(id x) { + [results addObject:x]; + }]; + + [subject1 sendNext:@"1"]; + [subject2 sendNext:@"2"]; + + [subject1 sendNext:@"3"]; + [subject2 sendNext:@"4"]; + + expect(results[0]).to.equal(RACTuplePack(@"1", @"2")); + expect(results[1]).to.equal(RACTuplePack(@"3", @"2")); + expect(results[2]).to.equal(RACTuplePack(@"3", @"4")); + }); + + it(@"should complete when only both signals complete", ^{ + __block BOOL completed = NO; + + [combined subscribeCompleted:^{ + completed = YES; + }]; + + expect(completed).to.beFalsy(); + + [subject1 sendCompleted]; + expect(completed).to.beFalsy(); + + [subject2 sendCompleted]; + expect(completed).to.beTruthy(); + }); + + it(@"should error when either signal errors", ^{ + __block NSError *receivedError = nil; + [combined subscribeError:^(NSError *error) { + receivedError = error; + }]; + + [subject1 sendError:RACSignalTestError]; + expect(receivedError).to.equal(RACSignalTestError); + }); + + it(@"shouldn't create a retain cycle", ^{ + __block BOOL subjectDeallocd = NO; + __block BOOL signalDeallocd = NO; + + @autoreleasepool { + RACSubject *subject __attribute__((objc_precise_lifetime)) = [RACSubject subject]; + [subject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + subjectDeallocd = YES; + }]]; + + RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal combineLatest:@[ subject ]]; + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + signalDeallocd = YES; + }]]; + + [signal subscribeCompleted:^{}]; + [subject sendCompleted]; + } + + expect(subjectDeallocd).will.beTruthy(); + expect(signalDeallocd).will.beTruthy(); + }); + + it(@"should combine the same signal", ^{ + RACSignal *combined = [subject1 combineLatestWith:subject1]; + + __block RACTuple *tuple; + [combined subscribeNext:^(id x) { + tuple = x; + }]; + + [subject1 sendNext:@"foo"]; + expect(tuple).to.equal(RACTuplePack(@"foo", @"foo")); + + [subject1 sendNext:@"bar"]; + expect(tuple).to.equal(RACTuplePack(@"bar", @"bar")); + }); + + it(@"should combine the same side-effecting signal", ^{ + __block NSUInteger counter = 0; + RACSignal *sideEffectingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@(++counter)]; + [subscriber sendCompleted]; + return nil; + }]; + + RACSignal *combined = [sideEffectingSignal combineLatestWith:sideEffectingSignal]; + expect(counter).to.equal(0); + + NSMutableArray *receivedValues = [NSMutableArray array]; + [combined subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + expect(counter).to.equal(2); + + NSArray *expected = @[ RACTuplePack(@1, @2) ]; + expect(receivedValues).to.equal(expected); + }); +}); + +describe(@"+combineLatest:", ^{ + it(@"should return tuples even when only combining one signal", ^{ + RACSubject *subject = [RACSubject subject]; + + __block RACTuple *tuple; + [[RACSignal combineLatest:@[ subject ]] subscribeNext:^(id x) { + tuple = x; + }]; + + [subject sendNext:@"foo"]; + expect(tuple).to.equal(RACTuplePack(@"foo")); + }); + + it(@"should complete immediately when not given any signals", ^{ + RACSignal *signal = [RACSignal combineLatest:@[]]; + + __block BOOL completed = NO; + [signal subscribeCompleted:^{ + completed = YES; + }]; + + expect(completed).to.beTruthy(); + }); + + it(@"should only complete after all its signals complete", ^{ + RACSubject *subject1 = [RACSubject subject]; + RACSubject *subject2 = [RACSubject subject]; + RACSubject *subject3 = [RACSubject subject]; + RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject3 ]]; + + __block BOOL completed = NO; + [combined subscribeCompleted:^{ + completed = YES; + }]; + + expect(completed).to.beFalsy(); + + [subject1 sendCompleted]; + expect(completed).to.beFalsy(); + + [subject2 sendCompleted]; + expect(completed).to.beFalsy(); + + [subject3 sendCompleted]; + expect(completed).to.beTruthy(); + }); +}); + +describe(@"+combineLatest:reduce:", ^{ + __block RACSubject *subject1; + __block RACSubject *subject2; + __block RACSubject *subject3; + + beforeEach(^{ + subject1 = [RACSubject subject]; + subject2 = [RACSubject subject]; + subject3 = [RACSubject subject]; + }); + + it(@"should send nils for nil values", ^{ + __block id receivedVal1; + __block id receivedVal2; + __block id receivedVal3; + + RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject3 ] reduce:^ id (id val1, id val2, id val3) { + receivedVal1 = val1; + receivedVal2 = val2; + receivedVal3 = val3; + return nil; + }]; + + __block BOOL gotValue = NO; + [combined subscribeNext:^(id x) { + gotValue = YES; + }]; + + [subject1 sendNext:nil]; + [subject2 sendNext:nil]; + [subject3 sendNext:nil]; + + expect(gotValue).to.beTruthy(); + expect(receivedVal1).to.beNil(); + expect(receivedVal2).to.beNil(); + expect(receivedVal3).to.beNil(); + }); + + it(@"should send the return result of the reduce block", ^{ + RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject3 ] reduce:^(NSString *string1, NSString *string2, NSString *string3) { + return [NSString stringWithFormat:@"%@: %@%@", string1, string2, string3]; + }]; + + __block id received; + [combined subscribeNext:^(id x) { + received = x; + }]; + + [subject1 sendNext:@"hello"]; + [subject2 sendNext:@"world"]; + [subject3 sendNext:@"!!1"]; + + expect(received).to.equal(@"hello: world!!1"); + }); + + it(@"should handle multiples of the same signals", ^{ + RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject1, subject3 ] reduce:^(NSString *string1, NSString *string2, NSString *string3, NSString *string4) { + return [NSString stringWithFormat:@"%@ : %@ = %@ : %@", string1, string2, string3, string4]; + }]; + + NSMutableArray *receivedValues = NSMutableArray.array; + + [combined subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + [subject1 sendNext:@"apples"]; + expect(receivedValues.lastObject).to.beNil(); + + [subject2 sendNext:@"oranges"]; + expect(receivedValues.lastObject).to.beNil(); + + [subject3 sendNext:@"cattle"]; + expect(receivedValues.lastObject).to.equal(@"apples : oranges = apples : cattle"); + + [subject1 sendNext:@"horses"]; + expect(receivedValues.lastObject).to.equal(@"horses : oranges = horses : cattle"); + }); + + it(@"should handle multiples of the same side-effecting signal", ^{ + __block NSUInteger counter = 0; + RACSignal *sideEffectingSignal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:@(++counter)]; + [subscriber sendCompleted]; + return nil; + }]; + + RACSignal *combined = [RACSignal combineLatest:@[ sideEffectingSignal, sideEffectingSignal, sideEffectingSignal ] reduce:^(id x, id y, id z) { + return [NSString stringWithFormat:@"%@%@%@", x, y, z]; + }]; + + NSMutableArray *receivedValues = [NSMutableArray array]; + expect(counter).to.equal(0); + + [combined subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + expect(counter).to.equal(3); + expect(receivedValues).to.equal(@[ @"123" ]); + }); +}); + +describe(@"distinctUntilChanged", ^{ + it(@"should only send values that are distinct from the previous value", ^{ + RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:@2]; + [subscriber sendNext:@2]; + [subscriber sendNext:@1]; + [subscriber sendNext:@1]; + [subscriber sendCompleted]; + return nil; + }] distinctUntilChanged]; + + NSArray *values = sub.toArray; + NSArray *expected = @[ @1, @2, @1 ]; + expect(values).to.equal(expected); + }); + + it(@"shouldn't consider nils to always be distinct", ^{ + RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:nil]; + [subscriber sendNext:nil]; + [subscriber sendNext:nil]; + [subscriber sendNext:@1]; + [subscriber sendCompleted]; + return nil; + }] distinctUntilChanged]; + + NSArray *values = sub.toArray; + NSArray *expected = @[ @1, [NSNull null], @1 ]; + expect(values).to.equal(expected); + }); + + it(@"should consider initial nil to be distinct", ^{ + RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:nil]; + [subscriber sendNext:nil]; + [subscriber sendNext:@1]; + [subscriber sendCompleted]; + return nil; + }] distinctUntilChanged]; + + NSArray *values = sub.toArray; + NSArray *expected = @[ [NSNull null], @1 ]; + expect(values).to.equal(expected); + }); +}); + +describe(@"RACObserve", ^{ + __block RACTestObject *testObject; + + beforeEach(^{ + testObject = [[RACTestObject alloc] init]; + }); + + it(@"should work with object properties", ^{ + NSArray *expected = @[ @"hello", @"world" ]; + testObject.objectValue = expected[0]; + + NSMutableArray *valuesReceived = [NSMutableArray array]; + [RACObserve(testObject, objectValue) subscribeNext:^(id x) { + [valuesReceived addObject:x]; + }]; + + testObject.objectValue = expected[1]; + + expect(valuesReceived).to.equal(expected); + }); + + it(@"should work with non-object properties", ^{ + NSArray *expected = @[ @42, @43 ]; + testObject.integerValue = [expected[0] integerValue]; + + NSMutableArray *valuesReceived = [NSMutableArray array]; + [RACObserve(testObject, integerValue) subscribeNext:^(id x) { + [valuesReceived addObject:x]; + }]; + + testObject.integerValue = [expected[1] integerValue]; + + expect(valuesReceived).to.equal(expected); + }); + + it(@"should read the initial value upon subscription", ^{ + testObject.objectValue = @"foo"; + + RACSignal *signal = RACObserve(testObject, objectValue); + testObject.objectValue = @"bar"; + + expect([signal first]).to.equal(@"bar"); + }); +}); + +describe(@"-setKeyPath:onObject:", ^{ + id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { + [signal setKeyPath:keyPath onObject:testObject nilValue:nilValue]; + }; + + itShouldBehaveLike(RACPropertySignalExamples, ^{ + return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; + }); + + it(@"shouldn't send values to dealloc'd objects", ^{ + RACSubject *subject = [RACSubject subject]; + @autoreleasepool { + RACTestObject *testObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; + [subject setKeyPath:@keypath(testObject.objectValue) onObject:testObject]; + expect(testObject.objectValue).to.beNil(); + + [subject sendNext:@1]; + expect(testObject.objectValue).to.equal(@1); + + [subject sendNext:@2]; + expect(testObject.objectValue).to.equal(@2); + } + + // This shouldn't do anything. + [subject sendNext:@3]; + }); + + it(@"should allow a new derivation after the signal's completed", ^{ + RACSubject *subject1 = [RACSubject subject]; + RACTestObject *testObject = [[RACTestObject alloc] init]; + [subject1 setKeyPath:@keypath(testObject.objectValue) onObject:testObject]; + [subject1 sendCompleted]; + + RACSubject *subject2 = [RACSubject subject]; + // This will assert if the previous completion didn't dispose of the + // subscription. + [subject2 setKeyPath:@keypath(testObject.objectValue) onObject:testObject]; + }); + + it(@"should set the given value when nil is received", ^{ + RACSubject *subject = [RACSubject subject]; + RACTestObject *testObject = [[RACTestObject alloc] init]; + [subject setKeyPath:@keypath(testObject.integerValue) onObject:testObject nilValue:@5]; + + [subject sendNext:@1]; + expect(testObject.integerValue).to.equal(1); + + [subject sendNext:nil]; + expect(testObject.integerValue).to.equal(5); + + [subject sendCompleted]; + expect(testObject.integerValue).to.equal(5); + }); + + it(@"should keep object alive over -sendNext:", ^{ + RACSubject *subject = [RACSubject subject]; + __block RACTestObject *testObject = [[RACTestObject alloc] init]; + __block id deallocValue; + + __unsafe_unretained RACTestObject *unsafeTestObject = testObject; + [testObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocValue = unsafeTestObject.slowObjectValue; + }]]; + + [subject setKeyPath:@keypath(testObject.slowObjectValue) onObject:testObject]; + expect(testObject.slowObjectValue).to.beNil(); + + // Attempt to deallocate concurrently. + [[RACScheduler scheduler] afterDelay:0.01 schedule:^{ + testObject = nil; + }]; + + expect(deallocValue).to.beNil(); + [subject sendNext:@1]; + expect(deallocValue).to.equal(@1); + }); +}); + +describe(@"memory management", ^{ + it(@"should dealloc signals if the signal does nothing", ^{ + __block BOOL deallocd = NO; + @autoreleasepool { + RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + return nil; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + } + + expect(deallocd).will.beTruthy(); + }); + + it(@"should retain signals for a single run loop iteration", ^{ + __block BOOL deallocd = NO; + + @autoreleasepool { + RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + return nil; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + } + + expect(deallocd).to.beFalsy(); + expect(deallocd).will.beTruthy(); + }); + + it(@"should dealloc signals if the signal immediately completes", ^{ + __block BOOL deallocd = NO; + @autoreleasepool { + __block BOOL done = NO; + + RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendCompleted]; + return nil; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + + [signal subscribeCompleted:^{ + done = YES; + }]; + + expect(done).will.beTruthy(); + } + + expect(deallocd).will.beTruthy(); + }); + + it(@"should dealloc a replay subject if it completes immediately", ^{ + __block BOOL completed = NO; + __block BOOL deallocd = NO; + @autoreleasepool { + RACReplaySubject *subject __attribute__((objc_precise_lifetime)) = [RACReplaySubject subject]; + [subject sendCompleted]; + + [subject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + + [subject subscribeCompleted:^{ + completed = YES; + }]; + } + + expect(completed).will.beTruthy(); + + expect(deallocd).will.beTruthy(); + }); + + it(@"should dealloc if the signal was created on a background queue", ^{ + __block BOOL completed = NO; + __block BOOL deallocd = NO; + @autoreleasepool { + [[RACScheduler scheduler] schedule:^{ + RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendCompleted]; + return nil; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + + [signal subscribeCompleted:^{ + completed = YES; + }]; + }]; + } + + expect(completed).will.beTruthy(); + + expect(deallocd).will.beTruthy(); + }); + + it(@"should dealloc if the signal was created on a background queue, never gets any subscribers, and the background queue gets delayed", ^{ + __block BOOL deallocd = NO; + @autoreleasepool { + [[RACScheduler scheduler] schedule:^{ + RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + return nil; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + + [NSThread sleepForTimeInterval:1]; + + expect(deallocd).to.beFalsy(); + }]; + } + + // The default test timeout is 1s so we'd race to see if the queue delay + // or default timeout happens first. To avoid that, just bump the + // timeout slightly for this test. + NSTimeInterval originalTestTimeout = Expecta.asynchronousTestTimeout; + Expecta.asynchronousTestTimeout = 1.1f; + expect(deallocd).will.beTruthy(); + Expecta.asynchronousTestTimeout = originalTestTimeout; + }); + + it(@"should retain signals when subscribing", ^{ + __block BOOL deallocd = NO; + + RACDisposable *disposable; + @autoreleasepool { + @autoreleasepool { + @autoreleasepool { + RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + return nil; + }]; + + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocd = YES; + }]]; + + disposable = [signal subscribeCompleted:^{}]; + } + + // Spin the run loop to account for RAC magic that retains the + // signal for a single iteration. + [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; + } + + expect(deallocd).to.beFalsy(); + + [disposable dispose]; + } + expect(deallocd).will.beTruthy(); + }); + + it(@"should retain intermediate signals when subscribing", ^{ + RACSubject *subject = [RACSubject subject]; + expect(subject).notTo.beNil(); + + __block BOOL gotNext = NO; + __block BOOL completed = NO; + + RACDisposable *disposable; + + @autoreleasepool { + @autoreleasepool { + RACSignal *intermediateSignal = [subject doNext:^(id _) { + gotNext = YES; + }]; + + expect(intermediateSignal).notTo.beNil(); + + disposable = [intermediateSignal subscribeCompleted:^{ + completed = YES; + }]; + } + + // Spin the run loop to account for RAC magic that retains the + // signal for a single iteration. + [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; + } + + [subject sendNext:@5]; + expect(gotNext).to.beTruthy(); + + [subject sendCompleted]; + expect(completed).to.beTruthy(); + + [disposable dispose]; + }); +}); + +describe(@"-merge:", ^{ + __block RACSubject *sub1; + __block RACSubject *sub2; + __block RACSignal *merged; + beforeEach(^{ + sub1 = [RACSubject subject]; + sub2 = [RACSubject subject]; + merged = [sub1 merge:sub2]; + }); + + it(@"should send all values from both signals", ^{ + NSMutableArray *values = [NSMutableArray array]; + [merged subscribeNext:^(id x) { + [values addObject:x]; + }]; + + [sub1 sendNext:@1]; + [sub2 sendNext:@2]; + [sub2 sendNext:@3]; + [sub1 sendNext:@4]; + + NSArray *expected = @[ @1, @2, @3, @4 ]; + expect(values).to.equal(expected); + }); + + it(@"should send an error if one occurs", ^{ + __block NSError *errorReceived; + [merged subscribeError:^(NSError *error) { + errorReceived = error; + }]; + + [sub1 sendError:RACSignalTestError]; + expect(errorReceived).to.equal(RACSignalTestError); + }); + + it(@"should complete only after both signals complete", ^{ + NSMutableArray *values = [NSMutableArray array]; + __block BOOL completed = NO; + [merged subscribeNext:^(id x) { + [values addObject:x]; + } completed:^{ + completed = YES; + }]; + + [sub1 sendNext:@1]; + [sub2 sendNext:@2]; + [sub2 sendNext:@3]; + [sub2 sendCompleted]; + expect(completed).to.beFalsy(); + + [sub1 sendNext:@4]; + [sub1 sendCompleted]; + expect(completed).to.beTruthy(); + + NSArray *expected = @[ @1, @2, @3, @4 ]; + expect(values).to.equal(expected); + }); + + it(@"should complete only after both signals complete for any number of subscribers", ^{ + __block BOOL completed1 = NO; + __block BOOL completed2 = NO; + [merged subscribeCompleted:^{ + completed1 = YES; + }]; + + [merged subscribeCompleted:^{ + completed2 = YES; + }]; + + expect(completed1).to.beFalsy(); + expect(completed2).to.beFalsy(); + + [sub1 sendCompleted]; + [sub2 sendCompleted]; + expect(completed1).to.beTruthy(); + expect(completed2).to.beTruthy(); + }); +}); + +describe(@"+merge:", ^{ + __block RACSubject *sub1; + __block RACSubject *sub2; + __block RACSignal *merged; + beforeEach(^{ + sub1 = [RACSubject subject]; + sub2 = [RACSubject subject]; + merged = [RACSignal merge:@[ sub1, sub2 ].objectEnumerator]; + }); + + it(@"should send all values from both signals", ^{ + NSMutableArray *values = [NSMutableArray array]; + [merged subscribeNext:^(id x) { + [values addObject:x]; + }]; + + [sub1 sendNext:@1]; + [sub2 sendNext:@2]; + [sub2 sendNext:@3]; + [sub1 sendNext:@4]; + + NSArray *expected = @[ @1, @2, @3, @4 ]; + expect(values).to.equal(expected); + }); + + it(@"should send an error if one occurs", ^{ + __block NSError *errorReceived; + [merged subscribeError:^(NSError *error) { + errorReceived = error; + }]; + + [sub1 sendError:RACSignalTestError]; + expect(errorReceived).to.equal(RACSignalTestError); + }); + + it(@"should complete only after both signals complete", ^{ + NSMutableArray *values = [NSMutableArray array]; + __block BOOL completed = NO; + [merged subscribeNext:^(id x) { + [values addObject:x]; + } completed:^{ + completed = YES; + }]; + + [sub1 sendNext:@1]; + [sub2 sendNext:@2]; + [sub2 sendNext:@3]; + [sub2 sendCompleted]; + expect(completed).to.beFalsy(); + + [sub1 sendNext:@4]; + [sub1 sendCompleted]; + expect(completed).to.beTruthy(); + + NSArray *expected = @[ @1, @2, @3, @4 ]; + expect(values).to.equal(expected); + }); + + it(@"should complete immediately when not given any signals", ^{ + RACSignal *signal = [RACSignal merge:@[].objectEnumerator]; + + __block BOOL completed = NO; + [signal subscribeCompleted:^{ + completed = YES; + }]; + + expect(completed).to.beTruthy(); + }); + + it(@"should complete only after both signals complete for any number of subscribers", ^{ + __block BOOL completed1 = NO; + __block BOOL completed2 = NO; + [merged subscribeCompleted:^{ + completed1 = YES; + }]; + + [merged subscribeCompleted:^{ + completed2 = YES; + }]; + + expect(completed1).to.beFalsy(); + expect(completed2).to.beFalsy(); + + [sub1 sendCompleted]; + [sub2 sendCompleted]; + expect(completed1).to.beTruthy(); + expect(completed2).to.beTruthy(); + }); +}); + +describe(@"-flatten:", ^{ + __block BOOL subscribedTo1 = NO; + __block BOOL subscribedTo2 = NO; + __block BOOL subscribedTo3 = NO; + __block RACSignal *sub1; + __block RACSignal *sub2; + __block RACSignal *sub3; + __block RACSubject *subject1; + __block RACSubject *subject2; + __block RACSubject *subject3; + __block RACSubject *signalsSubject; + __block NSMutableArray *values; + + beforeEach(^{ + subscribedTo1 = NO; + subject1 = [RACSubject subject]; + sub1 = [RACSignal defer:^{ + subscribedTo1 = YES; + return subject1; + }]; + + subscribedTo2 = NO; + subject2 = [RACSubject subject]; + sub2 = [RACSignal defer:^{ + subscribedTo2 = YES; + return subject2; + }]; + + subscribedTo3 = NO; + subject3 = [RACSubject subject]; + sub3 = [RACSignal defer:^{ + subscribedTo3 = YES; + return subject3; + }]; + + signalsSubject = [RACSubject subject]; + + values = [NSMutableArray array]; + }); + + describe(@"when its max is 0", ^{ + it(@"should merge all the signals concurrently", ^{ + [[signalsSubject flatten:0] subscribeNext:^(id x) { + [values addObject:x]; + }]; + + expect(subscribedTo1).to.beFalsy(); + expect(subscribedTo2).to.beFalsy(); + expect(subscribedTo3).to.beFalsy(); + + [signalsSubject sendNext:sub1]; + [signalsSubject sendNext:sub2]; + + expect(subscribedTo1).to.beTruthy(); + expect(subscribedTo2).to.beTruthy(); + expect(subscribedTo3).to.beFalsy(); + + [subject1 sendNext:@1]; + + [signalsSubject sendNext:sub3]; + + expect(subscribedTo1).to.beTruthy(); + expect(subscribedTo2).to.beTruthy(); + expect(subscribedTo3).to.beTruthy(); + + [subject1 sendCompleted]; + + [subject2 sendNext:@2]; + [subject2 sendCompleted]; + + [subject3 sendNext:@3]; + [subject3 sendCompleted]; + + NSArray *expected = @[ @1, @2, @3 ]; + expect(values).to.equal(expected); + }); + + itShouldBehaveLike(RACSignalMergeConcurrentCompletionExampleGroup, @{ RACSignalMaxConcurrent: @0 }); + }); + + describe(@"when its max is > 0", ^{ + it(@"should merge only the given number at a time", ^{ + [[signalsSubject flatten:1] subscribeNext:^(id x) { + [values addObject:x]; + }]; + + expect(subscribedTo1).to.beFalsy(); + expect(subscribedTo2).to.beFalsy(); + expect(subscribedTo3).to.beFalsy(); + + [signalsSubject sendNext:sub1]; + [signalsSubject sendNext:sub2]; + + expect(subscribedTo1).to.beTruthy(); + expect(subscribedTo2).to.beFalsy(); + expect(subscribedTo3).to.beFalsy(); + + [subject1 sendNext:@1]; + + [signalsSubject sendNext:sub3]; + + expect(subscribedTo1).to.beTruthy(); + expect(subscribedTo2).to.beFalsy(); + expect(subscribedTo3).to.beFalsy(); + + [signalsSubject sendCompleted]; + + expect(subscribedTo1).to.beTruthy(); + expect(subscribedTo2).to.beFalsy(); + expect(subscribedTo3).to.beFalsy(); + + [subject1 sendCompleted]; + + expect(subscribedTo2).to.beTruthy(); + expect(subscribedTo3).to.beFalsy(); + + [subject2 sendNext:@2]; + [subject2 sendCompleted]; + + expect(subscribedTo3).to.beTruthy(); + + [subject3 sendNext:@3]; + [subject3 sendCompleted]; + + NSArray *expected = @[ @1, @2, @3 ]; + expect(values).to.equal(expected); + }); + + itShouldBehaveLike(RACSignalMergeConcurrentCompletionExampleGroup, @{ RACSignalMaxConcurrent: @1 }); + }); + + it(@"shouldn't create a retain cycle", ^{ + __block BOOL subjectDeallocd = NO; + __block BOOL signalDeallocd = NO; + @autoreleasepool { + RACSubject *subject __attribute__((objc_precise_lifetime)) = [RACSubject subject]; + [subject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + subjectDeallocd = YES; + }]]; + + RACSignal *signal __attribute__((objc_precise_lifetime)) = [subject flatten]; + [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + signalDeallocd = YES; + }]]; + + [signal subscribeCompleted:^{}]; + + [subject sendCompleted]; + } + + expect(subjectDeallocd).will.beTruthy(); + expect(signalDeallocd).will.beTruthy(); + }); + + it(@"should not crash when disposing while subscribing", ^{ + RACDisposable *disposable = [[signalsSubject flatten:0] subscribeCompleted:^{ + }]; + + [signalsSubject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [disposable dispose]; + [subscriber sendCompleted]; + return nil; + }]]; + + [signalsSubject sendCompleted]; + }); + + it(@"should dispose after last synchronous signal subscription and should not crash", ^{ + + RACSignal *flattened = [signalsSubject flatten:1]; + RACDisposable *flattenDisposable = [flattened subscribeCompleted:^{}]; + + RACSignal *syncSignal = [RACSignal createSignal:^ RACDisposable *(id<RACSubscriber> subscriber) { + expect(flattenDisposable.disposed).to.beFalsy(); + [subscriber sendCompleted]; + expect(flattenDisposable.disposed).to.beTruthy(); + return nil; + }]; + + RACSignal *asyncSignal = [sub1 delay:0]; + + [signalsSubject sendNext:asyncSignal]; + [signalsSubject sendNext:syncSignal]; + + [signalsSubject sendCompleted]; + + [subject1 sendCompleted]; + + expect(flattenDisposable.disposed).will.beTruthy(); + }); + + it(@"should not crash when disposed because of takeUntil:", ^{ + for (int i = 0; i < 100; i++) { + RACSubject *flattenedReceiver = [RACSubject subject]; + RACSignal *done = [flattenedReceiver map:^(NSNumber *n) { + return @(n.integerValue == 1); + }]; + + RACSignal *flattened = [signalsSubject flatten:1]; + + RACDisposable *flattenDisposable = [[flattened takeUntil:[done ignore:@NO]] subscribe:flattenedReceiver]; + + RACSignal *syncSignal = [RACSignal createSignal:^ RACDisposable *(id<RACSubscriber> subscriber) { + expect(flattenDisposable.disposed).to.beFalsy(); + [subscriber sendNext:@1]; + expect(flattenDisposable.disposed).to.beTruthy(); + [subscriber sendCompleted]; + return nil; + }]; + + RACSignal *asyncSignal = [sub1 delay:0]; + [subject1 sendNext:@0]; + + [signalsSubject sendNext:asyncSignal]; + [signalsSubject sendNext:syncSignal]; + [signalsSubject sendCompleted]; + + [subject1 sendCompleted]; + + expect(flattenDisposable.disposed).will.beTruthy(); + } + }); +}); + +describe(@"-switchToLatest", ^{ + __block RACSubject *subject; + + __block NSMutableArray *values; + __block NSError *lastError = nil; + __block BOOL completed = NO; + + beforeEach(^{ + subject = [RACSubject subject]; + + values = [NSMutableArray array]; + lastError = nil; + completed = NO; + + [[subject switchToLatest] subscribeNext:^(id x) { + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + [values addObject:x]; + } error:^(NSError *error) { + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + lastError = error; + } completed:^{ + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + completed = YES; + }]; + }); + + it(@"should send values from the most recent signal", ^{ + [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:@2]; + return nil; + }]]; + + [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@3]; + [subscriber sendNext:@4]; + return nil; + }]]; + + NSArray *expected = @[ @1, @2, @3, @4 ]; + expect(values).to.equal(expected); + }); + + it(@"should send errors from the most recent signal", ^{ + [subject sendNext:[RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; + return nil; + }]]; + + expect(lastError).notTo.beNil(); + }); + + it(@"should not send completed if only the switching signal completes", ^{ + [subject sendNext:RACSignal.never]; + + expect(completed).to.beFalsy(); + + [subject sendCompleted]; + expect(completed).to.beFalsy(); + }); + + it(@"should send completed when the switching signal completes and the last sent signal does", ^{ + [subject sendNext:RACSignal.empty]; + + expect(completed).to.beFalsy(); + + [subject sendCompleted]; + expect(completed).to.beTruthy(); + }); + + it(@"should accept nil signals", ^{ + [subject sendNext:nil]; + [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:@2]; + return nil; + }]]; + + NSArray *expected = @[ @1, @2 ]; + expect(values).to.equal(expected); + }); + + it(@"should return a cold signal", ^{ + __block NSUInteger subscriptions = 0; + RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + subscriptions++; + [subscriber sendNext:[RACSignal empty]]; + return nil; + }]; + + RACSignal *switched = [signalOfSignals switchToLatest]; + + [[switched publish] connect]; + expect(subscriptions).to.equal(1); + + [[switched publish] connect]; + expect(subscriptions).to.equal(2); + }); +}); + +describe(@"+switch:cases:default:", ^{ + __block RACSubject *keySubject; + + __block RACSubject *subjectZero; + __block RACSubject *subjectOne; + __block RACSubject *subjectTwo; + + __block RACSubject *defaultSubject; + + __block NSMutableArray *values; + __block NSError *lastError = nil; + __block BOOL completed = NO; + + beforeEach(^{ + keySubject = [RACSubject subject]; + + subjectZero = [RACSubject subject]; + subjectOne = [RACSubject subject]; + subjectTwo = [RACSubject subject]; + + defaultSubject = [RACSubject subject]; + + values = [NSMutableArray array]; + lastError = nil; + completed = NO; + }); + + describe(@"switching between values with a default", ^{ + __block RACSignal *switchSignal; + + beforeEach(^{ + switchSignal = [RACSignal switch:keySubject cases:@{ + @0: subjectZero, + @1: subjectOne, + @2: subjectTwo, + } default:[RACSignal never]]; + + [switchSignal subscribeNext:^(id x) { + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + [values addObject:x]; + } error:^(NSError *error) { + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + lastError = error; + } completed:^{ + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + completed = YES; + }]; + }); + + it(@"should not send any values before a key is sent", ^{ + [subjectZero sendNext:RACUnit.defaultUnit]; + [subjectOne sendNext:RACUnit.defaultUnit]; + [subjectTwo sendNext:RACUnit.defaultUnit]; + + expect(values).to.equal(@[]); + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + }); + + it(@"should send events based on the latest key", ^{ + [keySubject sendNext:@0]; + + [subjectZero sendNext:@"zero"]; + [subjectZero sendNext:@"zero"]; + [subjectOne sendNext:@"one"]; + [subjectTwo sendNext:@"two"]; + + NSArray *expected = @[ @"zero", @"zero" ]; + expect(values).to.equal(expected); + + [keySubject sendNext:@1]; + + [subjectZero sendNext:@"zero"]; + [subjectOne sendNext:@"one"]; + [subjectTwo sendNext:@"two"]; + + expected = @[ @"zero", @"zero", @"one" ]; + expect(values).to.equal(expected); + + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + [keySubject sendNext:@2]; + + [subjectZero sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; + [subjectOne sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; + expect(lastError).to.beNil(); + + [subjectTwo sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; + expect(lastError).notTo.beNil(); + }); + + it(@"should not send completed when only the key signal completes", ^{ + [keySubject sendNext:@0]; + [subjectZero sendNext:@"zero"]; + [keySubject sendCompleted]; + + expect(values).to.equal(@[ @"zero" ]); + expect(completed).to.beFalsy(); + }); + + it(@"should send completed when the key signal and the latest sent signal complete", ^{ + [keySubject sendNext:@0]; + [subjectZero sendNext:@"zero"]; + [keySubject sendCompleted]; + [subjectZero sendCompleted]; + + expect(values).to.equal(@[ @"zero" ]); + expect(completed).to.beTruthy(); + }); + }); + + it(@"should use the default signal if key that was sent does not have an associated signal", ^{ + [[RACSignal + switch:keySubject + cases:@{ + @0: subjectZero, + @1: subjectOne, + } + default:defaultSubject] + subscribeNext:^(id x) { + [values addObject:x]; + }]; + + [keySubject sendNext:@"not a valid key"]; + [defaultSubject sendNext:@"default"]; + + expect(values).to.equal(@[ @"default" ]); + + [keySubject sendNext:nil]; + [defaultSubject sendNext:@"default"]; + + expect(values).to.equal((@[ @"default", @"default" ])); + }); + + it(@"should send an error if key that was sent does not have an associated signal and there's no default", ^{ + [[RACSignal + switch:keySubject + cases:@{ + @0: subjectZero, + @1: subjectOne, + } + default:nil] + subscribeNext:^(id x) { + [values addObject:x]; + } error:^(NSError *error) { + lastError = error; + }]; + + [keySubject sendNext:@0]; + [subjectZero sendNext:@"zero"]; + + expect(values).to.equal(@[ @"zero" ]); + expect(lastError).to.beNil(); + + [keySubject sendNext:nil]; + + expect(values).to.equal(@[ @"zero" ]); + expect(lastError).notTo.beNil(); + expect(lastError.domain).to.equal(RACSignalErrorDomain); + expect(lastError.code).to.equal(RACSignalErrorNoMatchingCase); + }); + + it(@"should match RACTupleNil case when a nil value is sent", ^{ + [[RACSignal + switch:keySubject + cases:@{ + RACTupleNil.tupleNil: subjectZero, + } + default:defaultSubject] + subscribeNext:^(id x) { + [values addObject:x]; + }]; + + [keySubject sendNext:nil]; + [subjectZero sendNext:@"zero"]; + expect(values).to.equal(@[ @"zero" ]); + }); +}); + +describe(@"+if:then:else", ^{ + __block RACSubject *boolSubject; + __block RACSubject *trueSubject; + __block RACSubject *falseSubject; + + __block NSMutableArray *values; + __block NSError *lastError = nil; + __block BOOL completed = NO; + + beforeEach(^{ + boolSubject = [RACSubject subject]; + trueSubject = [RACSubject subject]; + falseSubject = [RACSubject subject]; + + values = [NSMutableArray array]; + lastError = nil; + completed = NO; + + [[RACSignal if:boolSubject then:trueSubject else:falseSubject] subscribeNext:^(id x) { + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + [values addObject:x]; + } error:^(NSError *error) { + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + lastError = error; + } completed:^{ + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + completed = YES; + }]; + }); + + it(@"should not send any values before a boolean is sent", ^{ + [trueSubject sendNext:RACUnit.defaultUnit]; + [falseSubject sendNext:RACUnit.defaultUnit]; + + expect(values).to.equal(@[]); + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + }); + + it(@"should send events based on the latest boolean", ^{ + [boolSubject sendNext:@YES]; + + [trueSubject sendNext:@"foo"]; + [falseSubject sendNext:@"buzz"]; + [trueSubject sendNext:@"bar"]; + + NSArray *expected = @[ @"foo", @"bar" ]; + expect(values).to.equal(expected); + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + [boolSubject sendNext:@NO]; + + [trueSubject sendNext:@"baz"]; + [falseSubject sendNext:@"buzz"]; + [trueSubject sendNext:@"barfoo"]; + + expected = @[ @"foo", @"bar", @"buzz" ]; + expect(values).to.equal(expected); + expect(lastError).to.beNil(); + expect(completed).to.beFalsy(); + + [trueSubject sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; + expect(lastError).to.beNil(); + + [falseSubject sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; + expect(lastError).notTo.beNil(); + }); + + it(@"should not send completed when only the BOOL signal completes", ^{ + [boolSubject sendNext:@YES]; + [trueSubject sendNext:@"foo"]; + [boolSubject sendCompleted]; + + expect(values).to.equal(@[ @"foo" ]); + expect(completed).to.beFalsy(); + }); + + it(@"should send completed when the BOOL signal and the latest sent signal complete", ^{ + [boolSubject sendNext:@YES]; + [trueSubject sendNext:@"foo"]; + [trueSubject sendCompleted]; + [boolSubject sendCompleted]; + + expect(values).to.equal(@[ @"foo" ]); + expect(completed).to.beTruthy(); + }); +}); + +describe(@"+interval:onScheduler: and +interval:onScheduler:withLeeway:", ^{ + static const NSTimeInterval interval = 0.1; + static const NSTimeInterval leeway = 0.2; + + __block void (^testTimer)(RACSignal *, NSNumber *, NSNumber *) = nil; + + before(^{ + testTimer = [^(RACSignal *timer, NSNumber *minInterval, NSNumber *leeway) { + __block NSUInteger nextsReceived = 0; + + NSTimeInterval startTime = NSDate.timeIntervalSinceReferenceDate; + [[timer take:3] subscribeNext:^(NSDate *date) { + ++nextsReceived; + + NSTimeInterval currentTime = date.timeIntervalSinceReferenceDate; + + // Uniformly distribute the expected interval for all + // received values. We do this instead of saving a timestamp + // because a delayed interval may cause the _next_ value to + // send sooner than the interval. + NSTimeInterval expectedMinInterval = minInterval.doubleValue * nextsReceived; + NSTimeInterval expectedMaxInterval = expectedMinInterval + leeway.doubleValue * 3 + 0.05; + + expect(currentTime - startTime).beGreaterThanOrEqualTo(expectedMinInterval); + expect(currentTime - startTime).beLessThanOrEqualTo(expectedMaxInterval); + }]; + + expect(nextsReceived).will.equal(3); + } copy]; + }); + + describe(@"+interval:onScheduler:", ^{ + it(@"should work on the main thread scheduler", ^{ + testTimer([RACSignal interval:interval onScheduler:RACScheduler.mainThreadScheduler], @(interval), @0); + }); + + it(@"should work on a background scheduler", ^{ + testTimer([RACSignal interval:interval onScheduler:[RACScheduler scheduler]], @(interval), @0); + }); + }); + + describe(@"+interval:onScheduler:withLeeway:", ^{ + it(@"should work on the main thread scheduler", ^{ + testTimer([RACSignal interval:interval onScheduler:RACScheduler.mainThreadScheduler withLeeway:leeway], @(interval), @(leeway)); + }); + + it(@"should work on a background scheduler", ^{ + testTimer([RACSignal interval:interval onScheduler:[RACScheduler scheduler] withLeeway:leeway], @(interval), @(leeway)); + }); + }); +}); + +describe(@"-timeout:onScheduler:", ^{ + __block RACSubject *subject; + + beforeEach(^{ + subject = [RACSubject subject]; + }); + + it(@"should time out", ^{ + RACTestScheduler *scheduler = [[RACTestScheduler alloc] init]; + + __block NSError *receivedError = nil; + [[subject timeout:1 onScheduler:scheduler] subscribeError:^(NSError *e) { + receivedError = e; + }]; + + expect(receivedError).to.beNil(); + + [scheduler stepAll]; + expect(receivedError).willNot.beNil(); + expect(receivedError.domain).to.equal(RACSignalErrorDomain); + expect(receivedError.code).to.equal(RACSignalErrorTimedOut); + }); + + it(@"should pass through events while not timed out", ^{ + __block id next = nil; + __block BOOL completed = NO; + [[subject timeout:1 onScheduler:RACScheduler.mainThreadScheduler] subscribeNext:^(id x) { + next = x; + } completed:^{ + completed = YES; + }]; + + [subject sendNext:RACUnit.defaultUnit]; + expect(next).to.equal(RACUnit.defaultUnit); + + [subject sendCompleted]; + expect(completed).to.beTruthy(); + }); + + it(@"should not time out after disposal", ^{ + RACTestScheduler *scheduler = [[RACTestScheduler alloc] init]; + + __block NSError *receivedError = nil; + RACDisposable *disposable = [[subject timeout:1 onScheduler:scheduler] subscribeError:^(NSError *e) { + receivedError = e; + }]; + + [disposable dispose]; + [scheduler stepAll]; + expect(receivedError).to.beNil(); + }); +}); + +describe(@"-delay:", ^{ + __block RACSubject *subject; + __block RACSignal *delayedSignal; + + beforeEach(^{ + subject = [RACSubject subject]; + delayedSignal = [subject delay:0]; + }); + + it(@"should delay nexts", ^{ + __block id next = nil; + [delayedSignal subscribeNext:^(id x) { + next = x; + }]; + + [subject sendNext:@"foo"]; + expect(next).to.beNil(); + expect(next).will.equal(@"foo"); + }); + + it(@"should delay completed", ^{ + __block BOOL completed = NO; + [delayedSignal subscribeCompleted:^{ + completed = YES; + }]; + + [subject sendCompleted]; + expect(completed).to.beFalsy(); + expect(completed).will.beTruthy(); + }); + + it(@"should not delay errors", ^{ + __block NSError *error = nil; + [delayedSignal subscribeError:^(NSError *e) { + error = e; + }]; + + [subject sendError:RACSignalTestError]; + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should cancel delayed events when disposed", ^{ + __block id next = nil; + RACDisposable *disposable = [delayedSignal subscribeNext:^(id x) { + next = x; + }]; + + [subject sendNext:@"foo"]; + + __block BOOL done = NO; + [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + done = YES; + }]; + + [disposable dispose]; + + expect(done).will.beTruthy(); + expect(next).to.beNil(); + }); +}); + +describe(@"-catch:", ^{ + it(@"should subscribe to ensuing signal on error", ^{ + RACSubject *subject = [RACSubject subject]; + + RACSignal *signal = [subject catch:^(NSError *error) { + return [RACSignal return:@41]; + }]; + + __block id value = nil; + [signal subscribeNext:^(id x) { + value = x; + }]; + + [subject sendError:RACSignalTestError]; + expect(value).to.equal(@41); + }); + + it(@"should prevent source error from propagating", ^{ + RACSubject *subject = [RACSubject subject]; + + RACSignal *signal = [subject catch:^(NSError *error) { + return [RACSignal empty]; + }]; + + __block BOOL errorReceived = NO; + [signal subscribeError:^(NSError *error) { + errorReceived = YES; + }]; + + [subject sendError:RACSignalTestError]; + expect(errorReceived).to.beFalsy(); + }); + + it(@"should propagate error from ensuing signal", ^{ + RACSubject *subject = [RACSubject subject]; + + NSError *secondaryError = [NSError errorWithDomain:@"bubs" code:41 userInfo:nil]; + RACSignal *signal = [subject catch:^(NSError *error) { + return [RACSignal error:secondaryError]; + }]; + + __block NSError *errorReceived = nil; + [signal subscribeError:^(NSError *error) { + errorReceived = error; + }]; + + [subject sendError:RACSignalTestError]; + expect(errorReceived).to.equal(secondaryError); + }); + + it(@"should dispose ensuing signal", ^{ + RACSubject *subject = [RACSubject subject]; + + __block BOOL disposed = NO; + RACSignal *signal = [subject catch:^(NSError *error) { + return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + }]; + + RACDisposable *disposable = [signal subscribeCompleted:^{}]; + [subject sendError:RACSignalTestError]; + [disposable dispose]; + + expect(disposed).will.beTruthy(); + }); +}); + +describe(@"-try:", ^{ + __block RACSubject *subject; + __block NSError *receivedError; + __block NSMutableArray *nextValues; + __block BOOL completed; + + beforeEach(^{ + subject = [RACSubject subject]; + nextValues = [NSMutableArray array]; + completed = NO; + receivedError = nil; + + [[subject try:^(NSString *value, NSError **error) { + if (value != nil) return YES; + + if (error != nil) *error = RACSignalTestError; + + return NO; + }] subscribeNext:^(id x) { + [nextValues addObject:x]; + } error:^(NSError *error) { + receivedError = error; + } completed:^{ + completed = YES; + }]; + }); + + it(@"should pass values while YES is returned from the tryBlock", ^{ + [subject sendNext:@"foo"]; + [subject sendNext:@"bar"]; + [subject sendNext:@"baz"]; + [subject sendNext:@"buzz"]; + [subject sendCompleted]; + + NSArray *receivedValues = [nextValues copy]; + NSArray *expectedValues = @[ @"foo", @"bar", @"baz", @"buzz" ]; + + expect(receivedError).to.beNil(); + expect(receivedValues).to.equal(expectedValues); + expect(completed).to.beTruthy(); + }); + + it(@"should pass values until NO is returned from the tryBlock", ^{ + [subject sendNext:@"foo"]; + [subject sendNext:@"bar"]; + [subject sendNext:nil]; + [subject sendNext:@"buzz"]; + [subject sendCompleted]; + + NSArray *receivedValues = [nextValues copy]; + NSArray *expectedValues = @[ @"foo", @"bar" ]; + + expect(receivedError).to.equal(RACSignalTestError); + expect(receivedValues).to.equal(expectedValues); + expect(completed).to.beFalsy(); + }); +}); + +describe(@"-tryMap:", ^{ + __block RACSubject *subject; + __block NSError *receivedError; + __block NSMutableArray *nextValues; + __block BOOL completed; + + beforeEach(^{ + subject = [RACSubject subject]; + nextValues = [NSMutableArray array]; + completed = NO; + receivedError = nil; + + [[subject tryMap:^ id (NSString *value, NSError **error) { + if (value != nil) return [NSString stringWithFormat:@"%@_a", value]; + + if (error != nil) *error = RACSignalTestError; + + return nil; + }] subscribeNext:^(id x) { + [nextValues addObject:x]; + } error:^(NSError *error) { + receivedError = error; + } completed:^{ + completed = YES; + }]; + }); + + it(@"should map values with the mapBlock", ^{ + [subject sendNext:@"foo"]; + [subject sendNext:@"bar"]; + [subject sendNext:@"baz"]; + [subject sendNext:@"buzz"]; + [subject sendCompleted]; + + NSArray *receivedValues = [nextValues copy]; + NSArray *expectedValues = @[ @"foo_a", @"bar_a", @"baz_a", @"buzz_a" ]; + + expect(receivedError).to.beNil(); + expect(receivedValues).to.equal(expectedValues); + expect(completed).to.beTruthy(); + }); + + it(@"should map values with the mapBlock, until the mapBlock returns nil", ^{ + [subject sendNext:@"foo"]; + [subject sendNext:@"bar"]; + [subject sendNext:nil]; + [subject sendNext:@"buzz"]; + [subject sendCompleted]; + + NSArray *receivedValues = [nextValues copy]; + NSArray *expectedValues = @[ @"foo_a", @"bar_a" ]; + + expect(receivedError).to.equal(RACSignalTestError); + expect(receivedValues).to.equal(expectedValues); + expect(completed).to.beFalsy(); + }); +}); + +describe(@"throttling", ^{ + __block RACSubject *subject; + + beforeEach(^{ + subject = [RACSubject subject]; + }); + + describe(@"-throttle:", ^{ + __block RACSignal *throttledSignal; + + beforeEach(^{ + throttledSignal = [subject throttle:0]; + }); + + it(@"should throttle nexts", ^{ + NSMutableArray *valuesReceived = [NSMutableArray array]; + [throttledSignal subscribeNext:^(id x) { + [valuesReceived addObject:x]; + }]; + + [subject sendNext:@"foo"]; + [subject sendNext:@"bar"]; + expect(valuesReceived).to.equal(@[]); + + NSArray *expected = @[ @"bar" ]; + expect(valuesReceived).will.equal(expected); + + [subject sendNext:@"buzz"]; + expect(valuesReceived).to.equal(expected); + + expected = @[ @"bar", @"buzz" ]; + expect(valuesReceived).will.equal(expected); + }); + + it(@"should forward completed immediately", ^{ + __block BOOL completed = NO; + [throttledSignal subscribeCompleted:^{ + completed = YES; + }]; + + [subject sendCompleted]; + expect(completed).to.beTruthy(); + }); + + it(@"should forward errors immediately", ^{ + __block NSError *error = nil; + [throttledSignal subscribeError:^(NSError *e) { + error = e; + }]; + + [subject sendError:RACSignalTestError]; + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should cancel future nexts when disposed", ^{ + __block id next = nil; + RACDisposable *disposable = [throttledSignal subscribeNext:^(id x) { + next = x; + }]; + + [subject sendNext:@"foo"]; + + __block BOOL done = NO; + [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + done = YES; + }]; + + [disposable dispose]; + + expect(done).will.beTruthy(); + expect(next).to.beNil(); + }); + }); + + describe(@"-throttle:valuesPassingTest:", ^{ + __block RACSignal *throttledSignal; + __block BOOL shouldThrottle; + + beforeEach(^{ + shouldThrottle = YES; + + __block id value = nil; + throttledSignal = [[subject + doNext:^(id x) { + value = x; + }] + throttle:0 valuesPassingTest:^(id x) { + // Make sure that we're given the latest value. + expect(x).to.beIdenticalTo(value); + + return shouldThrottle; + }]; + + expect(throttledSignal).notTo.beNil(); + }); + + describe(@"nexts", ^{ + __block NSMutableArray *valuesReceived; + __block NSMutableArray *expected; + + beforeEach(^{ + expected = [[NSMutableArray alloc] init]; + valuesReceived = [[NSMutableArray alloc] init]; + + [throttledSignal subscribeNext:^(id x) { + [valuesReceived addObject:x]; + }]; + }); + + it(@"should forward unthrottled values immediately", ^{ + shouldThrottle = NO; + [subject sendNext:@"foo"]; + + [expected addObject:@"foo"]; + expect(valuesReceived).to.equal(expected); + }); + + it(@"should delay throttled values", ^{ + [subject sendNext:@"bar"]; + expect(valuesReceived).to.equal(expected); + + [expected addObject:@"bar"]; + expect(valuesReceived).will.equal(expected); + }); + + it(@"should drop buffered values when a throttled value arrives", ^{ + [subject sendNext:@"foo"]; + [subject sendNext:@"bar"]; + [subject sendNext:@"buzz"]; + expect(valuesReceived).to.equal(expected); + + [expected addObject:@"buzz"]; + expect(valuesReceived).will.equal(expected); + }); + + it(@"should drop buffered values when an immediate value arrives", ^{ + [subject sendNext:@"foo"]; + [subject sendNext:@"bar"]; + + shouldThrottle = NO; + [subject sendNext:@"buzz"]; + [expected addObject:@"buzz"]; + expect(valuesReceived).to.equal(expected); + + // Make sure that nothing weird happens when sending another + // throttled value. + shouldThrottle = YES; + [subject sendNext:@"baz"]; + expect(valuesReceived).to.equal(expected); + + [expected addObject:@"baz"]; + expect(valuesReceived).will.equal(expected); + }); + + it(@"should not be resent upon completion", ^{ + [subject sendNext:@"bar"]; + [expected addObject:@"bar"]; + expect(valuesReceived).will.equal(expected); + + [subject sendCompleted]; + expect(valuesReceived).to.equal(expected); + }); + }); + + it(@"should forward completed immediately", ^{ + __block BOOL completed = NO; + [throttledSignal subscribeCompleted:^{ + completed = YES; + }]; + + [subject sendCompleted]; + expect(completed).to.beTruthy(); + }); + + it(@"should forward errors immediately", ^{ + __block NSError *error = nil; + [throttledSignal subscribeError:^(NSError *e) { + error = e; + }]; + + [subject sendError:RACSignalTestError]; + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should cancel future nexts when disposed", ^{ + __block id next = nil; + RACDisposable *disposable = [throttledSignal subscribeNext:^(id x) { + next = x; + }]; + + [subject sendNext:@"foo"]; + + __block BOOL done = NO; + [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ + done = YES; + }]; + + [disposable dispose]; + + expect(done).will.beTruthy(); + expect(next).to.beNil(); + }); + }); +}); + +describe(@"-then:", ^{ + it(@"should continue onto returned signal", ^{ + RACSubject *subject = [RACSubject subject]; + + __block id value = nil; + [[subject then:^{ + return [RACSignal return:@2]; + }] subscribeNext:^(id x) { + value = x; + }]; + + [subject sendNext:@1]; + + // The value shouldn't change until the first signal completes. + expect(value).to.beNil(); + + [subject sendCompleted]; + + expect(value).to.equal(@2); + }); + + it(@"should sequence even if no next value is sent", ^{ + RACSubject *subject = [RACSubject subject]; + + __block id value = nil; + [[subject then:^{ + return [RACSignal return:RACUnit.defaultUnit]; + }] subscribeNext:^(id x) { + value = x; + }]; + + [subject sendCompleted]; + + expect(value).to.equal(RACUnit.defaultUnit); + }); +}); + +describe(@"-sequence", ^{ + RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:@2]; + [subscriber sendNext:@3]; + [subscriber sendNext:@4]; + [subscriber sendCompleted]; + return nil; + }]; + + itShouldBehaveLike(RACSequenceExamples, ^{ + return @{ + RACSequenceExampleSequence: signal.sequence, + RACSequenceExampleExpectedValues: @[ @1, @2, @3, @4 ] + }; + }); +}); + +it(@"should complete take: even if the original signal doesn't", ^{ + RACSignal *sendOne = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + [subscriber sendNext:RACUnit.defaultUnit]; + return nil; + }]; + + __block id value = nil; + __block BOOL completed = NO; + [[sendOne take:1] subscribeNext:^(id received) { + value = received; + } completed:^{ + completed = YES; + }]; + + expect(value).to.equal(RACUnit.defaultUnit); + expect(completed).to.beTruthy(); +}); + +describe(@"+zip:", ^{ + __block RACSubject *subject1 = nil; + __block RACSubject *subject2 = nil; + __block BOOL hasSentError = NO; + __block BOOL hasSentCompleted = NO; + __block RACDisposable *disposable = nil; + __block void (^send2NextAndErrorTo1)(void) = nil; + __block void (^send3NextAndErrorTo1)(void) = nil; + __block void (^send2NextAndCompletedTo2)(void) = nil; + __block void (^send3NextAndCompletedTo2)(void) = nil; + + before(^{ + send2NextAndErrorTo1 = [^{ + [subject1 sendNext:@1]; + [subject1 sendNext:@2]; + [subject1 sendError:RACSignalTestError]; + } copy]; + send3NextAndErrorTo1 = [^{ + [subject1 sendNext:@1]; + [subject1 sendNext:@2]; + [subject1 sendNext:@3]; + [subject1 sendError:RACSignalTestError]; + } copy]; + send2NextAndCompletedTo2 = [^{ + [subject2 sendNext:@1]; + [subject2 sendNext:@2]; + [subject2 sendCompleted]; + } copy]; + send3NextAndCompletedTo2 = [^{ + [subject2 sendNext:@1]; + [subject2 sendNext:@2]; + [subject2 sendNext:@3]; + [subject2 sendCompleted]; + } copy]; + subject1 = [RACSubject subject]; + subject2 = [RACSubject subject]; + hasSentError = NO; + hasSentCompleted = NO; + disposable = [[RACSignal zip:@[ subject1, subject2 ]] subscribeError:^(NSError *error) { + hasSentError = YES; + } completed:^{ + hasSentCompleted = YES; + }]; + }); + + after(^{ + [disposable dispose]; + }); + + it(@"should complete as soon as no new zipped values are possible", ^{ + [subject1 sendNext:@1]; + [subject2 sendNext:@1]; + expect(hasSentCompleted).to.beFalsy(); + + [subject1 sendNext:@2]; + [subject1 sendCompleted]; + expect(hasSentCompleted).to.beFalsy(); + + [subject2 sendNext:@2]; + expect(hasSentCompleted).to.beTruthy(); + }); + + it(@"outcome should not be dependent on order of signals", ^{ + [subject2 sendCompleted]; + expect(hasSentCompleted).to.beTruthy(); + }); + + it(@"should forward errors sent earlier than (time-wise) and before (position-wise) a complete", ^{ + send2NextAndErrorTo1(); + send3NextAndCompletedTo2(); + expect(hasSentError).to.beTruthy(); + expect(hasSentCompleted).to.beFalsy(); + }); + + it(@"should forward errors sent earlier than (time-wise) and after (position-wise) a complete", ^{ + send3NextAndErrorTo1(); + send2NextAndCompletedTo2(); + expect(hasSentError).to.beTruthy(); + expect(hasSentCompleted).to.beFalsy(); + }); + + it(@"should forward errors sent later than (time-wise) and before (position-wise) a complete", ^{ + send3NextAndCompletedTo2(); + send2NextAndErrorTo1(); + expect(hasSentError).to.beTruthy(); + expect(hasSentCompleted).to.beFalsy(); + }); + + it(@"should ignore errors sent later than (time-wise) and after (position-wise) a complete", ^{ + send2NextAndCompletedTo2(); + send3NextAndErrorTo1(); + expect(hasSentError).to.beFalsy(); + expect(hasSentCompleted).to.beTruthy(); + }); + + it(@"should handle signals sending values unevenly", ^{ + __block NSError *receivedError = nil; + __block BOOL hasCompleted = NO; + + RACSubject *a = [RACSubject subject]; + RACSubject *b = [RACSubject subject]; + RACSubject *c = [RACSubject subject]; + + NSMutableArray *receivedValues = NSMutableArray.array; + NSArray *expectedValues = nil; + + [[RACSignal zip:@[ a, b, c ] reduce:^(NSNumber *a, NSNumber *b, NSNumber *c) { + return [NSString stringWithFormat:@"%@%@%@", a, b, c]; + }] subscribeNext:^(id x) { + [receivedValues addObject:x]; + } error:^(NSError *error) { + receivedError = error; + } completed:^{ + hasCompleted = YES; + }]; + + [a sendNext:@1]; + [a sendNext:@2]; + [a sendNext:@3]; + + [b sendNext:@1]; + + [c sendNext:@1]; + [c sendNext:@2]; + + // a: [===......] + // b: [=........] + // c: [==.......] + + expectedValues = @[ @"111" ]; + expect(receivedValues).to.equal(expectedValues); + expect(receivedError).to.beNil(); + expect(hasCompleted).to.beFalsy(); + + [b sendNext:@2]; + [b sendNext:@3]; + [b sendNext:@4]; + [b sendCompleted]; + + // a: [===......] + // b: [====C....] + // c: [==.......] + + expectedValues = @[ @"111", @"222" ]; + expect(receivedValues).to.equal(expectedValues); + expect(receivedError).to.beNil(); + expect(hasCompleted).to.beFalsy(); + + [c sendNext:@3]; + [c sendNext:@4]; + [c sendNext:@5]; + [c sendError:RACSignalTestError]; + + // a: [===......] + // b: [====C....] + // c: [=====E...] + + expectedValues = @[ @"111", @"222", @"333" ]; + expect(receivedValues).to.equal(expectedValues); + expect(receivedError).to.equal(RACSignalTestError); + expect(hasCompleted).to.beFalsy(); + + [a sendNext:@4]; + [a sendNext:@5]; + [a sendNext:@6]; + [a sendNext:@7]; + + // a: [=======..] + // b: [====C....] + // c: [=====E...] + + expectedValues = @[ @"111", @"222", @"333" ]; + expect(receivedValues).to.equal(expectedValues); + expect(receivedError).to.equal(RACSignalTestError); + expect(hasCompleted).to.beFalsy(); + }); + + it(@"should handle multiples of the same side-effecting signal", ^{ + __block NSUInteger counter = 0; + RACSignal *sideEffectingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + ++counter; + [subscriber sendNext:@1]; + [subscriber sendCompleted]; + return nil; + }]; + RACSignal *combined = [RACSignal zip:@[ sideEffectingSignal, sideEffectingSignal ] reduce:^ NSString * (id x, id y) { + return [NSString stringWithFormat:@"%@%@", x, y]; + }]; + NSMutableArray *receivedValues = NSMutableArray.array; + + expect(counter).to.equal(0); + + [combined subscribeNext:^(id x) { + [receivedValues addObject:x]; + }]; + + expect(counter).to.equal(2); + expect(receivedValues).to.equal(@[ @"11" ]); + }); +}); + +describe(@"-sample:", ^{ + it(@"should send the latest value when the sampler signal fires", ^{ + RACSubject *subject = [RACSubject subject]; + RACSubject *sampleSubject = [RACSubject subject]; + RACSignal *sampled = [subject sample:sampleSubject]; + NSMutableArray *values = [NSMutableArray array]; + [sampled subscribeNext:^(id x) { + [values addObject:x]; + }]; + + [sampleSubject sendNext:RACUnit.defaultUnit]; + expect(values).to.equal(@[]); + + [subject sendNext:@1]; + [subject sendNext:@2]; + expect(values).to.equal(@[]); + + [sampleSubject sendNext:RACUnit.defaultUnit]; + NSArray *expected = @[ @2 ]; + expect(values).to.equal(expected); + + [subject sendNext:@3]; + expect(values).to.equal(expected); + + [sampleSubject sendNext:RACUnit.defaultUnit]; + expected = @[ @2, @3 ]; + expect(values).to.equal(expected); + + [sampleSubject sendNext:RACUnit.defaultUnit]; + expected = @[ @2, @3, @3 ]; + expect(values).to.equal(expected); + }); +}); + +describe(@"-collect", ^{ + __block RACSubject *subject; + __block RACSignal *collected; + + __block id value; + __block BOOL hasCompleted; + + beforeEach(^{ + subject = [RACSubject subject]; + collected = [subject collect]; + + value = nil; + hasCompleted = NO; + + [collected subscribeNext:^(id x) { + value = x; + } completed:^{ + hasCompleted = YES; + }]; + }); + + it(@"should send a single array when the original signal completes", ^{ + NSArray *expected = @[ @1, @2, @3 ]; + + [subject sendNext:@1]; + [subject sendNext:@2]; + [subject sendNext:@3]; + expect(value).to.beNil(); + + [subject sendCompleted]; + expect(value).to.equal(expected); + expect(hasCompleted).to.beTruthy(); + }); + + it(@"should add NSNull to an array for nil values", ^{ + NSArray *expected = @[ NSNull.null, @1, NSNull.null ]; + + [subject sendNext:nil]; + [subject sendNext:@1]; + [subject sendNext:nil]; + expect(value).to.beNil(); + + [subject sendCompleted]; + expect(value).to.equal(expected); + expect(hasCompleted).to.beTruthy(); + }); +}); + +describe(@"-bufferWithTime:", ^{ + __block RACTestScheduler *scheduler; + + __block RACSubject *input; + __block RACSignal *bufferedInput; + __block RACTuple *latestValue; + + beforeEach(^{ + scheduler = [[RACTestScheduler alloc] init]; + + input = [RACSubject subject]; + bufferedInput = [input bufferWithTime:1 onScheduler:scheduler]; + latestValue = nil; + + [bufferedInput subscribeNext:^(RACTuple *x) { + latestValue = x; + }]; + }); + + it(@"should buffer nexts", ^{ + [input sendNext:@1]; + [input sendNext:@2]; + + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(@1, @2)); + + [input sendNext:@3]; + [input sendNext:@4]; + + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(@3, @4)); + }); + + it(@"should not perform buffering until a value is sent", ^{ + [input sendNext:@1]; + [input sendNext:@2]; + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(@1, @2)); + + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(@1, @2)); + + [input sendNext:@3]; + [input sendNext:@4]; + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(@3, @4)); + }); + + it(@"should flush any buffered nexts upon completion", ^{ + [input sendNext:@1]; + [input sendCompleted]; + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(@1)); + }); + + it(@"should support NSNull values", ^{ + [input sendNext:NSNull.null]; + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(NSNull.null)); + }); + + it(@"should buffer nil values", ^{ + [input sendNext:nil]; + [scheduler stepAll]; + expect(latestValue).to.equal(RACTuplePack(nil)); + }); +}); + +describe(@"-concat", ^{ + __block RACSubject *subject; + + __block RACSignal *oneSignal; + __block RACSignal *twoSignal; + __block RACSignal *threeSignal; + + __block RACSignal *errorSignal; + __block RACSignal *completedSignal; + + beforeEach(^{ + subject = [RACReplaySubject subject]; + + oneSignal = [RACSignal return:@1]; + twoSignal = [RACSignal return:@2]; + threeSignal = [RACSignal return:@3]; + + errorSignal = [RACSignal error:RACSignalTestError]; + completedSignal = RACSignal.empty; + }); + + it(@"should concatenate the values of inner signals", ^{ + [subject sendNext:oneSignal]; + [subject sendNext:twoSignal]; + [subject sendNext:completedSignal]; + [subject sendNext:threeSignal]; + + NSMutableArray *values = [NSMutableArray array]; + [[subject concat] subscribeNext:^(id x) { + [values addObject:x]; + }]; + + NSArray *expected = @[ @1, @2, @3 ]; + expect(values).to.equal(expected); + }); + + it(@"should complete only after all signals complete", ^{ + RACReplaySubject *valuesSubject = [RACReplaySubject subject]; + + [subject sendNext:valuesSubject]; + [subject sendCompleted]; + + [valuesSubject sendNext:@1]; + [valuesSubject sendNext:@2]; + [valuesSubject sendCompleted]; + + NSArray *expected = @[ @1, @2 ]; + expect([[subject concat] toArray]).to.equal(expected); + }); + + it(@"should pass through errors", ^{ + [subject sendNext:errorSignal]; + + NSError *error = nil; + [[subject concat] firstOrDefault:nil success:NULL error:&error]; + expect(error).to.equal(RACSignalTestError); + }); + + it(@"should concat signals sent later", ^{ + [subject sendNext:oneSignal]; + + NSMutableArray *values = [NSMutableArray array]; + [[subject concat] subscribeNext:^(id x) { + [values addObject:x]; + }]; + + NSArray *expected = @[ @1 ]; + expect(values).to.equal(expected); + + [subject sendNext:[twoSignal delay:0]]; + + expected = @[ @1, @2 ]; + expect(values).will.equal(expected); + + [subject sendNext:threeSignal]; + + expected = @[ @1, @2, @3 ]; + expect(values).to.equal(expected); + }); + + it(@"should dispose the current signal", ^{ + __block BOOL disposed = NO; + RACSignal *innerSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + + RACDisposable *concatDisposable = [[subject concat] subscribeCompleted:^{}]; + + [subject sendNext:innerSignal]; + expect(disposed).notTo.beTruthy(); + + [concatDisposable dispose]; + expect(disposed).to.beTruthy(); + }); + + it(@"should dispose later signals", ^{ + __block BOOL disposed = NO; + RACSignal *laterSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + + RACSubject *firstSignal = [RACSubject subject]; + RACSignal *outerSignal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:firstSignal]; + [subscriber sendNext:laterSignal]; + return nil; + }]; + + RACDisposable *concatDisposable = [[outerSignal concat] subscribeCompleted:^{}]; + + [firstSignal sendCompleted]; + expect(disposed).notTo.beTruthy(); + + [concatDisposable dispose]; + expect(disposed).to.beTruthy(); + }); +}); + +describe(@"-initially:", ^{ + __block RACSubject *subject; + + __block NSUInteger initiallyInvokedCount; + __block RACSignal *signal; + + beforeEach(^{ + subject = [RACSubject subject]; + + initiallyInvokedCount = 0; + signal = [subject initially:^{ + ++initiallyInvokedCount; + }]; + }); + + it(@"should not run without a subscription", ^{ + [subject sendCompleted]; + expect(initiallyInvokedCount).to.equal(0); + }); + + it(@"should run on subscription", ^{ + [signal subscribe:[RACSubscriber new]]; + expect(initiallyInvokedCount).to.equal(1); + }); + + it(@"should re-run for each subscription", ^{ + [signal subscribe:[RACSubscriber new]]; + [signal subscribe:[RACSubscriber new]]; + expect(initiallyInvokedCount).to.equal(2); + }); +}); + +describe(@"-finally:", ^{ + __block RACSubject *subject; + + __block BOOL finallyInvoked; + __block RACSignal *signal; + + beforeEach(^{ + subject = [RACSubject subject]; + + finallyInvoked = NO; + signal = [subject finally:^{ + finallyInvoked = YES; + }]; + }); + + it(@"should not run finally without a subscription", ^{ + [subject sendCompleted]; + expect(finallyInvoked).to.beFalsy(); + }); + + describe(@"with a subscription", ^{ + __block RACDisposable *disposable; + + beforeEach(^{ + disposable = [signal subscribeCompleted:^{}]; + }); + + afterEach(^{ + [disposable dispose]; + }); + + it(@"should not run finally upon next", ^{ + [subject sendNext:RACUnit.defaultUnit]; + expect(finallyInvoked).to.beFalsy(); + }); + + it(@"should run finally upon completed", ^{ + [subject sendCompleted]; + expect(finallyInvoked).to.beTruthy(); + }); + + it(@"should run finally upon error", ^{ + [subject sendError:nil]; + expect(finallyInvoked).to.beTruthy(); + }); + }); +}); + +describe(@"-ignoreValues", ^{ + __block RACSubject *subject; + + __block BOOL gotNext; + __block BOOL gotCompleted; + __block NSError *receivedError; + + beforeEach(^{ + subject = [RACSubject subject]; + + gotNext = NO; + gotCompleted = NO; + receivedError = nil; + + [[subject ignoreValues] subscribeNext:^(id _) { + gotNext = YES; + } error:^(NSError *error) { + receivedError = error; + } completed:^{ + gotCompleted = YES; + }]; + }); + + it(@"should skip nexts and pass through completed", ^{ + [subject sendNext:RACUnit.defaultUnit]; + [subject sendCompleted]; + + expect(gotNext).to.beFalsy(); + expect(gotCompleted).to.beTruthy(); + expect(receivedError).to.beNil(); + }); + + it(@"should skip nexts and pass through errors", ^{ + [subject sendNext:RACUnit.defaultUnit]; + [subject sendError:RACSignalTestError]; + + expect(gotNext).to.beFalsy(); + expect(gotCompleted).to.beFalsy(); + expect(receivedError).to.equal(RACSignalTestError); + }); +}); + +describe(@"-materialize", ^{ + it(@"should convert nexts and completed into RACEvents", ^{ + NSArray *events = [[[RACSignal return:RACUnit.defaultUnit] materialize] toArray]; + NSArray *expected = @[ + [RACEvent eventWithValue:RACUnit.defaultUnit], + RACEvent.completedEvent + ]; + + expect(events).to.equal(expected); + }); + + it(@"should convert errors into RACEvents and complete", ^{ + NSArray *events = [[[RACSignal error:RACSignalTestError] materialize] toArray]; + NSArray *expected = @[ [RACEvent eventWithError:RACSignalTestError] ]; + expect(events).to.equal(expected); + }); +}); + +describe(@"-dematerialize", ^{ + it(@"should convert nexts from RACEvents", ^{ + RACSignal *events = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:[RACEvent eventWithValue:@1]]; + [subscriber sendNext:[RACEvent eventWithValue:@2]]; + [subscriber sendCompleted]; + return nil; + }]; + + NSArray *expected = @[ @1, @2 ]; + expect([[events dematerialize] toArray]).to.equal(expected); + }); + + it(@"should convert completed from a RACEvent", ^{ + RACSignal *events = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:[RACEvent eventWithValue:@1]]; + [subscriber sendNext:RACEvent.completedEvent]; + [subscriber sendNext:[RACEvent eventWithValue:@2]]; + [subscriber sendCompleted]; + return nil; + }]; + + NSArray *expected = @[ @1 ]; + expect([[events dematerialize] toArray]).to.equal(expected); + }); + + it(@"should convert error from a RACEvent", ^{ + RACSignal *events = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:[RACEvent eventWithError:RACSignalTestError]]; + [subscriber sendNext:[RACEvent eventWithValue:@1]]; + [subscriber sendCompleted]; + return nil; + }]; + + __block NSError *error = nil; + expect([[events dematerialize] firstOrDefault:nil success:NULL error:&error]).to.beNil(); + expect(error).to.equal(RACSignalTestError); + }); +}); + +describe(@"-not", ^{ + it(@"should invert every BOOL sent", ^{ + RACSubject *subject = [RACReplaySubject subject]; + [subject sendNext:@NO]; + [subject sendNext:@YES]; + [subject sendCompleted]; + NSArray *results = [[subject not] toArray]; + NSArray *expected = @[ @YES, @NO ]; + expect(results).to.equal(expected); + }); +}); + +describe(@"-and", ^{ + it(@"should return YES if all YES values are sent", ^{ + RACSubject *subject = [RACReplaySubject subject]; + + [subject sendNext:RACTuplePack(@YES, @NO, @YES)]; + [subject sendNext:RACTuplePack(@NO, @NO, @NO)]; + [subject sendNext:RACTuplePack(@YES, @YES, @YES)]; + [subject sendCompleted]; + + NSArray *results = [[subject and] toArray]; + NSArray *expected = @[ @NO, @NO, @YES ]; + + expect(results).to.equal(expected); + }); +}); + +describe(@"-or", ^{ + it(@"should return YES for any YES values sent", ^{ + RACSubject *subject = [RACReplaySubject subject]; + + [subject sendNext:RACTuplePack(@YES, @NO, @YES)]; + [subject sendNext:RACTuplePack(@NO, @NO, @NO)]; + [subject sendCompleted]; + + NSArray *results = [[subject or] toArray]; + NSArray *expected = @[ @YES, @NO ]; + + expect(results).to.equal(expected); + }); +}); + +describe(@"-groupBy:", ^{ + it(@"should send completed to all grouped signals.", ^{ + RACSubject *subject = [RACReplaySubject subject]; + + __block NSUInteger groupedSignalCount = 0; + __block NSUInteger completedGroupedSignalCount = 0; + [[subject groupBy:^(NSNumber *number) { + return @(floorf(number.floatValue)); + }] subscribeNext:^(RACGroupedSignal *groupedSignal) { + ++groupedSignalCount; + + [groupedSignal subscribeCompleted:^{ + ++completedGroupedSignalCount; + }]; + }]; + + [subject sendNext:@1]; + [subject sendNext:@2]; + [subject sendCompleted]; + + expect(completedGroupedSignalCount).to.equal(groupedSignalCount); + }); + + it(@"should send error to all grouped signals.", ^{ + RACSubject *subject = [RACReplaySubject subject]; + + __block NSUInteger groupedSignalCount = 0; + __block NSUInteger erroneousGroupedSignalCount = 0; + [[subject groupBy:^(NSNumber *number) { + return @(floorf(number.floatValue)); + }] subscribeNext:^(RACGroupedSignal *groupedSignal) { + ++groupedSignalCount; + + [groupedSignal subscribeError:^(NSError *error) { + ++erroneousGroupedSignalCount; + + expect(error.domain).to.equal(@"TestDomain"); + expect(error.code).to.equal(123); + }]; + }]; + + [subject sendNext:@1]; + [subject sendNext:@2]; + [subject sendError:[NSError errorWithDomain:@"TestDomain" code:123 userInfo:nil]]; + + expect(erroneousGroupedSignalCount).to.equal(groupedSignalCount); + }); +}); + +describe(@"starting signals", ^{ + describe(@"+startLazilyWithScheduler:block:", ^{ + itBehavesLike(RACSignalStartSharedExamplesName, ^{ + NSArray *expectedValues = @[ @42, @43 ]; + RACScheduler *scheduler = [RACScheduler scheduler]; + RACSignal *signal = [RACSignal startLazilyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { + for (id value in expectedValues) { + [subscriber sendNext:value]; + } + [subscriber sendCompleted]; + }]; + return @{ + RACSignalStartSignal: signal, + RACSignalStartExpectedValues: expectedValues, + RACSignalStartExpectedScheduler: scheduler, + }; + }); + + __block NSUInteger invokedCount = 0; + __block void (^subscribe)(void); + + beforeEach(^{ + invokedCount = 0; + + RACSignal *signal = [RACSignal startLazilyWithScheduler:RACScheduler.immediateScheduler block:^(id<RACSubscriber> subscriber) { + invokedCount++; + [subscriber sendNext:@42]; + [subscriber sendCompleted]; + }]; + + subscribe = [^{ + [signal subscribe:[RACSubscriber subscriberWithNext:nil error:nil completed:nil]]; + } copy]; + }); + + it(@"should only invoke the block on subscription", ^{ + expect(invokedCount).to.equal(0); + subscribe(); + expect(invokedCount).to.equal(1); + }); + + it(@"should only invoke the block once", ^{ + expect(invokedCount).to.equal(0); + subscribe(); + expect(invokedCount).to.equal(1); + subscribe(); + expect(invokedCount).to.equal(1); + subscribe(); + expect(invokedCount).to.equal(1); + }); + + it(@"should invoke the block on the given scheduler", ^{ + RACScheduler *scheduler = [RACScheduler scheduler]; + __block RACScheduler *currentScheduler; + [[[RACSignal + startLazilyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { + currentScheduler = RACScheduler.currentScheduler; + }] + publish] + connect]; + + expect(currentScheduler).will.equal(scheduler); + }); + }); + + describe(@"+startEagerlyWithScheduler:block:", ^{ + itBehavesLike(RACSignalStartSharedExamplesName, ^{ + NSArray *expectedValues = @[ @42, @43 ]; + RACScheduler *scheduler = [RACScheduler scheduler]; + RACSignal *signal = [RACSignal startEagerlyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { + for (id value in expectedValues) { + [subscriber sendNext:value]; + } + [subscriber sendCompleted]; + }]; + return @{ + RACSignalStartSignal: signal, + RACSignalStartExpectedValues: expectedValues, + RACSignalStartExpectedScheduler: scheduler, + }; + }); + + it(@"should immediately invoke the block", ^{ + __block BOOL blockInvoked = NO; + [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) { + blockInvoked = YES; + }]; + + expect(blockInvoked).will.beTruthy(); + }); + + it(@"should only invoke the block once", ^{ + __block NSUInteger invokedCount = 0; + RACSignal *signal = [RACSignal startEagerlyWithScheduler:RACScheduler.immediateScheduler block:^(id<RACSubscriber> subscriber) { + invokedCount++; + }]; + + expect(invokedCount).to.equal(1); + + [[signal publish] connect]; + expect(invokedCount).to.equal(1); + + [[signal publish] connect]; + expect(invokedCount).to.equal(1); + }); + + it(@"should invoke the block on the given scheduler", ^{ + RACScheduler *scheduler = [RACScheduler scheduler]; + __block RACScheduler *currentScheduler; + [RACSignal startEagerlyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { + currentScheduler = RACScheduler.currentScheduler; + }]; + + expect(currentScheduler).will.equal(scheduler); + }); + }); +}); + +describe(@"-toArray", ^{ + __block RACSubject *subject; + + beforeEach(^{ + subject = [RACReplaySubject subject]; + }); + + it(@"should return an array which contains NSNulls for nil values", ^{ + NSArray *expected = @[ NSNull.null, @1, NSNull.null ]; + + [subject sendNext:nil]; + [subject sendNext:@1]; + [subject sendNext:nil]; + [subject sendCompleted]; + + expect([subject toArray]).to.equal(expected); + }); + + it(@"should return nil upon error", ^{ + [subject sendError:nil]; + expect([subject toArray]).to.beNil(); + }); + + it(@"should return nil upon error even if some nexts were sent", ^{ + [subject sendNext:@1]; + [subject sendNext:@2]; + [subject sendError:nil]; + + expect([subject toArray]).to.beNil(); + }); +}); + +describe(@"-ignore:", ^{ + it(@"should ignore nil", ^{ + RACSignal *signal = [[RACSignal + createSignal:^ id (id<RACSubscriber> subscriber) { + [subscriber sendNext:@1]; + [subscriber sendNext:nil]; + [subscriber sendNext:@3]; + [subscriber sendNext:@4]; + [subscriber sendNext:nil]; + [subscriber sendCompleted]; + return nil; + }] + ignore:nil]; + + NSArray *expected = @[ @1, @3, @4 ]; + expect([signal toArray]).to.equal(expected); + }); +}); + +describe(@"-replayLazily", ^{ + __block NSUInteger subscriptionCount; + __block BOOL disposed; + + __block RACSignal *signal; + __block RACSubject *disposeSubject; + __block RACSignal *replayedSignal; + + beforeEach(^{ + subscriptionCount = 0; + disposed = NO; + + signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { + subscriptionCount++; + [subscriber sendNext:RACUnit.defaultUnit]; + + RACDisposable *schedulingDisposable = [RACScheduler.mainThreadScheduler schedule:^{ + [subscriber sendNext:RACUnit.defaultUnit]; + [subscriber sendCompleted]; + }]; + + return [RACDisposable disposableWithBlock:^{ + [schedulingDisposable dispose]; + disposed = YES; + }]; + }]; + + disposeSubject = [RACSubject subject]; + replayedSignal = [[signal takeUntil:disposeSubject] replayLazily]; + }); + + it(@"should forward the input signal upon subscription", ^{ + expect(subscriptionCount).to.equal(0); + + expect([replayedSignal asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); + expect(subscriptionCount).to.equal(1); + }); + + it(@"should replay the input signal for future subscriptions", ^{ + NSArray *events = [[[replayedSignal materialize] collect] asynchronousFirstOrDefault:nil success:NULL error:NULL]; + expect(events).notTo.beNil(); + + expect([[[replayedSignal materialize] collect] asynchronousFirstOrDefault:nil success:NULL error:NULL]).to.equal(events); + expect(subscriptionCount).to.equal(1); + }); + + it(@"should replay even after disposal", ^{ + __block NSUInteger valueCount = 0; + [replayedSignal subscribeNext:^(id x) { + valueCount++; + }]; + + [disposeSubject sendCompleted]; + expect(valueCount).to.equal(1); + expect([[replayedSignal toArray] count]).to.equal(valueCount); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalStartExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalStartExamples.h new file mode 100644 index 0000000..3e01c36 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalStartExamples.h
@@ -0,0 +1,20 @@ +// +// RACSignalStartExamples.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/29/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +extern NSString * const RACSignalStartSharedExamplesName; + +// The signal to test, created by some +start...: variation. +extern NSString * const RACSignalStartSignal; + +// An NSArray of the values which the signal should be expected to send. +extern NSString * const RACSignalStartExpectedValues; + +// The scheduler on which the signal should be expected to send values. +extern NSString * const RACSignalStartExpectedScheduler;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalStartExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalStartExamples.m new file mode 100644 index 0000000..6416630 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSignalStartExamples.m
@@ -0,0 +1,74 @@ +// +// RACSignalStartExamples.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 5/29/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignalStartExamples.h" +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "RACScheduler.h" +#import "RACSubscriber.h" +#import "RACMulticastConnection.h" + +NSString * const RACSignalStartSharedExamplesName = @"RACSignalStartSharedExamplesName"; + +NSString * const RACSignalStartSignal = @"RACSignalStartSignal"; +NSString * const RACSignalStartExpectedValues = @"RACSignalStartExpectedValues"; +NSString * const RACSignalStartExpectedScheduler = @"RACSignalStartExpectedScheduler"; + +SharedExampleGroupsBegin(RACSignalStartSpec) + +sharedExamples(RACSignalStartSharedExamplesName, ^(NSDictionary *data) { + __block RACSignal *signal; + __block NSArray *expectedValues; + __block RACScheduler *scheduler; + __block RACScheduler * (^subscribeAndGetScheduler)(void); + + beforeEach(^{ + signal = data[RACSignalStartSignal]; + expectedValues = data[RACSignalStartExpectedValues]; + scheduler = data[RACSignalStartExpectedScheduler]; + + subscribeAndGetScheduler = [^{ + __block RACScheduler *schedulerInDelivery; + [signal subscribeNext:^(id _) { + schedulerInDelivery = RACScheduler.currentScheduler; + }]; + + expect(schedulerInDelivery).willNot.beNil(); + return schedulerInDelivery; + } copy]; + }); + + it(@"should send values from the returned signal", ^{ + NSArray *values = [signal toArray]; + expect(values).to.equal(expectedValues); + }); + + it(@"should replay all values", ^{ + // Force a subscription so that we get replayed results. + [[signal publish] connect]; + + NSArray *values = [signal toArray]; + expect(values).to.equal(expectedValues); + }); + + it(@"should deliver the original results on the given scheduler", ^{ + RACScheduler *currentScheduler = subscribeAndGetScheduler(); + expect(currentScheduler).to.equal(scheduler); + }); + + it(@"should deliver replayed results on the given scheduler", ^{ + // Force a subscription so that we get replayed results on the + // tested subscription. + subscribeAndGetScheduler(); + + RACScheduler *currentScheduler = subscribeAndGetScheduler(); + expect(currentScheduler).to.equal(scheduler); + }); +}); + +SharedExampleGroupsEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACStreamExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACStreamExamples.h new file mode 100644 index 0000000..318a6a8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACStreamExamples.h
@@ -0,0 +1,26 @@ +// +// RACStreamExamples.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-01. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +// The name of the shared examples for a RACStream subclass. +extern NSString * const RACStreamExamples; + +// The RACStream subclass to test. +extern NSString * const RACStreamExamplesClass; + +// An infinite RACStream to test, making sure that certain operations +// terminate. +// +// The stream should contain infinite RACUnit values. +extern NSString * const RACStreamExamplesInfiniteStream; + +// A block with the signature: +// +// void (^)(RACStream *stream, NSArray *expectedValues) +// +// … used to verify that a stream contains the expected values. +extern NSString * const RACStreamExamplesVerifyValuesBlock;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACStreamExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACStreamExamples.m new file mode 100644 index 0000000..bdf22f1 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACStreamExamples.m
@@ -0,0 +1,648 @@ +// +// RACStreamExamples.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-01. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACStreamExamples.h" + +#import "RACStream.h" +#import "RACUnit.h" +#import "RACTuple.h" + +NSString * const RACStreamExamples = @"RACStreamExamples"; +NSString * const RACStreamExamplesClass = @"RACStreamExamplesClass"; +NSString * const RACStreamExamplesInfiniteStream = @"RACStreamExamplesInfiniteStream"; +NSString * const RACStreamExamplesVerifyValuesBlock = @"RACStreamExamplesVerifyValuesBlock"; + +SharedExampleGroupsBegin(RACStreamExamples) + +sharedExamplesFor(RACStreamExamples, ^(NSDictionary *data) { + __block Class streamClass; + __block void (^verifyValues)(RACStream *, NSArray *); + __block RACStream *infiniteStream; + + __block RACStream *(^streamWithValues)(NSArray *); + + before(^{ + streamClass = data[RACStreamExamplesClass]; + verifyValues = data[RACStreamExamplesVerifyValuesBlock]; + infiniteStream = data[RACStreamExamplesInfiniteStream]; + streamWithValues = [^(NSArray *values) { + RACStream *stream = [streamClass empty]; + + for (id value in values) { + stream = [stream concat:[streamClass return:value]]; + } + + return stream; + } copy]; + }); + + it(@"should return an empty stream", ^{ + RACStream *stream = [streamClass empty]; + verifyValues(stream, @[]); + }); + + it(@"should lift a value into a stream", ^{ + RACStream *stream = [streamClass return:RACUnit.defaultUnit]; + verifyValues(stream, @[ RACUnit.defaultUnit ]); + }); + + describe(@"-concat:", ^{ + it(@"should concatenate two streams", ^{ + RACStream *stream = [[streamClass return:@0] concat:[streamClass return:@1]]; + verifyValues(stream, @[ @0, @1 ]); + }); + + it(@"should concatenate three streams", ^{ + RACStream *stream = [[[streamClass return:@0] concat:[streamClass return:@1]] concat:[streamClass return:@2]]; + verifyValues(stream, @[ @0, @1, @2 ]); + }); + + it(@"should concatenate around an empty stream", ^{ + RACStream *stream = [[[streamClass return:@0] concat:[streamClass empty]] concat:[streamClass return:@2]]; + verifyValues(stream, @[ @0, @2 ]); + }); + }); + + it(@"should flatten", ^{ + RACStream *stream = [[streamClass return:[streamClass return:RACUnit.defaultUnit]] flatten]; + verifyValues(stream, @[ RACUnit.defaultUnit ]); + }); + + describe(@"-bind:", ^{ + it(@"should return the result of binding a single value", ^{ + RACStream *stream = [[streamClass return:@0] bind:^{ + return ^(NSNumber *value, BOOL *stop) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }; + }]; + + verifyValues(stream, @[ @1 ]); + }); + + it(@"should concatenate the result of binding multiple values", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1 ]); + RACStream *stream = [baseStream bind:^{ + return ^(NSNumber *value, BOOL *stop) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }; + }]; + + verifyValues(stream, @[ @1, @2 ]); + }); + + it(@"should concatenate with an empty result from binding a value", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream bind:^{ + return ^(NSNumber *value, BOOL *stop) { + if (value.integerValue == 1) return [streamClass empty]; + + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }; + }]; + + verifyValues(stream, @[ @1, @3 ]); + }); + + it(@"should terminate immediately when returning nil", ^{ + RACStream *stream = [infiniteStream bind:^{ + return ^ id (id _, BOOL *stop) { + return nil; + }; + }]; + + verifyValues(stream, @[]); + }); + + it(@"should terminate after one value when setting 'stop'", ^{ + RACStream *stream = [infiniteStream bind:^{ + return ^ id (id value, BOOL *stop) { + *stop = YES; + return [streamClass return:value]; + }; + }]; + + verifyValues(stream, @[ RACUnit.defaultUnit ]); + }); + + it(@"should terminate immediately when returning nil and setting 'stop'", ^{ + RACStream *stream = [infiniteStream bind:^{ + return ^ id (id _, BOOL *stop) { + *stop = YES; + return nil; + }; + }]; + + verifyValues(stream, @[]); + }); + + it(@"should be restartable even with block state", ^{ + NSArray *values = @[ @0, @1, @2 ]; + RACStream *baseStream = streamWithValues(values); + + RACStream *countingStream = [baseStream bind:^{ + __block NSUInteger counter = 0; + + return ^(id x, BOOL *stop) { + return [streamClass return:@(counter++)]; + }; + }]; + + verifyValues(countingStream, @[ @0, @1, @2 ]); + verifyValues(countingStream, @[ @0, @1, @2 ]); + }); + + it(@"should be interleavable even with block state", ^{ + NSArray *values = @[ @0, @1, @2 ]; + RACStream *baseStream = streamWithValues(values); + + RACStream *countingStream = [baseStream bind:^{ + __block NSUInteger counter = 0; + + return ^(id x, BOOL *stop) { + return [streamClass return:@(counter++)]; + }; + }]; + + // Just so +zip:reduce: thinks this is a unique stream. + RACStream *anotherStream = [[streamClass empty] concat:countingStream]; + + RACStream *zipped = [streamClass zip:@[ countingStream, anotherStream ] reduce:^(NSNumber *v1, NSNumber *v2) { + return @(v1.integerValue + v2.integerValue); + }]; + + verifyValues(zipped, @[ @0, @2, @4 ]); + }); + }); + + describe(@"-flattenMap:", ^{ + it(@"should return a single mapped result", ^{ + RACStream *stream = [[streamClass return:@0] flattenMap:^(NSNumber *value) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1 ]); + }); + + it(@"should concatenate the results of mapping multiple values", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1 ]); + RACStream *stream = [baseStream flattenMap:^(NSNumber *value) { + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1, @2 ]); + }); + + it(@"should concatenate with an empty result from mapping a value", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream flattenMap:^(NSNumber *value) { + if (value.integerValue == 1) return [streamClass empty]; + + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1, @3 ]); + }); + + it(@"should treat nil streams like empty streams", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream flattenMap:^ RACStream * (NSNumber *value) { + if (value.integerValue == 1) return nil; + + NSNumber *newValue = @(value.integerValue + 1); + return [streamClass return:newValue]; + }]; + + verifyValues(stream, @[ @1, @3 ]); + }); + }); + + it(@"should map", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream map:^(NSNumber *value) { + return @(value.integerValue + 1); + }]; + + verifyValues(stream, @[ @1, @2, @3 ]); + }); + + it(@"should map and replace", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); + RACStream *stream = [baseStream mapReplace:RACUnit.defaultUnit]; + + verifyValues(stream, @[ RACUnit.defaultUnit, RACUnit.defaultUnit, RACUnit.defaultUnit ]); + }); + + it(@"should filter", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2, @3, @4, @5, @6 ]); + RACStream *stream = [baseStream filter:^ BOOL (NSNumber *value) { + return value.integerValue % 2 == 0; + }]; + + verifyValues(stream, @[ @0, @2, @4, @6 ]); + }); + + describe(@"-ignore:", ^{ + it(@"should ignore a value", ^{ + RACStream *baseStream = streamWithValues(@[ @0, @1, @2, @3, @4, @5, @6 ]); + RACStream *stream = [baseStream ignore:@1]; + + verifyValues(stream, @[ @0, @2, @3, @4, @5, @6 ]); + }); + + it(@"should ignore based on object equality", ^{ + RACStream *baseStream = streamWithValues(@[ @"0", @"1", @"2", @"3", @"4", @"5", @"6" ]); + + NSMutableString *valueToIgnore = [[NSMutableString alloc] init]; + [valueToIgnore appendString:@"1"]; + RACStream *stream = [baseStream ignore:valueToIgnore]; + + verifyValues(stream, @[ @"0", @"2", @"3", @"4", @"5", @"6" ]); + }); + }); + + it(@"should start with a value", ^{ + RACStream *stream = [[streamClass return:@1] startWith:@0]; + verifyValues(stream, @[ @0, @1 ]); + }); + + describe(@"-skip:", ^{ + __block NSArray *values; + __block RACStream *stream; + + before(^{ + values = @[ @0, @1, @2 ]; + stream = streamWithValues(values); + }); + + it(@"should skip any valid number of values", ^{ + for (NSUInteger i = 0; i < values.count; i++) { + verifyValues([stream skip:i], [values subarrayWithRange:NSMakeRange(i, values.count - i)]); + } + }); + + it(@"should return an empty stream when skipping too many values", ^{ + verifyValues([stream skip:4], @[]); + }); + }); + + describe(@"-take:", ^{ + describe(@"with three values", ^{ + __block NSArray *values; + __block RACStream *stream; + + before(^{ + values = @[ @0, @1, @2 ]; + stream = streamWithValues(values); + }); + + it(@"should take any valid number of values", ^{ + for (NSUInteger i = 0; i < values.count; i++) { + verifyValues([stream take:i], [values subarrayWithRange:NSMakeRange(0, i)]); + } + }); + + it(@"should return the same stream when taking too many values", ^{ + verifyValues([stream take:4], values); + }); + }); + + it(@"should take and terminate from an infinite stream", ^{ + verifyValues([infiniteStream take:0], @[]); + verifyValues([infiniteStream take:1], @[ RACUnit.defaultUnit ]); + verifyValues([infiniteStream take:2], @[ RACUnit.defaultUnit, RACUnit.defaultUnit ]); + }); + + it(@"should take and terminate from a single-item stream", ^{ + NSArray *values = @[ RACUnit.defaultUnit ]; + RACStream *stream = streamWithValues(values); + verifyValues([stream take:1], values); + }); + }); + + describe(@"zip stream creation methods", ^{ + __block NSArray *valuesOne; + + __block RACStream *streamOne; + __block RACStream *streamTwo; + __block RACStream *streamThree; + __block NSArray *threeStreams; + + __block NSArray *oneStreamTuples; + __block NSArray *twoStreamTuples; + __block NSArray *threeStreamTuples; + + before(^{ + valuesOne = @[ @"Ada", @"Bob", @"Dea" ]; + NSArray *valuesTwo = @[ @"eats", @"cooks", @"jumps" ]; + NSArray *valuesThree = @[ @"fish", @"bear", @"rock" ]; + + streamOne = streamWithValues(valuesOne); + streamTwo = streamWithValues(valuesTwo); + streamThree = streamWithValues(valuesThree); + threeStreams = @[ streamOne, streamTwo, streamThree ]; + + oneStreamTuples = @[ + RACTuplePack(valuesOne[0]), + RACTuplePack(valuesOne[1]), + RACTuplePack(valuesOne[2]), + ]; + + twoStreamTuples = @[ + RACTuplePack(valuesOne[0], valuesTwo[0]), + RACTuplePack(valuesOne[1], valuesTwo[1]), + RACTuplePack(valuesOne[2], valuesTwo[2]), + ]; + + threeStreamTuples = @[ + RACTuplePack(valuesOne[0], valuesTwo[0], valuesThree[0]), + RACTuplePack(valuesOne[1], valuesTwo[1], valuesThree[1]), + RACTuplePack(valuesOne[2], valuesTwo[2], valuesThree[2]), + ]; + }); + + describe(@"-zipWith:", ^{ + it(@"should make a stream of tuples", ^{ + RACStream *stream = [streamOne zipWith:streamTwo]; + verifyValues(stream, twoStreamTuples); + }); + + it(@"should truncate streams", ^{ + RACStream *shortStream = streamWithValues(@[ @"now", @"later" ]); + RACStream *stream = [streamOne zipWith:shortStream]; + + verifyValues(stream, @[ + RACTuplePack(valuesOne[0], @"now"), + RACTuplePack(valuesOne[1], @"later") + ]); + }); + + it(@"should work on infinite streams", ^{ + RACStream *stream = [streamOne zipWith:infiniteStream]; + verifyValues(stream, @[ + RACTuplePack(valuesOne[0], RACUnit.defaultUnit), + RACTuplePack(valuesOne[1], RACUnit.defaultUnit), + RACTuplePack(valuesOne[2], RACUnit.defaultUnit) + ]); + }); + + it(@"should handle multiples of the same stream", ^{ + RACStream *stream = [streamOne zipWith:streamOne]; + verifyValues(stream, @[ + RACTuplePack(valuesOne[0], valuesOne[0]), + RACTuplePack(valuesOne[1], valuesOne[1]), + RACTuplePack(valuesOne[2], valuesOne[2]), + ]); + }); + }); + + describe(@"+zip:reduce:", ^{ + it(@"should reduce values", ^{ + RACStream *stream = [streamClass zip:threeStreams reduce:^ NSString * (id x, id y, id z) { + return [NSString stringWithFormat:@"%@ %@ %@", x, y, z]; + }]; + verifyValues(stream, @[ @"Ada eats fish", @"Bob cooks bear", @"Dea jumps rock" ]); + }); + + it(@"should truncate streams", ^{ + RACStream *shortStream = streamWithValues(@[ @"now", @"later" ]); + NSArray *streams = [threeStreams arrayByAddingObject:shortStream]; + RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id w, id x, id y, id z) { + return [NSString stringWithFormat:@"%@ %@ %@ %@", w, x, y, z]; + }]; + verifyValues(stream, @[ @"Ada eats fish now", @"Bob cooks bear later" ]); + }); + + it(@"should work on infinite streams", ^{ + NSArray *streams = [threeStreams arrayByAddingObject:infiniteStream]; + RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id w, id x, id y, id z) { + return [NSString stringWithFormat:@"%@ %@ %@", w, x, y]; + }]; + verifyValues(stream, @[ @"Ada eats fish", @"Bob cooks bear", @"Dea jumps rock" ]); + }); + + it(@"should handle multiples of the same stream", ^{ + NSArray *streams = @[ streamOne, streamOne, streamTwo, streamThree, streamTwo, streamThree ]; + RACStream *stream = [streamClass zip:streams reduce:^ NSString * (id x1, id x2, id y1, id z1, id y2, id z2) { + return [NSString stringWithFormat:@"%@ %@ %@ %@ %@ %@", x1, x2, y1, z1, y2, z2]; + }]; + verifyValues(stream, @[ @"Ada Ada eats fish eats fish", @"Bob Bob cooks bear cooks bear", @"Dea Dea jumps rock jumps rock" ]); + }); + }); + + describe(@"+zip:", ^{ + it(@"should make a stream of tuples out of single value", ^{ + RACStream *stream = [streamClass zip:@[ streamOne ]]; + verifyValues(stream, oneStreamTuples); + }); + + it(@"should make a stream of tuples out of an array of streams", ^{ + RACStream *stream = [streamClass zip:threeStreams]; + verifyValues(stream, threeStreamTuples); + }); + + it(@"should make an empty stream if given an empty array", ^{ + RACStream *stream = [streamClass zip:@[]]; + verifyValues(stream, @[]); + }); + + it(@"should make a stream of tuples out of an enumerator of streams", ^{ + RACStream *stream = [streamClass zip:threeStreams.objectEnumerator]; + verifyValues(stream, threeStreamTuples); + }); + + it(@"should make an empty stream if given an empty enumerator", ^{ + RACStream *stream = [streamClass zip:@[].objectEnumerator]; + verifyValues(stream, @[]); + }); + }); + }); + + describe(@"+concat:", ^{ + __block NSArray *streams = nil; + __block NSArray *result = nil; + + before(^{ + RACStream *a = [streamClass return:@0]; + RACStream *b = [streamClass empty]; + RACStream *c = streamWithValues(@[ @1, @2, @3 ]); + RACStream *d = [streamClass return:@4]; + RACStream *e = [streamClass return:@5]; + RACStream *f = [streamClass empty]; + RACStream *g = [streamClass empty]; + RACStream *h = streamWithValues(@[ @6, @7 ]); + streams = @[ a, b, c, d, e, f, g, h ]; + result = @[ @0, @1, @2, @3, @4, @5, @6, @7 ]; + }); + + it(@"should concatenate an array of streams", ^{ + RACStream *stream = [streamClass concat:streams]; + verifyValues(stream, result); + }); + + it(@"should concatenate an enumerator of streams", ^{ + RACStream *stream = [streamClass concat:streams.objectEnumerator]; + verifyValues(stream, result); + }); + }); + + it(@"should scan", ^{ + RACStream *stream = streamWithValues(@[ @1, @2, @3, @4 ]); + RACStream *scanned = [stream scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) { + return @(running.integerValue + next.integerValue); + }]; + + verifyValues(scanned, @[ @1, @3, @6, @10 ]); + }); + + describe(@"taking with a predicate", ^{ + NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; + + __block RACStream *stream; + + before(^{ + stream = streamWithValues(values); + }); + + it(@"should take until a predicate is true", ^{ + RACStream *taken = [stream takeUntilBlock:^ BOOL (NSNumber *x) { + return x.integerValue >= 3; + }]; + + verifyValues(taken, @[ @0, @1, @2 ]); + }); + + it(@"should take while a predicate is true", ^{ + RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 1; + }]; + + verifyValues(taken, @[ @0, @1 ]); + }); + + it(@"should take a full stream", ^{ + RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 10; + }]; + + verifyValues(taken, values); + }); + + it(@"should return an empty stream", ^{ + RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue < 0; + }]; + + verifyValues(taken, @[]); + }); + + it(@"should terminate an infinite stream", ^{ + RACStream *infiniteCounter = [infiniteStream scanWithStart:@0 reduce:^(NSNumber *running, id _) { + return @(running.unsignedIntegerValue + 1); + }]; + + RACStream *taken = [infiniteCounter takeWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 5; + }]; + + verifyValues(taken, @[ @1, @2, @3, @4, @5 ]); + }); + }); + + describe(@"skipping with a predicate", ^{ + NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; + + __block RACStream *stream; + + before(^{ + stream = streamWithValues(values); + }); + + it(@"should skip until a predicate is true", ^{ + RACStream *taken = [stream skipUntilBlock:^ BOOL (NSNumber *x) { + return x.integerValue >= 3; + }]; + + verifyValues(taken, @[ @3, @0, @2, @4 ]); + }); + + it(@"should skip while a predicate is true", ^{ + RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 1; + }]; + + verifyValues(taken, @[ @2, @3, @0, @2, @4 ]); + }); + + it(@"should skip a full stream", ^{ + RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue <= 10; + }]; + + verifyValues(taken, @[]); + }); + + it(@"should finish skipping immediately", ^{ + RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { + return x.integerValue < 0; + }]; + + verifyValues(taken, values); + }); + }); + + describe(@"-combinePreviousWithStart:reduce:", ^{ + NSArray *values = @[ @1, @2, @3 ]; + __block RACStream *stream; + beforeEach(^{ + stream = streamWithValues(values); + }); + + it(@"should pass the previous next into the reduce block", ^{ + NSMutableArray *previouses = [NSMutableArray array]; + RACStream *mapped = [stream combinePreviousWithStart:nil reduce:^(id previous, id next) { + [previouses addObject:previous ?: RACTupleNil.tupleNil]; + return next; + }]; + + verifyValues(mapped, @[ @1, @2, @3 ]); + + NSArray *expected = @[ RACTupleNil.tupleNil, @1, @2 ]; + expect(previouses).to.equal(expected); + }); + + it(@"should send the combined value", ^{ + RACStream *mapped = [stream combinePreviousWithStart:@1 reduce:^(NSNumber *previous, NSNumber *next) { + return [NSString stringWithFormat:@"%lu - %lu", (unsigned long)previous.unsignedIntegerValue, (unsigned long)next.unsignedIntegerValue]; + }]; + + verifyValues(mapped, @[ @"1 - 1", @"1 - 2", @"2 - 3" ]); + }); + }); + + it(@"should reduce tuples", ^{ + RACStream *stream = streamWithValues(@[ + RACTuplePack(@"foo", @"bar"), + RACTuplePack(@"buzz", @"baz"), + RACTuplePack(@"", @"_") + ]); + + RACStream *reduced = [stream reduceEach:^(NSString *a, NSString *b) { + return [a stringByAppendingString:b]; + }]; + + verifyValues(reduced, @[ @"foobar", @"buzzbaz", @"_" ]); + }); +}); + +SharedExampleGroupsEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubclassObject.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubclassObject.h new file mode 100644 index 0000000..962b7eb --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubclassObject.h
@@ -0,0 +1,23 @@ +// +// RACSubclassObject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" + +@interface RACSubclassObject : RACTestObject + +// Set whenever -forwardInvocation: is invoked on the receiver. +@property (nonatomic, assign) SEL forwardedSelector; + +// Invokes the superclass implementation with `objectValue` concatenated to +// "SUBCLASS". +- (NSString *)combineObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue; + +// Asynchronously invokes the superclass implementation on the current scheduler. +- (void)setObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubclassObject.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubclassObject.m new file mode 100644 index 0000000..41e61f7 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubclassObject.m
@@ -0,0 +1,38 @@ +// +// RACSubclassObject.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 3/18/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSubclassObject.h" +#import "RACScheduler.h" + +@implementation RACSubclassObject + +- (void)forwardInvocation:(NSInvocation *)invocation { + self.forwardedSelector = invocation.selector; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + NSParameterAssert(selector != NULL); + + NSMethodSignature *signature = [super methodSignatureForSelector:selector]; + if (signature != nil) return signature; + + return [super methodSignatureForSelector:@selector(description)]; +} + +- (NSString *)combineObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { + NSString *appended = [[objectValue description] stringByAppendingString:@"SUBCLASS"]; + return [super combineObjectValue:appended andIntegerValue:integerValue]; +} + +- (void)setObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue { + [RACScheduler.currentScheduler schedule:^{ + [super setObjectValue:objectValue andSecondObjectValue:secondObjectValue]; + }]; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubjectSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubjectSpec.m new file mode 100644 index 0000000..6bc175f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubjectSpec.m
@@ -0,0 +1,335 @@ +// +// RACSubjectSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/24/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriberExamples.h" + +#import <libkern/OSAtomic.h> +#import "EXTScope.h" +#import "RACBehaviorSubject.h" +#import "RACDisposable.h" +#import "RACReplaySubject.h" +#import "RACScheduler.h" +#import "RACSignal+Operations.h" +#import "RACSubject.h" +#import "RACUnit.h" + +SpecBegin(RACSubject) + +describe(@"RACSubject", ^{ + __block RACSubject *subject; + __block NSMutableArray *values; + + __block BOOL success; + __block NSError *error; + + beforeEach(^{ + values = [NSMutableArray array]; + + subject = [RACSubject subject]; + success = YES; + error = nil; + + [subject subscribeNext:^(id value) { + [values addObject:value]; + } error:^(NSError *e) { + error = e; + success = NO; + } completed:^{ + success = YES; + }]; + }); + + itShouldBehaveLike(RACSubscriberExamples, ^{ + return @{ + RACSubscriberExampleSubscriber: subject, + RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], + RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], + RACSubscriberExampleSuccessBlock: [^{ return success; } copy] + }; + }); +}); + +describe(@"RACReplaySubject", ^{ + __block RACReplaySubject *subject = nil; + + describe(@"with a capacity of 1", ^{ + beforeEach(^{ + subject = [RACReplaySubject replaySubjectWithCapacity:1]; + }); + + it(@"should send the last value", ^{ + id firstValue = @"blah"; + id secondValue = @"more blah"; + + [subject sendNext:firstValue]; + [subject sendNext:secondValue]; + + __block id valueReceived = nil; + [subject subscribeNext:^(id x) { + valueReceived = x; + }]; + + expect(valueReceived).to.equal(secondValue); + }); + + it(@"should send the last value to new subscribers after completion", ^{ + id firstValue = @"blah"; + id secondValue = @"more blah"; + + __block id valueReceived = nil; + __block NSUInteger nextsReceived = 0; + + [subject sendNext:firstValue]; + [subject sendNext:secondValue]; + + expect(nextsReceived).to.equal(0); + expect(valueReceived).to.beNil(); + + [subject sendCompleted]; + + [subject subscribeNext:^(id x) { + valueReceived = x; + nextsReceived++; + }]; + + expect(nextsReceived).to.equal(1); + expect(valueReceived).to.equal(secondValue); + }); + + it(@"should not send any values to new subscribers if none were sent originally", ^{ + [subject sendCompleted]; + + __block BOOL nextInvoked = NO; + [subject subscribeNext:^(id x) { + nextInvoked = YES; + }]; + + expect(nextInvoked).to.beFalsy(); + }); + + it(@"should resend errors", ^{ + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; + [subject sendError:error]; + + __block BOOL errorSent = NO; + [subject subscribeError:^(NSError *sentError) { + expect(sentError).to.equal(error); + errorSent = YES; + }]; + + expect(errorSent).to.beTruthy(); + }); + + it(@"should resend nil errors", ^{ + [subject sendError:nil]; + + __block BOOL errorSent = NO; + [subject subscribeError:^(NSError *sentError) { + expect(sentError).to.beNil(); + errorSent = YES; + }]; + + expect(errorSent).to.beTruthy(); + }); + }); + + describe(@"with an unlimited capacity", ^{ + beforeEach(^{ + subject = [RACReplaySubject subject]; + }); + + itShouldBehaveLike(RACSubscriberExamples, ^{ + return @{ + RACSubscriberExampleSubscriber: subject, + RACSubscriberExampleValuesReceivedBlock: [^{ + NSMutableArray *values = [NSMutableArray array]; + + // This subscription should synchronously dump all values already + // received into 'values'. + [subject subscribeNext:^(id value) { + [values addObject:value]; + }]; + + return values; + } copy], + RACSubscriberExampleErrorReceivedBlock: [^{ + __block NSError *error = nil; + + [subject subscribeError:^(NSError *x) { + error = x; + }]; + + return error; + } copy], + RACSubscriberExampleSuccessBlock: [^{ + __block BOOL success = YES; + + [subject subscribeError:^(NSError *x) { + success = NO; + }]; + + return success; + } copy] + }; + }); + + it(@"should send both values to new subscribers after completion", ^{ + id firstValue = @"blah"; + id secondValue = @"more blah"; + + [subject sendNext:firstValue]; + [subject sendNext:secondValue]; + [subject sendCompleted]; + + __block BOOL completed = NO; + NSMutableArray *valuesReceived = [NSMutableArray array]; + [subject subscribeNext:^(id x) { + [valuesReceived addObject:x]; + } completed:^{ + completed = YES; + }]; + + expect(valuesReceived.count).to.equal(2); + NSArray *expected = [NSArray arrayWithObjects:firstValue, secondValue, nil]; + expect(valuesReceived).to.equal(expected); + expect(completed).to.beTruthy(); + }); + + it(@"should send values in the same order live as when replaying", ^{ + NSUInteger count = 49317; + + // Just leak it, ain't no thang. + __unsafe_unretained volatile id *values = (__unsafe_unretained id *)calloc(count, sizeof(*values)); + __block volatile int32_t nextIndex = 0; + + [subject subscribeNext:^(NSNumber *value) { + int32_t indexPlusOne = OSAtomicIncrement32(&nextIndex); + values[indexPlusOne - 1] = value; + }]; + + dispatch_queue_t queue = dispatch_queue_create("com.github.ReactiveCocoa.RACSubjectSpec", DISPATCH_QUEUE_CONCURRENT); + @onExit { + dispatch_release(queue); + }; + + dispatch_suspend(queue); + + for (NSUInteger i = 0; i < count; i++) { + dispatch_async(queue, ^{ + [subject sendNext:@(i)]; + }); + } + + dispatch_resume(queue); + dispatch_barrier_sync(queue, ^{ + [subject sendCompleted]; + }); + + OSMemoryBarrier(); + + NSArray *liveValues = [NSArray arrayWithObjects:(id *)values count:(NSUInteger)nextIndex]; + expect(liveValues.count).to.equal(count); + + NSArray *replayedValues = subject.toArray; + expect(replayedValues.count).to.equal(count); + + // It should return the same ordering for multiple invocations too. + expect(replayedValues).to.equal(subject.toArray); + + [replayedValues enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) { + expect(liveValues[index]).to.equal(value); + }]; + }); + + it(@"should have a current scheduler when replaying", ^{ + [subject sendNext:RACUnit.defaultUnit]; + + __block RACScheduler *currentScheduler; + [subject subscribeNext:^(id x) { + currentScheduler = RACScheduler.currentScheduler; + }]; + + expect(currentScheduler).notTo.beNil(); + + currentScheduler = nil; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + currentScheduler = RACScheduler.currentScheduler; + }]; + }); + + expect(currentScheduler).willNot.beNil(); + }); + + it(@"should stop replaying when the subscription is disposed", ^{ + NSMutableArray *values = [NSMutableArray array]; + + [subject sendNext:@0]; + [subject sendNext:@1]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + __block RACDisposable *disposable = [subject subscribeNext:^(id x) { + expect(disposable).notTo.beNil(); + + [values addObject:x]; + [disposable dispose]; + }]; + }); + + expect(values).will.equal(@[ @0 ]); + }); + + it(@"should finish replaying before completing", ^{ + [subject sendNext:@1]; + + __block id received; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + received = x; + }]; + + [subject sendCompleted]; + }); + + expect(received).will.equal(@1); + }); + + it(@"should finish replaying before erroring", ^{ + [subject sendNext:@1]; + + __block id received; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + received = x; + }]; + + [subject sendError:[NSError errorWithDomain:@"blah" code:-99 userInfo:nil]]; + }); + + expect(received).will.equal(@1); + }); + + it(@"should finish replaying before sending new values", ^{ + [subject sendNext:@1]; + + NSMutableArray *received = [NSMutableArray array]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [subject subscribeNext:^(id x) { + [received addObject:x]; + }]; + + [subject sendNext:@2]; + }); + + NSArray *expected = @[ @1, @2 ]; + expect(received).will.equal(expected); + }); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberExamples.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberExamples.h new file mode 100644 index 0000000..edc9e5a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberExamples.h
@@ -0,0 +1,23 @@ +// +// RACSubscriberExamples.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-27. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +// The name of the shared examples for implementors of <RACSubscriber>. +extern NSString * const RACSubscriberExamples; + +// id<RACSubscriber> +extern NSString * const RACSubscriberExampleSubscriber; + +// A block which returns an NSArray of the values received so far. +extern NSString * const RACSubscriberExampleValuesReceivedBlock; + +// A block which returns any NSError received so far. +extern NSString * const RACSubscriberExampleErrorReceivedBlock; + +// A block which returns a BOOL indicating whether the subscriber is successful +// so far. +extern NSString * const RACSubscriberExampleSuccessBlock;
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberExamples.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberExamples.m new file mode 100644 index 0000000..946a307 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberExamples.m
@@ -0,0 +1,185 @@ +// +// RACSubscriberExamples.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-27. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriberExamples.h" + +#import "NSObject+RACDeallocating.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSubject.h" +#import "RACSubscriber.h" + +NSString * const RACSubscriberExamples = @"RACSubscriberExamples"; +NSString * const RACSubscriberExampleSubscriber = @"RACSubscriberExampleSubscriber"; +NSString * const RACSubscriberExampleValuesReceivedBlock = @"RACSubscriberExampleValuesReceivedBlock"; +NSString * const RACSubscriberExampleErrorReceivedBlock = @"RACSubscriberExampleErrorReceivedBlock"; +NSString * const RACSubscriberExampleSuccessBlock = @"RACSubscriberExampleSuccessBlock"; + +SharedExampleGroupsBegin(RACSubscriberExamples) + +sharedExamplesFor(RACSubscriberExamples, ^(NSDictionary *data) { + __block NSArray * (^valuesReceived)(void); + __block NSError * (^errorReceived)(void); + __block BOOL (^success)(void); + __block id<RACSubscriber> subscriber; + + beforeEach(^{ + valuesReceived = data[RACSubscriberExampleValuesReceivedBlock]; + errorReceived = data[RACSubscriberExampleErrorReceivedBlock]; + success = data[RACSubscriberExampleSuccessBlock]; + subscriber = data[RACSubscriberExampleSubscriber]; + expect(subscriber).notTo.beNil(); + }); + + it(@"should accept a nil error", ^{ + [subscriber sendError:nil]; + + expect(success()).to.beFalsy(); + expect(errorReceived()).to.beNil(); + expect(valuesReceived()).to.equal(@[]); + }); + + describe(@"with values", ^{ + __block NSSet *values; + + beforeEach(^{ + NSMutableSet *mutableValues = [NSMutableSet set]; + for (NSUInteger i = 0; i < 20; i++) { + [mutableValues addObject:@(i)]; + } + + values = [mutableValues copy]; + }); + + it(@"should send nexts serially, even when delivered from multiple threads", ^{ + NSArray *allValues = values.allObjects; + dispatch_apply(allValues.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), [^(size_t index) { + [subscriber sendNext:allValues[index]]; + } copy]); + + expect(success()).to.beTruthy(); + expect(errorReceived()).to.beNil(); + + NSSet *valuesReceivedSet = [NSSet setWithArray:valuesReceived()]; + expect(valuesReceivedSet).to.equal(values); + }); + }); + + describe(@"multiple subscriptions", ^{ + __block RACSubject *first; + __block RACSubject *second; + + beforeEach(^{ + first = [RACSubject subject]; + [first subscribe:subscriber]; + + second = [RACSubject subject]; + [second subscribe:subscriber]; + }); + + it(@"should send values from all subscriptions", ^{ + [first sendNext:@"foo"]; + [second sendNext:@"bar"]; + [first sendNext:@"buzz"]; + [second sendNext:@"baz"]; + + expect(success()).to.beTruthy(); + expect(errorReceived()).to.beNil(); + + NSArray *expected = @[ @"foo", @"bar", @"buzz", @"baz" ]; + expect(valuesReceived()).to.equal(expected); + }); + + it(@"should terminate after the first error from any subscription", ^{ + NSError *error = [NSError errorWithDomain:@"" code:-1 userInfo:nil]; + + [first sendNext:@"foo"]; + [second sendError:error]; + [first sendNext:@"buzz"]; + + expect(success()).to.beFalsy(); + expect(errorReceived()).to.equal(error); + + NSArray *expected = @[ @"foo" ]; + expect(valuesReceived()).to.equal(expected); + }); + + it(@"should terminate after the first completed from any subscription", ^{ + [first sendNext:@"foo"]; + [second sendNext:@"bar"]; + [first sendCompleted]; + [second sendNext:@"baz"]; + + expect(success()).to.beTruthy(); + expect(errorReceived()).to.beNil(); + + NSArray *expected = @[ @"foo", @"bar" ]; + expect(valuesReceived()).to.equal(expected); + }); + + it(@"should dispose of all current subscriptions upon termination", ^{ + __block BOOL firstDisposed = NO; + RACSignal *firstDisposableSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + firstDisposed = YES; + }]; + }]; + + __block BOOL secondDisposed = NO; + RACSignal *secondDisposableSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + secondDisposed = YES; + }]; + }]; + + [firstDisposableSignal subscribe:subscriber]; + [secondDisposableSignal subscribe:subscriber]; + + expect(firstDisposed).to.beFalsy(); + expect(secondDisposed).to.beFalsy(); + + [first sendCompleted]; + + expect(firstDisposed).to.beTruthy(); + expect(secondDisposed).to.beTruthy(); + }); + + it(@"should dispose of future subscriptions upon termination", ^{ + __block BOOL disposed = NO; + RACSignal *disposableSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { + return [RACDisposable disposableWithBlock:^{ + disposed = YES; + }]; + }]; + + [first sendCompleted]; + expect(disposed).to.beFalsy(); + + [disposableSignal subscribe:subscriber]; + expect(disposed).to.beTruthy(); + }); + }); + + describe(@"memory management", ^{ + it(@"should not retain disposed disposables", ^{ + __block BOOL disposableDeallocd = NO; + @autoreleasepool { + RACCompoundDisposable *disposable __attribute__((objc_precise_lifetime)) = [RACCompoundDisposable disposableWithBlock:^{}]; + [disposable.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + disposableDeallocd = YES; + }]]; + + [subscriber didSubscribeWithDisposable:disposable]; + [disposable dispose]; + } + expect(disposableDeallocd).to.beTruthy(); + }); + }); +}); + +SharedExampleGroupsEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberSpec.m new file mode 100644 index 0000000..8a1725d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriberSpec.m
@@ -0,0 +1,130 @@ +// +// RACSubscriberSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-27. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriberExamples.h" + +#import "RACSubscriber.h" +#import "RACSubscriber+Private.h" +#import <libkern/OSAtomic.h> + +SpecBegin(RACSubscriber) + +__block RACSubscriber *subscriber; +__block NSMutableArray *values; + +__block volatile BOOL finished; +__block volatile int32_t nextsAfterFinished; + +__block BOOL success; +__block NSError *error; + +beforeEach(^{ + values = [NSMutableArray array]; + + finished = NO; + nextsAfterFinished = 0; + + success = YES; + error = nil; + + subscriber = [RACSubscriber subscriberWithNext:^(id value) { + if (finished) OSAtomicIncrement32Barrier(&nextsAfterFinished); + + [values addObject:value]; + } error:^(NSError *e) { + error = e; + success = NO; + } completed:^{ + success = YES; + }]; +}); + +itShouldBehaveLike(RACSubscriberExamples, ^{ + return @{ + RACSubscriberExampleSubscriber: subscriber, + RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], + RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], + RACSubscriberExampleSuccessBlock: [^{ return success; } copy] + }; +}); + +describe(@"finishing", ^{ + __block void (^sendValues)(void); + __block BOOL expectedSuccess; + + __block dispatch_group_t dispatchGroup; + __block dispatch_queue_t concurrentQueue; + + beforeEach(^{ + dispatchGroup = dispatch_group_create(); + expect(dispatchGroup).notTo.beNil(); + + concurrentQueue = dispatch_queue_create("com.github.ReactiveCocoa.RACSubscriberSpec", DISPATCH_QUEUE_CONCURRENT); + expect(concurrentQueue).notTo.beNil(); + + dispatch_suspend(concurrentQueue); + + sendValues = [^{ + for (NSUInteger i = 0; i < 15; i++) { + dispatch_group_async(dispatchGroup, concurrentQueue, ^{ + [subscriber sendNext:@(i)]; + }); + } + } copy]; + + sendValues(); + }); + + afterEach(^{ + sendValues(); + dispatch_resume(concurrentQueue); + + // Time out after one second. + dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)); + expect(dispatch_group_wait(dispatchGroup, time)).to.equal(0); + + dispatch_release(dispatchGroup); + dispatchGroup = NULL; + + dispatch_release(concurrentQueue); + concurrentQueue = NULL; + + expect(nextsAfterFinished).to.equal(0); + + if (expectedSuccess) { + expect(success).to.beTruthy(); + expect(error).to.beNil(); + } else { + expect(success).to.beFalsy(); + } + }); + + it(@"should never invoke next after sending completed", ^{ + expectedSuccess = YES; + + dispatch_group_async(dispatchGroup, concurrentQueue, ^{ + [subscriber sendCompleted]; + + finished = YES; + OSMemoryBarrier(); + }); + }); + + it(@"should never invoke next after sending error", ^{ + expectedSuccess = NO; + + dispatch_group_async(dispatchGroup, concurrentQueue, ^{ + [subscriber sendError:nil]; + + finished = YES; + OSMemoryBarrier(); + }); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriptingAssignmentTrampolineSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriptingAssignmentTrampolineSpec.m new file mode 100644 index 0000000..9165f11 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACSubscriptingAssignmentTrampolineSpec.m
@@ -0,0 +1,33 @@ +// +// RACSubscriptingAssignmentTrampolineSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 9/24/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACSubscriptingAssignmentTrampoline.h" +#import "RACPropertySignalExamples.h" +#import "RACTestObject.h" +#import "RACSubject.h" + +SpecBegin(RACSubscriptingAssignmentTrampoline) + +id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { + [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:testObject nilValue:nilValue][keyPath] = signal; +}; + +itShouldBehaveLike(RACPropertySignalExamples, ^{ + return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; +}); + +it(@"should expand the RAC macro properly", ^{ + RACSubject *subject = [RACSubject subject]; + RACTestObject *testObject = [[RACTestObject alloc] init]; + RAC(testObject, objectValue) = subject; + + [subject sendNext:@1]; + expect(testObject.objectValue).to.equal(@1); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTargetQueueSchedulerSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTargetQueueSchedulerSpec.m new file mode 100644 index 0000000..b193844 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTargetQueueSchedulerSpec.m
@@ -0,0 +1,50 @@ +// +// RACTargetQueueSchedulerSpec.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/7/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTargetQueueScheduler.h" +#import <libkern/OSAtomic.h> + +SpecBegin(RACTargetQueueScheduler) + +it(@"should have a valid current scheduler", ^{ + dispatch_queue_t queue = dispatch_queue_create("test-queue", DISPATCH_QUEUE_SERIAL); + RACScheduler *scheduler = [[RACTargetQueueScheduler alloc] initWithName:@"test-scheduler" targetQueue:queue]; + __block RACScheduler *currentScheduler; + [scheduler schedule:^{ + currentScheduler = RACScheduler.currentScheduler; + }]; + + expect(currentScheduler).will.equal(scheduler); + + dispatch_release(queue); +}); + +it(@"should schedule blocks FIFO even when given a concurrent queue", ^{ + dispatch_queue_t queue = dispatch_queue_create("test-queue", DISPATCH_QUEUE_CONCURRENT); + RACScheduler *scheduler = [[RACTargetQueueScheduler alloc] initWithName:@"test-scheduler" targetQueue:queue]; + __block volatile int32_t startedCount = 0; + __block volatile uint32_t waitInFirst = 1; + [scheduler schedule:^{ + OSAtomicIncrement32Barrier(&startedCount); + while (waitInFirst == 1) ; + }]; + + [scheduler schedule:^{ + OSAtomicIncrement32Barrier(&startedCount); + }]; + + expect(startedCount).will.equal(1); + + OSAtomicAnd32Barrier(0, &waitInFirst); + + expect(startedCount).will.equal(2); + + dispatch_release(queue); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestExampleScheduler.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestExampleScheduler.h new file mode 100644 index 0000000..0470d98 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestExampleScheduler.h
@@ -0,0 +1,15 @@ +// +// RACTestExampleScheduler.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/7/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <ReactiveCocoa/ReactiveCocoa.h> + +@interface RACTestExampleScheduler : RACQueueScheduler + +- (id)initWithQueue:(dispatch_queue_t)queue; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestExampleScheduler.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestExampleScheduler.m new file mode 100644 index 0000000..859055c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestExampleScheduler.m
@@ -0,0 +1,39 @@ +// +// RACTestExampleScheduler.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 6/7/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestExampleScheduler.h" +#import "RACQueueScheduler+Subclass.h" + +@implementation RACTestExampleScheduler + +#pragma mark Lifecycle + +- (id)initWithQueue:(dispatch_queue_t)queue { + return [super initWithName:nil queue:queue]; +} + +#pragma mark RACScheduler + +- (RACDisposable *)schedule:(void (^)(void))block { + dispatch_async(self.queue, ^{ + [self performAsCurrentScheduler:block]; + }); + + return nil; +} + +- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { + dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)([date timeIntervalSinceNow] * NSEC_PER_SEC)); + dispatch_after(when, self.queue, ^{ + [self performAsCurrentScheduler:block]; + }); + + return nil; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestObject.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestObject.h new file mode 100644 index 0000000..a963b0a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestObject.h
@@ -0,0 +1,86 @@ +// +// RACTestObject.h +// ReactiveCocoa +// +// Created by Josh Abernathy on 9/18/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> + +typedef struct { + long long integerField; + double doubleField; +} RACTestStruct; + +@protocol RACTestProtocol <NSObject> + +@optional +- (void)optionalProtocolMethodWithObjectValue:(id)objectValue; + +@end + +@interface RACTestObject : NSObject <RACTestProtocol> + +@property (nonatomic, strong) id objectValue; +@property (nonatomic, strong) id secondObjectValue; +@property (nonatomic, strong) RACTestObject *strongTestObjectValue; +@property (nonatomic, weak) RACTestObject *weakTestObjectValue; +@property (nonatomic, weak) id<RACTestProtocol> weakObjectWithProtocol; +@property (nonatomic, assign) NSInteger integerValue; +// Holds a copy of the string. +@property (nonatomic, assign) char *charPointerValue; +// Holds a copy of the string. +@property (nonatomic, assign) const char *constCharPointerValue; +@property (nonatomic, assign) CGRect rectValue; +@property (nonatomic, assign) CGSize sizeValue; +@property (nonatomic, assign) CGPoint pointValue; +@property (nonatomic, assign) NSRange rangeValue; +@property (nonatomic, assign) RACTestStruct structValue; +@property (nonatomic, assign) _Bool c99BoolValue; +@property (nonatomic, copy) NSString *stringValue; +@property (nonatomic, copy) NSArray *arrayValue; +@property (nonatomic, copy) NSSet *setValue; +@property (nonatomic, copy) NSOrderedSet *orderedSetValue; +@property (nonatomic, strong) id slowObjectValue; + +// Returns a new object each time, with the integerValue set to 42. +@property (nonatomic, copy, readonly) RACTestObject *dynamicObjectProperty; + +// Returns a new object each time, with the integerValue set to 42. +- (RACTestObject *)dynamicObjectMethod; + +// Whether to allow -setNilValueForKey: to be invoked without throwing an +// exception. +@property (nonatomic, assign) BOOL catchSetNilValueForKey; + +// Has -setObjectValue:andIntegerValue: been called? +@property (nonatomic, assign) BOOL hasInvokedSetObjectValueAndIntegerValue; + +// Has -setObjectValue:andSecondObjectValue: been called? +@property (nonatomic, assign) BOOL hasInvokedSetObjectValueAndSecondObjectValue; + +- (void)setObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue; +- (void)setObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue; + +// Returns a string of the form "objectValue: integerValue". +- (NSString *)combineObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue; +- (NSString *)combineObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue; + +- (void)lifeIsGood:(id)sender; + ++ (void)lifeIsGood:(id)sender; + +- (NSRange)returnRangeValueWithObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue; + +// Writes 5 to the int pointed to by intPointer. +- (void)write5ToIntPointer:(int *)intPointer; + +- (NSInteger)doubleInteger:(NSInteger)integer; +- (char *)doubleString:(char *)string; +- (const char *)doubleConstString:(const char *)string; +- (RACTestStruct)doubleStruct:(RACTestStruct)testStruct; + +- (dispatch_block_t)wrapBlock:(dispatch_block_t)block; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestObject.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestObject.m new file mode 100644 index 0000000..f590703 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestObject.m
@@ -0,0 +1,121 @@ +// +// RACTestObject.m +// ReactiveCocoa +// +// Created by Josh Abernathy on 9/18/12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACTestObject.h" + +@implementation RACTestObject + +- (void)dealloc { + free(_charPointerValue); + free((void *)_constCharPointerValue); +} + +- (void)setNilValueForKey:(NSString *)key { + if (!self.catchSetNilValueForKey) [super setNilValueForKey:key]; +} + +- (void)setCharPointerValue:(char *)charPointerValue { + if (charPointerValue == _charPointerValue) return; + free(_charPointerValue); + _charPointerValue = strdup(charPointerValue); +} + +- (void)setConstCharPointerValue:(const char *)constCharPointerValue { + if (constCharPointerValue == _constCharPointerValue) return; + free((void *)_constCharPointerValue); + _constCharPointerValue = strdup(constCharPointerValue); +} + +- (void)setObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { + self.hasInvokedSetObjectValueAndIntegerValue = YES; + self.objectValue = objectValue; + self.integerValue = integerValue; +} + +- (void)setObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue { + self.hasInvokedSetObjectValueAndSecondObjectValue = YES; + self.objectValue = objectValue; + self.secondObjectValue = secondObjectValue; +} + +- (void)setSlowObjectValue:(id)value { + [NSThread sleepForTimeInterval:0.02]; + _slowObjectValue = value; +} + +- (NSString *)combineObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { + return [NSString stringWithFormat:@"%@: %ld", objectValue, (long)integerValue]; +} + +- (NSString *)combineObjectValue:(id)objectValue andSecondObjectValue:(id)secondObjectValue { + return [NSString stringWithFormat:@"%@: %@", objectValue, secondObjectValue]; +} + +- (void)lifeIsGood:(id)sender { + +} + ++ (void)lifeIsGood:(id)sender { + +} + +- (NSRange)returnRangeValueWithObjectValue:(id)objectValue andIntegerValue:(NSInteger)integerValue { + return NSMakeRange((NSUInteger)[objectValue integerValue], (NSUInteger)integerValue); +} + +- (RACTestObject *)dynamicObjectProperty { + return [self dynamicObjectMethod]; +} + +- (RACTestObject *)dynamicObjectMethod { + RACTestObject *testObject = [[RACTestObject alloc] init]; + testObject.integerValue = 42; + return testObject; +} + +- (void)write5ToIntPointer:(int *)intPointer { + NSCParameterAssert(intPointer != NULL); + *intPointer = 5; +} + +- (NSInteger)doubleInteger:(NSInteger)integer { + return integer * 2; +} + +- (char *)doubleString:(char *)string { + size_t doubledSize = strlen(string) * 2 + 1; + char *doubledString = malloc(sizeof(char) * doubledSize); + + doubledString[0] = '\0'; + strlcat(doubledString, string, doubledSize); + strlcat(doubledString, string, doubledSize); + + dispatch_async(dispatch_get_main_queue(), ^{ + free(doubledString); + }); + + return doubledString; +} + +- (const char *)doubleConstString:(const char *)string { + return [self doubleString:(char *)string]; +} + +- (RACTestStruct)doubleStruct:(RACTestStruct)testStruct { + testStruct.integerField *= 2; + testStruct.doubleField *= 2; + return testStruct; +} + +- (dispatch_block_t)wrapBlock:(dispatch_block_t)block { + return ^{ + block(); + }; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestSchedulerSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestSchedulerSpec.m new file mode 100644 index 0000000..baa8994 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestSchedulerSpec.m
@@ -0,0 +1,175 @@ +// +// RACTestSchedulerSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestScheduler.h" + +SpecBegin(RACTestScheduler) + +__block RACTestScheduler *scheduler; + +beforeEach(^{ + scheduler = [[RACTestScheduler alloc] init]; + expect(scheduler).notTo.beNil(); +}); + +it(@"should do nothing when stepping while empty", ^{ + [scheduler step]; + [scheduler step:5]; + [scheduler stepAll]; +}); + +it(@"should execute the earliest enqueued block when stepping", ^{ + __block BOOL firstExecuted = NO; + [scheduler schedule:^{ + firstExecuted = YES; + }]; + + __block BOOL secondExecuted = NO; + [scheduler schedule:^{ + secondExecuted = YES; + }]; + + expect(firstExecuted).to.beFalsy(); + expect(secondExecuted).to.beFalsy(); + + [scheduler step]; + expect(firstExecuted).to.beTruthy(); + expect(secondExecuted).to.beFalsy(); + + [scheduler step]; + expect(secondExecuted).to.beTruthy(); +}); + +it(@"should step multiple times", ^{ + __block BOOL firstExecuted = NO; + [scheduler schedule:^{ + firstExecuted = YES; + }]; + + __block BOOL secondExecuted = NO; + [scheduler schedule:^{ + secondExecuted = YES; + }]; + + __block BOOL thirdExecuted = NO; + [scheduler schedule:^{ + thirdExecuted = YES; + }]; + + expect(firstExecuted).to.beFalsy(); + expect(secondExecuted).to.beFalsy(); + expect(thirdExecuted).to.beFalsy(); + + [scheduler step:2]; + expect(firstExecuted).to.beTruthy(); + expect(secondExecuted).to.beTruthy(); + expect(thirdExecuted).to.beFalsy(); + + [scheduler step:1]; + expect(thirdExecuted).to.beTruthy(); +}); + +it(@"should step through all scheduled blocks", ^{ + __block NSUInteger executions = 0; + for (NSUInteger i = 0; i < 10; i++) { + [scheduler schedule:^{ + executions++; + }]; + } + + expect(executions).to.equal(0); + + [scheduler stepAll]; + expect(executions).to.equal(10); +}); + +it(@"should execute blocks in date order when stepping", ^{ + __block BOOL laterExecuted = NO; + [scheduler after:[NSDate distantFuture] schedule:^{ + laterExecuted = YES; + }]; + + __block BOOL earlierExecuted = NO; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:20] schedule:^{ + earlierExecuted = YES; + }]; + + expect(earlierExecuted).to.beFalsy(); + expect(laterExecuted).to.beFalsy(); + + [scheduler step]; + expect(earlierExecuted).to.beTruthy(); + expect(laterExecuted).to.beFalsy(); + + [scheduler step]; + expect(laterExecuted).to.beTruthy(); +}); + +it(@"should execute delayed blocks in date order when stepping", ^{ + __block BOOL laterExecuted = NO; + [scheduler afterDelay:100 schedule:^{ + laterExecuted = YES; + }]; + + __block BOOL earlierExecuted = NO; + [scheduler afterDelay:50 schedule:^{ + earlierExecuted = YES; + }]; + + expect(earlierExecuted).to.beFalsy(); + expect(laterExecuted).to.beFalsy(); + + [scheduler step]; + expect(earlierExecuted).to.beTruthy(); + expect(laterExecuted).to.beFalsy(); + + [scheduler step]; + expect(laterExecuted).to.beTruthy(); +}); + +it(@"should execute a repeating blocks in date order", ^{ + __block NSUInteger firstExecutions = 0; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:20] repeatingEvery:5 withLeeway:0 schedule:^{ + firstExecutions++; + }]; + + __block NSUInteger secondExecutions = 0; + [scheduler after:[NSDate dateWithTimeIntervalSinceNow:22] repeatingEvery:10 withLeeway:0 schedule:^{ + secondExecutions++; + }]; + + expect(firstExecutions).to.equal(0); + expect(secondExecutions).to.equal(0); + + // 20 ticks + [scheduler step]; + expect(firstExecutions).to.equal(1); + expect(secondExecutions).to.equal(0); + + // 22 ticks + [scheduler step]; + expect(firstExecutions).to.equal(1); + expect(secondExecutions).to.equal(1); + + // 25 ticks + [scheduler step]; + expect(firstExecutions).to.equal(2); + expect(secondExecutions).to.equal(1); + + // 30 ticks + [scheduler step]; + expect(firstExecutions).to.equal(3); + expect(secondExecutions).to.equal(1); + + // 32 ticks + [scheduler step]; + expect(firstExecutions).to.equal(3); + expect(secondExecutions).to.equal(2); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestUIButton.h b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestUIButton.h new file mode 100644 index 0000000..03e9716 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestUIButton.h
@@ -0,0 +1,16 @@ +// +// RACTestUIButton.h +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +// Enables use of -sendActionsForControlEvents: in unit tests. +@interface RACTestUIButton : UIButton + ++ (instancetype)button; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestUIButton.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestUIButton.m new file mode 100644 index 0000000..48b2674 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTestUIButton.m
@@ -0,0 +1,27 @@ +// +// RACTestUIButton.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestUIButton.h" + +@implementation RACTestUIButton + ++ (instancetype)button { + RACTestUIButton *button = [self buttonWithType:UIButtonTypeCustom]; + return button; +} + +// Required for unit testing – controls don't work normally +// outside of normal apps. +-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [target performSelector:action withObject:self]; +#pragma clang diagnostic pop +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/RACTupleSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTupleSpec.m new file mode 100644 index 0000000..e85d76d --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/RACTupleSpec.m
@@ -0,0 +1,120 @@ +// +// RACTupleSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-12-12. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import "RACTuple.h" +#import "RACUnit.h" + +SpecBegin(RACTuple) + +describe(@"RACTupleUnpack", ^{ + it(@"should unpack a single value", ^{ + RACTupleUnpack(RACUnit *value) = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; + expect(value).to.equal(RACUnit.defaultUnit); + }); + + it(@"should translate RACTupleNil", ^{ + RACTupleUnpack(id value) = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; + expect(value).to.beNil(); + }); + + it(@"should unpack multiple values", ^{ + RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", @5, nil]; + + expect(str).to.equal(@"foobar"); + expect(num).to.equal(@5); + }); + + it(@"should fill in missing values with nil", ^{ + RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", nil]; + + expect(str).to.equal(@"foobar"); + expect(num).to.beNil(); + }); + + it(@"should skip any values not assigned to", ^{ + RACTupleUnpack(NSString *str, NSNumber *num) = [RACTuple tupleWithObjects:@"foobar", @5, RACUnit.defaultUnit, nil]; + + expect(str).to.equal(@"foobar"); + expect(num).to.equal(@5); + }); + + it(@"should keep an unpacked value alive when captured in a block", ^{ + __weak id weakPtr = nil; + id (^block)(void) = nil; + + @autoreleasepool { + RACTupleUnpack(NSString *str) = [RACTuple tupleWithObjects:[[NSMutableString alloc] init], nil]; + + weakPtr = str; + expect(weakPtr).notTo.beNil(); + + block = [^{ + return str; + } copy]; + } + + expect(weakPtr).notTo.beNil(); + expect(block()).to.equal(weakPtr); + }); +}); + +describe(@"RACTuplePack", ^{ + it(@"should pack a single value", ^{ + RACTuple *tuple = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; + expect(RACTuplePack(RACUnit.defaultUnit)).to.equal(tuple); + }); + + it(@"should translate nil", ^{ + RACTuple *tuple = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; + expect(RACTuplePack(nil)).to.equal(tuple); + }); + + it(@"should pack multiple values", ^{ + NSString *string = @"foobar"; + NSNumber *number = @5; + RACTuple *tuple = [RACTuple tupleWithObjects:string, number, nil]; + expect(RACTuplePack(string, number)).to.equal(tuple); + }); +}); + +describe(@"-tupleByAddingObject:", ^{ + __block RACTuple *tuple; + + beforeEach(^{ + tuple = RACTuplePack(@"foo", nil, @"bar"); + }); + + it(@"should add a non-nil object", ^{ + RACTuple *newTuple = [tuple tupleByAddingObject:@"buzz"]; + expect(newTuple.count).to.equal(4); + expect(newTuple[0]).to.equal(@"foo"); + expect(newTuple[1]).to.beNil(); + expect(newTuple[2]).to.equal(@"bar"); + expect(newTuple[3]).to.equal(@"buzz"); + }); + + it(@"should add nil", ^{ + RACTuple *newTuple = [tuple tupleByAddingObject:nil]; + expect(newTuple.count).to.equal(4); + expect(newTuple[0]).to.equal(@"foo"); + expect(newTuple[1]).to.beNil(); + expect(newTuple[2]).to.equal(@"bar"); + expect(newTuple[3]).to.beNil(); + }); + + it(@"should add NSNull", ^{ + RACTuple *newTuple = [tuple tupleByAddingObject:NSNull.null]; + expect(newTuple.count).to.equal(4); + expect(newTuple[0]).to.equal(@"foo"); + expect(newTuple[1]).to.beNil(); + expect(newTuple[2]).to.equal(@"bar"); + expect(newTuple[3]).to.equal(NSNull.null); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/ReactiveCocoaTests-Info.plist b/ReactiveCocoaFramework/ReactiveCocoaTests/ReactiveCocoaTests-Info.plist new file mode 100644 index 0000000..d783a87 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/ReactiveCocoaTests-Info.plist
@@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>com.github.${PRODUCT_NAME:rfc1034identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist>
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch b/ReactiveCocoaFramework/ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch new file mode 100644 index 0000000..970a36e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/ReactiveCocoaTests-Prefix.pch
@@ -0,0 +1,14 @@ +// +// ReactiveCocoaTests-Prefix.pch +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2012-11-29. +// Copyright (c) 2012 GitHub, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import <CoreGraphics/CGGeometry.h> + +#define EXP_SHORTHAND +#import "Specta.h" +#import "Expecta.h"
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIActionSheetRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIActionSheetRACSupportSpec.m new file mode 100644 index 0000000..a695ea5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIActionSheetRACSupportSpec.m
@@ -0,0 +1,36 @@ +// +// UIActionSheetRACSupportSpec.m +// ReactiveCocoa +// +// Created by Dave Lee on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "UIActionSheet+RACSignalSupport.h" + +SpecBegin(UIActionSheetRACSupportSpec) + +describe(@"-rac_buttonClickedSignal", ^{ + __block UIActionSheet *actionSheet; + + beforeEach(^{ + actionSheet = [[UIActionSheet alloc] init]; + [actionSheet addButtonWithTitle:@"Button 0"]; + [actionSheet addButtonWithTitle:@"Button 1"]; + expect(actionSheet).notTo.beNil(); + }); + + it(@"should send the index of the clicked button", ^{ + __block NSNumber *index = nil; + [actionSheet.rac_buttonClickedSignal subscribeNext:^(NSNumber *i) { + index = i; + }]; + + [actionSheet.delegate actionSheet:actionSheet clickedButtonAtIndex:1]; + expect(index).to.equal(@1); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIAlertViewRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIAlertViewRACSupportSpec.m new file mode 100644 index 0000000..f37294e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIAlertViewRACSupportSpec.m
@@ -0,0 +1,34 @@ +// +// UIAlertViewRACSupportSpec.m +// ReactiveCocoa +// +// Created by Henrik Hodne on 6/16/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <objc/message.h> +#import "RACSignal.h" +#import "UIAlertView+RACSignalSupport.h" + +SpecBegin(UIAlertViewRACSupport) + +describe(@"UIAlertView", ^{ + __block UIAlertView *alertView; + + beforeEach(^{ + alertView = [[UIAlertView alloc] initWithFrame:CGRectZero]; + expect(alertView).notTo.beNil(); + }); + + it(@"sends the index of the clicked button to the buttonClickedSignal when a button is clicked", ^{ + __block NSInteger index = -1; + [alertView.rac_buttonClickedSignal subscribeNext:^(NSNumber *sentIndex) { + index = sentIndex.integerValue; + }]; + + [alertView.delegate alertView:alertView clickedButtonAtIndex:2]; + expect(index).to.equal(2); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIBarButtonItemRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIBarButtonItemRACSupportSpec.m new file mode 100644 index 0000000..089630b --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIBarButtonItemRACSupportSpec.m
@@ -0,0 +1,40 @@ +// +// UIBarButtonItemRACSupportSpec.m +// ReactiveCocoa +// +// Created by Kyle LeNeau on 4/13/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACControlCommandExamples.h" + +#import "UIBarButtonItem+RACCommandSupport.h" +#import "RACCommand.h" +#import "RACDisposable.h" + +SpecBegin(UIBarButtonItemRACSupport) + +describe(@"UIBarButtonItem", ^{ + __block UIBarButtonItem *button; + + beforeEach(^{ + button = [[UIBarButtonItem alloc] init]; + expect(button).notTo.beNil(); + }); + + itShouldBehaveLike(RACControlCommandExamples, ^{ + return @{ + RACControlCommandExampleControl: button, + RACControlCommandExampleActivateBlock: ^(UIBarButtonItem *button) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[button.target methodSignatureForSelector:button.action]]; + invocation.selector = button.action; + + id target = button.target; + [invocation setArgument:&target atIndex:2]; + [invocation invokeWithTarget:target]; + } + }; + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIButtonRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIButtonRACSupportSpec.m new file mode 100644 index 0000000..0fefa92 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIButtonRACSupportSpec.m
@@ -0,0 +1,39 @@ +// +// UIButtonRACSupportSpec.m +// ReactiveCocoa +// +// Created by Ash Furrow on 2013-06-06. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACControlCommandExamples.h" +#import "RACTestUIButton.h" + +#import "UIButton+RACCommandSupport.h" +#import "RACCommand.h" +#import "RACDisposable.h" + +SpecBegin(UIButtonRACSupport) + +describe(@"UIButton", ^{ + __block UIButton *button; + + beforeEach(^{ + button = [RACTestUIButton button]; + expect(button).notTo.beNil(); + }); + + itShouldBehaveLike(RACControlCommandExamples, ^{ + return @{ + RACControlCommandExampleControl: button, + RACControlCommandExampleActivateBlock: ^(UIButton *button) { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [button sendActionsForControlEvents:UIControlEventTouchUpInside]; + #pragma clang diagnostic pop + } + }; + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIControlRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIControlRACSupportSpec.m new file mode 100644 index 0000000..4aaf68c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIControlRACSupportSpec.m
@@ -0,0 +1,105 @@ +// +// UIControlRACSupportSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-15. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestUIButton.h" + +#import "EXTKeyPathCoding.h" +#import "NSObject+RACDeallocating.h" +#import "RACChannelExamples.h" +#import "RACCompoundDisposable.h" +#import "RACDisposable.h" +#import "RACSignal.h" +#import "UIControl+RACSignalSupport.h" +#import "UISlider+RACSignalSupport.h" + +SpecBegin(UIControlRACSupport) + +void (^setViewValueBlock)(UISlider *, NSNumber *) = ^(UISlider *view, NSNumber *value) { + view.value = value.floatValue; + + // UIControlEvents don't trigger from programmatic modification. Do it + // manually. + for (id target in view.allTargets) { + // Control events are a mask, but UIControlEventAllEvents doesn't seem to + // match anything, 0 does. + for (NSString *selectorString in [view actionsForTarget:target forControlEvent:0]) { + SEL selector = NSSelectorFromString(selectorString); + + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]]; + invocation.selector = selector; + UIEvent *event = nil; + [invocation setArgument:&event atIndex:2]; + + [invocation invokeWithTarget:target]; + } + } +}; + +itShouldBehaveLike(RACViewChannelExamples, ^{ + return @{ + RACViewChannelExampleCreateViewBlock: ^{ + return [[UISlider alloc] init]; + }, + RACViewChannelExampleCreateTerminalBlock: ^(UISlider *view) { + return [view rac_newValueChannelWithNilValue:@0.0]; + }, + RACViewChannelExampleKeyPath: @keypath(UISlider.new, value), + RACViewChannelExampleSetViewValueBlock: setViewValueBlock + }; +}); + +it(@"should send on the returned signal when matching actions are sent", ^{ + UIControl *control = [RACTestUIButton button]; + expect(control).notTo.beNil(); + + __block NSUInteger receivedCount = 0; + [[control + rac_signalForControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside] + subscribeNext:^(UIControl *sender) { + expect(sender).to.beIdenticalTo(control); + receivedCount++; + }]; + + expect(receivedCount).to.equal(0); + + [control sendActionsForControlEvents:UIControlEventTouchUpInside]; + expect(receivedCount).to.equal(1); + + // Should do nothing. + [control sendActionsForControlEvents:UIControlEventTouchDown]; + expect(receivedCount).to.equal(1); + + [control sendActionsForControlEvents:UIControlEventTouchUpOutside]; + expect(receivedCount).to.equal(2); +}); + +it(@"should send completed when the control is deallocated", ^{ + __block BOOL completed = NO; + __block BOOL deallocated = NO; + + @autoreleasepool { + UIControl *control __attribute__((objc_precise_lifetime)) = [RACTestUIButton button]; + [control.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ + deallocated = YES; + }]]; + + [[control + rac_signalForControlEvents:UIControlEventTouchDown] + subscribeCompleted:^{ + completed = YES; + }]; + + expect(deallocated).to.beFalsy(); + expect(completed).to.beFalsy(); + } + + expect(deallocated).to.beTruthy(); + expect(completed).to.beTruthy(); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default-568h@2x.png b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default-568h@2x.png new file mode 100644 index 0000000..0891b7a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default-568h@2x.png Binary files differ
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default.png b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default.png new file mode 100644 index 0000000..4c8ca6f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default.png Binary files differ
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default@2x.png b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default@2x.png new file mode 100644 index 0000000..35b84cf --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/Default@2x.png Binary files differ
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACAppDelegate.h b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACAppDelegate.h new file mode 100644 index 0000000..88abcec --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACAppDelegate.h
@@ -0,0 +1,17 @@ +// +// RACAppDelegate.h +// ReactiveCocoa-iOS-UIKitTestHost +// +// Created by Andrew Mackenzie-Ross on 27/06/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@interface RACAppDelegate : UIResponder <UIApplicationDelegate> + +@property (nonatomic, strong) UIWindow *window; + ++ (instancetype)delegate; + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACAppDelegate.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACAppDelegate.m new file mode 100644 index 0000000..6af5af8 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACAppDelegate.m
@@ -0,0 +1,24 @@ +// +// RACAppDelegate.m +// ReactiveCocoa-iOS-UIKitTestHost +// +// Created by Andrew Mackenzie-Ross on 27/06/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACAppDelegate.h" + +@implementation RACAppDelegate + ++ (instancetype)delegate { + return (id)UIApplication.sharedApplication.delegate; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + [self.window makeKeyAndVisible]; + + return YES; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACTestTableViewController.h b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACTestTableViewController.h new file mode 100644 index 0000000..a4163aa --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACTestTableViewController.h
@@ -0,0 +1,13 @@ +// +// RACTestTableViewController.h +// ReactiveCocoa +// +// Created by Syo Ikeda on 12/30/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@interface RACTestTableViewController : UITableViewController + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACTestTableViewController.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACTestTableViewController.m new file mode 100644 index 0000000..0a9d496 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/RACTestTableViewController.m
@@ -0,0 +1,39 @@ +// +// RACTestTableViewController.m +// ReactiveCocoa +// +// Created by Syo Ikeda on 12/30/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACTestTableViewController.h" + +@implementation RACTestTableViewController + +- (instancetype)initWithStyle:(UITableViewStyle)style { + self = [super initWithStyle:style]; + if (self == nil) return nil; + + [self.tableView registerClass:UITableViewHeaderFooterView.class forHeaderFooterViewReuseIdentifier:NSStringFromClass(self.class)]; + + return self; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:NSStringFromClass(self.class)]; + return headerView; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + return [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(self.class)] ?: [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass(self.class)]; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 2; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return 20; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Info.plist b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Info.plist new file mode 100644 index 0000000..9d6f424 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Info.plist
@@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleDisplayName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>com.reactivecocoa.${PRODUCT_NAME:rfc1034identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>UIRequiredDeviceCapabilities</key> + <array> + <string>armv7</string> + </array> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>UISupportedInterfaceOrientations~ipad</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist>
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Prefix.pch b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Prefix.pch new file mode 100644 index 0000000..d6ec33c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/ReactiveCocoa-iOS-UIKitTestHost-Prefix.pch
@@ -0,0 +1,14 @@ +// +// Prefix header for all source files of the 'ReactiveCocoa-iOS-UIKitTestHost' target in the 'ReactiveCocoa-iOS-UIKitTestHost' project +// + +#import <Availability.h> + +#ifndef __IPHONE_3_0 +#warning "This project uses features only available in iOS SDK 3.0 and later." +#endif + +#ifdef __OBJC__ + #import <UIKit/UIKit.h> + #import <Foundation/Foundation.h> +#endif
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/en.lproj/InfoPlist.strings b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ +
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/main.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/main.m new file mode 100644 index 0000000..7dfdd3f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTestHost/main.m
@@ -0,0 +1,18 @@ +// +// main.m +// ReactiveCocoa-iOS-UIKitTestHost +// +// Created by Andrew Mackenzie-Ross on 27/06/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import <UIKit/UIKit.h> + +#import "RACAppDelegate.h" + +int main(int argc, char *argv[]) +{ + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([RACAppDelegate class])); + } +}
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTest-Prefix.pch b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTest-Prefix.pch new file mode 100644 index 0000000..97b985e --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTest-Prefix.pch
@@ -0,0 +1,2 @@ +#import "ReactiveCocoa-iOS-UIKitTest-Prefix.pch" +#import "ReactiveCocoaTests-Prefix.pch"
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist new file mode 100644 index 0000000..efa4ce0 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/ReactiveCocoa-iOS-UIKitTestHostTests-Info.plist
@@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>com.reactivecocoa.${PRODUCT_NAME:rfc1034identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist>
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UICollectionReusableViewRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UICollectionReusableViewRACSupportSpec.m new file mode 100644 index 0000000..5ffe160 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UICollectionReusableViewRACSupportSpec.m
@@ -0,0 +1,73 @@ +// +// UICollectionViewCellRACSupportSpec.m +// ReactiveCocoa +// +// Created by Kent Wong on 2013-10-04. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACAppDelegate.h" + +#import "RACSignal.h" +#import "RACUnit.h" +#import "UICollectionReusableView+RACSignalSupport.h" + +@interface TestCollectionViewController : UICollectionViewController +@end + +SpecBegin(UICollectionReusableViewRACSupport) + +__block UICollectionViewFlowLayout *collectionViewFlowLayout; +__block TestCollectionViewController *collectionViewController; + +beforeEach(^{ + collectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init]; + CGSize screenSize = UIScreen.mainScreen.bounds.size; + collectionViewFlowLayout.itemSize = CGSizeMake(screenSize.width, screenSize.height / 2); + + collectionViewController = [[TestCollectionViewController alloc] initWithCollectionViewLayout:collectionViewFlowLayout]; + expect(collectionViewController).notTo.beNil(); + + [collectionViewController.collectionView registerClass:UICollectionViewCell.class forCellWithReuseIdentifier:NSStringFromClass(collectionViewController.class)]; + + RACAppDelegate.delegate.window.rootViewController = collectionViewController; + expect(collectionViewController.collectionView.visibleCells.count).will.beGreaterThan(0); +}); + +it(@"should send on rac_prepareForReuseSignal", ^{ + UICollectionViewCell *cell = collectionViewController.collectionView.visibleCells[0]; + + __block NSUInteger invocationCount = 0; + [cell.rac_prepareForReuseSignal subscribeNext:^(id value) { + expect(value).to.equal(RACUnit.defaultUnit); + ++invocationCount; + }]; + + expect(invocationCount).to.equal(0); + + // The following two expectations will fail in the iOS 7 simulator, but pass on an iPad 4th gen device running iOS 7. + // This appears to be a known issue with the iOS 7 simulator. See http://openradar.appspot.com/14973972 + // These tests will pass with iOS 6.0 and 6.1. + if ([UIDevice.currentDevice.systemVersion compare:@"7.0" options:NSNumericSearch] == NSOrderedAscending || + (!TARGET_IPHONE_SIMULATOR && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)) { + [collectionViewController.collectionView reloadData]; + expect(invocationCount).will.equal(1); + + [collectionViewController.collectionView reloadData]; + expect(invocationCount).will.equal(2); + } +}); + +SpecEnd + +@implementation TestCollectionViewController + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + return [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(self.class) forIndexPath:indexPath]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return 20; +} + +@end
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITableViewCellRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITableViewCellRACSupportSpec.m new file mode 100644 index 0000000..2a69143 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITableViewCellRACSupportSpec.m
@@ -0,0 +1,46 @@ +// +// UITableViewCellRACSupportSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-07-23. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACAppDelegate.h" +#import "RACTestTableViewController.h" + +#import "RACSignal.h" +#import "RACUnit.h" +#import "UITableViewCell+RACSignalSupport.h" + +SpecBegin(UITableViewCellRACSupport) + +__block RACTestTableViewController *tableViewController; + +beforeEach(^{ + tableViewController = [[RACTestTableViewController alloc] initWithStyle:UITableViewStylePlain]; + expect(tableViewController).notTo.beNil(); + + RACAppDelegate.delegate.window.rootViewController = tableViewController; + expect(tableViewController.tableView.visibleCells.count).will.beGreaterThan(0); +}); + +it(@"should send on rac_prepareForReuseSignal", ^{ + UITableViewCell *cell = tableViewController.tableView.visibleCells[0]; + + __block NSUInteger invocationCount = 0; + [cell.rac_prepareForReuseSignal subscribeNext:^(id value) { + expect(value).to.equal(RACUnit.defaultUnit); + invocationCount++; + }]; + + expect(invocationCount).to.equal(0); + + [tableViewController.tableView reloadData]; + expect(invocationCount).will.equal(1); + + [tableViewController.tableView reloadData]; + expect(invocationCount).will.equal(2); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITableViewHeaderFooterViewRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITableViewHeaderFooterViewRACSupportSpec.m new file mode 100644 index 0000000..4658ae9 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITableViewHeaderFooterViewRACSupportSpec.m
@@ -0,0 +1,51 @@ +// +// UITableViewHeaderFooterViewRACSupportSpec.m +// ReactiveCocoa +// +// Created by Syo Ikeda on 12/30/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACAppDelegate.h" +#import "RACTestTableViewController.h" + +#import "RACSignal.h" +#import "RACUnit.h" +#import "UITableViewHeaderFooterView+RACSignalSupport.h" + +SpecBegin(UITableViewHeaderFooterViewRACSupportSpec) + +__block RACTestTableViewController *tableViewController; + +beforeEach(^{ + tableViewController = [[RACTestTableViewController alloc] initWithStyle:UITableViewStylePlain]; + expect(tableViewController).notTo.beNil(); + + RACAppDelegate.delegate.window.rootViewController = tableViewController; + expect([tableViewController.tableView headerViewForSection:0]).notTo.beNil(); +}); + +it(@"should send on rac_prepareForReuseSignal", ^{ + UITableViewHeaderFooterView *headerView = [tableViewController.tableView headerViewForSection:0]; + + __block NSUInteger invocationCount = 0; + [headerView.rac_prepareForReuseSignal subscribeNext:^(id value) { + expect(value).to.equal(RACUnit.defaultUnit); + invocationCount++; + }]; + + expect(invocationCount).to.equal(0); + + void (^scrollToSectionForReuse)(NSInteger) = ^(NSInteger section) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:section]; + [tableViewController.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO]; + }; + + scrollToSectionForReuse(tableViewController.tableView.numberOfSections - 1); + expect(invocationCount).will.equal(1); + + scrollToSectionForReuse(0); + expect(invocationCount).will.equal(2); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITextFieldRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITextFieldRACSupportSpec.m new file mode 100644 index 0000000..0aeb49c --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITextFieldRACSupportSpec.m
@@ -0,0 +1,53 @@ +// +// UITextFieldRACSupportSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "UITextField+RACSignalSupport.h" + +SpecBegin(UITextFieldRACSupport) + +describe(@"-rac_textSignal", ^{ + __block UITextField *textField; + + beforeEach(^{ + textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 20)]; + expect(textField).notTo.beNil(); + }); + + it(@"should start with the initial text", ^{ + textField.text = @"foo"; + + RACSignal *textSignal = textField.rac_textSignal; + expect([textSignal first]).to.equal(@"foo"); + + textField.text = @"bar"; + expect([textSignal first]).to.equal(@"bar"); + }); + + it(@"should clear text upon editing", ^{ + textField.text = @"foo"; + textField.clearsOnBeginEditing = YES; + + UIWindow *win = [UIWindow new]; + [win addSubview:textField]; + + __block NSString *str = @"bar"; + + RACSignal *textSignal = textField.rac_textSignal; + [textSignal subscribeNext:^(id x) { + str = x; + }]; + expect(str).to.equal(@"foo"); + + [textField becomeFirstResponder]; + expect(str).to.equal(@""); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITextViewRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITextViewRACSupportSpec.m new file mode 100644 index 0000000..f46e17a --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/UITextViewRACSupportSpec.m
@@ -0,0 +1,34 @@ +// +// UITextViewRACSupportSpec.m +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2013-06-22. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "RACSignal.h" +#import "RACSignal+Operations.h" +#import "UITextView+RACSignalSupport.h" + +SpecBegin(UITextViewRACSupport) + +describe(@"-rac_textSignal", ^{ + __block UITextView *textView; + + beforeEach(^{ + textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 100, 20)]; + expect(textView).notTo.beNil(); + }); + + it(@"should start with the initial text", ^{ + textView.text = @"foo"; + + RACSignal *textSignal = textView.rac_textSignal; + expect([textSignal first]).to.equal(@"foo"); + + textView.text = @"bar"; + expect([textSignal first]).to.equal(@"bar"); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/en.lproj/InfoPlist.strings b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIKit/UIKitTests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ +
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/UIRefreshControlRACSupportSpec.m b/ReactiveCocoaFramework/ReactiveCocoaTests/UIRefreshControlRACSupportSpec.m new file mode 100644 index 0000000..0a9b651 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/UIRefreshControlRACSupportSpec.m
@@ -0,0 +1,68 @@ +// +// UIRefreshControlRACSupportSpec.m +// ReactiveCocoa +// +// Created by Dave Lee on 2013-10-17. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "UIRefreshControl+RACCommandSupport.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACControlCommandExamples.h" +#import "RACCommand.h" +#import "RACSignal.h" + +SpecBegin(UIRefreshControlRACSupport) + +describe(@"UIRefreshControl", ^{ + __block UIRefreshControl *refreshControl; + + beforeEach(^{ + refreshControl = [[UIRefreshControl alloc] init]; + expect(refreshControl).notTo.beNil(); + }); + + itShouldBehaveLike(RACControlCommandExamples, ^{ + return @{ + RACControlCommandExampleControl: refreshControl, + RACControlCommandExampleActivateBlock: ^(UIRefreshControl *refreshControl) { + [refreshControl sendActionsForControlEvents:UIControlEventValueChanged]; + } + }; + }); + + describe(@"finishing", ^{ + __block RACSignal *commandSignal; + __block BOOL refreshingEnded; + + beforeEach(^{ + refreshControl.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { + return commandSignal; + }]; + + // Just -rac_signalForSelector: posing as a mock. + refreshingEnded = NO; + [[refreshControl + rac_signalForSelector:@selector(endRefreshing)] + subscribeNext:^(id _) { + refreshingEnded = YES; + }]; + }); + + it(@"should call -endRefreshing upon completion", ^{ + commandSignal = [RACSignal empty]; + + [refreshControl sendActionsForControlEvents:UIControlEventValueChanged]; + expect(refreshingEnded).will.beTruthy(); + }); + + it(@"should call -endRefreshing upon error", ^{ + commandSignal = [RACSignal error:[NSError errorWithDomain:@"" code:1 userInfo:nil]]; + + [refreshControl sendActionsForControlEvents:UIControlEventValueChanged]; + expect(refreshingEnded).will.beTruthy(); + }); + }); +}); + +SpecEnd
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/en.lproj/InfoPlist.strings b/ReactiveCocoaFramework/ReactiveCocoaTests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ +
diff --git a/ReactiveCocoaFramework/ReactiveCocoaTests/test-data.json b/ReactiveCocoaFramework/ReactiveCocoaTests/test-data.json new file mode 100644 index 0000000..0599ee5 --- /dev/null +++ b/ReactiveCocoaFramework/ReactiveCocoaTests/test-data.json
@@ -0,0 +1,5 @@ +[ + { "item": 1 }, + { "item": 2 }, + { "item": 3 } +]
diff --git a/script/LICENSE.md b/script/LICENSE.md new file mode 100644 index 0000000..8d92384 --- /dev/null +++ b/script/LICENSE.md
@@ -0,0 +1,18 @@ +**Copyright (c) 2013 Justin Spahr-Summers** + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/script/README.md b/script/README.md new file mode 100644 index 0000000..f66206f --- /dev/null +++ b/script/README.md
@@ -0,0 +1,82 @@ +# objc-build-scripts + +This project is a collection of scripts created with two goals: + + 1. To standardize how Objective-C projects are bootstrapped after cloning + 1. To easily build Objective-C projects on continuous integration servers + +## Scripts + +Right now, there are two important scripts: [`bootstrap`](#bootstrap) and +[`cibuild`](#cibuild). Both are Bash scripts, to maximize compatibility and +eliminate pesky system configuration issues (like setting up a working Ruby +environment). + +The structure of the scripts on disk is meant to follow that of a typical Ruby +project: + +``` +script/ + bootstrap + cibuild +``` + +### bootstrap + +This script is responsible for bootstrapping (initializing) your project after +it's been checked out. Here, you should install or clone any dependencies that +are required for a working build and development environment. + +By default, the script will verify that [xctool][] is installed, then initialize +and update submodules recursively. If any submodules contain `script/bootstrap`, +that will be run as well. + +To check that other tools are installed, you can set the `REQUIRED_TOOLS` +environment variable before running `script/bootstrap`, or edit it within the +script directly. Note that no installation is performed automatically, though +this can always be added within your specific project. + +### cibuild + +This script is responsible for building the project, as you would want it built +for continuous integration. This is preferable to putting the logic on the CI +server itself, since it ensures that any changes are versioned along with the +source. + +By default, the script will run [`bootstrap`](#bootstrap), look for any Xcode +workspace or project in the working directory, then build all targets/schemes +(as found by `xcodebuild -list`) using [xctool][]. + +You can also specify the schemes to build by passing them into the script: + +```sh +script/cibuild ReactiveCocoa-Mac ReactiveCocoa-iOS +``` + +As with the `bootstrap` script, there are several environment variables that can +be used to customize behavior. They can be set on the command line before +invoking the script, or the defaults changed within the script directly. + +## Getting Started + +To add the scripts to your project, read the contents of this repository into +a `script` folder: + +``` +$ git remote add objc-build-scripts https://github.com/jspahrsummers/objc-build-scripts.git +$ git fetch objc-build-scripts +$ git read-tree --prefix=script/ -u objc-build-scripts/master +``` + +Then commit the changes, to incorporate the scripts into your own repository's +history. You can also freely tweak the scripts for your specific project's +needs. + +To merge in upstream changes later: + +``` +$ git fetch -p objc-build-scripts +$ git merge --ff --squash -Xsubtree=script objc-build-scripts/master +``` + +[xctool]: https://github.com/facebook/xctool
diff --git a/script/bootstrap b/script/bootstrap new file mode 100755 index 0000000..89cbb8e --- /dev/null +++ b/script/bootstrap
@@ -0,0 +1,80 @@ +#!/bin/bash + +export SCRIPT_DIR=$(dirname "$0") + +## +## Configuration Variables +## + +config () +{ + # A whitespace-separated list of executables that must be present and locatable. + : ${REQUIRED_TOOLS="xctool"} + + export REQUIRED_TOOLS +} + +## +## Bootstrap Process +## + +main () +{ + config + + if [ -n "$REQUIRED_TOOLS" ] + then + echo "*** Checking dependencies..." + check_deps + fi + + local submodules=$(git submodule status) + local result=$? + + if [ "$result" -ne "0" ] + then + exit $result + fi + + if [ -n "$submodules" ] + then + echo "*** Updating submodules..." + update_submodules + fi +} + +check_deps () +{ + for tool in $REQUIRED_TOOLS + do + which -s "$tool" + if [ "$?" -ne "0" ] + then + echo "*** Error: $tool not found. Please install it and bootstrap again." + exit 1 + fi + done +} + +bootstrap_submodule () +{ + local bootstrap="script/bootstrap" + + if [ -e "$bootstrap" ] + then + echo "*** Bootstrapping $name..." + "$bootstrap" >/dev/null + else + update_submodules + fi +} + +update_submodules () +{ + git submodule sync --quiet && git submodule update --init && git submodule foreach --quiet bootstrap_submodule +} + +export -f bootstrap_submodule +export -f update_submodules + +main
diff --git a/script/cibuild b/script/cibuild new file mode 100755 index 0000000..a604019 --- /dev/null +++ b/script/cibuild
@@ -0,0 +1,156 @@ +#!/bin/bash + +export SCRIPT_DIR=$(dirname "$0") + +## +## Configuration Variables +## + +SCHEMES="$@" + +config () +{ + # The workspace to build. + # + # If not set and no workspace is found, the -workspace flag will not be passed + # to `xctool`. + # + # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will + # take precedence. + : ${XCWORKSPACE=} + + # The project to build. + # + # If not set and no project is found, the -project flag will not be passed + # to `xctool`. + # + # Only one of `XCWORKSPACE` and `XCODEPROJ` needs to be set. The former will + # take precedence. + : ${XCODEPROJ="ReactiveCocoaFramework/ReactiveCocoa.xcodeproj"} + + # A bootstrap script to run before building. + # + # If this file does not exist, it is not considered an error. + : ${BOOTSTRAP="$SCRIPT_DIR/bootstrap"} + + # Extra options to pass to xctool. + : ${XCTOOL_OPTIONS="RUN_CLANG_STATIC_ANALYZER=NO"} + + # A whitespace-separated list of default schemes to build. + # + # Individual names can be quoted to avoid word splitting. + : ${SCHEMES:=$(xcodebuild -list -project "$XCODEPROJ" 2>/dev/null | awk -f "$SCRIPT_DIR/schemes.awk")} + + export XCWORKSPACE + export XCODEPROJ + export BOOTSTRAP + export XCTOOL_OPTIONS + export SCHEMES +} + +## +## Build Process +## + +main () +{ + config + + if [ -f "$BOOTSTRAP" ] + then + echo "*** Bootstrapping..." + "$BOOTSTRAP" || exit $? + fi + + echo "*** The following schemes will be built:" + echo "$SCHEMES" | xargs -n 1 echo " " + echo + + echo "$SCHEMES" | xargs -n 1 | ( + local status=0 + + while read scheme + do + build_scheme "$scheme" || status=1 + done + + exit $status + ) +} + +find_pattern () +{ + ls -d $1 2>/dev/null | head -n 1 +} + +run_xctool () +{ + if [ -n "$XCWORKSPACE" ] + then + xctool -workspace "$XCWORKSPACE" $XCTOOL_OPTIONS "$@" 2>&1 + elif [ -n "$XCODEPROJ" ] + then + xctool -project "$XCODEPROJ" $XCTOOL_OPTIONS "$@" 2>&1 + else + echo "*** No workspace or project file found." + exit 1 + fi +} + +parse_build () +{ + awk -f "$SCRIPT_DIR/xctool.awk" 2>&1 >/dev/null +} + +build_scheme () +{ + local scheme=$1 + + echo "*** Cleaning $scheme..." + run_xctool -scheme "$scheme" clean >/dev/null || exit $? + + echo "*** Building and testing $scheme..." + echo + + local sdkflag= + local action=test + + # Determine whether we can run unit tests for this target. + run_xctool -scheme "$scheme" run-tests | parse_build + + local awkstatus=$? + + if [ "$awkstatus" -ne "0" ] + then + # Unit tests aren't supported. + action=build + fi + + if [ "$awkstatus" -eq "1" ] + then + # Build for iOS. + sdkflag="-sdk iphonesimulator" + fi + + run_xctool $sdkflag -scheme "$scheme" $action | awk '{ print; } /Failed to query the list of test cases in the test bundle/ { exit 1; }' + + local awkstatus=$? + local result=${PIPESTATUS[0]} + + if [ "$awkstatus" -eq "1" ] + then + echo + echo "*** Mac application tests are currently buggy, so they have been skipped." + echo "*** See https://github.com/facebook/xctool/issues/243 for more information." + echo + return 0 + fi + + return $result +} + +export -f build_scheme +export -f run_xctool +export -f parse_build + +main
diff --git a/script/schemes.awk b/script/schemes.awk new file mode 100644 index 0000000..d101b4f --- /dev/null +++ b/script/schemes.awk
@@ -0,0 +1,12 @@ +BEGIN { + FS = "\n"; +} + +/Targets:/ { + while (getline && $0 != "") { + if ($0 ~ /Test/) continue; + + sub(/^ +/, ""); + print "'" $0 "'"; + } +}
diff --git a/script/targets.awk b/script/targets.awk new file mode 100644 index 0000000..117660d --- /dev/null +++ b/script/targets.awk
@@ -0,0 +1,12 @@ +BEGIN { + FS = "\n"; +} + +/Targets:/ { + while (getline && $0 != "") { + if ($0 ~ /Tests/) continue; + + sub(/^ +/, ""); + print "'" $0 "'"; + } +}
diff --git a/script/xcodebuild.awk b/script/xcodebuild.awk new file mode 100644 index 0000000..c746b09 --- /dev/null +++ b/script/xcodebuild.awk
@@ -0,0 +1,35 @@ +# Exit statuses: +# +# 0 - No errors found. +# 1 - Build or test failure. Errors will be logged automatically. +# 2 - Untestable target. Retry with the "build" action. + +BEGIN { + status = 0; +} + +{ + print; + fflush(stdout); +} + +/is not valid for Testing/ { + exit 2; +} + +/[0-9]+: (error|warning):/ { + errors = errors $0 "\n"; +} + +/(TEST|BUILD) FAILED/ { + status = 1; +} + +END { + if (length(errors) > 0) { + print "\n*** All errors:\n" errors; + } + + fflush(stdout); + exit status; +}
diff --git a/script/xctool.awk b/script/xctool.awk new file mode 100644 index 0000000..f613258 --- /dev/null +++ b/script/xctool.awk
@@ -0,0 +1,25 @@ +# Exit statuses: +# +# 0 - No errors found. +# 1 - Wrong SDK. Retry with SDK `iphonesimulator`. +# 2 - Missing target. + +BEGIN { + status = 0; +} + +{ + print; +} + +/Testing with the '(.+)' SDK is not yet supported/ { + status = 1; +} + +/does not contain a target named/ { + status = 2; +} + +END { + exit status; +}