diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..3ddb9d4205 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ + +#### Checklist +- [ ] Updated CHANGELOG.md. diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 0000000000..ff6e4139c0 --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,53 @@ +on: + push: + branches: + - master + pull_request: + types: [labeled] + branches: + - master +name: Verification +jobs: + carthage: + if: ${{ github.event_name == 'push' || ( github.event_name == 'pull_request' && github.event.label.name == 'ci:verify' ) }} + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Recover cached dependencies + uses: actions/cache@v1 + id: dependency-cache + with: + path: ~/Library/Caches/org.carthage.CarthageKit + key: 4-carthage-verification-${{ runner.os }}-${{ hashFiles('Cartfile.resolved') }} + - name: Carthage verification + run: | + carthage checkout + carthage build --cache-builds --no-skip-current --use-xcframeworks + swiftpm-macos: + if: ${{ github.event_name == 'push' || ( github.event_name == 'pull_request' && github.event.label.name == 'ci:verify' ) }} + name: SwiftPM macOS + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Pull dependencies + run: | + swift package resolve + - name: Test via SwiftPM + run: | + swift --version + swift build + cocoapods: + if: ${{ github.event_name == 'push' || ( github.event_name == 'pull_request' && github.event.label.name == 'ci:verify' ) }} + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: CocoaPods verification + run: | + # To work around the lint error: "ERROR | swift: Specification `ReactiveCocoa` specifies an inconsistent `swift_version` (`4.1`) compared to the one present in your `.swift-version` file (`4.1.2`). Please remove the `.swift-version` file which is now deprecated and only use the `swift_version` attribute within your podspec." + # `.swift-version` is for swiftenv, not for CocoaPods, so we can't remove the file as suggested. + rm .swift-version + pod repo update + pod lib lint --use-libraries \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..b5c80fc2cb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,62 @@ +on: pull_request +name: Test +jobs: + test: + name: Test + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + destination: [macOS, macCatalyst, iOS, tvOS, watchOS] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Recover cached dependencies + uses: actions/cache@v1 + id: dependency-cache + with: + path: Carthage/Checkouts + key: carthage-${{ hashFiles('Cartfile.resolved') }} + - name: Pull dependencies + if: steps.dependency-cache.outputs.cache-hit != 'true' + run: | + carthage checkout + - name: Test via xcodebuild + run: | + ACTION=test + DESTINATION=unknown + SCHEME=unknown + case "${{ matrix.destination }}" in + "iOS") + DESTINATION="platform=iOS Simulator,name=iPhone 11 Pro" + SCHEME=ReactiveCocoa-iOS + ;; + "tvOS") + DESTINATION="platform=tvOS Simulator,name=Apple TV" + SCHEME=ReactiveCocoa-tvOS + ;; + "watchOS") + ACTION=build + DESTINATION="platform=watchOS Simulator,name=Apple Watch Series 5 (44mm)" + SCHEME=ReactiveCocoa-watchOS + ;; + "macCatalyst") + DESTINATION="platform=macOS,variant=Mac Catalyst" + SCHEME=ReactiveCocoa-iOS + ;; + "macOS") + DESTINATION="platform=macOS,arch=x86_64" + SCHEME=ReactiveCocoa-macOS + ;; + *) + echo "Unknown destination." + exit 1 + ;; + esac + xcodebuild clean ${ACTION} \ + -destination "${DESTINATION}" \ + -scheme ${SCHEME} \ + -workspace ReactiveCocoa.xcworkspace \ + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + ONLY_ACTIVE_ARCH=YES diff --git a/.gitignore b/.gitignore index d1ba8ca674..048025513e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,19 @@ xcuserdata profile *.moved-aside PlaygroundUtility.remap +*.xctimeline +*.xcscmblueprint +*.o # SwiftPM .build +Packages # Carthage Carthage/Build + +# macOS +.DS_Store + +# Jazzy +docs diff --git a/.gitmodules b/.gitmodules index 50cdf7fcbb..72a6626ffd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,12 @@ [submodule "Carthage/Checkouts/Nimble"] path = Carthage/Checkouts/Nimble - url = https://github.com/norio-nomura/Nimble.git + url = https://github.com/Quick/Nimble.git [submodule "Carthage/Checkouts/Quick"] path = Carthage/Checkouts/Quick - url = https://github.com/norio-nomura/Quick.git + url = https://github.com/Quick/Quick.git [submodule "Carthage/Checkouts/xcconfigs"] path = Carthage/Checkouts/xcconfigs - url = https://github.com/jspahrsummers/xcconfigs.git -[submodule "Carthage/Checkouts/Result"] - path = Carthage/Checkouts/Result - url = https://github.com/antitypical/Result.git + url = https://github.com/xcconfigs/xcconfigs.git +[submodule "Carthage/Checkouts/ReactiveSwift"] + path = Carthage/Checkouts/ReactiveSwift + url = https://github.com/ReactiveCocoa/ReactiveSwift.git diff --git a/.swift-version b/.swift-version index d54e0b2d65..ef425ca982 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0-PREVIEW-4 +5.2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 85e315aaed..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -language: objective-c -osx_image: xcode8 -before_install: true -install: true -git: - submodules: false -before_script: - - git submodule update --init --recursive -script: - - script/build -xcode_workspace: ReactiveCocoa.xcworkspace -matrix: - include: - - xcode_scheme: ReactiveCocoa-Mac - env: - - XCODE_SDK=macosx - - XCODE_ACTION="build test" - - XCODE_DESTINATION="arch=x86_64" - - XCODE_PLAYGROUND_TARGET="x86_64-apple-macosx10.10" - - xcode_scheme: ReactiveCocoa-iOS - env: - - XCODE_SDK=iphonesimulator - - XCODE_ACTION="build-for-testing test-without-building" - - XCODE_DESTINATION="platform=iOS Simulator,name=iPhone 6s" - - xcode_scheme: ReactiveCocoa-tvOS - env: - - XCODE_SDK=appletvsimulator - - XCODE_ACTION="build-for-testing test-without-building" - - XCODE_DESTINATION="platform=tvOS Simulator,name=Apple TV 1080p" - - xcode_scheme: ReactiveCocoa-watchOS - env: - - XCODE_SDK=watchsimulator - - XCODE_ACTION=build - - XCODE_DESTINATION="platform=watchOS Simulator,name=Apple Watch - 38mm" - - script: - - brew update - - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current - env: - - JOB=CARTHAGE - - os: osx - language: generic - script: swift build - env: - - JOB=SWIFTPM_DARWIN -notifications: - email: false - slack: - secure: C9QTry5wUG9CfeH3rm3Z19R5rDWqDO7EhHAqHDXBxT6CpGRkTPFliJexpjBYB4sroJ8CiY5ZgTI2sjRBiAdGoE5ZQkfnwSoKQhWXkwo19TnbSnufr3cKO2SZkUhBqOlZcA+mgfjZ7rm2wm7RhpCR/4z8oBXDN4/xv0U5R2fLCLE= diff --git a/CHANGELOG.md b/CHANGELOG.md index bd49c774c7..f90f91ed41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,503 @@ +# master + +1. Add compatibility with ReactiveSwift 7.1.1 and bump minimum deployment targets to macOS 10.13, iOS 11.0, tvOS 11.0 and watchOS 4.0. + +# 12.0.0 + +1. Requires ReactiveSwift 7.0. + +# 11.2.2 + +1. Building from Xcode project no longer warns about use of deprecated `class` keyword usage. (#3726, kudos to @michalsrutek) +2. Updated Carthage xcconfig dependency to 1.1 for proper building arm64 macOS variants. + +# 11.2.1 + +1. Fixed missing Foundation import when building with SPM in Xcode 12.5. (#3725, kudos to @TimPapler) + +# 11.2.0 + +1. Requires ReactiveSwift 6.6.0 or later. +1. The minimum deployment target for iOS has been raised consistently to 9.0 across all integration mediums. + +# 11.1.0 +# 11.0.01. Binding for `WKInterfaceActivityRing` has been removed, since it causes watchOS builds to be linked with HealthKit, leading to potential App Store rejections for apps who do not use HealthKit. (#3706) + + Users who depend on the `WKInterfaceActivityRing` binding [should consider replicating them in their projects instead](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/10.3.0/ReactiveCocoa/WatchKit/WKInterfaceActivityRing.swift). + +# 10.3.0 +1. Don't include code which uses unavailable classes (like `NSSlider`) when targeting macOS Catalyst (#3698, kudos to @nkristek) +1. Fixed watchOS build issues. (#3703, kudos to @JaviSoto) + +# 10.2.0 +1. Update ReactiveSwift to 6.2. +1. Updated `README.md` to reflect Swift 5.1 compatibility and point snippets to 10.1.0 (#3691, kudos to @Marcocanc) +1. Support for Swift Package Manager (#3692, #3676 & #3693, kudos to @fabio-cerdeiral-ck, @sharplet and @simba909) + +# 10.1.0 +1. Update dependencies so ReactiveCocoa can be used with Xcode 11 (#3677, kudos to @olejnjak) +1. Add a binding target for the `barTintColor` of `UINavigationBar` (#3675, kudos to @rehatkathuria) +1. Add reactive extensions for standard WatchKit interface objects. (#3670, kudos to @tdimeco) +1. Fix crashes of `NSObject.signal(for:)` and `NSObject.producer(for:)` with Objective-C enums (#3667, kudos to @gfontenot) + +# 10.3.0 +# 10.0.0 +1. Update ReactiveSwift to 6.0 +1. Remove dependency on antitypical/Result + +**Upgrade to master** + +* If you have used `Result` only as dependency of `ReactiveSwift`, remove all instances of `import Result`, `import enum Result.NoError` or `import struct Result.AnyError` and remove the `Result` Framework from your project. +* Replace all cases where `NoError` was used in a `Signal` or `SignalProducer` with `Never` +* Replace all cases where `AnyError` was used in a `Signal` or `SignalProducer` with `Swift.Error` + +# 9.0.0 +1. Make UITextField and UITextView text and attributedText values non-optional. (#3591, kudos to @Marcocanc) +1. KVO observations can now be made with Smart Key Path in Swift 3.2+, using `producer(for:)` and `signal(for:)` available on `NSObject.reactive`. (#3491, kudos to @andersio) +1. Fix warnings for deprecated use of Swift's allocate/deallocate methods. +1. Update Quick (2.0.0) and Nimble (8.0.1). +1. Update ReactiveSwift to 5.0. +1. Added `applicationIconBadgeNumber` binding target to `UIApplication` (#3589, kudos to @cocoahero). +1. Add extension for `alphaValue` property of `NSView` class. (#3636, kuds to @eimantas) +1. Add extension for `isHidden` property of `NSView` class. (#3634, kudos to @eimantas) + +# 8.0.2 +1. ReactiveMapKit has now platform specific build targets and schemes. (#3625, kudos to @andersio) + +# 8.0.1 +1. Add extensions for several properties on `WKInterfaceLabel` and `WKInterfaceButton`. (#3616, kudos to @yoching) +1. Add `swift_version` to podspecs (#3622, kudos to @olejnjak) +1. Introduce Lifetime.of(_:) which retrieves the lifetime of any Objective-C or Swift native object. (#3614, kudos to @ra1028) +1. Fixed an issue of `SignalProducer.take(duringLifetimeOf:)` incorrectly retaining its argument. (#3615, kudos to @andrei-kuzma) + +# 8.0.0 +1. Add extensions for several properties on `UIBarButtonItem` (#3586, kudos to @asmallteapot). + +# 8.0.0-rc.1 +1. Add support for Cocoapods 1.5.0 static frameworks (#3590, kudos to @mishagray) + +1. Add `becomeFirstResponder` and `resignFirstResponder` extensions to `UIResponder`. (#3585, kudos to @Marcocanc) +2. Added `title` binding target to `UIViewController` (#3588, kudos to @cocoahero). +3. Added several trigger signals for view lifecycle events to `UIViewController` (#3588, kudos to @cocoahero). + +# 7.2.0 +1. Fixed a compilation issue related to [SR-7299](https://bugs.swift.org/browse/SR-7299). (#3580) + +1. Improved the interoperability of method interception. (#3570, kudos to @andersio) + +1. Add `showsCancelButton`, `textDidBeginEditing` and `textDidEndEditing` extensions to `UISearchBar`. (#3565, kudos to @banjun) + +1. `NotificationCenter.reactive.keyboard(_:)` for system keyboard notification by the event types. (#3566, kudos to @ra1028) + +1. Add extensions for several properties on `UINavigationItem`. (#3576, kudos to @asmallteapot) + +1. Add extension for `search` on MKLocalSearchRequest. (#3571, kudos to @Marcocanc) + +# 7.1.0 +# 7.1.0-rc.2 +1. Fix an issue preventing ReactiveCocoa from being built with the Swift 3.2 language mode. (#3556) + +# 7.1.0-rc.1 +1. Requires ReactiveSwift 3.1.0 Release Candidate 1. (#3555) + +1. Added reactive extension for AppKit's NSTextView. (#3549, kudos to @Palleas) + +# 7.0.1 +1. Added `tintColor` binding target to `UIView`. (#3542, kudos to @iv-mexx) + +1. Fixed `DynamicProperty` for optional properties. (#3548, kudos to @iv-mexx) + +1. Made `makeBindingTarget` available on Reactive extensions on all objects, not just `NSObject`. (#3545, kudos to @Burgestrand) + +# 7.0.0 +1. Update ReactiveSwift to 3.0. + +1. Added `placeholder` binding target to `UITextField`. (#3536) + +# 7.0.0-rc.1 +1. UISearchBar has gained more reactive bindings and signals. (#3531, kudos to @andersio) + + **Signals:** Search Button Clicked, Bookmark Button Clicked, Results List Clicked, Selected Scope Button Index + + **Binding Target:** Selected Scope Button Indices. + +# 7.0.0-alpha.2 +1. Requires ReactiveSwift 3.0.0 alpha 1. + +1. ReactiveCocoa is now compatible with the Swift 4.0 language mode, in addition to the Swift 3.2 compatibility mode. (#3526, kudos to @andersio) + +# 7.0.0-alpha.1 +1. MapKit reactive bindings have been moved to a new **ReactiveMapKit** framework. (#3524) + + Sources that use the MapKit bindings are now required to import ReactiveMapKit. + + For all Xcode project users (including Carthage), targets need to be configured to link against ReactiveMapKit. For CocoaPods users, the framework is offered as a standalone podspec, so the Podfile needs to be updated with a new entry. + +# 6.1.0-alpha.2 +# 6.1.0-alpha.1 +1. Added `cancelButtonClicked` signal to `UISearchBar`. +1. Subscripting `reactive` with a key path now yields a corresponding `BindingTarget` under Swift 3.2+. (#3489, kudos to @andersio) + + Example: + ```swift + label.reactive[\.text] <~ viewModel.title + ``` + +# 6.0.2 +1. Disabled code coverage data to allow app submissions with Xcode 9.0 (see https://github.com/Carthage/Carthage/issues/2056, kudos to @NachoSoto) + +# 6.0.1 +1. [Xcode 9 beta 5] Fixed an issue causing infinite recursion in the Swift runtime. (#3498, kudos to @andersio) + +# 6.0.0 +# 6.0.0-rc.3 +# 6.0.0-rc.2 +1. `NSObject` reactive extensions now work in generic environments that are limited to `NSObjectProtocol`. (#3484, kudos to @nickdomenicali) + +1. New reactive extension for `UIScrollView`: `scrollsToTop`. (#3481, kudos to @Qata) + +# 6.0.0-rc.1 +1. `UIButton.reactive.pressed` now reacts to the `primaryActionTriggered` control event, instead of `touchUpInside`, on iOS 9.0+ and tvOS 9.0+. (#3480, kudos to @andrei-kuzma) + +1. New reactive extension: `UITextField.reactive.selectedRangeValues`. (#3479, kudos to @Igor-Palaguta) + +# 6.0.0-alpha.1 +# 5.0.4 +1. UITextField text signals now react to `editingDidEndOnExit`. (#3474) + +1. Introduce `mapControlEvents(_:_:)` which is set to replace `controlEvents(_:_:)` in most cases. (#3472) + + You should use `mapControlEvents` in general unless the state of the control — e.g. `text`, `state` — is **not** concerned. In other words, you should avoid using `map` on a control event signal to extract the state from the control. + +1. Resigning first responder when reacting to a `UITextField` signal no longer deadlocks. (#3453, #3472) + +1. New operator: `take(duringLifetimeOf:)`. (#3466, kudos to @andersio) + It is available on `Signal` and `SignalProducer`, and supports both Objective-C and native Swift objects. + +# 5.0 + +### Table of Contents +1. [Repository Split](#repository-split) +1. [Swift 3.0 API Renaming](#swift-30-api-renaming) +1. [New in 5.0: Cocoa Extensions](#new-in-50-cocoa-extensions) +1. [Changes in ReactiveSwift 1.0](#changes-in-reactiveswift-10) +1. [Migrating from the ReactiveObjC API](#migrating-from-the-reactiveobjc-api) + +### Repository Split +In version 5.0, we split ReactiveCocoa into multiple repositories for reasons explained in the sections below. The following should help you get started with choosing the repositories you require: + +**If you’re using only the Swift APIs**, you can continue to include ReactiveCocoa. You will also need to link against [ReactiveSwift][], which is now a dependency of ReactiveCocoa. + +**If you’re using only the Objective-C APIs**, you can switch to using [ReactiveObjC][]. It has all the Obj-C code from RAC 2. + +**If you’re using both the Swift and Objective-C APIs**, you likely require both ReactiveCocoa and [ReactiveObjCBridge][], which depend on [ReactiveSwift][] and [ReactiveObjC][]. + +**Attention:** If you're using ReactiveCocoa, you'll most likely need to import ReactiveSwift as well when using classes or operators that are implemented in ReactiveSwift. + +#### ReactiveCocoa +The ReactiveCocoa library is newly focused on Swift and the UI layers of Apple’s platforms, building on the work of [Rex](https://github.com/neilpa/Rex). + +Reactive programming provides significant benefit in UI programming. RAC 3 and 4 focused on building out the new core Swift API. But we feel that those APIs have matured and it’s time for RAC-friendly extensions to AppKit and UIKit. + +#### ReactiveSwift +The core, platform-independent Swift APIs have been extracted to a new framework, [ReactiveSwift][]. + +As Swift continues to grow as a language and a platform, we hope that it will expand beyond Cocoa and Apple’s platforms. Separating the Swift code makes it possible to use the reactive paradigm on other platforms. + +[ReactiveSwift]: https://github.com/ReactiveCocoa/ReactiveSwift + +#### ReactiveObjC +The 3.x and 4.x releases of ReactiveCocoa included the Objective-C code from ReactiveCocoa 2.x. That code has been moved to [ReactiveObjC][] because: + + 1. It’s independent of the Swift code + 2. It has a separate user base + 3. It has a separate group of maintainers + +We hope that this move will enable continued support of ReactiveObjC. + +[ReactiveObjC]: https://github.com/ReactiveCocoa/ReactiveObjC + +#### ReactiveObjCBridge +Moving the Swift and Objective-C APIs to separate repositories meant that a new home was needed for the bridging layer between the two. + +This bridge is an important tool for users that are working in mixed-language code bases. Whether you are slowly adding Swift to a mature product built with the ReactiveCocoa Objective-C APIs, or looking to adopt ReactiveCocoa in a mixed code base, the bridge is required to communicate between Swift and Objective-C code. + +[ReactiveObjCBridge]: https://github.com/ReactiveCocoa/ReactiveObjCBridge + +### Swift 3.0 API Renaming + +We mostly adjusted the ReactiveCocoa API to follow the [Swift 3 API Design Guidelines](https://swift.org/blog/swift-3-api-design/), or to match the Cocoa and Foundation API changes that came with Swift 3 and the latest platform SDKs. + +Lots has changed, but if you're already migrating to Swift 3 then that should not come as a surprise. Fortunately for you, we've provided annotations in the source that should help you while using the Swift 3 migration tool that ships with Xcode 8. When changes aren't picked up by the migrator, they are often provided for you as Fix-Its. + +**Tip:** You can apply all the suggested fix-its in the current scope by choosing Editor > Fix All In Scope from the main menu in Xcode, or by using the associated keyboard shortcut. + +### New in 5.0: Cocoa Extensions + +#### Foundation: Object Interception + +RAC 5.0 includes a few object interception tools from ReactiveObjC, remastered for ReactiveSwift. + +1. **Method Call Interception** + + Create signals that are sourced by intercepting Objective-C objects. + + ```swift + // Notify after every time `viewWillAppear(_:)` is called. + let appearing = viewController.reactive.trigger(for: #selector(UIViewController.viewWillAppear(_:))) + ``` + +1. **Object Lifetime** + + Obtain a `Lifetime` token for any `NSObject` to observe their deinitialization. + + ```swift + // Observe the lifetime of `object`. + object.reactive.lifetime.ended.observeCompleted(doCleanup) + ``` + +1. **Expressive, Safe Key Path Observation** + + Establish key-value observations in the form of [`SignalProducer`][]s and + strong-typed `DynamicProperty`s, and enjoy the inherited composability. + + ```swift + // A producer that sends the current value of `keyPath`, followed by + // subsequent changes. + // + // Terminate the KVO observation if the lifetime of `self` ends. + let producer = object.reactive.values(forKeyPath: #keyPath(key)) + .take(during: self.reactive.lifetime) + + // A parameterized property that represents the supplied key path of the + // wrapped object. It holds a weak reference to the wrapped object. + let property = DynamicProperty(object: person, + keyPath: #keyPath(person.name)) + ``` + +These are accessible via the `reactive` magic property that is available on any ObjC objects. + +#### AppKit & UIKit: UI bindings + +UI components now expose a collection of binding targets to which can be bound from any arbitrary streams of values. + +1. **UI Bindings** + + UI components exposes [`BindingTarget`][]s, which accept bindings from any + kind of streams of values via the `<~` operator. + + ```swift + // Bind the `name` property of `person` to the text value of an `UILabel`. + nameLabel.reactive.text <~ person.name + ``` + +1. **Controls and User Interactions** + + Interactive UI components expose [`Signal`][]s for control events + and updates in the control value upon user interactions. + + A selected set of controls provide a convenience, expressive binding + API for [`Action`][]s. + + + ```swift + // Update `allowsCookies` whenever the toggle is flipped. + preferences.allowsCookies <~ toggle.reactive.isOnValues + + // Compute live character counts from the continuous stream of user initiated + // changes in the text. + textField.reactive.continuousTextValues.map { $0.characters.count } + + // Trigger `commit` whenever the button is pressed. + button.reactive.pressed = CocoaAction(viewModel.commit) + ``` + +These are accessible via the `reactive` magic property that is available on any ObjC objects. + +### Changes in ReactiveSwift 1.0 + +#### Signal: Lifetime Semantics + +Prior to RAC 5.0, `Signal`s lived and continued to emit values (and side effects) until they completed. This was very confusing, even for RAC veterans. So [changes have been made](https://github.com/ReactiveCocoa/ReactiveCocoa/pull/2959) to the lifetime semantics. `Signal`s now live and continue to emit events only while either (a) they have observers or (b) they are retained. This clears up a number of unexpected cases and makes `Signal`s much less dangerous. + +#### SignalProducer: `buffer` has been removed. +Consider using `Signal.pipe` for `buffer(0)`, `MutableProperty` for `buffer(1)` or `replayLazily(upTo: n)` for `buffer(n)`. + +#### Properties: Composition +Properties are now composable! They have many of the same operators as `Signal` and `SignalProducer`: `map`, `filter`, `combineLatest`, `zip`, `flatten`, etc. + +#### Properties: Lifetime Semantics +Composed properties, including those created via `Property(initial:then:)`, are semantically a view to their ultimate sources. In other words, the lifetime, the signal and the producer would respect the ultimate sources, and deinitialization of an instance of composed property would not have an effect on these. + +```swift +let property = MutableProperty(1) +var composed: Property = property.map { $0 + 10 } +composed.startWithValues { print("\($0)") } +composed = nil + +property.value = 2 +// The produced signal is still alive, printing `12` to the output stream. +``` + +#### Atomic: A more efficient `modify` + +`Atomic.modify` now passes its value to the supplied action as an `inout`. This enables the compiler to optimize it as an in-place mutation, which benefits collections, large `struct`s and `struct`s with considerable amount of references. + +Moreover, `Atomic.modify` now returns the returned value from the supplied action, instead of the old value as in RAC 4.x, so as to reduce unnecessary copying. + +```swift +// ReactiveCocoa 4.0 +let old = atomicCount.modify { $0 + 1 } + +// ReactiveSwift 1.0 +let old = atomicCount.modify { value in + let old = value + value += 1 + return old +} +``` + +#### BindingTarget + +The new `BindingTargetProtocol` protocol has been formally introduced to represent an entity to which can form a unidirectional binding using the `<~` operator. A new type `BindingTarget` has also been introduced to represent non-observable targets that are expected to only be written to. + +```swift +// The `UIControl` exposes a `isEnabled` binding target. +control.isEnabled <~ viewModel.isEnabled +``` + +#### Lifetime + +`Lifetime` is introduced to represent the lifetime of any arbitrary reference types. It works by completing the signal when its wrapping `Lifetime.Token` deinitializes with the associated reference type. While it is provided as `NSObject.reactive.lifetime` on Objective-C objects, it can also be associated manually with Swift classes to provide the same semantics. + +```swift +public final class MyController { + private let token = Lifetime.Token() + public let lifetime: Lifetime + + public init() { + lifetime = Lifetime(token) + } +} +``` + +### Migrating from the ReactiveObjC API + +#### Primitives + + + + + + + + + + + + + + + + + + + + + + + + + +
ReactiveObjCReactiveCocoa 5.0
Cold RACSignalSignalProducer
Hot RACSignalSignal
Serial RACCommandAction
Concurrent RACCommandCurrently no counterpart.
+ +#### Macros + + + + + + + + + + + + + + + + + +
ReactiveObjCReactiveCocoa 5.0
RAC(label, text)Discover binding targets via .reactive on UI components. +

label.reactive.text <~ viewModel.name

+
RACObserve(object, keyPath)NSObject.reactive.values(forKeyPath:)
+#### NSObject interception + + + + + + + + + + + + + + + + + + + + + + + + + +
ReactiveObjCReactiveCocoa 5.0
rac_willDeallocSignalNSObject.reactive.lifetime, in conjunction with the take(during:) operator. +

signal.take(during: object.reactive.lifetime)

+
rac_liftSelector:withSignals:Apply combineLatest to your signals, and invoke the method in observeValues. +

+

Signal.combineLatest(signal1, signal2)
+	.take(during: self.reactive.lifetime)
+	.observeValues { [weak self] in self?.perform(first: $0, second: $1) }
+

+
rac_signalForSelector:NSObject.reactive.trigger(for:) and NSObject.reactive.signal(for:)
rac_signalForSelector:fromProtocol:Currently no counterpart.
+#### Control bindings and observations + + + + + + + + + + + + + + + + + + + + + +
ReactiveObjCReactiveCocoa 5.0
Control value changes, e.g. textField.rac_textSignal()Discover control value `Signal`s via .reactive on UI components. +

viewModel.searchString <~ textField.reactive.textValues

+
rac_signalForControlEvents:UIControl.reactive.trigger(for:)
rac_commandDiscover action binding APIs via .reactive on UI components. +

button.reactive.pressed = CocoaAction(viewModel.submitAction)

+
+ # 4.0 -If you’re new to the Swift API and migrating from RAC 2, start with the [3.0 changes](#30). This section only covers the differences between `3.0` and `4.0`. +If you’re new to the Swift API and migrating from RAC 2, start with the [3.0 changes](#30). This section only covers the differences between `3.0` and `4.0`. Just like in `RAC 3`, because Objective-C is still in widespread use, 99% of `RAC 2.x` code will continue to work under `RAC 4.0` without any changes. That is, `RAC 2.x` primitives are still available in `RAC 4.0`. @@ -17,9 +514,9 @@ Previously the custom `|>` was required to enable chaining global functions with ```swift /// RAC 3 -signal - |> filter { $0 % 2 == 0 } - |> map { $0 * $0 } +signal + |> filter { $0 % 2 == 0 } + |> map { $0 * $0 } |> observe { print($0) } /// RAC 4 @@ -453,3 +950,8 @@ soon as possible on the main thread—even synchronously (if possible), thereby replacing RAC 2’s `-performOnMainThread` operator—while `QueueScheduler.mainQueueScheduler` will always enqueue work after the current run loop iteration, and can be used to schedule work at a future date. + +[`Signal`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#signals +[`SignalProducer`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#signal-producers +[`Action`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#actions +[`BindingTarget`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#binding-target diff --git a/Cartfile b/Cartfile index e438273700..d55c71844c 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "antitypical/Result" "3.0.0-alpha.4" +github "ReactiveCocoa/ReactiveSwift" "master" diff --git a/Cartfile.private b/Cartfile.private index e595107b31..2ff3b76213 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,3 +1,3 @@ -github "jspahrsummers/xcconfigs" "3d9d996" -github "norio-nomura/Quick" "nn-swift-3-compatibility" -github "norio-nomura/Nimble" "nn-swift-3-compatibility" +github "xcconfigs/xcconfigs" ~> 1.1 +github "Quick/Quick" ~> 4.0 +github "Quick/Nimble" ~> 9.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index f2e6b9d31a..b090175891 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,4 +1,4 @@ -github "norio-nomura/Nimble" "3d82a185b49e8fc9f40e7b72feeb279c778f0935" -github "norio-nomura/Quick" "4a7f1d0a7db637c3ca15b8f198a516fbc5581f87" -github "antitypical/Result" "3.0.0-alpha.4" -github "jspahrsummers/xcconfigs" "3d9d99634cae6d586e272543d527681283b33eb0" +github "Quick/Nimble" "v9.2.1" +github "Quick/Quick" "v4.0.0" +github "ReactiveCocoa/ReactiveSwift" "f4f3d4d7375ce26a797f7f0b4c246444c3afd43f" +github "xcconfigs/xcconfigs" "1.1" diff --git a/Carthage/Checkouts/Nimble b/Carthage/Checkouts/Nimble index 3d82a185b4..c93f16c25a 160000 --- a/Carthage/Checkouts/Nimble +++ b/Carthage/Checkouts/Nimble @@ -1 +1 @@ -Subproject commit 3d82a185b49e8fc9f40e7b72feeb279c778f0935 +Subproject commit c93f16c25af5770f0d3e6af27c9634640946b068 diff --git a/Carthage/Checkouts/Quick b/Carthage/Checkouts/Quick index 4a7f1d0a7d..bd86ca0141 160000 --- a/Carthage/Checkouts/Quick +++ b/Carthage/Checkouts/Quick @@ -1 +1 @@ -Subproject commit 4a7f1d0a7db637c3ca15b8f198a516fbc5581f87 +Subproject commit bd86ca0141e3cfb333546de5a11ede63f0c4a0e6 diff --git a/Carthage/Checkouts/ReactiveSwift b/Carthage/Checkouts/ReactiveSwift new file mode 160000 index 0000000000..f4f3d4d737 --- /dev/null +++ b/Carthage/Checkouts/ReactiveSwift @@ -0,0 +1 @@ +Subproject commit f4f3d4d7375ce26a797f7f0b4c246444c3afd43f diff --git a/Carthage/Checkouts/Result b/Carthage/Checkouts/Result deleted file mode 160000 index cc1699dbd8..0000000000 --- a/Carthage/Checkouts/Result +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cc1699dbd812c71bee7c44c8cbd75497713bc279 diff --git a/Carthage/Checkouts/xcconfigs b/Carthage/Checkouts/xcconfigs index 3d9d99634c..4ced0ad5a9 160000 --- a/Carthage/Checkouts/xcconfigs +++ b/Carthage/Checkouts/xcconfigs @@ -1 +1 @@ -Subproject commit 3d9d99634cae6d586e272543d527681283b33eb0 +Subproject commit 4ced0ad5a971220917994a4edfa6abf9702e3818 diff --git a/Documentation/BasicOperators.md b/Documentation/BasicOperators.md deleted file mode 100644 index ee45dac7dc..0000000000 --- a/Documentation/BasicOperators.md +++ /dev/null @@ -1,498 +0,0 @@ -# Basic Operators - -This document explains some of the most common operators used in ReactiveCocoa, -and includes examples demonstrating their use. - -Note that “operators”, in this context, refers to functions that transform -[signals][] and [signal producers][], _not_ custom Swift operators. In other -words, these are composable primitives provided by ReactiveCocoa for working -with event streams. - -This document will use the term “event stream” when dealing with concepts that -apply to both `Signal` and `SignalProducer`. When the distinction matters, the -types will be referred to by name. - -**[Performing side effects with event streams](#performing-side-effects-with-event-streams)** - - 1. [Observation](#observation) - 1. [Injecting effects](#injecting-effects) - -**[Operator composition](#operator-composition)** - - 1. [Lifting](#lifting) - -**[Transforming event streams](#transforming-event-streams)** - - 1. [Mapping](#mapping) - 1. [Filtering](#filtering) - 1. [Aggregating](#aggregating) - -**[Combining event streams](#combining-event-streams)** - - 1. [Combining latest values](#combining-latest-values) - 1. [Zipping](#zipping) - -**[Flattening producers](#flattening-producers)** - - 1. [Merging](#merging) - 1. [Concatenating](#concatenating) - 1. [Switching to the latest](#switching-to-the-latest) - -**[Handling failures](#handling-failures)** - - 1. [Catching failures](#catching-failures) - 1. [Retrying](#retrying) - 1. [Mapping errors](#mapping-errors) - 1. [Promote](#promote) - -## Performing side effects with event streams - -### Observation - -`Signal`s can be observed with the `observe` function. It takes an `Observer` as argument to which any future events are sent. - -```Swift -signal.observe(Signal.Observer { event in - switch event { - case let .Next(next): - print("Next: \(next)") - case let .Failed(error): - print("Failed: \(error)") - case .Completed: - print("Completed") - case .Interrupted: - print("Interrupted") - } -}) -``` - -Alternatively, callbacks for the `Next`, `Failed`, `Completed` and `Interrupted` events can be provided which will be called when a corresponding event occurs. - -```Swift -signal.observeNext { next in - print("Next: \(next)") -} -signal.observeFailed { error in - print("Failed: \(error)") -} -signal.observeCompleted { - print("Completed") -} -signal.observeInterrupted { - print("Interrupted") -} -``` - -Note that it is not necessary to observe all four types of event - all of them are optional, you only need to provide callbacks for the events you care about. - -### Injecting effects - -Side effects can be injected on a `SignalProducer` with the `on` operator without actually subscribing to it. - -```Swift -let producer = signalProducer - .on(started: { - print("Started") - }, event: { event in - print("Event: \(event)") - }, failed: { error in - print("Failed: \(error)") - }, completed: { - print("Completed") - }, interrupted: { - print("Interrupted") - }, terminated: { - print("Terminated") - }, disposed: { - print("Disposed") - }, next: { value in - print("Next: \(value)") - }) -``` - -Similar to `observe`, all the parameters are optional and you only need to provide callbacks for the events you care about. - -Note that nothing will be printed until `producer` is started (possibly somewhere else). - -## Operator composition - -### Lifting - -`Signal` operators can be _lifted_ to operate upon `SignalProducer`s using the -`lift` method. - -This will create a new `SignalProducer` which will apply the given operator to -_every_ `Signal` created, just as if the operator had been applied to each -produced `Signal` individually. - -## Transforming event streams - -These operators transform an event stream into a new stream. - -### Mapping - -The `map` operator is used to transform the values in an event stream, creating -a new stream with the results. - -```Swift -let (signal, observer) = Signal.pipe() - -signal - .map { string in string.uppercaseString } - .observeNext { next in print(next) } - -observer.sendNext("a") // Prints A -observer.sendNext("b") // Prints B -observer.sendNext("c") // Prints C -``` - -[Interactive visualisation of the `map` operator.](http://neilpa.me/rac-marbles/#map) - -### Filtering - -The `filter` operator is used to only include values in an event stream that -satisfy a predicate. - -```Swift -let (signal, observer) = Signal.pipe() - -signal - .filter { number in number % 2 == 0 } - .observeNext { next in print(next) } - -observer.sendNext(1) // Not printed -observer.sendNext(2) // Prints 2 -observer.sendNext(3) // Not printed -observer.sendNext(4) // prints 4 -``` - -[Interactive visualisation of the `filter` operator.](http://neilpa.me/rac-marbles/#filter) - -### Aggregating - -The `reduce` operator is used to aggregate a event stream’s values into a single -combined value. Note that the final value is only sent after the input stream -completes. - -```Swift -let (signal, observer) = Signal.pipe() - -signal - .reduce(1) { $0 * $1 } - .observeNext { next in print(next) } - -observer.sendNext(1) // nothing printed -observer.sendNext(2) // nothing printed -observer.sendNext(3) // nothing printed -observer.sendCompleted() // prints 6 -``` - -The `collect` operator is used to aggregate a event stream’s values into -a single array value. Note that the final value is only sent after the input -stream completes. - -```Swift -let (signal, observer) = Signal.pipe() - -signal - .collect() - .observeNext { next in print(next) } - -observer.sendNext(1) // nothing printed -observer.sendNext(2) // nothing printed -observer.sendNext(3) // nothing printed -observer.sendCompleted() // prints [1, 2, 3] -``` - -[Interactive visualisation of the `reduce` operator.](http://neilpa.me/rac-marbles/#reduce) - -## Combining event streams - -These operators combine values from multiple event streams into a new, unified -stream. - -### Combining latest values - -The `combineLatest` function combines the latest values of two (or more) event -streams. - -The resulting stream will only send its first value after each input has sent at -least one value. After that, new values on any of the inputs will result in -a new value on the output. - -```Swift -let (numbersSignal, numbersObserver) = Signal.pipe() -let (lettersSignal, lettersObserver) = Signal.pipe() - -let signal = combineLatest(numbersSignal, lettersSignal) -signal.observeNext { next in print("Next: \(next)") } -signal.observeCompleted { print("Completed") } - -numbersObserver.sendNext(0) // nothing printed -numbersObserver.sendNext(1) // nothing printed -lettersObserver.sendNext("A") // prints (1, A) -numbersObserver.sendNext(2) // prints (2, A) -numbersObserver.sendCompleted() // nothing printed -lettersObserver.sendNext("B") // prints (2, B) -lettersObserver.sendNext("C") // prints (2, C) -lettersObserver.sendCompleted() // prints "Completed" -``` - -The `combineLatestWith` operator works in the same way, but as an operator. - -[Interactive visualisation of the `combineLatest` operator.](http://neilpa.me/rac-marbles/#combineLatest) - -### Zipping - -The `zip` function joins values of two (or more) event streams pair-wise. The -elements of any Nth tuple correspond to the Nth elements of the input streams. - -That means the Nth value of the output stream cannot be sent until each input -has sent at least N values. - -```Swift -let (numbersSignal, numbersObserver) = Signal.pipe() -let (lettersSignal, lettersObserver) = Signal.pipe() - -let signal = zip(numbersSignal, lettersSignal) -signal.observeNext { next in print("Next: \(next)") } -signal.observeCompleted { print("Completed") } - -numbersObserver.sendNext(0) // nothing printed -numbersObserver.sendNext(1) // nothing printed -lettersObserver.sendNext("A") // prints (0, A) -numbersObserver.sendNext(2) // nothing printed -numbersObserver.sendCompleted() // nothing printed -lettersObserver.sendNext("B") // prints (1, B) -lettersObserver.sendNext("C") // prints (2, C) & "Completed" - -``` - -The `zipWith` operator works in the same way, but as an operator. - -[Interactive visualisation of the `zip` operator.](http://neilpa.me/rac-marbles/#zip) - -## Flattening producers - -The `flatten` operator transforms a stream-of-streams into a single stream - where values are forwarded from the inner stream in accordance with the provided `FlattenStrategy`. The flattened result becomes that of the outer stream type - i.e. a `SignalProducer`-of-`SignalProducer`s or `SignalProducer`-of-`Signal`s gets flattened to a `SignalProducer`, and likewise a `Signal`-of-`SignalProducer`s or `Signal`-of-`Signal`s gets flattened to a `Signal`. - -To understand why there are different strategies and how they compare to each other, take a look at this example and imagine the column offsets as time: - -```Swift -let values = [ -// imagine column offset as time -[ 1, 2, 3 ], - [ 4, 5, 6 ], - [ 7, 8 ], -] - -let merge = -[ 1, 4, 2, 7,5, 3,8,6 ] - -let concat = -[ 1, 2, 3,4, 5, 6,7, 8] - -let latest = -[ 1, 4, 7, 8 ] -``` - -Note, how the values interleave and which values are even included in the resulting array. - - -### Merging - -The `.Merge` strategy immediately forwards every value of the inner `SignalProducer`s to the outer `SignalProducer`. Any failure sent on the outer producer or any inner producer is immediately sent on the flattened producer and terminates it. - -```Swift -let (producerA, lettersObserver) = SignalProducer.buffer(5) -let (producerB, numbersObserver) = SignalProducer.buffer(5) -let (signal, observer) = SignalProducer, NoError>.buffer(5) - -signal.flatten(.Merge).startWithNext { next in print(next) } - -observer.sendNext(producerA) -observer.sendNext(producerB) -observer.sendCompleted() - -lettersObserver.sendNext("a") // prints "a" -numbersObserver.sendNext("1") // prints "1" -lettersObserver.sendNext("b") // prints "b" -numbersObserver.sendNext("2") // prints "2" -lettersObserver.sendNext("c") // prints "c" -numbersObserver.sendNext("3") // prints "3" -``` - -[Interactive visualisation of the `flatten(.Merge)` operator.](http://neilpa.me/rac-marbles/#merge) - -### Concatenating - -The `.Concat` strategy is used to serialize work of the inner `SignalProducer`s. The outer producer is started immediately. Each subsequent producer is not started until the preceeding one has completed. Failures are immediately forwarded to the flattened producer. - -```Swift -let (producerA, lettersObserver) = SignalProducer.buffer(5) -let (producerB, numbersObserver) = SignalProducer.buffer(5) -let (signal, observer) = SignalProducer, NoError>.buffer(5) - -signal.flatten(.Concat).startWithNext { next in print(next) } - -observer.sendNext(producerA) -observer.sendNext(producerB) -observer.sendCompleted() - -numbersObserver.sendNext("1") // nothing printed -lettersObserver.sendNext("a") // prints "a" -lettersObserver.sendNext("b") // prints "b" -numbersObserver.sendNext("2") // nothing printed -lettersObserver.sendNext("c") // prints "c" -lettersObserver.sendCompleted() // prints "1", "2" -numbersObserver.sendNext("3") // prints "3" -numbersObserver.sendCompleted() -``` - -[Interactive visualisation of the `flatten(.Concat)` operator.](http://neilpa.me/rac-marbles/#concat) - -### Switching to the latest - -The `.Latest` strategy forwards only values from the latest input `SignalProducer`. - -```Swift -let (producerA, observerA) = SignalProducer.buffer(5) -let (producerB, observerB) = SignalProducer.buffer(5) -let (producerC, observerC) = SignalProducer.buffer(5) -let (signal, observer) = SignalProducer, NoError>.buffer(5) - -signal.flatten(.Latest).startWithNext { next in print(next) } - -observer.sendNext(producerA) // nothing printed -observerC.sendNext("X") // nothing printed -observerA.sendNext("a") // prints "a" -observerB.sendNext("1") // nothing printed -observer.sendNext(producerB) // prints "1" -observerA.sendNext("b") // nothing printed -observerB.sendNext("2") // prints "2" -observerC.sendNext("Y") // nothing printed -observerA.sendNext("c") // nothing printed -observer.sendNext(producerC) // prints "X", "Y" -observerB.sendNext("3") // nothing printed -observerC.sendNext("Z") // prints "Z" -``` - -## Handling failures - -These operators are used to handle failures that might occur on an event stream. - -### Catching failures - -The `flatMapError` operator catches any failure that may occur on the input `SignalProducer`, then starts a new `SignalProducer` in its place. - -```Swift -let (producer, observer) = SignalProducer.buffer(5) -let error = NSError(domain: "domain", code: 0, userInfo: nil) - -producer - .flatMapError { _ in SignalProducer(value: "Default") } - .startWithNext { next in print(next) } - - -observer.sendNext("First") // prints "First" -observer.sendNext("Second") // prints "Second" -observer.sendFailed(error) // prints "Default" -``` - -### Retrying - -The `retry` operator will restart the original `SignalProducer` on failure up to `count` times. - -```Swift -var tries = 0 -let limit = 2 -let error = NSError(domain: "domain", code: 0, userInfo: nil) -let producer = SignalProducer { (observer, _) in - if tries++ < limit { - observer.sendFailed(error) - } else { - observer.sendNext("Success") - observer.sendCompleted() - } -} - -producer - .on(failed: {e in print("Failure")}) // prints "Failure" twice - .retry(2) - .start { event in - switch event { - case let .Next(next): - print(next) // prints "Success" - case let .Failed(error): - print("Failed: \(error)") - case .Completed: - print("Completed") - case .Interrupted: - print("Interrupted") - } - } -``` - -If the `SignalProducer` does not succeed after `count` tries, the resulting `SignalProducer` will fail. E.g., if `retry(1)` is used in the example above instead of `retry(2)`, `"Signal Failure"` will be printed instead of `"Success"`. - -### Mapping errors - -The `mapError` operator transforms the error of any failure in an event stream into a new error. - -```Swift -enum CustomError: String, ErrorType { - case Foo = "Foo" - case Bar = "Bar" - case Other = "Other" - - var nsError: NSError { - return NSError(domain: "CustomError.\(rawValue)", code: 0, userInfo: nil) - } - - var description: String { - return "\(rawValue) Error" - } -} - -let (signal, observer) = Signal.pipe() - -signal - .mapError { (error: NSError) -> CustomError in - switch error.domain { - case "com.example.foo": - return .Foo - case "com.example.bar": - return .Bar - default: - return .Other - } - } - .observeFailed { error in - print(error) - } - -observer.sendFailed(NSError(domain: "com.example.foo", code: 42, userInfo: nil)) // prints "Foo Error" -``` - -### Promote - -The `promoteErrors` operator promotes an event stream that does not generate failures into one that can. - -```Swift -let (numbersSignal, numbersObserver) = Signal.pipe() -let (lettersSignal, lettersObserver) = Signal.pipe() - -numbersSignal - .promoteErrors(NSError) - .combineLatestWith(lettersSignal) -``` - -The given stream will still not _actually_ generate failures, but this is useful -because some operators to [combine streams](#combining-event-streams) require -the inputs to have matching error types. - - -[Signals]: FrameworkOverview.md#signals -[Signal Producers]: FrameworkOverview.md#signal-producers -[Observation]: FrameworkOverview.md#observation - diff --git a/Documentation/DebuggingTechniques.md b/Documentation/DebuggingTechniques.md index d605844f87..778752a32c 100644 --- a/Documentation/DebuggingTechniques.md +++ b/Documentation/DebuggingTechniques.md @@ -2,6 +2,10 @@ This document lists debugging techniques and infrastructure helpful for debugging ReactiveCocoa applications. +#### Use of unresolved operator '<~' or not found in RAC 5 + +Since the split into ReactiveCocoa and ReactiveSwift, you'll need to `import ReactiveSwift` as well when using classes or operators that are implemented in ReactiveSwift. + #### Unscrambling Swift compiler errors Type inferrence can be a source of hard-to-debug compiler errors. There are two potential places to be wrong when type inferrence used: @@ -15,7 +19,7 @@ Below is an example of type-error scenario: ```swift SignalProducer(value:42) - .on(next: { answer in + .on(value: { answer in return _ }) .startWithCompleted { @@ -23,12 +27,11 @@ SignalProducer(value:42) } ``` -The code above will not compile with the following error on a `print` call `error: ambiguous reference to member 'print' -print("Completed.")`. To find the actual compile error, the chain needs to be broken apart. Add explicit definitions of closure types on each of the steps: +The code above will not compile with the following error on the `.startWithCompleted` call `error: cannot convert value of type 'Disposable' to closure result type '()'. To find the actual compile error, the chain needs to be broken apart. Add explicit definitions of closure types on each of the steps: ```swift let initialProducer = SignalProducer.init(value:42) -let sideEffectProducer = initialProducer.on(next: { (answer: Int) in +let sideEffectProducer = initialProducer.on(value: { (answer: Int) in return _ }) let disposable = sideEffectProducer.startWithCompleted { @@ -36,45 +39,15 @@ let disposable = sideEffectProducer.startWithCompleted { } ``` -The code above will not compile too, but with the error `error: cannot convert value of type '(_) -> _' to expected argument type '(Int -> ())?'` on definition of `on` closure. This gives enough of information to locate unexpected `return _` since `on` closure should not have any return value. - -#### Binding `DynamicProperty` with `<~` operator - -Using the `<~` operator to bind a `Signal` or a `SignalProducer` to a `DynamicProperty` can result in unexpected compiler errors. - -Below is an example of this scenario: - -```swift -let label = UILabel() -let property = MutableProperty("") - -DynamicProperty(object: label, keyPath: "text") <~ property.producer -``` - -This will often result in a compiler error: - -> error: binary operator '<~' cannot be applied to operands of type 'DynamicProperty' and 'SignalProducer' -DynamicProperty(object: label, keyPath: "text") <~ property.producer - -The reason is a limitation in the swift type checker - A `DynamicProperty` always has a type of `AnyObject?`, but the `<~` operator requires the values of both sides to have the same type, so the right side value would have to be `AnyObject?` as well, but usually a more concrete type is used (in this example `String`). - -Usually, the fix is as easy as adding a `.map{ $0 }`. - -```swift -DynamicProperty(object: label, keyPath: "text") <~ property.producer.map { $0 } -``` - -This allows the type checker to infer that `String` can be converted to `AnyProperty?` and thus, the binding succeeds. +The code above will not compile too, but with the error `error: cannot convert value of type '(Int) -> _' to expected argument type '((Int) -> Void)?'` on definition of `on` closure. This gives enough of information to locate unexpected `return _` since `on` closure should not have any return value. #### Debugging event streams As mentioned in the README, stream debugging can be quite difficut and tedious, so we provide the `logEvents` operator. In its simplest form: ```swift -let searchString = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) +let searchString = textField.reactive.continuousTextValues + .throttle(0.5, on: QueueScheduler.main) .logEvents() ``` @@ -98,31 +71,26 @@ func debugLog(identifier: String, event: String, fileName: String, functionName: You would then: ```swift -let searchString = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) +let searchString = textField.reactive.continuousTextValues + .throttle(0.5, on: QueueScheduler.main) .logEvents(logger: debugLog) ``` We also provide the `identifier` parameter. This is useful when you are debugging multiple streams and you don't want to get lost: ```swift -let searchString = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) +let searchString = textField.reactive.continuousTextValues + .throttle(0.5, on: QueueScheduler.main) .logEvents(identifier: "✨My awesome stream ✨") ``` -There also cases, specially with [hot signals][[Signals]], when there is simply too much output. For those, you can specify which events you are interested in: +There also cases, especially with [hot signals][Signal], when there is simply too much output. For those, you can specify which events you are interested in: ```swift -let searchString = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) - .logEvents(events:[.Disposed]) // This will happen when the `UITextField` is released +let searchString = textField.reactive.continuousTextValues + .throttle(0.5, on: QueueScheduler.main) + .logEvents(events: [.disposed]) ``` +[Signal]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Signal.swift diff --git a/Documentation/DesignGuidelines.md b/Documentation/DesignGuidelines.md deleted file mode 100644 index 897659d49c..0000000000 --- a/Documentation/DesignGuidelines.md +++ /dev/null @@ -1,530 +0,0 @@ -# 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 main types and concepts provided by RAC. - -**[The `Event` contract](#the-event-contract)** - - 1. [`Next`s provide values or indicate the occurrence of events](#nexts-provide-values-or-indicate-the-occurrence-of-events) - 1. [Failures behave like exceptions and propagate immediately](#failures-behave-like-exceptions-and-propagate-immediately) - 1. [Completion indicates success](#completion-indicates-success) - 1. [Interruption cancels outstanding work and usually propagates immediately](#interruption-cancels-outstanding-work-and-usually-propagates-immediately) - 1. [Events are serial](#events-are-serial) - 1. [Events cannot be sent recursively](#events-cannot-be-sent-recursively) - 1. [Events are sent synchronously by default](#events-are-sent-synchronously-by-default) - -**[The `Signal` contract](#the-signal-contract)** - - 1. [Signals start work when instantiated](#signals-start-work-when-instantiated) - 1. [Observing a signal does not have side effects](#observing-a-signal-does-not-have-side-effects) - 1. [All observers of a signal see the same events in the same order](#all-observers-of-a-signal-see-the-same-events-in-the-same-order) - 1. [A signal is alive as long as it is publicly reachable or is being observed](#a-signal-is-alive-as-long-as-it-is-publicly-reachable-or-is-being-observed) - 1. [Terminating events dispose of signal resources](#terminating-events-dispose-of-signal-resources) - -**[The `SignalProducer` contract](#the-signalproducer-contract)** - - 1. [Signal producers start work on demand by creating signals](#signal-producers-start-work-on-demand-by-creating-signals) - 1. [Each produced signal may send different events at different times](#each-produced-signal-may-send-different-events-at-different-times) - 1. [Signal operators can be lifted to apply to signal producers](#signal-operators-can-be-lifted-to-apply-to-signal-producers) - 1. [Disposing of a produced signal will interrupt it](#disposing-of-a-produced-signal-will-interrupt-it) - -**[Best practices](#best-practices)** - - 1. [Process only as many values as needed](#process-only-as-many-values-as-needed) - 1. [Observe events on a known scheduler](#observe-events-on-a-known-scheduler) - 1. [Switch schedulers in as few places as possible](#switch-schedulers-in-as-few-places-as-possible) - 1. [Capture side effects within signal producers](#capture-side-effects-within-signal-producers) - 1. [Share the side effects of a signal producer by sharing one produced signal](#share-the-side-effects-of-a-signal-producer-by-sharing-one-produced-signal) - 1. [Prefer managing lifetime with operators over explicit disposal](#prefer-managing-lifetime-with-operators-over-explicit-disposal) - -**[Implementing new operators](#implementing-new-operators)** - - 1. [Prefer writing operators that apply to both signals and producers](#prefer-writing-operators-that-apply-to-both-signals-and-producers) - 1. [Compose existing operators when possible](#compose-existing-operators-when-possible) - 1. [Forward failure and interruption events as soon as possible](#forward-failure-and-interruption-events-as-soon-as-possible) - 1. [Switch over `Event` values](#switch-over-event-values) - 1. [Avoid introducing concurrency](#avoid-introducing-concurrency) - 1. [Avoid blocking in operators](#avoid-blocking-in-operators) - -## The `Event` contract - -[Events][] are fundamental to ReactiveCocoa. [Signals][] and [signal producers][] both send -events, and may be collectively called “event streams.” - -Event streams must conform to the following grammar: - -``` -Next* (Interrupted | Failed | Completed)? -``` - -This states that an event stream consists of: - - 1. Any number of `Next` events - 1. Optionally followed by one terminating event, which is any of `Interrupted`, `Failed`, or `Completed` - -After a terminating event, no other events will be received. - -#### `Next`s provide values or indicate the occurrence of events - -`Next` events contain a payload known as the “value.” Only `Next` events are -said to have a value. Since an event stream can contain any number of `Next`s, -there are few restrictions on what those values can mean or be used for, except -that they must be of the same type. - -As an example, the value might represent an element from a collection, or -a progress update about some long-running operation. The value of a `Next` event -might even represent nothing at all—for example, it’s common to use a value type -of `()` to indicate that something happened, without being more specific about -what that something was. - -Most of the event stream [operators][] act upon `Next` events, as they represent the -“meaningful data” of a signal or producer. - -#### Failures behave like exceptions and propagate immediately - -`Failed` events indicate that something went wrong, and contain a concrete error -that indicates what happened. Failures are fatal, and propagate as quickly as -possible to the consumer for handling. - -Failures also behave like exceptions, in that they “skip” operators, terminating -them along the way. In other words, most [operators][] immediately stop doing -work when a failure is received, and then propagate the failure onward. This even applies to time-shifted operators, like [`delay`][delay]—which, despite its name, will forward any failures immediately. - -Consequently, failures should only be used to represent “abnormal” termination. If it is important to let operators (or consumers) finish their work, a `Next` -event describing the result might be more appropriate. - -If an event stream can _never_ fail, it should be parameterized with the -special [`NoError`][NoError] type, which statically guarantees that a `Failed` -event cannot be sent upon the stream. - -#### Completion indicates success - -An event stream sends `Completed` when the operation has completed successfully, -or to indicate that the stream has terminated normally. - -Many operators manipulate the `Completed` event to shorten or extend the -lifetime of an event stream. - -For example, [`take`][take] will complete after the specified number of values have -been received, thereby terminating the stream early. On the other hand, most -operators that accept multiple signals or producers will wait until _all_ of -them have completed before forwarding a `Completed` event, since a successful -outcome will usually depend on all the inputs. - -#### Interruption cancels outstanding work and usually propagates immediately - -An `Interrupted` event is sent when an event stream should cancel processing. -Interruption is somewhere between [success](#completion-indicates-success) -and [failure](#failures-behave-like-exceptions-and-propagate-immediately)—the -operation was not successful, because it did not get to finish, but it didn’t -necessarily “fail” either. - -Most [operators][] will propagate interruption immediately, but there are some -exceptions. For example, the [flattening operators][flatten] will ignore -`Interrupted` events that occur on the _inner_ producers, since the cancellation -of an inner operation should not necessarily cancel the larger unit of work. - -RAC will automatically send an `Interrupted` event upon [disposal][Disposables], but it can -also be sent manually if necessary. Additionally, [custom -operators](#implementing-new-operators) must make sure to forward interruption -events to the observer. - -#### Events are serial - -RAC guarantees that all events upon a stream will arrive serially. In other -words, it’s impossible for the observer of a signal or producer to receive -multiple `Event`s concurrently, even if the events are sent on multiple threads -simultaneously. - -This simplifies [operator][Operators] implementations and [observers][]. - -#### Events cannot be sent recursively - -Just like RAC guarantees that [events will not be received -concurrently](#events-are-serial), it also guarantees that they won’t be -received recursively. As a consequence, [operators][] and [observers][] _do not_ need to -be reentrant. - -If an event is sent upon a signal from a thread that is _already processing_ -a previous event from that signal, deadlock will result. This is because -recursive signals are usually programmer error, and the determinacy of -a deadlock is preferable to nondeterministic race conditions. - -When a recursive signal is explicitly desired, the recursive event should be -time-shifted, with an operator like [`delay`][delay], to ensure that it isn’t sent from -an already-running event handler. - -#### Events are sent synchronously by default - -RAC does not implicitly introduce concurrency or asynchrony. [Operators][] that -accept a [scheduler][Schedulers] may, but they must be explicitly invoked by the consumer of -the framework. - -A “vanilla” signal or producer will send all of its events synchronously by -default, meaning that the [observer][Observers] will be synchronously invoked for each event -as it is sent, and that the underlying work will not resume until the event -handler finishes. - -This is similar to how `NSNotificationCenter` or `UIControl` events are -distributed. - -## The `Signal` contract - -A [signal][Signals] is a stream of values that obeys [the `Event` contract](#the-event-contract). - -`Signal` is a reference type, because each signal has identity—in other words, each -signal has its own lifetime, and may eventually terminate. Once terminated, -a signal cannot be restarted. - -#### Signals start work when instantiated - -[`Signal.init`][Signal.init] immediately executes the generator closure that is passed to it. -This means that side effects may occur even before the initializer returns. - -It is also possible to send [events][] before the initializer returns. However, -since it is impossible for any [observers][] to be attached at this point, any -events sent this way cannot be received. - -#### Observing a signal does not have side effects - -The work associated with a `Signal` does not start or stop when [observers][] are -added or removed, so the [`observe`][observe] method (or the cancellation thereof) never -has side effects. - -A signal’s side effects can only be stopped through [a terminating event](#signals-are-retained-until-a-terminating-event-occurs), or by a silent disposal at the point that [the signal is neither publicly reachable nor being observed](#a-signal-is-alive-as-long-as-it-is-publicly-reachable-or-is-being-observed). - -#### All observers of a signal see the same events in the same order - -Because [observation does not have side -effects](#observing-a-signal-does-not-have-side-effects), a `Signal` never -customizes events for different [observers][]. When an event is sent upon a signal, -it will be [synchronously](#events-are-sent-synchronously-by-default) -distributed to all observers that are attached at that time, much like -how `NSNotificationCenter` sends notifications. - -In other words, there are not different event “timelines” per observer. All -observers effectively see the same stream of events. - -There is one exception to this rule: adding an observer to a signal _after_ it -has already terminated will result in exactly one -[`Interrupted`](#interruption-cancels-outstanding-work-and-usually-propagates-immediately) -event sent to that specific observer. - -#### A signal is alive as long as it is publicly reachable or is being observed - -A `Signal` must be publicly retained for attaching new observers, but not -necessarily for keeping the stream of events alive. Moreover, a `Signal` retains -itself as long as there is still an active observer. - -In other words, if a `Signal` is neither publicly retained nor being observed, -it would dispose of the signal resources silently. - -Note that the input observer of a signal does not retain the signal itself. - -Long-running side effects are recommended to be modeled as an observer to the -signal. - -#### Terminating events dispose of signal resources - -When a terminating [event][Events] is sent along a `Signal`, all [observers][] will be -released, and any resources being used to generate events should be disposed of. - -The easiest way to ensure proper resource cleanup is to return a [disposable][Disposables] -from the generator closure, which will be disposed of when termination occurs. -The disposable should be responsible for releasing memory, closing file handles, -canceling network requests, or anything else that may have been associated with -the work being performed. - -## The `SignalProducer` contract - -A [signal producer][Signal Producers] is like a “recipe” for creating -[signals][]. Signal producers do not do anything by themselves—[work begins only -when a signal is produced](#signal-producers-start-work-on-demand-by-creating-signals). - -Since a signal producer is just a declaration of _how_ to create signals, it is -a value type, and has no memory management to speak of. - -#### Signal producers start work on demand by creating signals - -The [`start`][start] and [`startWithSignal`][startWithSignal] methods each -produce a `Signal` (implicitly and explicitly, respectively). After -instantiating the signal, the closure that was passed to -[`SignalProducer.init`][SignalProducer.init] will be executed, to start the flow -of [events][] after any observers have been attached. - -Although the producer itself is not _really_ responsible for the execution of -work, it’s common to speak of “starting” and “canceling” a producer. These terms -refer to producing a `Signal` that will start work, and [disposing of that -signal](#disposing-of-a-produced-signal-will-interrupt-it) to stop work. - -A producer can be started any number of times (including zero), and the work -associated with it will execute exactly that many times as well. - -#### Each produced signal may send different events at different times - -Because signal producers [start work on -demand](#signal-producers-start-work-on-demand-by-creating-signals), there may -be different [observers][] associated with each execution, and those observers -may see completely different [event][Events] timelines. - -In other words, events are generated from scratch for each time the producer is -started, and can be completely different (or in a completely different order) -from other times the producer is started. - -Nonetheless, each execution of a signal producer will follow [the `Event` -contract](#the-event-contract). - -#### Signal operators can be lifted to apply to signal producers - -Due to the relationship between signals and signal producers, it is possible to -automatically promote any [operators][] over one or more `Signal`s to apply to -the same number of `SignalProducer`s instead, using the [`lift`][lift] method. - -`lift` will apply the behavior of the specified operator to each `Signal` that -is [created when the signal producer is started](#signal-producers-start-work-on-demand-by-creating-signals). - -#### Disposing of a produced signal will interrupt it - -When a producer is started using the [`start`][start] or -[`startWithSignal`][startWithSignal] methods, a [`Disposable`][Disposables] is -automatically created and passed back. - -Disposing of this object will -[interrupt](#interruption-cancels-outstanding-work-and-usually-propagates-immediately) -the produced `Signal`, thereby canceling outstanding work and sending an -`Interrupted` [event][Events] to all [observers][], and will also dispose of -everything added to the [`CompositeDisposable`][CompositeDisposable] in -[SignalProducer.init]. - -Note that disposing of one produced `Signal` will not affect other signals created -by the same `SignalProducer`. - -## 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. - -#### Process only as many values as needed - -Keeping an event stream alive longer than necessary can waste CPU and memory, as -unnecessary work is performed for results that will never be used. - -If only a certain number of values or certain number of time is required from -a [signal][Signals] or [producer][Signal Producers], operators like -[`take`][take] or [`takeUntil`][takeUntil] can be used to -automatically complete the stream once a certain condition is fulfilled. - -The benefit is exponential, too, as this will terminate dependent operators -sooner, potentially saving a significant amount of work. - -#### Observe events on a known scheduler - -When receiving a [signal][Signals] or [producer][Signal Producers] from unknown -code, it can be difficult to know which thread [events][] will arrive upon. Although -events are [guaranteed to be serial](#events-are-serial), sometimes stronger -guarantees are needed, like when performing UI updates (which must occur on the -main thread). - -Whenever such a guarantee is important, the [`observeOn`][observeOn] -[operator][Operators] should be used to force events to be received upon -a specific [scheduler][Schedulers]. - -#### Switch schedulers in as few places as possible - -Notwithstanding the [above](#observe-events-on-a-known-scheduler), [events][] -should only be delivered to a specific [scheduler][Schedulers] when absolutely -necessary. Switching schedulers can introduce unnecessary delays and cause an -increase in CPU load. - -Generally, [`observeOn`][observeOn] should only be used right before observing -the [signal][Signals], starting the [producer][Signal Producers], or binding to -a [property][Properties]. This ensures that events arrive on the expected -scheduler, without introducing multiple thread hops before their arrival. - -#### Capture side effects within signal producers - -Because [signal producers start work on -demand](#signal-producers-start-work-on-demand-by-creating-signals), any -functions or methods that return a [signal producer][Signal Producers] should -make sure that side effects are captured _within_ the producer itself, instead -of being part of the function or method call. - -For example, a function like this: - -```swift -func search(text: String) -> SignalProducer -``` - -… should _not_ immediately start a search. - -Instead, the returned producer should execute the search once for every time -that it is started. This also means that if the producer is never started, -a search will never have to be performed either. - -#### Share the side effects of a signal producer by sharing one produced signal - -If multiple [observers][] are interested in the results of a [signal -producer][Signal Producers], calling [`start`][start] once for each observer -means that the work associated with the producer will [execute that many -times](#signal-producers-start-work-on-demand-by-creating-signals) and [may not -generate the same results](#each-produced-signal-may-send-different-events-at-different-times). - -If: - - 1. the observers need to receive the exact same results - 1. the observers know about each other, or - 1. the code starting the producer knows about each observer - -… it may be more appropriate to start the producer _just once_, and share the -results of that one [signal][Signals] to all observers, by attaching them within -the closure passed to the [`startWithSignal`][startWithSignal] method. - -#### Prefer managing lifetime with operators over explicit disposal - -Although the [disposable][Disposables] returned from [`start`][start] makes -canceling a [signal producer][Signal Producers] really easy, explicit use of -disposables can quickly lead to a rat's nest of resource management and cleanup -code. - -There are almost always higher-level [operators][] that can be used instead of manual -disposal: - - * [`take`][take] can be used to automatically terminate a stream once a certain - number of values have been received. - * [`takeUntil`][takeUntil] can be used to automatically terminate - a [signal][Signals] or producer when an event occurs (for example, when - a “Cancel” button is pressed in the UI). - * [Properties][] and the `<~` operator can be used to “bind” the result of - a signal or producer, until termination or until the property is deallocated. - This can replace a manual observation that sets a value somewhere. - -## Implementing new operators - -RAC provides a long list of built-in [operators][] 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. It may also help to look at the implementations of -existing `Signal` and `SignalProducer` operators for reference points. - -#### Prefer writing operators that apply to both signals and producers - -Since any [signal operator can apply to signal -producers](#signal-operators-can-be-lifted-to-apply-to-signal-producers), -writing custom operators in terms of [`Signal`][Signals] means that -[`SignalProducer`][Signal Producers] will get it “for free.” - -Even if the caller only needs to apply the new operator to signal producers at -first, this generality can save time and effort in the future. - -Of course, some capabilities _require_ producers (for example, any retrying or -repeating), so it may not always be possible to write a signal-based version -instead. - -#### 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. - -#### Forward failure and interruption events as soon as possible - -Unless an operator is specifically built to handle -[failures](#failures-behave-like-exceptions-and-propagate-immediately) and -[interruption](#interruption-cancels-outstanding-work-and-usually-propagates-immedaitely) -in a custom way, it should propagate those events to the observer as soon as -possible, to ensure that their semantics are honored. - -#### Switch over `Event` values - -Instead of using [`start(failed:completed:interrupted:next:)`][start] or -[`observe(failed:completed:interrupted:next:)`][observe], create your own -[observer][Observers] to process raw [`Event`][Events] values, and use -a `switch` statement to determine the event type. - -For example: - -```swift -producer.start { event in - switch event { - case let .Next(value): - print("Next event: \(value)") - - case let .Failed(error): - print("Failed event: \(error)") - - case .Completed: - print("Completed event") - - case .Interrupted: - print("Interrupted event") - } -} -``` - -Since the compiler will generate a warning if the `switch` is missing any case, -this prevents mistakes in a custom operator’s event handling. - -#### 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 [observe events on a specific -scheduler](#observe-events-on-a-known-scheduler), and RAC offers built-in ways -to parallelize work, so custom operators don’t need to be concerned with it. - -#### Avoid blocking in operators - -Signal or producer operators should return a new signal or producer -(respectively) as quickly as possible. Any work that the operator needs to -perform should be part of the event handling logic, _not_ part of the operator -invocation itself. - -This guideline can be safely ignored when the purpose of an operator is to -synchronously retrieve one or more values from a stream, like `single()` or -`wait()`. - -[CompositeDisposable]: ../ReactiveCocoa/Swift/Disposable.swift -[Disposables]: FrameworkOverview.md#disposables -[Events]: FrameworkOverview.md#events -[Framework Overview]: FrameworkOverview.md -[NoError]: ../ReactiveCocoa/Swift/Errors.swift -[Observers]: FrameworkOverview.md#observers -[Operators]: BasicOperators.md -[Properties]: FrameworkOverview.md#properties -[Schedulers]: FrameworkOverview.md#schedulers -[Signal Producers]: FrameworkOverview.md#signal-producers -[Signal.init]: ../ReactiveCocoa/Swift/Signal.swift -[Signal.pipe]: ../ReactiveCocoa/Swift/Signal.swift -[SignalProducer.init]: ../ReactiveCocoa/Swift/SignalProducer.swift -[Signals]: FrameworkOverview.md#signals -[delay]: ../ReactiveCocoa/Swift/Signal.swift -[flatten]: BasicOperators.md#flattening-producers -[lift]: ../ReactiveCocoa/Swift/SignalProducer.swift -[observe]: ../ReactiveCocoa/Swift/Signal.swift -[observeOn]: ../ReactiveCocoa/Swift/Signal.swift -[start]: ../ReactiveCocoa/Swift/SignalProducer.swift -[startWithSignal]: ../ReactiveCocoa/Swift/SignalProducer.swift -[take]: ../ReactiveCocoa/Swift/Signal.swift -[takeUntil]: ../ReactiveCocoa/Swift/Signal.swift diff --git a/Documentation/DocumentingCode.md b/Documentation/DocumentingCode.md index 059953020a..c893660a7a 100644 --- a/Documentation/DocumentingCode.md +++ b/Documentation/DocumentingCode.md @@ -130,3 +130,14 @@ init(count: Int) /// DON'T: /// Does something magical and returns pixie dust from `self`. ``` + +- Document return value on the same line as `return:` delimiter. + +``` +/// DO: +/// - returns: A signal with mapped value over given function +/// +/// DON'T: +/// - returns: +/// A signal with mapped value over given function +``` diff --git a/Documentation/FrameworkOverview.md b/Documentation/FrameworkOverview.md deleted file mode 100644 index 10211955b5..0000000000 --- a/Documentation/FrameworkOverview.md +++ /dev/null @@ -1,210 +0,0 @@ -# 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][]. - -## Events - -An **event**, represented by the [`Event`][Event] type, is the formalized representation -of the fact that _something has happened_. In ReactiveCocoa, events are the centerpiece -of communication. An event might represent the press of a button, a piece -of information received from an API, the occurrence of an error, or the completion -of a long-running operation. In any case, something generates the events and sends them over a -[signal](#signals) to any number of [observers](#observers). - -`Event` is an enumerated type representing either a value or one of three -terminal events: - - * The `Next` event provides a new value from the source. - * The `Failed` event indicates that an error occurred before the signal could - finish. Events are parameterized by an `ErrorType`, which determines the kind - of failure that’s permitted to appear in the event. If a failure is not - permitted, the event can use type `NoError` to prevent any from being - provided. - * The `Completed` event indicates that the signal finished successfully, and - that no more values will be sent by the source. - * The `Interrupted` event indicates that the signal has terminated due to - cancellation, meaning that the operation was neither successful nor - unsuccessful. - -## Signals - -A **signal**, represented by the [`Signal`][Signal] type, is any series of [events](#events) -over time that can be observed. - -Signals are generally used to represent event streams that are already “in progress”, -like notifications, user input, etc. As work is performed or data is received, -events are _sent_ on the signal, which pushes them out to any observers. -All observers see the events at the same time. - -Users must [observe](#observers) a signal in order to access its events. -Observing a signal does not trigger any side effects. In other words, -signals are entirely producer-driven and push-based, and consumers (observers) -cannot have any effect on their lifetime. While observing a signal, the user -can only evaluate the events in the same order as they are sent on the signal. There -is no random access to values of a signal. - -Signals can be manipulated by applying [primitives][BasicOperators] to them. -Typical primitives to manipulate a single signal like `filter`, `map` and -`reduce` are available, as well as primitives to manipulate multiple signals -at once (`zip`). Primitives operate only on the `Next` events of a signal. - -The lifetime of a signal consists of any number of `Next` events, followed by -one terminating event, which may be any one of `Failed`, `Completed`, or -`Interrupted` (but not a combination). -Terminating events are not included in the signal’s values—they must be -handled specially. - -### Pipes - -A **pipe**, created by `Signal.pipe()`, is a [signal](#signals) -that can be manually controlled. - -The method returns a [signal](#signals) and an [observer](#observers). -The signal can be controlled by sending events to the observer. This -can be 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 the observer. Meanwhile, the signal -can be returned, hiding the implementation detail of the callbacks. - -## Signal Producers - -A **signal producer**, represented by the [`SignalProducer`][SignalProducer] type, creates -[signals](#signals) and performs side effects. - -They can be used to represent operations or tasks, like network -requests, where each invocation of `start()` will create a new underlying -operation, and allow the caller to observe the result(s). The -`startWithSignal()` variant gives access to the produced signal, allowing it to -be observed multiple times if desired. - -Because of the behavior of `start()`, each signal created from the same -producer may see a different ordering or version of events, or the stream might -even be completely different! Unlike a plain signal, no work is started (and -thus no events are generated) until an observer is attached, and the work is -restarted anew for each additional observer. - -Starting a signal producer returns a [disposable](#disposables) that can be used to -interrupt/cancel the work associated with the produced signal. - -Just like signals, signal producers can also be manipulated via primitives -like `map`, `filter`, etc. -Every signal primitive can be “lifted” to operate upon signal producers instead, -using the `lift` method. -Furthermore, there are additional primitives that control _when_ and _how_ work -is started—for example, `times`. - -### Buffers - -A **buffer**, created by `SignalProducer.buffer()`, is a (optionally bounded) -queue for [events](#events) that replays those events when new -[signals](#signals) are created from the producer. - -Similar to a [pipe](#pipes), the method returns an [observer](#observers). -Events sent to this observer will be added to the queue. If the buffer is already -at capacity when a new value arrives, the earliest (oldest) value will be -dropped to make room for it. - -## Observers - -An **observer** is anything that is waiting or capable of waiting for [events](#events) -from a [signal](#signals). Within RAC, an observer is represented as -an [`Observer`][Observer] that accepts [`Event`][Event] values. - -Observers can be implicitly created by using the callback-based versions of the -`Signal.observe` or `SignalProducer.start` methods. - -## Actions - -An **action**, represented by the [`Action`][Action] type, will do some work when -executed with an input. While executing, zero or more output values and/or a -failure may be generated. - -Actions are useful for performing side-effecting work upon user interaction, like when a button is -clicked. Actions can also be automatically disabled based on a [property](#properties), and this -disabled state can be represented in a UI by disabling any controls associated -with the action. - -For interaction with `NSControl` or `UIControl`, RAC provides the -[`CocoaAction`][CocoaAction] type for bridging actions to Objective-C. - -## Properties - -A **property**, represented by the [`PropertyType`][Property] protocol, -stores a value and notifies observers about future changes to that value. - -The current value of a property can be obtained from the `value` getter. The -`producer` getter returns a [signal producer](#signal-producers) that will send -the property’s current value, followed by all changes over time. - -The `<~` operator can be used to bind properties in different ways. Note that in -all cases, the target has to be a [`MutablePropertyType`][Property]. - -* `property <~ signal` binds a [signal](#signals) to the property, updating the - property’s value to the latest value sent by the signal. -* `property <~ producer` starts the given [signal producer](#signal-producers), - and binds the property’s value to the latest value sent on the started signal. -* `property <~ otherProperty` binds one property to another, so that the destination - property’s value is updated whenever the source property is updated. - -The [`DynamicProperty`][Property] type can be used to bridge to Objective-C APIs -that require Key-Value Coding (KVC) or Key-Value Observing (KVO), like -`NSOperation`. Note that most AppKit and UIKit properties do _not_ support KVO, -so their changes should be observed through other mechanisms. -[`MutableProperty`][Property] should be preferred over dynamic properties -whenever possible! - -## Disposables - -A **disposable**, represented by the [`Disposable`][Disposable] protocol, is a mechanism -for memory management and cancellation. - -When starting a [signal producer](#signal-producers), a disposable will be returned. -This disposable can be used by the caller to cancel the work that has been started -(e.g. background processing, network requests, etc.), clean up all temporary -resources, then send a final `Interrupted` event upon the particular -[signal](#signals) that was created. - -Observing a [signal](#signals) may also return a disposable. Disposing it will -prevent the observer from receiving any future events from that signal, but it -will not have any effect on the signal itself. - -For more information about cancellation, see the RAC [Design Guidelines][]. - -## Schedulers - -A **scheduler**, represented by the [`SchedulerType`][Scheduler] protocol, is a -serial execution queue to perform work or deliver results upon. - -[Signals](#signals) and [signal producers](#signal-producers) can be ordered to -deliver events on a specific scheduler. [Signal producers](#signal-producers) -can additionally be ordered to start their work on a specific scheduler. - -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`][Scheduler], schedulers do not -offer synchronous execution. This helps avoid deadlocks, and encourages the use -of [signal and signal producer primitives][BasicOperators] instead of blocking work. - -Schedulers are also somewhat similar to `NSOperationQueue`, but schedulers -do not allow tasks to be reordered or depend on one another. - - -[Design Guidelines]: DesignGuidelines.md -[BasicOperators]: BasicOperators.md -[README]: ../README.md -[Signal]: ../ReactiveCocoa/Swift/Signal.swift -[SignalProducer]: ../ReactiveCocoa/Swift/SignalProducer.swift -[Action]: ../ReactiveCocoa/Swift/Action.swift -[CocoaAction]: ../ReactiveCocoa/Swift/CocoaAction.swift -[Disposable]: ../ReactiveCocoa/Swift/Disposable.swift -[Scheduler]: ../ReactiveCocoa/Swift/Scheduler.swift -[Property]: ../ReactiveCocoa/Swift/Property.swift -[Event]: ../ReactiveCocoa/Swift/Event.swift -[Observer]: ../ReactiveCocoa/Swift/Observer.swift diff --git a/Documentation/Legacy/BasicOperators.md b/Documentation/Legacy/BasicOperators.md deleted file mode 100644 index 5e4e1f3bab..0000000000 --- a/Documentation/Legacy/BasicOperators.md +++ /dev/null @@ -1,364 +0,0 @@ -# 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 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 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 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]: ../../ReactiveCocoa/Objective-C/RACSequence.h -[RACSignal]: ../../ReactiveCocoa/Objective-C/RACSignal.h -[RACSignal+Operations]: ../../ReactiveCocoa/Objective-C/RACSignal+Operations.h -[RACStream]: ../../ReactiveCocoa/Objective-C/RACStream.h -[Sequences]: FrameworkOverview.md#sequences -[Signals]: FrameworkOverview.md#signals -[Streams]: FrameworkOverview.md#streams -[Subscription]: FrameworkOverview.md#subscription diff --git a/Documentation/Legacy/DesignGuidelines.md b/Documentation/Legacy/DesignGuidelines.md deleted file mode 100644 index 8198f73e69..0000000000 --- a/Documentation/Legacy/DesignGuidelines.md +++ /dev/null @@ -1,751 +0,0 @@ -# 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 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 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 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 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]: ../../ReactiveCocoa/Objective-C/NSObject+RACLifting.h -[NSObject+RACSelectorSignal]: ../../ReactiveCocoa/Objective-C/NSObject+RACSelectorSignal.h -[RAC]: ../../ReactiveCocoa/Objective-C/RACSubscriptingAssignmentTrampoline.h -[RACChannelTo]: ../../ReactiveCocoa/Objective-C/RACKVOChannel.h -[RACCommand]: ../../ReactiveCocoa/Objective-C/RACCommand.h -[RACDisposable]: ../../ReactiveCocoa/Objective-C/RACDisposable.h -[RACEvent]: ../../ReactiveCocoa/Objective-C/RACEvent.h -[RACMulticastConnection]: ../../ReactiveCocoa/Objective-C/RACMulticastConnection.h -[RACObserve]: ../../ReactiveCocoa/Objective-C/NSObject+RACPropertySubscribing.h -[RACScheduler]: ../../ReactiveCocoa/Objective-C/RACScheduler.h -[RACSequence]: ../../ReactiveCocoa/Objective-C/RACSequence.h -[RACSignal]: ../../ReactiveCocoa/Objective-C/RACSignal.h -[RACSignal+Operations]: ../../ReactiveCocoa/Objective-C/RACSignal+Operations.h -[RACStream]: ../../ReactiveCocoa/Objective-C/RACStream.h -[RACSubscriber]: ../../ReactiveCocoa/Objective-C/RACSubscriber.h -[Subjects]: FrameworkOverview.md#subjects -[Parallelizing Independent Work]: ../README.md#parallelizing-independent-work diff --git a/Documentation/Legacy/FrameworkOverview.md b/Documentation/Legacy/FrameworkOverview.md deleted file mode 100644 index ed5b88c0d8..0000000000 --- a/Documentation/Legacy/FrameworkOverview.md +++ /dev/null @@ -1,203 +0,0 @@ -# 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]. - -[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]: https://downloads.haskell.org/~ghc/latest/docs/html/libraries/Data-List.html -[Memory Management]: MemoryManagement.md -[monads]: http://en.wikipedia.org/wiki/Monad_(functional_programming) -[Monoid]: http://downloads.haskell.org/~ghc/latest/docs/html/libraries/Data-Monoid.html -[MonadZip]: http://downloads.haskell.org/~ghc/latest/docs/html/libraries/Control-Monad-Zip.html -[NSButton+RACCommandSupport]: ../../ReactiveCocoa/Objective-C/NSButton+RACCommandSupport.h -[RACCommand]: ../../ReactiveCocoa/Objective-C/RACCommand.h -[RACDisposable]: ../../ReactiveCocoa/Objective-C/RACDisposable.h -[RACEvent]: ../../ReactiveCocoa/Objective-C/RACEvent.h -[RACMulticastConnection]: ../../ReactiveCocoa/Objective-C/RACMulticastConnection.h -[RACReplaySubject]: ../../ReactiveCocoa/Objective-C/RACReplaySubject.h -[RACScheduler]: ../../ReactiveCocoa/Objective-C/RACScheduler.h -[RACSequence]: ../../ReactiveCocoa/Objective-C/RACSequence.h -[RACSignal]: ../../ReactiveCocoa/Objective-C/RACSignal.h -[RACSignal+Operations]: ../../ReactiveCocoa/Objective-C/RACSignal+Operations.h -[RACStream]: ../../ReactiveCocoa/Objective-C/RACStream.h -[RACSubject]: ../../ReactiveCocoa/Objective-C/RACSubject.h -[RACSubscriber]: ../../ReactiveCocoa/Objective-C/RACSubscriber.h -[RACTuple]: ../../ReactiveCocoa/Objective-C/RACTuple.h -[RACUnit]: ../../ReactiveCocoa/Objective-C/RACUnit.h -[README]: README.md -[seq]: http://clojure.org/sequences diff --git a/Documentation/Legacy/MemoryManagement.md b/Documentation/Legacy/MemoryManagement.md deleted file mode 100644 index e442b1f6e0..0000000000 --- a/Documentation/Legacy/MemoryManagement.md +++ /dev/null @@ -1,139 +0,0 @@ -# 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/Legacy/README.md b/Documentation/Legacy/README.md deleted file mode 100644 index 99892c42ca..0000000000 --- a/Documentation/Legacy/README.md +++ /dev/null @@ -1,552 +0,0 @@ -# ReactiveCocoa - -_NOTE: This is legacy introduction to the Objective-C ReactiveCocoa. For the -updated version that uses Swift please see the main [README][]_ - -ReactiveCocoa (RAC) is an Objective-C framework inspired by [Functional Reactive -Programming][]. It provides APIs for **composing and transforming streams of -values**. - -If you're already familiar with functional reactive programming or know the basic -premise of ReactiveCocoa, check out the other documentation in this folder for a -framework overview and more in-depth information about how it all works in practice. - -## New to 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](../../ReactiveCocoa/Objective-C) - 1. Previously answered [Stack Overflow](https://github.com/ReactiveCocoa/ReactiveCocoa/wiki) - questions and [GitHub issues](https://github.com/ReactiveCocoa/ReactiveCocoa/issues?labels=question&state=closed) - 1. The rest of this folder - 1. [Functional Reactive Programming on iOS](https://leanpub.com/iosfrp/) - (eBook) - -If you have any further questions, please feel free to [file an issue](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/new). - -## Introduction - -ReactiveCocoa is inspired by [functional reactive -programming](http://blog.maybeapps.com/post/42894317939/input-and-output). -Rather than using mutable variables which are replaced and modified in-place, -RAC provides signals (represented by `RACSignal`) that capture present and -future values. - -By chaining, combining, and reacting to signals, software can be written -declaratively, without the need for code that continually observes and updates -values. - -For example, a text field can be bound to the latest time, even as it changes, -instead of using additional code that watches the clock and updates the -text field every second. It works much like KVO, but with blocks instead of -overriding `-observeValueForKeyPath:ofObject:change:context:`. - -Signals can also represent asynchronous operations, much like [futures and -promises][]. This greatly simplifies asynchronous software, including networking -code. - -One of the major advantages of RAC is that it provides a single, unified -approach to dealing with asynchronous behaviors, including delegate methods, -callback blocks, target-action mechanisms, notifications, and KVO. - -Here's a simple example: - -```objc -// When self.username changes, logs the new name to the console. -// -// RACObserve(self, username) creates a new RACSignal that sends the current -// value of self.username, then the new value whenever it changes. -// -subscribeNext: will execute the block whenever the signal sends a value. -[RACObserve(self, username) subscribeNext:^(NSString *newName) { - NSLog(@"%@", newName); -}]; -``` - -But unlike KVO notifications, signals can be chained together and operated on: - -```objc -// Only logs names that starts with "j". -// -// -filter returns a new RACSignal that only sends a new value when its block -// returns YES. -[[RACObserve(self, username) - filter:^(NSString *newName) { - return [newName hasPrefix:@"j"]; - }] - subscribeNext:^(NSString *newName) { - NSLog(@"%@", newName); - }]; -``` - -Signals can also be used to derive state. Instead of observing properties and -setting other properties in response to the new values, RAC makes it possible to -express properties in terms of signals and operations: - -```objc -// Creates a one-way binding so that self.createEnabled will be -// true whenever self.password and self.passwordConfirmation -// are equal. -// -// RAC() is a macro that makes the binding look nicer. -// -// +combineLatest:reduce: takes an array of signals, executes the block with the -// latest value from each signal whenever any of them changes, and returns a new -// RACSignal that sends the return value of that block as values. -RAC(self, createEnabled) = [RACSignal - combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] - reduce:^(NSString *password, NSString *passwordConfirm) { - return @([passwordConfirm isEqualToString:password]); - }]; -``` - -Signals can be built on any stream of values over time, not just KVO. For -example, they can also represent button presses: - -```objc -// Logs a message whenever the button is pressed. -// -// RACCommand creates signals to represent UI actions. Each signal can -// represent a button press, for example, and have additional work associated -// with it. -// -// -rac_command is an addition to NSButton. The button will send itself on that -// command whenever it's pressed. -self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { - NSLog(@"button was pressed!"); - return [RACSignal empty]; -}]; -``` - -Or asynchronous network operations: - -```objc -// Hooks up a "Log in" button to log in over the network. -// -// This block will be run whenever the login command is executed, starting -// the login process. -self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) { - // The hypothetical -logIn method returns a signal that sends a value when - // the network request finishes. - return [client logIn]; -}]; - -// -executionSignals returns a signal that includes the signals returned from -// the above block, one for each time the command is executed. -[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) { - // Log a message whenever we log in successfully. - [loginSignal subscribeCompleted:^{ - NSLog(@"Logged in successfully!"); - }]; -}]; - -// Executes the login command when the button is pressed. -self.loginButton.rac_command = self.loginCommand; -``` - -Signals can also represent timers, other UI events, or anything else that -changes over time. - -Using signals for asynchronous operations makes it possible to build up more -complex behavior by chaining and transforming those signals. Work can easily be -triggered after a group of operations completes: - -```objc -// Performs 2 network operations and logs a message to the console when they are -// both completed. -// -// +merge: takes an array of signals and returns a new RACSignal that passes -// through the values of all of the signals and completes when all of the -// signals complete. -// -// -subscribeCompleted: will execute the block when the signal completes. -[[RACSignal - merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] - subscribeCompleted:^{ - NSLog(@"They're both done!"); - }]; -``` - -Signals can be chained to sequentially execute asynchronous operations, instead -of nesting callbacks with blocks. This is similar to how [futures and promises][] -are usually used: - -```objc -// Logs in the user, then loads any cached messages, then fetches the remaining -// messages from the server. After that's all done, logs a message to the -// console. -// -// The hypothetical -logInUser methods returns a signal that completes after -// logging in. -// -// -flattenMap: will execute its block whenever the signal sends a value, and -// returns a new RACSignal that merges all of the signals returned from the block -// into a single signal. -[[[[client - logInUser] - flattenMap:^(User *user) { - // Return a signal that loads cached messages for the user. - return [client loadCachedMessagesForUser:user]; - }] - flattenMap:^(NSArray *messages) { - // Return a signal that fetches any remaining messages. - return [client fetchMessagesAfterMessage:messages.lastObject]; - }] - subscribeNext:^(NSArray *newMessages) { - NSLog(@"New messages: %@", newMessages); - } completed:^{ - NSLog(@"Fetched all messages."); - }]; -``` - -RAC even makes it easy to bind to the result of an asynchronous operation: - -```objc -// Creates a one-way binding so that self.imageView.image will be set as the user's -// avatar as soon as it's downloaded. -// -// The hypothetical -fetchUserWithUsername: method returns a signal which sends -// the user. -// -// -deliverOn: creates new signals that will do their work on other queues. In -// this example, it's used to move work to a background queue and then back to the main thread. -// -// -map: calls its block with each user that's fetched and returns a new -// RACSignal that sends values returned from the block. -RAC(self.imageView, image) = [[[[client - fetchUserWithUsername:@"joshaber"] - deliverOn:[RACScheduler scheduler]] - map:^(User *user) { - // Download the avatar (this is done on a background queue). - return [[NSImage alloc] initWithContentsOfURL:user.avatarURL]; - }] - // Now the assignment will be done on the main thread. - deliverOn:RACScheduler.mainThreadScheduler]; -``` - -That demonstrates some of what RAC can do, but it doesn't demonstrate why RAC is -so powerful. It's hard to appreciate RAC from README-sized examples, but it -makes it possible to write code with less state, less boilerplate, better code -locality, and better expression of intent. - -For more sample code, check out [C-41][] or [GroceryList][], which are real iOS -apps written using ReactiveCocoa. Additional information about RAC can be found -in this 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 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.8+ and iOS 8.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 `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/Specs/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. - -## More Info - -ReactiveCocoa is inspired by .NET's [Reactive -Extensions](http://msdn.microsoft.com/en-us/data/gg577609) (Rx). Most of the -principles of Rx apply to RAC as well. There are some really good Rx resources -out there: - -* [Reactive Extensions MSDN entry](http://msdn.microsoft.com/en-us/library/hh242985.aspx) -* [Reactive Extensions for .NET Introduction](http://leecampbell.blogspot.com/2010/08/reactive-extensions-for-net.html) -* [Rx - Channel 9 videos](http://channel9.msdn.com/tags/Rx/) -* [Reactive Extensions wiki](http://rxwiki.wikidot.com/) -* [101 Rx Samples](http://rxwiki.wikidot.com/101samples) -* [Programming Reactive Extensions and LINQ](http://www.amazon.com/Programming-Reactive-Extensions-Jesse-Liberty/dp/1430237473) - -RAC and Rx are both frameworks inspired by functional reactive programming. Here -are some resources related to FRP: - -* [What is FRP? - Elm Language](http://elm-lang.org/learn/What-is-FRP.elm) -* [What is Functional Reactive Programming - Stack Overflow](http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming/1030631#1030631) -* [Specification for a Functional Reactive Language - Stack Overflow](http://stackoverflow.com/questions/5875929/specification-for-a-functional-reactive-programming-language#5878525) -* [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) - -[README]: ../../README.md -[Basic Operators]: BasicOperators.md -[Framework Overview]: FrameworkOverview.md -[Functional Reactive Programming]: http://en.wikipedia.org/wiki/Functional_reactive_programming -[GroceryList]: https://github.com/jspahrsummers/GroceryList -[RACSequence]: ../../ReactiveCocoa/Objective-C/RACSequence.h -[RACSignal]: ../../ReactiveCocoa/Objective-C/RACSignal.h -[futures and promises]: http://en.wikipedia.org/wiki/Futures_and_promises -[C-41]: https://github.com/AshFurrow/C-41 diff --git a/Documentation/ObjectiveCBridging.md b/Documentation/ObjectiveCBridging.md deleted file mode 100644 index f5016999a2..0000000000 --- a/Documentation/ObjectiveCBridging.md +++ /dev/null @@ -1,99 +0,0 @@ -# Objective-C Bridging - -While ReactiveCocoa 3.0 introduces an entirely new design, it also aims for maximum compatibility with RAC 2, to ease the pain of migration. To interoperate with RAC 2’s Objective-C APIs, RAC 3 offers bridging functions that can convert Objective-C types to Swift types and vice-versa. - -Because the APIs are based on fundamentally different designs, the conversion is not always one-to-one; however, every attempt has been made to faithfully translate the concepts between the two APIs (and languages). - -The bridged types include: - - 1. [`RACSignal` and `SignalProducer` or `Signal`](#racsignal-and-signalproducer-or-signal) - 1. [`RACCommand` and `Action`](#raccommand-and-action) - 1. [`RACScheduler` and `SchedulerType`](#racscheduler-and-schedulertype) - 1. [`RACDisposable` and `Disposable`](#racdisposable-and-disposable) - -For the complete bridging API, including documentation, see [`ObjectiveCBridging.swift`][ObjectiveCBridging]. To learn more about how to migrate between ReactiveCocoa 2 and 3, see the [CHANGELOG][]. - -## `RACSignal` and `SignalProducer` or `Signal` - -In RAC 3, “cold” signals are represented by the `SignalProducer` type, and “hot” signals are represented by the `Signal` type. - -“Cold” `RACSignal`s can be converted into `SignalProducer`s using the new `toSignalProducer` method: - -```swift -extension RACSignal { - func toSignalProducer() -> SignalProducer -} -``` - -“Hot” `RACSignal`s cannot be directly converted into `Signal`s, because _any_ `RACSignal` subscription could potentially involve side effects. To obtain a `Signal`, use `RACSignal.toSignalProducer` followed by `SignalProducer.start`, which will make those potential side effects explicit. - -For the other direction, use the `toRACSignal()` function. - -When called with a `SignalProducer`, these functions will create a `RACSignal` to `start()` the producer once for each subscription: - -```swift -func toRACSignal(producer: SignalProducer) -> RACSignal -func toRACSignal(producer: SignalProducer) -> RACSignal -``` - -When called with a `Signal`, these functions will create a `RACSignal` that simply observes it: - -```swift -func toRACSignal(signal: Signal) -> RACSignal -func toRACSignal(signal: Signal) -> RACSignal -``` - -## `RACCommand` and `Action` - -To convert `RACCommand`s into the new `Action` type, use the `toAction()` extension method: - -```swift -extension RACCommand { - func toAction() -> Action -} -``` - -To convert `Action`s into `RACCommand`s, use the `toRACCommand()` function: - -```swift -func toRACCommand(action: Action) -> RACCommand -func toRACCommand(action: Action) -> RACCommand -``` - -**NOTE:** The `executing` properties of actions and commands are not synchronized across the API bridge. To ensure consistency, only observe the `executing` property from the base object (the one passed _into_ the bridge, not retrieved from it), so updates occur no matter which object is used for execution. - -## `RACScheduler` and `SchedulerType` - -Any `RACScheduler` instance is automatically a `DateSchedulerType` (and therefore a `SchedulerType`), and can be passed directly into any function or method that expects one. - -Some (but not all) `SchedulerType`s from RAC 3 can be converted into `RACScheduler` instances, using the `toRACScheduler()` method: - -```swift -extension ImmediateScheduler { - func toRACScheduler() -> RACScheduler -} - -extension UIScheduler { - func toRACScheduler() -> RACScheduler -} - -extension QueueScheduler { - func toRACScheduler() -> RACScheduler -} -``` - -## `RACDisposable` and `Disposable` - -Any `RACDisposable` instance is automatically a `Disposable`, and can be used directly anywhere a type conforming to `Disposable` is expected. - -Although there is no direct conversion from `Disposable` into `RACDisposable`, it is easy to do manually: - -```swift -let swiftDisposable: Disposable -let objcDisposable = RACDisposable { - swiftDisposable.dispose() -} -``` - -[CHANGELOG]: ../CHANGELOG.md -[ObjectiveCBridging]: ../ReactiveCocoa/Swift/ObjectiveCBridging.swift diff --git a/Documentation/README.md b/Documentation/README.md index 4e0d4ed285..78d6dd2cd8 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -1,2 +1,115 @@ -This folder contains conceptual documentation and design guidelines that don't -fit well on a single class or in any specific header file. +# ReactiveSwift + +ReactiveCocoa is based on ReactiveSwift and extends ReactiveSwift by Cocoa (esp. UIKit and AppKit) specific aspects. + +For documentation of the basics, please refer to the [ReactiveSwift Documentation][ReactiveSwiftDocumentation]. Specifically, you can find documentation about + +* [Framework Overview][] +* [Basic Operators][] +* [Design Guidelines][] + +This document outlines the additions that ReactiveCocoa brings over ReactiveSwift. + +# Additions in ReactiveCocoa + +## Foundation: Object Interception + +ReactiveCocoa includes a few object interception tools from ReactiveObjC, remastered for use with Swift. + +1. **Method Call Interception** + + Create signals that are sourced by intercepting Objective-C objects. + + ```swift + // Notify after every time `viewWillAppear(_:)` is called. + let appearing = viewController.reactive.trigger(for: #selector(UIViewController.viewWillAppear(_:))) + ``` + +1. **Object Lifetime** + + Obtain a `Lifetime` token for any `NSObject` to observe their deinitialization. + + ```swift + // Observe the lifetime of `object`. + object.reactive.lifetime.ended.observeCompleted(doCleanup) + ``` + +1. **Dynamic Property** + + The [`DynamicProperty`][] type can be used to bridge to Objective-C APIs that require Key-Value Coding (KVC) or Key-Value Observing (KVO), like `NSOperation`. Note that most AppKit and UIKit properties do _not_ support KVO, so their changes should be observed through other mechanisms. + + + For binding UI, [UIKit][UIKit-bindings] and [AppKit](AppKit-bindings) bindings provided by ReactiveCocoa are preferred. + In all other cases, [`MutableProperty`][] should be preferred over dynamic properties whenever possible! + +1. **Expressive, Safe Key Path Observation** + + Establish key-value observations in the form of [`SignalProducer`][]s and + strong-typed [`DynamicProperty`][]s, and enjoy the inherited composability. + + ```swift + // A producer that sends the current value of `keyPath`, followed by + // subsequent changes. + // + // Terminate the KVO observation if the lifetime of `self` ends. + let producer = object.reactive.values(forKeyPath: #keyPath(key)) + .take(during: self.reactive.lifetime) + + // A parameterized property that represents the supplied key path of the + // wrapped object. It holds a weak reference to the wrapped object. + let property = DynamicProperty(object: person, + keyPath: #keyPath(person.name)) + ``` + + These are accessible via the `reactive` magic property that is available on any ObjC objects. + +## UI Bindings + +ReactiveCocoa provides UI bindings for UIKit and AppKit via the `reactive` structure. + +1. **BindingTarget** + + UI components expose [`BindingTarget`][]s, which accept bindings from any + kind of streams of values via the `<~` operator. + + ```swift + // Bind the `name` property of `person` to the text value of an `UILabel`. + nameLabel.reactive.text <~ person.name + ``` + +1. **Controls and User Interactions** + + Interactive UI components expose [`Signal`][]s for control events + and updates in the control value upon user interactions. + + A selected set of controls provide a convenience, expressive binding + API for [`Action`][]s. + + ```swift + // Update `allowsCookies` whenever the toggle is flipped. + preferences.allowsCookies <~ toggle.reactive.isOnValues + + // Compute live character counts from the continuous stream of user initiated + // changes in the text. + textField.reactive.continuousTextValues.map { $0.characters.count } + + // Trigger `commit` whenever the button is pressed. + button.reactive.pressed = CocoaAction(viewModel.commit) + ``` + + These are accessible via the `reactive` magic property that is available on any ObjC objects. + + CocoaAction wraps an Action for use by a GUI control (such as `NSControl` or `UIControl`), with KVO, or with Cocoa Bindings. + +[ReactiveSwiftDocumentation]: https://github.com/ReactiveCocoa/ReactiveSwift/tree/master/Documentation +[Framework Overview]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md +[Basic Operators]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/BasicOperators.md +[Design Guidelines]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/DesignGuidelines.md +[`Signal`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#signals +[`SignalProducer`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#signal-producers +[`Action`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#actions +[`BindingTarget`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#properties +[`MutableProperty`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Property.swift#L583 +[`DynamicProperty`]: https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/DynamicProperty.swift +[UIKit-bindings]: https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/ReactiveCocoa/UIKit +[AppKit-bindings]: https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/ReactiveCocoa/AppKit diff --git a/Instruments/Disposable Growth.tracetemplate b/Instruments/Disposable Growth.tracetemplate deleted file mode 100644 index 24dc61ce79..0000000000 Binary files a/Instruments/Disposable Growth.tracetemplate and /dev/null differ diff --git a/Instruments/README.md b/Instruments/README.md deleted file mode 100644 index e2f1d4a8e0..0000000000 --- a/Instruments/README.md +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 4c90d88d80..0000000000 Binary files a/Instruments/Signal Events.tracetemplate and /dev/null differ diff --git a/Logo/PNG/Docs.png b/Logo/PNG/Docs.png new file mode 100644 index 0000000000..fb17387014 Binary files /dev/null and b/Logo/PNG/Docs.png differ diff --git a/Logo/PNG/JoinSlack.png b/Logo/PNG/JoinSlack.png new file mode 100644 index 0000000000..dfcb7bf9ff Binary files /dev/null and b/Logo/PNG/JoinSlack.png differ diff --git a/Logo/PNG/logo-unpadded.png b/Logo/PNG/logo-unpadded.png new file mode 100644 index 0000000000..c14eb29fdb Binary files /dev/null and b/Logo/PNG/logo-unpadded.png differ diff --git a/Logo/PNG/logo.png b/Logo/PNG/logo.png index c14eb29fdb..d64e8ecdf3 100644 Binary files a/Logo/PNG/logo.png and b/Logo/PNG/logo.png differ diff --git a/Logo/header.png b/Logo/header.png index 95b4dece8f..5c03daeae8 100644 Binary files a/Logo/header.png and b/Logo/header.png differ diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000000..8d624fbdc5 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "CwlCatchException", + "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", + "state": { + "branch": null, + "revision": "35f9e770f54ce62dd8526470f14c6e137cef3eea", + "version": "2.1.1" + } + }, + { + "package": "CwlPreconditionTesting", + "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state": { + "branch": null, + "revision": "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688", + "version": "2.1.0" + } + }, + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "c93f16c25af5770f0d3e6af27c9634640946b068", + "version": "9.2.1" + } + }, + { + "package": "Quick", + "repositoryURL": "https://github.com/Quick/Quick.git", + "state": { + "branch": null, + "revision": "bd86ca0141e3cfb333546de5a11ede63f0c4a0e6", + "version": "4.0.0" + } + }, + { + "package": "ReactiveSwift", + "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift", + "state": { + "branch": null, + "revision": "40c465af19b993344e84355c00669ba2022ca3cd", + "version": "7.1.1" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 814e59be46..8b60ce3233 100644 --- a/Package.swift +++ b/Package.swift @@ -1,24 +1,44 @@ +// swift-tools-version:5.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription -let excludes: [String] -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - excludes = [ - "Sources/Deprecations+Removals.swift", - "Sources/ObjectiveCBridging.swift" - ] -#else - excludes = [ - "Sources/Deprecations+Removals.swift", - "Sources/DynamicProperty.swift", - "Sources/NSObject+KeyValueObserving.swift", - "Sources/ObjectiveCBridging.swift" - ] -#endif - let package = Package( name: "ReactiveCocoa", + platforms: [ + .macOS(.v10_13), .iOS(.v11), .tvOS(.v11), .watchOS(.v4) + ], + products: [ + .library(name: "ReactiveCocoa", targets: ["ReactiveCocoa"]) + ], dependencies: [ - .Package(url: "https://github.com/antitypical/Result.git", "3.0.0-alpha.2") + .package(url: "https://github.com/ReactiveCocoa/ReactiveSwift", from: "7.0.0"), + .package(url: "https://github.com/Quick/Quick.git", from: "4.0.0"), + .package(url: "https://github.com/Quick/Nimble.git", from: "9.0.0"), + ], + targets: [ + .target( + name: "ReactiveCocoaObjC", + dependencies: [], + path: "ReactiveCocoaObjC"), + + .target( + name: "ReactiveCocoa", + dependencies: ["ReactiveSwift", "ReactiveCocoaObjC"], + path: "ReactiveCocoa"), + + .target( + name: "ReactiveCocoaObjCTestSupport", + path: "ReactiveCocoaObjCTestSupport"), + + .testTarget( + name: "ReactiveCocoaTests", + dependencies: [ + "ReactiveCocoa", + "ReactiveCocoaObjCTestSupport", + "Quick", + "Nimble" + ], + path: "ReactiveCocoaTests"), ], - exclude: excludes + swiftLanguageVersions: [.v5] ) diff --git a/README.md b/README.md index 578cc7b88f..140211c135 100644 --- a/README.md +++ b/README.md @@ -1,376 +1,162 @@ -![](Logo/header.png) +

+ ReactiveCocoa

+ Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift.

+ Join the ReactiveSwift Slack community. +

+
-[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![GitHub release](https://img.shields.io/github/release/ReactiveCocoa/ReactiveCocoa.svg)](https://github.com/ReactiveCocoa/ReactiveCocoa/releases) ![Swift 3.0.x](https://img.shields.io/badge/Swift-3.0.x-orange.svg) ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20OS%20X%20%7C%20watchOS%20%7C%20tvOS%20-lightgrey.svg) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](#carthage) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/ReactiveCocoa.svg)](#cocoapods) [![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-orange.svg)](#swift-package-manager) [![GitHub release](https://img.shields.io/github/release/ReactiveCocoa/ReactiveCocoa.svg)](https://github.com/ReactiveCocoa/ReactiveCocoa/releases) ![Swift 5.1](https://img.shields.io/badge/Swift-5.1-orange.svg) ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20OS%20X%20%7C%20watchOS%20%7C%20tvOS%20-lightgrey.svg) -ReactiveCocoa (RAC) is a Cocoa framework inspired by [Functional Reactive Programming](https://en.wikipedia.org/wiki/Functional_reactive_programming). It provides APIs for composing and transforming **streams of values over time**. +⚠️ [Looking for the Objective-C API?][] - 1. [Introduction](#introduction) - 1. [Example: online search](#example-online-search) - 1. [Objective-C and Swift](#objective-c-and-swift) - 1. [How does ReactiveCocoa relate to Rx?](#how-does-reactivecocoa-relate-to-rx) - 1. [Getting started](#getting-started) - 1. [Playground](#playground) +🎉 [Migrating from RAC 4.x?][CHANGELOG] -If you’re already familiar with functional reactive programming or what -ReactiveCocoa is about, check out the [Documentation][] folder for more in-depth -information about how it all works. Then, dive straight into our [documentation -comments][Code] for learning more about individual APIs. +🚄 [Release Roadmap](#release-roadmap) -If you have a question, please see if any discussions in our [GitHub -issues](https://github.com/ReactiveCocoa/ReactiveCocoa/issues?q=is%3Aissue+label%3Aquestion+) or [Stack -Overflow](http://stackoverflow.com/questions/tagged/reactive-cocoa) have already -answered it. If not, please feel free to [file your -own](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/new)! +## What is ReactiveSwift? +__ReactiveSwift__ offers composable, declarative and flexible primitives that are built around the grand concept of ___streams of values over time___. These primitives can be used to uniformly represent common Cocoa and generic programming patterns that are fundamentally an act of observation. -#### Compatibility +For more information about the core primitives, see [ReactiveSwift][]. -This documents the RAC 5 which targets `Swift 3.0.x`. For `Swift 2.x` support see [RAC -4](https://github.com/ReactiveCocoa/ReactiveCocoa/tree/v4.0.0). +## What is ReactiveCocoa? -## Introduction +__ReactiveCocoa__ wraps various aspects of Cocoa frameworks with the declarative [ReactiveSwift][] primitives. -ReactiveCocoa is inspired by [functional reactive -programming](https://joshaber.github.io/2013/02/11/input-and-output/). -Rather than using mutable variables which are replaced and modified in-place, -RAC offers “event streams,” represented by the [`Signal`][Signals] and -[`SignalProducer`][Signal producers] types, that send values over time. +1. **UI Bindings** -Event streams unify all of Cocoa’s common patterns for asynchrony and event -handling, including: + UI components expose [`BindingTarget`][]s, which accept bindings from any + kind of streams of values via the `<~` operator. - * Delegate methods - * Callback blocks - * `NSNotification`s - * Control actions and responder chain events - * [Futures and promises](https://en.wikipedia.org/wiki/Futures_and_promises) - * [Key-value observing](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) (KVO) + ```swift + // Bind the `name` property of `person` to the text value of an `UILabel`. + nameLabel.reactive.text <~ person.name + ``` -Because all of these different mechanisms can be represented in the _same_ way, -it’s easy to declaratively chain and combine them together, with less spaghetti -code and state to bridge the gap. + _Note_: You'll need to import ReactiveSwift as well to make use of the `<~` operator. -For more information about the concepts in ReactiveCocoa, see the [Framework -Overview][]. +1. **Controls and User Interactions** -## Example: online search + Interactive UI components expose [`Signal`][]s for control events + and updates in the control value upon user interactions. -Let’s say you have a text field, and whenever the user types something into it, -you want to make a network request which searches for that query. + A selected set of controls provide a convenience, expressive binding + API for [`Action`][]s. -#### Observing text edits -The first step is to observe edits to the text field, using a RAC extension to -`UITextField` specifically for this purpose: + ```swift + // Update `allowsCookies` whenever the toggle is flipped. + preferences.allowsCookies <~ toggle.reactive.isOnValues -```swift -let searchStrings = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } -``` - -This gives us a [signal producer][Signal producers] which sends -values of type `String`. _(The cast is [currently -necessary](https://github.com/ReactiveCocoa/ReactiveCocoa/issues/2182) to bridge -this extension method from Objective-C.)_ - -#### Making network requests - -With each string, we want to execute a network request. Luckily, RAC offers an -`NSURLSession` extension for doing exactly that: - -```swift -let searchResults = searchStrings - .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in - let URLRequest = self.searchRequestWithEscapedQuery(query) - return NSURLSession.sharedSession().rac_dataWithRequest(URLRequest) - } - .map { (data, URLResponse) -> String in - let string = String(data: data, encoding: NSUTF8StringEncoding)! - return self.parseJSONResultsFromString(string) - } - .observeOn(UIScheduler()) -``` + // Compute live character counts from the continuous stream of user initiated + // changes in the text. + textField.reactive.continuousTextValues.map { $0.characters.count } -This has transformed our producer of `String`s into a producer of `Array`s -containing the search results, which will be forwarded on the main thread -(thanks to the [`UIScheduler`][Schedulers]). + // Trigger `commit` whenever the button is pressed. + button.reactive.pressed = CocoaAction(viewModel.commit) + ``` -Additionally, [`flatMap(.Latest)`][flatMapLatest] here ensures that _only one search_—the -latest—is allowed to be running. If the user types another character while the -network request is still in flight, it will be cancelled before starting a new -one. Just think of how much code that would take to do by hand! +1. **Declarative Objective-C Dynamism** -#### Receiving the results + Create signals that are sourced by intercepting Objective-C objects, + e.g. method call interception and object deinitialization. -This won’t actually execute yet, because producers must be _started_ in order to -receive the results (which prevents doing work when the results are never used). -That’s easy enough: + ```swift + // Notify after every time `viewWillAppear(_:)` is called. + let appearing = viewController.reactive.trigger(for: #selector(UIViewController.viewWillAppear(_:))) -```swift -searchResults.startWithNext { results in - print("Search results: \(results)") -} -``` + // Observe the lifetime of `object`. + object.reactive.lifetime.ended.observeCompleted(doCleanup) + ``` -Here, we watch for the `Next` [event][Events], which contains our results, and -just log them to the console. This could easily do something else instead, like -update a table view or a label on screen. +1. **Expressive, Safe Key Path Observation** -#### Handling failures + Establish key-value observations in the form of [`SignalProducer`][]s and + `DynamicProperty`s, and enjoy the inherited composability. -In this example so far, any network error will generate a `Failed` -[event][Events], which will terminate the event stream. Unfortunately, this -means that future queries won’t even be attempted. + ```swift + // A producer that sends the current value of `keyPath`, followed by + // subsequent changes. + // + // Terminate the KVO observation if the lifetime of `self` ends. + let producer = object.reactive.producer(forKeyPath: #keyPath(key)) + .take(during: self.reactive.lifetime) -To remedy this, we need to decide what to do with failures that occur. The -quickest solution would be to log them, then ignore them: + // A parameterized property that represents the supplied key path of the + // wrapped object. It holds a weak reference to the wrapped object. + let property = DynamicProperty(object: person, + keyPath: #keyPath(person.name)) + ``` -```swift - .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in - let URLRequest = self.searchRequestWithEscapedQuery(query) +But there are still more to be discovered and introduced. Read our in-code documentations and release notes to +find out more. - return NSURLSession.sharedSession() - .rac_dataWithRequest(URLRequest) - .flatMapError { error in - print("Network error occurred: \(error)") - return SignalProducer.empty - } - } -``` - -By replacing failures with the `empty` event stream, we’re able to effectively -ignore them. - -However, it’s probably more appropriate to retry at least a couple of times -before giving up. Conveniently, there’s a [`retry`][retry] operator to do exactly that! - -Our improved `searchResults` producer might look like this: - -```swift -let searchResults = searchStrings - .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in - let URLRequest = self.searchRequestWithEscapedQuery(query) - - return NSURLSession.sharedSession() - .rac_dataWithRequest(URLRequest) - .retry(2) - .flatMapError { error in - print("Network error occurred: \(error)") - return SignalProducer.empty - } - } - .map { (data, URLResponse) -> String in - let string = String(data: data, encoding: NSUTF8StringEncoding)! - return self.parseJSONResultsFromString(string) - } - .observeOn(UIScheduler()) -``` +## Getting started -#### Throttling requests +ReactiveCocoa supports macOS 10.9+, iOS 8.0+, watchOS 2.0+, and tvOS 9.0+. -Now, let’s say you only want to actually perform the search periodically, -to minimize traffic. +#### Carthage -ReactiveCocoa has a declarative `throttle` operator that we can apply to our -search strings: +If you use [Carthage][] to manage your dependencies, simply add +ReactiveCocoa to your `Cartfile`: -```swift -let searchStrings = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) +``` +github "ReactiveCocoa/ReactiveCocoa" ~> 10.1 ``` -This prevents values from being sent less than 0.5 seconds apart. - -To do this manually would require significant state, and end up much harder to -read! With ReactiveCocoa, we can use just one operator to incorporate _time_ into -our event stream. +If you use Carthage to build your dependencies, make sure you have added `ReactiveCocoa.framework` and `ReactiveSwift.framework` to the "_Linked Frameworks and Libraries_" section of your target, and have included them in your Carthage framework copying build phase. -#### Debugging event streams +#### CocoaPods -Due to its nature, a stream's stack trace might have dozens of frames, which, more often than not, can make debugging a very frustrating activity. -A naive way of debugging, is by injecting side effects into the stream, like so: +If you use [CocoaPods][] to manage your dependencies, simply add +ReactiveCocoa to your `Podfile`: -```swift -let searchString = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) - .on(event: { print ($0) }) // the side effect ``` - -This will print the stream's [events][Events], while preserving the original stream behaviour. Both [`SignalProducer`][Signal producers] -and [`Signal`][Signals] provide the `logEvents` operator, that will do this automatically for you: - -```swift -let searchString = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) - .logEvents() +pod 'ReactiveCocoa', '~> 10.1' ``` -For more information and advance usage, check the [Debugging Techniques](Documentation/DebuggingTechniques.md) document. - - -## Objective-C and Swift - -Although ReactiveCocoa was started as an Objective-C framework, as of [version -3.0][CHANGELOG], all major feature development is concentrated on the [Swift API][]. - -RAC’s [Objective-C API][] and Swift API are entirely separate, but there is -a [bridge][Objective-C Bridging] to convert between the two. This -is mostly meant as a compatibility layer for older ReactiveCocoa projects, or to -use Cocoa extensions which haven’t been added to the Swift API yet. - -The Objective-C API will continue to exist and be supported for the foreseeable -future, but it won’t receive many improvements. For more information about using -this API, please consult our [legacy documentation][]. - -**We highly recommend that all new projects use the Swift API.** - -## How does ReactiveCocoa relate to Rx? - -ReactiveCocoa was originally inspired, and therefore heavily influenced, by -Microsoft’s [Reactive -Extensions](https://msdn.microsoft.com/en-us/data/gg577609.aspx) (Rx) library. There are many ports of Rx, including [RxSwift](https://github.com/ReactiveX/RxSwift), but ReactiveCocoa is _intentionally_ not a direct port. - -**Where RAC differs from Rx**, it is usually to: - - * Create a simpler API - * Address common sources of confusion - * More closely match Cocoa conventions - -The following are some of the concrete differences, along with their rationales. - -### Naming +#### Swift Package Manager -In most versions of Rx, Streams over time are known as `Observable`s, which -parallels the `Enumerable` type in .NET. Additionally, most operations in Rx.NET -borrow names from [LINQ](https://msdn.microsoft.com/en-us/library/bb397926.aspx), -which uses terms reminiscent of relational databases, like `Select` and `Where`. +If you use Swift Package Manager, simply add ReactiveCocoa as a dependency +of your package in `Package.swift`: -**RAC is focused on matching Swift naming first and foremost**, with terms like -`map` and `filter` instead. Other naming differences are typically inspired by -significantly better alternatives from [Haskell](https://www.haskell.org) or -[Elm](http://elm-lang.org) (which is the primary source for the “signal” -terminology). - -### Signals and Signal Producers (“hot” and “cold” observables) - -One of the most confusing aspects of Rx is that of [“hot”, “cold”, and “warm” -observables](http://www.introtorx.com/content/v1.0.10621.0/14_HotAndColdObservables.html) (event streams). - -In short, given just a method or function declaration like this, in C#: - -```csharp -IObservable Search(string query) +``` +.package(url: "https://github.com/ReactiveCocoa/ReactiveCocoa.git", branch: "master") ``` -… it is **impossible to tell** whether subscribing to (observing) that -`IObservable` will involve side effects. If it _does_ involve side effects, it’s -also impossible to tell whether _each subscription_ has a side effect, or if only -the first one does. - -This example is contrived, but it demonstrates **a real, pervasive problem** -that makes it extremely hard to understand Rx code (and pre-3.0 ReactiveCocoa -code) at a glance. - -[ReactiveCocoa 3.0][CHANGELOG] has solved this problem by distinguishing side -effects with the separate [`Signal`][Signals] and [`SignalProducer`][Signal producers] types. Although this -means there’s another type to learn about, it improves code clarity and helps -communicates intent much better. - -In other words, **ReactiveCocoa’s changes here are [simple, not -easy](http://www.infoq.com/presentations/Simple-Made-Easy)**. - -### Typed errors - -When [signals][] and [signal producers][] are allowed to [fail][Events] in ReactiveCocoa, -the kind of error must be specified in the type system. For example, -`Signal` is a signal of integer values that may fail with an error -of type `NSError`. - -More importantly, RAC allows the special type `NoError` to be used instead, -which _statically guarantees_ that an event stream is not allowed to send a -failure. **This eliminates many bugs caused by unexpected failure events.** - -In Rx systems with types, event streams only specify the type of their -values—not the type of their errors—so this sort of guarantee is impossible. - -### UI programming - -Rx is basically agnostic as to how it’s used. Although UI programming with Rx is -very common, it has few features tailored to that particular case. - -RAC takes a lot of inspiration from [ReactiveUI](http://reactiveui.net/), -including the basis for [Actions][]. - -Unlike ReactiveUI, which unfortunately cannot directly change Rx to make it more -friendly for UI programming, **ReactiveCocoa has been improved many times -specifically for this purpose**—even when it means diverging further from Rx. - -## Getting started - -ReactiveCocoa supports `OS X 10.9+`, `iOS 8.0+`, `watchOS 2.0`, and `tvOS 9.0`. - -To add RAC to your application: +#### Git submodule - 1. Add the ReactiveCocoa repository as a - [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) of your + 1. Add the ReactiveCocoa repository as a [submodule][] of your application’s repository. 1. Run `git submodule update --init --recursive` from within the ReactiveCocoa folder. - 1. Drag and drop `ReactiveCocoa.xcodeproj` and `Carthage/Checkouts/Result/Result.xcodeproj` - into your application’s Xcode project or workspace. + 1. Drag and drop `ReactiveCocoa.xcodeproj` and `Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj` into your application’s Xcode + project or workspace. 1. On the “General” tab of your application target’s settings, add - `ReactiveCocoa.framework` and `Result.framework` to the “Embedded Binaries” section. + `ReactiveCocoa.framework` and `ReactiveSwift.framework` to the “Embedded Binaries” section. 1. If your application target does not contain Swift code at all, you should also set the `EMBEDDED_CONTENT_CONTAINS_SWIFT` build setting to “Yes”. -Or, if you’re using [Carthage](https://github.com/Carthage/Carthage), simply add -ReactiveCocoa to your `Cartfile`: +## Have a question? +If you need any help, please visit our [GitHub issues][] or [Stack Overflow][]. Feel free to file an issue if you do not manage to find any solution from the archives. -``` -github "ReactiveCocoa/ReactiveCocoa" -``` -Make sure to add both `ReactiveCocoa.framework` and `Result.framework` to "Linked Frameworks and Libraries" and "copy-frameworks" Build Phases. - -If you would prefer to use [CocoaPods](https://cocoapods.org), there are some -[unofficial podspecs](https://github.com/CocoaPods/Specs/tree/master/Specs/ReactiveCocoa) -that have been generously contributed by third parties. - -Once you’ve set up your project, check out the [Framework Overview][] for -a tour of ReactiveCocoa’s concepts, and the [Basic Operators][] for some -introductory examples of using it. - -## Playground - -We also provide a great Playground, so you can get used to ReactiveCocoa's operators. In order to start using it: - - 1. Clone the ReactiveCocoa repository. - 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: - - `git submodule update --init --recursive` **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed - - `carthage checkout` - 1. Open `ReactiveCocoa.xcworkspace` - 1. Build `Result-Mac` scheme - 1. Build `ReactiveCocoa-Mac` scheme - 1. Finally open the `ReactiveCocoa.playground` - 1. Choose `View > Show Debug Area` - -[Actions]: Documentation/FrameworkOverview.md#actions -[Basic Operators]: Documentation/BasicOperators.md +## Release Roadmap +**Current Stable Release:**
[![GitHub release](https://img.shields.io/github/release/ReactiveCocoa/ReactiveCocoa.svg)](https://github.com/ReactiveCocoa/ReactiveCocoa/releases) + +### In Development +### Plan of Record +#### ABI stability release +ReactiveCocoa is expected to declare library ABI stability when Swift rolls out resilience support in Swift 5. Until then, ReactiveCocoa will incrementally adopt new language features. + +[ReactiveSwift]: https://github.com/ReactiveCocoa/ReactiveSwift +[ReactiveObjC]: https://github.com/ReactiveCocoa/ReactiveObjC +[GitHub issues]: https://github.com/ReactiveCocoa/ReactiveCocoa/issues?q=is%3Aissue+label%3Aquestion+ +[Stack Overflow]: http://stackoverflow.com/questions/tagged/reactive-cocoa [CHANGELOG]: CHANGELOG.md -[Code]: ReactiveCocoa -[Documentation]: Documentation -[Events]: Documentation/FrameworkOverview.md#events -[Framework Overview]: Documentation/FrameworkOverview.md -[Legacy Documentation]: Documentation/Legacy -[Objective-C API]: ReactiveCocoa/Objective-C -[Objective-C Bridging]: Documentation/ObjectiveCBridging.md -[Schedulers]: Documentation/FrameworkOverview.md#schedulers -[Signal producers]: Documentation/FrameworkOverview.md#signal-producers -[Signals]: Documentation/FrameworkOverview.md#signals -[Swift API]: ReactiveCocoa/Swift -[flatMapLatest]: Documentation/BasicOperators.md#switching-to-the-latest -[retry]: Documentation/BasicOperators.md#retrying +[Carthage]: https://github.com/Carthage/Carthage +[CocoaPods]: https://cocoapods.org/ +[submodule]: https://git-scm.com/book/en/v2/Git-Tools-Submodules +[Looking for the Objective-C API?]: https://github.com/ReactiveCocoa/ReactiveObjC +[Still using Swift 2.x?]: https://github.com/ReactiveCocoa/ReactiveCocoa/tree/v4.0.0 +[`Signal`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#signals +[`SignalProducer`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#signal-producers +[`Action`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#actions +[`BindingTarget`]: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md#properties diff --git a/ReactiveCocoa-iOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift b/ReactiveCocoa-iOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000000..5743e44ae4 --- /dev/null +++ b/ReactiveCocoa-iOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift @@ -0,0 +1,23 @@ +/*: +> ## IMPORTANT: To use `ReactiveCocoa-iOS.playground`, please: + +1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: +- `git submodule update --init --recursive` +**OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed +- `carthage checkout --no-use-binaries` +1. Open `ReactiveCocoa.xcworkspace` +1. Build `ReactiveSwift-iOS` scheme +1. Build `ReactiveCocoa-iOS` scheme +1. Finally open the `ReactiveCocoa-iOS.playground` +1. Choose `View > Show Debug Area` +*/ + +import ReactiveCocoa +import ReactiveSwift +import UIKit + +/*: +## Sandbox + +A place where you can build your sand castles 🏖. +*/ diff --git a/ReactiveCocoa.playground/Sources/PlaygroundUtility.swift b/ReactiveCocoa-iOS.playground/Sources/PlaygroundUtility.swift similarity index 100% rename from ReactiveCocoa.playground/Sources/PlaygroundUtility.swift rename to ReactiveCocoa-iOS.playground/Sources/PlaygroundUtility.swift diff --git a/ReactiveCocoa-iOS.playground/contents.xcplayground b/ReactiveCocoa-iOS.playground/contents.xcplayground new file mode 100644 index 0000000000..5ed29117bc --- /dev/null +++ b/ReactiveCocoa-iOS.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ReactiveCocoa-macOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift b/ReactiveCocoa-macOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000000..43f51659b4 --- /dev/null +++ b/ReactiveCocoa-macOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift @@ -0,0 +1,23 @@ +/*: +> ## IMPORTANT: To use `ReactiveCocoa-macOS.playground`, please: + +1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: +- `git submodule update --init --recursive` +**OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed +- `carthage checkout --no-use-binaries` +1. Open `ReactiveCocoa.xcworkspace` +1. Build `ReactiveSwift-macOS` scheme +1. Build `ReactiveCocoa-macOS` scheme +1. Finally open the `ReactiveCocoa-macOS.playground` +1. Choose `View > Show Debug Area` +*/ + +import ReactiveCocoa +import ReactiveSwift +import AppKit + +/*: +## Sandbox + +A place where you can build your sand castles 🏖. +*/ diff --git a/ReactiveCocoa-macOS.playground/Sources/PlaygroundUtility.swift b/ReactiveCocoa-macOS.playground/Sources/PlaygroundUtility.swift new file mode 100644 index 0000000000..282b07f49e --- /dev/null +++ b/ReactiveCocoa-macOS.playground/Sources/PlaygroundUtility.swift @@ -0,0 +1,10 @@ +import Foundation + +public func scopedExample(_ exampleDescription: String, _ action: () -> Void) { + print("\n--- \(exampleDescription) ---\n") + action() +} + +public enum PlaygroundError: Error { + case example(String) +} diff --git a/ReactiveCocoa-macOS.playground/contents.xcplayground b/ReactiveCocoa-macOS.playground/contents.xcplayground new file mode 100644 index 0000000000..3740719fea --- /dev/null +++ b/ReactiveCocoa-macOS.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ReactiveCocoa-tvOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift b/ReactiveCocoa-tvOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000000..238545fcbd --- /dev/null +++ b/ReactiveCocoa-tvOS.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift @@ -0,0 +1,23 @@ +/*: +> ## IMPORTANT: To use `ReactiveCocoa-tvOS.playground`, please: + +1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: +- `git submodule update --init --recursive` +**OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed +- `carthage checkout --no-use-binaries` +1. Open `ReactiveCocoa.xcworkspace` +1. Build `ReactiveSwift-tvOS` scheme +1. Build `ReactiveCocoa-tvOS` scheme +1. Finally open the `ReactiveCocoa-tvOS.playground` +1. Choose `View > Show Debug Area` +*/ + +import ReactiveCocoa +import ReactiveSwift +import UIKit + +/*: +## Sandbox + +A place where you can build your sand castles 🏖. +*/ diff --git a/ReactiveCocoa-tvOS.playground/Sources/PlaygroundUtility.swift b/ReactiveCocoa-tvOS.playground/Sources/PlaygroundUtility.swift new file mode 100644 index 0000000000..282b07f49e --- /dev/null +++ b/ReactiveCocoa-tvOS.playground/Sources/PlaygroundUtility.swift @@ -0,0 +1,10 @@ +import Foundation + +public func scopedExample(_ exampleDescription: String, _ action: () -> Void) { + print("\n--- \(exampleDescription) ---\n") + action() +} + +public enum PlaygroundError: Error { + case example(String) +} diff --git a/ReactiveCocoa-tvOS.playground/contents.xcplayground b/ReactiveCocoa-tvOS.playground/contents.xcplayground new file mode 100644 index 0000000000..5fd6ef8029 --- /dev/null +++ b/ReactiveCocoa-tvOS.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ReactiveCocoa.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift b/ReactiveCocoa.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift deleted file mode 100644 index afe8ad4d77..0000000000 --- a/ReactiveCocoa.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift +++ /dev/null @@ -1,25 +0,0 @@ -/*: - > # IMPORTANT: To use `ReactiveCocoa.playground`, please: - - 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: - - `script/bootstrap` - **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed - - `carthage checkout` - 1. Open `ReactiveCocoa.xcworkspace` - 1. Build `Result-Mac` scheme - 1. Build `ReactiveCocoa-Mac` scheme - 1. Finally open the `ReactiveCocoa.playground` - 1. Choose `View > Show Debug Area` - */ - -import Result -import ReactiveCocoa -import Foundation - -/*: - ## Sandbox - - A place where you can build your sand castles 🏖. -*/ - - diff --git a/ReactiveCocoa.playground/Pages/Signal.xcplaygroundpage/Contents.swift b/ReactiveCocoa.playground/Pages/Signal.xcplaygroundpage/Contents.swift deleted file mode 100644 index 6a310cd20f..0000000000 --- a/ReactiveCocoa.playground/Pages/Signal.xcplaygroundpage/Contents.swift +++ /dev/null @@ -1,240 +0,0 @@ -/*: -> # IMPORTANT: To use `ReactiveCocoa.playground`, please: - -1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: - - `script/bootstrap` -**OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed - - `carthage checkout` -1. Open `ReactiveCocoa.xcworkspace` -1. Build `Result-Mac` scheme -1. Build `ReactiveCocoa-Mac` scheme -1. Finally open the `ReactiveCocoa.playground` -1. Choose `View > Show Debug Area` -*/ - -import Result -import ReactiveCocoa -import Foundation - -/*: -## Signal - -A **signal**, represented by the [`Signal`](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/Signal.swift) type, is any series of [`Event`](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/Event.swift) values -over time that can be observed. - -Signals are generally used to represent event streams that are already “in progress”, -like notifications, user input, etc. As work is performed or data is received, -events are _sent_ on the signal, which pushes them out to any observers. -All observers see the events at the same time. - -Users must observe a signal in order to access its events. -Observing a signal does not trigger any side effects. In other words, -signals are entirely producer-driven and push-based, and consumers (observers) -cannot have any effect on their lifetime. While observing a signal, the user -can only evaluate the events in the same order as they are sent on the signal. There -is no random access to values of a signal. - -Signals can be manipulated by applying [primitives](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md) to them. -Typical primitives to manipulate a single signal like `filter`, `map` and -`reduce` are available, as well as primitives to manipulate multiple signals -at once (`zip`). Primitives operate only on the `Next` events of a signal. - -The lifetime of a signal consists of any number of `Next` events, followed by -one terminating event, which may be any one of `Failed`, `Completed`, or -`Interrupted` (but not a combination). -Terminating events are not included in the signal’s values—they must be -handled specially. -*/ - -/*: -### `Subscription` -A Signal represents and event stream that is already "in progress", sometimes also called "hot". This means, that a subscriber may miss events that have been sent before the subscription. -Furthermore, the subscription to a signal does not trigger any side effects -*/ -scopedExample("Subscription") { - // Signal.pipe is a way to manually control a signal. the returned observer can be used to send values to the signal - let (signal, observer) = Signal.pipe() - - let subscriber1 = Observer(next: { print("Subscriber 1 received \($0)") } ) - let subscriber2 = Observer(next: { print("Subscriber 2 received \($0)") } ) - - print("Subscriber 1 subscribes to the signal") - signal.observe(subscriber1) - - print("Send value `10` on the signal") - // subscriber1 will receive the value - observer.sendNext(10) - - print("Subscriber 2 subscribes to the signal") - // Notice how nothing happens at this moment, i.e. subscriber2 does not receive the previously sent value - signal.observe(subscriber2) - - print("Send value `20` on the signal") - // Notice that now, subscriber1 and subscriber2 will receive the value - observer.sendNext(20) -} - -/*: -### `empty` -A Signal that completes immediately without emitting any value. -*/ -scopedExample("`empty`") { - let emptySignal = Signal.empty - - let observer = Observer( - next: { _ in print("next not called") }, - failed: { _ in print("error not called") }, - completed: { print("completed not called") }, - interrupted: { print("interrupted called") } - ) - - emptySignal.observe(observer) -} - -/*: -### `never` -A Signal that never sends any events to its observers. -*/ -scopedExample("`never`") { - let neverSignal = Signal.never - - let observer = Observer( - next: { _ in print("next not called") }, - failed: { _ in print("error not called") }, - completed: { print("completed not called") }, - interrupted: { print("interrupted not called") } - ) - - neverSignal.observe(observer) -} - -/*: -## `Operators` -### `uniqueValues` -Forwards only those values from `self` that are unique across the set of -all values that have been seen. - -Note: This causes the values to be retained to check for uniqueness. Providing -a function that returns a unique value for each sent value can help you reduce -the memory footprint. -*/ -scopedExample("`uniqueValues`") { - let (signal, observer) = Signal.pipe() - let subscriber = Observer(next: { print("Subscriber received \($0)") } ) - let uniqueSignal = signal.uniqueValues() - - uniqueSignal.observe(subscriber) - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - observer.sendNext(3) - observer.sendNext(3) - observer.sendNext(5) -} - -/*: -### `map` -Maps each value in the signal to a new value. -*/ -scopedExample("`map`") { - let (signal, observer) = Signal.pipe() - let subscriber = Observer(next: { print("Subscriber received \($0)") } ) - let mappedSignal = signal.map { $0 * 2 } - - mappedSignal.observe(subscriber) - print("Send value `10` on the signal") - observer.sendNext(10) -} - -/*: -### `mapError` -Maps errors in the signal to a new error. -*/ -scopedExample("`mapError`") { - let (signal, observer) = Signal.pipe() - let subscriber = Observer(failed: { print("Subscriber received error: \($0)") } ) - let mappedErrorSignal = signal.mapError { (error:NSError) -> NSError in - let userInfo = [NSLocalizedDescriptionKey: "🔥"] - let code = error.code + 10000 - let mappedError = NSError(domain: "com.reactivecocoa.errordomain", code: code, userInfo: userInfo) - return mappedError - } - - mappedErrorSignal.observe(subscriber) - print("Send error `NSError(domain: \"com.reactivecocoa.errordomain\", code: 4815, userInfo: nil)` on the signal") - observer.sendFailed(NSError(domain: "com.reactivecocoa.errordomain", code: 4815, userInfo: nil)) -} - -/*: -### `filter` -Preserves only the values of the signal that pass the given predicate. -*/ -scopedExample("`filter`") { - let (signal, observer) = Signal.pipe() - let subscriber = Observer(next: { print("Subscriber received \($0)") } ) - // subscriber will only receive events with values greater than 12 - let filteredSignal = signal.filter { $0 > 12 ? true : false } - - filteredSignal.observe(subscriber) - observer.sendNext(10) - observer.sendNext(11) - observer.sendNext(12) - observer.sendNext(13) - observer.sendNext(14) -} - -/*: -### `skipNil` -Unwraps non-`nil` values and forwards them on the returned signal, `nil` -values are dropped. -*/ -scopedExample("`skipNil`") { - let (signal, observer) = Signal.pipe() - // note that the signal is of type `Int?` and observer is of type `Int`, given we're unwrapping - // non-`nil` values - let subscriber = Observer(next: { print("Subscriber received \($0)") } ) - let skipNilSignal = signal.skipNil() - - skipNilSignal.observe(subscriber) - observer.sendNext(1) - observer.sendNext(nil) - observer.sendNext(3) -} - -/*: -### `take(first:)` -Returns a signal that will yield the first `count` values from `self` -*/ -scopedExample("`take(first:)`") { - let (signal, observer) = Signal.pipe() - let subscriber = Observer(next: { print("Subscriber received \($0)") } ) - let takeSignal = signal.take(first: 2) - - takeSignal.observe(subscriber) - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) -} - -/*: -### `collect` -Returns a signal that will yield an array of values when `self` completes. -- Note: When `self` completes without collecting any value, it will send -an empty array of values. -*/ -scopedExample("`collect`") { - let (signal, observer) = Signal.pipe() - // note that the signal is of type `Int` and observer is of type `[Int]` given we're "collecting" - // `Int` values for the lifetime of the signal - let subscriber = Observer<[Int], NoError>(next: { print("Subscriber received \($0)") } ) - let collectSignal = signal.collect() - - collectSignal.observe(subscriber) - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - observer.sendCompleted() -} diff --git a/ReactiveCocoa.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift b/ReactiveCocoa.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift deleted file mode 100644 index 9cd61b63c5..0000000000 --- a/ReactiveCocoa.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift +++ /dev/null @@ -1,767 +0,0 @@ -/*: -> # IMPORTANT: To use `ReactiveCocoa.playground`, please: - -1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: - - `script/bootstrap` - **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed - - `carthage checkout` -1. Open `ReactiveCocoa.xcworkspace` -1. Build `Result-Mac` scheme -1. Build `ReactiveCocoa-Mac` scheme -1. Finally open the `ReactiveCocoa.playground` -1. Choose `View > Show Debug Area` -*/ - -import Result -import ReactiveCocoa -import Foundation - -/*: -## SignalProducer - -A **signal producer**, represented by the [`SignalProducer`](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/SignalProducer.swift) type, creates -[signals](https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ReactiveCocoa/Swift/Signal.swift) and performs side effects. - -They can be used to represent operations or tasks, like network -requests, where each invocation of `start()` will create a new underlying -operation, and allow the caller to observe the result(s). The -`startWithSignal()` variant gives access to the produced signal, allowing it to -be observed multiple times if desired. - -Because of the behavior of `start()`, each signal created from the same -producer may see a different ordering or version of events, or the stream might -even be completely different! Unlike a plain signal, no work is started (and -thus no events are generated) until an observer is attached, and the work is -restarted anew for each additional observer. - -Starting a signal producer returns a [disposable](#disposables) that can be used to -interrupt/cancel the work associated with the produced signal. - -Just like signals, signal producers can also be manipulated via primitives -like `map`, `filter`, etc. -Every signal primitive can be “lifted” to operate upon signal producers instead, -using the `lift` method. -Furthermore, there are additional primitives that control _when_ and _how_ work -is started—for example, `times`. -*/ - -/*: -### `Subscription` -A SignalProducer represents an operation that can be started on demand. Starting the operation returns a Signal on which the result(s) of the operation can be observed. This behavior is sometimes also called "cold". -This means that a subscriber will never miss any values sent by the SignalProducer. -*/ -scopedExample("Subscription") { - let producer = SignalProducer { observer, _ in - print("New subscription, starting operation") - observer.sendNext(1) - observer.sendNext(2) - } - - let subscriber1 = Observer(next: { print("Subscriber 1 received \($0)") }) - let subscriber2 = Observer(next: { print("Subscriber 2 received \($0)") }) - - print("Subscriber 1 subscribes to producer") - producer.start(subscriber1) - - print("Subscriber 2 subscribes to producer") - // Notice, how the producer will start the work again - producer.start(subscriber2) -} - -/*: -### `empty` -A producer for a Signal that will immediately complete without sending -any values. -*/ -scopedExample("`empty`") { - let emptyProducer = SignalProducer.empty - - let observer = Observer( - next: { _ in print("next not called") }, - failed: { _ in print("error not called") }, - completed: { print("completed called") } - ) - - emptyProducer.start(observer) -} - -/*: -### `never` -A producer for a Signal that never sends any events to its observers. -*/ -scopedExample("`never`") { - let neverProducer = SignalProducer.never - - let observer = Observer( - next: { _ in print("next not called") }, - failed: { _ in print("error not called") }, - completed: { print("completed not called") } - ) - - neverProducer.start(observer) -} - -/*: -### `startWithSignal` -Creates a Signal from the producer, passes it into the given closure, -then starts sending events on the Signal when the closure has returned. - -The closure will also receive a disposable which can be used to -interrupt the work associated with the signal and immediately send an -`Interrupted` event. -*/ -scopedExample("`startWithSignal`") { - var started = false - var value: Int? - - SignalProducer(value: 42) - .on(next: { - value = $0 - }) - .startWithSignal { signal, disposable in - print(value) - } - - print(value) -} - -/*: -### `startWithResult` -Creates a Signal from the producer, then adds exactly one observer to -the Signal, which will invoke the given callback when `next` or `failed` -events are received. - -Returns a Disposable which can be used to interrupt the work associated -with the Signal, and prevent any future callbacks from being invoked. -*/ -scopedExample("`startWithResult`") { - SignalProducer(value: 42) - .startWithResult { result in - print(result.value) - } -} - -/*: -### `startWithNext` -Creates a Signal from the producer, then adds exactly one observer to -the Signal, which will invoke the given callback when `next` events are -received. - -This method is available only if the producer never emits error, or in -other words, has an error type of `NoError`. - -Returns a Disposable which can be used to interrupt the work associated -with the Signal, and prevent any future callbacks from being invoked. -*/ -scopedExample("`startWithNext`") { - SignalProducer(value: 42) - .startWithNext { value in - print(value) - } -} - -/*: -### `startWithCompleted` -Creates a Signal from the producer, then adds exactly one observer to -the Signal, which will invoke the given callback when a `completed` event is -received. - -Returns a Disposable which can be used to interrupt the work associated -with the Signal. -*/ -scopedExample("`startWithCompleted`") { - SignalProducer(value: 42) - .startWithCompleted { - print("completed called") - } -} - -/*: -### `startWithFailed` -Creates a Signal from the producer, then adds exactly one observer to -the Signal, which will invoke the given callback when a `failed` event is -received. - -Returns a Disposable which can be used to interrupt the work associated -with the Signal. -*/ -scopedExample("`startWithFailed`") { - SignalProducer(error: NSError(domain: "example", code: 42, userInfo: nil)) - .startWithFailed { error in - print(error) - } -} - -/*: -### `startWithInterrupted` -Creates a Signal from the producer, then adds exactly one observer to -the Signal, which will invoke the given callback when an `interrupted` event -is received. - -Returns a Disposable which can be used to interrupt the work associated -with the Signal. -*/ -scopedExample("`startWithInterrupted`") { - let disposable = SignalProducer.never - .startWithInterrupted { - print("interrupted called") - } - - disposable.dispose() -} - - -/*: -### `lift` -Lifts an unary Signal operator to operate upon SignalProducers instead. - -In other words, this will create a new SignalProducer which will apply -the given Signal operator to _every_ created Signal, just as if the -operator had been applied to each Signal yielded from start(). -*/ -scopedExample("`lift`") { - var counter = 0 - let transform: (Signal) -> Signal = { signal in - counter = 42 - return signal - } - - SignalProducer(value: 0) - .lift(transform) - .startWithNext { _ in - print(counter) - } -} - -/*: -### `map` -Maps each value in the producer to a new value. -*/ -scopedExample("`map`") { - SignalProducer(value: 1) - .map { $0 + 41 } - .startWithNext { value in - print(value) - } -} - -/*: -### `mapError` -Maps errors in the producer to a new error. -*/ -scopedExample("`mapError`") { - SignalProducer(error: NSError(domain: "mapError", code: 42, userInfo: nil)) - .mapError { PlaygroundError.example($0.description) } - .startWithFailed { error in - print(error) - } -} - -/*: -### `filter` -Preserves only the values of the producer that pass the given predicate. -*/ -scopedExample("`filter`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .filter { $0 > 3 } - .startWithNext { value in - print(value) - } -} - -/*: -### `take(first:)` -Returns a producer that will yield the first `count` values from the -input producer. -*/ -scopedExample("`take(first:)`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .take(first: 2) - .startWithNext { value in - print(value) - } -} - -/*: -### `observe(on:)` -Forwards all events onto the given scheduler, instead of whichever -scheduler they originally arrived upon. -*/ -scopedExample("`observe(on:)`") { - let baseProducer = SignalProducer(values: [ 1, 2, 3, 4 ]) - let completion = { print("is main thread? \(Thread.current.isMainThread)") } - - baseProducer - .observe(on: QueueScheduler(qos: .default, name: "test")) - .startWithCompleted(completion) - - baseProducer - .startWithCompleted(completion) -} - -/*: -### `collect` -Returns a producer that will yield an array of values until it completes. -*/ -scopedExample("`collect`") { - SignalProducer { observer, disposable in - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - observer.sendCompleted() - } - .collect() - .startWithNext { value in - print(value) - } -} - -/*: -### `collect(count:)` -Returns a producer that will yield an array of values until it reaches a certain count. -*/ -scopedExample("`collect(count:)`") { - SignalProducer { observer, disposable in - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - observer.sendCompleted() - } - .collect(count: 2) - .startWithNext { value in - print(value) - } -} - -/*: -### `collect(_:)` matching values inclusively -Returns a producer that will yield an array of values based on a predicate -which matches the values collected. - -When producer completes any remaining values will be sent, the last values -array may not match `predicate`. Alternatively, if were not collected any -values will sent an empty array of values. -*/ -scopedExample("`collect(_:)` matching values inclusively") { - SignalProducer { observer, disposable in - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - observer.sendCompleted() - } - .collect { values in values.reduce(0, +) == 3 } - .startWithNext { value in - print(value) - } -} - -/*: -### `collect(_:)` matching values exclusively -Returns a producer that will yield an array of values based on a predicate -which matches the values collected and the next value. - -When producer completes any remaining values will be sent, the last values -array may not match `predicate`. Alternatively, if were not collected any -values will sent an empty array of values. -*/ -scopedExample("`collect(_:)` matching values exclusively") { - SignalProducer { observer, disposable in - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - observer.sendCompleted() - } - .collect { values, next in next == 3 } - .startWithNext { value in - print(value) - } -} - -/*: -### `combineLatest(with:)` -Combines the latest value of the receiver with the latest value from -the given producer. - -The returned producer will not send a value until both inputs have sent at -least one value each. If either producer is interrupted, the returned producer -will also be interrupted. -*/ -scopedExample("`combineLatest(with:)`") { - let producer1 = SignalProducer(values: [ 1, 2, 3, 4 ]) - let producer2 = SignalProducer(values: [ 1, 2 ]) - - producer1 - .combineLatest(with: producer2) - .startWithNext { value in - print("\(value)") - } -} - -/*: -### `skip(first:)` -Returns a producer that will skip the first `count` values, then forward -everything afterward. -*/ -scopedExample("`skip(first:)`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .skip(first: 2) - .startWithNext { value in - print(value) - } -} - -/*: -### `materialize` - -Treats all Events from the input producer as plain values, allowing them to be -manipulated just like any other value. - -In other words, this brings Events “into the monad.” - -When a Completed or Failed event is received, the resulting producer will send -the Event itself and then complete. When an Interrupted event is received, -the resulting producer will send the Event itself and then interrupt. -*/ -scopedExample("`materialize`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .materialize() - .startWithNext { value in - print(value) - } -} - -/*: -### `sample(on:)` -Forwards the latest value from `self` whenever `sampler` sends a Next -event. - -If `sampler` fires before a value has been observed on `self`, nothing -happens. - -Returns a producer that will send values from `self`, sampled (possibly -multiple times) by `sampler`, then complete once both input producers have -completed, or interrupt if either input producer is interrupted. -*/ -scopedExample("`sample(on:)`") { - let baseProducer = SignalProducer(values: [ 1, 2, 3, 4 ]) - let sampledOnProducer = SignalProducer(values: [ 1, 2 ]) - .map { _ in () } - - baseProducer - .sample(on: sampledOnProducer) - .startWithNext { value in - print(value) - } -} - -/*: -### `combinePrevious` -Forwards events from `self` with history: values of the returned producer -are a tuple whose first member is the previous value and whose second member -is the current value. `initial` is supplied as the first member when `self` -sends its first value. -*/ -scopedExample("`combinePrevious`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .combinePrevious(42) - .startWithNext { value in - print("\(value)") - } -} - -/*: -### `scan` -Aggregates `self`'s values into a single combined value. When `self` emits -its first value, `combine` is invoked with `initial` as the first argument and -that emitted value as the second argument. The result is emitted from the -producer returned from `scan`. That result is then passed to `combine` as the -first argument when the next value is emitted, and so on. -*/ -scopedExample("`scan`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .scan(0, +) - .startWithNext { value in - print(value) - } -} - -/*: -### `reduce` -Like `scan`, but sends only the final value and then immediately completes. -*/ -scopedExample("`reduce`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .reduce(0, +) - .startWithNext { value in - print(value) - } -} - -/*: -### `skipRepeats` -Forwards only those values from `self` which do not pass `isRepeat` with -respect to the previous value. The first value is always forwarded. -*/ -scopedExample("`skipRepeats`") { - SignalProducer(values: [ 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 1, 1, 1, 2, 2, 2, 4 ]) - .skipRepeats(==) - .startWithNext { value in - print(value) - } -} - -/*: -### `skip(while:)` -Does not forward any values from `self` until `predicate` returns false, -at which point the returned signal behaves exactly like `self`. -*/ -scopedExample("`skip(while:)`") { - // Note that trailing closure is used for `skip(while:)`. - SignalProducer(values: [ 3, 3, 3, 3, 1, 2, 3, 4 ]) - .skip { $0 > 2 } - .startWithNext { value in - print(value) - } -} - -/*: -### `take(untilReplacement:)` -Forwards events from `self` until `replacement` begins sending events. - -Returns a producer which passes through `Next`, `Failed`, and `Interrupted` -events from `self` until `replacement` sends an event, at which point the -returned producer will send that event and switch to passing through events -from `replacement` instead, regardless of whether `self` has sent events -already. -*/ -scopedExample("`take(untilReplacement:)`") { - let (replacementSignal, incomingReplacementObserver) = Signal.pipe() - - let baseProducer = SignalProducer { incomingObserver, _ in - incomingObserver.sendNext(1) - incomingObserver.sendNext(2) - incomingObserver.sendNext(3) - - incomingReplacementObserver.sendNext(42) - - incomingObserver.sendNext(4) - - incomingReplacementObserver.sendNext(42) - } - - baseProducer - .take(untilReplacement: replacementSignal) - .startWithNext { value in - print(value) - } -} - -/*: -### `take(last:)` -Waits until `self` completes and then forwards the final `count` values -on the returned producer. -*/ -scopedExample("`take(last:)`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .take(last: 2) - .startWithNext { value in - print(value) - } -} - -/*: -### `skipNil` -Unwraps non-`nil` values and forwards them on the returned signal, `nil` -values are dropped. -*/ -scopedExample("`skipNil`") { - SignalProducer(values: [ nil, 1, 2, nil, 3, 4, nil ]) - .skipNil() - .startWithNext { value in - print(value) - } -} - - -/*: -### `zip(with:)` -Zips elements of two producers into pairs. The elements of any Nth pair -are the Nth elements of the two input producers. -*/ -scopedExample("`zip(with:)`") { - let baseProducer = SignalProducer(values: [ 1, 2, 3, 4 ]) - let zippedProducer = SignalProducer(values: [ 42, 43 ]) - - baseProducer - .zip(with: zippedProducer) - .startWithNext { value in - print("\(value)") - } -} - -/*: -### `times` -Repeats `self` a total of `count` times. Repeating `1` times results in -an equivalent signal producer. -*/ -scopedExample("`times`") { - var counter = 0 - - SignalProducer<(), NoError> { observer, disposable in - counter += 1 - observer.sendCompleted() - } - .times(42) - .start() - - print(counter) -} - -/*: -### `retry(upTo:)` -Ignores failures up to `count` times. -*/ -scopedExample("`retry(upTo:)`") { - var tries = 0 - - SignalProducer { observer, disposable in - if tries == 0 { - tries += 1 - observer.sendFailed(NSError(domain: "retry", code: 0, userInfo: nil)) - } else { - observer.sendNext(42) - observer.sendCompleted() - } - } - .retry(upTo: 1) - .startWithResult { result in - print(result) - } -} - -/*: -### `then` -Waits for completion of `producer`, *then* forwards all events from -`replacement`. Any failure sent from `producer` is forwarded immediately, in -which case `replacement` will not be started, and none of its events will be -be forwarded. All values sent from `producer` are ignored. -*/ -scopedExample("`then`") { - let baseProducer = SignalProducer(values: [ 1, 2, 3, 4 ]) - let thenProducer = SignalProducer(value: 42) - - baseProducer - .then(thenProducer) - .startWithNext { value in - print(value) - } -} - -/*: -### `replayLazily(upTo:)` -Creates a new `SignalProducer` that will multicast values emitted by -the underlying producer, up to `capacity`. -This means that all clients of this `SignalProducer` will see the same version -of the emitted values/errors. - -The underlying `SignalProducer` will not be started until `self` is started -for the first time. When subscribing to this producer, all previous values -(up to `capacity`) will be emitted, followed by any new values. - -If you find yourself needing *the current value* (the last buffered value) -you should consider using `PropertyType` instead, which, unlike this operator, -will guarantee at compile time that there's always a buffered value. -This operator is not recommended in most cases, as it will introduce an implicit -relationship between the original client and the rest, so consider alternatives -like `PropertyType`, `SignalProducer.buffer`, or representing your stream using -a `Signal` instead. - -This operator is only recommended when you absolutely need to introduce -a layer of caching in front of another `SignalProducer`. - -This operator has the same semantics as `SignalProducer.buffer`. -*/ -scopedExample("`replayLazily(upTo:)`") { - let baseProducer = SignalProducer(values: [ 1, 2, 3, 4, 42 ]) - .replayLazily(upTo: 2) - - baseProducer.startWithNext { value in - print(value) - } - - baseProducer.startWithNext { value in - print(value) - } - - baseProducer.startWithNext { value in - print(value) - } -} - -/*: -### `flatMap(.latest)` -Maps each event from `self` to a new producer, then flattens the -resulting producers (into a producer of values), according to the -semantics of the given strategy. - -If `self` or any of the created producers fail, the returned producer -will forward that failure immediately. -*/ -scopedExample("`flatMap(.latest)`") { - SignalProducer(values: [ 1, 2, 3, 4 ]) - .flatMap(.latest) { SignalProducer(value: $0 + 3) } - .startWithNext { value in - print(value) - } -} - -/*: -### `flatMapError` -Catches any failure that may occur on the input producer, mapping to a new producer -that starts in its place. -*/ -scopedExample("`flatMapError`") { - SignalProducer(error: NSError(domain: "flatMapError", code: 42, userInfo: nil)) - .flatMapError { SignalProducer(value: $0.code) } - .startWithNext { value in - print(value) - } -} - -/*: -### `sample(with:)` -Forwards the latest value from `self` with the value from `sampler` as a tuple, -only when `sampler` sends a Next event. - -If `sampler` fires before a value has been observed on `self`, nothing happens. -Returns a producer that will send values from `self` and `sampler`, -sampled (possibly multiple times) by `sampler`, then complete once both -input producers have completed, or interrupt if either input producer is interrupted. -*/ -scopedExample("`sample(with:)`") { - let producer = SignalProducer(values: [ 1, 2, 3, 4 ]) - let sampler = SignalProducer(values: [ "a", "b" ]) - - let result = producer.sample(with: sampler) - - result.startWithNext { left, right in - print("\(left) \(right)") - } -} - -/*: -### `logEvents` -Logs all events that the receiver sends. -By default, it will print to the standard output. -*/ -scopedExample("`log events`") { - let baseProducer = SignalProducer(values: [ 1, 2, 3, 4, 42 ]) - - baseProducer - .logEvents(identifier: "Playground is fun!") - .start() -} diff --git a/ReactiveCocoa.playground/contents.xcplayground b/ReactiveCocoa.playground/contents.xcplayground deleted file mode 100644 index fa86d24d80..0000000000 --- a/ReactiveCocoa.playground/contents.xcplayground +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/ReactiveCocoa.podspec b/ReactiveCocoa.podspec new file mode 100644 index 0000000000..4106c676ac --- /dev/null +++ b/ReactiveCocoa.podspec @@ -0,0 +1,31 @@ +Pod::Spec.new do |s| + s.name = "ReactiveCocoa" + s.version = "12.0.0" + s.summary = "Streams of values over time" + s.description = <<-DESC + ReactiveCocoa (RAC) is a Cocoa framework built on top of ReactiveSwift. It provides APIs for using ReactiveSwift with Apple's Cocoa frameworks. + DESC + s.homepage = "https://github.com/ReactiveCocoa/ReactiveCocoa" + s.license = { :type => "MIT", :file => "LICENSE.md" } + s.author = "ReactiveCocoa" + + s.osx.deployment_target = "10.13" + s.ios.deployment_target = "11.0" + s.tvos.deployment_target = "11.0" + s.watchos.deployment_target = "4.0" + + s.source = { :git => "https://github.com/ReactiveCocoa/ReactiveCocoa.git", :tag => "#{s.version}" } + s.source_files = "ReactiveCocoa/*.{swift,h,m}", "ReactiveCocoa/Shared/*.{swift}", "ReactiveCocoaObjC/**/*.{h,m}" + s.public_header_files = "ReactiveCocoaObjC/include/ObjCRuntimeAliases.h" + s.osx.source_files = "ReactiveCocoa/AppKit/*.{swift}" + s.ios.source_files = "ReactiveCocoa/UIKit/*.{swift}", "ReactiveCocoa/UIKit/iOS/*.{swift}" + s.tvos.source_files = "ReactiveCocoa/UIKit/*.{swift}" + s.watchos.exclude_files = "ReactiveCocoa/Shared/*.{swift}" + s.watchos.source_files = "ReactiveCocoa/WatchKit/*.{swift}" + s.module_name = 'ReactiveCocoa' + + s.dependency 'ReactiveSwift', '~> 7.0' + + s.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS[config=Release]" => "$(inherited) -suppress-warnings" } + s.swift_versions = ['5.1', '5.2'] +end diff --git a/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoa.xcodeproj/project.pbxproj index 1ba48fa6fb..89b157c07e 100644 --- a/ReactiveCocoa.xcodeproj/project.pbxproj +++ b/ReactiveCocoa.xcodeproj/project.pbxproj @@ -7,853 +7,425 @@ objects = { /* Begin PBXBuildFile section */ - 02D2602A1C1D6DAF003ACC61 /* SignalLifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D260291C1D6DAF003ACC61 /* SignalLifetimeSpec.swift */; }; - 02D2602B1C1D6DB8003ACC61 /* SignalLifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D260291C1D6DAF003ACC61 /* SignalLifetimeSpec.swift */; }; - 314304171ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 314304151ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 314304181ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 314304161ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.m */; }; - 4A0E10FF1D2A92720065D310 /* Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* Lifetime.swift */; }; - 4A0E11001D2A92720065D310 /* Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* Lifetime.swift */; }; - 4A0E11011D2A92720065D310 /* Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* Lifetime.swift */; }; - 4A0E11021D2A92720065D310 /* Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* Lifetime.swift */; }; - 4A0E11041D2A95200065D310 /* LifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E11031D2A95200065D310 /* LifetimeSpec.swift */; }; - 4A0E11051D2A95200065D310 /* LifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E11031D2A95200065D310 /* LifetimeSpec.swift */; }; - 4A0E11061D2A95200065D310 /* LifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E11031D2A95200065D310 /* LifetimeSpec.swift */; }; - 579504331BB8A34200A5E482 /* BagSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312EF19EF2A7700984962 /* BagSpec.swift */; }; - 579504341BB8A34300A5E482 /* BagSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312EF19EF2A7700984962 /* BagSpec.swift */; }; - 57A4D1B11BA13D7A00F7D4B1 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = D871D69E1B3B29A40070F16C /* Optional.swift */; }; - 57A4D1B21BA13D7A00F7D4B1 /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D037646A19EDA41200A782A9 /* RACCompoundDisposableProvider.d */; }; - 57A4D1B31BA13D7A00F7D4B1 /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03764A319EDA41200A782A9 /* RACSignalProvider.d */; }; - 57A4D1B41BA13D7A00F7D4B1 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BE19EF2A5800984962 /* Disposable.swift */; }; - 57A4D1B61BA13D7A00F7D4B1 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B51A69A3DB00AD8286 /* Event.swift */; }; - 57A4D1B71BA13D7A00F7D4B1 /* ObjectiveCBridging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */; }; - 57A4D1B81BA13D7A00F7D4B1 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C819EF2A5800984962 /* Scheduler.swift */; }; - 57A4D1B91BA13D7A00F7D4B1 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54AF1A69A2AC00AD8286 /* Action.swift */; }; - 57A4D1BA1BA13D7A00F7D4B1 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B01A69A2AC00AD8286 /* Property.swift */; }; - 57A4D1BB1BA13D7A00F7D4B1 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B11A69A2AC00AD8286 /* Signal.swift */; }; - 57A4D1BC1BA13D7A00F7D4B1 /* SignalProducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */; }; - 57A4D1BD1BA13D7A00F7D4B1 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BB19EF2A5800984962 /* Atomic.swift */; }; - 57A4D1BE1BA13D7A00F7D4B1 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BC19EF2A5800984962 /* Bag.swift */; }; - 57A4D1BF1BA13D7A00F7D4B1 /* TupleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00004081A46864E000E7D41 /* TupleExtensions.swift */; }; - 57A4D1C01BA13D7A00F7D4B1 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */; }; - 57A4D1C11BA13D7A00F7D4B1 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037666819EDA57100A782A9 /* EXTRuntimeExtensions.m */; }; - 57A4D1C21BA13D7A00F7D4B1 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037642B19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m */; }; - 57A4D1C31BA13D7A00F7D4B1 /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643119EDA41200A782A9 /* NSData+RACSupport.m */; }; - 57A4D1C41BA13D7A00F7D4B1 /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643319EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m */; }; - 57A4D1C51BA13D7A00F7D4B1 /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643519EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m */; }; - 57A4D1C61BA13D7A00F7D4B1 /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643719EDA41200A782A9 /* NSFileHandle+RACSupport.m */; }; - 57A4D1C71BA13D7A00F7D4B1 /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643919EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m */; }; - 57A4D1C81BA13D7A00F7D4B1 /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m */; }; - 57A4D1C91BA13D7A00F7D4B1 /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m */; }; - 57A4D1CA1BA13D7A00F7D4B1 /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644119EDA41200A782A9 /* NSObject+RACDeallocating.m */; }; - 57A4D1CB1BA13D7A00F7D4B1 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644319EDA41200A782A9 /* NSObject+RACDescription.m */; }; - 57A4D1CC1BA13D7A00F7D4B1 /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644519EDA41200A782A9 /* NSObject+RACKVOWrapper.m */; }; - 57A4D1CD1BA13D7A00F7D4B1 /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644719EDA41200A782A9 /* NSObject+RACLifting.m */; }; - 57A4D1CE1BA13D7A00F7D4B1 /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644919EDA41200A782A9 /* NSObject+RACPropertySubscribing.m */; }; - 57A4D1CF1BA13D7A00F7D4B1 /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m */; }; - 57A4D1D01BA13D7A00F7D4B1 /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m */; }; - 57A4D1D11BA13D7A00F7D4B1 /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644F19EDA41200A782A9 /* NSSet+RACSequenceAdditions.m */; }; - 57A4D1D21BA13D7A00F7D4B1 /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645119EDA41200A782A9 /* NSString+RACKeyPathUtilities.m */; }; - 57A4D1D31BA13D7A00F7D4B1 /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645319EDA41200A782A9 /* NSString+RACSequenceAdditions.m */; }; - 57A4D1D41BA13D7A00F7D4B1 /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645519EDA41200A782A9 /* NSString+RACSupport.m */; }; - 57A4D1D61BA13D7A00F7D4B1 /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m */; }; - 57A4D1D71BA13D7A00F7D4B1 /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645D19EDA41200A782A9 /* RACArraySequence.m */; }; - 57A4D1D81BA13D7A00F7D4B1 /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646119EDA41200A782A9 /* RACBehaviorSubject.m */; }; - 57A4D1D91BA13D7A00F7D4B1 /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646319EDA41200A782A9 /* RACBlockTrampoline.m */; }; - 57A4D1DA1BA13D7A00F7D4B1 /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646519EDA41200A782A9 /* RACChannel.m */; }; - 57A4D1DB1BA13D7A00F7D4B1 /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646719EDA41200A782A9 /* RACCommand.m */; }; - 57A4D1DC1BA13D7A00F7D4B1 /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646919EDA41200A782A9 /* RACCompoundDisposable.m */; }; - 57A4D1DD1BA13D7A00F7D4B1 /* RACDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646C19EDA41200A782A9 /* RACDelegateProxy.m */; }; - 57A4D1DE1BA13D7A00F7D4B1 /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646E19EDA41200A782A9 /* RACDisposable.m */; }; - 57A4D1DF1BA13D7A00F7D4B1 /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647019EDA41200A782A9 /* RACDynamicSequence.m */; }; - 57A4D1E01BA13D7A00F7D4B1 /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647219EDA41200A782A9 /* RACDynamicSignal.m */; }; - 57A4D1E11BA13D7A00F7D4B1 /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647419EDA41200A782A9 /* RACEagerSequence.m */; }; - 57A4D1E21BA13D7A00F7D4B1 /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647619EDA41200A782A9 /* RACEmptySequence.m */; }; - 57A4D1E31BA13D7A00F7D4B1 /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647819EDA41200A782A9 /* RACEmptySignal.m */; }; - 57A4D1E41BA13D7A00F7D4B1 /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647A19EDA41200A782A9 /* RACErrorSignal.m */; }; - 57A4D1E51BA13D7A00F7D4B1 /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647C19EDA41200A782A9 /* RACEvent.m */; }; - 57A4D1E61BA13D7A00F7D4B1 /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647E19EDA41200A782A9 /* RACGroupedSignal.m */; }; - 57A4D1E71BA13D7A00F7D4B1 /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648019EDA41200A782A9 /* RACImmediateScheduler.m */; }; - 57A4D1E81BA13D7A00F7D4B1 /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648219EDA41200A782A9 /* RACIndexSetSequence.m */; }; - 57A4D1E91BA13D7A00F7D4B1 /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648419EDA41200A782A9 /* RACKVOChannel.m */; }; - 57A4D1EA1BA13D7A00F7D4B1 /* RACKVOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */; }; - 57A4D1EB1BA13D7A00F7D4B1 /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648619EDA41200A782A9 /* RACKVOTrampoline.m */; }; - 57A4D1EC1BA13D7A00F7D4B1 /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648819EDA41200A782A9 /* RACMulticastConnection.m */; }; - 57A4D1EE1BA13D7A00F7D4B1 /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648D19EDA41200A782A9 /* RACPassthroughSubscriber.m */; }; - 57A4D1EF1BA13D7A00F7D4B1 /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648F19EDA41200A782A9 /* RACQueueScheduler.m */; }; - 57A4D1F01BA13D7A00F7D4B1 /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649219EDA41200A782A9 /* RACReplaySubject.m */; }; - 57A4D1F11BA13D7A00F7D4B1 /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649419EDA41200A782A9 /* RACReturnSignal.m */; }; - 57A4D1F21BA13D7A00F7D4B1 /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649619EDA41200A782A9 /* RACScheduler.m */; }; - 57A4D1F31BA13D7A00F7D4B1 /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649A19EDA41200A782A9 /* RACScopedDisposable.m */; }; - 57A4D1F41BA13D7A00F7D4B1 /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649C19EDA41200A782A9 /* RACSequence.m */; }; - 57A4D1F51BA13D7A00F7D4B1 /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649E19EDA41200A782A9 /* RACSerialDisposable.m */; }; - 57A4D1F61BA13D7A00F7D4B1 /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A019EDA41200A782A9 /* RACSignal.m */; }; - 57A4D1F71BA13D7A00F7D4B1 /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A219EDA41200A782A9 /* RACSignal+Operations.m */; }; - 57A4D1F81BA13D7A00F7D4B1 /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A519EDA41200A782A9 /* RACSignalSequence.m */; }; - 57A4D1F91BA13D7A00F7D4B1 /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A719EDA41200A782A9 /* RACStream.m */; }; - 57A4D1FA1BA13D7A00F7D4B1 /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AA19EDA41200A782A9 /* RACStringSequence.m */; }; - 57A4D1FB1BA13D7A00F7D4B1 /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AC19EDA41200A782A9 /* RACSubject.m */; }; - 57A4D1FC1BA13D7A00F7D4B1 /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AE19EDA41200A782A9 /* RACSubscriber.m */; }; - 57A4D1FD1BA13D7A00F7D4B1 /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B119EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m */; }; - 57A4D1FE1BA13D7A00F7D4B1 /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B319EDA41200A782A9 /* RACSubscriptionScheduler.m */; }; - 57A4D1FF1BA13D7A00F7D4B1 /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B519EDA41200A782A9 /* RACTargetQueueScheduler.m */; }; - 57A4D2001BA13D7A00F7D4B1 /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B719EDA41200A782A9 /* RACTestScheduler.m */; }; - 57A4D2011BA13D7A00F7D4B1 /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B919EDA41200A782A9 /* RACTuple.m */; }; - 57A4D2021BA13D7A00F7D4B1 /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BB19EDA41200A782A9 /* RACTupleSequence.m */; }; - 57A4D2031BA13D7A00F7D4B1 /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BD19EDA41200A782A9 /* RACUnarySequence.m */; }; - 57A4D2041BA13D7A00F7D4B1 /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BF19EDA41200A782A9 /* RACUnit.m */; }; - 57A4D2051BA13D7A00F7D4B1 /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C119EDA41200A782A9 /* RACValueTransformer.m */; }; - 57A4D2081BA13D7A00F7D4B1 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; }; + 004FD0071E26CDB300A03A82 /* NSButtonSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004FD0061E26CDB300A03A82 /* NSButtonSpec.swift */; }; + 006518761E26865800C3139A /* NSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006518751E26865800C3139A /* NSButton.swift */; }; + 3B30EE8C1E7BE529007CC8EF /* DeprecationsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B30EE8B1E7BE529007CC8EF /* DeprecationsSpec.swift */; }; + 3B30EE8D1E7BE529007CC8EF /* DeprecationsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B30EE8B1E7BE529007CC8EF /* DeprecationsSpec.swift */; }; + 3B30EE8E1E7BE529007CC8EF /* DeprecationsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B30EE8B1E7BE529007CC8EF /* DeprecationsSpec.swift */; }; + 3BCAAC7A1DEE19BC00B30335 /* UIRefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCAAC791DEE19BC00B30335 /* UIRefreshControl.swift */; }; + 3BCAAC7D1DEE1A2D00B30335 /* UIRefreshControlSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCAAC7B1DEE1A2300B30335 /* UIRefreshControlSpec.swift */; }; + 419139461DB910570043C9D1 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419139431DB910570043C9D1 /* UIGestureRecognizer.swift */; }; + 419139491DB910570043C9D1 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419139431DB910570043C9D1 /* UIGestureRecognizer.swift */; }; + 4191394E1DBA01A00043C9D1 /* UIGestureRecognizerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4191394B1DBA002C0043C9D1 /* UIGestureRecognizerSpec.swift */; }; + 4A0E10FF1D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* AnyObject+Lifetime.swift */; }; + 4A0E11001D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* AnyObject+Lifetime.swift */; }; + 4A0E11011D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* AnyObject+Lifetime.swift */; }; + 4A0E11021D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* AnyObject+Lifetime.swift */; }; + 4ABEFE1F1DCFCEF60066A8C2 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFDE11DCFC8560066A8C2 /* UITableView.swift */; }; + 4ABEFE201DCFCEF80066A8C2 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFDE11DCFC8560066A8C2 /* UITableView.swift */; }; + 4ABEFE211DCFCF090066A8C2 /* UITableViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFDE31DCFCCD70066A8C2 /* UITableViewSpec.swift */; }; + 4ABEFE221DCFCF0A0066A8C2 /* UITableViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFDE31DCFCCD70066A8C2 /* UITableViewSpec.swift */; }; + 4ABEFE251DCFCF630066A8C2 /* UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE231DCFCF5C0066A8C2 /* UICollectionView.swift */; }; + 4ABEFE261DCFCF640066A8C2 /* UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE231DCFCF5C0066A8C2 /* UICollectionView.swift */; }; + 4ABEFE281DCFCFA90066A8C2 /* UICollectionViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE271DCFCFA90066A8C2 /* UICollectionViewSpec.swift */; }; + 4ABEFE291DCFCFA90066A8C2 /* UICollectionViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE271DCFCFA90066A8C2 /* UICollectionViewSpec.swift */; }; + 4ABEFE2B1DCFD0030066A8C2 /* NSTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE2A1DCFD0030066A8C2 /* NSTableView.swift */; }; + 4ABEFE2E1DCFD01F0066A8C2 /* NSTableViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE2C1DCFD0180066A8C2 /* NSTableViewSpec.swift */; }; + 4ABEFE301DCFD0530066A8C2 /* NSCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE2F1DCFD0530066A8C2 /* NSCollectionView.swift */; }; + 4ABEFE331DCFD0630066A8C2 /* NSCollectionViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE311DCFD05F0066A8C2 /* NSCollectionViewSpec.swift */; }; + 4EE6372E2090EEFA00ECD02A /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE6372D2090EEFA00ECD02A /* UIViewController.swift */; }; + 4EE6372F2090EEFA00ECD02A /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE6372D2090EEFA00ECD02A /* UIViewController.swift */; }; + 4EE637332090EFDD00ECD02A /* UIViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE637302090EFD300ECD02A /* UIViewControllerSpec.swift */; }; + 4EE637342090EFDF00ECD02A /* UIViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE637302090EFD300ECD02A /* UIViewControllerSpec.swift */; }; + 4EE637362090F92600ECD02A /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE637352090F92600ECD02A /* UIApplication.swift */; }; + 4EE637372090F92600ECD02A /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE637352090F92600ECD02A /* UIApplication.swift */; }; + 531866F81DD7920400D1285F /* UIStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531866F71DD7920400D1285F /* UIStepper.swift */; }; + 531866FA1DD7925600D1285F /* UIStepperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531866F91DD7925600D1285F /* UIStepperSpec.swift */; }; + 537EC08121A950CA00D6EE18 /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537EC08021A950CA00D6EE18 /* NSView.swift */; }; + 537EC08321A9557400D6EE18 /* NSViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537EC08221A9557400D6EE18 /* NSViewSpec.swift */; }; + 538DCB791DCA5E6C00332880 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538DCB781DCA5E6C00332880 /* NSLayoutConstraint.swift */; }; + 538DCB7A1DCA5E6C00332880 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538DCB781DCA5E6C00332880 /* NSLayoutConstraint.swift */; }; + 538DCB7B1DCA5E6C00332880 /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538DCB781DCA5E6C00332880 /* NSLayoutConstraint.swift */; }; + 538DCB7D1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538DCB7C1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift */; }; + 538DCB7E1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538DCB7C1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift */; }; + 538DCB7F1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538DCB7C1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift */; }; + 53AC46CC1DD6F97400C799E1 /* UISlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AC46CB1DD6F97400C799E1 /* UISlider.swift */; }; + 53AC46CF1DD6FC0000C799E1 /* UISliderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AC46CE1DD6FC0000C799E1 /* UISliderSpec.swift */; }; + 57911A0D270C43AE00E26A24 /* UISwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F71D93E9F100ACF44C /* UISwitch.swift */; }; + 57911A0E270C43B200E26A24 /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EC1D93E9F100ACF44C /* UIActivityIndicatorView.swift */; }; + 57911A0F270C43B600E26A24 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05ED1D93E9F100ACF44C /* UIBarButtonItem.swift */; }; + 57911A10270C43BA00E26A24 /* UIBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EE1D93E9F100ACF44C /* UIBarItem.swift */; }; + 57911A11270C43BC00E26A24 /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EF1D93E9F100ACF44C /* UIButton.swift */; }; + 57911A12270C43BF00E26A24 /* UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE231DCFCF5C0066A8C2 /* UICollectionView.swift */; }; + 57911A13270C43C100E26A24 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F11D93E9F100ACF44C /* UIControl.swift */; }; + 57911A14270C43C400E26A24 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419139431DB910570043C9D1 /* UIGestureRecognizer.swift */; }; + 57911A15270C43C600E26A24 /* UIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F31D93E9F100ACF44C /* UIImageView.swift */; }; + 57911A16270C43CB00E26A24 /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F41D93E9F100ACF44C /* UILabel.swift */; }; + 57911A17270C43CE00E26A24 /* UINavigationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D1C1E94ECAC002A9BCC /* UINavigationItem.swift */; }; + 57911A18270C43D400E26A24 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4335641E02AC7600AC88DD /* UIScrollView.swift */; }; + 57911A19270C43D800E26A24 /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F61D93E9F100ACF44C /* UISegmentedControl.swift */; }; + 57911A1B270C43E000E26A24 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFDE11DCFC8560066A8C2 /* UITableView.swift */; }; + 57911A1C270C43E700E26A24 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FC1D93E9F100ACF44C /* UIView.swift */; }; + 57911A1D270C43EA00E26A24 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE6372D2090EEFA00ECD02A /* UIViewController.swift */; }; + 579C7D4E2731BC43009F8A2F /* UIKitReusableComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA221DB8F51C0013AAEA /* UIKitReusableComponents.swift */; }; 57A4D20A1BA13D7A00F7D4B1 /* ReactiveCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D20B1BA13D7A00F7D4B1 /* EXTKeyPathCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666619EDA57100A782A9 /* EXTKeyPathCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D20C1BA13D7A00F7D4B1 /* EXTScope.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666919EDA57100A782A9 /* EXTScope.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D20D1BA13D7A00F7D4B1 /* metamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666A19EDA57100A782A9 /* metamacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D20E1BA13D7A00F7D4B1 /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037642A19EDA41200A782A9 /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D20F1BA13D7A00F7D4B1 /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643019EDA41200A782A9 /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2101BA13D7A00F7D4B1 /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643219EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2111BA13D7A00F7D4B1 /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643419EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2121BA13D7A00F7D4B1 /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643619EDA41200A782A9 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2131BA13D7A00F7D4B1 /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643819EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2141BA13D7A00F7D4B1 /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2151BA13D7A00F7D4B1 /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644019EDA41200A782A9 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2161BA13D7A00F7D4B1 /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644619EDA41200A782A9 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2171BA13D7A00F7D4B1 /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644819EDA41200A782A9 /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2181BA13D7A00F7D4B1 /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644A19EDA41200A782A9 /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2191BA13D7A00F7D4B1 /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D21A1BA13D7A00F7D4B1 /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644E19EDA41200A782A9 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D21B1BA13D7A00F7D4B1 /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645219EDA41200A782A9 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D21C1BA13D7A00F7D4B1 /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645419EDA41200A782A9 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D21E1BA13D7A00F7D4B1 /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645A19EDA41200A782A9 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D21F1BA13D7A00F7D4B1 /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646019EDA41200A782A9 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2201BA13D7A00F7D4B1 /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646419EDA41200A782A9 /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2211BA13D7A00F7D4B1 /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646619EDA41200A782A9 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2221BA13D7A00F7D4B1 /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646819EDA41200A782A9 /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2231BA13D7A00F7D4B1 /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646D19EDA41200A782A9 /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2241BA13D7A00F7D4B1 /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647B19EDA41200A782A9 /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2251BA13D7A00F7D4B1 /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647D19EDA41200A782A9 /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2261BA13D7A00F7D4B1 /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648319EDA41200A782A9 /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2271BA13D7A00F7D4B1 /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648719EDA41200A782A9 /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2281BA13D7A00F7D4B1 /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648E19EDA41200A782A9 /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2291BA13D7A00F7D4B1 /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649019EDA41200A782A9 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D22A1BA13D7A00F7D4B1 /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649119EDA41200A782A9 /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D22B1BA13D7A00F7D4B1 /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649519EDA41200A782A9 /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D22C1BA13D7A00F7D4B1 /* RACScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649819EDA41200A782A9 /* RACScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D22D1BA13D7A00F7D4B1 /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649919EDA41200A782A9 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D22E1BA13D7A00F7D4B1 /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649B19EDA41200A782A9 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D22F1BA13D7A00F7D4B1 /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649D19EDA41200A782A9 /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2301BA13D7A00F7D4B1 /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649F19EDA41200A782A9 /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2311BA13D7A00F7D4B1 /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A119EDA41200A782A9 /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2321BA13D7A00F7D4B1 /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A619EDA41200A782A9 /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2331BA13D7A00F7D4B1 /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AB19EDA41200A782A9 /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2341BA13D7A00F7D4B1 /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AD19EDA41200A782A9 /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2351BA13D7A00F7D4B1 /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B019EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2361BA13D7A00F7D4B1 /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B419EDA41200A782A9 /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2371BA13D7A00F7D4B1 /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B619EDA41200A782A9 /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2381BA13D7A00F7D4B1 /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B819EDA41200A782A9 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57A4D2391BA13D7A00F7D4B1 /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764BE19EDA41200A782A9 /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57D4768D1C42063C00EFE697 /* UIControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764CD19EDA41200A782A9 /* UIControl+RACSignalSupport.m */; }; - 57D476901C4206D400EFE697 /* UIControl+RACSignalSupportPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764CF19EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.m */; }; - 57D476911C4206DA00EFE697 /* UIGestureRecognizer+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764D319EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.m */; }; - 57D476921C4206DF00EFE697 /* UISegmentedControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764D919EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.m */; }; - 57D476951C4206EC00EFE697 /* UITableViewCell+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E119EDA41200A782A9 /* UITableViewCell+RACSignalSupport.m */; }; - 57D476961C4206EC00EFE697 /* UITableViewHeaderFooterView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E319EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.m */; }; - 57D476971C4206EC00EFE697 /* UITextField+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E519EDA41200A782A9 /* UITextField+RACSignalSupport.m */; }; - 57D476981C4206EC00EFE697 /* UITextView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E719EDA41200A782A9 /* UITextView+RACSignalSupport.m */; }; - 57D4769A1C4206F200EFE697 /* UIButton+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C919EDA41200A782A9 /* UIButton+RACCommandSupport.m */; }; - 57D4769B1C4206F200EFE697 /* UICollectionReusableView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764CB19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.m */; }; - 57DC89A01C5066D400E367B7 /* UIGestureRecognizer+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764D219EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A11C50672B00E367B7 /* UIControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764CC19EDA41200A782A9 /* UIControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A21C50673C00E367B7 /* UISegmentedControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764D819EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A31C50674300E367B7 /* UITableViewCell+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E019EDA41200A782A9 /* UITableViewCell+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A41C50674D00E367B7 /* UITableViewHeaderFooterView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E219EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A51C50675700E367B7 /* UITextField+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E419EDA41200A782A9 /* UITextField+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A61C50675F00E367B7 /* UITextView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E619EDA41200A782A9 /* UITextView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A71C50679700E367B7 /* UIButton+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764C819EDA41200A782A9 /* UIButton+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 57DC89A81C50679E00E367B7 /* UICollectionReusableView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764CA19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7A7065811A3F88B8001E8354 /* RACKVOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */; }; - 7A7065821A3F88B8001E8354 /* RACKVOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */; }; - 7A7065841A3F8967001E8354 /* RACKVOProxySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */; }; - 7A7065851A3F8967001E8354 /* RACKVOProxySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */; }; + 57C885112A47756D00FC133C /* ReactiveCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 57C885122A47756D00FC133C /* ObjCRuntimeAliases.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A1D06751D9415FB00ACF44C /* ObjCRuntimeAliases.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 57C885142A47756D00FC133C /* UINavigationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D1C1E94ECAC002A9BCC /* UINavigationItem.swift */; }; + 57C885152A47756D00FC133C /* UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFE231DCFCF5C0066A8C2 /* UICollectionView.swift */; }; + 57C885162A47756D00FC133C /* UIBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EE1D93E9F100ACF44C /* UIBarItem.swift */; }; + 57C885182A47756D00FC133C /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FA1D93E9F100ACF44C /* UITextField.swift */; }; + 57C885192A47756D00FC133C /* ReactiveSwift+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A90374E1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift */; }; + 57C8851A2A47756D00FC133C /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB15C791E26CD9A00997378 /* Deprecations+Removals.swift */; }; + 57C8851B2A47756D00FC133C /* UIKitReusableComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA221DB8F51C0013AAEA /* UIKitReusableComponents.swift */; }; + 57C8851C2A47756D00FC133C /* DelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7488471E3B8ACE00CD0317 /* DelegateProxy.swift */; }; + 57C8851D2A47756D00FC133C /* UITextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FB1D93E9F100ACF44C /* UITextView.swift */; }; + 57C8851E2A47756D00FC133C /* UISearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF775E1DFAD8A50058006E /* UISearchBar.swift */; }; + 57C8851F2A47756D00FC133C /* UIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F31D93E9F100ACF44C /* UIImageView.swift */; }; + 57C885202A47756D00FC133C /* NSObject+BindingTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0EA741D9A7FF700F27DDF /* NSObject+BindingTarget.swift */; }; + 57C885212A47756D00FC133C /* DynamicProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */; }; + 57C885222A47756D00FC133C /* ObjC+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE7C2A31DDD7F5100F7534C /* ObjC+Messages.swift */; }; + 57C885232A47756D00FC133C /* CocoaTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2E425D1DAA6737006D909F /* CocoaTarget.swift */; }; + 57C885242A47756D00FC133C /* UIKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AAD49871DED2C350068EC9B /* UIKeyboard.swift */; }; + 57C885252A47756D00FC133C /* UISlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AC46CB1DD6F97400C799E1 /* UISlider.swift */; }; + 57C885262A47756D00FC133C /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE637352090F92600ECD02A /* UIApplication.swift */; }; + 57C885272A47756D00FC133C /* ObjC+RuntimeSubclassing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD801DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift */; }; + 57C885282A47756D00FC133C /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FC1D93E9F100ACF44C /* UIView.swift */; }; + 57C885292A47756D00FC133C /* UIDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F21D93E9F100ACF44C /* UIDatePicker.swift */; }; + 57C8852A2A47756D00FC133C /* UIResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8BA70207CD7090031733D /* UIResponder.swift */; }; + 57C8852B2A47756D00FC133C /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05DF1D93E99100ACF44C /* NSObject+Association.swift */; }; + 57C8852C2A47756D00FC133C /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F11D93E9F100ACF44C /* UIControl.swift */; }; + 57C8852D2A47756D00FC133C /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EF1D93E9F100ACF44C /* UIButton.swift */; }; + 57C8852E2A47756D00FC133C /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE6372D2090EEFA00ECD02A /* UIViewController.swift */; }; + 57C8852F2A47756D00FC133C /* NSObject+ObjCRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD8E1DDE29F800531FCF /* NSObject+ObjCRuntime.swift */; }; + 57C885302A47756D00FC133C /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419139431DB910570043C9D1 /* UIGestureRecognizer.swift */; }; + 57C885312A47756D00FC133C /* ObjC+Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD771DDE03DE00531FCF /* ObjC+Runtime.swift */; }; + 57C885322A47756D00FC133C /* UIPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE145881E43991A00208736 /* UIPickerView.swift */; }; + 57C885332A47756D00FC133C /* UISwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F71D93E9F100ACF44C /* UISwitch.swift */; }; + 57C885342A47756D00FC133C /* NSObject+Intercepting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D065A1D93EC6E00ACF44C /* NSObject+Intercepting.swift */; }; + 57C885362A47756D00FC133C /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F41D93E9F100ACF44C /* UILabel.swift */; }; + 57C885372A47756D00FC133C /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4335641E02AC7600AC88DD /* UIScrollView.swift */; }; + 57C885382A47756D00FC133C /* AnyObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* AnyObject+Lifetime.swift */; }; + 57C885392A47756D00FC133C /* NSObject+ReactiveExtensionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A901DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift */; }; + 57C8853A2A47756D00FC133C /* Synchronizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A41DC0001C001E11F7 /* Synchronizing.swift */; }; + 57C8853B2A47756D00FC133C /* NSObject+KeyValueObserving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */; }; + 57C8853C2A47756D00FC133C /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABEFDE11DCFC8560066A8C2 /* UITableView.swift */; }; + 57C8853D2A47756D00FC133C /* UIProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F51D93E9F100ACF44C /* UIProgressView.swift */; }; + 57C8853E2A47756D00FC133C /* UIStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531866F71DD7920400D1285F /* UIStepper.swift */; }; + 57C8853F2A47756D00FC133C /* UITabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D241E94F335002A9BCC /* UITabBarItem.swift */; }; + 57C885402A47756D00FC133C /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538DCB781DCA5E6C00332880 /* NSLayoutConstraint.swift */; }; + 57C885422A47756D00FC133C /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05ED1D93E9F100ACF44C /* UIBarButtonItem.swift */; }; + 57C885432A47756D00FC133C /* ObjCRuntimeAliases.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD971DDE7A2200531FCF /* ObjCRuntimeAliases.m */; }; + 57C885442A47756D00FC133C /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EC1D93E9F100ACF44C /* UIActivityIndicatorView.swift */; }; + 57C885462A47756D00FC133C /* ObjC+Selector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A21A1DE00D09001739B3 /* ObjC+Selector.swift */; }; + 57C885472A47756D00FC133C /* ObjC+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD891DDE153A00531FCF /* ObjC+Constants.swift */; }; + 57C885482A47756D00FC133C /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F61D93E9F100ACF44C /* UISegmentedControl.swift */; }; + 57C885492A47756D00FC133C /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A7B1DA44A9E005C2AC8 /* CocoaAction.swift */; }; + 57C8854A2A47756D00FC133C /* UIRefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCAAC791DEE19BC00B30335 /* UIRefreshControl.swift */; }; + 57C8854C2A47756D00FC133C /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 5B76DE402498EDDC00E8B4F3 /* QueueScheduler+Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B76DE3F2498EDDC00E8B4F3 /* QueueScheduler+Factory.swift */; }; + 5B76DE412498EDDC00E8B4F3 /* QueueScheduler+Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B76DE3F2498EDDC00E8B4F3 /* QueueScheduler+Factory.swift */; }; + 5B76DE422498EDDC00E8B4F3 /* QueueScheduler+Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B76DE3F2498EDDC00E8B4F3 /* QueueScheduler+Factory.swift */; }; + 654DE7B02205F9DE0048FE14 /* ReactiveCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D04725EA19E49ED7006002AA /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 654DE7B22205FA0A0048FE14 /* ReactiveCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D047260C19E49F82006002AA /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 654DE7B32205FA200048FE14 /* ReactiveCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 7A8BA0FA1FCC86FC003241C7 /* NSTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8BA0F91FCC86FC003241C7 /* NSTextView.swift */; }; 7DFBED081CDB8C9500EE435B /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */; }; 7DFBED141CDB8CE600EE435B /* test-data.json in Resources */ = {isa = PBXBuildFile; fileRef = D03766B119EDA60000A782A9 /* test-data.json */; }; 7DFBED1E1CDB8D7000EE435B /* ReactiveCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7DFBED1F1CDB8D7800EE435B /* Quick.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D037672B19EDA75D00A782A9 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7DFBED201CDB8D7D00EE435B /* Nimble.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D05E662419EDD82000904ACA /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 7DFBED211CDB8D8300EE435B /* Result.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 7DFBED221CDB8DE300EE435B /* ActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021671C1A6CD50500987861 /* ActionSpec.swift */; }; - 7DFBED231CDB8DE300EE435B /* AtomicSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312EE19EF2A7700984962 /* AtomicSpec.swift */; }; - 7DFBED241CDB8DE300EE435B /* BagSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312EF19EF2A7700984962 /* BagSpec.swift */; }; - 7DFBED251CDB8DE300EE435B /* DisposableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312F019EF2A7700984962 /* DisposableSpec.swift */; }; - 7DFBED261CDB8DE300EE435B /* FoundationExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8170FC01B100EBC004192AD /* FoundationExtensionsSpec.swift */; }; - 7DFBED271CDB8DE300EE435B /* ObjectiveCBridgingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A226101A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift */; }; - 7DFBED281CDB8DE300EE435B /* PropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* PropertySpec.swift */; }; - 7DFBED291CDB8DE300EE435B /* SchedulerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312F219EF2A7700984962 /* SchedulerSpec.swift */; }; - 7DFBED2A1CDB8DE300EE435B /* SignalLifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D260291C1D6DAF003ACC61 /* SignalLifetimeSpec.swift */; }; - 7DFBED2B1CDB8DE300EE435B /* SignalProducerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260A1A72E6C500D33B74 /* SignalProducerSpec.swift */; }; - 7DFBED2C1CDB8DE300EE435B /* SignalProducerLiftingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8024DB11B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift */; }; - 7DFBED2D1CDB8DE300EE435B /* SignalSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A226071A72E0E900D33B74 /* SignalSpec.swift */; }; - 7DFBED2E1CDB8DE300EE435B /* FlattenSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6F284F1C52626B001879D2 /* FlattenSpec.swift */; }; - 7DFBED2F1CDB8DE300EE435B /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; - 7DFBED301CDB8DE300EE435B /* TestLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79B64731CD38B2B003F2376 /* TestLogger.swift */; }; - 7DFBED321CDB8DE300EE435B /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667819EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m */; }; - 7DFBED331CDB8DE300EE435B /* NSNotificationCenterRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667919EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m */; }; - 7DFBED351CDB8DE300EE435B /* NSObjectRACDeallocatingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667B19EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m */; }; - 7DFBED361CDB8DE300EE435B /* NSObjectRACLiftingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667C19EDA60000A782A9 /* NSObjectRACLiftingSpec.m */; }; - 7DFBED381CDB8DE300EE435B /* NSObjectRACPropertySubscribingExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667E19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m */; }; - 7DFBED391CDB8DE300EE435B /* NSObjectRACPropertySubscribingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667F19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m */; }; - 7DFBED3A1CDB8DE300EE435B /* NSObjectRACSelectorSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668019EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m */; }; - 7DFBED3B1CDB8DE300EE435B /* NSStringRACKeyPathUtilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668119EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m */; }; - 7DFBED3D1CDB8DE300EE435B /* NSUserDefaultsRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m */; }; - 7DFBED3E1CDB8DE300EE435B /* RACBlockTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668619EDA60000A782A9 /* RACBlockTrampolineSpec.m */; }; - 7DFBED401CDB8DE300EE435B /* RACChannelExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668819EDA60000A782A9 /* RACChannelExamples.m */; }; - 7DFBED411CDB8DE300EE435B /* RACChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668919EDA60000A782A9 /* RACChannelSpec.m */; }; - 7DFBED421CDB8DE300EE435B /* RACCommandSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668A19EDA60000A782A9 /* RACCommandSpec.m */; }; - 7DFBED431CDB8DE300EE435B /* RACCompoundDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668B19EDA60000A782A9 /* RACCompoundDisposableSpec.m */; }; - 7DFBED451CDB8DE300EE435B /* RACControlCommandExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668D19EDA60000A782A9 /* RACControlCommandExamples.m */; }; - 7DFBED461CDB8DE300EE435B /* RACDelegateProxySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668E19EDA60000A782A9 /* RACDelegateProxySpec.m */; }; - 7DFBED471CDB8DE300EE435B /* RACDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668F19EDA60000A782A9 /* RACDisposableSpec.m */; }; - 7DFBED481CDB8DE300EE435B /* RACEventSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669019EDA60000A782A9 /* RACEventSpec.m */; }; - 7DFBED491CDB8DE300EE435B /* RACKVOChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669119EDA60000A782A9 /* RACKVOChannelSpec.m */; }; - 7DFBED4A1CDB8DE300EE435B /* RACKVOProxySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */; }; - 7DFBED4B1CDB8DE300EE435B /* RACKVOWrapperSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669219EDA60000A782A9 /* RACKVOWrapperSpec.m */; }; - 7DFBED4C1CDB8DE300EE435B /* RACMulticastConnectionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669319EDA60000A782A9 /* RACMulticastConnectionSpec.m */; }; - 7DFBED4E1CDB8DE300EE435B /* RACPropertySignalExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669519EDA60000A782A9 /* RACPropertySignalExamples.m */; }; - 7DFBED4F1CDB8DE300EE435B /* RACSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669619EDA60000A782A9 /* RACSchedulerSpec.m */; }; - 7DFBED501CDB8DE300EE435B /* RACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669719EDA60000A782A9 /* RACSequenceAdditionsSpec.m */; }; - 7DFBED521CDB8DE300EE435B /* RACSequenceExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669919EDA60000A782A9 /* RACSequenceExamples.m */; }; - 7DFBED531CDB8DE300EE435B /* RACSequenceSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669A19EDA60000A782A9 /* RACSequenceSpec.m */; }; - 7DFBED541CDB8DE300EE435B /* RACSerialDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669B19EDA60000A782A9 /* RACSerialDisposableSpec.m */; }; - 7DFBED551CDB8DE300EE435B /* RACSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669C19EDA60000A782A9 /* RACSignalSpec.m */; }; - 7DFBED571CDB8DE300EE435B /* RACStreamExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A019EDA60000A782A9 /* RACStreamExamples.m */; }; - 7DFBED591CDB8DE300EE435B /* RACSubclassObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A219EDA60000A782A9 /* RACSubclassObject.m */; }; - 7DFBED5A1CDB8DE300EE435B /* RACSubjectSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A319EDA60000A782A9 /* RACSubjectSpec.m */; }; - 7DFBED5C1CDB8DE300EE435B /* RACSubscriberExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A519EDA60000A782A9 /* RACSubscriberExamples.m */; }; - 7DFBED5D1CDB8DE300EE435B /* RACSubscriberSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A619EDA60000A782A9 /* RACSubscriberSpec.m */; }; - 7DFBED5E1CDB8DE300EE435B /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A719EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m */; }; - 7DFBED5F1CDB8DE300EE435B /* RACTargetQueueSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A819EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m */; }; - 7DFBED601CDB8DE300EE435B /* RACTupleSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B019EDA60000A782A9 /* RACTupleSpec.m */; }; - 7DFBED631CDB8DE300EE435B /* UIBarButtonItemRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B419EDA60000A782A9 /* UIBarButtonItemRACSupportSpec.m */; }; - 7DFBED641CDB8DE300EE435B /* UIButtonRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B519EDA60000A782A9 /* UIButtonRACSupportSpec.m */; }; - 7DFBED671CDB8DE300EE435B /* RACTestExampleScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131819EF2D9700984962 /* RACTestExampleScheduler.m */; }; - 7DFBED691CDB8DE300EE435B /* RACTestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131A19EF2D9700984962 /* RACTestObject.m */; }; - 7DFBED6A1CDB8DE300EE435B /* RACTestSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131B19EF2D9700984962 /* RACTestSchedulerSpec.m */; }; - 7DFBED6C1CDB8DE300EE435B /* RACTestUIButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131D19EF2D9700984962 /* RACTestUIButton.m */; }; + 7DFBED281CDB8DE300EE435B /* DynamicPropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.swift */; }; 7DFBED6D1CDB8F7D00EE435B /* SignalProducerNimbleMatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */; }; - 7DFBED6E1CDB918900EE435B /* UIBarButtonItem+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C719EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m */; }; - 7DFBED6F1CDB926400EE435B /* UIBarButtonItem+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764C619EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 834DE1011E4109750099F4E5 /* NSImageViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DE1001E4109750099F4E5 /* NSImageViewSpec.swift */; }; + 834DE1121E4120340099F4E5 /* NSSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DE1111E4120340099F4E5 /* NSSegmentedControl.swift */; }; + 834DE1141E4122910099F4E5 /* NSSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834DE1131E4122910099F4E5 /* NSSlider.swift */; }; + 8392D8FD1DB93E5E00504ED4 /* NSImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8392D8FC1DB93E5E00504ED4 /* NSImageView.swift */; }; + 84ADBDAB26660EA800ACE342 /* UIImpactFeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ADBDA826660EA800ACE342 /* UIImpactFeedbackGenerator.swift */; }; + 84ADBDB026660EE800ACE342 /* UINotificationFeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ADBDAE26660EE800ACE342 /* UINotificationFeedbackGenerator.swift */; }; + 84ADBDB126660EE800ACE342 /* UISelectionFeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ADBDAF26660EE800ACE342 /* UISelectionFeedbackGenerator.swift */; }; + 9A0726F31E912B610081F3F7 /* ActionProxySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0726F21E912B610081F3F7 /* ActionProxySpec.swift */; }; + 9A16755D1F80DE1C00B63650 /* MKMapViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A6BED51DD4BD2C0016C058 /* MKMapViewSpec.swift */; }; + 9A1D05E01D93E99100ACF44C /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05DF1D93E99100ACF44C /* NSObject+Association.swift */; }; + 9A1D05E11D93E99100ACF44C /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05DF1D93E99100ACF44C /* NSObject+Association.swift */; }; + 9A1D05E21D93E99100ACF44C /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05DF1D93E99100ACF44C /* NSObject+Association.swift */; }; + 9A1D05E31D93E99100ACF44C /* NSObject+Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05DF1D93E99100ACF44C /* NSObject+Association.swift */; }; + 9A1D05FF1D93EA0000ACF44C /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EC1D93E9F100ACF44C /* UIActivityIndicatorView.swift */; }; + 9A1D06001D93EA0000ACF44C /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05ED1D93E9F100ACF44C /* UIBarButtonItem.swift */; }; + 9A1D06011D93EA0000ACF44C /* UIBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EE1D93E9F100ACF44C /* UIBarItem.swift */; }; + 9A1D06021D93EA0000ACF44C /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EF1D93E9F100ACF44C /* UIButton.swift */; }; + 9A1D06041D93EA0000ACF44C /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F11D93E9F100ACF44C /* UIControl.swift */; }; + 9A1D06051D93EA0000ACF44C /* UIDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F21D93E9F100ACF44C /* UIDatePicker.swift */; }; + 9A1D06061D93EA0000ACF44C /* UIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F31D93E9F100ACF44C /* UIImageView.swift */; }; + 9A1D06071D93EA0000ACF44C /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F41D93E9F100ACF44C /* UILabel.swift */; }; + 9A1D06081D93EA0000ACF44C /* UIProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F51D93E9F100ACF44C /* UIProgressView.swift */; }; + 9A1D06091D93EA0000ACF44C /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F61D93E9F100ACF44C /* UISegmentedControl.swift */; }; + 9A1D060A1D93EA0000ACF44C /* UISwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F71D93E9F100ACF44C /* UISwitch.swift */; }; + 9A1D060D1D93EA0000ACF44C /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FA1D93E9F100ACF44C /* UITextField.swift */; }; + 9A1D060E1D93EA0000ACF44C /* UITextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FB1D93E9F100ACF44C /* UITextView.swift */; }; + 9A1D060F1D93EA0000ACF44C /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FC1D93E9F100ACF44C /* UIView.swift */; }; + 9A1D06111D93EA0100ACF44C /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EC1D93E9F100ACF44C /* UIActivityIndicatorView.swift */; }; + 9A1D06121D93EA0100ACF44C /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05ED1D93E9F100ACF44C /* UIBarButtonItem.swift */; }; + 9A1D06131D93EA0100ACF44C /* UIBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EE1D93E9F100ACF44C /* UIBarItem.swift */; }; + 9A1D06141D93EA0100ACF44C /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05EF1D93E9F100ACF44C /* UIButton.swift */; }; + 9A1D06161D93EA0100ACF44C /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F11D93E9F100ACF44C /* UIControl.swift */; }; + 9A1D06181D93EA0100ACF44C /* UIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F31D93E9F100ACF44C /* UIImageView.swift */; }; + 9A1D06191D93EA0100ACF44C /* UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F41D93E9F100ACF44C /* UILabel.swift */; }; + 9A1D061A1D93EA0100ACF44C /* UIProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F51D93E9F100ACF44C /* UIProgressView.swift */; }; + 9A1D061B1D93EA0100ACF44C /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05F61D93E9F100ACF44C /* UISegmentedControl.swift */; }; + 9A1D061F1D93EA0100ACF44C /* UITextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FA1D93E9F100ACF44C /* UITextField.swift */; }; + 9A1D06201D93EA0100ACF44C /* UITextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FB1D93E9F100ACF44C /* UITextView.swift */; }; + 9A1D06211D93EA0100ACF44C /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D05FC1D93E9F100ACF44C /* UIView.swift */; }; + 9A1D06361D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06241D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift */; }; + 9A1D06371D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06241D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift */; }; + 9A1D06381D93EA7E00ACF44C /* UIBarButtonItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06251D93EA7E00ACF44C /* UIBarButtonItemSpec.swift */; }; + 9A1D06391D93EA7E00ACF44C /* UIBarButtonItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06251D93EA7E00ACF44C /* UIBarButtonItemSpec.swift */; }; + 9A1D063A1D93EA7E00ACF44C /* UIButtonSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06261D93EA7E00ACF44C /* UIButtonSpec.swift */; }; + 9A1D063B1D93EA7E00ACF44C /* UIButtonSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06261D93EA7E00ACF44C /* UIButtonSpec.swift */; }; + 9A1D063E1D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06281D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift */; }; + 9A1D063F1D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06281D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift */; }; + 9A1D06401D93EA7E00ACF44C /* UIControlSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06291D93EA7E00ACF44C /* UIControlSpec.swift */; }; + 9A1D06411D93EA7E00ACF44C /* UIControlSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06291D93EA7E00ACF44C /* UIControlSpec.swift */; }; + 9A1D06421D93EA7E00ACF44C /* UIDatePickerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062A1D93EA7E00ACF44C /* UIDatePickerSpec.swift */; }; + 9A1D06441D93EA7E00ACF44C /* UIImageViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062B1D93EA7E00ACF44C /* UIImageViewSpec.swift */; }; + 9A1D06451D93EA7E00ACF44C /* UIImageViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062B1D93EA7E00ACF44C /* UIImageViewSpec.swift */; }; + 9A1D06461D93EA7E00ACF44C /* UILabelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062C1D93EA7E00ACF44C /* UILabelSpec.swift */; }; + 9A1D06471D93EA7E00ACF44C /* UILabelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062C1D93EA7E00ACF44C /* UILabelSpec.swift */; }; + 9A1D06481D93EA7E00ACF44C /* UIProgressViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062D1D93EA7E00ACF44C /* UIProgressViewSpec.swift */; }; + 9A1D06491D93EA7E00ACF44C /* UIProgressViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062D1D93EA7E00ACF44C /* UIProgressViewSpec.swift */; }; + 9A1D064A1D93EA7E00ACF44C /* UISegmentedControlSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062E1D93EA7E00ACF44C /* UISegmentedControlSpec.swift */; }; + 9A1D064B1D93EA7E00ACF44C /* UISegmentedControlSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062E1D93EA7E00ACF44C /* UISegmentedControlSpec.swift */; }; + 9A1D064C1D93EA7E00ACF44C /* UISwitchSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D062F1D93EA7E00ACF44C /* UISwitchSpec.swift */; }; + 9A1D06521D93EA7E00ACF44C /* UITextFieldSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06321D93EA7E00ACF44C /* UITextFieldSpec.swift */; }; + 9A1D06531D93EA7E00ACF44C /* UITextFieldSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06321D93EA7E00ACF44C /* UITextFieldSpec.swift */; }; + 9A1D06541D93EA7E00ACF44C /* UITextViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06331D93EA7E00ACF44C /* UITextViewSpec.swift */; }; + 9A1D06551D93EA7E00ACF44C /* UITextViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06331D93EA7E00ACF44C /* UITextViewSpec.swift */; }; + 9A1D06581D93EA7E00ACF44C /* UIViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06351D93EA7E00ACF44C /* UIViewSpec.swift */; }; + 9A1D06591D93EA7E00ACF44C /* UIViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D06351D93EA7E00ACF44C /* UIViewSpec.swift */; }; + 9A1D065B1D93EC6E00ACF44C /* NSObject+Intercepting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D065A1D93EC6E00ACF44C /* NSObject+Intercepting.swift */; }; + 9A1D065C1D93EC6E00ACF44C /* NSObject+Intercepting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D065A1D93EC6E00ACF44C /* NSObject+Intercepting.swift */; }; + 9A1D065E1D93EC6E00ACF44C /* NSObject+Intercepting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D065A1D93EC6E00ACF44C /* NSObject+Intercepting.swift */; }; 9A1E72BA1D4DE96500CC20C3 /* KeyValueObservingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1E72B91D4DE96500CC20C3 /* KeyValueObservingSpec.swift */; }; 9A1E72BB1D4DE96500CC20C3 /* KeyValueObservingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1E72B91D4DE96500CC20C3 /* KeyValueObservingSpec.swift */; }; 9A1E72BC1D4DE96500CC20C3 /* KeyValueObservingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1E72B91D4DE96500CC20C3 /* KeyValueObservingSpec.swift */; }; - 9A694EF31D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A694EF21D5CE02E009B05BD /* UnidirectionalBinding.swift */; }; - 9A694EF41D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A694EF21D5CE02E009B05BD /* UnidirectionalBinding.swift */; }; - 9A694EF51D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A694EF21D5CE02E009B05BD /* UnidirectionalBinding.swift */; }; - 9A694EF61D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A694EF21D5CE02E009B05BD /* UnidirectionalBinding.swift */; }; - 9ABCB1851D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */; }; - 9ABCB1861D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */; }; - 9ABCB1871D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */; }; - 9ABCB1881D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */; }; + 9A24A8451DE142A400987AF9 /* SwizzlingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A24A8431DE1429600987AF9 /* SwizzlingSpec.swift */; }; + 9A24A8461DE142A500987AF9 /* SwizzlingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A24A8431DE1429600987AF9 /* SwizzlingSpec.swift */; }; + 9A24A8471DE142A600987AF9 /* SwizzlingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A24A8431DE1429600987AF9 /* SwizzlingSpec.swift */; }; + 9A2E425E1DAA6737006D909F /* CocoaTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2E425D1DAA6737006D909F /* CocoaTarget.swift */; }; + 9A2E425F1DAA6737006D909F /* CocoaTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2E425D1DAA6737006D909F /* CocoaTarget.swift */; }; + 9A2E42601DAA6737006D909F /* CocoaTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2E425D1DAA6737006D909F /* CocoaTarget.swift */; }; + 9A2E42611DAA6737006D909F /* CocoaTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2E425D1DAA6737006D909F /* CocoaTarget.swift */; }; + 9A3C54EB21726A1200E98207 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3C54EA21726A1200E98207 /* Nimble.framework */; }; + 9A3C54ED21726A1200E98207 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3C54EC21726A1200E98207 /* Quick.framework */; }; + 9A3C54F121726BD200E98207 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3C54F021726BD200E98207 /* Nimble.framework */; }; + 9A3C54F321726BD200E98207 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3C54F221726BD200E98207 /* Quick.framework */; }; + 9A54A2111DDF5B4D001739B3 /* InterceptingPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A2101DDF5B4D001739B3 /* InterceptingPerformanceTests.swift */; }; + 9A54A2121DDF5B4D001739B3 /* InterceptingPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A2101DDF5B4D001739B3 /* InterceptingPerformanceTests.swift */; }; + 9A54A2131DDF5B4D001739B3 /* InterceptingPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A2101DDF5B4D001739B3 /* InterceptingPerformanceTests.swift */; }; + 9A54A21B1DE00D09001739B3 /* ObjC+Selector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A21A1DE00D09001739B3 /* ObjC+Selector.swift */; }; + 9A54A21C1DE00D09001739B3 /* ObjC+Selector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A21A1DE00D09001739B3 /* ObjC+Selector.swift */; }; + 9A54A21D1DE00D09001739B3 /* ObjC+Selector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A21A1DE00D09001739B3 /* ObjC+Selector.swift */; }; + 9A54A21E1DE00D09001739B3 /* ObjC+Selector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54A21A1DE00D09001739B3 /* ObjC+Selector.swift */; }; + 9A6AAA0E1DB6A4CF0013AAEA /* InterceptingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA0D1DB6A4CF0013AAEA /* InterceptingSpec.swift */; }; + 9A6AAA0F1DB6A4CF0013AAEA /* InterceptingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA0D1DB6A4CF0013AAEA /* InterceptingSpec.swift */; }; + 9A6AAA101DB6A4CF0013AAEA /* InterceptingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA0D1DB6A4CF0013AAEA /* InterceptingSpec.swift */; }; + 9A6AAA191DB808A80013AAEA /* ObjCRuntimeAliases.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A1D06751D9415FB00ACF44C /* ObjCRuntimeAliases.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9A6AAA1A1DB808A80013AAEA /* ObjCRuntimeAliases.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A1D06751D9415FB00ACF44C /* ObjCRuntimeAliases.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9A6AAA1B1DB808A90013AAEA /* ObjCRuntimeAliases.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A1D06751D9415FB00ACF44C /* ObjCRuntimeAliases.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9A6AAA1C1DB808AA0013AAEA /* ObjCRuntimeAliases.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A1D06751D9415FB00ACF44C /* ObjCRuntimeAliases.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9A6AAA231DB8F51C0013AAEA /* UIKitReusableComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA221DB8F51C0013AAEA /* UIKitReusableComponents.swift */; }; + 9A6AAA241DB8F51C0013AAEA /* UIKitReusableComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA221DB8F51C0013AAEA /* UIKitReusableComponents.swift */; }; + 9A6AAA261DB8F5280013AAEA /* AppKitReusableComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA251DB8F5280013AAEA /* AppKitReusableComponents.swift */; }; + 9A6AAA2B1DB8F85C0013AAEA /* AppKitReusableComponentsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA271DB8F7EB0013AAEA /* AppKitReusableComponentsSpec.swift */; }; + 9A6AAA2E1DB903A20013AAEA /* UIKitReusableComponentsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA291DB8F7F10013AAEA /* UIKitReusableComponentsSpec.swift */; }; + 9A6AAA2F1DB903A40013AAEA /* UIKitReusableComponentsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6AAA291DB8F7F10013AAEA /* UIKitReusableComponentsSpec.swift */; }; + 9A73DFAE216D3C550069AD76 /* MKMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A6BED11DD4BCA90016C058 /* MKMapView.swift */; }; + 9A73DFAF216D3C550069AD76 /* MKLocalSearchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91244E720389AEA0001BBCB /* MKLocalSearchRequest.swift */; }; + 9A73DFBF216D3C570069AD76 /* MKMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A6BED11DD4BCA90016C058 /* MKMapView.swift */; }; + 9A73DFC0216D3C570069AD76 /* MKLocalSearchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91244E720389AEA0001BBCB /* MKLocalSearchRequest.swift */; }; + 9A73DFE3216D3CEB0069AD76 /* MKMapViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A6BED51DD4BD2C0016C058 /* MKMapViewSpec.swift */; }; + 9A73DFFE216D3CEE0069AD76 /* MKMapViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A6BED51DD4BD2C0016C058 /* MKMapViewSpec.swift */; }; + 9A73E015216D3E660069AD76 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D04725EA19E49ED7006002AA /* ReactiveCocoa.framework */; }; + 9A73E01C216D3E700069AD76 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D047260C19E49F82006002AA /* ReactiveCocoa.framework */; }; + 9A73E036216D3FC50069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E039216D3FEB0069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E03A216D3FF10069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E03B216D3FF50069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E03C216D3FFC0069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E03D216D40070069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E03E216D400D0069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E040216D40170069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E042216D40280069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E049216D40800069AD76 /* ReactiveMapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73DFCC216D3C570069AD76 /* ReactiveMapKit.framework */; }; + 9A73E04A216D40850069AD76 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */; }; + 9A73E04C216D408D0069AD76 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E04B216D408D0069AD76 /* Nimble.framework */; }; + 9A73E04E216D408D0069AD76 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E04D216D408D0069AD76 /* Quick.framework */; }; + 9A73E04F216D40980069AD76 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E04B216D408D0069AD76 /* Nimble.framework */; }; + 9A73E050216D40980069AD76 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E04D216D408D0069AD76 /* Quick.framework */; }; + 9A73E051216D40A20069AD76 /* ReactiveMapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73DFBB216D3C550069AD76 /* ReactiveMapKit.framework */; }; + 9A73E052216D40AC0069AD76 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D047260C19E49F82006002AA /* ReactiveCocoa.framework */; }; + 9A73E053216D40C80069AD76 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D04725EA19E49ED7006002AA /* ReactiveCocoa.framework */; }; + 9A73E054216D40CD0069AD76 /* ReactiveMapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AC03A571F7CC3BF00EC33C1 /* ReactiveMapKit.framework */; }; + 9A73E055216D40D10069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E059216D40E30069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E05B216D40EC0069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E05D216D40F70069AD76 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */; }; + 9A73E05F216D41FA0069AD76 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */; }; + 9A7488481E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7488471E3B8ACE00CD0317 /* DelegateProxy.swift */; }; + 9A7488491E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7488471E3B8ACE00CD0317 /* DelegateProxy.swift */; }; + 9A74884A1E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7488471E3B8ACE00CD0317 /* DelegateProxy.swift */; }; + 9A74884B1E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7488471E3B8ACE00CD0317 /* DelegateProxy.swift */; }; + 9A7990CE1F1085D8001493A3 /* BindingTargetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7990CD1F1085D8001493A3 /* BindingTargetSpec.swift */; }; + 9A7990CF1F1085D8001493A3 /* BindingTargetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7990CD1F1085D8001493A3 /* BindingTargetSpec.swift */; }; + 9A7990D01F1085D8001493A3 /* BindingTargetSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7990CD1F1085D8001493A3 /* BindingTargetSpec.swift */; }; + 9A892D8F1E8D19BE00EA35F3 /* DelegateProxySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A892D8E1E8D19BE00EA35F3 /* DelegateProxySpec.swift */; }; + 9A892D901E8D19BE00EA35F3 /* DelegateProxySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A892D8E1E8D19BE00EA35F3 /* DelegateProxySpec.swift */; }; + 9A892D911E8D19BE00EA35F3 /* DelegateProxySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A892D8E1E8D19BE00EA35F3 /* DelegateProxySpec.swift */; }; + 9A90374F1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A90374E1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift */; }; + 9A9037501ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A90374E1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift */; }; + 9A9037511ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A90374E1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift */; }; + 9A9037521ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A90374E1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift */; }; + 9A9A129A1DC7A97100D10223 /* UIGestureRecognizerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4191394B1DBA002C0043C9D1 /* UIGestureRecognizerSpec.swift */; }; + 9A9DFEE51DA7B5500039EE1B /* NSObject+Intercepting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1D065A1D93EC6E00ACF44C /* NSObject+Intercepting.swift */; }; + 9A9DFEE91DA7EFB60039EE1B /* AssociationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9DFEE81DA7EFB60039EE1B /* AssociationSpec.swift */; }; + 9A9DFEEA1DA7EFB60039EE1B /* AssociationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9DFEE81DA7EFB60039EE1B /* AssociationSpec.swift */; }; + 9A9DFEEB1DA7EFB60039EE1B /* AssociationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9DFEE81DA7EFB60039EE1B /* AssociationSpec.swift */; }; + 9AA0BD7C1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD771DDE03DE00531FCF /* ObjC+Runtime.swift */; }; + 9AA0BD7D1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD771DDE03DE00531FCF /* ObjC+Runtime.swift */; }; + 9AA0BD7E1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD771DDE03DE00531FCF /* ObjC+Runtime.swift */; }; + 9AA0BD7F1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD771DDE03DE00531FCF /* ObjC+Runtime.swift */; }; + 9AA0BD811DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD801DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift */; }; + 9AA0BD821DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD801DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift */; }; + 9AA0BD831DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD801DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift */; }; + 9AA0BD841DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD801DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift */; }; + 9AA0BD8A1DDE153A00531FCF /* ObjC+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD891DDE153A00531FCF /* ObjC+Constants.swift */; }; + 9AA0BD8B1DDE153A00531FCF /* ObjC+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD891DDE153A00531FCF /* ObjC+Constants.swift */; }; + 9AA0BD8C1DDE153A00531FCF /* ObjC+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD891DDE153A00531FCF /* ObjC+Constants.swift */; }; + 9AA0BD8D1DDE153A00531FCF /* ObjC+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD891DDE153A00531FCF /* ObjC+Constants.swift */; }; + 9AA0BD8F1DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD8E1DDE29F800531FCF /* NSObject+ObjCRuntime.swift */; }; + 9AA0BD901DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD8E1DDE29F800531FCF /* NSObject+ObjCRuntime.swift */; }; + 9AA0BD911DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD8E1DDE29F800531FCF /* NSObject+ObjCRuntime.swift */; }; + 9AA0BD921DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD8E1DDE29F800531FCF /* NSObject+ObjCRuntime.swift */; }; + 9AA0BD981DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD971DDE7A2200531FCF /* ObjCRuntimeAliases.m */; }; + 9AA0BD991DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD971DDE7A2200531FCF /* ObjCRuntimeAliases.m */; }; + 9AA0BD9A1DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD971DDE7A2200531FCF /* ObjCRuntimeAliases.m */; }; + 9AA0BD9B1DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0BD971DDE7A2200531FCF /* ObjCRuntimeAliases.m */; }; + 9AA6A1E51F11F9B000CA2257 /* KeyValueObservingSpec+Swift4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA6A1E31F11F9A800CA2257 /* KeyValueObservingSpec+Swift4.swift */; }; + 9AA6A1E61F11F9B100CA2257 /* KeyValueObservingSpec+Swift4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA6A1E31F11F9A800CA2257 /* KeyValueObservingSpec+Swift4.swift */; }; + 9AA6A1E71F11F9B100CA2257 /* KeyValueObservingSpec+Swift4.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA6A1E31F11F9A800CA2257 /* KeyValueObservingSpec+Swift4.swift */; }; + 9AAD49881DED2C350068EC9B /* UIKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AAD49871DED2C350068EC9B /* UIKeyboard.swift */; }; + 9AAD498A1DED2F380068EC9B /* UIKeyboardSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AAD49891DED2F380068EC9B /* UIKeyboardSpec.swift */; }; + 9AADB6F41F84AECB00EFFD19 /* Swift4TestInteroperability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AADB6F31F84AECB00EFFD19 /* Swift4TestInteroperability.swift */; }; + 9AB15C7A1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB15C791E26CD9A00997378 /* Deprecations+Removals.swift */; }; + 9AB15C7B1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB15C791E26CD9A00997378 /* Deprecations+Removals.swift */; }; + 9AB15C7C1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB15C791E26CD9A00997378 /* Deprecations+Removals.swift */; }; + 9AB15C7D1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB15C791E26CD9A00997378 /* Deprecations+Removals.swift */; }; + 9AC03A611F7CC5E300EC33C1 /* MKMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A6BED11DD4BCA90016C058 /* MKMapView.swift */; }; 9AD0F06A1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */; }; 9AD0F06B1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */; }; 9AD0F06C1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */; }; 9AD0F06D1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */; }; - A1046B7A1BFF5661004D8045 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; }; - A1046B7B1BFF5662004D8045 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; }; - A1046B7C1BFF5662004D8045 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; }; - A1046B7D1BFF5664004D8045 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; }; - A9B3155E1B3940750001CB9C /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037666819EDA57100A782A9 /* EXTRuntimeExtensions.m */; }; - A9B315601B3940750001CB9C /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037642B19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m */; }; - A9B315631B3940750001CB9C /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643119EDA41200A782A9 /* NSData+RACSupport.m */; }; - A9B315641B3940750001CB9C /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643319EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m */; }; - A9B315651B3940750001CB9C /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643519EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m */; }; - A9B315661B3940750001CB9C /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643719EDA41200A782A9 /* NSFileHandle+RACSupport.m */; }; - A9B315671B3940750001CB9C /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643919EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m */; }; - A9B315681B3940750001CB9C /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m */; }; - A9B315691B3940750001CB9C /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m */; }; - A9B3156B1B3940750001CB9C /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644119EDA41200A782A9 /* NSObject+RACDeallocating.m */; }; - A9B3156C1B3940750001CB9C /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644319EDA41200A782A9 /* NSObject+RACDescription.m */; }; - A9B3156D1B3940750001CB9C /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644519EDA41200A782A9 /* NSObject+RACKVOWrapper.m */; }; - A9B3156E1B3940750001CB9C /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644719EDA41200A782A9 /* NSObject+RACLifting.m */; }; - A9B3156F1B3940750001CB9C /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644919EDA41200A782A9 /* NSObject+RACPropertySubscribing.m */; }; - A9B315701B3940750001CB9C /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m */; }; - A9B315711B3940750001CB9C /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m */; }; - A9B315721B3940750001CB9C /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644F19EDA41200A782A9 /* NSSet+RACSequenceAdditions.m */; }; - A9B315731B3940750001CB9C /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645119EDA41200A782A9 /* NSString+RACKeyPathUtilities.m */; }; - A9B315741B3940750001CB9C /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645319EDA41200A782A9 /* NSString+RACSequenceAdditions.m */; }; - A9B315751B3940750001CB9C /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645519EDA41200A782A9 /* NSString+RACSupport.m */; }; - A9B315781B3940750001CB9C /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m */; }; - A9B315791B3940750001CB9C /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645D19EDA41200A782A9 /* RACArraySequence.m */; }; - A9B3157A1B3940750001CB9C /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646119EDA41200A782A9 /* RACBehaviorSubject.m */; }; - A9B3157B1B3940750001CB9C /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646319EDA41200A782A9 /* RACBlockTrampoline.m */; }; - A9B3157C1B3940750001CB9C /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646519EDA41200A782A9 /* RACChannel.m */; }; - A9B3157D1B3940750001CB9C /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646719EDA41200A782A9 /* RACCommand.m */; }; - A9B3157E1B3940750001CB9C /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646919EDA41200A782A9 /* RACCompoundDisposable.m */; }; - A9B3157F1B3940750001CB9C /* RACDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646C19EDA41200A782A9 /* RACDelegateProxy.m */; }; - A9B315801B3940750001CB9C /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646E19EDA41200A782A9 /* RACDisposable.m */; }; - A9B315811B3940750001CB9C /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647019EDA41200A782A9 /* RACDynamicSequence.m */; }; - A9B315821B3940750001CB9C /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647219EDA41200A782A9 /* RACDynamicSignal.m */; }; - A9B315831B3940750001CB9C /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647419EDA41200A782A9 /* RACEagerSequence.m */; }; - A9B315841B3940750001CB9C /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647619EDA41200A782A9 /* RACEmptySequence.m */; }; - A9B315851B3940750001CB9C /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647819EDA41200A782A9 /* RACEmptySignal.m */; }; - A9B315861B3940750001CB9C /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647A19EDA41200A782A9 /* RACErrorSignal.m */; }; - A9B315871B3940750001CB9C /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647C19EDA41200A782A9 /* RACEvent.m */; }; - A9B315881B3940750001CB9C /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647E19EDA41200A782A9 /* RACGroupedSignal.m */; }; - A9B315891B3940750001CB9C /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648019EDA41200A782A9 /* RACImmediateScheduler.m */; }; - A9B3158A1B3940750001CB9C /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648219EDA41200A782A9 /* RACIndexSetSequence.m */; }; - A9B3158B1B3940750001CB9C /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648419EDA41200A782A9 /* RACKVOChannel.m */; }; - A9B3158C1B3940750001CB9C /* RACKVOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */; }; - A9B3158D1B3940750001CB9C /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648619EDA41200A782A9 /* RACKVOTrampoline.m */; }; - A9B3158E1B3940750001CB9C /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648819EDA41200A782A9 /* RACMulticastConnection.m */; }; - A9B315901B3940750001CB9C /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648D19EDA41200A782A9 /* RACPassthroughSubscriber.m */; }; - A9B315911B3940750001CB9C /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648F19EDA41200A782A9 /* RACQueueScheduler.m */; }; - A9B315921B3940750001CB9C /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649219EDA41200A782A9 /* RACReplaySubject.m */; }; - A9B315931B3940750001CB9C /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649419EDA41200A782A9 /* RACReturnSignal.m */; }; - A9B315941B3940750001CB9C /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649619EDA41200A782A9 /* RACScheduler.m */; }; - A9B315951B3940750001CB9C /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649A19EDA41200A782A9 /* RACScopedDisposable.m */; }; - A9B315961B3940750001CB9C /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649C19EDA41200A782A9 /* RACSequence.m */; }; - A9B315971B3940750001CB9C /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649E19EDA41200A782A9 /* RACSerialDisposable.m */; }; - A9B315981B3940750001CB9C /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A019EDA41200A782A9 /* RACSignal.m */; }; - A9B315991B3940750001CB9C /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A219EDA41200A782A9 /* RACSignal+Operations.m */; }; - A9B3159A1B3940750001CB9C /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A519EDA41200A782A9 /* RACSignalSequence.m */; }; - A9B3159B1B3940750001CB9C /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A719EDA41200A782A9 /* RACStream.m */; }; - A9B3159C1B3940750001CB9C /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AA19EDA41200A782A9 /* RACStringSequence.m */; }; - A9B3159D1B3940750001CB9C /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AC19EDA41200A782A9 /* RACSubject.m */; }; - A9B3159E1B3940750001CB9C /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AE19EDA41200A782A9 /* RACSubscriber.m */; }; - A9B3159F1B3940750001CB9C /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B119EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m */; }; - A9B315A01B3940750001CB9C /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B319EDA41200A782A9 /* RACSubscriptionScheduler.m */; }; - A9B315A11B3940750001CB9C /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B519EDA41200A782A9 /* RACTargetQueueScheduler.m */; }; - A9B315A21B3940750001CB9C /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B719EDA41200A782A9 /* RACTestScheduler.m */; }; - A9B315A31B3940750001CB9C /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B919EDA41200A782A9 /* RACTuple.m */; }; - A9B315A41B3940750001CB9C /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BB19EDA41200A782A9 /* RACTupleSequence.m */; }; - A9B315A51B3940750001CB9C /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BD19EDA41200A782A9 /* RACUnarySequence.m */; }; - A9B315A61B3940750001CB9C /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BF19EDA41200A782A9 /* RACUnit.m */; }; - A9B315A71B3940750001CB9C /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C119EDA41200A782A9 /* RACValueTransformer.m */; }; - A9B315BC1B3940810001CB9C /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BE19EF2A5800984962 /* Disposable.swift */; }; - A9B315BE1B3940810001CB9C /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B51A69A3DB00AD8286 /* Event.swift */; }; - A9B315BF1B3940810001CB9C /* ObjectiveCBridging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */; }; - A9B315C01B3940810001CB9C /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C819EF2A5800984962 /* Scheduler.swift */; }; - A9B315C11B3940810001CB9C /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54AF1A69A2AC00AD8286 /* Action.swift */; }; - A9B315C21B3940810001CB9C /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B01A69A2AC00AD8286 /* Property.swift */; }; - A9B315C31B3940810001CB9C /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B11A69A2AC00AD8286 /* Signal.swift */; }; - A9B315C41B3940810001CB9C /* SignalProducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */; }; - A9B315C51B3940810001CB9C /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BB19EF2A5800984962 /* Atomic.swift */; }; - A9B315C61B3940810001CB9C /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BC19EF2A5800984962 /* Bag.swift */; }; - A9B315C71B3940810001CB9C /* TupleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00004081A46864E000E7D41 /* TupleExtensions.swift */; }; - A9B315C81B3940810001CB9C /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */; }; - A9B315C91B3940980001CB9C /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; }; + 9AD841DC204C29B90040F0C0 /* MessageForwardingEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AD841DB204C29B90040F0C0 /* MessageForwardingEntity.m */; }; + 9AD841DD204C29B90040F0C0 /* MessageForwardingEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AD841DB204C29B90040F0C0 /* MessageForwardingEntity.m */; }; + 9AD841DE204C29B90040F0C0 /* MessageForwardingEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AD841DB204C29B90040F0C0 /* MessageForwardingEntity.m */; }; + 9ADE4A7C1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A7B1DA44A9E005C2AC8 /* CocoaAction.swift */; }; + 9ADE4A7D1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A7B1DA44A9E005C2AC8 /* CocoaAction.swift */; }; + 9ADE4A7E1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A7B1DA44A9E005C2AC8 /* CocoaAction.swift */; }; + 9ADE4A7F1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A7B1DA44A9E005C2AC8 /* CocoaAction.swift */; }; + 9ADE4A891DA6D206005C2AC8 /* NSControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A881DA6D206005C2AC8 /* NSControl.swift */; }; + 9ADE4A8F1DA6DA20005C2AC8 /* NSControlSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A8D1DA6D965005C2AC8 /* NSControlSpec.swift */; }; + 9ADE4A911DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A901DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift */; }; + 9ADE4A921DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A901DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift */; }; + 9ADE4A931DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A901DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift */; }; + 9ADE4A941DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A901DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift */; }; + 9ADE4A961DA6F018005C2AC8 /* NSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADE4A951DA6F018005C2AC8 /* NSTextField.swift */; }; + 9ADFE5A11DBFFBCF001E11F7 /* LifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A01DBFFBCF001E11F7 /* LifetimeSpec.swift */; }; + 9ADFE5A21DBFFBCF001E11F7 /* LifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A01DBFFBCF001E11F7 /* LifetimeSpec.swift */; }; + 9ADFE5A31DBFFBCF001E11F7 /* LifetimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A01DBFFBCF001E11F7 /* LifetimeSpec.swift */; }; + 9ADFE5A51DC0001C001E11F7 /* Synchronizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A41DC0001C001E11F7 /* Synchronizing.swift */; }; + 9ADFE5A61DC0001C001E11F7 /* Synchronizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A41DC0001C001E11F7 /* Synchronizing.swift */; }; + 9ADFE5A71DC0001C001E11F7 /* Synchronizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A41DC0001C001E11F7 /* Synchronizing.swift */; }; + 9ADFE5A81DC0001C001E11F7 /* Synchronizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADFE5A41DC0001C001E11F7 /* Synchronizing.swift */; }; + 9AE7C2A41DDD7F5100F7534C /* ObjC+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE7C2A31DDD7F5100F7534C /* ObjC+Messages.swift */; }; + 9AE7C2A51DDD7F5100F7534C /* ObjC+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE7C2A31DDD7F5100F7534C /* ObjC+Messages.swift */; }; + 9AE7C2A61DDD7F5100F7534C /* ObjC+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE7C2A31DDD7F5100F7534C /* ObjC+Messages.swift */; }; + 9AE7C2A71DDD7F5100F7534C /* ObjC+Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE7C2A31DDD7F5100F7534C /* ObjC+Messages.swift */; }; + 9AED64C51E496A3700321004 /* ActionProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AED64C41E496A3700321004 /* ActionProxy.swift */; }; + 9AF0EA751D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0EA741D9A7FF700F27DDF /* NSObject+BindingTarget.swift */; }; + 9AF0EA761D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0EA741D9A7FF700F27DDF /* NSObject+BindingTarget.swift */; }; + 9AF0EA771D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0EA741D9A7FF700F27DDF /* NSObject+BindingTarget.swift */; }; + 9AF0EA781D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0EA741D9A7FF700F27DDF /* NSObject+BindingTarget.swift */; }; + 9AFCBFE31EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AFCBFE21EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift */; }; + 9AFCBFE41EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AFCBFE21EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift */; }; + 9AFCBFE51EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AFCBFE21EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift */; }; + A91244E820389AEA0001BBCB /* MKLocalSearchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91244E720389AEA0001BBCB /* MKLocalSearchRequest.swift */; }; A9B315CA1B3940AB0001CB9C /* ReactiveCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315CB1B3940AB0001CB9C /* EXTKeyPathCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666619EDA57100A782A9 /* EXTKeyPathCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315CD1B3940AB0001CB9C /* EXTScope.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666919EDA57100A782A9 /* EXTScope.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315CE1B3940AB0001CB9C /* metamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666A19EDA57100A782A9 /* metamacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315D01B3940AB0001CB9C /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037642A19EDA41200A782A9 /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315D31B3940AB0001CB9C /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643019EDA41200A782A9 /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315D41B3940AB0001CB9C /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643219EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315D51B3940AB0001CB9C /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643419EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315D61B3940AB0001CB9C /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643619EDA41200A782A9 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315D71B3940AB0001CB9C /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643819EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315D91B3940AB0001CB9C /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315DB1B3940AB0001CB9C /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644019EDA41200A782A9 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315DE1B3940AB0001CB9C /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644619EDA41200A782A9 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315DF1B3940AB0001CB9C /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644819EDA41200A782A9 /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315E01B3940AB0001CB9C /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644A19EDA41200A782A9 /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315E11B3940AB0001CB9C /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315E21B3940AB0001CB9C /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644E19EDA41200A782A9 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315E41B3940AB0001CB9C /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645219EDA41200A782A9 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315E51B3940AB0001CB9C /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645419EDA41200A782A9 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315E81B3940AB0001CB9C /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645A19EDA41200A782A9 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315EA1B3940AB0001CB9C /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646019EDA41200A782A9 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315EC1B3940AB0001CB9C /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646419EDA41200A782A9 /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315ED1B3940AC0001CB9C /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646619EDA41200A782A9 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315EE1B3940AC0001CB9C /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646819EDA41200A782A9 /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315F01B3940AC0001CB9C /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646D19EDA41200A782A9 /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315F71B3940AC0001CB9C /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647B19EDA41200A782A9 /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315F81B3940AC0001CB9C /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647D19EDA41200A782A9 /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315FB1B3940AC0001CB9C /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648319EDA41200A782A9 /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B315FE1B3940AC0001CB9C /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648719EDA41200A782A9 /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316021B3940AD0001CB9C /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648E19EDA41200A782A9 /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316031B3940AD0001CB9C /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649019EDA41200A782A9 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316041B3940AD0001CB9C /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649119EDA41200A782A9 /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316061B3940AD0001CB9C /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649519EDA41200A782A9 /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316081B3940AD0001CB9C /* RACScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649819EDA41200A782A9 /* RACScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316091B3940AD0001CB9C /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649919EDA41200A782A9 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B3160A1B3940AD0001CB9C /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649B19EDA41200A782A9 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B3160B1B3940AD0001CB9C /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649D19EDA41200A782A9 /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B3160C1B3940AE0001CB9C /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649F19EDA41200A782A9 /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B3160D1B3940AE0001CB9C /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A119EDA41200A782A9 /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B3160F1B3940AE0001CB9C /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A619EDA41200A782A9 /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316121B3940AE0001CB9C /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AB19EDA41200A782A9 /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316131B3940AE0001CB9C /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AD19EDA41200A782A9 /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316151B3940AE0001CB9C /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B019EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316171B3940AF0001CB9C /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B419EDA41200A782A9 /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316181B3940AF0001CB9C /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B619EDA41200A782A9 /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316191B3940AF0001CB9C /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B819EDA41200A782A9 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B3161C1B3940AF0001CB9C /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764BE19EDA41200A782A9 /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A9B316341B394C7F0001CB9C /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D037646A19EDA41200A782A9 /* RACCompoundDisposableProvider.d */; }; - A9B316351B394C7F0001CB9C /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03764A319EDA41200A782A9 /* RACSignalProvider.d */; }; - A9F793341B60D0140026BCBA /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = D871D69E1B3B29A40070F16C /* Optional.swift */; }; + A9D8BA71207CD7090031733D /* UIResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8BA70207CD7090031733D /* UIResponder.swift */; }; + A9D8BA72207CD7090031733D /* UIResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8BA70207CD7090031733D /* UIResponder.swift */; }; + A9D8BA74207CD8430031733D /* UIResponderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8BA73207CD8430031733D /* UIResponderSpec.swift */; }; + A9D8BA75207CD8430031733D /* UIResponderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8BA73207CD8430031733D /* UIResponderSpec.swift */; }; + A9EB3D201E94F08A002A9BCC /* UINavigationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D1C1E94ECAC002A9BCC /* UINavigationItem.swift */; }; + A9EB3D211E94F0AF002A9BCC /* UINavigationItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D1E1E94ED84002A9BCC /* UINavigationItemSpec.swift */; }; + A9EB3D221E94F308002A9BCC /* UINavigationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D1C1E94ECAC002A9BCC /* UINavigationItem.swift */; }; + A9EB3D231E94F314002A9BCC /* UINavigationItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D1E1E94ED84002A9BCC /* UINavigationItemSpec.swift */; }; + A9EB3D291E94F3D3002A9BCC /* UITabBarItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D261E94F3C7002A9BCC /* UITabBarItemSpec.swift */; }; + A9EB3D2B1E94F3D9002A9BCC /* UITabBarItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D261E94F3C7002A9BCC /* UITabBarItemSpec.swift */; }; + A9EB3D2C1E94F5A2002A9BCC /* UITabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D241E94F335002A9BCC /* UITabBarItem.swift */; }; + A9EB3D2D1E94F5A2002A9BCC /* UITabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D241E94F335002A9BCC /* UITabBarItem.swift */; }; + A9EB3D811E955602002A9BCC /* UIFeedbackGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EB3D7D1E955602002A9BCC /* UIFeedbackGenerator.swift */; }; B696FB811A7640C00075236D /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; B696FB821A7640C00075236D /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; - BEBDD6E51CDC292D009A75A9 /* RACDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646B19EDA41200A782A9 /* RACDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BEBDD6E61CDC292D009A75A9 /* RACDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646B19EDA41200A782A9 /* RACDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BEBDD6E71CDC292E009A75A9 /* RACDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646B19EDA41200A782A9 /* RACDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BEBDD6E81CDC292F009A75A9 /* RACDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646B19EDA41200A782A9 /* RACDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BEE020661D637B0000DF261F /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; + BF4335651E02AC7600AC88DD /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4335641E02AC7600AC88DD /* UIScrollView.swift */; }; + BF4335681E02EF0600AC88DD /* UIScrollViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4335661E02EEDE00AC88DD /* UIScrollViewSpec.swift */; }; BFA6B94D1A7604D400C846D1 /* SignalProducerNimbleMatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */; }; BFA6B94E1A7604D500C846D1 /* SignalProducerNimbleMatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */; }; - C7142DBC1CDEA167009F402D /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7142DBB1CDEA167009F402D /* CocoaAction.swift */; }; - C7142DBD1CDEA194009F402D /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7142DBB1CDEA167009F402D /* CocoaAction.swift */; }; - C7142DBE1CDEA194009F402D /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7142DBB1CDEA167009F402D /* CocoaAction.swift */; }; - C7142DBF1CDEA195009F402D /* CocoaAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7142DBB1CDEA167009F402D /* CocoaAction.swift */; }; - C79B64741CD38B2B003F2376 /* TestLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79B64731CD38B2B003F2376 /* TestLogger.swift */; }; - C79B64751CD38B2B003F2376 /* TestLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79B64731CD38B2B003F2376 /* TestLogger.swift */; }; - C79B647C1CD52E23003F2376 /* EventLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79B647B1CD52E23003F2376 /* EventLogger.swift */; }; - C79B647D1CD52E4A003F2376 /* EventLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79B647B1CD52E23003F2376 /* EventLogger.swift */; }; - C79B647F1CD52E4D003F2376 /* EventLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79B647B1CD52E23003F2376 /* EventLogger.swift */; }; - C79B64801CD52E4E003F2376 /* EventLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79B647B1CD52E23003F2376 /* EventLogger.swift */; }; - CA6F28501C52626B001879D2 /* FlattenSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6F284F1C52626B001879D2 /* FlattenSpec.swift */; }; - CA6F28511C52626B001879D2 /* FlattenSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA6F284F1C52626B001879D2 /* FlattenSpec.swift */; }; + BFBD68451E48DBD3003CB580 /* UIPickerViewSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBD68431E48DA21003CB580 /* UIPickerViewSpec.swift */; }; + BFCF775F1DFAD8A50058006E /* UISearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF775E1DFAD8A50058006E /* UISearchBar.swift */; }; + BFCF77621DFAD9440058006E /* UISearchBarSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCF77601DFAD9120058006E /* UISearchBarSpec.swift */; }; + BFE1458A1E439AB000208736 /* UIPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE145881E43991A00208736 /* UIPickerView.swift */; }; CD0C45DE1CC9A288009F5BF0 /* DynamicProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */; }; CD0C45DF1CC9A288009F5BF0 /* DynamicProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */; }; CD0C45E01CC9A288009F5BF0 /* DynamicProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */; }; CD0C45E11CC9A288009F5BF0 /* DynamicProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */; }; + CD42C69B1E951F6900AA9504 /* ReactiveCocoaTestsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD42C69A1E951F6900AA9504 /* ReactiveCocoaTestsConfiguration.swift */; }; + CD42C69C1E951F6A00AA9504 /* ReactiveCocoaTestsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD42C69A1E951F6900AA9504 /* ReactiveCocoaTestsConfiguration.swift */; }; + CD42C69D1E951F6A00AA9504 /* ReactiveCocoaTestsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD42C69A1E951F6900AA9504 /* ReactiveCocoaTestsConfiguration.swift */; }; CD8401831CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8401821CEE8ED7009F0ABF /* CocoaActionSpec.swift */; }; CD8401841CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8401821CEE8ED7009F0ABF /* CocoaActionSpec.swift */; }; CD8401851CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD8401821CEE8ED7009F0ABF /* CocoaActionSpec.swift */; }; - CDC42E2F1AE7AB8B00965373 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; }; - CDC42E301AE7AB8B00965373 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; }; - CDC42E311AE7AB8B00965373 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; }; - CDC42E331AE7AC6D00965373 /* Result.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - CDCD247A1C277EEC00710AEE /* AtomicSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312EE19EF2A7700984962 /* AtomicSpec.swift */; }; - CDCD247B1C277EED00710AEE /* AtomicSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312EE19EF2A7700984962 /* AtomicSpec.swift */; }; CDF066CA1CDC1CA200199626 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05E662419EDD82000904ACA /* Nimble.framework */; }; CDF066CB1CDC1CA200199626 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D037672B19EDA75D00A782A9 /* Quick.framework */; }; - D00004091A46864E000E7D41 /* TupleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00004081A46864E000E7D41 /* TupleExtensions.swift */; }; - D000040A1A46864E000E7D41 /* TupleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00004081A46864E000E7D41 /* TupleExtensions.swift */; }; D01B7B6219EDD8FE00D26E01 /* Nimble.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D05E662419EDD82000904ACA /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D01B7B6319EDD8FE00D26E01 /* Quick.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D037672B19EDA75D00A782A9 /* Quick.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D01B7B6419EDD94B00D26E01 /* ReactiveCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D047260C19E49F82006002AA /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D021671D1A6CD50500987861 /* ActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021671C1A6CD50500987861 /* ActionSpec.swift */; }; - D021671E1A6CD50500987861 /* ActionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021671C1A6CD50500987861 /* ActionSpec.swift */; }; - D03764E819EDA41200A782A9 /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037642A19EDA41200A782A9 /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764E919EDA41200A782A9 /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037642A19EDA41200A782A9 /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764EA19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037642B19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m */; }; - D03764EB19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037642B19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m */; }; - D03764EC19EDA41200A782A9 /* NSControl+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037642C19EDA41200A782A9 /* NSControl+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764EE19EDA41200A782A9 /* NSControl+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037642D19EDA41200A782A9 /* NSControl+RACCommandSupport.m */; }; - D03764F019EDA41200A782A9 /* NSControl+RACTextSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037642E19EDA41200A782A9 /* NSControl+RACTextSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764F219EDA41200A782A9 /* NSControl+RACTextSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037642F19EDA41200A782A9 /* NSControl+RACTextSignalSupport.m */; }; - D03764F419EDA41200A782A9 /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643019EDA41200A782A9 /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764F519EDA41200A782A9 /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643019EDA41200A782A9 /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764F619EDA41200A782A9 /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643119EDA41200A782A9 /* NSData+RACSupport.m */; }; - D03764F719EDA41200A782A9 /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643119EDA41200A782A9 /* NSData+RACSupport.m */; }; - D03764F819EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643219EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764F919EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643219EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764FA19EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643319EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m */; }; - D03764FB19EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643319EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m */; }; - D03764FC19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643419EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764FD19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643419EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03764FE19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643519EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m */; }; - D03764FF19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643519EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m */; }; - D037650019EDA41200A782A9 /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643619EDA41200A782A9 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037650119EDA41200A782A9 /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643619EDA41200A782A9 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037650219EDA41200A782A9 /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643719EDA41200A782A9 /* NSFileHandle+RACSupport.m */; }; - D037650319EDA41200A782A9 /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643719EDA41200A782A9 /* NSFileHandle+RACSupport.m */; }; - D037650419EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643819EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037650519EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643819EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037650619EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643919EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m */; }; - D037650719EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643919EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m */; }; - D037650A19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m */; }; - D037650B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m */; }; - D037650C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037650D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037650E19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m */; }; - D037650F19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m */; }; - D037651019EDA41200A782A9 /* NSObject+RACAppKitBindings.h in Headers */ = {isa = PBXBuildFile; fileRef = D037643E19EDA41200A782A9 /* NSObject+RACAppKitBindings.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037651219EDA41200A782A9 /* NSObject+RACAppKitBindings.m in Sources */ = {isa = PBXBuildFile; fileRef = D037643F19EDA41200A782A9 /* NSObject+RACAppKitBindings.m */; }; - D037651419EDA41200A782A9 /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644019EDA41200A782A9 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037651519EDA41200A782A9 /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644019EDA41200A782A9 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037651619EDA41200A782A9 /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644119EDA41200A782A9 /* NSObject+RACDeallocating.m */; }; - D037651719EDA41200A782A9 /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644119EDA41200A782A9 /* NSObject+RACDeallocating.m */; }; - D037651A19EDA41200A782A9 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644319EDA41200A782A9 /* NSObject+RACDescription.m */; }; - D037651B19EDA41200A782A9 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644319EDA41200A782A9 /* NSObject+RACDescription.m */; }; - D037651E19EDA41200A782A9 /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644519EDA41200A782A9 /* NSObject+RACKVOWrapper.m */; }; - D037651F19EDA41200A782A9 /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644519EDA41200A782A9 /* NSObject+RACKVOWrapper.m */; }; - D037652019EDA41200A782A9 /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644619EDA41200A782A9 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652119EDA41200A782A9 /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644619EDA41200A782A9 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652219EDA41200A782A9 /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644719EDA41200A782A9 /* NSObject+RACLifting.m */; }; - D037652319EDA41200A782A9 /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644719EDA41200A782A9 /* NSObject+RACLifting.m */; }; - D037652419EDA41200A782A9 /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644819EDA41200A782A9 /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652519EDA41200A782A9 /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644819EDA41200A782A9 /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652619EDA41200A782A9 /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644919EDA41200A782A9 /* NSObject+RACPropertySubscribing.m */; }; - D037652719EDA41200A782A9 /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644919EDA41200A782A9 /* NSObject+RACPropertySubscribing.m */; }; - D037652819EDA41200A782A9 /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644A19EDA41200A782A9 /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652919EDA41200A782A9 /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644A19EDA41200A782A9 /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652A19EDA41200A782A9 /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m */; }; - D037652B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m */; }; - D037652C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037652E19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m */; }; - D037652F19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m */; }; - D037653019EDA41200A782A9 /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644E19EDA41200A782A9 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037653119EDA41200A782A9 /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037644E19EDA41200A782A9 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037653219EDA41200A782A9 /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644F19EDA41200A782A9 /* NSSet+RACSequenceAdditions.m */; }; - D037653319EDA41200A782A9 /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037644F19EDA41200A782A9 /* NSSet+RACSequenceAdditions.m */; }; - D037653619EDA41200A782A9 /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645119EDA41200A782A9 /* NSString+RACKeyPathUtilities.m */; }; - D037653719EDA41200A782A9 /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645119EDA41200A782A9 /* NSString+RACKeyPathUtilities.m */; }; - D037653819EDA41200A782A9 /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645219EDA41200A782A9 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037653919EDA41200A782A9 /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645219EDA41200A782A9 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037653A19EDA41200A782A9 /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645319EDA41200A782A9 /* NSString+RACSequenceAdditions.m */; }; - D037653B19EDA41200A782A9 /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645319EDA41200A782A9 /* NSString+RACSequenceAdditions.m */; }; - D037653C19EDA41200A782A9 /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645419EDA41200A782A9 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037653D19EDA41200A782A9 /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645419EDA41200A782A9 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037653E19EDA41200A782A9 /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645519EDA41200A782A9 /* NSString+RACSupport.m */; }; - D037653F19EDA41200A782A9 /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645519EDA41200A782A9 /* NSString+RACSupport.m */; }; - D037654019EDA41200A782A9 /* NSText+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645619EDA41200A782A9 /* NSText+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037654219EDA41200A782A9 /* NSText+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645719EDA41200A782A9 /* NSText+RACSignalSupport.m */; }; - D037654419EDA41200A782A9 /* NSURLConnection+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645819EDA41200A782A9 /* NSURLConnection+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037654519EDA41200A782A9 /* NSURLConnection+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645819EDA41200A782A9 /* NSURLConnection+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037654619EDA41200A782A9 /* NSURLConnection+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645919EDA41200A782A9 /* NSURLConnection+RACSupport.m */; }; - D037654719EDA41200A782A9 /* NSURLConnection+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645919EDA41200A782A9 /* NSURLConnection+RACSupport.m */; }; - D037654819EDA41200A782A9 /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645A19EDA41200A782A9 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037654919EDA41200A782A9 /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D037645A19EDA41200A782A9 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037654A19EDA41200A782A9 /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m */; }; - D037654B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m */; }; - D037654E19EDA41200A782A9 /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645D19EDA41200A782A9 /* RACArraySequence.m */; }; - D037654F19EDA41200A782A9 /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037645D19EDA41200A782A9 /* RACArraySequence.m */; }; - D037655619EDA41200A782A9 /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646119EDA41200A782A9 /* RACBehaviorSubject.m */; }; - D037655719EDA41200A782A9 /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646119EDA41200A782A9 /* RACBehaviorSubject.m */; }; - D037655A19EDA41200A782A9 /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646319EDA41200A782A9 /* RACBlockTrampoline.m */; }; - D037655B19EDA41200A782A9 /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646319EDA41200A782A9 /* RACBlockTrampoline.m */; }; - D037655C19EDA41200A782A9 /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646419EDA41200A782A9 /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037655D19EDA41200A782A9 /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646419EDA41200A782A9 /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037655E19EDA41200A782A9 /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646519EDA41200A782A9 /* RACChannel.m */; }; - D037655F19EDA41200A782A9 /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646519EDA41200A782A9 /* RACChannel.m */; }; - D037656019EDA41200A782A9 /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646619EDA41200A782A9 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037656119EDA41200A782A9 /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646619EDA41200A782A9 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037656219EDA41200A782A9 /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646719EDA41200A782A9 /* RACCommand.m */; }; - D037656319EDA41200A782A9 /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646719EDA41200A782A9 /* RACCommand.m */; }; - D037656419EDA41200A782A9 /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646819EDA41200A782A9 /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037656519EDA41200A782A9 /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646819EDA41200A782A9 /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037656619EDA41200A782A9 /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646919EDA41200A782A9 /* RACCompoundDisposable.m */; }; - D037656719EDA41200A782A9 /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646919EDA41200A782A9 /* RACCompoundDisposable.m */; }; - D037656819EDA41200A782A9 /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D037646A19EDA41200A782A9 /* RACCompoundDisposableProvider.d */; }; - D037656919EDA41200A782A9 /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D037646A19EDA41200A782A9 /* RACCompoundDisposableProvider.d */; }; - D037656C19EDA41200A782A9 /* RACDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646C19EDA41200A782A9 /* RACDelegateProxy.m */; }; - D037656D19EDA41200A782A9 /* RACDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646C19EDA41200A782A9 /* RACDelegateProxy.m */; }; - D037656E19EDA41200A782A9 /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646D19EDA41200A782A9 /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037656F19EDA41200A782A9 /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646D19EDA41200A782A9 /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037657019EDA41200A782A9 /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646E19EDA41200A782A9 /* RACDisposable.m */; }; - D037657119EDA41200A782A9 /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037646E19EDA41200A782A9 /* RACDisposable.m */; }; - D037657419EDA41200A782A9 /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647019EDA41200A782A9 /* RACDynamicSequence.m */; }; - D037657519EDA41200A782A9 /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647019EDA41200A782A9 /* RACDynamicSequence.m */; }; - D037657819EDA41200A782A9 /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647219EDA41200A782A9 /* RACDynamicSignal.m */; }; - D037657919EDA41200A782A9 /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647219EDA41200A782A9 /* RACDynamicSignal.m */; }; - D037657C19EDA41200A782A9 /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647419EDA41200A782A9 /* RACEagerSequence.m */; }; - D037657D19EDA41200A782A9 /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647419EDA41200A782A9 /* RACEagerSequence.m */; }; - D037658019EDA41200A782A9 /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647619EDA41200A782A9 /* RACEmptySequence.m */; }; - D037658119EDA41200A782A9 /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647619EDA41200A782A9 /* RACEmptySequence.m */; }; - D037658419EDA41200A782A9 /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647819EDA41200A782A9 /* RACEmptySignal.m */; }; - D037658519EDA41200A782A9 /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647819EDA41200A782A9 /* RACEmptySignal.m */; }; - D037658819EDA41200A782A9 /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647A19EDA41200A782A9 /* RACErrorSignal.m */; }; - D037658919EDA41200A782A9 /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647A19EDA41200A782A9 /* RACErrorSignal.m */; }; - D037658A19EDA41200A782A9 /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647B19EDA41200A782A9 /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037658B19EDA41200A782A9 /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647B19EDA41200A782A9 /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037658C19EDA41200A782A9 /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647C19EDA41200A782A9 /* RACEvent.m */; }; - D037658D19EDA41200A782A9 /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647C19EDA41200A782A9 /* RACEvent.m */; }; - D037658E19EDA41200A782A9 /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647D19EDA41200A782A9 /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037658F19EDA41200A782A9 /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037647D19EDA41200A782A9 /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037659019EDA41200A782A9 /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647E19EDA41200A782A9 /* RACGroupedSignal.m */; }; - D037659119EDA41200A782A9 /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037647E19EDA41200A782A9 /* RACGroupedSignal.m */; }; - D037659419EDA41200A782A9 /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648019EDA41200A782A9 /* RACImmediateScheduler.m */; }; - D037659519EDA41200A782A9 /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648019EDA41200A782A9 /* RACImmediateScheduler.m */; }; - D037659819EDA41200A782A9 /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648219EDA41200A782A9 /* RACIndexSetSequence.m */; }; - D037659919EDA41200A782A9 /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648219EDA41200A782A9 /* RACIndexSetSequence.m */; }; - D037659A19EDA41200A782A9 /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648319EDA41200A782A9 /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037659B19EDA41200A782A9 /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648319EDA41200A782A9 /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037659C19EDA41200A782A9 /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648419EDA41200A782A9 /* RACKVOChannel.m */; }; - D037659D19EDA41200A782A9 /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648419EDA41200A782A9 /* RACKVOChannel.m */; }; - D03765A019EDA41200A782A9 /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648619EDA41200A782A9 /* RACKVOTrampoline.m */; }; - D03765A119EDA41200A782A9 /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648619EDA41200A782A9 /* RACKVOTrampoline.m */; }; - D03765A219EDA41200A782A9 /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648719EDA41200A782A9 /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765A319EDA41200A782A9 /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648719EDA41200A782A9 /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765A419EDA41200A782A9 /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648819EDA41200A782A9 /* RACMulticastConnection.m */; }; - D03765A519EDA41200A782A9 /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648819EDA41200A782A9 /* RACMulticastConnection.m */; }; - D03765AE19EDA41200A782A9 /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648D19EDA41200A782A9 /* RACPassthroughSubscriber.m */; }; - D03765AF19EDA41200A782A9 /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648D19EDA41200A782A9 /* RACPassthroughSubscriber.m */; }; - D03765B019EDA41200A782A9 /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648E19EDA41200A782A9 /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765B119EDA41200A782A9 /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037648E19EDA41200A782A9 /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765B219EDA41200A782A9 /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648F19EDA41200A782A9 /* RACQueueScheduler.m */; }; - D03765B319EDA41200A782A9 /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037648F19EDA41200A782A9 /* RACQueueScheduler.m */; }; - D03765B419EDA41200A782A9 /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649019EDA41200A782A9 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765B519EDA41200A782A9 /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649019EDA41200A782A9 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765B619EDA41200A782A9 /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649119EDA41200A782A9 /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765B719EDA41200A782A9 /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649119EDA41200A782A9 /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765B819EDA41200A782A9 /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649219EDA41200A782A9 /* RACReplaySubject.m */; }; - D03765B919EDA41200A782A9 /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649219EDA41200A782A9 /* RACReplaySubject.m */; }; - D03765BC19EDA41200A782A9 /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649419EDA41200A782A9 /* RACReturnSignal.m */; }; - D03765BD19EDA41200A782A9 /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649419EDA41200A782A9 /* RACReturnSignal.m */; }; - D03765BE19EDA41200A782A9 /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649519EDA41200A782A9 /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765BF19EDA41200A782A9 /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649519EDA41200A782A9 /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765C019EDA41200A782A9 /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649619EDA41200A782A9 /* RACScheduler.m */; }; - D03765C119EDA41200A782A9 /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649619EDA41200A782A9 /* RACScheduler.m */; }; - D03765C419EDA41200A782A9 /* RACScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649819EDA41200A782A9 /* RACScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765C519EDA41200A782A9 /* RACScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649819EDA41200A782A9 /* RACScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765C619EDA41200A782A9 /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649919EDA41200A782A9 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765C719EDA41200A782A9 /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649919EDA41200A782A9 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765C819EDA41200A782A9 /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649A19EDA41200A782A9 /* RACScopedDisposable.m */; }; - D03765C919EDA41200A782A9 /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649A19EDA41200A782A9 /* RACScopedDisposable.m */; }; - D03765CA19EDA41200A782A9 /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649B19EDA41200A782A9 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765CB19EDA41200A782A9 /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649B19EDA41200A782A9 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765CC19EDA41200A782A9 /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649C19EDA41200A782A9 /* RACSequence.m */; }; - D03765CD19EDA41200A782A9 /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649C19EDA41200A782A9 /* RACSequence.m */; }; - D03765CE19EDA41200A782A9 /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649D19EDA41200A782A9 /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765CF19EDA41200A782A9 /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649D19EDA41200A782A9 /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765D019EDA41200A782A9 /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649E19EDA41200A782A9 /* RACSerialDisposable.m */; }; - D03765D119EDA41200A782A9 /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D037649E19EDA41200A782A9 /* RACSerialDisposable.m */; }; - D03765D219EDA41200A782A9 /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649F19EDA41200A782A9 /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765D319EDA41200A782A9 /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D037649F19EDA41200A782A9 /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765D419EDA41200A782A9 /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A019EDA41200A782A9 /* RACSignal.m */; }; - D03765D519EDA41200A782A9 /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A019EDA41200A782A9 /* RACSignal.m */; }; - D03765D619EDA41200A782A9 /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A119EDA41200A782A9 /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765D719EDA41200A782A9 /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A119EDA41200A782A9 /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765D819EDA41200A782A9 /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A219EDA41200A782A9 /* RACSignal+Operations.m */; }; - D03765D919EDA41200A782A9 /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A219EDA41200A782A9 /* RACSignal+Operations.m */; }; - D03765DA19EDA41200A782A9 /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03764A319EDA41200A782A9 /* RACSignalProvider.d */; }; - D03765DB19EDA41200A782A9 /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03764A319EDA41200A782A9 /* RACSignalProvider.d */; }; - D03765DE19EDA41200A782A9 /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A519EDA41200A782A9 /* RACSignalSequence.m */; }; - D03765DF19EDA41200A782A9 /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A519EDA41200A782A9 /* RACSignalSequence.m */; }; - D03765E019EDA41200A782A9 /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A619EDA41200A782A9 /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765E119EDA41200A782A9 /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764A619EDA41200A782A9 /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765E219EDA41200A782A9 /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A719EDA41200A782A9 /* RACStream.m */; }; - D03765E319EDA41200A782A9 /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764A719EDA41200A782A9 /* RACStream.m */; }; - D03765E819EDA41200A782A9 /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AA19EDA41200A782A9 /* RACStringSequence.m */; }; - D03765E919EDA41200A782A9 /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AA19EDA41200A782A9 /* RACStringSequence.m */; }; - D03765EA19EDA41200A782A9 /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AB19EDA41200A782A9 /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765EB19EDA41200A782A9 /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AB19EDA41200A782A9 /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765EC19EDA41200A782A9 /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AC19EDA41200A782A9 /* RACSubject.m */; }; - D03765ED19EDA41200A782A9 /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AC19EDA41200A782A9 /* RACSubject.m */; }; - D03765EE19EDA41200A782A9 /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AD19EDA41200A782A9 /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765EF19EDA41200A782A9 /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764AD19EDA41200A782A9 /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765F019EDA41200A782A9 /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AE19EDA41200A782A9 /* RACSubscriber.m */; }; - D03765F119EDA41200A782A9 /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764AE19EDA41200A782A9 /* RACSubscriber.m */; }; - D03765F419EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B019EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765F519EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B019EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765F619EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B119EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m */; }; - D03765F719EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B119EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m */; }; - D03765FA19EDA41200A782A9 /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B319EDA41200A782A9 /* RACSubscriptionScheduler.m */; }; - D03765FB19EDA41200A782A9 /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B319EDA41200A782A9 /* RACSubscriptionScheduler.m */; }; - D03765FC19EDA41200A782A9 /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B419EDA41200A782A9 /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765FD19EDA41200A782A9 /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B419EDA41200A782A9 /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03765FE19EDA41200A782A9 /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B519EDA41200A782A9 /* RACTargetQueueScheduler.m */; }; - D03765FF19EDA41200A782A9 /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B519EDA41200A782A9 /* RACTargetQueueScheduler.m */; }; - D037660019EDA41200A782A9 /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B619EDA41200A782A9 /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037660119EDA41200A782A9 /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B619EDA41200A782A9 /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037660219EDA41200A782A9 /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B719EDA41200A782A9 /* RACTestScheduler.m */; }; - D037660319EDA41200A782A9 /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B719EDA41200A782A9 /* RACTestScheduler.m */; }; - D037660419EDA41200A782A9 /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B819EDA41200A782A9 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037660519EDA41200A782A9 /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764B819EDA41200A782A9 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037660619EDA41200A782A9 /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B919EDA41200A782A9 /* RACTuple.m */; }; - D037660719EDA41200A782A9 /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764B919EDA41200A782A9 /* RACTuple.m */; }; - D037660A19EDA41200A782A9 /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BB19EDA41200A782A9 /* RACTupleSequence.m */; }; - D037660B19EDA41200A782A9 /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BB19EDA41200A782A9 /* RACTupleSequence.m */; }; - D037660E19EDA41200A782A9 /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BD19EDA41200A782A9 /* RACUnarySequence.m */; }; - D037660F19EDA41200A782A9 /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BD19EDA41200A782A9 /* RACUnarySequence.m */; }; - D037661019EDA41200A782A9 /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764BE19EDA41200A782A9 /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037661119EDA41200A782A9 /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764BE19EDA41200A782A9 /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037661219EDA41200A782A9 /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BF19EDA41200A782A9 /* RACUnit.m */; }; - D037661319EDA41200A782A9 /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764BF19EDA41200A782A9 /* RACUnit.m */; }; - D037661619EDA41200A782A9 /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C119EDA41200A782A9 /* RACValueTransformer.m */; }; - D037661719EDA41200A782A9 /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C119EDA41200A782A9 /* RACValueTransformer.m */; }; - D037661919EDA41200A782A9 /* UIActionSheet+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764C219EDA41200A782A9 /* UIActionSheet+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037661B19EDA41200A782A9 /* UIActionSheet+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C319EDA41200A782A9 /* UIActionSheet+RACSignalSupport.m */; }; - D037661D19EDA41200A782A9 /* UIAlertView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764C419EDA41200A782A9 /* UIAlertView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037661F19EDA41200A782A9 /* UIAlertView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C519EDA41200A782A9 /* UIAlertView+RACSignalSupport.m */; }; - D037662119EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764C619EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037662319EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C719EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m */; }; - D037662519EDA41200A782A9 /* UIButton+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764C819EDA41200A782A9 /* UIButton+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037662719EDA41200A782A9 /* UIButton+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C919EDA41200A782A9 /* UIButton+RACCommandSupport.m */; }; - D037662919EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764CA19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037662B19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764CB19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.m */; }; - D037662D19EDA41200A782A9 /* UIControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764CC19EDA41200A782A9 /* UIControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037662F19EDA41200A782A9 /* UIControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764CD19EDA41200A782A9 /* UIControl+RACSignalSupport.m */; }; - D037663319EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764CF19EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.m */; }; - D037663519EDA41200A782A9 /* UIDatePicker+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764D019EDA41200A782A9 /* UIDatePicker+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037663719EDA41200A782A9 /* UIDatePicker+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764D119EDA41200A782A9 /* UIDatePicker+RACSignalSupport.m */; }; - D037663919EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764D219EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037663B19EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764D319EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.m */; }; - D037663D19EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764D419EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037663F19EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764D519EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.m */; }; - D037664119EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764D619EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037664319EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764D719EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.m */; }; - D037664519EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764D819EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037664719EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764D919EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.m */; }; - D037664919EDA41200A782A9 /* UISlider+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764DA19EDA41200A782A9 /* UISlider+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037664B19EDA41200A782A9 /* UISlider+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764DB19EDA41200A782A9 /* UISlider+RACSignalSupport.m */; }; - D037664D19EDA41200A782A9 /* UIStepper+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764DC19EDA41200A782A9 /* UIStepper+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037664F19EDA41200A782A9 /* UIStepper+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764DD19EDA41200A782A9 /* UIStepper+RACSignalSupport.m */; }; - D037665119EDA41200A782A9 /* UISwitch+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764DE19EDA41200A782A9 /* UISwitch+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037665319EDA41200A782A9 /* UISwitch+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764DF19EDA41200A782A9 /* UISwitch+RACSignalSupport.m */; }; - D037665519EDA41200A782A9 /* UITableViewCell+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E019EDA41200A782A9 /* UITableViewCell+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037665719EDA41200A782A9 /* UITableViewCell+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E119EDA41200A782A9 /* UITableViewCell+RACSignalSupport.m */; }; - D037665919EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E219EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037665B19EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E319EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.m */; }; - D037665D19EDA41200A782A9 /* UITextField+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E419EDA41200A782A9 /* UITextField+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037665F19EDA41200A782A9 /* UITextField+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E519EDA41200A782A9 /* UITextField+RACSignalSupport.m */; }; - D037666119EDA41200A782A9 /* UITextView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764E619EDA41200A782A9 /* UITextView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037666319EDA41200A782A9 /* UITextView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764E719EDA41200A782A9 /* UITextView+RACSignalSupport.m */; }; D037666419EDA43C00A782A9 /* ReactiveCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037666B19EDA57100A782A9 /* EXTKeyPathCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666619EDA57100A782A9 /* EXTKeyPathCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037666C19EDA57100A782A9 /* EXTKeyPathCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666619EDA57100A782A9 /* EXTKeyPathCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037666F19EDA57100A782A9 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037666819EDA57100A782A9 /* EXTRuntimeExtensions.m */; }; - D037667019EDA57100A782A9 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D037666819EDA57100A782A9 /* EXTRuntimeExtensions.m */; }; - D037667119EDA57100A782A9 /* EXTScope.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666919EDA57100A782A9 /* EXTScope.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037667219EDA57100A782A9 /* EXTScope.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666919EDA57100A782A9 /* EXTScope.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037667319EDA57100A782A9 /* metamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666A19EDA57100A782A9 /* metamacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037667419EDA57100A782A9 /* metamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666A19EDA57100A782A9 /* metamacros.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D03766B919EDA60000A782A9 /* NSControllerRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667619EDA60000A782A9 /* NSControllerRACSupportSpec.m */; }; - D03766BD19EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667819EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m */; }; - D03766BE19EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667819EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m */; }; - D03766BF19EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667919EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m */; }; - D03766C019EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667919EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m */; }; - D03766C119EDA60000A782A9 /* NSObjectRACAppKitBindingsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667A19EDA60000A782A9 /* NSObjectRACAppKitBindingsSpec.m */; }; - D03766C319EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667B19EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m */; }; - D03766C419EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667B19EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m */; }; - D03766C519EDA60000A782A9 /* NSObjectRACLiftingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667C19EDA60000A782A9 /* NSObjectRACLiftingSpec.m */; }; - D03766C619EDA60000A782A9 /* NSObjectRACLiftingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667C19EDA60000A782A9 /* NSObjectRACLiftingSpec.m */; }; - D03766C719EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667E19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m */; }; - D03766C819EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667E19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m */; }; - D03766C919EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667F19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m */; }; - D03766CA19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037667F19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m */; }; - D03766CB19EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668019EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m */; }; - D03766CC19EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668019EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m */; }; - D03766CD19EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668119EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m */; }; - D03766CE19EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668119EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m */; }; - D03766D119EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668319EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m */; }; - D03766D219EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668319EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m */; }; - D03766D319EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m */; }; - D03766D419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m */; }; - D03766D719EDA60000A782A9 /* RACBlockTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668619EDA60000A782A9 /* RACBlockTrampolineSpec.m */; }; - D03766D819EDA60000A782A9 /* RACBlockTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668619EDA60000A782A9 /* RACBlockTrampolineSpec.m */; }; - D03766D919EDA60000A782A9 /* RACChannelExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668819EDA60000A782A9 /* RACChannelExamples.m */; }; - D03766DA19EDA60000A782A9 /* RACChannelExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668819EDA60000A782A9 /* RACChannelExamples.m */; }; - D03766DB19EDA60000A782A9 /* RACChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668919EDA60000A782A9 /* RACChannelSpec.m */; }; - D03766DC19EDA60000A782A9 /* RACChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668919EDA60000A782A9 /* RACChannelSpec.m */; }; - D03766DD19EDA60000A782A9 /* RACCommandSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668A19EDA60000A782A9 /* RACCommandSpec.m */; }; - D03766DE19EDA60000A782A9 /* RACCommandSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668A19EDA60000A782A9 /* RACCommandSpec.m */; }; - D03766DF19EDA60000A782A9 /* RACCompoundDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668B19EDA60000A782A9 /* RACCompoundDisposableSpec.m */; }; - D03766E019EDA60000A782A9 /* RACCompoundDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668B19EDA60000A782A9 /* RACCompoundDisposableSpec.m */; }; - D03766E119EDA60000A782A9 /* RACControlCommandExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668D19EDA60000A782A9 /* RACControlCommandExamples.m */; }; - D03766E219EDA60000A782A9 /* RACControlCommandExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668D19EDA60000A782A9 /* RACControlCommandExamples.m */; }; - D03766E319EDA60000A782A9 /* RACDelegateProxySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668E19EDA60000A782A9 /* RACDelegateProxySpec.m */; }; - D03766E419EDA60000A782A9 /* RACDelegateProxySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668E19EDA60000A782A9 /* RACDelegateProxySpec.m */; }; - D03766E519EDA60000A782A9 /* RACDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668F19EDA60000A782A9 /* RACDisposableSpec.m */; }; - D03766E619EDA60000A782A9 /* RACDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037668F19EDA60000A782A9 /* RACDisposableSpec.m */; }; - D03766E719EDA60000A782A9 /* RACEventSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669019EDA60000A782A9 /* RACEventSpec.m */; }; - D03766E819EDA60000A782A9 /* RACEventSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669019EDA60000A782A9 /* RACEventSpec.m */; }; - D03766E919EDA60000A782A9 /* RACKVOChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669119EDA60000A782A9 /* RACKVOChannelSpec.m */; }; - D03766EA19EDA60000A782A9 /* RACKVOChannelSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669119EDA60000A782A9 /* RACKVOChannelSpec.m */; }; - D03766EB19EDA60000A782A9 /* RACKVOWrapperSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669219EDA60000A782A9 /* RACKVOWrapperSpec.m */; }; - D03766EC19EDA60000A782A9 /* RACKVOWrapperSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669219EDA60000A782A9 /* RACKVOWrapperSpec.m */; }; - D03766ED19EDA60000A782A9 /* RACMulticastConnectionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669319EDA60000A782A9 /* RACMulticastConnectionSpec.m */; }; - D03766EE19EDA60000A782A9 /* RACMulticastConnectionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669319EDA60000A782A9 /* RACMulticastConnectionSpec.m */; }; - D03766EF19EDA60000A782A9 /* RACPropertySignalExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669519EDA60000A782A9 /* RACPropertySignalExamples.m */; }; - D03766F019EDA60000A782A9 /* RACPropertySignalExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669519EDA60000A782A9 /* RACPropertySignalExamples.m */; }; - D03766F119EDA60000A782A9 /* RACSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669619EDA60000A782A9 /* RACSchedulerSpec.m */; }; - D03766F219EDA60000A782A9 /* RACSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669619EDA60000A782A9 /* RACSchedulerSpec.m */; }; - D03766F319EDA60000A782A9 /* RACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669719EDA60000A782A9 /* RACSequenceAdditionsSpec.m */; }; - D03766F419EDA60000A782A9 /* RACSequenceAdditionsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669719EDA60000A782A9 /* RACSequenceAdditionsSpec.m */; }; - D03766F519EDA60000A782A9 /* RACSequenceExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669919EDA60000A782A9 /* RACSequenceExamples.m */; }; - D03766F619EDA60000A782A9 /* RACSequenceExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669919EDA60000A782A9 /* RACSequenceExamples.m */; }; - D03766F719EDA60000A782A9 /* RACSequenceSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669A19EDA60000A782A9 /* RACSequenceSpec.m */; }; - D03766F819EDA60000A782A9 /* RACSequenceSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669A19EDA60000A782A9 /* RACSequenceSpec.m */; }; - D03766F919EDA60000A782A9 /* RACSerialDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669B19EDA60000A782A9 /* RACSerialDisposableSpec.m */; }; - D03766FA19EDA60000A782A9 /* RACSerialDisposableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669B19EDA60000A782A9 /* RACSerialDisposableSpec.m */; }; - D03766FB19EDA60000A782A9 /* RACSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669C19EDA60000A782A9 /* RACSignalSpec.m */; }; - D03766FC19EDA60000A782A9 /* RACSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D037669C19EDA60000A782A9 /* RACSignalSpec.m */; }; - D03766FF19EDA60000A782A9 /* RACStreamExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A019EDA60000A782A9 /* RACStreamExamples.m */; }; - D037670019EDA60000A782A9 /* RACStreamExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A019EDA60000A782A9 /* RACStreamExamples.m */; }; - D037670119EDA60000A782A9 /* RACSubclassObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A219EDA60000A782A9 /* RACSubclassObject.m */; }; - D037670219EDA60000A782A9 /* RACSubclassObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A219EDA60000A782A9 /* RACSubclassObject.m */; }; - D037670319EDA60000A782A9 /* RACSubjectSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A319EDA60000A782A9 /* RACSubjectSpec.m */; }; - D037670419EDA60000A782A9 /* RACSubjectSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A319EDA60000A782A9 /* RACSubjectSpec.m */; }; - D037670519EDA60000A782A9 /* RACSubscriberExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A519EDA60000A782A9 /* RACSubscriberExamples.m */; }; - D037670619EDA60000A782A9 /* RACSubscriberExamples.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A519EDA60000A782A9 /* RACSubscriberExamples.m */; }; - D037670719EDA60000A782A9 /* RACSubscriberSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A619EDA60000A782A9 /* RACSubscriberSpec.m */; }; - D037670819EDA60000A782A9 /* RACSubscriberSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A619EDA60000A782A9 /* RACSubscriberSpec.m */; }; - D037670919EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A719EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m */; }; - D037670A19EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A719EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m */; }; - D037670B19EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A819EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m */; }; - D037670C19EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766A819EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m */; }; - D037671519EDA60000A782A9 /* RACTupleSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B019EDA60000A782A9 /* RACTupleSpec.m */; }; - D037671619EDA60000A782A9 /* RACTupleSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B019EDA60000A782A9 /* RACTupleSpec.m */; }; D037671719EDA60000A782A9 /* test-data.json in Resources */ = {isa = PBXBuildFile; fileRef = D03766B119EDA60000A782A9 /* test-data.json */; }; D037671819EDA60000A782A9 /* test-data.json in Resources */ = {isa = PBXBuildFile; fileRef = D03766B119EDA60000A782A9 /* test-data.json */; }; - D037671A19EDA60000A782A9 /* UIActionSheetRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B219EDA60000A782A9 /* UIActionSheetRACSupportSpec.m */; }; - D037671C19EDA60000A782A9 /* UIAlertViewRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B319EDA60000A782A9 /* UIAlertViewRACSupportSpec.m */; }; - D037671E19EDA60000A782A9 /* UIBarButtonItemRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B419EDA60000A782A9 /* UIBarButtonItemRACSupportSpec.m */; }; - D037672019EDA60000A782A9 /* UIButtonRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B519EDA60000A782A9 /* UIButtonRACSupportSpec.m */; }; - D037672419EDA60000A782A9 /* UIImagePickerControllerRACSupportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D03766B719EDA60000A782A9 /* UIImagePickerControllerRACSupportSpec.m */; }; - D037672719EDA63400A782A9 /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646019EDA41200A782A9 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037672819EDA63500A782A9 /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = D037646019EDA41200A782A9 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D037672D19EDA75D00A782A9 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D037672B19EDA75D00A782A9 /* Quick.framework */; }; D037672F19EDA78B00A782A9 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D037672B19EDA75D00A782A9 /* Quick.framework */; }; - D03B4A3D19F4C39A009E02AC /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */; }; - D03B4A3E19F4C39A009E02AC /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */; }; D04725F019E49ED7006002AA /* ReactiveCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; D04725F619E49ED7006002AA /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D04725EA19E49ED7006002AA /* ReactiveCocoa.framework */; }; D047261719E49F82006002AA /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D047260C19E49F82006002AA /* ReactiveCocoa.framework */; }; - D05E662519EDD82000904ACA /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05E662419EDD82000904ACA /* Nimble.framework */; }; D05E662619EDD83000904ACA /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05E662419EDD82000904ACA /* Nimble.framework */; }; - D08C54B31A69A2AE00AD8286 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B11A69A2AC00AD8286 /* Signal.swift */; }; - D08C54B41A69A2AF00AD8286 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B11A69A2AC00AD8286 /* Signal.swift */; }; - D08C54B61A69A3DB00AD8286 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B51A69A3DB00AD8286 /* Event.swift */; }; - D08C54B71A69A3DB00AD8286 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B51A69A3DB00AD8286 /* Event.swift */; }; - D08C54B81A69A9D000AD8286 /* SignalProducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */; }; - D08C54B91A69A9D100AD8286 /* SignalProducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */; }; - D08C54BA1A69C54300AD8286 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B01A69A2AC00AD8286 /* Property.swift */; }; - D08C54BB1A69C54400AD8286 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B01A69A2AC00AD8286 /* Property.swift */; }; - D0A226081A72E0E900D33B74 /* SignalSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A226071A72E0E900D33B74 /* SignalSpec.swift */; }; - D0A226091A72E0E900D33B74 /* SignalSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A226071A72E0E900D33B74 /* SignalSpec.swift */; }; - D0A2260B1A72E6C500D33B74 /* SignalProducerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260A1A72E6C500D33B74 /* SignalProducerSpec.swift */; }; - D0A2260C1A72E6C500D33B74 /* SignalProducerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260A1A72E6C500D33B74 /* SignalProducerSpec.swift */; }; - D0A2260E1A72F16D00D33B74 /* PropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* PropertySpec.swift */; }; - D0A2260F1A72F16D00D33B74 /* PropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* PropertySpec.swift */; }; - D0A226111A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A226101A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift */; }; - D0A226121A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A226101A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift */; }; - D0C312CD19EF2A5800984962 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BB19EF2A5800984962 /* Atomic.swift */; }; - D0C312CE19EF2A5800984962 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BB19EF2A5800984962 /* Atomic.swift */; }; - D0C312CF19EF2A5800984962 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BC19EF2A5800984962 /* Bag.swift */; }; - D0C312D019EF2A5800984962 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BC19EF2A5800984962 /* Bag.swift */; }; - D0C312D319EF2A5800984962 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BE19EF2A5800984962 /* Disposable.swift */; }; - D0C312D419EF2A5800984962 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BE19EF2A5800984962 /* Disposable.swift */; }; - D0C312DF19EF2A5800984962 /* ObjectiveCBridging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */; }; - D0C312E019EF2A5800984962 /* ObjectiveCBridging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */; }; - D0C312E719EF2A5800984962 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C819EF2A5800984962 /* Scheduler.swift */; }; - D0C312E819EF2A5800984962 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C819EF2A5800984962 /* Scheduler.swift */; }; - D0C3130C19EF2B1F00984962 /* DisposableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312F019EF2A7700984962 /* DisposableSpec.swift */; }; - D0C3130E19EF2B1F00984962 /* SchedulerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312F219EF2A7700984962 /* SchedulerSpec.swift */; }; - D0C3131219EF2B2000984962 /* DisposableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312F019EF2A7700984962 /* DisposableSpec.swift */; }; - D0C3131419EF2B2000984962 /* SchedulerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312F219EF2A7700984962 /* SchedulerSpec.swift */; }; - D0C3131E19EF2D9700984962 /* RACTestExampleScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131819EF2D9700984962 /* RACTestExampleScheduler.m */; }; - D0C3131F19EF2D9700984962 /* RACTestExampleScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131819EF2D9700984962 /* RACTestExampleScheduler.m */; }; - D0C3132019EF2D9700984962 /* RACTestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131A19EF2D9700984962 /* RACTestObject.m */; }; - D0C3132119EF2D9700984962 /* RACTestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131A19EF2D9700984962 /* RACTestObject.m */; }; - D0C3132219EF2D9700984962 /* RACTestSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131B19EF2D9700984962 /* RACTestSchedulerSpec.m */; }; - D0C3132319EF2D9700984962 /* RACTestSchedulerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131B19EF2D9700984962 /* RACTestSchedulerSpec.m */; }; - D0C3132519EF2D9700984962 /* RACTestUIButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131D19EF2D9700984962 /* RACTestUIButton.m */; }; - D0D11AB91A6AE87700C1F8B1 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54AF1A69A2AC00AD8286 /* Action.swift */; }; - D0D11ABA1A6AE87700C1F8B1 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54AF1A69A2AC00AD8286 /* Action.swift */; }; - D8024DB21B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8024DB11B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift */; }; - D8024DB31B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8024DB11B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift */; }; - D8170FC11B100EBC004192AD /* FoundationExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8170FC01B100EBC004192AD /* FoundationExtensionsSpec.swift */; }; - D8170FC21B100EBC004192AD /* FoundationExtensionsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8170FC01B100EBC004192AD /* FoundationExtensionsSpec.swift */; }; - D85C652A1C0D84C7005A77AD /* Flatten.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85C65291C0D84C7005A77AD /* Flatten.swift */; }; - D85C652B1C0E70E3005A77AD /* Flatten.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85C65291C0D84C7005A77AD /* Flatten.swift */; }; - D85C652C1C0E70E4005A77AD /* Flatten.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85C65291C0D84C7005A77AD /* Flatten.swift */; }; - D85C652D1C0E70E5005A77AD /* Flatten.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85C65291C0D84C7005A77AD /* Flatten.swift */; }; - D871D69F1B3B29A40070F16C /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = D871D69E1B3B29A40070F16C /* Optional.swift */; }; - D8E84A671B3B32FB00C3E831 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = D871D69E1B3B29A40070F16C /* Optional.swift */; }; - EBCC7DBC1BBF010C00A2AE92 /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC7DBB1BBF010C00A2AE92 /* Observer.swift */; }; - EBCC7DBD1BBF01E100A2AE92 /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC7DBB1BBF010C00A2AE92 /* Observer.swift */; }; - EBCC7DBE1BBF01E200A2AE92 /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC7DBB1BBF010C00A2AE92 /* Observer.swift */; }; - EBCC7DBF1BBF01E200A2AE92 /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC7DBB1BBF010C00A2AE92 /* Observer.swift */; }; + D0A2260E1A72F16D00D33B74 /* DynamicPropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.swift */; }; + D0A2260F1A72F16D00D33B74 /* DynamicPropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.swift */; }; + D9558AB81DFF805A003254E1 /* NSPopUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9558AB71DFF805A003254E1 /* NSPopUpButton.swift */; }; + D9558AB91DFF86C0003254E1 /* NSPopUpButtonSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9558AB51DFF7B90003254E1 /* NSPopUpButtonSpec.swift */; }; + E3AA54C12142918B0077B206 /* WKInterfaceLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3AA54C02142918B0077B206 /* WKInterfaceLabel.swift */; }; + E3AA54C3214292550077B206 /* WKInterfaceButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3AA54C2214292540077B206 /* WKInterfaceButton.swift */; }; + E921428C2284A136007E412D /* WKInterfaceObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921428A2284A12B007E412D /* WKInterfaceObject.swift */; }; + E921428E2284A44F007E412D /* WKInterfaceDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921428D2284A44F007E412D /* WKInterfaceDate.swift */; }; + E92142902284A4EF007E412D /* WKInterfaceTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921428F2284A4EF007E412D /* WKInterfaceTimer.swift */; }; + E92142922284AA64007E412D /* WKInterfaceSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92142912284AA64007E412D /* WKInterfaceSwitch.swift */; }; + E92142942284ABDF007E412D /* WKInterfaceSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92142932284ABDF007E412D /* WKInterfaceSlider.swift */; }; + E92142982284AEF0007E412D /* WKInterfaceGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92142972284AEF0007E412D /* WKInterfaceGroup.swift */; }; + E921429A2284AF99007E412D /* WKInterfaceSeparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92142992284AF99007E412D /* WKInterfaceSeparator.swift */; }; + E921429C2284B0A8007E412D /* WKInterfaceImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921429B2284B0A8007E412D /* WKInterfaceImage.swift */; }; + E921429E2284B1C4007E412D /* WKInterfaceVolumeControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921429D2284B1C4007E412D /* WKInterfaceVolumeControl.swift */; }; + E92142A02284B4FF007E412D /* WKInterfacePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921429F2284B4FF007E412D /* WKInterfacePicker.swift */; }; + E92142A22284B64E007E412D /* WKInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92142A12284B64E007E412D /* WKInterfaceController.swift */; }; + E92142A9228552CC007E412D /* WKInterfaceMovie.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92142A8228552CC007E412D /* WKInterfaceMovie.swift */; }; + E92142AB228553C5007E412D /* WKInterfaceInlineMovie.swift in Sources */ = {isa = PBXBuildFile; fileRef = E92142AA228553C5007E412D /* WKInterfaceInlineMovie.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -864,6 +436,27 @@ remoteGlobalIDString = 57A4D1AF1BA13D7A00F7D4B1; remoteInfo = "ReactiveCocoa-tvOS"; }; + 9A73DFE1216D3CEB0069AD76 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D04725E119E49ED7006002AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AC03A561F7CC3BF00EC33C1; + remoteInfo = ReactiveMapKit; + }; + 9A73E045216D404D0069AD76 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D04725E119E49ED7006002AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A73DFAC216D3C550069AD76; + remoteInfo = "ReactiveMapKit-iOS"; + }; + 9A73E047216D40550069AD76 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D04725E119E49ED7006002AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A73DFBD216D3C570069AD76; + remoteInfo = "ReactiveMapKit-tvOS"; + }; D04725F719E49ED7006002AA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D04725E119E49ED7006002AA /* Project object */; @@ -881,13 +474,42 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 654DE7AE2205F9C30048FE14 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 654DE7B32205FA200048FE14 /* ReactiveCocoa.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 654DE7AF2205F9D60048FE14 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 654DE7B02205F9DE0048FE14 /* ReactiveCocoa.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 654DE7B12205F9F60048FE14 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 654DE7B22205FA0A0048FE14 /* ReactiveCocoa.framework in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7DFBED151CDB8CEC00EE435B /* Copy Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 7DFBED211CDB8D8300EE435B /* Result.framework in Copy Frameworks */, 7DFBED201CDB8D7D00EE435B /* Nimble.framework in Copy Frameworks */, 7DFBED1F1CDB8D7800EE435B /* Quick.framework in Copy Frameworks */, 7DFBED1E1CDB8D7000EE435B /* ReactiveCocoa.framework in Copy Frameworks */, @@ -901,7 +523,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - CDC42E331AE7AC6D00965373 /* Result.framework in Copy Frameworks */, D01B7B6219EDD8FE00D26E01 /* Nimble.framework in Copy Frameworks */, D01B7B6319EDD8FE00D26E01 /* Quick.framework in Copy Frameworks */, D01B7B6419EDD94B00D26E01 /* ReactiveCocoa.framework in Copy Frameworks */, @@ -912,286 +533,164 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 02D260291C1D6DAF003ACC61 /* SignalLifetimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalLifetimeSpec.swift; sourceTree = ""; }; - 314304151ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MKAnnotationView+RACSignalSupport.h"; sourceTree = ""; }; - 314304161ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MKAnnotationView+RACSignalSupport.m"; sourceTree = ""; }; - 4A0E10FE1D2A92720065D310 /* Lifetime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lifetime.swift; sourceTree = ""; }; - 4A0E11031D2A95200065D310 /* LifetimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LifetimeSpec.swift; sourceTree = ""; }; + 004FD0061E26CDB300A03A82 /* NSButtonSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSButtonSpec.swift; sourceTree = ""; }; + 006518751E26865800C3139A /* NSButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSButton.swift; sourceTree = ""; }; + 3B30EE8B1E7BE529007CC8EF /* DeprecationsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeprecationsSpec.swift; sourceTree = ""; }; + 3BCAAC791DEE19BC00B30335 /* UIRefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIRefreshControl.swift; sourceTree = ""; }; + 3BCAAC7B1DEE1A2300B30335 /* UIRefreshControlSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIRefreshControlSpec.swift; sourceTree = ""; }; + 419139431DB910570043C9D1 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; + 4191394B1DBA002C0043C9D1 /* UIGestureRecognizerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizerSpec.swift; sourceTree = ""; }; + 4A0E10FE1D2A92720065D310 /* AnyObject+Lifetime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AnyObject+Lifetime.swift"; sourceTree = ""; }; + 4ABEFDE11DCFC8560066A8C2 /* UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; }; + 4ABEFDE31DCFCCD70066A8C2 /* UITableViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewSpec.swift; sourceTree = ""; }; + 4ABEFE231DCFCF5C0066A8C2 /* UICollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionView.swift; sourceTree = ""; }; + 4ABEFE271DCFCFA90066A8C2 /* UICollectionViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewSpec.swift; sourceTree = ""; }; + 4ABEFE2A1DCFD0030066A8C2 /* NSTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSTableView.swift; sourceTree = ""; }; + 4ABEFE2C1DCFD0180066A8C2 /* NSTableViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSTableViewSpec.swift; sourceTree = ""; }; + 4ABEFE2F1DCFD0530066A8C2 /* NSCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSCollectionView.swift; sourceTree = ""; }; + 4ABEFE311DCFD05F0066A8C2 /* NSCollectionViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSCollectionViewSpec.swift; sourceTree = ""; }; + 4EE6372D2090EEFA00ECD02A /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; + 4EE637302090EFD300ECD02A /* UIViewControllerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerSpec.swift; sourceTree = ""; }; + 4EE637352090F92600ECD02A /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = ""; }; + 531866F71DD7920400D1285F /* UIStepper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIStepper.swift; sourceTree = ""; }; + 531866F91DD7925600D1285F /* UIStepperSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIStepperSpec.swift; sourceTree = ""; }; + 537EC08021A950CA00D6EE18 /* NSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSView.swift; sourceTree = ""; }; + 537EC08221A9557400D6EE18 /* NSViewSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSViewSpec.swift; sourceTree = ""; }; + 538DCB781DCA5E6C00332880 /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = ""; }; + 538DCB7C1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraintSpec.swift; sourceTree = ""; }; + 53A6BED11DD4BCA90016C058 /* MKMapView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MKMapView.swift; sourceTree = ""; }; + 53A6BED51DD4BD2C0016C058 /* MKMapViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MKMapViewSpec.swift; sourceTree = ""; }; + 53AC46CB1DD6F97400C799E1 /* UISlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISlider.swift; sourceTree = ""; }; + 53AC46CE1DD6FC0000C799E1 /* UISliderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISliderSpec.swift; sourceTree = ""; }; 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "tvOS-Application.xcconfig"; sourceTree = ""; }; 57A4D2451BA13F9700F7D4B1 /* tvOS-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "tvOS-Base.xcconfig"; sourceTree = ""; }; 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "tvOS-Framework.xcconfig"; sourceTree = ""; }; 57A4D2471BA13F9700F7D4B1 /* tvOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "tvOS-StaticLibrary.xcconfig"; sourceTree = ""; }; - 7A70657D1A3F88B8001E8354 /* RACKVOProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACKVOProxy.h; sourceTree = ""; }; - 7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOProxy.m; sourceTree = ""; }; - 7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOProxySpec.m; sourceTree = ""; }; + 57C885532A47756D00FC133C /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5B76DE3F2498EDDC00E8B4F3 /* QueueScheduler+Factory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueScheduler+Factory.swift"; sourceTree = ""; }; + 7A8BA0F91FCC86FC003241C7 /* NSTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTextView.swift; sourceTree = ""; }; 7DFBED031CDB8C9500EE435B /* ReactiveCocoaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveCocoaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 834DE1001E4109750099F4E5 /* NSImageViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSImageViewSpec.swift; sourceTree = ""; }; + 834DE1111E4120340099F4E5 /* NSSegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSSegmentedControl.swift; sourceTree = ""; }; + 834DE1131E4122910099F4E5 /* NSSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSSlider.swift; sourceTree = ""; }; + 8392D8FC1DB93E5E00504ED4 /* NSImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSImageView.swift; sourceTree = ""; }; + 84ADBDA826660EA800ACE342 /* UIImpactFeedbackGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImpactFeedbackGenerator.swift; sourceTree = ""; }; + 84ADBDAE26660EE800ACE342 /* UINotificationFeedbackGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UINotificationFeedbackGenerator.swift; sourceTree = ""; }; + 84ADBDAF26660EE800ACE342 /* UISelectionFeedbackGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISelectionFeedbackGenerator.swift; sourceTree = ""; }; + 9A0726F21E912B610081F3F7 /* ActionProxySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionProxySpec.swift; sourceTree = ""; }; + 9A16753D1F80C35100B63650 /* ReactiveMapKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveMapKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A1675411F80C35100B63650 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9A1D05DF1D93E99100ACF44C /* NSObject+Association.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Association.swift"; sourceTree = ""; }; + 9A1D05EC1D93E9F100ACF44C /* UIActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorView.swift; sourceTree = ""; }; + 9A1D05ED1D93E9F100ACF44C /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = ""; }; + 9A1D05EE1D93E9F100ACF44C /* UIBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarItem.swift; sourceTree = ""; }; + 9A1D05EF1D93E9F100ACF44C /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = ""; }; + 9A1D05F11D93E9F100ACF44C /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = ""; }; + 9A1D05F21D93E9F100ACF44C /* UIDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDatePicker.swift; sourceTree = ""; }; + 9A1D05F31D93E9F100ACF44C /* UIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageView.swift; sourceTree = ""; }; + 9A1D05F41D93E9F100ACF44C /* UILabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UILabel.swift; sourceTree = ""; }; + 9A1D05F51D93E9F100ACF44C /* UIProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIProgressView.swift; sourceTree = ""; }; + 9A1D05F61D93E9F100ACF44C /* UISegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISegmentedControl.swift; sourceTree = ""; }; + 9A1D05F71D93E9F100ACF44C /* UISwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISwitch.swift; sourceTree = ""; }; + 9A1D05FA1D93E9F100ACF44C /* UITextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextField.swift; sourceTree = ""; }; + 9A1D05FB1D93E9F100ACF44C /* UITextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextView.swift; sourceTree = ""; }; + 9A1D05FC1D93E9F100ACF44C /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; + 9A1D06241D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIActivityIndicatorViewSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 9A1D06251D93EA7E00ACF44C /* UIBarButtonItemSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIBarButtonItemSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 9A1D06261D93EA7E00ACF44C /* UIButtonSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIButtonSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 9A1D06281D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIControl+EnableSendActionsForControlEvents.swift"; sourceTree = ""; }; + 9A1D06291D93EA7E00ACF44C /* UIControlSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIControlSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 9A1D062A1D93EA7E00ACF44C /* UIDatePickerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIDatePickerSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 9A1D062B1D93EA7E00ACF44C /* UIImageViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UIImageViewSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 9A1D062C1D93EA7E00ACF44C /* UILabelSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = UILabelSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 9A1D062D1D93EA7E00ACF44C /* UIProgressViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIProgressViewSpec.swift; sourceTree = ""; }; + 9A1D062E1D93EA7E00ACF44C /* UISegmentedControlSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISegmentedControlSpec.swift; sourceTree = ""; }; + 9A1D062F1D93EA7E00ACF44C /* UISwitchSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISwitchSpec.swift; sourceTree = ""; }; + 9A1D06321D93EA7E00ACF44C /* UITextFieldSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextFieldSpec.swift; sourceTree = ""; }; + 9A1D06331D93EA7E00ACF44C /* UITextViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextViewSpec.swift; sourceTree = ""; }; + 9A1D06351D93EA7E00ACF44C /* UIViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewSpec.swift; sourceTree = ""; }; + 9A1D065A1D93EC6E00ACF44C /* NSObject+Intercepting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Intercepting.swift"; sourceTree = ""; }; + 9A1D06751D9415FB00ACF44C /* ObjCRuntimeAliases.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ObjCRuntimeAliases.h; sourceTree = ""; }; 9A1E72B91D4DE96500CC20C3 /* KeyValueObservingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueObservingSpec.swift; sourceTree = ""; }; - 9A694EF21D5CE02E009B05BD /* UnidirectionalBinding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnidirectionalBinding.swift; sourceTree = ""; }; - 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deprecations+Removals.swift"; sourceTree = ""; }; + 9A24A8431DE1429600987AF9 /* SwizzlingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwizzlingSpec.swift; sourceTree = ""; }; + 9A2E425D1DAA6737006D909F /* CocoaTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CocoaTarget.swift; sourceTree = ""; }; + 9A3C54EA21726A1200E98207 /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A3C54EC21726A1200E98207 /* Quick.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A3C54F021726BD200E98207 /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A3C54F221726BD200E98207 /* Quick.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A54A2101DDF5B4D001739B3 /* InterceptingPerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterceptingPerformanceTests.swift; sourceTree = ""; }; + 9A54A21A1DE00D09001739B3 /* ObjC+Selector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObjC+Selector.swift"; sourceTree = ""; }; + 9A6AAA0D1DB6A4CF0013AAEA /* InterceptingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterceptingSpec.swift; sourceTree = ""; }; + 9A6AAA221DB8F51C0013AAEA /* UIKitReusableComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitReusableComponents.swift; sourceTree = ""; }; + 9A6AAA251DB8F5280013AAEA /* AppKitReusableComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppKitReusableComponents.swift; sourceTree = ""; }; + 9A6AAA271DB8F7EB0013AAEA /* AppKitReusableComponentsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppKitReusableComponentsSpec.swift; sourceTree = ""; }; + 9A6AAA291DB8F7F10013AAEA /* UIKitReusableComponentsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitReusableComponentsSpec.swift; sourceTree = ""; }; + 9A73DFBB216D3C550069AD76 /* ReactiveMapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveMapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A73DFCC216D3C570069AD76 /* ReactiveMapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveMapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A73DFF8216D3CEB0069AD76 /* ReactiveMapKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveMapKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A73E013216D3CEE0069AD76 /* ReactiveMapKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveMapKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A73E04B216D408D0069AD76 /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A73E04D216D408D0069AD76 /* Quick.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A7488471E3B8ACE00CD0317 /* DelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelegateProxy.swift; sourceTree = ""; }; + 9A7990CD1F1085D8001493A3 /* BindingTargetSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingTargetSpec.swift; sourceTree = ""; }; + 9A892D8E1E8D19BE00EA35F3 /* DelegateProxySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelegateProxySpec.swift; sourceTree = ""; }; + 9A90374E1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReactiveSwift+Lifetime.swift"; sourceTree = ""; }; + 9A9DFEE81DA7EFB60039EE1B /* AssociationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociationSpec.swift; sourceTree = ""; }; + 9AA0BD771DDE03DE00531FCF /* ObjC+Runtime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObjC+Runtime.swift"; sourceTree = ""; }; + 9AA0BD801DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObjC+RuntimeSubclassing.swift"; sourceTree = ""; }; + 9AA0BD891DDE153A00531FCF /* ObjC+Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObjC+Constants.swift"; sourceTree = ""; }; + 9AA0BD8E1DDE29F800531FCF /* NSObject+ObjCRuntime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+ObjCRuntime.swift"; sourceTree = ""; }; + 9AA0BD971DDE7A2200531FCF /* ObjCRuntimeAliases.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjCRuntimeAliases.m; sourceTree = ""; }; + 9AA6A1E31F11F9A800CA2257 /* KeyValueObservingSpec+Swift4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyValueObservingSpec+Swift4.swift"; sourceTree = ""; }; + 9AAD49871DED2C350068EC9B /* UIKeyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKeyboard.swift; sourceTree = ""; }; + 9AAD49891DED2F380068EC9B /* UIKeyboardSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKeyboardSpec.swift; sourceTree = ""; }; + 9AADB6F31F84AECB00EFFD19 /* Swift4TestInteroperability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swift4TestInteroperability.swift; sourceTree = ""; }; + 9AB15C791E26CD9A00997378 /* Deprecations+Removals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deprecations+Removals.swift"; sourceTree = ""; }; + 9AC03A571F7CC3BF00EC33C1 /* ReactiveMapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveMapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AC03A5A1F7CC3BF00EC33C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+KeyValueObserving.swift"; sourceTree = ""; }; + 9AD841D6204C29B90040F0C0 /* ReactiveCocoaTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReactiveCocoaTests-Bridging-Header.h"; sourceTree = ""; }; + 9AD841DA204C29B90040F0C0 /* MessageForwardingEntity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MessageForwardingEntity.h; sourceTree = ""; }; + 9AD841DB204C29B90040F0C0 /* MessageForwardingEntity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MessageForwardingEntity.m; sourceTree = ""; }; + 9ADE4A7B1DA44A9E005C2AC8 /* CocoaAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CocoaAction.swift; sourceTree = ""; }; + 9ADE4A881DA6D206005C2AC8 /* NSControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSControl.swift; sourceTree = ""; }; + 9ADE4A8D1DA6D965005C2AC8 /* NSControlSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSControlSpec.swift; sourceTree = ""; }; + 9ADE4A901DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+ReactiveExtensionsProvider.swift"; sourceTree = ""; }; + 9ADE4A951DA6F018005C2AC8 /* NSTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSTextField.swift; sourceTree = ""; }; + 9ADFE5A01DBFFBCF001E11F7 /* LifetimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LifetimeSpec.swift; sourceTree = ""; }; + 9ADFE5A41DC0001C001E11F7 /* Synchronizing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Synchronizing.swift; sourceTree = ""; }; + 9AE7C2A31DDD7F5100F7534C /* ObjC+Messages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObjC+Messages.swift"; sourceTree = ""; }; + 9AED64C41E496A3700321004 /* ActionProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionProxy.swift; sourceTree = ""; }; + 9AF0EA741D9A7FF700F27DDF /* NSObject+BindingTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+BindingTarget.swift"; sourceTree = ""; }; + 9AFCBFE21EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVOKVCExtensionSpec.swift; sourceTree = ""; }; + A91244E720389AEA0001BBCB /* MKLocalSearchRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MKLocalSearchRequest.swift; sourceTree = ""; }; A97451331B3A935E00F48E55 /* watchOS-Application.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Application.xcconfig"; sourceTree = ""; }; A97451341B3A935E00F48E55 /* watchOS-Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Base.xcconfig"; sourceTree = ""; }; A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Framework.xcconfig"; sourceTree = ""; }; A97451361B3A935E00F48E55 /* watchOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-StaticLibrary.xcconfig"; sourceTree = ""; }; A9B315541B3940610001CB9C /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A9D8BA70207CD7090031733D /* UIResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIResponder.swift; sourceTree = ""; }; + A9D8BA73207CD8430031733D /* UIResponderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIResponderSpec.swift; sourceTree = ""; }; + A9EB3D1C1E94ECAC002A9BCC /* UINavigationItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UINavigationItem.swift; sourceTree = ""; }; + A9EB3D1E1E94ED84002A9BCC /* UINavigationItemSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UINavigationItemSpec.swift; sourceTree = ""; }; + A9EB3D241E94F335002A9BCC /* UITabBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITabBarItem.swift; sourceTree = ""; }; + A9EB3D261E94F3C7002A9BCC /* UITabBarItemSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITabBarItemSpec.swift; sourceTree = ""; }; + A9EB3D7D1E955602002A9BCC /* UIFeedbackGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFeedbackGenerator.swift; sourceTree = ""; }; B696FB801A7640C00075236D /* TestError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = ""; }; - BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SignalProducerNimbleMatchers.swift; path = Swift/SignalProducerNimbleMatchers.swift; sourceTree = ""; }; - C7142DBB1CDEA167009F402D /* CocoaAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CocoaAction.swift; sourceTree = ""; }; - C79B64731CD38B2B003F2376 /* TestLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestLogger.swift; sourceTree = ""; }; - C79B647B1CD52E23003F2376 /* EventLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventLogger.swift; sourceTree = ""; }; - CA6F284F1C52626B001879D2 /* FlattenSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenSpec.swift; sourceTree = ""; }; + BF4335641E02AC7600AC88DD /* UIScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; }; + BF4335661E02EEDE00AC88DD /* UIScrollViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIScrollViewSpec.swift; sourceTree = ""; }; + BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalProducerNimbleMatchers.swift; sourceTree = ""; }; + BFBD68431E48DA21003CB580 /* UIPickerViewSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIPickerViewSpec.swift; sourceTree = ""; }; + BFCF775E1DFAD8A50058006E /* UISearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISearchBar.swift; sourceTree = ""; }; + BFCF77601DFAD9120058006E /* UISearchBarSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UISearchBarSpec.swift; sourceTree = ""; }; + BFE145881E43991A00208736 /* UIPickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIPickerView.swift; sourceTree = ""; }; CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicProperty.swift; sourceTree = ""; }; + CD42C69A1E951F6900AA9504 /* ReactiveCocoaTestsConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactiveCocoaTestsConfiguration.swift; sourceTree = ""; }; CD8401821CEE8ED7009F0ABF /* CocoaActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CocoaActionSpec.swift; sourceTree = ""; }; - CDC42E2E1AE7AB8B00965373 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Result.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D00004081A46864E000E7D41 /* TupleExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TupleExtensions.swift; sourceTree = ""; }; - D021671C1A6CD50500987861 /* ActionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ActionSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - D037642A19EDA41200A782A9 /* NSArray+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+RACSequenceAdditions.h"; sourceTree = ""; }; - D037642B19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+RACSequenceAdditions.m"; sourceTree = ""; }; - D037642C19EDA41200A782A9 /* NSControl+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSControl+RACCommandSupport.h"; sourceTree = ""; }; - D037642D19EDA41200A782A9 /* NSControl+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSControl+RACCommandSupport.m"; sourceTree = ""; }; - D037642E19EDA41200A782A9 /* NSControl+RACTextSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSControl+RACTextSignalSupport.h"; sourceTree = ""; }; - D037642F19EDA41200A782A9 /* NSControl+RACTextSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSControl+RACTextSignalSupport.m"; sourceTree = ""; }; - D037643019EDA41200A782A9 /* NSData+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+RACSupport.h"; sourceTree = ""; }; - D037643119EDA41200A782A9 /* NSData+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+RACSupport.m"; sourceTree = ""; }; - D037643219EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+RACSequenceAdditions.h"; sourceTree = ""; }; - D037643319EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+RACSequenceAdditions.m"; sourceTree = ""; }; - D037643419EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSEnumerator+RACSequenceAdditions.h"; sourceTree = ""; }; - D037643519EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSEnumerator+RACSequenceAdditions.m"; sourceTree = ""; }; - D037643619EDA41200A782A9 /* NSFileHandle+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileHandle+RACSupport.h"; sourceTree = ""; }; - D037643719EDA41200A782A9 /* NSFileHandle+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileHandle+RACSupport.m"; sourceTree = ""; }; - D037643819EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+RACSequenceAdditions.h"; sourceTree = ""; }; - D037643919EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+RACSequenceAdditions.m"; sourceTree = ""; }; - D037643A19EDA41200A782A9 /* NSInvocation+RACTypeParsing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSInvocation+RACTypeParsing.h"; sourceTree = ""; }; - D037643B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSInvocation+RACTypeParsing.m"; sourceTree = ""; }; - D037643C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+RACSupport.h"; sourceTree = ""; }; - D037643D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNotificationCenter+RACSupport.m"; sourceTree = ""; }; - D037643E19EDA41200A782A9 /* NSObject+RACAppKitBindings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACAppKitBindings.h"; sourceTree = ""; }; - D037643F19EDA41200A782A9 /* NSObject+RACAppKitBindings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACAppKitBindings.m"; sourceTree = ""; }; - D037644019EDA41200A782A9 /* NSObject+RACDeallocating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACDeallocating.h"; sourceTree = ""; }; - D037644119EDA41200A782A9 /* NSObject+RACDeallocating.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACDeallocating.m"; sourceTree = ""; }; - D037644219EDA41200A782A9 /* NSObject+RACDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACDescription.h"; sourceTree = ""; }; - D037644319EDA41200A782A9 /* NSObject+RACDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACDescription.m"; sourceTree = ""; }; - D037644419EDA41200A782A9 /* NSObject+RACKVOWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACKVOWrapper.h"; sourceTree = ""; }; - D037644519EDA41200A782A9 /* NSObject+RACKVOWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACKVOWrapper.m"; sourceTree = ""; }; - D037644619EDA41200A782A9 /* NSObject+RACLifting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACLifting.h"; sourceTree = ""; }; - D037644719EDA41200A782A9 /* NSObject+RACLifting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACLifting.m"; sourceTree = ""; }; - D037644819EDA41200A782A9 /* NSObject+RACPropertySubscribing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACPropertySubscribing.h"; sourceTree = ""; }; - D037644919EDA41200A782A9 /* NSObject+RACPropertySubscribing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACPropertySubscribing.m"; sourceTree = ""; }; - D037644A19EDA41200A782A9 /* NSObject+RACSelectorSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACSelectorSignal.h"; sourceTree = ""; }; - D037644B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACSelectorSignal.m"; sourceTree = ""; }; - D037644C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSOrderedSet+RACSequenceAdditions.h"; sourceTree = ""; }; - D037644D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSOrderedSet+RACSequenceAdditions.m"; sourceTree = ""; }; - D037644E19EDA41200A782A9 /* NSSet+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSSet+RACSequenceAdditions.h"; sourceTree = ""; }; - D037644F19EDA41200A782A9 /* NSSet+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSSet+RACSequenceAdditions.m"; sourceTree = ""; }; - D037645019EDA41200A782A9 /* NSString+RACKeyPathUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RACKeyPathUtilities.h"; sourceTree = ""; }; - D037645119EDA41200A782A9 /* NSString+RACKeyPathUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RACKeyPathUtilities.m"; sourceTree = ""; }; - D037645219EDA41200A782A9 /* NSString+RACSequenceAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RACSequenceAdditions.h"; sourceTree = ""; }; - D037645319EDA41200A782A9 /* NSString+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RACSequenceAdditions.m"; sourceTree = ""; }; - D037645419EDA41200A782A9 /* NSString+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RACSupport.h"; sourceTree = ""; }; - D037645519EDA41200A782A9 /* NSString+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RACSupport.m"; sourceTree = ""; }; - D037645619EDA41200A782A9 /* NSText+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSText+RACSignalSupport.h"; sourceTree = ""; }; - D037645719EDA41200A782A9 /* NSText+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSText+RACSignalSupport.m"; sourceTree = ""; }; - D037645819EDA41200A782A9 /* NSURLConnection+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLConnection+RACSupport.h"; sourceTree = ""; }; - D037645919EDA41200A782A9 /* NSURLConnection+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLConnection+RACSupport.m"; sourceTree = ""; }; - D037645A19EDA41200A782A9 /* NSUserDefaults+RACSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserDefaults+RACSupport.h"; sourceTree = ""; }; - D037645B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUserDefaults+RACSupport.m"; sourceTree = ""; }; - D037645C19EDA41200A782A9 /* RACArraySequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACArraySequence.h; sourceTree = ""; }; - D037645D19EDA41200A782A9 /* RACArraySequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACArraySequence.m; sourceTree = ""; }; - D037646019EDA41200A782A9 /* RACBehaviorSubject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACBehaviorSubject.h; sourceTree = ""; }; - D037646119EDA41200A782A9 /* RACBehaviorSubject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACBehaviorSubject.m; sourceTree = ""; }; - D037646219EDA41200A782A9 /* RACBlockTrampoline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACBlockTrampoline.h; sourceTree = ""; }; - D037646319EDA41200A782A9 /* RACBlockTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACBlockTrampoline.m; sourceTree = ""; }; - D037646419EDA41200A782A9 /* RACChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACChannel.h; sourceTree = ""; }; - D037646519EDA41200A782A9 /* RACChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACChannel.m; sourceTree = ""; }; - D037646619EDA41200A782A9 /* RACCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACCommand.h; sourceTree = ""; }; - D037646719EDA41200A782A9 /* RACCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACCommand.m; sourceTree = ""; }; - D037646819EDA41200A782A9 /* RACCompoundDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACCompoundDisposable.h; sourceTree = ""; }; - D037646919EDA41200A782A9 /* RACCompoundDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACCompoundDisposable.m; sourceTree = ""; }; - D037646A19EDA41200A782A9 /* RACCompoundDisposableProvider.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; path = RACCompoundDisposableProvider.d; sourceTree = ""; }; - D037646B19EDA41200A782A9 /* RACDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDelegateProxy.h; sourceTree = ""; }; - D037646C19EDA41200A782A9 /* RACDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDelegateProxy.m; sourceTree = ""; }; - D037646D19EDA41200A782A9 /* RACDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDisposable.h; sourceTree = ""; }; - D037646E19EDA41200A782A9 /* RACDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDisposable.m; sourceTree = ""; }; - D037646F19EDA41200A782A9 /* RACDynamicSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDynamicSequence.h; sourceTree = ""; }; - D037647019EDA41200A782A9 /* RACDynamicSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDynamicSequence.m; sourceTree = ""; }; - D037647119EDA41200A782A9 /* RACDynamicSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACDynamicSignal.h; sourceTree = ""; }; - D037647219EDA41200A782A9 /* RACDynamicSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDynamicSignal.m; sourceTree = ""; }; - D037647319EDA41200A782A9 /* RACEagerSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEagerSequence.h; sourceTree = ""; }; - D037647419EDA41200A782A9 /* RACEagerSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEagerSequence.m; sourceTree = ""; }; - D037647519EDA41200A782A9 /* RACEmptySequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEmptySequence.h; sourceTree = ""; }; - D037647619EDA41200A782A9 /* RACEmptySequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEmptySequence.m; sourceTree = ""; }; - D037647719EDA41200A782A9 /* RACEmptySignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEmptySignal.h; sourceTree = ""; }; - D037647819EDA41200A782A9 /* RACEmptySignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEmptySignal.m; sourceTree = ""; }; - D037647919EDA41200A782A9 /* RACErrorSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACErrorSignal.h; sourceTree = ""; }; - D037647A19EDA41200A782A9 /* RACErrorSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACErrorSignal.m; sourceTree = ""; }; - D037647B19EDA41200A782A9 /* RACEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACEvent.h; sourceTree = ""; }; - D037647C19EDA41200A782A9 /* RACEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEvent.m; sourceTree = ""; }; - D037647D19EDA41200A782A9 /* RACGroupedSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACGroupedSignal.h; sourceTree = ""; }; - D037647E19EDA41200A782A9 /* RACGroupedSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACGroupedSignal.m; sourceTree = ""; }; - D037647F19EDA41200A782A9 /* RACImmediateScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACImmediateScheduler.h; sourceTree = ""; }; - D037648019EDA41200A782A9 /* RACImmediateScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACImmediateScheduler.m; sourceTree = ""; }; - D037648119EDA41200A782A9 /* RACIndexSetSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACIndexSetSequence.h; sourceTree = ""; }; - D037648219EDA41200A782A9 /* RACIndexSetSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACIndexSetSequence.m; sourceTree = ""; }; - D037648319EDA41200A782A9 /* RACKVOChannel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACKVOChannel.h; sourceTree = ""; }; - D037648419EDA41200A782A9 /* RACKVOChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOChannel.m; sourceTree = ""; }; - D037648519EDA41200A782A9 /* RACKVOTrampoline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACKVOTrampoline.h; sourceTree = ""; }; - D037648619EDA41200A782A9 /* RACKVOTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOTrampoline.m; sourceTree = ""; }; - D037648719EDA41200A782A9 /* RACMulticastConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACMulticastConnection.h; sourceTree = ""; }; - D037648819EDA41200A782A9 /* RACMulticastConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACMulticastConnection.m; sourceTree = ""; }; - D037648919EDA41200A782A9 /* RACMulticastConnection+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACMulticastConnection+Private.h"; sourceTree = ""; }; - D037648C19EDA41200A782A9 /* RACPassthroughSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACPassthroughSubscriber.h; sourceTree = ""; }; - D037648D19EDA41200A782A9 /* RACPassthroughSubscriber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACPassthroughSubscriber.m; sourceTree = ""; }; - D037648E19EDA41200A782A9 /* RACQueueScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACQueueScheduler.h; sourceTree = ""; }; - D037648F19EDA41200A782A9 /* RACQueueScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACQueueScheduler.m; sourceTree = ""; }; - D037649019EDA41200A782A9 /* RACQueueScheduler+Subclass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACQueueScheduler+Subclass.h"; sourceTree = ""; }; - D037649119EDA41200A782A9 /* RACReplaySubject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACReplaySubject.h; sourceTree = ""; }; - D037649219EDA41200A782A9 /* RACReplaySubject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACReplaySubject.m; sourceTree = ""; }; - D037649319EDA41200A782A9 /* RACReturnSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACReturnSignal.h; sourceTree = ""; }; - D037649419EDA41200A782A9 /* RACReturnSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACReturnSignal.m; sourceTree = ""; }; - D037649519EDA41200A782A9 /* RACScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACScheduler.h; sourceTree = ""; }; - D037649619EDA41200A782A9 /* RACScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACScheduler.m; sourceTree = ""; }; - D037649719EDA41200A782A9 /* RACScheduler+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACScheduler+Private.h"; sourceTree = ""; }; - D037649819EDA41200A782A9 /* RACScheduler+Subclass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACScheduler+Subclass.h"; sourceTree = ""; }; - D037649919EDA41200A782A9 /* RACScopedDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACScopedDisposable.h; sourceTree = ""; }; - D037649A19EDA41200A782A9 /* RACScopedDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACScopedDisposable.m; sourceTree = ""; }; - D037649B19EDA41200A782A9 /* RACSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSequence.h; sourceTree = ""; }; - D037649C19EDA41200A782A9 /* RACSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequence.m; sourceTree = ""; }; - D037649D19EDA41200A782A9 /* RACSerialDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSerialDisposable.h; sourceTree = ""; }; - D037649E19EDA41200A782A9 /* RACSerialDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSerialDisposable.m; sourceTree = ""; }; - D037649F19EDA41200A782A9 /* RACSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSignal.h; sourceTree = ""; }; - D03764A019EDA41200A782A9 /* RACSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSignal.m; sourceTree = ""; }; - D03764A119EDA41200A782A9 /* RACSignal+Operations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACSignal+Operations.h"; sourceTree = ""; }; - D03764A219EDA41200A782A9 /* RACSignal+Operations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RACSignal+Operations.m"; sourceTree = ""; }; - D03764A319EDA41200A782A9 /* RACSignalProvider.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; path = RACSignalProvider.d; sourceTree = ""; }; - D03764A419EDA41200A782A9 /* RACSignalSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSignalSequence.h; sourceTree = ""; }; - D03764A519EDA41200A782A9 /* RACSignalSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSignalSequence.m; sourceTree = ""; }; - D03764A619EDA41200A782A9 /* RACStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACStream.h; sourceTree = ""; }; - D03764A719EDA41200A782A9 /* RACStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACStream.m; sourceTree = ""; }; - D03764A819EDA41200A782A9 /* RACStream+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACStream+Private.h"; sourceTree = ""; }; - D03764A919EDA41200A782A9 /* RACStringSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACStringSequence.h; sourceTree = ""; }; - D03764AA19EDA41200A782A9 /* RACStringSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACStringSequence.m; sourceTree = ""; }; - D03764AB19EDA41200A782A9 /* RACSubject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubject.h; sourceTree = ""; }; - D03764AC19EDA41200A782A9 /* RACSubject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubject.m; sourceTree = ""; }; - D03764AD19EDA41200A782A9 /* RACSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriber.h; sourceTree = ""; }; - D03764AE19EDA41200A782A9 /* RACSubscriber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriber.m; sourceTree = ""; }; - D03764AF19EDA41200A782A9 /* RACSubscriber+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACSubscriber+Private.h"; sourceTree = ""; }; - D03764B019EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriptingAssignmentTrampoline.h; sourceTree = ""; }; - D03764B119EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriptingAssignmentTrampoline.m; sourceTree = ""; }; - D03764B219EDA41200A782A9 /* RACSubscriptionScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriptionScheduler.h; sourceTree = ""; }; - D03764B319EDA41200A782A9 /* RACSubscriptionScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriptionScheduler.m; sourceTree = ""; }; - D03764B419EDA41200A782A9 /* RACTargetQueueScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTargetQueueScheduler.h; sourceTree = ""; }; - D03764B519EDA41200A782A9 /* RACTargetQueueScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTargetQueueScheduler.m; sourceTree = ""; }; - D03764B619EDA41200A782A9 /* RACTestScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestScheduler.h; sourceTree = ""; }; - D03764B719EDA41200A782A9 /* RACTestScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestScheduler.m; sourceTree = ""; }; - D03764B819EDA41200A782A9 /* RACTuple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTuple.h; sourceTree = ""; }; - D03764B919EDA41200A782A9 /* RACTuple.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTuple.m; sourceTree = ""; }; - D03764BA19EDA41200A782A9 /* RACTupleSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTupleSequence.h; sourceTree = ""; }; - D03764BB19EDA41200A782A9 /* RACTupleSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTupleSequence.m; sourceTree = ""; }; - D03764BC19EDA41200A782A9 /* RACUnarySequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACUnarySequence.h; sourceTree = ""; }; - D03764BD19EDA41200A782A9 /* RACUnarySequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACUnarySequence.m; sourceTree = ""; }; - D03764BE19EDA41200A782A9 /* RACUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACUnit.h; sourceTree = ""; }; - D03764BF19EDA41200A782A9 /* RACUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACUnit.m; sourceTree = ""; }; - D03764C019EDA41200A782A9 /* RACValueTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACValueTransformer.h; sourceTree = ""; }; - D03764C119EDA41200A782A9 /* RACValueTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACValueTransformer.m; sourceTree = ""; }; - D03764C219EDA41200A782A9 /* UIActionSheet+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIActionSheet+RACSignalSupport.h"; sourceTree = ""; }; - D03764C319EDA41200A782A9 /* UIActionSheet+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIActionSheet+RACSignalSupport.m"; sourceTree = ""; }; - D03764C419EDA41200A782A9 /* UIAlertView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertView+RACSignalSupport.h"; sourceTree = ""; }; - D03764C519EDA41200A782A9 /* UIAlertView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertView+RACSignalSupport.m"; sourceTree = ""; }; - D03764C619EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+RACCommandSupport.h"; sourceTree = ""; }; - D03764C719EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+RACCommandSupport.m"; sourceTree = ""; }; - D03764C819EDA41200A782A9 /* UIButton+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+RACCommandSupport.h"; sourceTree = ""; }; - D03764C919EDA41200A782A9 /* UIButton+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+RACCommandSupport.m"; sourceTree = ""; }; - D03764CA19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionReusableView+RACSignalSupport.h"; sourceTree = ""; }; - D03764CB19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionReusableView+RACSignalSupport.m"; sourceTree = ""; }; - D03764CC19EDA41200A782A9 /* UIControl+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+RACSignalSupport.h"; sourceTree = ""; }; - D03764CD19EDA41200A782A9 /* UIControl+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+RACSignalSupport.m"; sourceTree = ""; }; - D03764CE19EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+RACSignalSupportPrivate.h"; sourceTree = ""; }; - D03764CF19EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+RACSignalSupportPrivate.m"; sourceTree = ""; }; - D03764D019EDA41200A782A9 /* UIDatePicker+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIDatePicker+RACSignalSupport.h"; sourceTree = ""; }; - D03764D119EDA41200A782A9 /* UIDatePicker+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDatePicker+RACSignalSupport.m"; sourceTree = ""; }; - D03764D219EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIGestureRecognizer+RACSignalSupport.h"; sourceTree = ""; }; - D03764D319EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIGestureRecognizer+RACSignalSupport.m"; sourceTree = ""; }; - D03764D419EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImagePickerController+RACSignalSupport.h"; sourceTree = ""; }; - D03764D519EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImagePickerController+RACSignalSupport.m"; sourceTree = ""; }; - D03764D619EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIRefreshControl+RACCommandSupport.h"; sourceTree = ""; }; - D03764D719EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIRefreshControl+RACCommandSupport.m"; sourceTree = ""; }; - D03764D819EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UISegmentedControl+RACSignalSupport.h"; sourceTree = ""; }; - D03764D919EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UISegmentedControl+RACSignalSupport.m"; sourceTree = ""; }; - D03764DA19EDA41200A782A9 /* UISlider+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UISlider+RACSignalSupport.h"; sourceTree = ""; }; - D03764DB19EDA41200A782A9 /* UISlider+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UISlider+RACSignalSupport.m"; sourceTree = ""; }; - D03764DC19EDA41200A782A9 /* UIStepper+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIStepper+RACSignalSupport.h"; sourceTree = ""; }; - D03764DD19EDA41200A782A9 /* UIStepper+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIStepper+RACSignalSupport.m"; sourceTree = ""; }; - D03764DE19EDA41200A782A9 /* UISwitch+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UISwitch+RACSignalSupport.h"; sourceTree = ""; }; - D03764DF19EDA41200A782A9 /* UISwitch+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UISwitch+RACSignalSupport.m"; sourceTree = ""; }; - D03764E019EDA41200A782A9 /* UITableViewCell+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITableViewCell+RACSignalSupport.h"; sourceTree = ""; }; - D03764E119EDA41200A782A9 /* UITableViewCell+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITableViewCell+RACSignalSupport.m"; sourceTree = ""; }; - D03764E219EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITableViewHeaderFooterView+RACSignalSupport.h"; sourceTree = ""; }; - D03764E319EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITableViewHeaderFooterView+RACSignalSupport.m"; sourceTree = ""; }; - D03764E419EDA41200A782A9 /* UITextField+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+RACSignalSupport.h"; sourceTree = ""; }; - D03764E519EDA41200A782A9 /* UITextField+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+RACSignalSupport.m"; sourceTree = ""; }; - D03764E619EDA41200A782A9 /* UITextView+RACSignalSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextView+RACSignalSupport.h"; sourceTree = ""; }; - D03764E719EDA41200A782A9 /* UITextView+RACSignalSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextView+RACSignalSupport.m"; sourceTree = ""; }; - D037666619EDA57100A782A9 /* EXTKeyPathCoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTKeyPathCoding.h; sourceTree = ""; }; - D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTRuntimeExtensions.h; sourceTree = ""; }; - D037666819EDA57100A782A9 /* EXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTRuntimeExtensions.m; sourceTree = ""; }; - D037666919EDA57100A782A9 /* EXTScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTScope.h; sourceTree = ""; }; - D037666A19EDA57100A782A9 /* metamacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = metamacros.h; sourceTree = ""; }; - D037667619EDA60000A782A9 /* NSControllerRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSControllerRACSupportSpec.m; sourceTree = ""; }; - D037667819EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSEnumeratorRACSequenceAdditionsSpec.m; sourceTree = ""; }; - D037667919EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSNotificationCenterRACSupportSpec.m; sourceTree = ""; }; - D037667A19EDA60000A782A9 /* NSObjectRACAppKitBindingsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACAppKitBindingsSpec.m; sourceTree = ""; }; - D037667B19EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACDeallocatingSpec.m; sourceTree = ""; }; - D037667C19EDA60000A782A9 /* NSObjectRACLiftingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACLiftingSpec.m; sourceTree = ""; }; - D037667D19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSObjectRACPropertySubscribingExamples.h; sourceTree = ""; }; - D037667E19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACPropertySubscribingExamples.m; sourceTree = ""; }; - D037667F19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACPropertySubscribingSpec.m; sourceTree = ""; }; - D037668019EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACSelectorSignalSpec.m; sourceTree = ""; }; - D037668119EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSStringRACKeyPathUtilitiesSpec.m; sourceTree = ""; }; - D037668319EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSURLConnectionRACSupportSpec.m; sourceTree = ""; }; - D037668419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSUserDefaultsRACSupportSpec.m; sourceTree = ""; }; - D037668619EDA60000A782A9 /* RACBlockTrampolineSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACBlockTrampolineSpec.m; sourceTree = ""; }; - D037668719EDA60000A782A9 /* RACChannelExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACChannelExamples.h; sourceTree = ""; }; - D037668819EDA60000A782A9 /* RACChannelExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACChannelExamples.m; sourceTree = ""; }; - D037668919EDA60000A782A9 /* RACChannelSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACChannelSpec.m; sourceTree = ""; }; - D037668A19EDA60000A782A9 /* RACCommandSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACCommandSpec.m; sourceTree = ""; }; - D037668B19EDA60000A782A9 /* RACCompoundDisposableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACCompoundDisposableSpec.m; sourceTree = ""; }; - D037668C19EDA60000A782A9 /* RACControlCommandExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACControlCommandExamples.h; sourceTree = ""; }; - D037668D19EDA60000A782A9 /* RACControlCommandExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACControlCommandExamples.m; sourceTree = ""; }; - D037668E19EDA60000A782A9 /* RACDelegateProxySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDelegateProxySpec.m; sourceTree = ""; }; - D037668F19EDA60000A782A9 /* RACDisposableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACDisposableSpec.m; sourceTree = ""; }; - D037669019EDA60000A782A9 /* RACEventSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACEventSpec.m; sourceTree = ""; }; - D037669119EDA60000A782A9 /* RACKVOChannelSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOChannelSpec.m; sourceTree = ""; }; - D037669219EDA60000A782A9 /* RACKVOWrapperSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOWrapperSpec.m; sourceTree = ""; }; - D037669319EDA60000A782A9 /* RACMulticastConnectionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACMulticastConnectionSpec.m; sourceTree = ""; }; - D037669419EDA60000A782A9 /* RACPropertySignalExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACPropertySignalExamples.h; sourceTree = ""; }; - D037669519EDA60000A782A9 /* RACPropertySignalExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACPropertySignalExamples.m; sourceTree = ""; }; - D037669619EDA60000A782A9 /* RACSchedulerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSchedulerSpec.m; sourceTree = ""; }; - D037669719EDA60000A782A9 /* RACSequenceAdditionsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequenceAdditionsSpec.m; sourceTree = ""; }; - D037669819EDA60000A782A9 /* RACSequenceExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSequenceExamples.h; sourceTree = ""; }; - D037669919EDA60000A782A9 /* RACSequenceExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequenceExamples.m; sourceTree = ""; }; - D037669A19EDA60000A782A9 /* RACSequenceSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSequenceSpec.m; sourceTree = ""; }; - D037669B19EDA60000A782A9 /* RACSerialDisposableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSerialDisposableSpec.m; sourceTree = ""; }; - D037669C19EDA60000A782A9 /* RACSignalSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSignalSpec.m; sourceTree = ""; }; - D037669F19EDA60000A782A9 /* RACStreamExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACStreamExamples.h; sourceTree = ""; }; - D03766A019EDA60000A782A9 /* RACStreamExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACStreamExamples.m; sourceTree = ""; }; - D03766A119EDA60000A782A9 /* RACSubclassObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubclassObject.h; sourceTree = ""; }; - D03766A219EDA60000A782A9 /* RACSubclassObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubclassObject.m; sourceTree = ""; }; - D03766A319EDA60000A782A9 /* RACSubjectSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubjectSpec.m; sourceTree = ""; }; - D03766A419EDA60000A782A9 /* RACSubscriberExamples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSubscriberExamples.h; sourceTree = ""; }; - D03766A519EDA60000A782A9 /* RACSubscriberExamples.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriberExamples.m; sourceTree = ""; }; - D03766A619EDA60000A782A9 /* RACSubscriberSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriberSpec.m; sourceTree = ""; }; - D03766A719EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscriptingAssignmentTrampolineSpec.m; sourceTree = ""; }; - D03766A819EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTargetQueueSchedulerSpec.m; sourceTree = ""; }; - D03766B019EDA60000A782A9 /* RACTupleSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTupleSpec.m; sourceTree = ""; }; D03766B119EDA60000A782A9 /* test-data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "test-data.json"; sourceTree = ""; }; - D03766B219EDA60000A782A9 /* UIActionSheetRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIActionSheetRACSupportSpec.m; sourceTree = ""; }; - D03766B319EDA60000A782A9 /* UIAlertViewRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIAlertViewRACSupportSpec.m; sourceTree = ""; }; - D03766B419EDA60000A782A9 /* UIBarButtonItemRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIBarButtonItemRACSupportSpec.m; sourceTree = ""; }; - D03766B519EDA60000A782A9 /* UIButtonRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIButtonRACSupportSpec.m; sourceTree = ""; }; - D03766B719EDA60000A782A9 /* UIImagePickerControllerRACSupportSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImagePickerControllerRACSupportSpec.m; sourceTree = ""; }; D037672B19EDA75D00A782A9 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = ""; }; D04725EA19E49ED7006002AA /* ReactiveCocoa.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D04725EE19E49ED7006002AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D04725EF19E49ED7006002AA /* ReactiveCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactiveCocoa.h; sourceTree = ""; }; @@ -1211,43 +710,33 @@ D047263319E49FE8006002AA /* iOS-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Base.xcconfig"; sourceTree = ""; }; D047263419E49FE8006002AA /* iOS-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Framework.xcconfig"; sourceTree = ""; }; D047263519E49FE8006002AA /* iOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-StaticLibrary.xcconfig"; sourceTree = ""; }; - D047263719E49FE8006002AA /* Mac-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Application.xcconfig"; sourceTree = ""; }; - D047263819E49FE8006002AA /* Mac-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Base.xcconfig"; sourceTree = ""; }; - D047263919E49FE8006002AA /* Mac-DynamicLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-DynamicLibrary.xcconfig"; sourceTree = ""; }; - D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Framework.xcconfig"; sourceTree = ""; }; - D047263B19E49FE8006002AA /* Mac-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-StaticLibrary.xcconfig"; sourceTree = ""; }; + D047263719E49FE8006002AA /* macOS-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "macOS-Application.xcconfig"; sourceTree = ""; }; + D047263819E49FE8006002AA /* macOS-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "macOS-Base.xcconfig"; sourceTree = ""; }; + D047263919E49FE8006002AA /* macOS-DynamicLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "macOS-DynamicLibrary.xcconfig"; sourceTree = ""; }; + D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "macOS-Framework.xcconfig"; sourceTree = ""; }; + D047263B19E49FE8006002AA /* macOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "macOS-StaticLibrary.xcconfig"; sourceTree = ""; }; D047263C19E49FE8006002AA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D05E662419EDD82000904ACA /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D08C54AF1A69A2AC00AD8286 /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; - D08C54B01A69A2AC00AD8286 /* Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Property.swift; sourceTree = ""; }; - D08C54B11A69A2AC00AD8286 /* Signal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signal.swift; sourceTree = ""; }; - D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalProducer.swift; sourceTree = ""; }; - D08C54B51A69A3DB00AD8286 /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; - D0A226071A72E0E900D33B74 /* SignalSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SignalSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - D0A2260A1A72E6C500D33B74 /* SignalProducerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SignalProducerSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - D0A2260D1A72F16D00D33B74 /* PropertySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PropertySpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - D0A226101A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObjectiveCBridgingSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - D0C312BB19EF2A5800984962 /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; - D0C312BC19EF2A5800984962 /* Bag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bag.swift; sourceTree = ""; }; - D0C312BE19EF2A5800984962 /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Disposable.swift; sourceTree = ""; }; - D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectiveCBridging.swift; sourceTree = ""; }; - D0C312C819EF2A5800984962 /* Scheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scheduler.swift; sourceTree = ""; }; - D0C312EE19EF2A7700984962 /* AtomicSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AtomicSpec.swift; sourceTree = ""; }; - D0C312EF19EF2A7700984962 /* BagSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BagSpec.swift; sourceTree = ""; }; - D0C312F019EF2A7700984962 /* DisposableSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisposableSpec.swift; sourceTree = ""; }; - D0C312F219EF2A7700984962 /* SchedulerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchedulerSpec.swift; sourceTree = ""; }; - D0C3131719EF2D9700984962 /* RACTestExampleScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestExampleScheduler.h; sourceTree = ""; }; - D0C3131819EF2D9700984962 /* RACTestExampleScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestExampleScheduler.m; sourceTree = ""; }; - D0C3131919EF2D9700984962 /* RACTestObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestObject.h; sourceTree = ""; }; - D0C3131A19EF2D9700984962 /* RACTestObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestObject.m; sourceTree = ""; }; - D0C3131B19EF2D9700984962 /* RACTestSchedulerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestSchedulerSpec.m; sourceTree = ""; }; - D0C3131C19EF2D9700984962 /* RACTestUIButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTestUIButton.h; sourceTree = ""; }; - D0C3131D19EF2D9700984962 /* RACTestUIButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTestUIButton.m; sourceTree = ""; }; - D8024DB11B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SignalProducerLiftingSpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - D8170FC01B100EBC004192AD /* FoundationExtensionsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationExtensionsSpec.swift; sourceTree = ""; }; - D85C65291C0D84C7005A77AD /* Flatten.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Flatten.swift; sourceTree = ""; }; - D871D69E1B3B29A40070F16C /* Optional.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Optional.swift; sourceTree = ""; }; - EBCC7DBB1BBF010C00A2AE92 /* Observer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Observer.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DynamicPropertySpec.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + D9558AB51DFF7B90003254E1 /* NSPopUpButtonSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPopUpButtonSpec.swift; sourceTree = ""; }; + D9558AB71DFF805A003254E1 /* NSPopUpButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSPopUpButton.swift; sourceTree = ""; }; + E3AA54C02142918B0077B206 /* WKInterfaceLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKInterfaceLabel.swift; sourceTree = ""; }; + E3AA54C2214292540077B206 /* WKInterfaceButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKInterfaceButton.swift; sourceTree = ""; }; + E6124BA8267DF3C0005A3490 /* macOS-XCTest.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "macOS-XCTest.xcconfig"; sourceTree = ""; }; + E921428A2284A12B007E412D /* WKInterfaceObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceObject.swift; sourceTree = ""; }; + E921428D2284A44F007E412D /* WKInterfaceDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceDate.swift; sourceTree = ""; }; + E921428F2284A4EF007E412D /* WKInterfaceTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceTimer.swift; sourceTree = ""; }; + E92142912284AA64007E412D /* WKInterfaceSwitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceSwitch.swift; sourceTree = ""; }; + E92142932284ABDF007E412D /* WKInterfaceSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceSlider.swift; sourceTree = ""; }; + E92142972284AEF0007E412D /* WKInterfaceGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceGroup.swift; sourceTree = ""; }; + E92142992284AF99007E412D /* WKInterfaceSeparator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceSeparator.swift; sourceTree = ""; }; + E921429B2284B0A8007E412D /* WKInterfaceImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceImage.swift; sourceTree = ""; }; + E921429D2284B1C4007E412D /* WKInterfaceVolumeControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceVolumeControl.swift; sourceTree = ""; }; + E921429F2284B4FF007E412D /* WKInterfacePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfacePicker.swift; sourceTree = ""; }; + E92142A12284B64E007E412D /* WKInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceController.swift; sourceTree = ""; }; + E92142A8228552CC007E412D /* WKInterfaceMovie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceMovie.swift; sourceTree = ""; }; + E92142AA228553C5007E412D /* WKInterfaceInlineMovie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKInterfaceInlineMovie.swift; sourceTree = ""; }; + F440783A2371BE3D00F103E7 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1255,7 +744,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 57A4D2081BA13D7A00F7D4B1 /* Result.framework in Frameworks */, + 9A73E03C216D3FFC0069AD76 /* ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 57C8854B2A47756D00FC133C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 57C8854C2A47756D00FC133C /* ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1266,6 +763,70 @@ CDF066CA1CDC1CA200199626 /* Nimble.framework in Frameworks */, CDF066CB1CDC1CA200199626 /* Quick.framework in Frameworks */, 7DFBED081CDB8C9500EE435B /* ReactiveCocoa.framework in Frameworks */, + 9A73E059216D40E30069AD76 /* ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A16753A1F80C35100B63650 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73E042216D40280069AD76 /* ReactiveSwift.framework in Frameworks */, + 9A73E052216D40AC0069AD76 /* ReactiveCocoa.framework in Frameworks */, + 9A73E051216D40A20069AD76 /* ReactiveMapKit.framework in Frameworks */, + 9A73E04F216D40980069AD76 /* Nimble.framework in Frameworks */, + 9A73E050216D40980069AD76 /* Quick.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFB0216D3C550069AD76 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73E01C216D3E700069AD76 /* ReactiveCocoa.framework in Frameworks */, + 9A73E03E216D400D0069AD76 /* ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFC1216D3C570069AD76 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73E05F216D41FA0069AD76 /* ReactiveCocoa.framework in Frameworks */, + 9A73E036216D3FC50069AD76 /* ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFE4216D3CEB0069AD76 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73E055216D40D10069AD76 /* ReactiveSwift.framework in Frameworks */, + 9A73E053216D40C80069AD76 /* ReactiveCocoa.framework in Frameworks */, + 9A73E054216D40CD0069AD76 /* ReactiveMapKit.framework in Frameworks */, + 9A3C54F121726BD200E98207 /* Nimble.framework in Frameworks */, + 9A3C54F321726BD200E98207 /* Quick.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFFF216D3CEE0069AD76 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73E040216D40170069AD76 /* ReactiveSwift.framework in Frameworks */, + 9A73E04A216D40850069AD76 /* ReactiveCocoa.framework in Frameworks */, + 9A73E049216D40800069AD76 /* ReactiveMapKit.framework in Frameworks */, + 9A73E04C216D408D0069AD76 /* Nimble.framework in Frameworks */, + 9A73E04E216D408D0069AD76 /* Quick.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AC03A531F7CC3BF00EC33C1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73E03D216D40070069AD76 /* ReactiveSwift.framework in Frameworks */, + 9A73E015216D3E660069AD76 /* ReactiveCocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1273,7 +834,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A9B315C91B3940980001CB9C /* Result.framework in Frameworks */, + 9A73E03B216D3FF50069AD76 /* ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1281,7 +842,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CDC42E2F1AE7AB8B00965373 /* Result.framework in Frameworks */, + 9A73E039216D3FEB0069AD76 /* ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1289,10 +850,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CDC42E301AE7AB8B00965373 /* Result.framework in Frameworks */, - D05E662519EDD82000904ACA /* Nimble.framework in Frameworks */, - D037672D19EDA75D00A782A9 /* Quick.framework in Frameworks */, + 9A3C54EB21726A1200E98207 /* Nimble.framework in Frameworks */, + 9A3C54ED21726A1200E98207 /* Quick.framework in Frameworks */, D04725F619E49ED7006002AA /* ReactiveCocoa.framework in Frameworks */, + 9A73E05D216D40F70069AD76 /* ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1300,7 +861,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CDC42E311AE7AB8B00965373 /* Result.framework in Frameworks */, + 9A73E03A216D3FF10069AD76 /* ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1311,12 +872,47 @@ D05E662619EDD83000904ACA /* Nimble.framework in Frameworks */, D037672F19EDA78B00A782A9 /* Quick.framework in Frameworks */, D047261719E49F82006002AA /* ReactiveCocoa.framework in Frameworks */, + 9A73E05B216D40EC0069AD76 /* ReactiveSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 056D088022C00A6A00291F50 /* ReactiveCocoaObjC */ = { + isa = PBXGroup; + children = ( + F44078372371B6E400F103E7 /* include */, + 9AA0BD971DDE7A2200531FCF /* ObjCRuntimeAliases.m */, + ); + path = ReactiveCocoaObjC; + sourceTree = ""; + }; + 056D088222C00B2700291F50 /* ReactiveCocoaObjCTestSupport */ = { + isa = PBXGroup; + children = ( + F440783B2371C00800F103E7 /* include */, + 9AD841DB204C29B90040F0C0 /* MessageForwardingEntity.m */, + ); + path = ReactiveCocoaObjCTestSupport; + sourceTree = ""; + }; + 538DCB761DCA5E1600332880 /* Shared */ = { + isa = PBXGroup; + children = ( + 538DCB781DCA5E6C00332880 /* NSLayoutConstraint.swift */, + ); + path = Shared; + sourceTree = ""; + }; + 538DCB771DCA5E3200332880 /* Shared */ = { + isa = PBXGroup; + children = ( + 538DCB7C1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift */, + ); + path = Shared; + sourceTree = ""; + }; 57A4D2431BA13F9700F7D4B1 /* tvOS */ = { isa = PBXGroup; children = ( @@ -1328,334 +924,184 @@ path = tvOS; sourceTree = ""; }; - A97451321B3A935E00F48E55 /* watchOS */ = { + 9A16753E1F80C35100B63650 /* ReactiveMapKitTests */ = { isa = PBXGroup; children = ( - A97451331B3A935E00F48E55 /* watchOS-Application.xcconfig */, - A97451341B3A935E00F48E55 /* watchOS-Base.xcconfig */, - A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */, - A97451361B3A935E00F48E55 /* watchOS-StaticLibrary.xcconfig */, + 9A1675411F80C35100B63650 /* Info.plist */, + 53A6BED51DD4BD2C0016C058 /* MKMapViewSpec.swift */, ); - path = watchOS; + path = ReactiveMapKitTests; sourceTree = ""; }; - D037642919EDA3B600A782A9 /* Objective-C */ = { + 9A1D05E91D93E9F100ACF44C /* AppKit */ = { isa = PBXGroup; children = ( - D037666519EDA57100A782A9 /* extobjc */, - 314304151ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.h */, - 314304161ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.m */, - D037642A19EDA41200A782A9 /* NSArray+RACSequenceAdditions.h */, - D037642B19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m */, - D037642C19EDA41200A782A9 /* NSControl+RACCommandSupport.h */, - D037642D19EDA41200A782A9 /* NSControl+RACCommandSupport.m */, - D037642E19EDA41200A782A9 /* NSControl+RACTextSignalSupport.h */, - D037642F19EDA41200A782A9 /* NSControl+RACTextSignalSupport.m */, - D037643019EDA41200A782A9 /* NSData+RACSupport.h */, - D037643119EDA41200A782A9 /* NSData+RACSupport.m */, - D037643219EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h */, - D037643319EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m */, - D037643419EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h */, - D037643519EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m */, - D037643619EDA41200A782A9 /* NSFileHandle+RACSupport.h */, - D037643719EDA41200A782A9 /* NSFileHandle+RACSupport.m */, - D037643819EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h */, - D037643919EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m */, - D037643A19EDA41200A782A9 /* NSInvocation+RACTypeParsing.h */, - D037643B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m */, - D037643C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h */, - D037643D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m */, - D037643E19EDA41200A782A9 /* NSObject+RACAppKitBindings.h */, - D037643F19EDA41200A782A9 /* NSObject+RACAppKitBindings.m */, - D037644019EDA41200A782A9 /* NSObject+RACDeallocating.h */, - D037644119EDA41200A782A9 /* NSObject+RACDeallocating.m */, - D037644219EDA41200A782A9 /* NSObject+RACDescription.h */, - D037644319EDA41200A782A9 /* NSObject+RACDescription.m */, - D037644419EDA41200A782A9 /* NSObject+RACKVOWrapper.h */, - D037644519EDA41200A782A9 /* NSObject+RACKVOWrapper.m */, - D037644619EDA41200A782A9 /* NSObject+RACLifting.h */, - D037644719EDA41200A782A9 /* NSObject+RACLifting.m */, - D037644819EDA41200A782A9 /* NSObject+RACPropertySubscribing.h */, - D037644919EDA41200A782A9 /* NSObject+RACPropertySubscribing.m */, - D037644A19EDA41200A782A9 /* NSObject+RACSelectorSignal.h */, - D037644B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m */, - D037644C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h */, - D037644D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m */, - D037644E19EDA41200A782A9 /* NSSet+RACSequenceAdditions.h */, - D037644F19EDA41200A782A9 /* NSSet+RACSequenceAdditions.m */, - D037645019EDA41200A782A9 /* NSString+RACKeyPathUtilities.h */, - D037645119EDA41200A782A9 /* NSString+RACKeyPathUtilities.m */, - D037645219EDA41200A782A9 /* NSString+RACSequenceAdditions.h */, - D037645319EDA41200A782A9 /* NSString+RACSequenceAdditions.m */, - D037645419EDA41200A782A9 /* NSString+RACSupport.h */, - D037645519EDA41200A782A9 /* NSString+RACSupport.m */, - D037645619EDA41200A782A9 /* NSText+RACSignalSupport.h */, - D037645719EDA41200A782A9 /* NSText+RACSignalSupport.m */, - D037645819EDA41200A782A9 /* NSURLConnection+RACSupport.h */, - D037645919EDA41200A782A9 /* NSURLConnection+RACSupport.m */, - D037645A19EDA41200A782A9 /* NSUserDefaults+RACSupport.h */, - D037645B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m */, - D037645C19EDA41200A782A9 /* RACArraySequence.h */, - D037645D19EDA41200A782A9 /* RACArraySequence.m */, - D037646019EDA41200A782A9 /* RACBehaviorSubject.h */, - D037646119EDA41200A782A9 /* RACBehaviorSubject.m */, - D037646219EDA41200A782A9 /* RACBlockTrampoline.h */, - D037646319EDA41200A782A9 /* RACBlockTrampoline.m */, - D037646419EDA41200A782A9 /* RACChannel.h */, - D037646519EDA41200A782A9 /* RACChannel.m */, - D037646619EDA41200A782A9 /* RACCommand.h */, - D037646719EDA41200A782A9 /* RACCommand.m */, - D037646819EDA41200A782A9 /* RACCompoundDisposable.h */, - D037646919EDA41200A782A9 /* RACCompoundDisposable.m */, - D037646A19EDA41200A782A9 /* RACCompoundDisposableProvider.d */, - D037646B19EDA41200A782A9 /* RACDelegateProxy.h */, - D037646C19EDA41200A782A9 /* RACDelegateProxy.m */, - D037646D19EDA41200A782A9 /* RACDisposable.h */, - D037646E19EDA41200A782A9 /* RACDisposable.m */, - D037646F19EDA41200A782A9 /* RACDynamicSequence.h */, - D037647019EDA41200A782A9 /* RACDynamicSequence.m */, - D037647119EDA41200A782A9 /* RACDynamicSignal.h */, - D037647219EDA41200A782A9 /* RACDynamicSignal.m */, - D037647319EDA41200A782A9 /* RACEagerSequence.h */, - D037647419EDA41200A782A9 /* RACEagerSequence.m */, - D037647519EDA41200A782A9 /* RACEmptySequence.h */, - D037647619EDA41200A782A9 /* RACEmptySequence.m */, - D037647719EDA41200A782A9 /* RACEmptySignal.h */, - D037647819EDA41200A782A9 /* RACEmptySignal.m */, - D037647919EDA41200A782A9 /* RACErrorSignal.h */, - D037647A19EDA41200A782A9 /* RACErrorSignal.m */, - D037647B19EDA41200A782A9 /* RACEvent.h */, - D037647C19EDA41200A782A9 /* RACEvent.m */, - D037647D19EDA41200A782A9 /* RACGroupedSignal.h */, - D037647E19EDA41200A782A9 /* RACGroupedSignal.m */, - D037647F19EDA41200A782A9 /* RACImmediateScheduler.h */, - D037648019EDA41200A782A9 /* RACImmediateScheduler.m */, - D037648119EDA41200A782A9 /* RACIndexSetSequence.h */, - D037648219EDA41200A782A9 /* RACIndexSetSequence.m */, - D037648319EDA41200A782A9 /* RACKVOChannel.h */, - D037648419EDA41200A782A9 /* RACKVOChannel.m */, - 7A70657D1A3F88B8001E8354 /* RACKVOProxy.h */, - 7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */, - D037648519EDA41200A782A9 /* RACKVOTrampoline.h */, - D037648619EDA41200A782A9 /* RACKVOTrampoline.m */, - D037648719EDA41200A782A9 /* RACMulticastConnection.h */, - D037648819EDA41200A782A9 /* RACMulticastConnection.m */, - D037648919EDA41200A782A9 /* RACMulticastConnection+Private.h */, - D037648C19EDA41200A782A9 /* RACPassthroughSubscriber.h */, - D037648D19EDA41200A782A9 /* RACPassthroughSubscriber.m */, - D037648E19EDA41200A782A9 /* RACQueueScheduler.h */, - D037648F19EDA41200A782A9 /* RACQueueScheduler.m */, - D037649019EDA41200A782A9 /* RACQueueScheduler+Subclass.h */, - D037649119EDA41200A782A9 /* RACReplaySubject.h */, - D037649219EDA41200A782A9 /* RACReplaySubject.m */, - D037649319EDA41200A782A9 /* RACReturnSignal.h */, - D037649419EDA41200A782A9 /* RACReturnSignal.m */, - D037649519EDA41200A782A9 /* RACScheduler.h */, - D037649619EDA41200A782A9 /* RACScheduler.m */, - D037649719EDA41200A782A9 /* RACScheduler+Private.h */, - D037649819EDA41200A782A9 /* RACScheduler+Subclass.h */, - D037649919EDA41200A782A9 /* RACScopedDisposable.h */, - D037649A19EDA41200A782A9 /* RACScopedDisposable.m */, - D037649B19EDA41200A782A9 /* RACSequence.h */, - D037649C19EDA41200A782A9 /* RACSequence.m */, - D037649D19EDA41200A782A9 /* RACSerialDisposable.h */, - D037649E19EDA41200A782A9 /* RACSerialDisposable.m */, - D037649F19EDA41200A782A9 /* RACSignal.h */, - D03764A019EDA41200A782A9 /* RACSignal.m */, - D03764A119EDA41200A782A9 /* RACSignal+Operations.h */, - D03764A219EDA41200A782A9 /* RACSignal+Operations.m */, - D03764A319EDA41200A782A9 /* RACSignalProvider.d */, - D03764A419EDA41200A782A9 /* RACSignalSequence.h */, - D03764A519EDA41200A782A9 /* RACSignalSequence.m */, - D03764A619EDA41200A782A9 /* RACStream.h */, - D03764A719EDA41200A782A9 /* RACStream.m */, - D03764A819EDA41200A782A9 /* RACStream+Private.h */, - D03764A919EDA41200A782A9 /* RACStringSequence.h */, - D03764AA19EDA41200A782A9 /* RACStringSequence.m */, - D03764AB19EDA41200A782A9 /* RACSubject.h */, - D03764AC19EDA41200A782A9 /* RACSubject.m */, - D03764AD19EDA41200A782A9 /* RACSubscriber.h */, - D03764AE19EDA41200A782A9 /* RACSubscriber.m */, - D03764AF19EDA41200A782A9 /* RACSubscriber+Private.h */, - D03764B019EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h */, - D03764B119EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m */, - D03764B219EDA41200A782A9 /* RACSubscriptionScheduler.h */, - D03764B319EDA41200A782A9 /* RACSubscriptionScheduler.m */, - D03764B419EDA41200A782A9 /* RACTargetQueueScheduler.h */, - D03764B519EDA41200A782A9 /* RACTargetQueueScheduler.m */, - D03764B619EDA41200A782A9 /* RACTestScheduler.h */, - D03764B719EDA41200A782A9 /* RACTestScheduler.m */, - D03764B819EDA41200A782A9 /* RACTuple.h */, - D03764B919EDA41200A782A9 /* RACTuple.m */, - D03764BA19EDA41200A782A9 /* RACTupleSequence.h */, - D03764BB19EDA41200A782A9 /* RACTupleSequence.m */, - D03764BC19EDA41200A782A9 /* RACUnarySequence.h */, - D03764BD19EDA41200A782A9 /* RACUnarySequence.m */, - D03764BE19EDA41200A782A9 /* RACUnit.h */, - D03764BF19EDA41200A782A9 /* RACUnit.m */, - D03764C019EDA41200A782A9 /* RACValueTransformer.h */, - D03764C119EDA41200A782A9 /* RACValueTransformer.m */, - D03764C219EDA41200A782A9 /* UIActionSheet+RACSignalSupport.h */, - D03764C319EDA41200A782A9 /* UIActionSheet+RACSignalSupport.m */, - D03764C419EDA41200A782A9 /* UIAlertView+RACSignalSupport.h */, - D03764C519EDA41200A782A9 /* UIAlertView+RACSignalSupport.m */, - D03764C619EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.h */, - D03764C719EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m */, - D03764C819EDA41200A782A9 /* UIButton+RACCommandSupport.h */, - D03764C919EDA41200A782A9 /* UIButton+RACCommandSupport.m */, - D03764CA19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.h */, - D03764CB19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.m */, - D03764CC19EDA41200A782A9 /* UIControl+RACSignalSupport.h */, - D03764CD19EDA41200A782A9 /* UIControl+RACSignalSupport.m */, - D03764CE19EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.h */, - D03764CF19EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.m */, - D03764D019EDA41200A782A9 /* UIDatePicker+RACSignalSupport.h */, - D03764D119EDA41200A782A9 /* UIDatePicker+RACSignalSupport.m */, - D03764D219EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.h */, - D03764D319EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.m */, - D03764D419EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.h */, - D03764D519EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.m */, - D03764D619EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.h */, - D03764D719EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.m */, - D03764D819EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.h */, - D03764D919EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.m */, - D03764DA19EDA41200A782A9 /* UISlider+RACSignalSupport.h */, - D03764DB19EDA41200A782A9 /* UISlider+RACSignalSupport.m */, - D03764DC19EDA41200A782A9 /* UIStepper+RACSignalSupport.h */, - D03764DD19EDA41200A782A9 /* UIStepper+RACSignalSupport.m */, - D03764DE19EDA41200A782A9 /* UISwitch+RACSignalSupport.h */, - D03764DF19EDA41200A782A9 /* UISwitch+RACSignalSupport.m */, - D03764E019EDA41200A782A9 /* UITableViewCell+RACSignalSupport.h */, - D03764E119EDA41200A782A9 /* UITableViewCell+RACSignalSupport.m */, - D03764E219EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.h */, - D03764E319EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.m */, - D03764E419EDA41200A782A9 /* UITextField+RACSignalSupport.h */, - D03764E519EDA41200A782A9 /* UITextField+RACSignalSupport.m */, - D03764E619EDA41200A782A9 /* UITextView+RACSignalSupport.h */, - D03764E719EDA41200A782A9 /* UITextView+RACSignalSupport.m */, - ); - path = "Objective-C"; + 9AED64C41E496A3700321004 /* ActionProxy.swift */, + 9A6AAA251DB8F5280013AAEA /* AppKitReusableComponents.swift */, + 006518751E26865800C3139A /* NSButton.swift */, + 4ABEFE2F1DCFD0530066A8C2 /* NSCollectionView.swift */, + 9ADE4A881DA6D206005C2AC8 /* NSControl.swift */, + 8392D8FC1DB93E5E00504ED4 /* NSImageView.swift */, + D9558AB71DFF805A003254E1 /* NSPopUpButton.swift */, + 834DE1111E4120340099F4E5 /* NSSegmentedControl.swift */, + 834DE1131E4122910099F4E5 /* NSSlider.swift */, + 4ABEFE2A1DCFD0030066A8C2 /* NSTableView.swift */, + 9ADE4A951DA6F018005C2AC8 /* NSTextField.swift */, + 7A8BA0F91FCC86FC003241C7 /* NSTextView.swift */, + 537EC08021A950CA00D6EE18 /* NSView.swift */, + ); + path = AppKit; sourceTree = ""; }; - D037666519EDA57100A782A9 /* extobjc */ = { + 9A1D05EB1D93E9F100ACF44C /* UIKit */ = { isa = PBXGroup; children = ( - D037666619EDA57100A782A9 /* EXTKeyPathCoding.h */, - D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */, - D037666819EDA57100A782A9 /* EXTRuntimeExtensions.m */, - D037666919EDA57100A782A9 /* EXTScope.h */, - D037666A19EDA57100A782A9 /* metamacros.h */, - ); - path = extobjc; + CD91E3D41DDAC67700FA70D0 /* iOS */, + 9A1D05EC1D93E9F100ACF44C /* UIActivityIndicatorView.swift */, + 4EE637352090F92600ECD02A /* UIApplication.swift */, + 9A1D05ED1D93E9F100ACF44C /* UIBarButtonItem.swift */, + 9A1D05EE1D93E9F100ACF44C /* UIBarItem.swift */, + 9A1D05EF1D93E9F100ACF44C /* UIButton.swift */, + 4ABEFE231DCFCF5C0066A8C2 /* UICollectionView.swift */, + 9A1D05F11D93E9F100ACF44C /* UIControl.swift */, + 419139431DB910570043C9D1 /* UIGestureRecognizer.swift */, + 9A1D05F31D93E9F100ACF44C /* UIImageView.swift */, + 9A6AAA221DB8F51C0013AAEA /* UIKitReusableComponents.swift */, + 9A1D05F41D93E9F100ACF44C /* UILabel.swift */, + A9EB3D1C1E94ECAC002A9BCC /* UINavigationItem.swift */, + 9A1D05F51D93E9F100ACF44C /* UIProgressView.swift */, + A9D8BA70207CD7090031733D /* UIResponder.swift */, + BF4335641E02AC7600AC88DD /* UIScrollView.swift */, + 9A1D05F61D93E9F100ACF44C /* UISegmentedControl.swift */, + A9EB3D241E94F335002A9BCC /* UITabBarItem.swift */, + 4ABEFDE11DCFC8560066A8C2 /* UITableView.swift */, + 9A1D05FA1D93E9F100ACF44C /* UITextField.swift */, + 9A1D05FB1D93E9F100ACF44C /* UITextView.swift */, + 9A1D05FC1D93E9F100ACF44C /* UIView.swift */, + 4EE6372D2090EEFA00ECD02A /* UIViewController.swift */, + ); + path = UIKit; sourceTree = ""; }; - D037667519EDA5D900A782A9 /* Objective-C */ = { + 9A1D06231D93EA7E00ACF44C /* UIKit */ = { isa = PBXGroup; children = ( - D037667619EDA60000A782A9 /* NSControllerRACSupportSpec.m */, - D037667819EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m */, - D037667919EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m */, - D037667A19EDA60000A782A9 /* NSObjectRACAppKitBindingsSpec.m */, - D037667B19EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m */, - D037667C19EDA60000A782A9 /* NSObjectRACLiftingSpec.m */, - D037667D19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.h */, - D037667E19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m */, - D037667F19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m */, - D037668019EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m */, - D037668119EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m */, - D037668319EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m */, - D037668419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m */, - D037668619EDA60000A782A9 /* RACBlockTrampolineSpec.m */, - D037668719EDA60000A782A9 /* RACChannelExamples.h */, - D037668819EDA60000A782A9 /* RACChannelExamples.m */, - D037668919EDA60000A782A9 /* RACChannelSpec.m */, - D037668A19EDA60000A782A9 /* RACCommandSpec.m */, - D037668B19EDA60000A782A9 /* RACCompoundDisposableSpec.m */, - D037668C19EDA60000A782A9 /* RACControlCommandExamples.h */, - D037668D19EDA60000A782A9 /* RACControlCommandExamples.m */, - D037668E19EDA60000A782A9 /* RACDelegateProxySpec.m */, - D037668F19EDA60000A782A9 /* RACDisposableSpec.m */, - D037669019EDA60000A782A9 /* RACEventSpec.m */, - D037669119EDA60000A782A9 /* RACKVOChannelSpec.m */, - 7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */, - D037669219EDA60000A782A9 /* RACKVOWrapperSpec.m */, - D037669319EDA60000A782A9 /* RACMulticastConnectionSpec.m */, - D037669419EDA60000A782A9 /* RACPropertySignalExamples.h */, - D037669519EDA60000A782A9 /* RACPropertySignalExamples.m */, - D037669619EDA60000A782A9 /* RACSchedulerSpec.m */, - D037669719EDA60000A782A9 /* RACSequenceAdditionsSpec.m */, - D037669819EDA60000A782A9 /* RACSequenceExamples.h */, - D037669919EDA60000A782A9 /* RACSequenceExamples.m */, - D037669A19EDA60000A782A9 /* RACSequenceSpec.m */, - D037669B19EDA60000A782A9 /* RACSerialDisposableSpec.m */, - D037669C19EDA60000A782A9 /* RACSignalSpec.m */, - D037669F19EDA60000A782A9 /* RACStreamExamples.h */, - D03766A019EDA60000A782A9 /* RACStreamExamples.m */, - D03766A119EDA60000A782A9 /* RACSubclassObject.h */, - D03766A219EDA60000A782A9 /* RACSubclassObject.m */, - D03766A319EDA60000A782A9 /* RACSubjectSpec.m */, - D03766A419EDA60000A782A9 /* RACSubscriberExamples.h */, - D03766A519EDA60000A782A9 /* RACSubscriberExamples.m */, - D03766A619EDA60000A782A9 /* RACSubscriberSpec.m */, - D03766A719EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m */, - D03766A819EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m */, - D03766B019EDA60000A782A9 /* RACTupleSpec.m */, - D03766B219EDA60000A782A9 /* UIActionSheetRACSupportSpec.m */, - D03766B319EDA60000A782A9 /* UIAlertViewRACSupportSpec.m */, - D03766B419EDA60000A782A9 /* UIBarButtonItemRACSupportSpec.m */, - D03766B519EDA60000A782A9 /* UIButtonRACSupportSpec.m */, - D03766B719EDA60000A782A9 /* UIImagePickerControllerRACSupportSpec.m */, - D0C3131719EF2D9700984962 /* RACTestExampleScheduler.h */, - D0C3131819EF2D9700984962 /* RACTestExampleScheduler.m */, - D0C3131919EF2D9700984962 /* RACTestObject.h */, - D0C3131A19EF2D9700984962 /* RACTestObject.m */, - D0C3131B19EF2D9700984962 /* RACTestSchedulerSpec.m */, - D0C3131C19EF2D9700984962 /* RACTestUIButton.h */, - D0C3131D19EF2D9700984962 /* RACTestUIButton.m */, - ); - path = "Objective-C"; + 9A6AAA291DB8F7F10013AAEA /* UIKitReusableComponentsSpec.swift */, + 9A1D06241D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift */, + 9A1D06251D93EA7E00ACF44C /* UIBarButtonItemSpec.swift */, + 9A1D06261D93EA7E00ACF44C /* UIButtonSpec.swift */, + 4ABEFE271DCFCFA90066A8C2 /* UICollectionViewSpec.swift */, + 9A1D06281D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift */, + 9A1D06291D93EA7E00ACF44C /* UIControlSpec.swift */, + 9A1D062A1D93EA7E00ACF44C /* UIDatePickerSpec.swift */, + 4191394B1DBA002C0043C9D1 /* UIGestureRecognizerSpec.swift */, + 9A1D062B1D93EA7E00ACF44C /* UIImageViewSpec.swift */, + 9A1D062C1D93EA7E00ACF44C /* UILabelSpec.swift */, + A9EB3D1E1E94ED84002A9BCC /* UINavigationItemSpec.swift */, + BFBD68431E48DA21003CB580 /* UIPickerViewSpec.swift */, + 9A1D062D1D93EA7E00ACF44C /* UIProgressViewSpec.swift */, + BF4335661E02EEDE00AC88DD /* UIScrollViewSpec.swift */, + BFCF77601DFAD9120058006E /* UISearchBarSpec.swift */, + 9A1D062E1D93EA7E00ACF44C /* UISegmentedControlSpec.swift */, + 53AC46CE1DD6FC0000C799E1 /* UISliderSpec.swift */, + 531866F91DD7925600D1285F /* UIStepperSpec.swift */, + 9A1D062F1D93EA7E00ACF44C /* UISwitchSpec.swift */, + 4ABEFDE31DCFCCD70066A8C2 /* UITableViewSpec.swift */, + A9EB3D261E94F3C7002A9BCC /* UITabBarItemSpec.swift */, + 9A1D06321D93EA7E00ACF44C /* UITextFieldSpec.swift */, + 9A1D06331D93EA7E00ACF44C /* UITextViewSpec.swift */, + 9A1D06351D93EA7E00ACF44C /* UIViewSpec.swift */, + 4EE637302090EFD300ECD02A /* UIViewControllerSpec.swift */, + 9AAD49891DED2F380068EC9B /* UIKeyboardSpec.swift */, + 3BCAAC7B1DEE1A2300B30335 /* UIRefreshControlSpec.swift */, + A9D8BA73207CD8430031733D /* UIResponderSpec.swift */, + ); + path = UIKit; sourceTree = ""; }; - D03B4A3919F4C25F009E02AC /* Signals */ = { + 9A73E026216D3F670069AD76 /* Frameworks */ = { isa = PBXGroup; children = ( - D08C54AF1A69A2AC00AD8286 /* Action.swift */, - C7142DBB1CDEA167009F402D /* CocoaAction.swift */, - CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */, - D85C65291C0D84C7005A77AD /* Flatten.swift */, - 4A0E10FE1D2A92720065D310 /* Lifetime.swift */, - D08C54B01A69A2AC00AD8286 /* Property.swift */, - D08C54B11A69A2AC00AD8286 /* Signal.swift */, - D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */, - 9A694EF21D5CE02E009B05BD /* UnidirectionalBinding.swift */, - ); - name = Signals; + 9A3C54F021726BD200E98207 /* Nimble.framework */, + 9A3C54F221726BD200E98207 /* Quick.framework */, + 9A3C54EA21726A1200E98207 /* Nimble.framework */, + 9A3C54EC21726A1200E98207 /* Quick.framework */, + 9A73E04B216D408D0069AD76 /* Nimble.framework */, + 9A73E04D216D408D0069AD76 /* Quick.framework */, + 9A73E035216D3FC50069AD76 /* ReactiveSwift.framework */, + ); + name = Frameworks; sourceTree = ""; }; - D03B4A3A19F4C26D009E02AC /* Internal Utilities */ = { + 9AC03A581F7CC3BF00EC33C1 /* ReactiveMapKit */ = { isa = PBXGroup; children = ( - D00004081A46864E000E7D41 /* TupleExtensions.swift */, + 9AC03A5A1F7CC3BF00EC33C1 /* Info.plist */, + A91244E720389AEA0001BBCB /* MKLocalSearchRequest.swift */, + 53A6BED11DD4BCA90016C058 /* MKMapView.swift */, ); - name = "Internal Utilities"; + path = ReactiveMapKit; sourceTree = ""; }; - D03B4A3B19F4C281009E02AC /* Extensions */ = { + 9ADE4A8C1DA6D94C005C2AC8 /* AppKit */ = { isa = PBXGroup; children = ( - D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */, - 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */, + 9A0726F21E912B610081F3F7 /* ActionProxySpec.swift */, + 004FD0061E26CDB300A03A82 /* NSButtonSpec.swift */, + 4ABEFE311DCFD05F0066A8C2 /* NSCollectionViewSpec.swift */, + 9ADE4A8D1DA6D965005C2AC8 /* NSControlSpec.swift */, + 834DE1001E4109750099F4E5 /* NSImageViewSpec.swift */, + D9558AB51DFF7B90003254E1 /* NSPopUpButtonSpec.swift */, + 4ABEFE2C1DCFD0180066A8C2 /* NSTableViewSpec.swift */, + 537EC08221A9557400D6EE18 /* NSViewSpec.swift */, + 9A6AAA271DB8F7EB0013AAEA /* AppKitReusableComponentsSpec.swift */, + 9AADB6F31F84AECB00EFFD19 /* Swift4TestInteroperability.swift */, + ); + path = AppKit; + sourceTree = ""; + }; + A97451321B3A935E00F48E55 /* watchOS */ = { + isa = PBXGroup; + children = ( + A97451331B3A935E00F48E55 /* watchOS-Application.xcconfig */, + A97451341B3A935E00F48E55 /* watchOS-Base.xcconfig */, + A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */, + A97451361B3A935E00F48E55 /* watchOS-StaticLibrary.xcconfig */, + ); + path = watchOS; + sourceTree = ""; + }; + CD91E3D41DDAC67700FA70D0 /* iOS */ = { + isa = PBXGroup; + children = ( + 9A1D05F21D93E9F100ACF44C /* UIDatePicker.swift */, + A9EB3D7D1E955602002A9BCC /* UIFeedbackGenerator.swift */, + 84ADBDA826660EA800ACE342 /* UIImpactFeedbackGenerator.swift */, + 84ADBDAE26660EE800ACE342 /* UINotificationFeedbackGenerator.swift */, + 84ADBDAF26660EE800ACE342 /* UISelectionFeedbackGenerator.swift */, + 9AAD49871DED2C350068EC9B /* UIKeyboard.swift */, + BFE145881E43991A00208736 /* UIPickerView.swift */, + 3BCAAC791DEE19BC00B30335 /* UIRefreshControl.swift */, + BFCF775E1DFAD8A50058006E /* UISearchBar.swift */, + 53AC46CB1DD6F97400C799E1 /* UISlider.swift */, + 531866F71DD7920400D1285F /* UIStepper.swift */, + 9A1D05F71D93E9F100ACF44C /* UISwitch.swift */, ); - name = Extensions; + path = iOS; sourceTree = ""; }; D04725E019E49ED7006002AA = { isa = PBXGroup; children = ( D04725EC19E49ED7006002AA /* ReactiveCocoa */, + 056D088022C00A6A00291F50 /* ReactiveCocoaObjC */, D04725F919E49ED7006002AA /* ReactiveCocoaTests */, + 056D088222C00B2700291F50 /* ReactiveCocoaObjCTestSupport */, D047262519E49FE8006002AA /* Configuration */, + 9AC03A581F7CC3BF00EC33C1 /* ReactiveMapKit */, + 9A16753E1F80C35100B63650 /* ReactiveMapKitTests */, D04725EB19E49ED7006002AA /* Products */, + 9A73E026216D3F670069AD76 /* Frameworks */, ); sourceTree = ""; usesTabs = 1; @@ -1670,6 +1116,13 @@ A9B315541B3940610001CB9C /* ReactiveCocoa.framework */, 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */, 7DFBED031CDB8C9500EE435B /* ReactiveCocoaTests.xctest */, + 9AC03A571F7CC3BF00EC33C1 /* ReactiveMapKit.framework */, + 9A16753D1F80C35100B63650 /* ReactiveMapKitTests.xctest */, + 9A73DFBB216D3C550069AD76 /* ReactiveMapKit.framework */, + 9A73DFCC216D3C570069AD76 /* ReactiveMapKit.framework */, + 9A73DFF8216D3CEB0069AD76 /* ReactiveMapKitTests.xctest */, + 9A73E013216D3CEE0069AD76 /* ReactiveMapKitTests.xctest */, + 57C885532A47756D00FC133C /* ReactiveCocoa.framework */, ); name = Products; sourceTree = ""; @@ -1677,10 +1130,31 @@ D04725EC19E49ED7006002AA /* ReactiveCocoa */ = { isa = PBXGroup; children = ( - D04725EF19E49ED7006002AA /* ReactiveCocoa.h */, - D0C312B919EF2A3000984962 /* Swift */, - D037642919EDA3B600A782A9 /* Objective-C */, + F44078392371BE3D00F103E7 /* include */, + 9A1D05E91D93E9F100ACF44C /* AppKit */, + 538DCB761DCA5E1600332880 /* Shared */, D04725ED19E49ED7006002AA /* Supporting Files */, + 9A1D05EB1D93E9F100ACF44C /* UIKit */, + E3AA54BF214291590077B206 /* WatchKit */, + 4A0E10FE1D2A92720065D310 /* AnyObject+Lifetime.swift */, + 9ADE4A7B1DA44A9E005C2AC8 /* CocoaAction.swift */, + 9A2E425D1DAA6737006D909F /* CocoaTarget.swift */, + 9A7488471E3B8ACE00CD0317 /* DelegateProxy.swift */, + 9AB15C791E26CD9A00997378 /* Deprecations+Removals.swift */, + CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */, + 9A1D05DF1D93E99100ACF44C /* NSObject+Association.swift */, + 9AF0EA741D9A7FF700F27DDF /* NSObject+BindingTarget.swift */, + 9A1D065A1D93EC6E00ACF44C /* NSObject+Intercepting.swift */, + 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */, + 9AA0BD8E1DDE29F800531FCF /* NSObject+ObjCRuntime.swift */, + 9ADE4A901DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift */, + 9AA0BD891DDE153A00531FCF /* ObjC+Constants.swift */, + 9AE7C2A31DDD7F5100F7534C /* ObjC+Messages.swift */, + 9AA0BD771DDE03DE00531FCF /* ObjC+Runtime.swift */, + 9AA0BD801DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift */, + 9A54A21A1DE00D09001739B3 /* ObjC+Selector.swift */, + 9A90374E1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift */, + 9ADFE5A41DC0001C001E11F7 /* Synchronizing.swift */, ); path = ReactiveCocoa; sourceTree = ""; @@ -1688,7 +1162,6 @@ D04725ED19E49ED7006002AA /* Supporting Files */ = { isa = PBXGroup; children = ( - CDC42E2E1AE7AB8B00965373 /* Result.framework */, D04725EE19E49ED7006002AA /* Info.plist */, ); name = "Supporting Files"; @@ -1697,9 +1170,26 @@ D04725F919E49ED7006002AA /* ReactiveCocoaTests */ = { isa = PBXGroup; children = ( - D0C312ED19EF2A6F00984962 /* Swift */, - D037667519EDA5D900A782A9 /* Objective-C */, + 9ADE4A8C1DA6D94C005C2AC8 /* AppKit */, + 538DCB771DCA5E3200332880 /* Shared */, D04725FA19E49ED7006002AA /* Supporting Files */, + 9A1D06231D93EA7E00ACF44C /* UIKit */, + 9AD841D6204C29B90040F0C0 /* ReactiveCocoaTests-Bridging-Header.h */, + 9A9DFEE81DA7EFB60039EE1B /* AssociationSpec.swift */, + 9A7990CD1F1085D8001493A3 /* BindingTargetSpec.swift */, + CD8401821CEE8ED7009F0ABF /* CocoaActionSpec.swift */, + 9A892D8E1E8D19BE00EA35F3 /* DelegateProxySpec.swift */, + 3B30EE8B1E7BE529007CC8EF /* DeprecationsSpec.swift */, + D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.swift */, + 9A54A2101DDF5B4D001739B3 /* InterceptingPerformanceTests.swift */, + 9A6AAA0D1DB6A4CF0013AAEA /* InterceptingSpec.swift */, + 9A1E72B91D4DE96500CC20C3 /* KeyValueObservingSpec.swift */, + 9AA6A1E31F11F9A800CA2257 /* KeyValueObservingSpec+Swift4.swift */, + 9AFCBFE21EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift */, + 9ADFE5A01DBFFBCF001E11F7 /* LifetimeSpec.swift */, + CD42C69A1E951F6900AA9504 /* ReactiveCocoaTestsConfiguration.swift */, + 9A24A8431DE1429600987AF9 /* SwizzlingSpec.swift */, + B696FB801A7640C00075236D /* TestError.swift */, ); path = ReactiveCocoaTests; sourceTree = ""; @@ -1712,6 +1202,7 @@ D03766B119EDA60000A782A9 /* test-data.json */, BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */, D04725FB19E49ED7006002AA /* Info.plist */, + 5B76DE3F2498EDDC00E8B4F3 /* QueueScheduler+Factory.swift */, ); name = "Supporting Files"; sourceTree = ""; @@ -1721,7 +1212,7 @@ children = ( D047262619E49FE8006002AA /* Base */, D047263119E49FE8006002AA /* iOS */, - D047263619E49FE8006002AA /* Mac OS X */, + D047263619E49FE8006002AA /* macOS */, A97451321B3A935E00F48E55 /* watchOS */, 57A4D2431BA13F9700F7D4B1 /* tvOS */, D047263C19E49FE8006002AA /* README.md */, @@ -1772,61 +1263,64 @@ path = iOS; sourceTree = ""; }; - D047263619E49FE8006002AA /* Mac OS X */ = { + D047263619E49FE8006002AA /* macOS */ = { + isa = PBXGroup; + children = ( + D047263719E49FE8006002AA /* macOS-Application.xcconfig */, + D047263819E49FE8006002AA /* macOS-Base.xcconfig */, + D047263919E49FE8006002AA /* macOS-DynamicLibrary.xcconfig */, + D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */, + D047263B19E49FE8006002AA /* macOS-StaticLibrary.xcconfig */, + E6124BA8267DF3C0005A3490 /* macOS-XCTest.xcconfig */, + ); + path = macOS; + sourceTree = ""; + }; + E3AA54BF214291590077B206 /* WatchKit */ = { + isa = PBXGroup; + children = ( + E3AA54C2214292540077B206 /* WKInterfaceButton.swift */, + E92142A12284B64E007E412D /* WKInterfaceController.swift */, + E921428D2284A44F007E412D /* WKInterfaceDate.swift */, + E92142972284AEF0007E412D /* WKInterfaceGroup.swift */, + E921429B2284B0A8007E412D /* WKInterfaceImage.swift */, + E92142AA228553C5007E412D /* WKInterfaceInlineMovie.swift */, + E3AA54C02142918B0077B206 /* WKInterfaceLabel.swift */, + E92142A8228552CC007E412D /* WKInterfaceMovie.swift */, + E921428A2284A12B007E412D /* WKInterfaceObject.swift */, + E921429F2284B4FF007E412D /* WKInterfacePicker.swift */, + E92142992284AF99007E412D /* WKInterfaceSeparator.swift */, + E92142932284ABDF007E412D /* WKInterfaceSlider.swift */, + E92142912284AA64007E412D /* WKInterfaceSwitch.swift */, + E921428F2284A4EF007E412D /* WKInterfaceTimer.swift */, + E921429D2284B1C4007E412D /* WKInterfaceVolumeControl.swift */, + ); + path = WatchKit; + sourceTree = ""; + }; + F44078372371B6E400F103E7 /* include */ = { isa = PBXGroup; children = ( - D047263719E49FE8006002AA /* Mac-Application.xcconfig */, - D047263819E49FE8006002AA /* Mac-Base.xcconfig */, - D047263919E49FE8006002AA /* Mac-DynamicLibrary.xcconfig */, - D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */, - D047263B19E49FE8006002AA /* Mac-StaticLibrary.xcconfig */, + 9A1D06751D9415FB00ACF44C /* ObjCRuntimeAliases.h */, + D04725EF19E49ED7006002AA /* ReactiveCocoa.h */, ); - path = "Mac OS X"; + path = include; sourceTree = ""; }; - D0C312B919EF2A3000984962 /* Swift */ = { + F44078392371BE3D00F103E7 /* include */ = { isa = PBXGroup; children = ( - D0C312BB19EF2A5800984962 /* Atomic.swift */, - D0C312BC19EF2A5800984962 /* Bag.swift */, - D0C312BE19EF2A5800984962 /* Disposable.swift */, - D08C54B51A69A3DB00AD8286 /* Event.swift */, - D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */, - EBCC7DBB1BBF010C00A2AE92 /* Observer.swift */, - D871D69E1B3B29A40070F16C /* Optional.swift */, - D0C312C819EF2A5800984962 /* Scheduler.swift */, - C79B647B1CD52E23003F2376 /* EventLogger.swift */, - D03B4A3919F4C25F009E02AC /* Signals */, - D03B4A3A19F4C26D009E02AC /* Internal Utilities */, - D03B4A3B19F4C281009E02AC /* Extensions */, - 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */, - ); - path = Swift; + F440783A2371BE3D00F103E7 /* module.modulemap */, + ); + path = include; sourceTree = ""; }; - D0C312ED19EF2A6F00984962 /* Swift */ = { + F440783B2371C00800F103E7 /* include */ = { isa = PBXGroup; children = ( - D021671C1A6CD50500987861 /* ActionSpec.swift */, - D0C312EE19EF2A7700984962 /* AtomicSpec.swift */, - D0C312EF19EF2A7700984962 /* BagSpec.swift */, - CD8401821CEE8ED7009F0ABF /* CocoaActionSpec.swift */, - D0C312F019EF2A7700984962 /* DisposableSpec.swift */, - CA6F284F1C52626B001879D2 /* FlattenSpec.swift */, - D8170FC01B100EBC004192AD /* FoundationExtensionsSpec.swift */, - 4A0E11031D2A95200065D310 /* LifetimeSpec.swift */, - D0A226101A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift */, - D0A2260D1A72F16D00D33B74 /* PropertySpec.swift */, - D0C312F219EF2A7700984962 /* SchedulerSpec.swift */, - 02D260291C1D6DAF003ACC61 /* SignalLifetimeSpec.swift */, - D8024DB11B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift */, - D0A2260A1A72E6C500D33B74 /* SignalProducerSpec.swift */, - D0A226071A72E0E900D33B74 /* SignalSpec.swift */, - B696FB801A7640C00075236D /* TestError.swift */, - C79B64731CD38B2B003F2376 /* TestLogger.swift */, - 9A1E72B91D4DE96500CC20C3 /* KeyValueObservingSpec.swift */, + 9AD841DA204C29B90040F0C0 /* MessageForwardingEntity.h */, ); - path = Swift; + path = include; sourceTree = ""; }; /* End PBXGroup section */ @@ -1837,64 +1331,37 @@ buildActionMask = 2147483647; files = ( 57A4D20A1BA13D7A00F7D4B1 /* ReactiveCocoa.h in Headers */, - 57A4D20B1BA13D7A00F7D4B1 /* EXTKeyPathCoding.h in Headers */, - A1046B7D1BFF5664004D8045 /* EXTRuntimeExtensions.h in Headers */, - 57A4D20C1BA13D7A00F7D4B1 /* EXTScope.h in Headers */, - 57A4D20D1BA13D7A00F7D4B1 /* metamacros.h in Headers */, - 57A4D20E1BA13D7A00F7D4B1 /* NSArray+RACSequenceAdditions.h in Headers */, - 57A4D20F1BA13D7A00F7D4B1 /* NSData+RACSupport.h in Headers */, - BEBDD6E51CDC292D009A75A9 /* RACDelegateProxy.h in Headers */, - 57A4D2101BA13D7A00F7D4B1 /* NSDictionary+RACSequenceAdditions.h in Headers */, - 57A4D2111BA13D7A00F7D4B1 /* NSEnumerator+RACSequenceAdditions.h in Headers */, - 57A4D2121BA13D7A00F7D4B1 /* NSFileHandle+RACSupport.h in Headers */, - 57A4D2131BA13D7A00F7D4B1 /* NSIndexSet+RACSequenceAdditions.h in Headers */, - 57A4D2141BA13D7A00F7D4B1 /* NSNotificationCenter+RACSupport.h in Headers */, - 57A4D2151BA13D7A00F7D4B1 /* NSObject+RACDeallocating.h in Headers */, - 57A4D2161BA13D7A00F7D4B1 /* NSObject+RACLifting.h in Headers */, - 57A4D2171BA13D7A00F7D4B1 /* NSObject+RACPropertySubscribing.h in Headers */, - 57DC89A01C5066D400E367B7 /* UIGestureRecognizer+RACSignalSupport.h in Headers */, - 57A4D2181BA13D7A00F7D4B1 /* NSObject+RACSelectorSignal.h in Headers */, - 57A4D2191BA13D7A00F7D4B1 /* NSOrderedSet+RACSequenceAdditions.h in Headers */, - 57A4D21A1BA13D7A00F7D4B1 /* NSSet+RACSequenceAdditions.h in Headers */, - 57A4D21B1BA13D7A00F7D4B1 /* NSString+RACSequenceAdditions.h in Headers */, - 57A4D21C1BA13D7A00F7D4B1 /* NSString+RACSupport.h in Headers */, - 57A4D21E1BA13D7A00F7D4B1 /* NSUserDefaults+RACSupport.h in Headers */, - 57A4D21F1BA13D7A00F7D4B1 /* RACBehaviorSubject.h in Headers */, - 57A4D2201BA13D7A00F7D4B1 /* RACChannel.h in Headers */, - 57A4D2211BA13D7A00F7D4B1 /* RACCommand.h in Headers */, - 57A4D2221BA13D7A00F7D4B1 /* RACCompoundDisposable.h in Headers */, - 57A4D2231BA13D7A00F7D4B1 /* RACDisposable.h in Headers */, - 57A4D2241BA13D7A00F7D4B1 /* RACEvent.h in Headers */, - 7DFBED6F1CDB926400EE435B /* UIBarButtonItem+RACCommandSupport.h in Headers */, - 57A4D2251BA13D7A00F7D4B1 /* RACGroupedSignal.h in Headers */, - 57A4D2261BA13D7A00F7D4B1 /* RACKVOChannel.h in Headers */, - 57A4D2271BA13D7A00F7D4B1 /* RACMulticastConnection.h in Headers */, - 57A4D2281BA13D7A00F7D4B1 /* RACQueueScheduler.h in Headers */, - 57DC89A51C50675700E367B7 /* UITextField+RACSignalSupport.h in Headers */, - 57A4D2291BA13D7A00F7D4B1 /* RACQueueScheduler+Subclass.h in Headers */, - 57A4D22A1BA13D7A00F7D4B1 /* RACReplaySubject.h in Headers */, - 57DC89A21C50673C00E367B7 /* UISegmentedControl+RACSignalSupport.h in Headers */, - 57A4D22B1BA13D7A00F7D4B1 /* RACScheduler.h in Headers */, - 57DC89A41C50674D00E367B7 /* UITableViewHeaderFooterView+RACSignalSupport.h in Headers */, - 57A4D22C1BA13D7A00F7D4B1 /* RACScheduler+Subclass.h in Headers */, - 57DC89A11C50672B00E367B7 /* UIControl+RACSignalSupport.h in Headers */, - 57A4D22D1BA13D7A00F7D4B1 /* RACScopedDisposable.h in Headers */, - 57DC89A31C50674300E367B7 /* UITableViewCell+RACSignalSupport.h in Headers */, - 57A4D22E1BA13D7A00F7D4B1 /* RACSequence.h in Headers */, - 57A4D22F1BA13D7A00F7D4B1 /* RACSerialDisposable.h in Headers */, - 57A4D2301BA13D7A00F7D4B1 /* RACSignal.h in Headers */, - 57DC89A81C50679E00E367B7 /* UICollectionReusableView+RACSignalSupport.h in Headers */, - 57DC89A71C50679700E367B7 /* UIButton+RACCommandSupport.h in Headers */, - 57A4D2311BA13D7A00F7D4B1 /* RACSignal+Operations.h in Headers */, - 57A4D2321BA13D7A00F7D4B1 /* RACStream.h in Headers */, - 57A4D2331BA13D7A00F7D4B1 /* RACSubject.h in Headers */, - 57A4D2341BA13D7A00F7D4B1 /* RACSubscriber.h in Headers */, - 57A4D2351BA13D7A00F7D4B1 /* RACSubscriptingAssignmentTrampoline.h in Headers */, - 57A4D2361BA13D7A00F7D4B1 /* RACTargetQueueScheduler.h in Headers */, - 57A4D2371BA13D7A00F7D4B1 /* RACTestScheduler.h in Headers */, - 57A4D2381BA13D7A00F7D4B1 /* RACTuple.h in Headers */, - 57DC89A61C50675F00E367B7 /* UITextView+RACSignalSupport.h in Headers */, - 57A4D2391BA13D7A00F7D4B1 /* RACUnit.h in Headers */, + 9A6AAA1C1DB808AA0013AAEA /* ObjCRuntimeAliases.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 57C885102A47756D00FC133C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 57C885112A47756D00FC133C /* ReactiveCocoa.h in Headers */, + 57C885122A47756D00FC133C /* ObjCRuntimeAliases.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFB4216D3C550069AD76 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFC5216D3C570069AD76 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AC03A541F7CC3BF00EC33C1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1903,54 +1370,7 @@ buildActionMask = 2147483647; files = ( A9B315CA1B3940AB0001CB9C /* ReactiveCocoa.h in Headers */, - A9B315CB1B3940AB0001CB9C /* EXTKeyPathCoding.h in Headers */, - A1046B7C1BFF5662004D8045 /* EXTRuntimeExtensions.h in Headers */, - A9B315CD1B3940AB0001CB9C /* EXTScope.h in Headers */, - A9B315CE1B3940AB0001CB9C /* metamacros.h in Headers */, - A9B315D01B3940AB0001CB9C /* NSArray+RACSequenceAdditions.h in Headers */, - A9B315D31B3940AB0001CB9C /* NSData+RACSupport.h in Headers */, - A9B315D41B3940AB0001CB9C /* NSDictionary+RACSequenceAdditions.h in Headers */, - A9B315D51B3940AB0001CB9C /* NSEnumerator+RACSequenceAdditions.h in Headers */, - A9B315D61B3940AB0001CB9C /* NSFileHandle+RACSupport.h in Headers */, - A9B315D71B3940AB0001CB9C /* NSIndexSet+RACSequenceAdditions.h in Headers */, - A9B315D91B3940AB0001CB9C /* NSNotificationCenter+RACSupport.h in Headers */, - A9B315DB1B3940AB0001CB9C /* NSObject+RACDeallocating.h in Headers */, - A9B315DE1B3940AB0001CB9C /* NSObject+RACLifting.h in Headers */, - A9B315DF1B3940AB0001CB9C /* NSObject+RACPropertySubscribing.h in Headers */, - A9B315E01B3940AB0001CB9C /* NSObject+RACSelectorSignal.h in Headers */, - A9B315E11B3940AB0001CB9C /* NSOrderedSet+RACSequenceAdditions.h in Headers */, - A9B315E21B3940AB0001CB9C /* NSSet+RACSequenceAdditions.h in Headers */, - A9B315E41B3940AB0001CB9C /* NSString+RACSequenceAdditions.h in Headers */, - A9B315E51B3940AB0001CB9C /* NSString+RACSupport.h in Headers */, - A9B315E81B3940AB0001CB9C /* NSUserDefaults+RACSupport.h in Headers */, - A9B315EA1B3940AB0001CB9C /* RACBehaviorSubject.h in Headers */, - A9B315EC1B3940AB0001CB9C /* RACChannel.h in Headers */, - A9B315ED1B3940AC0001CB9C /* RACCommand.h in Headers */, - A9B315EE1B3940AC0001CB9C /* RACCompoundDisposable.h in Headers */, - A9B315F01B3940AC0001CB9C /* RACDisposable.h in Headers */, - A9B315F71B3940AC0001CB9C /* RACEvent.h in Headers */, - BEBDD6E61CDC292D009A75A9 /* RACDelegateProxy.h in Headers */, - A9B315F81B3940AC0001CB9C /* RACGroupedSignal.h in Headers */, - A9B315FB1B3940AC0001CB9C /* RACKVOChannel.h in Headers */, - A9B315FE1B3940AC0001CB9C /* RACMulticastConnection.h in Headers */, - A9B316021B3940AD0001CB9C /* RACQueueScheduler.h in Headers */, - A9B316031B3940AD0001CB9C /* RACQueueScheduler+Subclass.h in Headers */, - A9B316041B3940AD0001CB9C /* RACReplaySubject.h in Headers */, - A9B316061B3940AD0001CB9C /* RACScheduler.h in Headers */, - A9B316081B3940AD0001CB9C /* RACScheduler+Subclass.h in Headers */, - A9B316091B3940AD0001CB9C /* RACScopedDisposable.h in Headers */, - A9B3160A1B3940AD0001CB9C /* RACSequence.h in Headers */, - A9B3160B1B3940AD0001CB9C /* RACSerialDisposable.h in Headers */, - A9B3160C1B3940AE0001CB9C /* RACSignal.h in Headers */, - A9B3160D1B3940AE0001CB9C /* RACSignal+Operations.h in Headers */, - A9B3160F1B3940AE0001CB9C /* RACStream.h in Headers */, - A9B316121B3940AE0001CB9C /* RACSubject.h in Headers */, - A9B316131B3940AE0001CB9C /* RACSubscriber.h in Headers */, - A9B316151B3940AE0001CB9C /* RACSubscriptingAssignmentTrampoline.h in Headers */, - A9B316171B3940AF0001CB9C /* RACTargetQueueScheduler.h in Headers */, - A9B316181B3940AF0001CB9C /* RACTestScheduler.h in Headers */, - A9B316191B3940AF0001CB9C /* RACTuple.h in Headers */, - A9B3161C1B3940AF0001CB9C /* RACUnit.h in Headers */, + 9A6AAA1B1DB808A90013AAEA /* ObjCRuntimeAliases.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1959,59 +1379,7 @@ buildActionMask = 2147483647; files = ( D04725F019E49ED7006002AA /* ReactiveCocoa.h in Headers */, - D037652019EDA41200A782A9 /* NSObject+RACLifting.h in Headers */, - D03764EC19EDA41200A782A9 /* NSControl+RACCommandSupport.h in Headers */, - D037655C19EDA41200A782A9 /* RACChannel.h in Headers */, - D03765EE19EDA41200A782A9 /* RACSubscriber.h in Headers */, - D03765D219EDA41200A782A9 /* RACSignal.h in Headers */, - D037650419EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h in Headers */, - D037659A19EDA41200A782A9 /* RACKVOChannel.h in Headers */, - D037651419EDA41200A782A9 /* NSObject+RACDeallocating.h in Headers */, - D037650C19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h in Headers */, - D037667319EDA57100A782A9 /* metamacros.h in Headers */, - D037666B19EDA57100A782A9 /* EXTKeyPathCoding.h in Headers */, - D03765F419EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h in Headers */, - D03765C419EDA41200A782A9 /* RACScheduler+Subclass.h in Headers */, - D037656E19EDA41200A782A9 /* RACDisposable.h in Headers */, - D03765B019EDA41200A782A9 /* RACQueueScheduler.h in Headers */, - BEBDD6E81CDC292F009A75A9 /* RACDelegateProxy.h in Headers */, - D037652419EDA41200A782A9 /* NSObject+RACPropertySubscribing.h in Headers */, - D037650019EDA41200A782A9 /* NSFileHandle+RACSupport.h in Headers */, - D037653019EDA41200A782A9 /* NSSet+RACSequenceAdditions.h in Headers */, - D037654019EDA41200A782A9 /* NSText+RACSignalSupport.h in Headers */, - D03765E019EDA41200A782A9 /* RACStream.h in Headers */, - D03765FC19EDA41200A782A9 /* RACTargetQueueScheduler.h in Headers */, - D03765B419EDA41200A782A9 /* RACQueueScheduler+Subclass.h in Headers */, - D037661019EDA41200A782A9 /* RACUnit.h in Headers */, - D037656419EDA41200A782A9 /* RACCompoundDisposable.h in Headers */, - D03764F419EDA41200A782A9 /* NSData+RACSupport.h in Headers */, - D03764FC19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h in Headers */, - D03765CA19EDA41200A782A9 /* RACSequence.h in Headers */, - D037672719EDA63400A782A9 /* RACBehaviorSubject.h in Headers */, - D037653C19EDA41200A782A9 /* NSString+RACSupport.h in Headers */, - D03765CE19EDA41200A782A9 /* RACSerialDisposable.h in Headers */, - D03765D619EDA41200A782A9 /* RACSignal+Operations.h in Headers */, - D03765B619EDA41200A782A9 /* RACReplaySubject.h in Headers */, - D03765A219EDA41200A782A9 /* RACMulticastConnection.h in Headers */, - D037658E19EDA41200A782A9 /* RACGroupedSignal.h in Headers */, - D037654819EDA41200A782A9 /* NSUserDefaults+RACSupport.h in Headers */, - D03765BE19EDA41200A782A9 /* RACScheduler.h in Headers */, - D037656019EDA41200A782A9 /* RACCommand.h in Headers */, - D037660419EDA41200A782A9 /* RACTuple.h in Headers */, - D03765C619EDA41200A782A9 /* RACScopedDisposable.h in Headers */, - D037660019EDA41200A782A9 /* RACTestScheduler.h in Headers */, - D037652C19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h in Headers */, - D03764F019EDA41200A782A9 /* NSControl+RACTextSignalSupport.h in Headers */, - D03765EA19EDA41200A782A9 /* RACSubject.h in Headers */, - A1046B7A1BFF5661004D8045 /* EXTRuntimeExtensions.h in Headers */, - D037652819EDA41200A782A9 /* NSObject+RACSelectorSignal.h in Headers */, - D037654419EDA41200A782A9 /* NSURLConnection+RACSupport.h in Headers */, - D03764E819EDA41200A782A9 /* NSArray+RACSequenceAdditions.h in Headers */, - D037651019EDA41200A782A9 /* NSObject+RACAppKitBindings.h in Headers */, - D037658A19EDA41200A782A9 /* RACEvent.h in Headers */, - D037667119EDA57100A782A9 /* EXTScope.h in Headers */, - D037653819EDA41200A782A9 /* NSString+RACSequenceAdditions.h in Headers */, - D03764F819EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h in Headers */, + 9A6AAA191DB808A80013AAEA /* ObjCRuntimeAliases.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2019,75 +1387,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - D037664519EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.h in Headers */, D037666419EDA43C00A782A9 /* ReactiveCocoa.h in Headers */, - D037652519EDA41200A782A9 /* NSObject+RACPropertySubscribing.h in Headers */, - D03765B119EDA41200A782A9 /* RACQueueScheduler.h in Headers */, - D037662519EDA41200A782A9 /* UIButton+RACCommandSupport.h in Headers */, - D037672819EDA63500A782A9 /* RACBehaviorSubject.h in Headers */, - D037660119EDA41200A782A9 /* RACTestScheduler.h in Headers */, - D03765A319EDA41200A782A9 /* RACMulticastConnection.h in Headers */, - D03765B719EDA41200A782A9 /* RACReplaySubject.h in Headers */, - D037663D19EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.h in Headers */, - D037656F19EDA41200A782A9 /* RACDisposable.h in Headers */, - 314304171ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.h in Headers */, - D037654519EDA41200A782A9 /* NSURLConnection+RACSupport.h in Headers */, - D037661D19EDA41200A782A9 /* UIAlertView+RACSignalSupport.h in Headers */, - D037650D19EDA41200A782A9 /* NSNotificationCenter+RACSupport.h in Headers */, - D037650119EDA41200A782A9 /* NSFileHandle+RACSupport.h in Headers */, - D037666119EDA41200A782A9 /* UITextView+RACSignalSupport.h in Headers */, - D037659B19EDA41200A782A9 /* RACKVOChannel.h in Headers */, - D037652D19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.h in Headers */, - D03764F919EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.h in Headers */, - D037667219EDA57100A782A9 /* EXTScope.h in Headers */, - D037663519EDA41200A782A9 /* UIDatePicker+RACSignalSupport.h in Headers */, - D037667419EDA57100A782A9 /* metamacros.h in Headers */, - D03764FD19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.h in Headers */, - D037652119EDA41200A782A9 /* NSObject+RACLifting.h in Headers */, - D037665919EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.h in Headers */, - D037656519EDA41200A782A9 /* RACCompoundDisposable.h in Headers */, - D037653D19EDA41200A782A9 /* NSString+RACSupport.h in Headers */, - D037662919EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.h in Headers */, - D037660519EDA41200A782A9 /* RACTuple.h in Headers */, - D037665519EDA41200A782A9 /* UITableViewCell+RACSignalSupport.h in Headers */, - D03764F519EDA41200A782A9 /* NSData+RACSupport.h in Headers */, - D037653919EDA41200A782A9 /* NSString+RACSequenceAdditions.h in Headers */, - D037651519EDA41200A782A9 /* NSObject+RACDeallocating.h in Headers */, - D037658F19EDA41200A782A9 /* RACGroupedSignal.h in Headers */, - D03765C519EDA41200A782A9 /* RACScheduler+Subclass.h in Headers */, - D03765E119EDA41200A782A9 /* RACStream.h in Headers */, - D03765D719EDA41200A782A9 /* RACSignal+Operations.h in Headers */, - D037665D19EDA41200A782A9 /* UITextField+RACSignalSupport.h in Headers */, - D037664919EDA41200A782A9 /* UISlider+RACSignalSupport.h in Headers */, - D03765BF19EDA41200A782A9 /* RACScheduler.h in Headers */, - D03764E919EDA41200A782A9 /* NSArray+RACSequenceAdditions.h in Headers */, - D037654919EDA41200A782A9 /* NSUserDefaults+RACSupport.h in Headers */, - D037663919EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.h in Headers */, - D037653119EDA41200A782A9 /* NSSet+RACSequenceAdditions.h in Headers */, - D03765CB19EDA41200A782A9 /* RACSequence.h in Headers */, - D037662D19EDA41200A782A9 /* UIControl+RACSignalSupport.h in Headers */, - D037666C19EDA57100A782A9 /* EXTKeyPathCoding.h in Headers */, - D037658B19EDA41200A782A9 /* RACEvent.h in Headers */, - D03765CF19EDA41200A782A9 /* RACSerialDisposable.h in Headers */, - D037650519EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.h in Headers */, - D037655D19EDA41200A782A9 /* RACChannel.h in Headers */, - D03765B519EDA41200A782A9 /* RACQueueScheduler+Subclass.h in Headers */, - D037665119EDA41200A782A9 /* UISwitch+RACSignalSupport.h in Headers */, - D037664119EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.h in Headers */, - D037652919EDA41200A782A9 /* NSObject+RACSelectorSignal.h in Headers */, - D03765D319EDA41200A782A9 /* RACSignal.h in Headers */, - D03765F519EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.h in Headers */, - D03765C719EDA41200A782A9 /* RACScopedDisposable.h in Headers */, - A1046B7B1BFF5662004D8045 /* EXTRuntimeExtensions.h in Headers */, - D037661119EDA41200A782A9 /* RACUnit.h in Headers */, - D03765FD19EDA41200A782A9 /* RACTargetQueueScheduler.h in Headers */, - D037661919EDA41200A782A9 /* UIActionSheet+RACSignalSupport.h in Headers */, - D037664D19EDA41200A782A9 /* UIStepper+RACSignalSupport.h in Headers */, - D037662119EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.h in Headers */, - D03765EB19EDA41200A782A9 /* RACSubject.h in Headers */, - BEBDD6E71CDC292E009A75A9 /* RACDelegateProxy.h in Headers */, - D037656119EDA41200A782A9 /* RACCommand.h in Headers */, - D03765EF19EDA41200A782A9 /* RACSubscriber.h in Headers */, + 9A6AAA1A1DB808A80013AAEA /* ObjCRuntimeAliases.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2098,9 +1399,9 @@ isa = PBXNativeTarget; buildConfigurationList = 57A4D23C1BA13D7A00F7D4B1 /* Build configuration list for PBXNativeTarget "ReactiveCocoa-tvOS" */; buildPhases = ( + 57A4D2091BA13D7A00F7D4B1 /* Headers */, 57A4D1B01BA13D7A00F7D4B1 /* Sources */, 57A4D2071BA13D7A00F7D4B1 /* Frameworks */, - 57A4D2091BA13D7A00F7D4B1 /* Headers */, 57A4D23B1BA13D7A00F7D4B1 /* Resources */, ); buildRules = ( @@ -2108,10 +1409,28 @@ dependencies = ( ); name = "ReactiveCocoa-tvOS"; - productName = ReactiveCocoa; + productName = "ReactiveCocoa-tvOS"; productReference = 57A4D2411BA13D7A00F7D4B1 /* ReactiveCocoa.framework */; productType = "com.apple.product-type.framework"; }; + 57C8850F2A47756D00FC133C /* ReactiveCocoa-xrOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57C8854E2A47756D00FC133C /* Build configuration list for PBXNativeTarget "ReactiveCocoa-xrOS" */; + buildPhases = ( + 57C885102A47756D00FC133C /* Headers */, + 57C885132A47756D00FC133C /* Sources */, + 57C8854B2A47756D00FC133C /* Frameworks */, + 57C8854D2A47756D00FC133C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveCocoa-xrOS"; + productName = "ReactiveCocoa-iOS"; + productReference = 57C885532A47756D00FC133C /* ReactiveCocoa.framework */; + productType = "com.apple.product-type.framework"; + }; 7DFBED021CDB8C9500EE435B /* ReactiveCocoa-tvOSTests */ = { isa = PBXNativeTarget; buildConfigurationList = 7DFBED0F1CDB8C9500EE435B /* Build configuration list for PBXNativeTarget "ReactiveCocoa-tvOSTests" */; @@ -2131,13 +1450,124 @@ productReference = 7DFBED031CDB8C9500EE435B /* ReactiveCocoaTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 9A16753C1F80C35100B63650 /* ReactiveMapKitTests-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A1675451F80C35100B63650 /* Build configuration list for PBXNativeTarget "ReactiveMapKitTests-iOS" */; + buildPhases = ( + 9A1675391F80C35100B63650 /* Sources */, + 9A16753A1F80C35100B63650 /* Frameworks */, + 9A16753B1F80C35100B63650 /* Resources */, + 654DE7B12205F9F60048FE14 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 9A73E046216D404D0069AD76 /* PBXTargetDependency */, + ); + name = "ReactiveMapKitTests-iOS"; + productName = ReactiveMapKitTests; + productReference = 9A16753D1F80C35100B63650 /* ReactiveMapKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 9A73DFAC216D3C550069AD76 /* ReactiveMapKit-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A73DFB6216D3C550069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKit-iOS" */; + buildPhases = ( + 9A73DFB4216D3C550069AD76 /* Headers */, + 9A73DFAD216D3C550069AD76 /* Sources */, + 9A73DFB0216D3C550069AD76 /* Frameworks */, + 9A73DFB5216D3C550069AD76 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveMapKit-iOS"; + productName = ReactiveMapKit; + productReference = 9A73DFBB216D3C550069AD76 /* ReactiveMapKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 9A73DFBD216D3C570069AD76 /* ReactiveMapKit-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A73DFC7216D3C570069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKit-tvOS" */; + buildPhases = ( + 9A73DFC5216D3C570069AD76 /* Headers */, + 9A73DFBE216D3C570069AD76 /* Sources */, + 9A73DFC1216D3C570069AD76 /* Frameworks */, + 9A73DFC6216D3C570069AD76 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveMapKit-tvOS"; + productName = ReactiveMapKit; + productReference = 9A73DFCC216D3C570069AD76 /* ReactiveMapKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 9A73DFDF216D3CEB0069AD76 /* ReactiveMapKitTests-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A73DFF3216D3CEB0069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKitTests-macOS" */; + buildPhases = ( + 9A73DFE2216D3CEB0069AD76 /* Sources */, + 9A73DFE4216D3CEB0069AD76 /* Frameworks */, + 9A73DFEB216D3CEB0069AD76 /* Resources */, + 654DE7AF2205F9D60048FE14 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 9A73DFE0216D3CEB0069AD76 /* PBXTargetDependency */, + ); + name = "ReactiveMapKitTests-macOS"; + productName = ReactiveMapKitTests; + productReference = 9A73DFF8216D3CEB0069AD76 /* ReactiveMapKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 9A73DFFA216D3CEE0069AD76 /* ReactiveMapKitTests-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A73E00E216D3CEE0069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKitTests-tvOS" */; + buildPhases = ( + 9A73DFFD216D3CEE0069AD76 /* Sources */, + 9A73DFFF216D3CEE0069AD76 /* Frameworks */, + 9A73E006216D3CEE0069AD76 /* Resources */, + 654DE7AE2205F9C30048FE14 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 9A73E048216D40550069AD76 /* PBXTargetDependency */, + ); + name = "ReactiveMapKitTests-tvOS"; + productName = ReactiveMapKitTests; + productReference = 9A73E013216D3CEE0069AD76 /* ReactiveMapKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 9AC03A561F7CC3BF00EC33C1 /* ReactiveMapKit-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9AC03A5C1F7CC3BF00EC33C1 /* Build configuration list for PBXNativeTarget "ReactiveMapKit-macOS" */; + buildPhases = ( + 9AC03A541F7CC3BF00EC33C1 /* Headers */, + 9AC03A521F7CC3BF00EC33C1 /* Sources */, + 9AC03A531F7CC3BF00EC33C1 /* Frameworks */, + 9AC03A551F7CC3BF00EC33C1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveMapKit-macOS"; + productName = ReactiveMapKit; + productReference = 9AC03A571F7CC3BF00EC33C1 /* ReactiveMapKit.framework */; + productType = "com.apple.product-type.framework"; + }; A9B315531B3940610001CB9C /* ReactiveCocoa-watchOS */ = { isa = PBXNativeTarget; buildConfigurationList = A9B3155D1B3940610001CB9C /* Build configuration list for PBXNativeTarget "ReactiveCocoa-watchOS" */; buildPhases = ( + A9B315511B3940610001CB9C /* Headers */, A9B3154F1B3940610001CB9C /* Sources */, A9B315501B3940610001CB9C /* Frameworks */, - A9B315511B3940610001CB9C /* Headers */, A9B315521B3940610001CB9C /* Resources */, ); buildRules = ( @@ -2145,31 +1575,31 @@ dependencies = ( ); name = "ReactiveCocoa-watchOS"; - productName = ReactiveCocoa; + productName = "ReactiveCocoa-watchOS"; productReference = A9B315541B3940610001CB9C /* ReactiveCocoa.framework */; productType = "com.apple.product-type.framework"; }; - D04725E919E49ED7006002AA /* ReactiveCocoa-Mac */ = { + D04725E919E49ED7006002AA /* ReactiveCocoa-macOS */ = { isa = PBXNativeTarget; - buildConfigurationList = D047260019E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-Mac" */; + buildConfigurationList = D047260019E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-macOS" */; buildPhases = ( + D04725E719E49ED7006002AA /* Headers */, D04725E519E49ED7006002AA /* Sources */, D04725E619E49ED7006002AA /* Frameworks */, - D04725E719E49ED7006002AA /* Headers */, D04725E819E49ED7006002AA /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = "ReactiveCocoa-Mac"; - productName = ReactiveCocoa; + name = "ReactiveCocoa-macOS"; + productName = "ReactiveCocoa-macOS"; productReference = D04725EA19E49ED7006002AA /* ReactiveCocoa.framework */; productType = "com.apple.product-type.framework"; }; - D04725F419E49ED7006002AA /* ReactiveCocoa-MacTests */ = { + D04725F419E49ED7006002AA /* ReactiveCocoa-macOSTests */ = { isa = PBXNativeTarget; - buildConfigurationList = D047260319E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-MacTests" */; + buildConfigurationList = D047260319E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-macOSTests" */; buildPhases = ( D04725F119E49ED7006002AA /* Sources */, D04725F219E49ED7006002AA /* Frameworks */, @@ -2180,8 +1610,8 @@ dependencies = ( D04725F819E49ED7006002AA /* PBXTargetDependency */, ); - name = "ReactiveCocoa-MacTests"; - productName = ReactiveCocoaTests; + name = "ReactiveCocoa-macOSTests"; + productName = "ReactiveCocoa-macOSTests"; productReference = D04725F519E49ED7006002AA /* ReactiveCocoaTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; @@ -2189,9 +1619,9 @@ isa = PBXNativeTarget; buildConfigurationList = D047261F19E49F82006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-iOS" */; buildPhases = ( + D047260919E49F82006002AA /* Headers */, D047260719E49F82006002AA /* Sources */, D047260819E49F82006002AA /* Frameworks */, - D047260919E49F82006002AA /* Headers */, D047260A19E49F82006002AA /* Resources */, ); buildRules = ( @@ -2199,7 +1629,7 @@ dependencies = ( ); name = "ReactiveCocoa-iOS"; - productName = ReactiveCocoa; + productName = "ReactiveCocoa-iOS"; productReference = D047260C19E49F82006002AA /* ReactiveCocoa.framework */; productType = "com.apple.product-type.framework"; }; @@ -2218,7 +1648,7 @@ D047261919E49F82006002AA /* PBXTargetDependency */, ); name = "ReactiveCocoa-iOSTests"; - productName = ReactiveCocoaTests; + productName = "ReactiveCocoa-iOSTests"; productReference = D047261619E49F82006002AA /* ReactiveCocoaTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; @@ -2228,58 +1658,87 @@ D04725E119E49ED7006002AA /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0800; + LastSwiftUpdateCheck = 0900; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = GitHub; TargetAttributes = { 57A4D1AF1BA13D7A00F7D4B1 = { - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; 7DFBED021CDB8C9500EE435B = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; + }; + 9A16753C1F80C35100B63650 = { + CreatedOnToolsVersion = 9.0; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + }; + 9A73DFAC216D3C550069AD76 = { + LastSwiftMigration = 1020; + }; + 9A73DFBD216D3C570069AD76 = { + LastSwiftMigration = 1020; + }; + 9A73DFDF216D3CEB0069AD76 = { + LastSwiftMigration = 1020; + }; + 9A73DFFA216D3CEE0069AD76 = { + LastSwiftMigration = 1020; + }; + 9AC03A561F7CC3BF00EC33C1 = { + CreatedOnToolsVersion = 9.0; + LastSwiftMigration = 1020; }; A9B315531B3940610001CB9C = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; D04725E919E49ED7006002AA = { CreatedOnToolsVersion = 6.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; D04725F419E49ED7006002AA = { CreatedOnToolsVersion = 6.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; D047260B19E49F82006002AA = { CreatedOnToolsVersion = 6.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; D047261519E49F82006002AA = { CreatedOnToolsVersion = 6.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; }; }; buildConfigurationList = D04725E419E49ED7006002AA /* Build configuration list for PBXProject "ReactiveCocoa" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = D04725E019E49ED7006002AA; productRefGroup = D04725EB19E49ED7006002AA /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - D04725E919E49ED7006002AA /* ReactiveCocoa-Mac */, - D04725F419E49ED7006002AA /* ReactiveCocoa-MacTests */, + D04725E919E49ED7006002AA /* ReactiveCocoa-macOS */, + D04725F419E49ED7006002AA /* ReactiveCocoa-macOSTests */, D047260B19E49F82006002AA /* ReactiveCocoa-iOS */, D047261519E49F82006002AA /* ReactiveCocoa-iOSTests */, A9B315531B3940610001CB9C /* ReactiveCocoa-watchOS */, 57A4D1AF1BA13D7A00F7D4B1 /* ReactiveCocoa-tvOS */, 7DFBED021CDB8C9500EE435B /* ReactiveCocoa-tvOSTests */, + 57C8850F2A47756D00FC133C /* ReactiveCocoa-xrOS */, + 9AC03A561F7CC3BF00EC33C1 /* ReactiveMapKit-macOS */, + 9A73DFDF216D3CEB0069AD76 /* ReactiveMapKitTests-macOS */, + 9A73DFAC216D3C550069AD76 /* ReactiveMapKit-iOS */, + 9A16753C1F80C35100B63650 /* ReactiveMapKitTests-iOS */, + 9A73DFBD216D3C570069AD76 /* ReactiveMapKit-tvOS */, + 9A73DFFA216D3CEE0069AD76 /* ReactiveMapKitTests-tvOS */, ); }; /* End PBXProject section */ @@ -2292,6 +1751,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 57C8854D2A47756D00FC133C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7DFBED011CDB8C9500EE435B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2300,40 +1766,82 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - A9B315521B3940610001CB9C /* Resources */ = { + 9A16753B1F80C35100B63650 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - D04725E819E49ED7006002AA /* Resources */ = { + 9A73DFB5216D3C550069AD76 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - D04725F319E49ED7006002AA /* Resources */ = { + 9A73DFC6216D3C570069AD76 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D037671719EDA60000A782A9 /* test-data.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - D047260A19E49F82006002AA /* Resources */ = { + 9A73DFEB216D3CEB0069AD76 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - D047261419E49F82006002AA /* Resources */ = { + 9A73E006216D3CEE0069AD76 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D037671819EDA60000A782A9 /* test-data.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AC03A551F7CC3BF00EC33C1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A9B315521B3940610001CB9C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D04725E819E49ED7006002AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D04725F319E49ED7006002AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D037671719EDA60000A782A9 /* test-data.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047260A19E49F82006002AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047261419E49F82006002AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D037671819EDA60000A782A9 /* test-data.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2344,108 +1852,106 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 57A4D1B11BA13D7A00F7D4B1 /* Optional.swift in Sources */, - 57D476951C4206EC00EFE697 /* UITableViewCell+RACSignalSupport.m in Sources */, - 57A4D1B21BA13D7A00F7D4B1 /* RACCompoundDisposableProvider.d in Sources */, - 57D476901C4206D400EFE697 /* UIControl+RACSignalSupportPrivate.m in Sources */, - 57A4D1B31BA13D7A00F7D4B1 /* RACSignalProvider.d in Sources */, - 57A4D1B41BA13D7A00F7D4B1 /* Disposable.swift in Sources */, - 57A4D1B61BA13D7A00F7D4B1 /* Event.swift in Sources */, - 57A4D1B71BA13D7A00F7D4B1 /* ObjectiveCBridging.swift in Sources */, - 57A4D1B81BA13D7A00F7D4B1 /* Scheduler.swift in Sources */, - 57A4D1B91BA13D7A00F7D4B1 /* Action.swift in Sources */, - 57A4D1BA1BA13D7A00F7D4B1 /* Property.swift in Sources */, - 57A4D1BB1BA13D7A00F7D4B1 /* Signal.swift in Sources */, - 57A4D1BC1BA13D7A00F7D4B1 /* SignalProducer.swift in Sources */, - 57A4D1BD1BA13D7A00F7D4B1 /* Atomic.swift in Sources */, - 57A4D1BE1BA13D7A00F7D4B1 /* Bag.swift in Sources */, - 57A4D1BF1BA13D7A00F7D4B1 /* TupleExtensions.swift in Sources */, - 57A4D1C01BA13D7A00F7D4B1 /* FoundationExtensions.swift in Sources */, - 57A4D1C11BA13D7A00F7D4B1 /* EXTRuntimeExtensions.m in Sources */, - 57A4D1C21BA13D7A00F7D4B1 /* NSArray+RACSequenceAdditions.m in Sources */, - 57A4D1C31BA13D7A00F7D4B1 /* NSData+RACSupport.m in Sources */, - 57A4D1C41BA13D7A00F7D4B1 /* NSDictionary+RACSequenceAdditions.m in Sources */, - 57A4D1C51BA13D7A00F7D4B1 /* NSEnumerator+RACSequenceAdditions.m in Sources */, - D85C652D1C0E70E5005A77AD /* Flatten.swift in Sources */, - 57D476961C4206EC00EFE697 /* UITableViewHeaderFooterView+RACSignalSupport.m in Sources */, - 57A4D1C61BA13D7A00F7D4B1 /* NSFileHandle+RACSupport.m in Sources */, - 57A4D1C71BA13D7A00F7D4B1 /* NSIndexSet+RACSequenceAdditions.m in Sources */, - 57A4D1C81BA13D7A00F7D4B1 /* NSInvocation+RACTypeParsing.m in Sources */, - 57D4769B1C4206F200EFE697 /* UICollectionReusableView+RACSignalSupport.m in Sources */, - 57A4D1C91BA13D7A00F7D4B1 /* NSNotificationCenter+RACSupport.m in Sources */, - 7DFBED6E1CDB918900EE435B /* UIBarButtonItem+RACCommandSupport.m in Sources */, - 57A4D1CA1BA13D7A00F7D4B1 /* NSObject+RACDeallocating.m in Sources */, - 57A4D1CB1BA13D7A00F7D4B1 /* NSObject+RACDescription.m in Sources */, - 57A4D1CC1BA13D7A00F7D4B1 /* NSObject+RACKVOWrapper.m in Sources */, - 57A4D1CD1BA13D7A00F7D4B1 /* NSObject+RACLifting.m in Sources */, - 57A4D1CE1BA13D7A00F7D4B1 /* NSObject+RACPropertySubscribing.m in Sources */, - 57A4D1CF1BA13D7A00F7D4B1 /* NSObject+RACSelectorSignal.m in Sources */, - 9ABCB1881D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, - 57D476981C4206EC00EFE697 /* UITextView+RACSignalSupport.m in Sources */, - 57A4D1D01BA13D7A00F7D4B1 /* NSOrderedSet+RACSequenceAdditions.m in Sources */, - 57A4D1D11BA13D7A00F7D4B1 /* NSSet+RACSequenceAdditions.m in Sources */, - 57D476911C4206DA00EFE697 /* UIGestureRecognizer+RACSignalSupport.m in Sources */, - 57A4D1D21BA13D7A00F7D4B1 /* NSString+RACKeyPathUtilities.m in Sources */, - 57D4769A1C4206F200EFE697 /* UIButton+RACCommandSupport.m in Sources */, - 57A4D1D31BA13D7A00F7D4B1 /* NSString+RACSequenceAdditions.m in Sources */, - 57A4D1D41BA13D7A00F7D4B1 /* NSString+RACSupport.m in Sources */, - 57A4D1D61BA13D7A00F7D4B1 /* NSUserDefaults+RACSupport.m in Sources */, - 57A4D1D71BA13D7A00F7D4B1 /* RACArraySequence.m in Sources */, - 57A4D1D81BA13D7A00F7D4B1 /* RACBehaviorSubject.m in Sources */, - 57A4D1D91BA13D7A00F7D4B1 /* RACBlockTrampoline.m in Sources */, - 57A4D1DA1BA13D7A00F7D4B1 /* RACChannel.m in Sources */, - 57A4D1DB1BA13D7A00F7D4B1 /* RACCommand.m in Sources */, - 57A4D1DC1BA13D7A00F7D4B1 /* RACCompoundDisposable.m in Sources */, - 57A4D1DD1BA13D7A00F7D4B1 /* RACDelegateProxy.m in Sources */, - 57A4D1DE1BA13D7A00F7D4B1 /* RACDisposable.m in Sources */, + A9EB3D221E94F308002A9BCC /* UINavigationItem.swift in Sources */, + 9A74884B1E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */, + 9A1D06131D93EA0100ACF44C /* UIBarItem.swift in Sources */, + 9A1D061F1D93EA0100ACF44C /* UITextField.swift in Sources */, + 4ABEFE261DCFCF640066A8C2 /* UICollectionView.swift in Sources */, + 9AB15C7D1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */, + 9ADE4A7F1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */, + 9AE7C2A71DDD7F5100F7534C /* ObjC+Messages.swift in Sources */, + 4EE637372090F92600ECD02A /* UIApplication.swift in Sources */, + 9A1D06201D93EA0100ACF44C /* UITextView.swift in Sources */, + 9A6AAA241DB8F51C0013AAEA /* UIKitReusableComponents.swift in Sources */, + 4ABEFE201DCFCEF80066A8C2 /* UITableView.swift in Sources */, + 9A1D06181D93EA0100ACF44C /* UIImageView.swift in Sources */, + 9AA0BD841DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */, 9AD0F06D1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */, - EBCC7DBF1BBF01E200A2AE92 /* Observer.swift in Sources */, - 57A4D1DF1BA13D7A00F7D4B1 /* RACDynamicSequence.m in Sources */, - C7142DBF1CDEA195009F402D /* CocoaAction.swift in Sources */, - 57A4D1E01BA13D7A00F7D4B1 /* RACDynamicSignal.m in Sources */, - 57A4D1E11BA13D7A00F7D4B1 /* RACEagerSequence.m in Sources */, - C79B64801CD52E4E003F2376 /* EventLogger.swift in Sources */, - 57D4768D1C42063C00EFE697 /* UIControl+RACSignalSupport.m in Sources */, - 4A0E11021D2A92720065D310 /* Lifetime.swift in Sources */, - 57A4D1E21BA13D7A00F7D4B1 /* RACEmptySequence.m in Sources */, - 57A4D1E31BA13D7A00F7D4B1 /* RACEmptySignal.m in Sources */, - 57A4D1E41BA13D7A00F7D4B1 /* RACErrorSignal.m in Sources */, - 57A4D1E51BA13D7A00F7D4B1 /* RACEvent.m in Sources */, - 57A4D1E61BA13D7A00F7D4B1 /* RACGroupedSignal.m in Sources */, - 57A4D1E71BA13D7A00F7D4B1 /* RACImmediateScheduler.m in Sources */, - 57D476971C4206EC00EFE697 /* UITextField+RACSignalSupport.m in Sources */, - 57A4D1E81BA13D7A00F7D4B1 /* RACIndexSetSequence.m in Sources */, - 57A4D1E91BA13D7A00F7D4B1 /* RACKVOChannel.m in Sources */, - 57A4D1EA1BA13D7A00F7D4B1 /* RACKVOProxy.m in Sources */, - 57A4D1EB1BA13D7A00F7D4B1 /* RACKVOTrampoline.m in Sources */, - 57A4D1EC1BA13D7A00F7D4B1 /* RACMulticastConnection.m in Sources */, - 57A4D1EE1BA13D7A00F7D4B1 /* RACPassthroughSubscriber.m in Sources */, - 57A4D1EF1BA13D7A00F7D4B1 /* RACQueueScheduler.m in Sources */, - 57A4D1F01BA13D7A00F7D4B1 /* RACReplaySubject.m in Sources */, - 57A4D1F11BA13D7A00F7D4B1 /* RACReturnSignal.m in Sources */, - 57A4D1F21BA13D7A00F7D4B1 /* RACScheduler.m in Sources */, - 57A4D1F31BA13D7A00F7D4B1 /* RACScopedDisposable.m in Sources */, - 57A4D1F41BA13D7A00F7D4B1 /* RACSequence.m in Sources */, - 57A4D1F51BA13D7A00F7D4B1 /* RACSerialDisposable.m in Sources */, - 57A4D1F61BA13D7A00F7D4B1 /* RACSignal.m in Sources */, - 57D476921C4206DF00EFE697 /* UISegmentedControl+RACSignalSupport.m in Sources */, - 57A4D1F71BA13D7A00F7D4B1 /* RACSignal+Operations.m in Sources */, - 57A4D1F81BA13D7A00F7D4B1 /* RACSignalSequence.m in Sources */, - 57A4D1F91BA13D7A00F7D4B1 /* RACStream.m in Sources */, - 57A4D1FA1BA13D7A00F7D4B1 /* RACStringSequence.m in Sources */, - 9A694EF61D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */, + 4EE6372F2090EEFA00ECD02A /* UIViewController.swift in Sources */, + 9A1D06211D93EA0100ACF44C /* UIView.swift in Sources */, + 538DCB7B1DCA5E6C00332880 /* NSLayoutConstraint.swift in Sources */, + 9AA0BD9B1DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */, + 9ADE4A941DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */, + 9A1D05E31D93E99100ACF44C /* NSObject+Association.swift in Sources */, + 419139491DB910570043C9D1 /* UIGestureRecognizer.swift in Sources */, + 9A1D06161D93EA0100ACF44C /* UIControl.swift in Sources */, + 9A1D06141D93EA0100ACF44C /* UIButton.swift in Sources */, + 9A1D065E1D93EC6E00ACF44C /* NSObject+Intercepting.swift in Sources */, + 9AA0BD8D1DDE153A00531FCF /* ObjC+Constants.swift in Sources */, + 9AF0EA781D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */, + 9A1D06191D93EA0100ACF44C /* UILabel.swift in Sources */, + 4A0E11021D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */, + 9ADFE5A81DC0001C001E11F7 /* Synchronizing.swift in Sources */, + A9D8BA72207CD7090031733D /* UIResponder.swift in Sources */, CD0C45E11CC9A288009F5BF0 /* DynamicProperty.swift in Sources */, - 57A4D1FB1BA13D7A00F7D4B1 /* RACSubject.m in Sources */, - 57A4D1FC1BA13D7A00F7D4B1 /* RACSubscriber.m in Sources */, - 57A4D1FD1BA13D7A00F7D4B1 /* RACSubscriptingAssignmentTrampoline.m in Sources */, - 57A4D1FE1BA13D7A00F7D4B1 /* RACSubscriptionScheduler.m in Sources */, - 57A4D1FF1BA13D7A00F7D4B1 /* RACTargetQueueScheduler.m in Sources */, - 57A4D2001BA13D7A00F7D4B1 /* RACTestScheduler.m in Sources */, - 57A4D2011BA13D7A00F7D4B1 /* RACTuple.m in Sources */, - 57A4D2021BA13D7A00F7D4B1 /* RACTupleSequence.m in Sources */, - 57A4D2031BA13D7A00F7D4B1 /* RACUnarySequence.m in Sources */, - 57A4D2041BA13D7A00F7D4B1 /* RACUnit.m in Sources */, - 57A4D2051BA13D7A00F7D4B1 /* RACValueTransformer.m in Sources */, + A9EB3D2D1E94F5A2002A9BCC /* UITabBarItem.swift in Sources */, + 9AA0BD921DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */, + 9A1D061A1D93EA0100ACF44C /* UIProgressView.swift in Sources */, + 9AA0BD7F1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */, + 9A2E42611DAA6737006D909F /* CocoaTarget.swift in Sources */, + 9A9037521ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */, + 9A1D06121D93EA0100ACF44C /* UIBarButtonItem.swift in Sources */, + 9A1D06111D93EA0100ACF44C /* UIActivityIndicatorView.swift in Sources */, + 9A1D061B1D93EA0100ACF44C /* UISegmentedControl.swift in Sources */, + 9A54A21E1DE00D09001739B3 /* ObjC+Selector.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 57C885132A47756D00FC133C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 57C885142A47756D00FC133C /* UINavigationItem.swift in Sources */, + 57C885152A47756D00FC133C /* UICollectionView.swift in Sources */, + 57C885162A47756D00FC133C /* UIBarItem.swift in Sources */, + 57C885182A47756D00FC133C /* UITextField.swift in Sources */, + 57C885192A47756D00FC133C /* ReactiveSwift+Lifetime.swift in Sources */, + 57C8851A2A47756D00FC133C /* Deprecations+Removals.swift in Sources */, + 57C8851B2A47756D00FC133C /* UIKitReusableComponents.swift in Sources */, + 57C8851C2A47756D00FC133C /* DelegateProxy.swift in Sources */, + 57C8851D2A47756D00FC133C /* UITextView.swift in Sources */, + 57C8851E2A47756D00FC133C /* UISearchBar.swift in Sources */, + 57C8851F2A47756D00FC133C /* UIImageView.swift in Sources */, + 57C885202A47756D00FC133C /* NSObject+BindingTarget.swift in Sources */, + 57C885212A47756D00FC133C /* DynamicProperty.swift in Sources */, + 57C885222A47756D00FC133C /* ObjC+Messages.swift in Sources */, + 57C885232A47756D00FC133C /* CocoaTarget.swift in Sources */, + 57C885242A47756D00FC133C /* UIKeyboard.swift in Sources */, + 57C885252A47756D00FC133C /* UISlider.swift in Sources */, + 57C885262A47756D00FC133C /* UIApplication.swift in Sources */, + 57C885272A47756D00FC133C /* ObjC+RuntimeSubclassing.swift in Sources */, + 57C885282A47756D00FC133C /* UIView.swift in Sources */, + 57C885292A47756D00FC133C /* UIDatePicker.swift in Sources */, + 57C8852A2A47756D00FC133C /* UIResponder.swift in Sources */, + 57C8852B2A47756D00FC133C /* NSObject+Association.swift in Sources */, + 57C8852C2A47756D00FC133C /* UIControl.swift in Sources */, + 57C8852D2A47756D00FC133C /* UIButton.swift in Sources */, + 57C8852E2A47756D00FC133C /* UIViewController.swift in Sources */, + 57C8852F2A47756D00FC133C /* NSObject+ObjCRuntime.swift in Sources */, + 57C885302A47756D00FC133C /* UIGestureRecognizer.swift in Sources */, + 57C885312A47756D00FC133C /* ObjC+Runtime.swift in Sources */, + 57C885322A47756D00FC133C /* UIPickerView.swift in Sources */, + 57C885332A47756D00FC133C /* UISwitch.swift in Sources */, + 57C885342A47756D00FC133C /* NSObject+Intercepting.swift in Sources */, + 57C885362A47756D00FC133C /* UILabel.swift in Sources */, + 57C885372A47756D00FC133C /* UIScrollView.swift in Sources */, + 57C885382A47756D00FC133C /* AnyObject+Lifetime.swift in Sources */, + 57C885392A47756D00FC133C /* NSObject+ReactiveExtensionsProvider.swift in Sources */, + 57C8853A2A47756D00FC133C /* Synchronizing.swift in Sources */, + 57C8853B2A47756D00FC133C /* NSObject+KeyValueObserving.swift in Sources */, + 57C8853C2A47756D00FC133C /* UITableView.swift in Sources */, + 57C8853D2A47756D00FC133C /* UIProgressView.swift in Sources */, + 57C8853E2A47756D00FC133C /* UIStepper.swift in Sources */, + 57C8853F2A47756D00FC133C /* UITabBarItem.swift in Sources */, + 57C885402A47756D00FC133C /* NSLayoutConstraint.swift in Sources */, + 57C885422A47756D00FC133C /* UIBarButtonItem.swift in Sources */, + 57C885432A47756D00FC133C /* ObjCRuntimeAliases.m in Sources */, + 57C885442A47756D00FC133C /* UIActivityIndicatorView.swift in Sources */, + 57C885462A47756D00FC133C /* ObjC+Selector.swift in Sources */, + 57C885472A47756D00FC133C /* ObjC+Constants.swift in Sources */, + 57C885482A47756D00FC133C /* UISegmentedControl.swift in Sources */, + 57C885492A47756D00FC133C /* CocoaAction.swift in Sources */, + 57C8854A2A47756D00FC133C /* UIRefreshControl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2453,68 +1959,96 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7DFBED221CDB8DE300EE435B /* ActionSpec.swift in Sources */, - 7DFBED231CDB8DE300EE435B /* AtomicSpec.swift in Sources */, - 7DFBED241CDB8DE300EE435B /* BagSpec.swift in Sources */, - 7DFBED251CDB8DE300EE435B /* DisposableSpec.swift in Sources */, - 7DFBED261CDB8DE300EE435B /* FoundationExtensionsSpec.swift in Sources */, - 7DFBED271CDB8DE300EE435B /* ObjectiveCBridgingSpec.swift in Sources */, - 7DFBED281CDB8DE300EE435B /* PropertySpec.swift in Sources */, - 7DFBED291CDB8DE300EE435B /* SchedulerSpec.swift in Sources */, - 7DFBED2A1CDB8DE300EE435B /* SignalLifetimeSpec.swift in Sources */, - 7DFBED2B1CDB8DE300EE435B /* SignalProducerSpec.swift in Sources */, - 7DFBED2C1CDB8DE300EE435B /* SignalProducerLiftingSpec.swift in Sources */, - 7DFBED2D1CDB8DE300EE435B /* SignalSpec.swift in Sources */, - 7DFBED2E1CDB8DE300EE435B /* FlattenSpec.swift in Sources */, - 7DFBED2F1CDB8DE300EE435B /* TestError.swift in Sources */, - 7DFBED301CDB8DE300EE435B /* TestLogger.swift in Sources */, - 7DFBED321CDB8DE300EE435B /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */, - 7DFBED331CDB8DE300EE435B /* NSNotificationCenterRACSupportSpec.m in Sources */, - 7DFBED351CDB8DE300EE435B /* NSObjectRACDeallocatingSpec.m in Sources */, - 7DFBED361CDB8DE300EE435B /* NSObjectRACLiftingSpec.m in Sources */, - 7DFBED381CDB8DE300EE435B /* NSObjectRACPropertySubscribingExamples.m in Sources */, - 7DFBED391CDB8DE300EE435B /* NSObjectRACPropertySubscribingSpec.m in Sources */, - 7DFBED3A1CDB8DE300EE435B /* NSObjectRACSelectorSignalSpec.m in Sources */, - 7DFBED3B1CDB8DE300EE435B /* NSStringRACKeyPathUtilitiesSpec.m in Sources */, - 4A0E11061D2A95200065D310 /* LifetimeSpec.swift in Sources */, - 7DFBED3D1CDB8DE300EE435B /* NSUserDefaultsRACSupportSpec.m in Sources */, - 7DFBED3E1CDB8DE300EE435B /* RACBlockTrampolineSpec.m in Sources */, - 7DFBED401CDB8DE300EE435B /* RACChannelExamples.m in Sources */, - 7DFBED411CDB8DE300EE435B /* RACChannelSpec.m in Sources */, - 7DFBED421CDB8DE300EE435B /* RACCommandSpec.m in Sources */, - 7DFBED431CDB8DE300EE435B /* RACCompoundDisposableSpec.m in Sources */, - 7DFBED451CDB8DE300EE435B /* RACControlCommandExamples.m in Sources */, - 7DFBED461CDB8DE300EE435B /* RACDelegateProxySpec.m in Sources */, - 7DFBED471CDB8DE300EE435B /* RACDisposableSpec.m in Sources */, - 7DFBED481CDB8DE300EE435B /* RACEventSpec.m in Sources */, - 7DFBED491CDB8DE300EE435B /* RACKVOChannelSpec.m in Sources */, + A9EB3D231E94F314002A9BCC /* UINavigationItemSpec.swift in Sources */, + 9A9A129A1DC7A97100D10223 /* UIGestureRecognizerSpec.swift in Sources */, + 9A1D06591D93EA7E00ACF44C /* UIViewSpec.swift in Sources */, + 3B30EE8E1E7BE529007CC8EF /* DeprecationsSpec.swift in Sources */, + 7DFBED281CDB8DE300EE435B /* DynamicPropertySpec.swift in Sources */, + 9A9DFEEB1DA7EFB60039EE1B /* AssociationSpec.swift in Sources */, CD8401851CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */, - 7DFBED4A1CDB8DE300EE435B /* RACKVOProxySpec.m in Sources */, - 7DFBED4B1CDB8DE300EE435B /* RACKVOWrapperSpec.m in Sources */, 9A1E72BC1D4DE96500CC20C3 /* KeyValueObservingSpec.swift in Sources */, + 9AA6A1E71F11F9B100CA2257 /* KeyValueObservingSpec+Swift4.swift in Sources */, + 9A1D06451D93EA7E00ACF44C /* UIImageViewSpec.swift in Sources */, + 9A1D06551D93EA7E00ACF44C /* UITextViewSpec.swift in Sources */, + 9A7990D01F1085D8001493A3 /* BindingTargetSpec.swift in Sources */, + 9A1D063B1D93EA7E00ACF44C /* UIButtonSpec.swift in Sources */, + 9A24A8471DE142A600987AF9 /* SwizzlingSpec.swift in Sources */, + 9A1D064B1D93EA7E00ACF44C /* UISegmentedControlSpec.swift in Sources */, + CD42C69D1E951F6A00AA9504 /* ReactiveCocoaTestsConfiguration.swift in Sources */, + 9A1D063F1D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift in Sources */, + 9A1D06371D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift in Sources */, + 4EE637332090EFDD00ECD02A /* UIViewControllerSpec.swift in Sources */, + 9A6AAA2F1DB903A40013AAEA /* UIKitReusableComponentsSpec.swift in Sources */, + 538DCB7F1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift in Sources */, + 9A892D911E8D19BE00EA35F3 /* DelegateProxySpec.swift in Sources */, + 9A1D06471D93EA7E00ACF44C /* UILabelSpec.swift in Sources */, + 9A1D06391D93EA7E00ACF44C /* UIBarButtonItemSpec.swift in Sources */, + 9AD841DE204C29B90040F0C0 /* MessageForwardingEntity.m in Sources */, + A9EB3D2B1E94F3D9002A9BCC /* UITabBarItemSpec.swift in Sources */, 7DFBED6D1CDB8F7D00EE435B /* SignalProducerNimbleMatchers.swift in Sources */, - 7DFBED4C1CDB8DE300EE435B /* RACMulticastConnectionSpec.m in Sources */, - 7DFBED4E1CDB8DE300EE435B /* RACPropertySignalExamples.m in Sources */, - 7DFBED4F1CDB8DE300EE435B /* RACSchedulerSpec.m in Sources */, - 7DFBED501CDB8DE300EE435B /* RACSequenceAdditionsSpec.m in Sources */, - 7DFBED521CDB8DE300EE435B /* RACSequenceExamples.m in Sources */, - 7DFBED531CDB8DE300EE435B /* RACSequenceSpec.m in Sources */, - 7DFBED541CDB8DE300EE435B /* RACSerialDisposableSpec.m in Sources */, - 7DFBED551CDB8DE300EE435B /* RACSignalSpec.m in Sources */, - 7DFBED571CDB8DE300EE435B /* RACStreamExamples.m in Sources */, - 7DFBED591CDB8DE300EE435B /* RACSubclassObject.m in Sources */, - 7DFBED5A1CDB8DE300EE435B /* RACSubjectSpec.m in Sources */, - 7DFBED5C1CDB8DE300EE435B /* RACSubscriberExamples.m in Sources */, - 7DFBED5D1CDB8DE300EE435B /* RACSubscriberSpec.m in Sources */, - 7DFBED5E1CDB8DE300EE435B /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */, - 7DFBED5F1CDB8DE300EE435B /* RACTargetQueueSchedulerSpec.m in Sources */, - 7DFBED601CDB8DE300EE435B /* RACTupleSpec.m in Sources */, - 7DFBED631CDB8DE300EE435B /* UIBarButtonItemRACSupportSpec.m in Sources */, - 7DFBED641CDB8DE300EE435B /* UIButtonRACSupportSpec.m in Sources */, - 7DFBED671CDB8DE300EE435B /* RACTestExampleScheduler.m in Sources */, - 7DFBED691CDB8DE300EE435B /* RACTestObject.m in Sources */, - 7DFBED6A1CDB8DE300EE435B /* RACTestSchedulerSpec.m in Sources */, - 7DFBED6C1CDB8DE300EE435B /* RACTestUIButton.m in Sources */, + 9ADFE5A31DBFFBCF001E11F7 /* LifetimeSpec.swift in Sources */, + 9A1D06411D93EA7E00ACF44C /* UIControlSpec.swift in Sources */, + 9AFCBFE51EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift in Sources */, + 9A6AAA101DB6A4CF0013AAEA /* InterceptingSpec.swift in Sources */, + 4ABEFE221DCFCF0A0066A8C2 /* UITableViewSpec.swift in Sources */, + 9A1D06491D93EA7E00ACF44C /* UIProgressViewSpec.swift in Sources */, + A9D8BA75207CD8430031733D /* UIResponderSpec.swift in Sources */, + 4ABEFE291DCFCFA90066A8C2 /* UICollectionViewSpec.swift in Sources */, + 9A54A2131DDF5B4D001739B3 /* InterceptingPerformanceTests.swift in Sources */, + BEE020661D637B0000DF261F /* TestError.swift in Sources */, + 5B76DE422498EDDC00E8B4F3 /* QueueScheduler+Factory.swift in Sources */, + 9A1D06531D93EA7E00ACF44C /* UITextFieldSpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A1675391F80C35100B63650 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A16755D1F80DE1C00B63650 /* MKMapViewSpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFAD216D3C550069AD76 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73DFAE216D3C550069AD76 /* MKMapView.swift in Sources */, + 9A73DFAF216D3C550069AD76 /* MKLocalSearchRequest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFBE216D3C570069AD76 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73DFBF216D3C570069AD76 /* MKMapView.swift in Sources */, + 9A73DFC0216D3C570069AD76 /* MKLocalSearchRequest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFE2216D3CEB0069AD76 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73DFE3216D3CEB0069AD76 /* MKMapViewSpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A73DFFD216D3CEE0069AD76 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A73DFFE216D3CEE0069AD76 /* MKMapViewSpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AC03A521F7CC3BF00EC33C1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AC03A611F7CC5E300EC33C1 /* MKMapView.swift in Sources */, + A91244E820389AEA0001BBCB /* MKLocalSearchRequest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2522,97 +2056,41 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A9F793341B60D0140026BCBA /* Optional.swift in Sources */, - A9B316341B394C7F0001CB9C /* RACCompoundDisposableProvider.d in Sources */, - A9B316351B394C7F0001CB9C /* RACSignalProvider.d in Sources */, - A9B315BC1B3940810001CB9C /* Disposable.swift in Sources */, - A9B315BE1B3940810001CB9C /* Event.swift in Sources */, - A9B315BF1B3940810001CB9C /* ObjectiveCBridging.swift in Sources */, - A9B315C01B3940810001CB9C /* Scheduler.swift in Sources */, - A9B315C11B3940810001CB9C /* Action.swift in Sources */, - A9B315C21B3940810001CB9C /* Property.swift in Sources */, - A9B315C31B3940810001CB9C /* Signal.swift in Sources */, - A9B315C41B3940810001CB9C /* SignalProducer.swift in Sources */, - A9B315C51B3940810001CB9C /* Atomic.swift in Sources */, - A9B315C61B3940810001CB9C /* Bag.swift in Sources */, - A9B315C71B3940810001CB9C /* TupleExtensions.swift in Sources */, - A9B315C81B3940810001CB9C /* FoundationExtensions.swift in Sources */, - A9B3155E1B3940750001CB9C /* EXTRuntimeExtensions.m in Sources */, - A9B315601B3940750001CB9C /* NSArray+RACSequenceAdditions.m in Sources */, - A9B315631B3940750001CB9C /* NSData+RACSupport.m in Sources */, - A9B315641B3940750001CB9C /* NSDictionary+RACSequenceAdditions.m in Sources */, - A9B315651B3940750001CB9C /* NSEnumerator+RACSequenceAdditions.m in Sources */, - D85C652C1C0E70E4005A77AD /* Flatten.swift in Sources */, - A9B315661B3940750001CB9C /* NSFileHandle+RACSupport.m in Sources */, - A9B315671B3940750001CB9C /* NSIndexSet+RACSequenceAdditions.m in Sources */, - A9B315681B3940750001CB9C /* NSInvocation+RACTypeParsing.m in Sources */, - A9B315691B3940750001CB9C /* NSNotificationCenter+RACSupport.m in Sources */, - 9ABCB1871D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, - A9B3156B1B3940750001CB9C /* NSObject+RACDeallocating.m in Sources */, - A9B3156C1B3940750001CB9C /* NSObject+RACDescription.m in Sources */, - A9B3156D1B3940750001CB9C /* NSObject+RACKVOWrapper.m in Sources */, - A9B3156E1B3940750001CB9C /* NSObject+RACLifting.m in Sources */, - A9B3156F1B3940750001CB9C /* NSObject+RACPropertySubscribing.m in Sources */, - A9B315701B3940750001CB9C /* NSObject+RACSelectorSignal.m in Sources */, - A9B315711B3940750001CB9C /* NSOrderedSet+RACSequenceAdditions.m in Sources */, - A9B315721B3940750001CB9C /* NSSet+RACSequenceAdditions.m in Sources */, - A9B315731B3940750001CB9C /* NSString+RACKeyPathUtilities.m in Sources */, - A9B315741B3940750001CB9C /* NSString+RACSequenceAdditions.m in Sources */, - A9B315751B3940750001CB9C /* NSString+RACSupport.m in Sources */, - A9B315781B3940750001CB9C /* NSUserDefaults+RACSupport.m in Sources */, - A9B315791B3940750001CB9C /* RACArraySequence.m in Sources */, - A9B3157A1B3940750001CB9C /* RACBehaviorSubject.m in Sources */, - A9B3157B1B3940750001CB9C /* RACBlockTrampoline.m in Sources */, - A9B3157C1B3940750001CB9C /* RACChannel.m in Sources */, - A9B3157D1B3940750001CB9C /* RACCommand.m in Sources */, - A9B3157E1B3940750001CB9C /* RACCompoundDisposable.m in Sources */, - A9B3157F1B3940750001CB9C /* RACDelegateProxy.m in Sources */, - A9B315801B3940750001CB9C /* RACDisposable.m in Sources */, - EBCC7DBE1BBF01E200A2AE92 /* Observer.swift in Sources */, - C79B647F1CD52E4D003F2376 /* EventLogger.swift in Sources */, - A9B315811B3940750001CB9C /* RACDynamicSequence.m in Sources */, - A9B315821B3940750001CB9C /* RACDynamicSignal.m in Sources */, - A9B315831B3940750001CB9C /* RACEagerSequence.m in Sources */, - A9B315841B3940750001CB9C /* RACEmptySequence.m in Sources */, - A9B315851B3940750001CB9C /* RACEmptySignal.m in Sources */, - A9B315861B3940750001CB9C /* RACErrorSignal.m in Sources */, - A9B315871B3940750001CB9C /* RACEvent.m in Sources */, - A9B315881B3940750001CB9C /* RACGroupedSignal.m in Sources */, - A9B315891B3940750001CB9C /* RACImmediateScheduler.m in Sources */, - A9B3158A1B3940750001CB9C /* RACIndexSetSequence.m in Sources */, - A9B3158B1B3940750001CB9C /* RACKVOChannel.m in Sources */, - A9B3158C1B3940750001CB9C /* RACKVOProxy.m in Sources */, - A9B3158D1B3940750001CB9C /* RACKVOTrampoline.m in Sources */, - A9B3158E1B3940750001CB9C /* RACMulticastConnection.m in Sources */, - C7142DBE1CDEA194009F402D /* CocoaAction.swift in Sources */, - 9A694EF51D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */, - A9B315901B3940750001CB9C /* RACPassthroughSubscriber.m in Sources */, - A9B315911B3940750001CB9C /* RACQueueScheduler.m in Sources */, - A9B315921B3940750001CB9C /* RACReplaySubject.m in Sources */, - A9B315931B3940750001CB9C /* RACReturnSignal.m in Sources */, - A9B315941B3940750001CB9C /* RACScheduler.m in Sources */, - A9B315951B3940750001CB9C /* RACScopedDisposable.m in Sources */, - A9B315961B3940750001CB9C /* RACSequence.m in Sources */, - A9B315971B3940750001CB9C /* RACSerialDisposable.m in Sources */, - A9B315981B3940750001CB9C /* RACSignal.m in Sources */, - A9B315991B3940750001CB9C /* RACSignal+Operations.m in Sources */, - A9B3159A1B3940750001CB9C /* RACSignalSequence.m in Sources */, - A9B3159B1B3940750001CB9C /* RACStream.m in Sources */, - A9B3159C1B3940750001CB9C /* RACStringSequence.m in Sources */, + 9A2E42601DAA6737006D909F /* CocoaTarget.swift in Sources */, + E3AA54C3214292550077B206 /* WKInterfaceButton.swift in Sources */, + E92142942284ABDF007E412D /* WKInterfaceSlider.swift in Sources */, + 9A74884A1E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */, + E92142A9228552CC007E412D /* WKInterfaceMovie.swift in Sources */, + 9A9DFEE51DA7B5500039EE1B /* NSObject+Intercepting.swift in Sources */, + E921429E2284B1C4007E412D /* WKInterfaceVolumeControl.swift in Sources */, + 9A54A21D1DE00D09001739B3 /* ObjC+Selector.swift in Sources */, + 9AB15C7C1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */, + E92142982284AEF0007E412D /* WKInterfaceGroup.swift in Sources */, + 9AA0BD7E1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */, + E921428C2284A136007E412D /* WKInterfaceObject.swift in Sources */, + 9AA0BD9A1DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */, + 9ADE4A931DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */, + E92142A02284B4FF007E412D /* WKInterfacePicker.swift in Sources */, + 9AA0BD8C1DDE153A00531FCF /* ObjC+Constants.swift in Sources */, + 9ADFE5A71DC0001C001E11F7 /* Synchronizing.swift in Sources */, + 9ADE4A7E1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */, + E921429A2284AF99007E412D /* WKInterfaceSeparator.swift in Sources */, + 9AA0BD831DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */, + E3AA54C12142918B0077B206 /* WKInterfaceLabel.swift in Sources */, + E92142922284AA64007E412D /* WKInterfaceSwitch.swift in Sources */, CD0C45E01CC9A288009F5BF0 /* DynamicProperty.swift in Sources */, - A9B3159D1B3940750001CB9C /* RACSubject.m in Sources */, - 4A0E11011D2A92720065D310 /* Lifetime.swift in Sources */, - A9B3159E1B3940750001CB9C /* RACSubscriber.m in Sources */, - A9B3159F1B3940750001CB9C /* RACSubscriptingAssignmentTrampoline.m in Sources */, - A9B315A01B3940750001CB9C /* RACSubscriptionScheduler.m in Sources */, - A9B315A11B3940750001CB9C /* RACTargetQueueScheduler.m in Sources */, - A9B315A21B3940750001CB9C /* RACTestScheduler.m in Sources */, - A9B315A31B3940750001CB9C /* RACTuple.m in Sources */, - A9B315A41B3940750001CB9C /* RACTupleSequence.m in Sources */, - A9B315A51B3940750001CB9C /* RACUnarySequence.m in Sources */, - A9B315A61B3940750001CB9C /* RACUnit.m in Sources */, - A9B315A71B3940750001CB9C /* RACValueTransformer.m in Sources */, + 9AA0BD911DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */, + E92142A22284B64E007E412D /* WKInterfaceController.swift in Sources */, + E92142AB228553C5007E412D /* WKInterfaceInlineMovie.swift in Sources */, + E921428E2284A44F007E412D /* WKInterfaceDate.swift in Sources */, + 9AF0EA771D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */, + 9A9037511ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */, + E92142902284A4EF007E412D /* WKInterfaceTimer.swift in Sources */, + 9A1D05E21D93E99100ACF44C /* NSObject+Association.swift in Sources */, + 4A0E11011D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */, + 9AE7C2A61DDD7F5100F7534C /* ObjC+Messages.swift in Sources */, 9AD0F06C1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */, + E921429C2284B0A8007E412D /* WKInterfaceImage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2620,102 +2098,57 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D037654219EDA41200A782A9 /* NSText+RACSignalSupport.m in Sources */, - D037659C19EDA41200A782A9 /* RACKVOChannel.m in Sources */, - D03765C819EDA41200A782A9 /* RACScopedDisposable.m in Sources */, - D03764FE19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m in Sources */, - D03764EA19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m in Sources */, - D00004091A46864E000E7D41 /* TupleExtensions.swift in Sources */, - D03765C019EDA41200A782A9 /* RACScheduler.m in Sources */, - D037659819EDA41200A782A9 /* RACIndexSetSequence.m in Sources */, - D03765D819EDA41200A782A9 /* RACSignal+Operations.m in Sources */, - D871D69F1B3B29A40070F16C /* Optional.swift in Sources */, - D08C54B61A69A3DB00AD8286 /* Event.swift in Sources */, - 9A694EF31D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */, - D03764F219EDA41200A782A9 /* NSControl+RACTextSignalSupport.m in Sources */, - D037650219EDA41200A782A9 /* NSFileHandle+RACSupport.m in Sources */, - D03765E219EDA41200A782A9 /* RACStream.m in Sources */, - D037655619EDA41200A782A9 /* RACBehaviorSubject.m in Sources */, - D037660219EDA41200A782A9 /* RACTestScheduler.m in Sources */, - D03765B819EDA41200A782A9 /* RACReplaySubject.m in Sources */, - D03765EC19EDA41200A782A9 /* RACSubject.m in Sources */, - D03765D019EDA41200A782A9 /* RACSerialDisposable.m in Sources */, - D0C312D319EF2A5800984962 /* Disposable.swift in Sources */, - D037666F19EDA57100A782A9 /* EXTRuntimeExtensions.m in Sources */, - D037653E19EDA41200A782A9 /* NSString+RACSupport.m in Sources */, - D037653619EDA41200A782A9 /* NSString+RACKeyPathUtilities.m in Sources */, - D03764FA19EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m in Sources */, - EBCC7DBC1BBF010C00A2AE92 /* Observer.swift in Sources */, + 4ABEFE2B1DCFD0030066A8C2 /* NSTableView.swift in Sources */, CD0C45DE1CC9A288009F5BF0 /* DynamicProperty.swift in Sources */, - D037656819EDA41200A782A9 /* RACCompoundDisposableProvider.d in Sources */, - D03B4A3D19F4C39A009E02AC /* FoundationExtensions.swift in Sources */, - D037653A19EDA41200A782A9 /* NSString+RACSequenceAdditions.m in Sources */, - D03765E819EDA41200A782A9 /* RACStringSequence.m in Sources */, - D03764EE19EDA41200A782A9 /* NSControl+RACCommandSupport.m in Sources */, - D08C54B31A69A2AE00AD8286 /* Signal.swift in Sources */, + 4ABEFE301DCFD0530066A8C2 /* NSCollectionView.swift in Sources */, + 57911A1C270C43E700E26A24 /* UIView.swift in Sources */, + 57911A1D270C43EA00E26A24 /* UIViewController.swift in Sources */, + 57911A14270C43C400E26A24 /* UIGestureRecognizer.swift in Sources */, + 57911A13270C43C100E26A24 /* UIControl.swift in Sources */, + 537EC08121A950CA00D6EE18 /* NSView.swift in Sources */, 9AD0F06A1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */, - D037660A19EDA41200A782A9 /* RACTupleSequence.m in Sources */, - D03765D419EDA41200A782A9 /* RACSignal.m in Sources */, - D037651A19EDA41200A782A9 /* NSObject+RACDescription.m in Sources */, - D03765A419EDA41200A782A9 /* RACMulticastConnection.m in Sources */, - D037654E19EDA41200A782A9 /* RACArraySequence.m in Sources */, - D037652219EDA41200A782A9 /* NSObject+RACLifting.m in Sources */, - D037650619EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m in Sources */, - D037650E19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m in Sources */, - D03765FA19EDA41200A782A9 /* RACSubscriptionScheduler.m in Sources */, - D85C652A1C0D84C7005A77AD /* Flatten.swift in Sources */, - D0C312CF19EF2A5800984962 /* Bag.swift in Sources */, - D037658019EDA41200A782A9 /* RACEmptySequence.m in Sources */, - D037654A19EDA41200A782A9 /* NSUserDefaults+RACSupport.m in Sources */, - D037660E19EDA41200A782A9 /* RACUnarySequence.m in Sources */, - D03765FE19EDA41200A782A9 /* RACTargetQueueScheduler.m in Sources */, - C7142DBC1CDEA167009F402D /* CocoaAction.swift in Sources */, - D03765DE19EDA41200A782A9 /* RACSignalSequence.m in Sources */, - D037656C19EDA41200A782A9 /* RACDelegateProxy.m in Sources */, - D037657419EDA41200A782A9 /* RACDynamicSequence.m in Sources */, - D037657019EDA41200A782A9 /* RACDisposable.m in Sources */, - D03765DA19EDA41200A782A9 /* RACSignalProvider.d in Sources */, - D037653219EDA41200A782A9 /* NSSet+RACSequenceAdditions.m in Sources */, - D037651219EDA41200A782A9 /* NSObject+RACAppKitBindings.m in Sources */, - D037656619EDA41200A782A9 /* RACCompoundDisposable.m in Sources */, - D037655A19EDA41200A782A9 /* RACBlockTrampoline.m in Sources */, - D0C312DF19EF2A5800984962 /* ObjectiveCBridging.swift in Sources */, - D037659019EDA41200A782A9 /* RACGroupedSignal.m in Sources */, - D037655E19EDA41200A782A9 /* RACChannel.m in Sources */, - D037657C19EDA41200A782A9 /* RACEagerSequence.m in Sources */, - D037657819EDA41200A782A9 /* RACDynamicSignal.m in Sources */, - D037659419EDA41200A782A9 /* RACImmediateScheduler.m in Sources */, - 7A7065811A3F88B8001E8354 /* RACKVOProxy.m in Sources */, - D037651619EDA41200A782A9 /* NSObject+RACDeallocating.m in Sources */, - 4A0E10FF1D2A92720065D310 /* Lifetime.swift in Sources */, - D0C312E719EF2A5800984962 /* Scheduler.swift in Sources */, - D0C312CD19EF2A5800984962 /* Atomic.swift in Sources */, - D037658419EDA41200A782A9 /* RACEmptySignal.m in Sources */, - D037654619EDA41200A782A9 /* NSURLConnection+RACSupport.m in Sources */, - D03765F019EDA41200A782A9 /* RACSubscriber.m in Sources */, - D03764F619EDA41200A782A9 /* NSData+RACSupport.m in Sources */, - D037656219EDA41200A782A9 /* RACCommand.m in Sources */, - D037658819EDA41200A782A9 /* RACErrorSignal.m in Sources */, - D03765F619EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m in Sources */, - D08C54BA1A69C54300AD8286 /* Property.swift in Sources */, - D0D11AB91A6AE87700C1F8B1 /* Action.swift in Sources */, - D037661219EDA41200A782A9 /* RACUnit.m in Sources */, - D03765A019EDA41200A782A9 /* RACKVOTrampoline.m in Sources */, - D037650A19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m in Sources */, - D037660619EDA41200A782A9 /* RACTuple.m in Sources */, - D037651E19EDA41200A782A9 /* NSObject+RACKVOWrapper.m in Sources */, - D037661619EDA41200A782A9 /* RACValueTransformer.m in Sources */, - C79B647C1CD52E23003F2376 /* EventLogger.swift in Sources */, - D03765CC19EDA41200A782A9 /* RACSequence.m in Sources */, - 9ABCB1851D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, - D037652E19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m in Sources */, - D037652619EDA41200A782A9 /* NSObject+RACPropertySubscribing.m in Sources */, - D037658C19EDA41200A782A9 /* RACEvent.m in Sources */, - D08C54B81A69A9D000AD8286 /* SignalProducer.swift in Sources */, - D03765B219EDA41200A782A9 /* RACQueueScheduler.m in Sources */, - D037652A19EDA41200A782A9 /* NSObject+RACSelectorSignal.m in Sources */, - D03765AE19EDA41200A782A9 /* RACPassthroughSubscriber.m in Sources */, - D03765BC19EDA41200A782A9 /* RACReturnSignal.m in Sources */, + 9AA0BD8F1DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */, + 57911A10270C43BA00E26A24 /* UIBarItem.swift in Sources */, + 579C7D4E2731BC43009F8A2F /* UIKitReusableComponents.swift in Sources */, + 57911A0D270C43AE00E26A24 /* UISwitch.swift in Sources */, + 9A1D05E01D93E99100ACF44C /* NSObject+Association.swift in Sources */, + 538DCB791DCA5E6C00332880 /* NSLayoutConstraint.swift in Sources */, + 9A7488481E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */, + 9AE7C2A41DDD7F5100F7534C /* ObjC+Messages.swift in Sources */, + 57911A16270C43CB00E26A24 /* UILabel.swift in Sources */, + 006518761E26865800C3139A /* NSButton.swift in Sources */, + 9A54A21B1DE00D09001739B3 /* ObjC+Selector.swift in Sources */, + 57911A11270C43BC00E26A24 /* UIButton.swift in Sources */, + 57911A0F270C43B600E26A24 /* UIBarButtonItem.swift in Sources */, + 9AA0BD8A1DDE153A00531FCF /* ObjC+Constants.swift in Sources */, + 9AED64C51E496A3700321004 /* ActionProxy.swift in Sources */, + 9A6AAA261DB8F5280013AAEA /* AppKitReusableComponents.swift in Sources */, + 9A90374F1ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */, + 9AA0BD7C1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */, + 9ADFE5A51DC0001C001E11F7 /* Synchronizing.swift in Sources */, + 57911A0E270C43B200E26A24 /* UIActivityIndicatorView.swift in Sources */, + 9ADE4A7C1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */, + 9ADE4A961DA6F018005C2AC8 /* NSTextField.swift in Sources */, + 9AA0BD981DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */, + 57911A1B270C43E000E26A24 /* UITableView.swift in Sources */, + 57911A18270C43D400E26A24 /* UIScrollView.swift in Sources */, + 57911A19270C43D800E26A24 /* UISegmentedControl.swift in Sources */, + 7A8BA0FA1FCC86FC003241C7 /* NSTextView.swift in Sources */, + 57911A17270C43CE00E26A24 /* UINavigationItem.swift in Sources */, + 9A2E425E1DAA6737006D909F /* CocoaTarget.swift in Sources */, + 9AB15C7A1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */, + 8392D8FD1DB93E5E00504ED4 /* NSImageView.swift in Sources */, + 57911A12270C43BF00E26A24 /* UICollectionView.swift in Sources */, + 9ADE4A891DA6D206005C2AC8 /* NSControl.swift in Sources */, + 9AF0EA751D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */, + D9558AB81DFF805A003254E1 /* NSPopUpButton.swift in Sources */, + 834DE1141E4122910099F4E5 /* NSSlider.swift in Sources */, + 9ADE4A911DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */, + 834DE1121E4120340099F4E5 /* NSSegmentedControl.swift in Sources */, + 57911A15270C43C600E26A24 /* UIImageView.swift in Sources */, + 9A1D065B1D93EC6E00ACF44C /* NSObject+Intercepting.swift in Sources */, + 4A0E10FF1D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */, + 9AA0BD811DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2723,68 +2156,35 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D0A2260E1A72F16D00D33B74 /* PropertySpec.swift in Sources */, - D03766C719EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m in Sources */, - D03766E319EDA60000A782A9 /* RACDelegateProxySpec.m in Sources */, + 834DE1011E4109750099F4E5 /* NSImageViewSpec.swift in Sources */, + 9A0726F31E912B610081F3F7 /* ActionProxySpec.swift in Sources */, + 9A6AAA0E1DB6A4CF0013AAEA /* InterceptingSpec.swift in Sources */, + 9A54A2111DDF5B4D001739B3 /* InterceptingPerformanceTests.swift in Sources */, + 538DCB7D1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift in Sources */, + 9ADE4A8F1DA6DA20005C2AC8 /* NSControlSpec.swift in Sources */, + D0A2260E1A72F16D00D33B74 /* DynamicPropertySpec.swift in Sources */, + 9AFCBFE31EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift in Sources */, + 537EC08321A9557400D6EE18 /* NSViewSpec.swift in Sources */, + 9ADFE5A11DBFFBCF001E11F7 /* LifetimeSpec.swift in Sources */, + 4ABEFE331DCFD0630066A8C2 /* NSCollectionViewSpec.swift in Sources */, B696FB811A7640C00075236D /* TestError.swift in Sources */, - D021671D1A6CD50500987861 /* ActionSpec.swift in Sources */, - D03766F919EDA60000A782A9 /* RACSerialDisposableSpec.m in Sources */, - D0C3131E19EF2D9700984962 /* RACTestExampleScheduler.m in Sources */, - D037670B19EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m in Sources */, - D03766DD19EDA60000A782A9 /* RACCommandSpec.m in Sources */, - D0C3130E19EF2B1F00984962 /* SchedulerSpec.swift in Sources */, - D037670919EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */, + 9A892D8F1E8D19BE00EA35F3 /* DelegateProxySpec.swift in Sources */, + 004FD0071E26CDB300A03A82 /* NSButtonSpec.swift in Sources */, + 9AD841DC204C29B90040F0C0 /* MessageForwardingEntity.m in Sources */, + 9A7990CE1F1085D8001493A3 /* BindingTargetSpec.swift in Sources */, + D9558AB91DFF86C0003254E1 /* NSPopUpButtonSpec.swift in Sources */, BFA6B94D1A7604D400C846D1 /* SignalProducerNimbleMatchers.swift in Sources */, - D03766EB19EDA60000A782A9 /* RACKVOWrapperSpec.m in Sources */, - D03766E719EDA60000A782A9 /* RACEventSpec.m in Sources */, - D03766F719EDA60000A782A9 /* RACSequenceSpec.m in Sources */, - D8170FC11B100EBC004192AD /* FoundationExtensionsSpec.swift in Sources */, - D03766C919EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m in Sources */, - D03766C319EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m in Sources */, - C79B64741CD38B2B003F2376 /* TestLogger.swift in Sources */, - D03766BD19EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */, - CA6F28501C52626B001879D2 /* FlattenSpec.swift in Sources */, - D037670119EDA60000A782A9 /* RACSubclassObject.m in Sources */, - D03766CD19EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */, - 4A0E11041D2A95200065D310 /* LifetimeSpec.swift in Sources */, - D037671519EDA60000A782A9 /* RACTupleSpec.m in Sources */, - D03766C519EDA60000A782A9 /* NSObjectRACLiftingSpec.m in Sources */, - D03766D119EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m in Sources */, - D03766F319EDA60000A782A9 /* RACSequenceAdditionsSpec.m in Sources */, - D03766ED19EDA60000A782A9 /* RACMulticastConnectionSpec.m in Sources */, - CDCD247A1C277EEC00710AEE /* AtomicSpec.swift in Sources */, - D03766E919EDA60000A782A9 /* RACKVOChannelSpec.m in Sources */, - D03766FB19EDA60000A782A9 /* RACSignalSpec.m in Sources */, - 7A7065841A3F8967001E8354 /* RACKVOProxySpec.m in Sources */, - 579504331BB8A34200A5E482 /* BagSpec.swift in Sources */, - D037670719EDA60000A782A9 /* RACSubscriberSpec.m in Sources */, + 9A24A8451DE142A400987AF9 /* SwizzlingSpec.swift in Sources */, + 9A6AAA2B1DB8F85C0013AAEA /* AppKitReusableComponentsSpec.swift in Sources */, CD8401831CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */, - D03766EF19EDA60000A782A9 /* RACPropertySignalExamples.m in Sources */, - D037670519EDA60000A782A9 /* RACSubscriberExamples.m in Sources */, + 9A9DFEE91DA7EFB60039EE1B /* AssociationSpec.swift in Sources */, + 9AADB6F41F84AECB00EFFD19 /* Swift4TestInteroperability.swift in Sources */, + 3B30EE8C1E7BE529007CC8EF /* DeprecationsSpec.swift in Sources */, + CD42C69B1E951F6900AA9504 /* ReactiveCocoaTestsConfiguration.swift in Sources */, + 9AA6A1E51F11F9B000CA2257 /* KeyValueObservingSpec+Swift4.swift in Sources */, + 5B76DE402498EDDC00E8B4F3 /* QueueScheduler+Factory.swift in Sources */, 9A1E72BA1D4DE96500CC20C3 /* KeyValueObservingSpec.swift in Sources */, - D0A226081A72E0E900D33B74 /* SignalSpec.swift in Sources */, - D0C3132219EF2D9700984962 /* RACTestSchedulerSpec.m in Sources */, - 02D2602B1C1D6DB8003ACC61 /* SignalLifetimeSpec.swift in Sources */, - D0C3130C19EF2B1F00984962 /* DisposableSpec.swift in Sources */, - D03766D719EDA60000A782A9 /* RACBlockTrampolineSpec.m in Sources */, - D0A2260B1A72E6C500D33B74 /* SignalProducerSpec.swift in Sources */, - D03766FF19EDA60000A782A9 /* RACStreamExamples.m in Sources */, - D03766CB19EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m in Sources */, - D03766E119EDA60000A782A9 /* RACControlCommandExamples.m in Sources */, - D03766BF19EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m in Sources */, - D037670319EDA60000A782A9 /* RACSubjectSpec.m in Sources */, - D03766F119EDA60000A782A9 /* RACSchedulerSpec.m in Sources */, - D03766DF19EDA60000A782A9 /* RACCompoundDisposableSpec.m in Sources */, - D03766E519EDA60000A782A9 /* RACDisposableSpec.m in Sources */, - D0C3132019EF2D9700984962 /* RACTestObject.m in Sources */, - D03766D319EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m in Sources */, - D03766C119EDA60000A782A9 /* NSObjectRACAppKitBindingsSpec.m in Sources */, - D03766DB19EDA60000A782A9 /* RACChannelSpec.m in Sources */, - D0A226111A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift in Sources */, - D03766D919EDA60000A782A9 /* RACChannelExamples.m in Sources */, - D03766F519EDA60000A782A9 /* RACSequenceExamples.m in Sources */, - D8024DB21B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift in Sources */, - D03766B919EDA60000A782A9 /* NSControllerRACSupportSpec.m in Sources */, + 4ABEFE2E1DCFD01F0066A8C2 /* NSTableViewSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2792,118 +2192,61 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D037659D19EDA41200A782A9 /* RACKVOChannel.m in Sources */, - D08C54B41A69A2AF00AD8286 /* Signal.swift in Sources */, - D037666319EDA41200A782A9 /* UITextView+RACSignalSupport.m in Sources */, - D037662F19EDA41200A782A9 /* UIControl+RACSignalSupport.m in Sources */, - D03765C919EDA41200A782A9 /* RACScopedDisposable.m in Sources */, - D03764FF19EDA41200A782A9 /* NSEnumerator+RACSequenceAdditions.m in Sources */, - D037664719EDA41200A782A9 /* UISegmentedControl+RACSignalSupport.m in Sources */, - D8E84A671B3B32FB00C3E831 /* Optional.swift in Sources */, - D03764EB19EDA41200A782A9 /* NSArray+RACSequenceAdditions.m in Sources */, - D0C312D419EF2A5800984962 /* Disposable.swift in Sources */, - D03765C119EDA41200A782A9 /* RACScheduler.m in Sources */, - D037662B19EDA41200A782A9 /* UICollectionReusableView+RACSignalSupport.m in Sources */, - D037659919EDA41200A782A9 /* RACIndexSetSequence.m in Sources */, - D03765D919EDA41200A782A9 /* RACSignal+Operations.m in Sources */, - D037661B19EDA41200A782A9 /* UIActionSheet+RACSignalSupport.m in Sources */, - D037650319EDA41200A782A9 /* NSFileHandle+RACSupport.m in Sources */, - D08C54B91A69A9D100AD8286 /* SignalProducer.swift in Sources */, - 9ABCB1861D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, - D03765E319EDA41200A782A9 /* RACStream.m in Sources */, - D037655719EDA41200A782A9 /* RACBehaviorSubject.m in Sources */, - D037663B19EDA41200A782A9 /* UIGestureRecognizer+RACSignalSupport.m in Sources */, - D037660319EDA41200A782A9 /* RACTestScheduler.m in Sources */, - D03765B919EDA41200A782A9 /* RACReplaySubject.m in Sources */, - D03765ED19EDA41200A782A9 /* RACSubject.m in Sources */, - D037664F19EDA41200A782A9 /* UIStepper+RACSignalSupport.m in Sources */, - D03765D119EDA41200A782A9 /* RACSerialDisposable.m in Sources */, - EBCC7DBD1BBF01E100A2AE92 /* Observer.swift in Sources */, - D037663F19EDA41200A782A9 /* UIImagePickerController+RACSignalSupport.m in Sources */, - D037653F19EDA41200A782A9 /* NSString+RACSupport.m in Sources */, - D037653719EDA41200A782A9 /* NSString+RACKeyPathUtilities.m in Sources */, - D03764FB19EDA41200A782A9 /* NSDictionary+RACSequenceAdditions.m in Sources */, - D037656919EDA41200A782A9 /* RACCompoundDisposableProvider.d in Sources */, - D037653B19EDA41200A782A9 /* NSString+RACSequenceAdditions.m in Sources */, - D037661F19EDA41200A782A9 /* UIAlertView+RACSignalSupport.m in Sources */, - D03765E919EDA41200A782A9 /* RACStringSequence.m in Sources */, - D037660B19EDA41200A782A9 /* RACTupleSequence.m in Sources */, - D03765D519EDA41200A782A9 /* RACSignal.m in Sources */, - D037663319EDA41200A782A9 /* UIControl+RACSignalSupportPrivate.m in Sources */, + A9EB3D201E94F08A002A9BCC /* UINavigationItem.swift in Sources */, + 4ABEFE251DCFCF630066A8C2 /* UICollectionView.swift in Sources */, + 9A1D06011D93EA0000ACF44C /* UIBarItem.swift in Sources */, + A9EB3D811E955602002A9BCC /* UIFeedbackGenerator.swift in Sources */, + 9A1D060D1D93EA0000ACF44C /* UITextField.swift in Sources */, + 9A9037501ED61C6300345D62 /* ReactiveSwift+Lifetime.swift in Sources */, + 9AB15C7B1E26CD9A00997378 /* Deprecations+Removals.swift in Sources */, + 9A6AAA231DB8F51C0013AAEA /* UIKitReusableComponents.swift in Sources */, + 9A7488491E3B8ACE00CD0317 /* DelegateProxy.swift in Sources */, + 9A1D060E1D93EA0000ACF44C /* UITextView.swift in Sources */, + BFCF775F1DFAD8A50058006E /* UISearchBar.swift in Sources */, + 9A1D06061D93EA0000ACF44C /* UIImageView.swift in Sources */, + 9AF0EA761D9A7FF700F27DDF /* NSObject+BindingTarget.swift in Sources */, CD0C45DF1CC9A288009F5BF0 /* DynamicProperty.swift in Sources */, - D037664319EDA41200A782A9 /* UIRefreshControl+RACCommandSupport.m in Sources */, - D037651B19EDA41200A782A9 /* NSObject+RACDescription.m in Sources */, - D03765A519EDA41200A782A9 /* RACMulticastConnection.m in Sources */, - D85C652B1C0E70E3005A77AD /* Flatten.swift in Sources */, - D037654F19EDA41200A782A9 /* RACArraySequence.m in Sources */, - D037652319EDA41200A782A9 /* NSObject+RACLifting.m in Sources */, - C7142DBD1CDEA194009F402D /* CocoaAction.swift in Sources */, - D037650719EDA41200A782A9 /* NSIndexSet+RACSequenceAdditions.m in Sources */, - D037665F19EDA41200A782A9 /* UITextField+RACSignalSupport.m in Sources */, - D037650F19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m in Sources */, - D03765FB19EDA41200A782A9 /* RACSubscriptionScheduler.m in Sources */, - 4A0E11001D2A92720065D310 /* Lifetime.swift in Sources */, - D037658119EDA41200A782A9 /* RACEmptySequence.m in Sources */, - D0C312E019EF2A5800984962 /* ObjectiveCBridging.swift in Sources */, - D037654B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m in Sources */, - D037660F19EDA41200A782A9 /* RACUnarySequence.m in Sources */, - D08C54BB1A69C54400AD8286 /* Property.swift in Sources */, - D03765FF19EDA41200A782A9 /* RACTargetQueueScheduler.m in Sources */, - D03765DF19EDA41200A782A9 /* RACSignalSequence.m in Sources */, - D037656D19EDA41200A782A9 /* RACDelegateProxy.m in Sources */, - D03B4A3E19F4C39A009E02AC /* FoundationExtensions.swift in Sources */, - D037657519EDA41200A782A9 /* RACDynamicSequence.m in Sources */, - D037657119EDA41200A782A9 /* RACDisposable.m in Sources */, - D000040A1A46864E000E7D41 /* TupleExtensions.swift in Sources */, - D03765DB19EDA41200A782A9 /* RACSignalProvider.d in Sources */, - D037653319EDA41200A782A9 /* NSSet+RACSequenceAdditions.m in Sources */, - D037665319EDA41200A782A9 /* UISwitch+RACSignalSupport.m in Sources */, - D037664B19EDA41200A782A9 /* UISlider+RACSignalSupport.m in Sources */, - D037656719EDA41200A782A9 /* RACCompoundDisposable.m in Sources */, - D037655B19EDA41200A782A9 /* RACBlockTrampoline.m in Sources */, - D037659119EDA41200A782A9 /* RACGroupedSignal.m in Sources */, - D037655F19EDA41200A782A9 /* RACChannel.m in Sources */, - D037657D19EDA41200A782A9 /* RACEagerSequence.m in Sources */, - D037657919EDA41200A782A9 /* RACDynamicSignal.m in Sources */, - D037659519EDA41200A782A9 /* RACImmediateScheduler.m in Sources */, - D037651719EDA41200A782A9 /* NSObject+RACDeallocating.m in Sources */, - D037658519EDA41200A782A9 /* RACEmptySignal.m in Sources */, - D037663719EDA41200A782A9 /* UIDatePicker+RACSignalSupport.m in Sources */, - D08C54B71A69A3DB00AD8286 /* Event.swift in Sources */, - D037654719EDA41200A782A9 /* NSURLConnection+RACSupport.m in Sources */, - D03765F119EDA41200A782A9 /* RACSubscriber.m in Sources */, - D03764F719EDA41200A782A9 /* NSData+RACSupport.m in Sources */, - C79B647D1CD52E4A003F2376 /* EventLogger.swift in Sources */, - D0C312CE19EF2A5800984962 /* Atomic.swift in Sources */, - D0C312E819EF2A5800984962 /* Scheduler.swift in Sources */, - D037656319EDA41200A782A9 /* RACCommand.m in Sources */, - D037658919EDA41200A782A9 /* RACErrorSignal.m in Sources */, - D03765F719EDA41200A782A9 /* RACSubscriptingAssignmentTrampoline.m in Sources */, - D037661319EDA41200A782A9 /* RACUnit.m in Sources */, - D037662319EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m in Sources */, - D03765A119EDA41200A782A9 /* RACKVOTrampoline.m in Sources */, - D037665B19EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.m in Sources */, - 9A694EF41D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */, - D0C312D019EF2A5800984962 /* Bag.swift in Sources */, - D0D11ABA1A6AE87700C1F8B1 /* Action.swift in Sources */, - D037650B19EDA41200A782A9 /* NSInvocation+RACTypeParsing.m in Sources */, - D037660719EDA41200A782A9 /* RACTuple.m in Sources */, - D037667019EDA57100A782A9 /* EXTRuntimeExtensions.m in Sources */, - D037651F19EDA41200A782A9 /* NSObject+RACKVOWrapper.m in Sources */, - D037661719EDA41200A782A9 /* RACValueTransformer.m in Sources */, - D03765CD19EDA41200A782A9 /* RACSequence.m in Sources */, - 314304181ACA8B1E00595017 /* MKAnnotationView+RACSignalSupport.m in Sources */, + 9AE7C2A51DDD7F5100F7534C /* ObjC+Messages.swift in Sources */, + 9A2E425F1DAA6737006D909F /* CocoaTarget.swift in Sources */, + 9AAD49881DED2C350068EC9B /* UIKeyboard.swift in Sources */, + 53AC46CC1DD6F97400C799E1 /* UISlider.swift in Sources */, + 4EE637362090F92600ECD02A /* UIApplication.swift in Sources */, + 9AA0BD821DDE03F500531FCF /* ObjC+RuntimeSubclassing.swift in Sources */, + 9A1D060F1D93EA0000ACF44C /* UIView.swift in Sources */, + 9A1D06051D93EA0000ACF44C /* UIDatePicker.swift in Sources */, + A9D8BA71207CD7090031733D /* UIResponder.swift in Sources */, + 9A1D05E11D93E99100ACF44C /* NSObject+Association.swift in Sources */, + 9A1D06041D93EA0000ACF44C /* UIControl.swift in Sources */, + 9A1D06021D93EA0000ACF44C /* UIButton.swift in Sources */, + 4EE6372E2090EEFA00ECD02A /* UIViewController.swift in Sources */, + 9AA0BD901DDE29F800531FCF /* NSObject+ObjCRuntime.swift in Sources */, + 419139461DB910570043C9D1 /* UIGestureRecognizer.swift in Sources */, + 9AA0BD7D1DDE03DE00531FCF /* ObjC+Runtime.swift in Sources */, + BFE1458A1E439AB000208736 /* UIPickerView.swift in Sources */, + 9A1D060A1D93EA0000ACF44C /* UISwitch.swift in Sources */, + 9A1D065C1D93EC6E00ACF44C /* NSObject+Intercepting.swift in Sources */, + 84ADBDB026660EE800ACE342 /* UINotificationFeedbackGenerator.swift in Sources */, + 9A1D06071D93EA0000ACF44C /* UILabel.swift in Sources */, + BF4335651E02AC7600AC88DD /* UIScrollView.swift in Sources */, + 4A0E11001D2A92720065D310 /* AnyObject+Lifetime.swift in Sources */, + 9ADE4A921DA6EA40005C2AC8 /* NSObject+ReactiveExtensionsProvider.swift in Sources */, + 9ADFE5A61DC0001C001E11F7 /* Synchronizing.swift in Sources */, 9AD0F06B1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */, - D037652F19EDA41200A782A9 /* NSOrderedSet+RACSequenceAdditions.m in Sources */, - D037662719EDA41200A782A9 /* UIButton+RACCommandSupport.m in Sources */, - D037652719EDA41200A782A9 /* NSObject+RACPropertySubscribing.m in Sources */, - 7A7065821A3F88B8001E8354 /* RACKVOProxy.m in Sources */, - D037658D19EDA41200A782A9 /* RACEvent.m in Sources */, - D03765B319EDA41200A782A9 /* RACQueueScheduler.m in Sources */, - D037665719EDA41200A782A9 /* UITableViewCell+RACSignalSupport.m in Sources */, - D037652B19EDA41200A782A9 /* NSObject+RACSelectorSignal.m in Sources */, - D03765AF19EDA41200A782A9 /* RACPassthroughSubscriber.m in Sources */, - D03765BD19EDA41200A782A9 /* RACReturnSignal.m in Sources */, + 4ABEFE1F1DCFCEF60066A8C2 /* UITableView.swift in Sources */, + 9A1D06081D93EA0000ACF44C /* UIProgressView.swift in Sources */, + 531866F81DD7920400D1285F /* UIStepper.swift in Sources */, + A9EB3D2C1E94F5A2002A9BCC /* UITabBarItem.swift in Sources */, + 538DCB7A1DCA5E6C00332880 /* NSLayoutConstraint.swift in Sources */, + 84ADBDB126660EE800ACE342 /* UISelectionFeedbackGenerator.swift in Sources */, + 9A1D06001D93EA0000ACF44C /* UIBarButtonItem.swift in Sources */, + 9AA0BD991DDE7A2200531FCF /* ObjCRuntimeAliases.m in Sources */, + 9A1D05FF1D93EA0000ACF44C /* UIActivityIndicatorView.swift in Sources */, + 84ADBDAB26660EA800ACE342 /* UIImpactFeedbackGenerator.swift in Sources */, + 9A54A21C1DE00D09001739B3 /* ObjC+Selector.swift in Sources */, + 9AA0BD8B1DDE153A00531FCF /* ObjC+Constants.swift in Sources */, + 9A1D06091D93EA0000ACF44C /* UISegmentedControl.swift in Sources */, + 9ADE4A7D1DA44A9E005C2AC8 /* CocoaAction.swift in Sources */, + 3BCAAC7A1DEE19BC00B30335 /* UIRefreshControl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2911,72 +2254,54 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D0A2260C1A72E6C500D33B74 /* SignalProducerSpec.swift in Sources */, - D03766C819EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m in Sources */, - D037672419EDA60000A782A9 /* UIImagePickerControllerRACSupportSpec.m in Sources */, - D03766E419EDA60000A782A9 /* RACDelegateProxySpec.m in Sources */, - D0A2260F1A72F16D00D33B74 /* PropertySpec.swift in Sources */, - D03766FA19EDA60000A782A9 /* RACSerialDisposableSpec.m in Sources */, - D0A226091A72E0E900D33B74 /* SignalSpec.swift in Sources */, - CDCD247B1C277EED00710AEE /* AtomicSpec.swift in Sources */, - D037670C19EDA60000A782A9 /* RACTargetQueueSchedulerSpec.m in Sources */, - D03766DE19EDA60000A782A9 /* RACCommandSpec.m in Sources */, - D037670A19EDA60000A782A9 /* RACSubscriptingAssignmentTrampolineSpec.m in Sources */, - D03766EC19EDA60000A782A9 /* RACKVOWrapperSpec.m in Sources */, - D021671E1A6CD50500987861 /* ActionSpec.swift in Sources */, - D03766E819EDA60000A782A9 /* RACEventSpec.m in Sources */, - D03766F819EDA60000A782A9 /* RACSequenceSpec.m in Sources */, - D0A226121A72F30B00D33B74 /* ObjectiveCBridgingSpec.swift in Sources */, - D037671E19EDA60000A782A9 /* UIBarButtonItemRACSupportSpec.m in Sources */, - D8024DB31B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift in Sources */, + A9EB3D211E94F0AF002A9BCC /* UINavigationItemSpec.swift in Sources */, + 9A1D06581D93EA7E00ACF44C /* UIViewSpec.swift in Sources */, + 9AA6A1E61F11F9B100CA2257 /* KeyValueObservingSpec+Swift4.swift in Sources */, + 9AAD498A1DED2F380068EC9B /* UIKeyboardSpec.swift in Sources */, + BFCF77621DFAD9440058006E /* UISearchBarSpec.swift in Sources */, + D0A2260F1A72F16D00D33B74 /* DynamicPropertySpec.swift in Sources */, BFA6B94E1A7604D500C846D1 /* SignalProducerNimbleMatchers.swift in Sources */, - D03766CA19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m in Sources */, - D0C3132319EF2D9700984962 /* RACTestSchedulerSpec.m in Sources */, - D03766C419EDA60000A782A9 /* NSObjectRACDeallocatingSpec.m in Sources */, - D03766BE19EDA60000A782A9 /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */, - D037672019EDA60000A782A9 /* UIButtonRACSupportSpec.m in Sources */, - D0C3132519EF2D9700984962 /* RACTestUIButton.m in Sources */, - D037670219EDA60000A782A9 /* RACSubclassObject.m in Sources */, - D03766CE19EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m in Sources */, - D037671619EDA60000A782A9 /* RACTupleSpec.m in Sources */, - 7A7065851A3F8967001E8354 /* RACKVOProxySpec.m in Sources */, - D03766C619EDA60000A782A9 /* NSObjectRACLiftingSpec.m in Sources */, B696FB821A7640C00075236D /* TestError.swift in Sources */, - D0C3131F19EF2D9700984962 /* RACTestExampleScheduler.m in Sources */, - D8170FC21B100EBC004192AD /* FoundationExtensionsSpec.swift in Sources */, - D03766D219EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m in Sources */, - D03766F419EDA60000A782A9 /* RACSequenceAdditionsSpec.m in Sources */, - D0C3131419EF2B2000984962 /* SchedulerSpec.swift in Sources */, - C79B64751CD38B2B003F2376 /* TestLogger.swift in Sources */, - D0C3131219EF2B2000984962 /* DisposableSpec.swift in Sources */, - D03766EE19EDA60000A782A9 /* RACMulticastConnectionSpec.m in Sources */, - D03766EA19EDA60000A782A9 /* RACKVOChannelSpec.m in Sources */, - CA6F28511C52626B001879D2 /* FlattenSpec.swift in Sources */, - D0C3132119EF2D9700984962 /* RACTestObject.m in Sources */, - D03766FC19EDA60000A782A9 /* RACSignalSpec.m in Sources */, - D037670819EDA60000A782A9 /* RACSubscriberSpec.m in Sources */, - D037671C19EDA60000A782A9 /* UIAlertViewRACSupportSpec.m in Sources */, - D03766F019EDA60000A782A9 /* RACPropertySignalExamples.m in Sources */, - D037670619EDA60000A782A9 /* RACSubscriberExamples.m in Sources */, - D03766D819EDA60000A782A9 /* RACBlockTrampolineSpec.m in Sources */, - D037670019EDA60000A782A9 /* RACStreamExamples.m in Sources */, - D03766CC19EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m in Sources */, + BFBD68451E48DBD3003CB580 /* UIPickerViewSpec.swift in Sources */, + 9A1D064C1D93EA7E00ACF44C /* UISwitchSpec.swift in Sources */, + 4ABEFE281DCFCFA90066A8C2 /* UICollectionViewSpec.swift in Sources */, + 9A1D06441D93EA7E00ACF44C /* UIImageViewSpec.swift in Sources */, + 9A1D06541D93EA7E00ACF44C /* UITextViewSpec.swift in Sources */, + 9A1D063A1D93EA7E00ACF44C /* UIButtonSpec.swift in Sources */, + 9A1D064A1D93EA7E00ACF44C /* UISegmentedControlSpec.swift in Sources */, + 9A24A8461DE142A500987AF9 /* SwizzlingSpec.swift in Sources */, + 9A1D063E1D93EA7E00ACF44C /* UIControl+EnableSendActionsForControlEvents.swift in Sources */, + 4191394E1DBA01A00043C9D1 /* UIGestureRecognizerSpec.swift in Sources */, + 9A892D901E8D19BE00EA35F3 /* DelegateProxySpec.swift in Sources */, + 531866FA1DD7925600D1285F /* UIStepperSpec.swift in Sources */, + 9A1D06361D93EA7E00ACF44C /* UIActivityIndicatorViewSpec.swift in Sources */, + CD42C69C1E951F6A00AA9504 /* ReactiveCocoaTestsConfiguration.swift in Sources */, + 9A1D06461D93EA7E00ACF44C /* UILabelSpec.swift in Sources */, + 3B30EE8D1E7BE529007CC8EF /* DeprecationsSpec.swift in Sources */, + 9A9DFEEA1DA7EFB60039EE1B /* AssociationSpec.swift in Sources */, + 9A54A2121DDF5B4D001739B3 /* InterceptingPerformanceTests.swift in Sources */, + A9D8BA74207CD8430031733D /* UIResponderSpec.swift in Sources */, + 9ADFE5A21DBFFBCF001E11F7 /* LifetimeSpec.swift in Sources */, + 9A1D06421D93EA7E00ACF44C /* UIDatePickerSpec.swift in Sources */, + 53AC46CF1DD6FC0000C799E1 /* UISliderSpec.swift in Sources */, + 9A6AAA0F1DB6A4CF0013AAEA /* InterceptingSpec.swift in Sources */, + 9AFCBFE41EB1ABC0004B4C74 /* KVOKVCExtensionSpec.swift in Sources */, + BF4335681E02EF0600AC88DD /* UIScrollViewSpec.swift in Sources */, + 3BCAAC7D1DEE1A2D00B30335 /* UIRefreshControlSpec.swift in Sources */, + 9A1D06381D93EA7E00ACF44C /* UIBarButtonItemSpec.swift in Sources */, + 9AD841DD204C29B90040F0C0 /* MessageForwardingEntity.m in Sources */, 9A1E72BB1D4DE96500CC20C3 /* KeyValueObservingSpec.swift in Sources */, - D03766E219EDA60000A782A9 /* RACControlCommandExamples.m in Sources */, - D03766C019EDA60000A782A9 /* NSNotificationCenterRACSupportSpec.m in Sources */, - D037670419EDA60000A782A9 /* RACSubjectSpec.m in Sources */, - D03766F219EDA60000A782A9 /* RACSchedulerSpec.m in Sources */, - D03766E019EDA60000A782A9 /* RACCompoundDisposableSpec.m in Sources */, - D03766E619EDA60000A782A9 /* RACDisposableSpec.m in Sources */, - D03766D419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m in Sources */, - D03766DC19EDA60000A782A9 /* RACChannelSpec.m in Sources */, - 579504341BB8A34300A5E482 /* BagSpec.swift in Sources */, - D037671A19EDA60000A782A9 /* UIActionSheetRACSupportSpec.m in Sources */, - D03766DA19EDA60000A782A9 /* RACChannelExamples.m in Sources */, - D03766F619EDA60000A782A9 /* RACSequenceExamples.m in Sources */, + 9A6AAA2E1DB903A20013AAEA /* UIKitReusableComponentsSpec.swift in Sources */, + 4ABEFE211DCFCF090066A8C2 /* UITableViewSpec.swift in Sources */, + 9A1D06401D93EA7E00ACF44C /* UIControlSpec.swift in Sources */, + 9A7990CF1F1085D8001493A3 /* BindingTargetSpec.swift in Sources */, + 9A1D06481D93EA7E00ACF44C /* UIProgressViewSpec.swift in Sources */, CD8401841CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */, - 4A0E11051D2A95200065D310 /* LifetimeSpec.swift in Sources */, - 02D2602A1C1D6DAF003ACC61 /* SignalLifetimeSpec.swift in Sources */, + A9EB3D291E94F3D3002A9BCC /* UITabBarItemSpec.swift in Sources */, + 538DCB7E1DCA5E9B00332880 /* NSLayoutConstraintSpec.swift in Sources */, + 9A1D06521D93EA7E00ACF44C /* UITextFieldSpec.swift in Sources */, + 5B76DE412498EDDC00E8B4F3 /* QueueScheduler+Factory.swift in Sources */, + 4EE637342090EFDF00ECD02A /* UIViewControllerSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2988,9 +2313,24 @@ target = 57A4D1AF1BA13D7A00F7D4B1 /* ReactiveCocoa-tvOS */; targetProxy = 7DFBED091CDB8C9500EE435B /* PBXContainerItemProxy */; }; + 9A73DFE0216D3CEB0069AD76 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AC03A561F7CC3BF00EC33C1 /* ReactiveMapKit-macOS */; + targetProxy = 9A73DFE1216D3CEB0069AD76 /* PBXContainerItemProxy */; + }; + 9A73E046216D404D0069AD76 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A73DFAC216D3C550069AD76 /* ReactiveMapKit-iOS */; + targetProxy = 9A73E045216D404D0069AD76 /* PBXContainerItemProxy */; + }; + 9A73E048216D40550069AD76 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A73DFBD216D3C570069AD76 /* ReactiveMapKit-tvOS */; + targetProxy = 9A73E047216D40550069AD76 /* PBXContainerItemProxy */; + }; D04725F819E49ED7006002AA /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = D04725E919E49ED7006002AA /* ReactiveCocoa-Mac */; + target = D04725E919E49ED7006002AA /* ReactiveCocoa-macOS */; targetProxy = D04725F719E49ED7006002AA /* PBXContainerItemProxy */; }; D047261919E49F82006002AA /* PBXTargetDependency */ = { @@ -3007,12 +2347,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Debug; }; @@ -3022,12 +2358,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Test; }; @@ -3037,12 +2369,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Release; }; @@ -3052,12 +2380,60 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + }; + name = Profile; + }; + 57C8854F2A47756D00FC133C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SDKROOT = xros; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Debug; + }; + 57C885502A47756D00FC133C /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SDKROOT = xros; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Test; + }; + 57C885512A47756D00FC133C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SDKROOT = xros; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Release; + }; + 57C885522A47756D00FC133C /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SDKROOT = xros; + TARGETED_DEVICE_FAMILY = 7; }; name = Profile; }; @@ -3065,14 +2441,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = ""; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Debug; }; @@ -3080,14 +2451,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = ""; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Test; }; @@ -3095,14 +2461,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = ""; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Release; }; @@ -3110,14 +2471,277 @@ isa = XCBuildConfiguration; baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = ""; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; + }; + name = Profile; + }; + 9A1675461F80C35100B63650 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Debug; + }; + 9A1675471F80C35100B63650 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Test; + }; + 9A1675481F80C35100B63650 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Release; + }; + 9A1675491F80C35100B63650 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Profile; + }; + 9A73DFB7216D3C550069AD76 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Debug; + }; + 9A73DFB8216D3C550069AD76 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Test; + }; + 9A73DFB9216D3C550069AD76 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Release; + }; + 9A73DFBA216D3C550069AD76 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Profile; + }; + 9A73DFC8216D3C570069AD76 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Debug; + }; + 9A73DFC9216D3C570069AD76 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Test; + }; + 9A73DFCA216D3C570069AD76 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Release; + }; + 9A73DFCB216D3C570069AD76 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + }; + name = Profile; + }; + 9A73DFF4216D3CEB0069AD76 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* macOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Debug; + }; + 9A73DFF5216D3CEB0069AD76 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* macOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Test; + }; + 9A73DFF6216D3CEB0069AD76 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* macOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Release; + }; + 9A73DFF7216D3CEB0069AD76 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* macOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Profile; + }; + 9A73E00F216D3CEE0069AD76 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Debug; + }; + 9A73E010216D3CEE0069AD76 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Test; + }; + 9A73E011216D3CEE0069AD76 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Release; + }; + 9A73E012216D3CEE0069AD76 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + INFOPLIST_FILE = ReactiveMapKitTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKitTests; + PRODUCT_NAME = ReactiveMapKitTests; + }; + name = Profile; + }; + 9AC03A5D1F7CC3BF00EC33C1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + SUPPORTS_MACCATALYST = YES; + }; + name = Debug; + }; + 9AC03A5E1F7CC3BF00EC33C1 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + SUPPORTS_MACCATALYST = YES; + }; + name = Test; + }; + 9AC03A5F1F7CC3BF00EC33C1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + SUPPORTS_MACCATALYST = YES; + }; + name = Release; + }; + 9AC03A601F7CC3BF00EC33C1 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + INFOPLIST_FILE = ReactiveMapKit/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = org.reactivecocoa.ReactiveMapKit; + PRODUCT_NAME = ReactiveMapKit; + SUPPORTS_MACCATALYST = YES; }; name = Profile; }; @@ -3127,12 +2751,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Debug; }; @@ -3142,12 +2762,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Test; }; @@ -3157,12 +2773,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Release; }; @@ -3172,12 +2784,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DTRACE_PROBES_DISABLED=1", - ); INFOPLIST_FILE = ReactiveCocoa/Info.plist; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Profile; }; @@ -3185,21 +2793,28 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047262919E49FE8006002AA /* Debug.xcconfig */; buildSettings = { + APPLETVOS_DEPLOYMENT_TARGET = 9.0; BITCODE_GENERATION_MODE = bitcode; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; ENABLE_TESTABILITY = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; - ONLY_ACTIVE_ARCH = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; - SWIFT_VERSION = 3.0; - TVOS_DEPLOYMENT_TARGET = 9.0; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Debug; }; @@ -3207,74 +2822,74 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047262B19E49FE8006002AA /* Release.xcconfig */; buildSettings = { + APPLETVOS_DEPLOYMENT_TARGET = 9.0; BITCODE_GENERATION_MODE = bitcode; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; - GCC_OPTIMIZATION_LEVEL = 0; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; - SWIFT_VERSION = 3.0; - TVOS_DEPLOYMENT_TARGET = 9.0; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Release; }; D047260119E49ED7006002AA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SUPPORTS_MACCATALYST = YES; }; name = Debug; }; D047260219E49ED7006002AA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SUPPORTS_MACCATALYST = YES; }; name = Release; }; D047260419E49ED7006002AA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + baseConfigurationReference = E6124BA8267DF3C0005A3490 /* macOS-XCTest.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Debug; }; D047260519E49ED7006002AA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + baseConfigurationReference = E6124BA8267DF3C0005A3490 /* macOS-XCTest.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Release; }; @@ -3284,10 +2899,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Debug; }; @@ -3297,10 +2910,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Release; }; @@ -3308,13 +2919,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Debug; }; @@ -3322,13 +2929,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Release; }; @@ -3336,46 +2939,50 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047262A19E49FE8006002AA /* Profile.xcconfig */; buildSettings = { + APPLETVOS_DEPLOYMENT_TARGET = 9.0; BITCODE_GENERATION_MODE = bitcode; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; - SWIFT_VERSION = 3.0; - TVOS_DEPLOYMENT_TARGET = 9.0; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Profile; }; D047263E19E4A008006002AA /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SUPPORTS_MACCATALYST = YES; }; name = Profile; }; D047263F19E4A008006002AA /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + baseConfigurationReference = E6124BA8267DF3C0005A3490 /* macOS-XCTest.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Profile; }; @@ -3385,10 +2992,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Profile; }; @@ -3396,13 +3001,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Profile; }; @@ -3410,46 +3011,50 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047262C19E49FE8006002AA /* Test.xcconfig */; buildSettings = { + APPLETVOS_DEPLOYMENT_TARGET = 9.0; BITCODE_GENERATION_MODE = bitcode; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)-Tests"; PRODUCT_NAME = "$(PROJECT_NAME)"; - SWIFT_VERSION = 3.0; - TVOS_DEPLOYMENT_TARGET = 9.0; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Test; }; D047264319E4A00B006002AA /* Test */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + baseConfigurationReference = D047263A19E49FE8006002AA /* macOS-Framework.xcconfig */; buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; + SUPPORTS_MACCATALYST = YES; }; name = Test; }; D047264419E4A00B006002AA /* Test */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + baseConfigurationReference = E6124BA8267DF3C0005A3490 /* macOS-XCTest.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Test; }; @@ -3459,10 +3064,8 @@ buildSettings = { DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; - ENABLE_BITCODE = YES; INFOPLIST_FILE = ReactiveCocoa/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - USER_HEADER_SEARCH_PATHS = ReactiveCocoa/extobjc; + MODULEMAP_FILE = "$(SRCROOT)/ReactiveCocoa/include/module.modulemap"; }; name = Test; }; @@ -3470,13 +3073,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = ReactiveCocoaTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h"; }; name = Test; }; @@ -3494,6 +3093,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 57C8854E2A47756D00FC133C /* Build configuration list for PBXNativeTarget "ReactiveCocoa-xrOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57C8854F2A47756D00FC133C /* Debug */, + 57C885502A47756D00FC133C /* Test */, + 57C885512A47756D00FC133C /* Release */, + 57C885522A47756D00FC133C /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 7DFBED0F1CDB8C9500EE435B /* Build configuration list for PBXNativeTarget "ReactiveCocoa-tvOSTests" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3505,6 +3115,72 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9A1675451F80C35100B63650 /* Build configuration list for PBXNativeTarget "ReactiveMapKitTests-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A1675461F80C35100B63650 /* Debug */, + 9A1675471F80C35100B63650 /* Test */, + 9A1675481F80C35100B63650 /* Release */, + 9A1675491F80C35100B63650 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9A73DFB6216D3C550069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKit-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A73DFB7216D3C550069AD76 /* Debug */, + 9A73DFB8216D3C550069AD76 /* Test */, + 9A73DFB9216D3C550069AD76 /* Release */, + 9A73DFBA216D3C550069AD76 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9A73DFC7216D3C570069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKit-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A73DFC8216D3C570069AD76 /* Debug */, + 9A73DFC9216D3C570069AD76 /* Test */, + 9A73DFCA216D3C570069AD76 /* Release */, + 9A73DFCB216D3C570069AD76 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9A73DFF3216D3CEB0069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKitTests-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A73DFF4216D3CEB0069AD76 /* Debug */, + 9A73DFF5216D3CEB0069AD76 /* Test */, + 9A73DFF6216D3CEB0069AD76 /* Release */, + 9A73DFF7216D3CEB0069AD76 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9A73E00E216D3CEE0069AD76 /* Build configuration list for PBXNativeTarget "ReactiveMapKitTests-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A73E00F216D3CEE0069AD76 /* Debug */, + 9A73E010216D3CEE0069AD76 /* Test */, + 9A73E011216D3CEE0069AD76 /* Release */, + 9A73E012216D3CEE0069AD76 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9AC03A5C1F7CC3BF00EC33C1 /* Build configuration list for PBXNativeTarget "ReactiveMapKit-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AC03A5D1F7CC3BF00EC33C1 /* Debug */, + 9AC03A5E1F7CC3BF00EC33C1 /* Test */, + 9AC03A5F1F7CC3BF00EC33C1 /* Release */, + 9AC03A601F7CC3BF00EC33C1 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A9B3155D1B3940610001CB9C /* Build configuration list for PBXNativeTarget "ReactiveCocoa-watchOS" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3527,7 +3203,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D047260019E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-Mac" */ = { + D047260019E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( D047260119E49ED7006002AA /* Debug */, @@ -3538,7 +3214,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D047260319E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-MacTests" */ = { + D047260319E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveCocoa-macOSTests" */ = { isa = XCConfigurationList; buildConfigurations = ( D047260419E49ED7006002AA /* Debug */, diff --git a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS.xcscheme index a7c0659a74..df877beeb8 100644 --- a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS.xcscheme +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-iOS.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "D047260B19E49F82006002AA" + BuildableName = "ReactiveSwift.framework" + BlueprintName = "ReactiveSwift-iOS" + ReferencedContainer = "container:Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj"> + + + + + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -94,19 +116,23 @@ BlueprintName = "ReactiveCocoa-iOSTests" ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-tvOS.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-tvOS.xcscheme index d32ed666ed..5b96afca11 100644 --- a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-tvOS.xcscheme +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-tvOS.xcscheme @@ -1,6 +1,6 @@ + BlueprintIdentifier = "57A4D1AF1BA13D7A00F7D4B1" + BuildableName = "ReactiveSwift.framework" + BlueprintName = "ReactiveSwift-tvOS" + ReferencedContainer = "container:Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj"> + + + + + + + + + shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-watchOS.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-watchOS.xcscheme index 7ccd9d0bec..528b16750b 100644 --- a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-watchOS.xcscheme +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-watchOS.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-iOS.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-iOS.xcscheme new file mode 100644 index 0000000000..1411e36d18 --- /dev/null +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-iOS.xcscheme @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-macOS.xcscheme similarity index 76% rename from ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme rename to ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-macOS.xcscheme index 25ac5b0747..55d35f5d5d 100644 --- a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-macOS.xcscheme @@ -1,39 +1,11 @@ - - - - - - - - @@ -58,21 +30,49 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "DAEB6B8D1943873100289F44" BuildableName = "Quick.framework" - BlueprintName = "Quick-OSX" + BlueprintName = "Quick-macOS" ReferencedContainer = "container:Carthage/Checkouts/Quick/Quick.xcodeproj"> + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + + + + + + + + @@ -82,16 +82,15 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -99,9 +98,9 @@ @@ -121,9 +120,9 @@ @@ -131,7 +130,7 @@ diff --git a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-tvOS.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-tvOS.xcscheme new file mode 100644 index 0000000000..2fc416d800 --- /dev/null +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveMapKit-tvOS.xcscheme @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveCocoa.xcworkspace/contents.xcworkspacedata b/ReactiveCocoa.xcworkspace/contents.xcworkspacedata index 9dc8faab27..21cd483433 100644 --- a/ReactiveCocoa.xcworkspace/contents.xcworkspacedata +++ b/ReactiveCocoa.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,13 @@ + location = "group:ReactiveCocoa-macOS.playground"> + + + + @@ -14,6 +20,6 @@ location = "group:Carthage/Checkouts/Nimble/Nimble.xcodeproj"> + location = "group:Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj"> diff --git a/ReactiveCocoa/AnyObject+Lifetime.swift b/ReactiveCocoa/AnyObject+Lifetime.swift new file mode 100644 index 0000000000..9e3142efff --- /dev/null +++ b/ReactiveCocoa/AnyObject+Lifetime.swift @@ -0,0 +1,128 @@ +import Foundation +import ReactiveSwift + +/// Holds the `Lifetime` of the object. +private let isSwizzledKey = AssociationKey(default: false) + +/// Holds the `Lifetime` of the object. +private let lifetimeKey = AssociationKey(default: nil) + +/// Holds the `Lifetime.Token` of the object. +private let lifetimeTokenKey = AssociationKey(default: nil) + +public extension Lifetime { + /// Retrive the associated lifetime of given object. + /// The lifetime ends when the given object is deinitialized. + /// + /// - parameters: + /// - object: The object for which the lifetime is obtained. + /// + /// - returns: The lifetime ends when the given object is deinitialized. + static func of(_ object: AnyObject) -> Lifetime { + if let object = object as? NSObject { + return .of(object) + } + + return synchronized(object) { + let associations = Associations(object) + + if let lifetime = associations.value(forKey: lifetimeKey) { + return lifetime + } + + let (lifetime, token) = Lifetime.make() + + associations.setValue(token, forKey: lifetimeTokenKey) + associations.setValue(lifetime, forKey: lifetimeKey) + + return lifetime + } + } + + /// Retrive the associated lifetime of given object. + /// The lifetime ends when the given object is deinitialized. + /// + /// - parameters: + /// - object: The object for which the lifetime is obtained. + /// + /// - returns: The lifetime ends when the given object is deinitialized. + static func of(_ object: NSObject) -> Lifetime { + return synchronized(object) { + if let lifetime = object.associations.value(forKey: lifetimeKey) { + return lifetime + } + + let (lifetime, token) = Lifetime.make() + + let objcClass: AnyClass = (object as AnyObject).objcClass + let objcClassAssociations = Associations(objcClass as AnyObject) + + #if swift(>=4.0) + let deallocSelector = sel_registerName("dealloc") + #else + let deallocSelector = sel_registerName("dealloc")! + #endif + + // Swizzle `-dealloc` so that the lifetime token is released at the + // beginning of the deallocation chain, and only after the KVO `-dealloc`. + synchronized(objcClass) { + // Swizzle the class only if it has not been swizzled before. + if !objcClassAssociations.value(forKey: isSwizzledKey) { + objcClassAssociations.setValue(true, forKey: isSwizzledKey) + + var existingImpl: IMP? = nil + + let newImplBlock: @convention(block) (UnsafeRawPointer) -> Void = { objectRef in + // A custom trampoline of `objc_setAssociatedObject` is used, since + // the imported version has been inserted with ARC calls that would + // mess with the object deallocation chain. + + // Release the lifetime token. + unsafeSetAssociatedValue(nil, forKey: lifetimeTokenKey, forObjectAt: objectRef) + + let impl: IMP + + // Call the existing implementation if one has been caught. Otherwise, + // call the one first available in the superclass hierarchy. + if let existingImpl = existingImpl { + impl = existingImpl + } else { + let superclass: AnyClass = class_getSuperclass(objcClass)! + impl = class_getMethodImplementation(superclass, deallocSelector)! + } + + typealias Impl = @convention(c) (UnsafeRawPointer, Selector) -> Void + unsafeBitCast(impl, to: Impl.self)(objectRef, deallocSelector) + } + + let newImpl = imp_implementationWithBlock(newImplBlock as Any) + + if !class_addMethod(objcClass, deallocSelector, newImpl, "v@:") { + // The class has an existing `dealloc`. Preserve that as `existingImpl`. + let deallocMethod = class_getInstanceMethod(objcClass, deallocSelector)! + + // Store the existing implementation to `existingImpl` to ensure it is + // available before our version is swapped in. + existingImpl = method_getImplementation(deallocMethod) + + // Store the swapped-out implementation to `existingImpl` in case + // the implementation has been changed concurrently. + existingImpl = method_setImplementation(deallocMethod, newImpl) + } + } + } + + object.associations.setValue(token, forKey: lifetimeTokenKey) + object.associations.setValue(lifetime, forKey: lifetimeKey) + + return lifetime + } + } +} + +extension Reactive where Base: AnyObject { + /// Returns a lifetime that ends when the object is deallocated. + @nonobjc public var lifetime: Lifetime { + return .of(base) + } +} diff --git a/ReactiveCocoa/AppKit/ActionProxy.swift b/ReactiveCocoa/AppKit/ActionProxy.swift new file mode 100644 index 0000000000..55f2091f57 --- /dev/null +++ b/ReactiveCocoa/AppKit/ActionProxy.swift @@ -0,0 +1,95 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +internal final class ActionProxy: NSObject { + internal weak var target: AnyObject? + internal var action: Selector? + internal let invoked: Signal + + private let observer: Signal.Observer + private unowned let owner: Owner + + internal init(owner: Owner, lifetime: Lifetime) { + self.owner = owner + (invoked, observer) = Signal.pipe() + lifetime.ended.observeCompleted(observer.sendCompleted) + } + + // In AppKit, action messages always have only one parameter. + @objc func invoke(_ sender: Any?) { + if let action = action { + if let app = NSApp { + app.sendAction(action, to: target, from: sender) + } else { + _ = target?.perform(action, with: sender) + } + } + + observer.send(value: owner) + } +} + +private let hasSwizzledKey = AssociationKey(default: false) + +@objc internal protocol ActionMessageSending: AnyObject { + weak var target: AnyObject? { get set } + var action: Selector? { get set } +} + +extension Reactive where Base: NSObject, Base: ActionMessageSending { + internal var proxy: ActionProxy { + let key = AssociationKey?>((#function as StaticString)) + + return synchronized(base) { + if let proxy = base.associations.value(forKey: key) { + return proxy + } + + let superclass: AnyClass = class_getSuperclass(swizzleClass(base))! + + let proxy = ActionProxy(owner: base, lifetime: lifetime) + proxy.target = base.target + proxy.action = base.action + + // Set the proxy as the new delegate with all dynamic interception bypassed + // by directly invoking setters in the original class. + typealias TargetSetter = @convention(c) (NSObject, Selector, AnyObject?) -> Void + typealias ActionSetter = @convention(c) (NSObject, Selector, Selector?) -> Void + + let setTargetImpl = class_getMethodImplementation(superclass, #selector(setter: base.target)) + unsafeBitCast(setTargetImpl, to: TargetSetter.self)(base, #selector(setter: base.target), proxy) + + let setActionImpl = class_getMethodImplementation(superclass, #selector(setter: base.action)) + unsafeBitCast(setActionImpl, to: ActionSetter.self)(base, #selector(setter: base.action), #selector(proxy.invoke(_:))) + + base.associations.setValue(proxy, forKey: key) + + let newTargetSetterImpl: @convention(block) (NSObject, AnyObject?) -> Void = { object, target in + if let proxy = object.associations.value(forKey: key) { + proxy.target = target + } else { + let impl = class_getMethodImplementation(superclass, #selector(setter: self.base.target)) + unsafeBitCast(impl, to: TargetSetter.self)(object, #selector(setter: self.base.target), target) + } + } + + let newActionSetterImpl: @convention(block) (NSObject, Selector?) -> Void = { object, action in + if let proxy = object.associations.value(forKey: key) { + proxy.action = action + } else { + let impl = class_getMethodImplementation(superclass, #selector(setter: self.base.action)) + unsafeBitCast(impl, to: ActionSetter.self)(object, #selector(setter: self.base.action), action) + } + } + + // Swizzle the instance only after setting up the proxy. + base.swizzle((#selector(setter: base.target), newTargetSetterImpl), + (#selector(setter: base.action), newActionSetterImpl), + key: hasSwizzledKey) + + return proxy + } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/AppKitReusableComponents.swift b/ReactiveCocoa/AppKit/AppKitReusableComponents.swift new file mode 100644 index 0000000000..d2ff43f50d --- /dev/null +++ b/ReactiveCocoa/AppKit/AppKitReusableComponents.swift @@ -0,0 +1,16 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSView { + public var prepareForReuse: Signal<(), Never> { + return trigger(for: #selector(base.prepareForReuse)) + } +} + +extension Reactive where Base: NSObject, Base: NSCollectionViewElement { + public var prepareForReuse: Signal<(), Never> { + return trigger(for: #selector(base.prepareForReuse)) + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSButton.swift b/ReactiveCocoa/AppKit/NSButton.swift new file mode 100644 index 0000000000..701d5c5d4d --- /dev/null +++ b/ReactiveCocoa/AppKit/NSButton.swift @@ -0,0 +1,57 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSButton { + + internal var associatedAction: Atomic<(action: CocoaAction, disposable: CompositeDisposable)?> { + return associatedValue { _ in Atomic(nil) } + } + + public var pressed: CocoaAction? { + get { + return associatedAction.value?.action + } + + nonmutating set { + associatedAction + .modify { action in + action?.disposable.dispose() + action = newValue.map { action in + let disposable = CompositeDisposable() + disposable += isEnabled <~ action.isEnabled + disposable += proxy.invoked.observeValues(action.execute) + return (action, disposable) + } + } + } + } + + #if swift(>=4.0) + /// A signal of integer states (On, Off, Mixed), emitted by the button. + public var states: Signal { + return proxy.invoked.map { $0.state } + } + + /// Sets the button's state + public var state: BindingTarget { + return makeBindingTarget { $0.state = $1 } + } + #else + /// A signal of integer states (On, Off, Mixed), emitted by the button. + public var states: Signal { + return proxy.invoked.map { $0.state } + } + + /// Sets the button's state + public var state: BindingTarget { + return makeBindingTarget { $0.state = $1 } + } + #endif + + /// Sets the button's image + public var image: BindingTarget { + return makeBindingTarget { $0.image = $1 } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSCollectionView.swift b/ReactiveCocoa/AppKit/NSCollectionView.swift new file mode 100644 index 0000000000..c10619beab --- /dev/null +++ b/ReactiveCocoa/AppKit/NSCollectionView.swift @@ -0,0 +1,10 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSCollectionView { + public var reloadData: BindingTarget<()> { + return makeBindingTarget { base, _ in base.reloadData() } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSControl.swift b/ReactiveCocoa/AppKit/NSControl.swift new file mode 100644 index 0000000000..65e3fff521 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSControl.swift @@ -0,0 +1,101 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension NSControl: ActionMessageSending {} + +extension Reactive where Base: NSControl { + /// Sets whether the control is enabled. + public var isEnabled: BindingTarget { + return makeBindingTarget { $0.isEnabled = $1 } + } + + /// Sets the value of the control with an `NSAttributedString`. + public var attributedStringValue: BindingTarget { + return makeBindingTarget { $0.attributedStringValue = $1 } + } + + /// A signal of values in `NSAttributedString`, emitted by the control. + public var attributedStringValues: Signal { + return proxy.invoked.map { $0.attributedStringValue } + } + + /// Sets the value of the control with a `Bool`. + public var boolValue: BindingTarget { + #if swift(>=4.0) + return makeBindingTarget { $0.integerValue = $1 ? NSControl.StateValue.on.rawValue : NSControl.StateValue.off.rawValue } + #else + return makeBindingTarget { $0.integerValue = $1 ? NSOnState : NSOffState } + #endif + } + + /// A signal of values in `Bool`, emitted by the control. + public var boolValues: Signal { + #if swift(>=4.0) + return proxy.invoked.map { $0.integerValue != NSControl.StateValue.off.rawValue } + #else + return proxy.invoked.map { $0.integerValue != NSOffState } + #endif + } + + /// Sets the value of the control with a `Double`. + public var doubleValue: BindingTarget { + return makeBindingTarget { $0.doubleValue = $1 } + } + + /// A signal of values in `Double`, emitted by the control. + public var doubleValues: Signal { + return proxy.invoked.map { $0.doubleValue } + } + + /// Sets the value of the control with a `Float`. + public var floatValue: BindingTarget { + return makeBindingTarget { $0.floatValue = $1 } + } + + /// A signal of values in `Float`, emitted by the control. + public var floatValues: Signal { + return proxy.invoked.map { $0.floatValue } + } + + /// Sets the value of the control with an `Int32`. + public var intValue: BindingTarget { + return makeBindingTarget { $0.intValue = $1 } + } + + /// A signal of values in `Int32`, emitted by the control. + public var intValues: Signal { + return proxy.invoked.map { $0.intValue } + } + + /// Sets the value of the control with an `Int`. + public var integerValue: BindingTarget { + return makeBindingTarget { $0.integerValue = $1 } + } + + /// A signal of values in `Int`, emitted by the control. + public var integerValues: Signal { + return proxy.invoked.map { $0.integerValue } + } + + /// Sets the value of the control. + public var objectValue: BindingTarget { + return makeBindingTarget { $0.objectValue = $1 } + } + + /// A signal of values in `Any?`, emitted by the control. + public var objectValues: Signal { + return proxy.invoked.map { $0.objectValue } + } + + /// Sets the value of the control with a `String`. + public var stringValue: BindingTarget { + return makeBindingTarget { $0.stringValue = $1 } + } + + /// A signal of values in `String`, emitted by the control. + public var stringValues: Signal { + return proxy.invoked.map { $0.stringValue } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSImageView.swift b/ReactiveCocoa/AppKit/NSImageView.swift new file mode 100644 index 0000000000..a3b24cd513 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSImageView.swift @@ -0,0 +1,11 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSImageView { + /// Sets the currently displayed image + public var image: BindingTarget { + return makeBindingTarget { $0.image = $1 } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSPopUpButton.swift b/ReactiveCocoa/AppKit/NSPopUpButton.swift new file mode 100644 index 0000000000..9e504028e0 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSPopUpButton.swift @@ -0,0 +1,49 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSPopUpButton { + + /// A signal of selected indexes + public var selectedIndexes: Signal { + return proxy.invoked.map { $0.indexOfSelectedItem } + } + + /// Sets the button with an index. + public var selectedIndex: BindingTarget { + return makeBindingTarget { + $0.selectItem(at: $1 ?? -1) + } + } + + /// A signal of selected title + public var selectedTitles: Signal { + return proxy.invoked.map { $0.titleOfSelectedItem }.skipNil() + } + + /// Sets the button with title. + /// note: emitting nil to this target will set selectedTitle to empty string + public var selectedTitle: BindingTarget { + return makeBindingTarget { + $0.selectItem(withTitle: $1 ?? "") + } + } + + public var selectedItems: Signal { + return proxy.invoked.map { $0.selectedItem }.skipNil() + } + + + /// A signal of selected tags + public var selectedTags: Signal { + return proxy.invoked.map { $0.selectedTag() } + } + + /// Sets the selected tag + public var selectedTag: BindingTarget { + return makeBindingTarget { + $0.selectItem(withTag: $1) + } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSSegmentedControl.swift b/ReactiveCocoa/AppKit/NSSegmentedControl.swift new file mode 100644 index 0000000000..9ed515ec67 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSSegmentedControl.swift @@ -0,0 +1,23 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSSegmentedControl { + /// Changes the selected segment of the segmented control. + public var selectedSegment: BindingTarget { + return makeBindingTarget { $0.selectedSegment = $1 } + } + + /// A signal of indexes of selections emitted by the segmented control. + public var selectedSegments: Signal { + return proxy.invoked.map { $0.selectedSegment } + } + + /// The below are provided for cross-platform compatibility + + /// Changes the selected segment of the segmented control. + public var selectedSegmentIndex: BindingTarget { return selectedSegment } + /// A signal of indexes of selections emitted by the segmented control. + public var selectedSegmentIndexes: Signal { return selectedSegments } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSSlider.swift b/ReactiveCocoa/AppKit/NSSlider.swift new file mode 100644 index 0000000000..110181ce78 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSSlider.swift @@ -0,0 +1,12 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSSlider { + + // Provided for cross-platform compatibility + + public var value: BindingTarget { return floatValue } + public var values: Signal { return floatValues } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSTableView.swift b/ReactiveCocoa/AppKit/NSTableView.swift new file mode 100644 index 0000000000..be0691675e --- /dev/null +++ b/ReactiveCocoa/AppKit/NSTableView.swift @@ -0,0 +1,10 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSTableView { + public var reloadData: BindingTarget<()> { + return makeBindingTarget { base, _ in base.reloadData() } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSTextField.swift b/ReactiveCocoa/AppKit/NSTextField.swift new file mode 100644 index 0000000000..e94a16a337 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSTextField.swift @@ -0,0 +1,49 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSTextField { + private var notifications: Signal { + #if swift(>=4.0) + let name = NSControl.textDidChangeNotification + #else + let name = Notification.Name.NSControlTextDidChange + #endif + + return NotificationCenter.default + .reactive + .notifications(forName: name, object: base) + .take(during: lifetime) + } + + /// A signal of values in `String` from the text field upon any changes. + public var continuousStringValues: Signal { + return notifications + .map { ($0.object as! NSTextField).stringValue } + } + + /// A signal of values in `NSAttributedString` from the text field upon any + /// changes. + public var continuousAttributedStringValues: Signal { + return notifications + .map { ($0.object as! NSTextField).attributedStringValue } + } + + /// Wraps the `stringValue` binding target from NSControl for + /// cross-platform compatibility + public var text: BindingTarget { + return stringValue + } + + /// Wraps the `stringValue` binding target from NSControl for + /// cross-platform compatibility + public var attributedText: BindingTarget { + return attributedStringValue + } + + /// Sets the color of the text with an `NSColor`. + public var textColor: BindingTarget { + return makeBindingTarget { $0.textColor = $1 } + } +} +#endif diff --git a/ReactiveCocoa/AppKit/NSTextView.swift b/ReactiveCocoa/AppKit/NSTextView.swift new file mode 100644 index 0000000000..b54ea5ca14 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSTextView.swift @@ -0,0 +1,40 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSTextView { + private var notifications: Signal { + #if swift(>=4.0) + let name = NSTextView.didChangeNotification + #else + let name = Notification.Name.NSControlTextDidChange + #endif + + return NotificationCenter.default + .reactive + .notifications(forName: name, object: base) + .take(during: lifetime) + } + + /// A signal of values in `String` from the text field upon any changes. + public var continuousStringValues: Signal { + return notifications + .map { notification in + let textView = notification.object as! NSTextView + #if swift(>=4.0) + return textView.string + #else + return textView.string ?? "" + #endif + } + } + + /// A signal of values in `NSAttributedString` from the text field upon any + /// changes. + public var continuousAttributedStringValues: Signal { + return notifications + .map { ($0.object as! NSTextView).attributedString() } + } + +} +#endif diff --git a/ReactiveCocoa/AppKit/NSView.swift b/ReactiveCocoa/AppKit/NSView.swift new file mode 100644 index 0000000000..06e251cf93 --- /dev/null +++ b/ReactiveCocoa/AppKit/NSView.swift @@ -0,0 +1,16 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +import ReactiveSwift + +extension Reactive where Base: NSView { + /// Sets the visibility of the view. + public var isHidden: BindingTarget { + return makeBindingTarget { $0.isHidden = $1 } + } + + /// Sets the alpha value of the view. + public var alphaValue: BindingTarget { + return makeBindingTarget { $0.alphaValue = $1 } + } +} +#endif diff --git a/ReactiveCocoa/CocoaAction.swift b/ReactiveCocoa/CocoaAction.swift new file mode 100644 index 0000000000..7efa82d8df --- /dev/null +++ b/ReactiveCocoa/CocoaAction.swift @@ -0,0 +1,71 @@ +import Foundation +import ReactiveSwift + +/// CocoaAction wraps an `Action` for use by a UI control (such as `NSControl` or +/// `UIControl`). +public final class CocoaAction: NSObject { + /// The selector for message senders. + public static var selector: Selector { + return #selector(CocoaAction.execute(_:)) + } + + /// Whether the action is enabled. + /// + /// This property will only change on the main thread. + public let isEnabled: Property + + /// Whether the action is executing. + /// + /// This property will only change on the main thread. + public let isExecuting: Property + + private let _execute: (Sender) -> Void + + /// Initialize a CocoaAction that invokes the given Action by mapping the + /// sender to the input type of the Action. + /// + /// - parameters: + /// - action: The Action. + /// - inputTransform: A closure that maps Sender to the input type of the + /// Action. + public init(_ action: Action, _ inputTransform: @escaping (Sender) -> Input) { + _execute = { sender in + let producer = action.apply(inputTransform(sender)) + producer.start() + } + + isEnabled = Property(initial: action.isEnabled.value, + then: action.isEnabled.producer.observe(on: UIScheduler())) + isExecuting = Property(initial: action.isExecuting.value, + then: action.isExecuting.producer.observe(on: UIScheduler())) + + super.init() + } + + /// Initialize a CocoaAction that invokes the given Action. + /// + /// - parameters: + /// - action: The Action. + public convenience init(_ action: Action<(), Output, Error>) { + self.init(action, { _ in }) + } + + /// Initialize a CocoaAction that invokes the given Action with the given + /// constant. + /// + /// - parameters: + /// - action: The Action. + /// - input: The constant value as the input to the action. + public convenience init(_ action: Action, input: Input) { + self.init(action, { _ in input }) + } + + /// Attempt to execute the underlying action with the given input, subject + /// to the behavior described by the initializer that was used. + /// + /// - parameters: + /// - sender: The sender which initiates the attempt. + @IBAction public func execute(_ sender: Any) { + _execute(sender as! Sender) + } +} diff --git a/ReactiveCocoa/CocoaTarget.swift b/ReactiveCocoa/CocoaTarget.swift new file mode 100644 index 0000000000..46d3f3490f --- /dev/null +++ b/ReactiveCocoa/CocoaTarget.swift @@ -0,0 +1,58 @@ +import Foundation +import ReactiveSwift + +/// A target that accepts action messages. +internal final class CocoaTarget: NSObject { + private enum State { + case idle + case sending(queue: [Value]) + } + + private let observer: Signal.Observer + private let transform: (Any?) -> Value + + private var state: State + + internal init(_ observer: Signal.Observer, transform: @escaping (Any?) -> Value) { + self.observer = observer + self.transform = transform + self.state = .idle + } + + /// Broadcast the action message to all observers. + /// + /// Reentrancy is supported, and the action message would be deferred until the + /// delivery of the current message has completed. + /// + /// - note: It should only be invoked on the main queue. + /// + /// - parameters: + /// - sender: The object which sends the action message. + @objc internal func invoke(_ sender: Any?) { + switch state { + case .idle: + state = .sending(queue: []) + observer.send(value: transform(sender)) + + while case let .sending(values) = state { + guard !values.isEmpty else { + break + } + + state = .sending(queue: Array(values.dropFirst())) + observer.send(value: values[0]) + } + + state = .idle + + case let .sending(values): + state = .sending(queue: values + [transform(sender)]) + } + } +} + +extension CocoaTarget where Value == Void { + internal convenience init(_ observer: Signal<(), Never>.Observer) { + self.init(observer, transform: { _ in }) + } +} diff --git a/ReactiveCocoa/DelegateProxy.swift b/ReactiveCocoa/DelegateProxy.swift new file mode 100644 index 0000000000..4d484f33e9 --- /dev/null +++ b/ReactiveCocoa/DelegateProxy.swift @@ -0,0 +1,117 @@ +import Foundation +import ReactiveSwift + +internal class DelegateProxy: NSObject { + internal weak var forwardee: Delegate? { + didSet { + originalSetter(self) + } + } + + internal var interceptedSelectors: Set = [] + + private let lifetime: Lifetime + private let originalSetter: (AnyObject) -> Void + + required init(lifetime: Lifetime, _ originalSetter: @escaping (AnyObject) -> Void) { + self.lifetime = lifetime + self.originalSetter = originalSetter + } + + override func forwardingTarget(for selector: Selector!) -> Any? { + return interceptedSelectors.contains(selector) ? nil : forwardee + } + + func intercept(_ selector: Selector) -> Signal<(), Never> { + interceptedSelectors.insert(selector) + originalSetter(self) + return self.reactive.trigger(for: selector).take(during: lifetime) + } + + func intercept(_ selector: Selector) -> Signal<[Any?], Never> { + interceptedSelectors.insert(selector) + originalSetter(self) + return self.reactive.signal(for: selector).take(during: lifetime) + } + + override func responds(to selector: Selector!) -> Bool { + if interceptedSelectors.contains(selector) { + return true + } + + return (forwardee?.responds(to: selector) ?? false) || super.responds(to: selector) + } +} + +private let hasSwizzledKey = AssociationKey(default: false) + +extension DelegateProxy { + // FIXME: This is a workaround to a compiler issue, where any use of `Self` + // through a protocol would result in the following error messages: + // 1. PHI node operands are not the same type as the result! + // 2. LLVM ERROR: Broken function found, compilation aborted! + internal static func proxy>( + for instance: NSObject, + setter: Selector, + getter: Selector + ) -> P { + return _proxy(for: instance, setter: setter, getter: getter) as! P + } + + private static func _proxy( + for instance: NSObject, + setter: Selector, + getter: Selector + ) -> AnyObject { + return synchronized(instance) { + let key = AssociationKey(setter.delegateProxyAlias) + + if let proxy = instance.associations.value(forKey: key) { + return proxy + } + + let superclass: AnyClass = class_getSuperclass(swizzleClass(instance))! + + let invokeSuperSetter: @convention(c) (NSObject, AnyClass, Selector, AnyObject?) -> Void = { object, superclass, selector, delegate in + typealias Setter = @convention(c) (NSObject, Selector, AnyObject?) -> Void + let impl = class_getMethodImplementation(superclass, selector) + unsafeBitCast(impl, to: Setter.self)(object, selector, delegate) + } + + let newSetterImpl: @convention(block) (NSObject, AnyObject?) -> Void = { object, delegate in + if let proxy = object.associations.value(forKey: key) as! DelegateProxy? { + proxy.forwardee = (delegate as! Delegate?) + } else { + invokeSuperSetter(object, superclass, setter, delegate) + } + } + + // Hide the original setter, and redirect subsequent delegate assignment + // to the proxy. + instance.swizzle((setter, newSetterImpl), key: hasSwizzledKey) + + // As Objective-C classes may cache the information of their delegate at + // the time the delegates are set, the information has to be "flushed" + // whenever the proxy forwardee is replaced or a selector is intercepted. + let proxy = self.init(lifetime: instance.reactive.lifetime) { [weak instance] proxy in + guard let instance = instance else { return } + invokeSuperSetter(instance, superclass, setter, proxy) + } + + typealias Getter = @convention(c) (NSObject, Selector) -> AnyObject? + let getterImpl: IMP = class_getMethodImplementation(object_getClass(instance), getter)! + let original = unsafeBitCast(getterImpl, to: Getter.self)(instance, getter) as! Delegate? + + // `proxy.forwardee` would invoke the original setter regardless of + // `original` being `nil` or not. + proxy.forwardee = original + + // The proxy must be associated after it is set as the target, since + // `base` may be an isa-swizzled instance that is using the injected + // setters above. + instance.associations.setValue(proxy, forKey: key) + + return proxy + } + } +} diff --git a/ReactiveCocoa/Deprecations+Removals.swift b/ReactiveCocoa/Deprecations+Removals.swift new file mode 100644 index 0000000000..2b9ed5dcd7 --- /dev/null +++ b/ReactiveCocoa/Deprecations+Removals.swift @@ -0,0 +1,24 @@ +import Foundation +import ReactiveSwift + +extension Action { + @available(*, unavailable, message:"Use the `CocoaAction` initializers instead.") + public var unsafeCocoaAction: CocoaAction { fatalError() } +} + +extension Reactive where Base: NSObject { + @available(*, deprecated, renamed: "producer(forKeyPath:)") + public func values(forKeyPath keyPath: String) -> SignalProducer { + return producer(forKeyPath: keyPath) + } +} + +#if os(watchOS) +import WatchKit +extension Reactive where Base: WKInterfaceButton { + @available(*, unavailable, renamed: "title") + public var text: BindingTarget { + fatalError() + } +} +#endif diff --git a/ReactiveCocoa/DynamicProperty.swift b/ReactiveCocoa/DynamicProperty.swift new file mode 100644 index 0000000000..d102b85e80 --- /dev/null +++ b/ReactiveCocoa/DynamicProperty.swift @@ -0,0 +1,78 @@ +import Foundation +import ReactiveSwift + +/// A typed mutable property view to a certain key path of an Objective-C object using +/// Key-Value Coding and Key-Value Observing. +/// +/// Bindings towards a `DynamicProperty` would be directed to the underlying Objective-C +/// object, and would not be affected by the deinitialization of the `DynamicProperty`. +public final class DynamicProperty: MutablePropertyProtocol { + private weak var object: NSObject? + private let keyPath: String + private let cache: Property + private let transform: (Value) -> Any? + + /// The current value of the property, as read and written using Key-Value + /// Coding. + public var value: Value { + get { return cache.value } + set { object?.setValue(transform(newValue), forKeyPath: keyPath) } + } + + /// The lifetime of the property. + public var lifetime: Lifetime { + return object?.reactive.lifetime ?? .empty + } + + /// The binding target of the property. + public var bindingTarget: BindingTarget { + return BindingTarget(lifetime: lifetime) { [weak object, keyPath] value in + object?.setValue(value, forKey: keyPath) + } + } + + /// A producer that will create a Key-Value Observer for the given object, + /// send its initial value then all changes over time, and then complete + /// when the observed object has deallocated. + /// + /// - important: This only works if the object given to init() is KVO-compliant. + /// Most UI controls are not! + public var producer: SignalProducer { + return cache.producer + } + + public var signal: Signal { + return cache.signal + } + + internal init(object: NSObject, keyPath: String, cache: Property, transform: @escaping (Value) -> Any?) { + self.object = object + self.keyPath = keyPath + self.cache = cache + self.transform = transform + } + + /// Create a typed mutable view to the given key path of the given Objective-C object. + /// The generic type `Value` can be any Swift type, and will be bridged to Objective-C + /// via `Any`. + /// + /// - parameters: + /// - object: The Objective-C object to be observed. + /// - keyPath: The key path to observe. + public convenience init(object: NSObject, keyPath: String) { + self.init(object: object, keyPath: keyPath, cache: Property(object: object, keyPath: keyPath), transform: { $0 }) + } +} + +extension DynamicProperty where Value: OptionalProtocol { + /// Create a typed mutable view to the given key path of the given Objective-C object. + /// The generic type `Value` can be any Swift type, and will be bridged to Objective-C + /// via `Any`. + /// + /// - parameters: + /// - object: The Objective-C object to be observed. + /// - keyPath: The key path to observe. + public convenience init(object: NSObject, keyPath: String) { + self.init(object: object, keyPath: keyPath, cache: Property(object: object, keyPath: keyPath), transform: { $0.optional }) + } +} diff --git a/ReactiveCocoa/Info.plist b/ReactiveCocoa/Info.plist index 9a11afdc43..04e9494b8a 100644 --- a/ReactiveCocoa/Info.plist +++ b/ReactiveCocoa/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 12.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/ReactiveCocoa/NSObject+Association.swift b/ReactiveCocoa/NSObject+Association.swift new file mode 100644 index 0000000000..f145b00530 --- /dev/null +++ b/ReactiveCocoa/NSObject+Association.swift @@ -0,0 +1,135 @@ +import Foundation +#if SWIFT_PACKAGE +import ReactiveCocoaObjC +#endif +import ReactiveSwift + +internal struct AssociationKey { + fileprivate let address: UnsafeRawPointer + fileprivate let `default`: Value! + + /// Create an ObjC association key. + /// + /// - warning: The key must be uniqued. + /// + /// - parameters: + /// - default: The default value, or `nil` to trap on undefined value. It is + /// ignored if `Value` is an optional. + init(default: Value? = nil) { + self.address = UnsafeRawPointer(UnsafeMutablePointer.allocate(capacity: 1)) + self.default = `default` + } + + /// Create an ObjC association key from a `StaticString`. + /// + /// - precondition: `key` has a pointer representation. + /// + /// - parameters: + /// - default: The default value, or `nil` to trap on undefined value. It is + /// ignored if `Value` is an optional. + init(_ key: StaticString, default: Value? = nil) { + assert(key.hasPointerRepresentation) + self.address = UnsafeRawPointer(key.utf8Start) + self.default = `default` + } + + /// Create an ObjC association key from a `Selector`. + /// + /// - parameters: + /// - default: The default value, or `nil` to trap on undefined value. It is + /// ignored if `Value` is an optional. + init(_ key: Selector, default: Value? = nil) { + self.address = UnsafeRawPointer(key.utf8Start) + self.default = `default` + } +} + +internal struct Associations { + fileprivate let base: Base + + init(_ base: Base) { + self.base = base + } +} + +extension Reactive where Base: NSObjectProtocol { + /// Retrieve the associated value for the specified key. If the value does not + /// exist, `initial` would be called and the returned value would be + /// associated subsequently. + /// + /// - parameters: + /// - key: An optional key to differentiate different values. + /// - initial: The action that supples an initial value. + /// + /// - returns: The associated value for the specified key. + internal func associatedValue(forKey key: StaticString = #function, initial: (Base) -> T) -> T { + let key = AssociationKey(key) + + if let value = base.associations.value(forKey: key) { + return value + } + + let value = initial(base) + base.associations.setValue(value, forKey: key) + + return value + } +} + +extension NSObjectProtocol { + @nonobjc internal var associations: Associations { + return Associations(self) + } +} + +extension Associations { + /// Retrieve the associated value for the specified key. + /// + /// - parameters: + /// - key: The key. + /// + /// - returns: The associated value, or the default value if no value has been + /// associated with the key. + internal func value(forKey key: AssociationKey) -> Value { + return (objc_getAssociatedObject(base, key.address) as! Value?) ?? key.default + } + + /// Retrieve the associated value for the specified key. + /// + /// - parameters: + /// - key: The key. + /// + /// - returns: The associated value, or `nil` if no value is associated with + /// the key. + internal func value(forKey key: AssociationKey) -> Value? { + return objc_getAssociatedObject(base, key.address) as! Value? + } + + /// Set the associated value for the specified key. + /// + /// - parameters: + /// - value: The value to be associated. + /// - key: The key. + internal func setValue(_ value: Value, forKey key: AssociationKey) { + objc_setAssociatedObject(base, key.address, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + + /// Set the associated value for the specified key. + /// + /// - parameters: + /// - value: The value to be associated. + /// - key: The key. + internal func setValue(_ value: Value?, forKey key: AssociationKey) { + objc_setAssociatedObject(base, key.address, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } +} + +/// Set the associated value for the specified key. +/// +/// - parameters: +/// - value: The value to be associated. +/// - key: The key. +/// - address: The address of the object. +internal func unsafeSetAssociatedValue(_ value: Value?, forKey key: AssociationKey, forObjectAt address: UnsafeRawPointer) { + _rac_objc_setAssociatedObject(address, key.address, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +} diff --git a/ReactiveCocoa/NSObject+BindingTarget.swift b/ReactiveCocoa/NSObject+BindingTarget.swift new file mode 100644 index 0000000000..f55ac5441a --- /dev/null +++ b/ReactiveCocoa/NSObject+BindingTarget.swift @@ -0,0 +1,49 @@ +import Foundation +import ReactiveSwift + +extension Reactive where Base: AnyObject { + /// Creates a binding target which uses the lifetime of the object, and + /// weakly references the object so that the supplied `action` is triggered + /// only if the object has not deinitialized. + /// + /// - parameters: + /// - scheduler: An optional scheduler that the binding target uses. If it + /// is not specified, a UI scheduler would be used. + /// - action: The action to consume values from the bindings. + /// + /// - returns: A binding target that holds no strong references to the + /// object. + public func makeBindingTarget(on scheduler: Scheduler = UIScheduler(), _ action: @escaping (Base, U) -> Void) -> BindingTarget { + return BindingTarget(on: scheduler, lifetime: Lifetime.of(base)) { [weak base = self.base] value in + if let base = base { + action(base, value) + } + } + } +} + +#if swift(>=3.2) +extension Reactive where Base: AnyObject { + /// Creates a binding target that writes to the object with the given key path on a + /// `UIScheduler`. + /// + /// - parameters: + /// - keyPath: The key path to be written to. + /// + /// - returns: A binding target. + public subscript(keyPath: ReferenceWritableKeyPath) -> BindingTarget { + return BindingTarget(on: UIScheduler(), lifetime: Lifetime.of(base), object: base, keyPath: keyPath) + } + + /// Creates a binding target that writes to the object with the given key path. + /// + /// - parameters: + /// - keyPath: The key path to be written to. + /// - scheduler: The scheduler to perform the write on. + /// + /// - returns: A binding target. + public subscript(keyPath: ReferenceWritableKeyPath, on scheduler: Scheduler) -> BindingTarget { + return BindingTarget(on: scheduler, lifetime: Lifetime.of(base), object: base, keyPath: keyPath) + } +} +#endif diff --git a/ReactiveCocoa/NSObject+Intercepting.swift b/ReactiveCocoa/NSObject+Intercepting.swift new file mode 100644 index 0000000000..f1fd9ba242 --- /dev/null +++ b/ReactiveCocoa/NSObject+Intercepting.swift @@ -0,0 +1,471 @@ +import Foundation +#if SWIFT_PACKAGE +import ReactiveCocoaObjC +#endif +import ReactiveSwift + +/// Whether the runtime subclass has already been prepared for method +/// interception. +fileprivate let interceptedKey = AssociationKey(default: false) + +/// Holds the method signature cache of the runtime subclass. +fileprivate let signatureCacheKey = AssociationKey() + +/// Holds the method selector cache of the runtime subclass. +fileprivate let selectorCacheKey = AssociationKey() + +internal let noImplementation: IMP = unsafeBitCast(Int(0), to: IMP.self) + +extension Reactive where Base: NSObject { + /// Create a signal which sends a `next` event at the end of every + /// invocation of `selector` on the object. + /// + /// It completes when the object deinitializes. + /// + /// - note: Observers to the resulting signal should not call the method + /// specified by the selector. + /// + /// - parameters: + /// - selector: The selector to observe. + /// + /// - returns: A trigger signal. + public func trigger(for selector: Selector) -> Signal<(), Never> { + return base.intercept(selector).map { _ in } + } + + /// Create a signal which sends a `next` event, containing an array of + /// bridged arguments, at the end of every invocation of `selector` on the + /// object. + /// + /// It completes when the object deinitializes. + /// + /// - note: Observers to the resulting signal should not call the method + /// specified by the selector. + /// + /// - parameters: + /// - selector: The selector to observe. + /// + /// - returns: A signal that sends an array of bridged arguments. + public func signal(for selector: Selector) -> Signal<[Any?], Never> { + return base.intercept(selector).map(unpackInvocation) + } +} + +extension NSObject { + /// Setup the method interception. + /// + /// - parameters: + /// - object: The object to be intercepted. + /// - selector: The selector of the method to be intercepted. + /// + /// - returns: A signal that sends the corresponding `NSInvocation` after + /// every invocation of the method. + @nonobjc fileprivate func intercept(_ selector: Selector) -> Signal { + guard let method = class_getInstanceMethod(objcClass, selector) else { + fatalError("Selector `\(selector)` does not exist in class `\(String(describing: objcClass))`.") + } + + let typeEncoding = method_getTypeEncoding(method)! + assert(checkTypeEncoding(typeEncoding)) + + return synchronized(self) { + let alias = selector.alias + let stateKey = AssociationKey(alias) + let interopAlias = selector.interopAlias + + if let state = associations.value(forKey: stateKey) { + return state.signal + } + + let subclass: AnyClass = swizzleClass(self) + let subclassAssociations = Associations(subclass as AnyObject) + + ReactiveCocoa.synchronized(subclass) { + let isSwizzled = subclassAssociations.value(forKey: interceptedKey) + + let signatureCache: SignatureCache + let selectorCache: SelectorCache + + if isSwizzled { + signatureCache = subclassAssociations.value(forKey: signatureCacheKey) + selectorCache = subclassAssociations.value(forKey: selectorCacheKey) + } else { + signatureCache = SignatureCache() + selectorCache = SelectorCache() + + subclassAssociations.setValue(signatureCache, forKey: signatureCacheKey) + subclassAssociations.setValue(selectorCache, forKey: selectorCacheKey) + subclassAssociations.setValue(true, forKey: interceptedKey) + + enableMessageForwarding(subclass, selectorCache) + setupMethodSignatureCaching(subclass, signatureCache) + } + + selectorCache.cache(selector) + + if signatureCache[selector] == nil { + let signature = NSMethodSignature.objcSignature(withObjCTypes: typeEncoding) + signatureCache[selector] = signature + } + + // If an immediate implementation of the selector is found in the + // runtime subclass the first time the selector is intercepted, + // preserve the implementation. + // + // Example: KVO setters if the instance is swizzled by KVO before RAC + // does. + if !class_respondsToSelector(subclass, interopAlias) { + let immediateImpl = class_getImmediateMethod(subclass, selector) + .flatMap(method_getImplementation) + .flatMap { $0 != _rac_objc_msgForward ? $0 : nil } + + if let impl = immediateImpl { + let succeeds = class_addMethod(subclass, interopAlias, impl, typeEncoding) + precondition(succeeds, "RAC attempts to swizzle a selector that has message forwarding enabled with a runtime injected implementation. This is unsupported in the current version.") + } + } + } + + let state = InterceptingState(lifetime: reactive.lifetime) + associations.setValue(state, forKey: stateKey) + + // Start forwarding the messages of the selector. + _ = class_replaceMethod(subclass, selector, _rac_objc_msgForward, typeEncoding) + + return state.signal + } + } +} + +/// Swizzle `realClass` to enable message forwarding for method interception. +/// +/// - parameters: +/// - realClass: The runtime subclass to be swizzled. +private func enableMessageForwarding(_ realClass: AnyClass, _ selectorCache: SelectorCache) { + let perceivedClass: AnyClass = class_getSuperclass(realClass)! + + typealias ForwardInvocationImpl = @convention(block) (Unmanaged, AnyObject) -> Void + let newForwardInvocation: ForwardInvocationImpl = { objectRef, invocation in + let selector = invocation.selector! + let alias = selectorCache.alias(for: selector) + let interopAlias = selectorCache.interopAlias(for: selector) + + defer { + let stateKey = AssociationKey(alias) + if let state = objectRef.takeUnretainedValue().associations.value(forKey: stateKey) { + state.observer.send(value: invocation) + } + } + + let method = class_getInstanceMethod(perceivedClass, selector) + let typeEncoding: String + + if let runtimeTypeEncoding = method.flatMap(method_getTypeEncoding) { + typeEncoding = String(cString: runtimeTypeEncoding) + } else { + let methodSignature = (objectRef.takeUnretainedValue() as AnyObject) + .objcMethodSignature(for: selector) + let encodings = (0 ..< methodSignature.objcNumberOfArguments!) + .map { UInt8(methodSignature.objcArgumentType(at: $0).pointee) } + typeEncoding = String(bytes: encodings, encoding: .ascii)! + } + + if class_respondsToSelector(realClass, interopAlias) { + // RAC has preserved an immediate implementation found in the runtime + // subclass that was supplied by an external party. + // + // As the KVO setter relies on the selector to work, it has to be invoked + // by swapping in the preserved implementation and restore to the message + // forwarder afterwards. + // + // However, the IMP cache would be thrashed due to the swapping. + + let topLevelClass: AnyClass = object_getClass(objectRef.takeUnretainedValue())! + + // The locking below prevents RAC swizzling attempts from intervening the + // invocation. + // + // Given the implementation of `swizzleClass`, `topLevelClass` can only be: + // (1) the same as `realClass`; or (2) a subclass of `realClass`. In other + // words, this would deadlock only if the locking order is not followed in + // other nested locking scenarios of these metaclasses at compile time. + + synchronized(topLevelClass) { + func swizzle() { + let interopImpl = class_getMethodImplementation(topLevelClass, interopAlias)! + + let previousImpl = class_replaceMethod(topLevelClass, selector, interopImpl, typeEncoding) + invocation.objcInvoke() + + _ = class_replaceMethod(topLevelClass, selector, previousImpl ?? noImplementation, typeEncoding) + } + + if topLevelClass != realClass { + synchronized(realClass) { + // In addition to swapping in the implementation, the message + // forwarding needs to be temporarily disabled to prevent circular + // invocation. + _ = class_replaceMethod(realClass, selector, noImplementation, typeEncoding) + swizzle() + _ = class_replaceMethod(realClass, selector, _rac_objc_msgForward, typeEncoding) + } + } else { + swizzle() + } + } + + return + } + + let impl: IMP = method.map(method_getImplementation) ?? _rac_objc_msgForward + if impl != _rac_objc_msgForward { + // The perceived class, or its ancestors, responds to the selector. + // + // The implementation is invoked through the selector alias, which + // reflects the latest implementation of the selector in the perceived + // class. + + if class_getMethodImplementation(realClass, alias) != impl { + // Update the alias if and only if the implementation has changed, so as + // to avoid thrashing the IMP cache. + _ = class_replaceMethod(realClass, alias, impl, typeEncoding) + } + + invocation.objcSetSelector(alias) + invocation.objcInvoke() + + return + } + + // Forward the invocation to the closest `forwardInvocation(_:)` in the + // inheritance hierarchy, or the default handler returned by the runtime + // if it finds no implementation. + typealias SuperForwardInvocation = @convention(c) (Unmanaged, Selector, AnyObject) -> Void + let forwardInvocationImpl = class_getMethodImplementation(perceivedClass, ObjCSelector.forwardInvocation) + let forwardInvocation = unsafeBitCast(forwardInvocationImpl, to: SuperForwardInvocation.self) + forwardInvocation(objectRef, ObjCSelector.forwardInvocation, invocation) + } + + _ = class_replaceMethod(realClass, + ObjCSelector.forwardInvocation, + imp_implementationWithBlock(newForwardInvocation as Any), + ObjCMethodEncoding.forwardInvocation) +} + +/// Swizzle `realClass` to accelerate the method signature retrieval, using a +/// signature cache that covers all known intercepted selectors of `realClass`. +/// +/// - parameters: +/// - realClass: The runtime subclass to be swizzled. +/// - signatureCache: The method signature cache. +private func setupMethodSignatureCaching(_ realClass: AnyClass, _ signatureCache: SignatureCache) { + let perceivedClass: AnyClass = class_getSuperclass(realClass)! + + let newMethodSignatureForSelector: @convention(block) (Unmanaged, Selector) -> AnyObject? = { objectRef, selector in + if let signature = signatureCache[selector] { + return signature + } + + typealias SuperMethodSignatureForSelector = @convention(c) (Unmanaged, Selector, Selector) -> AnyObject? + let impl = class_getMethodImplementation(perceivedClass, ObjCSelector.methodSignatureForSelector) + let methodSignatureForSelector = unsafeBitCast(impl, to: SuperMethodSignatureForSelector.self) + return methodSignatureForSelector(objectRef, ObjCSelector.methodSignatureForSelector, selector) + } + + _ = class_replaceMethod(realClass, + ObjCSelector.methodSignatureForSelector, + imp_implementationWithBlock(newMethodSignatureForSelector as Any), + ObjCMethodEncoding.methodSignatureForSelector) +} + +/// The state of an intercepted method specific to an instance. +private final class InterceptingState { + let (signal, observer) = Signal.pipe() + + /// Initialize a state specific to an instance. + /// + /// - parameters: + /// - lifetime: The lifetime of the instance. + init(lifetime: Lifetime) { + lifetime.ended.observeCompleted(observer.sendCompleted) + } +} + +private final class SelectorCache { + private var map: [Selector: (main: Selector, interop: Selector)] = [:] + + init() {} + + /// Cache the aliases of the specified selector in the cache. + /// + /// - warning: Any invocation of this method must be synchronized against the + /// runtime subclass. + @discardableResult + func cache(_ selector: Selector) -> (main: Selector, interop: Selector) { + if let pair = map[selector] { + return pair + } + + let aliases = (selector.alias, selector.interopAlias) + map[selector] = aliases + + return aliases + } + + /// Get the alias of the specified selector. + /// + /// - parameters: + /// - selector: The selector alias. + func alias(for selector: Selector) -> Selector { + if let (main, _) = map[selector] { + return main + } + + return selector.alias + } + + /// Get the secondary alias of the specified selector. + /// + /// - parameters: + /// - selector: The selector alias. + func interopAlias(for selector: Selector) -> Selector { + if let (_, interop) = map[selector] { + return interop + } + + return selector.interopAlias + } +} + +// The signature cache for classes that have been swizzled for method +// interception. +// +// Read-copy-update is used here, since the cache has multiple readers but only +// one writer. +private final class SignatureCache { + // `Dictionary` takes 8 bytes for the reference to its storage and does CoW. + // So it should not encounter any corrupted, partially updated state. + private var map: [Selector: AnyObject] = [:] + + init() {} + + /// Get or set the signature for the specified selector. + /// + /// - warning: Any invocation of the setter must be synchronized against the + /// runtime subclass. + /// + /// - parameters: + /// - selector: The method signature. + subscript(selector: Selector) -> AnyObject? { + get { + return map[selector] + } + set { + if map[selector] == nil { + map[selector] = newValue + } + } + } +} + +/// Assert that the method does not contain types that cannot be intercepted. +/// +/// - parameters: +/// - types: The type encoding C string of the method. +/// +/// - returns: `true`. +private func checkTypeEncoding(_ types: UnsafePointer) -> Bool { + // Some types, including vector types, are not encoded. In these cases the + // signature starts with the size of the argument frame. + assert(types.pointee < Int8(UInt8(ascii: "1")) || types.pointee > Int8(UInt8(ascii: "9")), + "unknown method return type not supported in type encoding: \(String(cString: types))") + + assert(types.pointee != Int8(UInt8(ascii: "(")), "union method return type not supported") + assert(types.pointee != Int8(UInt8(ascii: "{")), "struct method return type not supported") + assert(types.pointee != Int8(UInt8(ascii: "[")), "array method return type not supported") + + assert(types.pointee != Int8(UInt8(ascii: "j")), "complex method return type not supported") + + return true +} + +/// Extract the arguments of an `NSInvocation` as an array of objects. +/// +/// - parameters: +/// - invocation: The `NSInvocation` to unpack. +/// +/// - returns: An array of objects. +private func unpackInvocation(_ invocation: AnyObject) -> [Any?] { + let invocation = invocation as AnyObject + let methodSignature = invocation.objcMethodSignature! + let count = methodSignature.objcNumberOfArguments! + + var bridged = [Any?]() + bridged.reserveCapacity(Int(count - 2)) + + // Ignore `self` and `_cmd` at index 0 and 1. + for position in 2 ..< count { + let rawEncoding = methodSignature.objcArgumentType(at: position) + let encoding = ObjCTypeEncoding(rawValue: rawEncoding.pointee) ?? .undefined + + func extract(_ type: U.Type) -> U { + let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, + alignment: MemoryLayout.alignment) + defer { + pointer.deallocate() + } + + invocation.objcCopy(to: pointer, forArgumentAt: Int(position)) + return pointer.assumingMemoryBound(to: type).pointee + } + + let value: Any? + + switch encoding { + case .char: + value = NSNumber(value: extract(CChar.self)) + case .int: + value = NSNumber(value: extract(CInt.self)) + case .short: + value = NSNumber(value: extract(CShort.self)) + case .long: + value = NSNumber(value: extract(CLong.self)) + case .longLong: + value = NSNumber(value: extract(CLongLong.self)) + case .unsignedChar: + value = NSNumber(value: extract(CUnsignedChar.self)) + case .unsignedInt: + value = NSNumber(value: extract(CUnsignedInt.self)) + case .unsignedShort: + value = NSNumber(value: extract(CUnsignedShort.self)) + case .unsignedLong: + value = NSNumber(value: extract(CUnsignedLong.self)) + case .unsignedLongLong: + value = NSNumber(value: extract(CUnsignedLongLong.self)) + case .float: + value = NSNumber(value: extract(CFloat.self)) + case .double: + value = NSNumber(value: extract(CDouble.self)) + case .bool: + value = NSNumber(value: extract(CBool.self)) + case .object: + value = extract((AnyObject?).self) + case .type: + value = extract((AnyClass?).self) + case .selector: + value = extract((Selector?).self) + case .undefined: + var size = 0, alignment = 0 + NSGetSizeAndAlignment(rawEncoding, &size, &alignment) + let buffer = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment) + defer { buffer.deallocate() } + + invocation.objcCopy(to: buffer, forArgumentAt: Int(position)) + value = NSValue(bytes: buffer, objCType: rawEncoding) + } + + bridged.append(value) + } + + return bridged +} diff --git a/ReactiveCocoa/NSObject+KeyValueObserving.swift b/ReactiveCocoa/NSObject+KeyValueObserving.swift new file mode 100644 index 0000000000..c7fcc3a923 --- /dev/null +++ b/ReactiveCocoa/NSObject+KeyValueObserving.swift @@ -0,0 +1,625 @@ +import Foundation +#if SWIFT_PACKAGE +import ReactiveCocoaObjC +#endif +import ReactiveSwift + +extension Reactive where Base: NSObject { + /// Create a producer which sends the current value and all the subsequent + /// changes of the property specified by the key path. + /// + /// The producer completes when the object deinitializes. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func producer(forKeyPath keyPath: String) -> SignalProducer { + return SignalProducer { observer, lifetime in + let disposable = KeyValueObserver.observe( + self.base, + keyPath: keyPath, + options: [.initial, .new], + action: observer.send + ) + + lifetime.observeEnded(disposable.dispose) + + if let lifetimeDisposable = self.lifetime.observeEnded(observer.sendCompleted) { + lifetime.observeEnded(lifetimeDisposable.dispose) + } + } + } + + /// Create a signal all changes of the property specified by the key path. + /// + /// The signal completes when the object deinitializes. + /// + /// - note: + /// Does not send the initial value. See `producer(forKeyPath:)`. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func signal(forKeyPath keyPath: String) -> Signal { + return Signal { observer, signalLifetime in + signalLifetime += KeyValueObserver.observe( + self.base, + keyPath: keyPath, + options: [.new], + action: observer.send + ) + signalLifetime += lifetime.observeEnded(observer.sendCompleted) + } + } + + private func producer( + for keyPath: KeyPath, + transform: @escaping (Any?) -> U + ) -> SignalProducer { + guard let kvcKeyPath = keyPath._kvcKeyPathString else { + fatalError("Cannot use `producer(for:)` on a non Objective-C property.") + } + + return SignalProducer { observer, lifetime in + lifetime += KeyValueObserver.observe( + self.base, + keyPath: kvcKeyPath, + options: [.initial, .new], + action: { observer.send(value: transform($0)) } + ) + + lifetime += self.lifetime.observeEnded(observer.sendCompleted) + } + } + + private func signal( + for keyPath: KeyPath, + transform: @escaping (Any?) -> U + ) -> Signal { + guard let kvcKeyPath = keyPath._kvcKeyPathString else { + fatalError("Cannot use `signal(for:)` on a non Objective-C property.") + } + + return Signal { observer, lifetime in + lifetime += KeyValueObserver.observe( + self.base, + keyPath: kvcKeyPath, + options: [.new], + action: { observer.send(value: transform($0)) } + ) + lifetime += self.lifetime.observeEnded(observer.sendCompleted) + } + } + + /// Create a producer which sends the current value and all the subsequent + /// changes of the property specified by the key path. + /// + /// The producer completes when the object deinitializes. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func producer(for keyPath: KeyPath) -> SignalProducer { + return producer(for: keyPath) { $0 as! U? } + } + + /// Create a producer which sends the current value and all the subsequent + /// changes of the property specified by the key path. + /// + /// The producer completes when the object deinitializes. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func producer(for keyPath: KeyPath) -> SignalProducer where U: RawRepresentable { + return producer(for: keyPath) { + $0.flatMap { value in U(rawValue: value as! U.RawValue)! } + } + } + + /// Create a signal all changes of the property specified by the key path. + /// + /// The signal completes when the object deinitializes. + /// + /// - note: + /// Does not send the initial value. See `producer(forKeyPath:)`. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func signal(for keyPath: KeyPath) -> Signal { + return signal(for: keyPath) { $0 as! U? } + } + + /// Create a signal all changes of the property specified by the key path. + /// + /// The signal completes when the object deinitializes. + /// + /// - note: + /// Does not send the initial value. See `producer(forKeyPath:)`. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func signal(for keyPath: KeyPath) -> Signal where U: RawRepresentable { + return signal(for: keyPath) { + $0.flatMap { value in U(rawValue: value as! U.RawValue) } + } + } + + /// Create a producer which sends the current value and all the subsequent + /// changes of the property specified by the key path. + /// + /// The producer completes when the object deinitializes. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func producer(for keyPath: KeyPath) -> SignalProducer { + return producer(for: keyPath) { $0 as! U } + } + + /// Create a producer which sends the current value and all the subsequent + /// changes of the property specified by the key path. + /// + /// The producer completes when the object deinitializes. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func producer(for keyPath: KeyPath) -> SignalProducer where U: RawRepresentable { + return producer(for: keyPath) { U(rawValue: $0 as! U.RawValue)! } + } + + /// Create a signal all changes of the property specified by the key path. + /// + /// The signal completes when the object deinitializes. + /// + /// - note: + /// Does not send the initial value. See `producer(forKeyPath:)`. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func signal(for keyPath: KeyPath) -> Signal { + return signal(for: keyPath) { $0 as! U } + } + + /// Create a signal all changes of the property specified by the key path. + /// + /// The signal completes when the object deinitializes. + /// + /// - note: + /// Does not send the initial value. See `producer(forKeyPath:)`. + /// + /// - parameters: + /// - keyPath: The key path of the property to be observed. + /// + /// - returns: A producer emitting values of the property specified by the + /// key path. + public func signal(for keyPath: KeyPath) -> Signal where U: RawRepresentable { + return signal(for: keyPath) { U(rawValue: $0 as! U.RawValue)! } + } +} + +extension Property where Value: OptionalProtocol { + /// Create a property that observes the given key path of the given object. The + /// generic type `Value` can be any Swift type that is Objective-C bridgeable. + /// + /// - parameters: + /// - object: An object to be observed. + /// - keyPath: The key path to observe. + public convenience init(object: NSObject, keyPath: String) { + // `Property(_:)` caches the latest value of the `DynamicProperty`, so it is + // saved to be used even after `object` deinitializes. + self.init(UnsafeKVOProperty(object: object, optionalAttributeKeyPath: keyPath)) + } +} + +extension Property { + /// Create a property that observes the given key path of the given object. The + /// generic type `Value` can be any Swift type that is Objective-C bridgeable. + /// + /// - parameters: + /// - object: An object to be observed. + /// - keyPath: The key path to observe. + public convenience init(object: NSObject, keyPath: String) { + // `Property(_:)` caches the latest value of the `DynamicProperty`, so it is + // saved to be used even after `object` deinitializes. + self.init(UnsafeKVOProperty(object: object, nonOptionalAttributeKeyPath: keyPath)) + } +} + +// `Property(unsafeProducer:)` is private to ReactiveSwift. So the fact that +// `Property(_:)` uses only the producer is explioted here to achieve the same effect. +private final class UnsafeKVOProperty: PropertyProtocol { + var value: Value { fatalError() } + var signal: Signal { fatalError() } + let producer: SignalProducer + + init(producer: SignalProducer) { + self.producer = producer + } + + convenience init(object: NSObject, nonOptionalAttributeKeyPath keyPath: String) { + self.init(producer: object.reactive.producer(forKeyPath: keyPath).map { $0 as! Value }) + } +} + +private extension UnsafeKVOProperty where Value: OptionalProtocol { + convenience init(object: NSObject, optionalAttributeKeyPath keyPath: String) { + self.init(producer: object.reactive.producer(forKeyPath: keyPath).map { + return Value(reconstructing: $0.optional as? Value.Wrapped) + }) + } +} + +extension BindingTarget { + /// Create a binding target that sets the given key path of the given object. The + /// generic type `Value` can be any Swift type that is Objective-C bridgeable. + /// + /// - parameters: + /// - object: An object to be observed. + /// - keyPath: The key path to set. + public init(object: NSObject, keyPath: String) { + self.init(lifetime: object.reactive.lifetime) { [weak object] value in + object?.setValue(value, forKey: keyPath) + } + } +} + +internal final class KeyValueObserver: NSObject { + typealias Action = (_ object: AnyObject?) -> Void + private static let context = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 0) + + unowned(unsafe) let unsafeObject: NSObject + let key: String + let action: Action + + fileprivate init(observing object: NSObject, key: String, options: NSKeyValueObservingOptions, action: @escaping Action) { + self.unsafeObject = object + self.key = key + self.action = action + + super.init() + + object.addObserver( + self, + forKeyPath: key, + options: options, + context: KeyValueObserver.context + ) + } + + func detach() { + unsafeObject.removeObserver(self, forKeyPath: key, context: KeyValueObserver.context) + } + + override func observeValue( + forKeyPath keyPath: String?, + of object: Any?, + change: [NSKeyValueChangeKey : Any]?, + context: UnsafeMutableRawPointer? + ) { + if context == KeyValueObserver.context { + action(object as! NSObject) + } + } +} + +extension KeyValueObserver { + /// Establish an observation to the property specified by the key path + /// of `object`. + /// + /// - warning: The observation would not be automatically removed when + /// `object` deinitializes. You must manually dispose of the + /// returned disposable before `object` completes its + /// deinitialization. + /// + /// - parameters: + /// - object: The object to be observed. + /// - keyPath: The key path of the property to be observed. + /// - options: The desired configuration of the observation. + /// - action: The action to be invoked upon arrival of changes. + /// + /// - returns: A disposable that would tear down the observation upon + /// disposal. + static func observe( + _ object: NSObject, + keyPath: String, + options: NSKeyValueObservingOptions, + action: @escaping (_ value: AnyObject?) -> Void + ) -> AnyDisposable { + // Compute the key path head and tail. + let components = keyPath.components(separatedBy: ".") + precondition(!components.isEmpty, "Received an empty key path.") + + let isNested = components.count > 1 + let keyPathHead = components[0] + let keyPathTail = components[1 ..< components.endIndex].joined(separator: ".") + + // The serial disposable for the head key. + // + // The inner disposable would be disposed of, and replaced with a new one + // when the value of the head key changes. + let headSerialDisposable = SerialDisposable() + + // If the property of the head key isn't actually an object (or is a Class + // object), there is no point in observing the deallocation. + // + // If this property is not a weak reference to an object, we don't need to + // watch for it spontaneously being set to nil. + // + // Attempting to observe non-weak properties using dynamic getters will + // result in broken behavior, so don't even try. + let (shouldObserveDeinit, isWeak) = keyPathHead.withCString { cString -> (Bool, Bool) in + if let propertyPointer = class_getProperty(type(of: object), cString) { + let attributes = PropertyAttributes(property: propertyPointer) + return (attributes.isObject && attributes.objectClass != NSClassFromString("Protocol") && !attributes.isBlock, attributes.isWeak) + } + + return (false, false) + } + + // Establish the observation. + // + // The initial value is also handled by the closure below, if `Initial` has + // been specified in the observation options. + let observer: KeyValueObserver + + if isNested { + observer = KeyValueObserver(observing: object, key: keyPathHead, options: options.union(.initial)) { object in + guard let value = object?.value(forKey: keyPathHead) as! NSObject? else { + headSerialDisposable.inner = nil + action(nil) + return + } + + let headDisposable = CompositeDisposable() + headSerialDisposable.inner = headDisposable + + if shouldObserveDeinit { + let disposable = value.reactive.lifetime.observeEnded { + if isWeak { + action(nil) + } + + // Detach the key path tail observers eagarly. + headSerialDisposable.inner = nil + } + headDisposable += disposable + } + + // Recursively add observers along the key path tail. + let disposable = KeyValueObserver.observe( + value, + keyPath: keyPathTail, + options: options.subtracting(.initial), + action: action + ) + headDisposable += disposable + + // Send the latest value of the key path tail. + action(value.value(forKeyPath: keyPathTail) as AnyObject?) + } + } else { + observer = KeyValueObserver(observing: object, key: keyPathHead, options: options) { object in + guard let value = object?.value(forKey: keyPathHead) as AnyObject? else { + action(nil) + return + } + + // For a direct key path, the deinitialization needs to be + // observed only if the key path is a weak property. + if shouldObserveDeinit && isWeak { + let disposable = Lifetime.of(value).observeEnded { + action(nil) + } + + headSerialDisposable.inner = disposable + } + + // Send the latest value of the key. + action(value) + } + } + + return AnyDisposable { + observer.detach() + headSerialDisposable.dispose() + } + } +} + +/// A descriptor of the attributes and type information of a property in +/// Objective-C. +internal struct PropertyAttributes { + struct Code { + static let start = Int8(UInt8(ascii: "T")) + static let quote = Int8(UInt8(ascii: "\"")) + static let nul = Int8(UInt8(ascii: "\0")) + static let comma = Int8(UInt8(ascii: ",")) + + struct ContainingType { + static let object = Int8(UInt8(ascii: "@")) + static let block = Int8(UInt8(ascii: "?")) + } + + struct Attribute { + static let readonly = Int8(UInt8(ascii: "R")) + static let copy = Int8(UInt8(ascii: "C")) + static let retain = Int8(UInt8(ascii: "&")) + static let nonatomic = Int8(UInt8(ascii: "N")) + static let getter = Int8(UInt8(ascii: "G")) + static let setter = Int8(UInt8(ascii: "S")) + static let dynamic = Int8(UInt8(ascii: "D")) + static let ivar = Int8(UInt8(ascii: "V")) + static let weak = Int8(UInt8(ascii: "W")) + static let collectable = Int8(UInt8(ascii: "P")) + static let oldTypeEncoding = Int8(UInt8(ascii: "t")) + } + } + + /// The class of the property. + let objectClass: AnyClass? + + /// Indicate whether the property is a weak reference. + let isWeak: Bool + + /// Indicate whether the property is an object. + let isObject: Bool + + /// Indicate whether the property is a block. + let isBlock: Bool + + init(property: objc_property_t) { + guard let attrString = property_getAttributes(property) else { + preconditionFailure("Could not get attribute string from property.") + } + + precondition(attrString[0] == Code.start, "Expected attribute string to start with 'T'.") + + let typeString = attrString + 1 + + let _next = NSGetSizeAndAlignment(typeString, nil, nil) + guard _next != typeString else { + let string = String(validatingUTF8: attrString) + preconditionFailure("Could not read past type in attribute string: \(String(describing: string)).") + } + var next = UnsafeMutablePointer(mutating: _next) + + let typeLength = typeString.distance(to: next) + precondition(typeLength > 0, "Invalid type in attribute string.") + + var objectClass: AnyClass? = nil + + // if this is an object type, and immediately followed by a quoted string... + if typeString[0] == Code.ContainingType.object && typeString[1] == Code.quote { + // we should be able to extract a class name + let className = typeString + 2; + + // fast forward the `next` pointer. + guard let endQuote = strchr(className, Int32(Code.quote)) else { + preconditionFailure("Could not read class name in attribute string.") + } + next = endQuote + + if className != UnsafePointer(next) { + let length = className.distance(to: next) + let name = UnsafeMutablePointer.allocate(capacity: length + 1) + name.initialize(from: UnsafeMutablePointer(mutating: className), count: length) + (name + length).initialize(to: Code.nul) + + // attempt to look up the class in the runtime + objectClass = objc_getClass(name) as! AnyClass? + + name.deinitialize(count: length + 1) + name.deallocate() + } + } + + if next.pointee != Code.nul { + // skip past any junk before the first flag + next = strchr(next, Int32(Code.comma)) + } + + let emptyString = UnsafeMutablePointer.allocate(capacity: 1) + emptyString.initialize(to: Code.nul) + defer { + emptyString.deinitialize(count: 1) + emptyString.deallocate() + } + + var isWeak = false + + while next.pointee == Code.comma { + let flag = next[1] + next += 2 + + switch flag { + case Code.nul: + break; + + case Code.Attribute.readonly: + break; + + case Code.Attribute.copy: + break; + + case Code.Attribute.retain: + break; + + case Code.Attribute.nonatomic: + break; + + case Code.Attribute.getter: + fallthrough + + case Code.Attribute.setter: + next = strchr(next, Int32(Code.comma)) ?? emptyString + + case Code.Attribute.dynamic: + break + + case Code.Attribute.ivar: + // assume that the rest of the string (if present) is the ivar name + if next.pointee != Code.nul { + next = emptyString + } + + case Code.Attribute.weak: + isWeak = true + + case Code.Attribute.collectable: + break + + case Code.Attribute.oldTypeEncoding: + let string = String(validatingUTF8: attrString) + assertionFailure("Old-style type encoding is unsupported in attribute string \"\(String(describing: string))\"") + + // skip over this type encoding + while next.pointee != Code.comma && next.pointee != Code.nul { + next += 1 + } + + default: + let pointer = UnsafeMutablePointer.allocate(capacity: 2) + pointer.initialize(to: flag) + (pointer + 1).initialize(to: Code.nul) + + let flag = String(validatingUTF8: pointer) + let string = String(validatingUTF8: attrString) + preconditionFailure("ERROR: Unrecognized attribute string flag '\(String(describing: flag))' in attribute string \"\(String(describing: string))\".") + } + } + + if next.pointee != Code.nul { + let unparsedData = String(validatingUTF8: next) + let string = String(validatingUTF8: attrString) + assertionFailure("Warning: Unparsed data \"\(String(describing: unparsedData))\" in attribute string \"\(String(describing: string))\".") + } + + self.objectClass = objectClass + self.isWeak = isWeak + self.isObject = typeString[0] == Code.ContainingType.object + self.isBlock = isObject && typeString[1] == Code.ContainingType.block + } +} diff --git a/ReactiveCocoa/NSObject+ObjCRuntime.swift b/ReactiveCocoa/NSObject+ObjCRuntime.swift new file mode 100644 index 0000000000..e4223a777a --- /dev/null +++ b/ReactiveCocoa/NSObject+ObjCRuntime.swift @@ -0,0 +1,11 @@ +import Foundation + +extension NSObject { + /// The class of the instance reported by the ObjC `-class:` message. + /// + /// - note: `type(of:)` might return the runtime subclass, while this property + /// always returns the original class. + @nonobjc internal var objcClass: AnyClass { + return (self as AnyObject).objcClass + } +} diff --git a/ReactiveCocoa/NSObject+ReactiveExtensionsProvider.swift b/ReactiveCocoa/NSObject+ReactiveExtensionsProvider.swift new file mode 100644 index 0000000000..62b568f79d --- /dev/null +++ b/ReactiveCocoa/NSObject+ReactiveExtensionsProvider.swift @@ -0,0 +1,4 @@ +import Foundation +import ReactiveSwift + +extension NSObject: ReactiveExtensionsProvider {} diff --git a/ReactiveCocoa/ObjC+Constants.swift b/ReactiveCocoa/ObjC+Constants.swift new file mode 100644 index 0000000000..274cc3eb5c --- /dev/null +++ b/ReactiveCocoa/ObjC+Constants.swift @@ -0,0 +1,48 @@ +import Foundation + +// Unavailable selectors in Swift. +internal enum ObjCSelector { + static let forwardInvocation = Selector((("forwardInvocation:"))) + static let methodSignatureForSelector = Selector((("methodSignatureForSelector:"))) + static let getClass = Selector((("class"))) +} + +// Method encoding of the unavailable selectors. +internal enum ObjCMethodEncoding { + static let forwardInvocation = extract("v@:@") + static let methodSignatureForSelector = extract("v@::") + static let getClass = extract("#@:") + + private static func extract(_ string: StaticString) -> UnsafePointer { + return UnsafeRawPointer(string.utf8Start).assumingMemoryBound(to: CChar.self) + } +} + +/// Objective-C type encoding. +/// +/// The enum does not cover all options, but only those that are expressive in +/// Swift. +internal enum ObjCTypeEncoding: Int8 { + case char = 99 + case int = 105 + case short = 115 + case long = 108 + case longLong = 113 + + case unsignedChar = 67 + case unsignedInt = 73 + case unsignedShort = 83 + case unsignedLong = 76 + case unsignedLongLong = 81 + + case float = 102 + case double = 100 + + case bool = 66 + + case object = 64 + case type = 35 + case selector = 58 + + case undefined = -1 +} diff --git a/ReactiveCocoa/ObjC+Messages.swift b/ReactiveCocoa/ObjC+Messages.swift new file mode 100644 index 0000000000..2668b73948 --- /dev/null +++ b/ReactiveCocoa/ObjC+Messages.swift @@ -0,0 +1,52 @@ +// Unavailable classes like `NSInvocation` can still be passed into Swift as +// `AnyClass` and `AnyObject`, and receive messages as `AnyClass` and +// `AnyObject` existentials. +// +// These `@objc` protocols host the method signatures so that they can be used +// with `AnyObject`. + +import Foundation + +internal let NSInvocation: AnyClass = NSClassFromString("NSInvocation")! +internal let NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")! + +// Signatures defined in `@objc` protocols would be available for ObjC message +// sending via `AnyObject`. +@objc internal protocol ObjCClassReporting { + // An alias for `-class`, which is unavailable in Swift. + @objc(class) + var objcClass: AnyClass! { get } + + @objc(methodSignatureForSelector:) + func objcMethodSignature(for selector: Selector) -> AnyObject +} + +// Methods of `NSInvocation`. +@objc internal protocol ObjCInvocation { + @objc(setSelector:) + func objcSetSelector(_ selector: Selector) + + @objc(methodSignature) + var objcMethodSignature: AnyObject { get } + + @objc(getArgument:atIndex:) + func objcCopy(to buffer: UnsafeMutableRawPointer?, forArgumentAt index: Int) + + @objc(invoke) + func objcInvoke() + + @objc(invocationWithMethodSignature:) + static func objcInvocation(withMethodSignature signature: AnyObject) -> AnyObject +} + +// Methods of `NSMethodSignature`. +@objc internal protocol ObjCMethodSignature { + @objc(numberOfArguments) + var objcNumberOfArguments: UInt { get } + + @objc(getArgumentTypeAtIndex:) + func objcArgumentType(at index: UInt) -> UnsafePointer + + @objc(signatureWithObjCTypes:) + static func objcSignature(withObjCTypes typeEncoding: UnsafePointer) -> AnyObject +} diff --git a/ReactiveCocoa/ObjC+Runtime.swift b/ReactiveCocoa/ObjC+Runtime.swift new file mode 100644 index 0000000000..71eef6b795 --- /dev/null +++ b/ReactiveCocoa/ObjC+Runtime.swift @@ -0,0 +1,27 @@ +import Foundation + +/// Search in `class` for any method that matches the supplied selector without +/// propagating to the ancestors. +/// +/// - parameters: +/// - class: The class to search the method in. +/// - selector: The selector of the method. +/// +/// - returns: The matching method, or `nil` if none is found. +internal func class_getImmediateMethod(_ `class`: AnyClass, _ selector: Selector) -> Method? { + var total: UInt32 = 0 + + if let methods = class_copyMethodList(`class`, &total) { + defer { free(methods) } + + for index in 0 ..< Int(total) { + let method = methods[index] + + if method_getName(method) == selector { + return method + } + } + } + + return nil +} diff --git a/ReactiveCocoa/ObjC+RuntimeSubclassing.swift b/ReactiveCocoa/ObjC+RuntimeSubclassing.swift new file mode 100644 index 0000000000..95c151ede6 --- /dev/null +++ b/ReactiveCocoa/ObjC+RuntimeSubclassing.swift @@ -0,0 +1,132 @@ +import Foundation +#if SWIFT_PACKAGE +import ReactiveCocoaObjC +#endif +import ReactiveSwift + +/// Whether the runtime subclass has already been swizzled. +fileprivate let runtimeSubclassedKey = AssociationKey(default: false) + +/// A known RAC runtime subclass of the instance. `nil` if the runtime subclass +/// has not been requested for the instance before. +fileprivate let knownRuntimeSubclassKey = AssociationKey(default: nil) + +extension NSObject { + /// Swizzle the given selectors. + /// + /// - warning: The swizzling **does not** apply on a per-instance basis. In + /// other words, repetitive swizzling of the same selector would + /// overwrite previous swizzling attempts, despite a different + /// instance being supplied. + /// + /// - parameters: + /// - pairs: Tuples of selectors and the respective implementions to be + /// swapped in. + /// - key: An association key which determines if the swizzling has already + /// been performed. + internal func swizzle(_ pairs: (Selector, Any)..., key hasSwizzledKey: AssociationKey) { + let subclass: AnyClass = swizzleClass(self) + + ReactiveCocoa.synchronized(subclass) { + let subclassAssociations = Associations(subclass as AnyObject) + + if !subclassAssociations.value(forKey: hasSwizzledKey) { + subclassAssociations.setValue(true, forKey: hasSwizzledKey) + + for (selector, body) in pairs { + let method = class_getInstanceMethod(subclass, selector)! + let typeEncoding = method_getTypeEncoding(method)! + + if method_getImplementation(method) == _rac_objc_msgForward { + let succeeds = class_addMethod(subclass, selector.interopAlias, imp_implementationWithBlock(body), typeEncoding) + precondition(succeeds, "RAC attempts to swizzle a selector that has message forwarding enabled with a runtime injected implementation. This is unsupported in the current version.") + } else { + let succeeds = class_addMethod(subclass, selector, imp_implementationWithBlock(body), typeEncoding) + precondition(succeeds, "RAC attempts to swizzle a selector that has already a runtime injected implementation. This is unsupported in the current version.") + } + } + } + } + } +} + +/// ISA-swizzle the class of the supplied instance. +/// +/// - note: If the instance has already been isa-swizzled, the swizzling happens +/// in place in the runtime subclass created by external parties. +/// +/// - warning: The swizzling **does not** apply on a per-instance basis. In +/// other words, repetitive swizzling of the same selector would +/// overwrite previous swizzling attempts, despite a different +/// instance being supplied. +/// +/// - parameters: +/// - instance: The instance to be swizzled. +/// +/// - returns: The runtime subclass of the perceived class of the instance. +internal func swizzleClass(_ instance: NSObject) -> AnyClass { + if let knownSubclass = instance.associations.value(forKey: knownRuntimeSubclassKey) { + return knownSubclass + } + + let perceivedClass: AnyClass = instance.objcClass + let realClass: AnyClass = object_getClass(instance)! + let realClassAssociations = Associations(realClass as AnyObject) + + if perceivedClass != realClass { + // 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 at runtime. + synchronized(realClass) { + let isSwizzled = realClassAssociations.value(forKey: runtimeSubclassedKey) + if !isSwizzled { + replaceGetClass(in: realClass, decoy: perceivedClass) + realClassAssociations.setValue(true, forKey: runtimeSubclassedKey) + } + } + + return realClass + } else { + let name = subclassName(of: perceivedClass) + let subclass: AnyClass = name.withCString { cString in + if let existingClass = objc_getClass(cString) as! AnyClass? { + return existingClass + } else { + let subclass: AnyClass = objc_allocateClassPair(perceivedClass, cString, 0)! + replaceGetClass(in: subclass, decoy: perceivedClass) + objc_registerClassPair(subclass) + return subclass + } + } + + object_setClass(instance, subclass) + instance.associations.setValue(subclass, forKey: knownRuntimeSubclassKey) + return subclass + } +} + +private func subclassName(of class: AnyClass) -> String { + return String(cString: class_getName(`class`)).appending("_RACSwift") +} + +/// Swizzle the `-class` and `+class` methods. +/// +/// - parameters: +/// - class: The class to swizzle. +/// - perceivedClass: The class to be reported by the methods. +private func replaceGetClass(in class: AnyClass, decoy perceivedClass: AnyClass) { + let getClass: @convention(block) (UnsafeRawPointer?) -> AnyClass = { _ in + return perceivedClass + } + + let impl = imp_implementationWithBlock(getClass as Any) + + _ = class_replaceMethod(`class`, + ObjCSelector.getClass, + impl, + ObjCMethodEncoding.getClass) + + _ = class_replaceMethod(object_getClass(`class`), + ObjCSelector.getClass, + impl, + ObjCMethodEncoding.getClass) +} diff --git a/ReactiveCocoa/ObjC+Selector.swift b/ReactiveCocoa/ObjC+Selector.swift new file mode 100644 index 0000000000..78c3ad0f66 --- /dev/null +++ b/ReactiveCocoa/ObjC+Selector.swift @@ -0,0 +1,45 @@ +import Foundation + +extension Selector { + /// `self` as a pointer. It is uniqued across instances, similar to + /// `StaticString`. + internal var utf8Start: UnsafePointer { + return unsafeBitCast(self, to: UnsafePointer.self) + } + + /// An alias of `self`, used in method interception. + internal var alias: Selector { + return prefixing("rac0_") + } + + /// An alias of `self`, used in method interception specifically for + /// preserving (if found) an immediate implementation of `self` in the + /// runtime subclass. + internal var interopAlias: Selector { + return prefixing("rac1_") + } + + /// An alias of `self`, used for delegate proxies. + internal var delegateProxyAlias: Selector { + return prefixing("rac2_") + } + + internal func prefixing(_ prefix: StaticString) -> Selector { + let length = Int(strlen(utf8Start)) + let prefixedLength = length + prefix.utf8CodeUnitCount + + let asciiPrefix = UnsafeRawPointer(prefix.utf8Start).assumingMemoryBound(to: Int8.self) + + let cString = UnsafeMutablePointer.allocate(capacity: prefixedLength + 1) + defer { + cString.deinitialize(count: prefixedLength + 1) + cString.deallocate() + } + + cString.initialize(from: asciiPrefix, count: prefix.utf8CodeUnitCount) + (cString + prefix.utf8CodeUnitCount).initialize(from: utf8Start, count: length) + (cString + prefixedLength).initialize(to: Int8(UInt8(ascii: "\0"))) + + return sel_registerName(cString) + } +} diff --git a/ReactiveCocoa/Objective-C/MKAnnotationView+RACSignalSupport.h b/ReactiveCocoa/Objective-C/MKAnnotationView+RACSignalSupport.h deleted file mode 100644 index 2158972760..0000000000 --- a/ReactiveCocoa/Objective-C/MKAnnotationView+RACSignalSupport.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// MKAnnotationView+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Zak Remer on 3/31/15. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -#import -#import - -@class RACSignal; - -@interface MKAnnotationView (RACSignalSupport) - -/// A signal which will send a RACUnit whenever -prepareForReuse is invoked upon -/// the receiver. -/// -/// Examples -/// -/// [[[self.cancelButton -/// rac_signalForControlEvents:UIControlEventTouchUpInside] -/// takeUntil:self.rac_prepareForReuseSignal] -/// subscribeNext:^(UIButton *x) { -/// // do other things -/// }]; -@property (nonatomic, strong, readonly) RACSignal *rac_prepareForReuseSignal; - -@end diff --git a/ReactiveCocoa/Objective-C/MKAnnotationView+RACSignalSupport.m b/ReactiveCocoa/Objective-C/MKAnnotationView+RACSignalSupport.m deleted file mode 100644 index 931f81a039..0000000000 --- a/ReactiveCocoa/Objective-C/MKAnnotationView+RACSignalSupport.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// MKAnnotationView+RACSignalSupport.m -// ReactiveCocoa -// -// Created by Zak Remer on 3/31/15. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -#import "MKAnnotationView+RACSignalSupport.h" -#import "NSObject+RACDescription.h" -#import "NSObject+RACSelectorSignal.h" -#import "RACSignal+Operations.h" -#import "RACUnit.h" -#import - -@implementation MKAnnotationView (RACSignalSupport) - -- (RACSignal *)rac_prepareForReuseSignal { - RACSignal *signal = objc_getAssociatedObject(self, _cmd); - if (signal != nil) return signal; - - signal = [[[self - rac_signalForSelector:@selector(prepareForReuse)] - mapReplace:RACUnit.defaultUnit] - setNameWithFormat:@"%@ -rac_prepareForReuseSignal", RACDescription(self)]; - - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSArray+RACSequenceAdditions.h b/ReactiveCocoa/Objective-C/NSArray+RACSequenceAdditions.h deleted file mode 100644 index d2d0b3fe61..0000000000 --- a/ReactiveCocoa/Objective-C/NSArray+RACSequenceAdditions.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// NSArray+RACSequenceAdditions.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-29. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSArray+RACSequenceAdditions.m b/ReactiveCocoa/Objective-C/NSArray+RACSequenceAdditions.m deleted file mode 100644 index ca2b9511a4..0000000000 --- a/ReactiveCocoa/Objective-C/NSArray+RACSequenceAdditions.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSControl+RACCommandSupport.h b/ReactiveCocoa/Objective-C/NSControl+RACCommandSupport.h deleted file mode 100644 index fecc511352..0000000000 --- a/ReactiveCocoa/Objective-C/NSControl+RACCommandSupport.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// NSControl+RACCommandSupport.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/3/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@class RACCommand<__contravariant InputType>; - -@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<__kindof NSControl *> *rac_command; - -@end diff --git a/ReactiveCocoa/Objective-C/NSControl+RACCommandSupport.m b/ReactiveCocoa/Objective-C/NSControl+RACCommandSupport.m deleted file mode 100644 index 5d9db01f8f..0000000000 --- a/ReactiveCocoa/Objective-C/NSControl+RACCommandSupport.m +++ /dev/null @@ -1,57 +0,0 @@ -// -// 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 "RACCommand.h" -#import "RACScopedDisposable.h" -#import "RACSignal+Operations.h" -#import - -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/ReactiveCocoa/Objective-C/NSControl+RACTextSignalSupport.h b/ReactiveCocoa/Objective-C/NSControl+RACTextSignalSupport.h deleted file mode 100644 index 3d3618d849..0000000000 --- a/ReactiveCocoa/Objective-C/NSControl+RACTextSignalSupport.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// NSControl+RACTextSignalSupport.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-03-08. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSControl+RACTextSignalSupport.m b/ReactiveCocoa/Objective-C/NSControl+RACTextSignalSupport.m deleted file mode 100644 index 6a75402976..0000000000 --- a/ReactiveCocoa/Objective-C/NSControl+RACTextSignalSupport.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// 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 -#import "NSObject+RACDescription.h" -#import "RACDisposable.h" -#import "RACSignal.h" -#import "RACSubscriber.h" - -@implementation NSControl (RACTextSignalSupport) - -- (RACSignal *)rac_textSignal { - @weakify(self); - return [[[[RACSignal - createSignal:^(id 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", RACDescription(self)]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSData+RACSupport.h b/ReactiveCocoa/Objective-C/NSData+RACSupport.h deleted file mode 100644 index 0e47f8a39f..0000000000 --- a/ReactiveCocoa/Objective-C/NSData+RACSupport.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// NSData+RACSupport.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 5/11/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@class RACScheduler; -@class RACSignal; - -@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/ReactiveCocoa/Objective-C/NSData+RACSupport.m b/ReactiveCocoa/Objective-C/NSData+RACSupport.m deleted file mode 100644 index 21b5185340..0000000000 --- a/ReactiveCocoa/Objective-C/NSData+RACSupport.m +++ /dev/null @@ -1,35 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSDictionary+RACSequenceAdditions.h b/ReactiveCocoa/Objective-C/NSDictionary+RACSequenceAdditions.h deleted file mode 100644 index 4871fe722a..0000000000 --- a/ReactiveCocoa/Objective-C/NSDictionary+RACSequenceAdditions.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// NSDictionary+RACSequenceAdditions.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-29. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSDictionary+RACSequenceAdditions.m b/ReactiveCocoa/Objective-C/NSDictionary+RACSequenceAdditions.m deleted file mode 100644 index e0e3b9297d..0000000000 --- a/ReactiveCocoa/Objective-C/NSDictionary+RACSequenceAdditions.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// 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 RACTuplePack(key, value); - }]; -} - -- (RACSequence *)rac_keySequence { - return self.allKeys.rac_sequence; -} - -- (RACSequence *)rac_valueSequence { - return self.allValues.rac_sequence; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSEnumerator+RACSequenceAdditions.h b/ReactiveCocoa/Objective-C/NSEnumerator+RACSequenceAdditions.h deleted file mode 100644 index 1d8fc84523..0000000000 --- a/ReactiveCocoa/Objective-C/NSEnumerator+RACSequenceAdditions.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// NSEnumerator+RACSequenceAdditions.h -// ReactiveCocoa -// -// Created by Uri Baghin on 07/01/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSEnumerator+RACSequenceAdditions.m b/ReactiveCocoa/Objective-C/NSEnumerator+RACSequenceAdditions.m deleted file mode 100644 index aa56eaaa69..0000000000 --- a/ReactiveCocoa/Objective-C/NSEnumerator+RACSequenceAdditions.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSFileHandle+RACSupport.h b/ReactiveCocoa/Objective-C/NSFileHandle+RACSupport.h deleted file mode 100644 index 985398de6b..0000000000 --- a/ReactiveCocoa/Objective-C/NSFileHandle+RACSupport.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// NSFileHandle+RACSupport.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 5/10/12. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSFileHandle+RACSupport.m b/ReactiveCocoa/Objective-C/NSFileHandle+RACSupport.m deleted file mode 100644 index eaee72ec59..0000000000 --- a/ReactiveCocoa/Objective-C/NSFileHandle+RACSupport.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// 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 "NSObject+RACDescription.h" -#import "RACReplaySubject.h" -#import "RACDisposable.h" - -@implementation NSFileHandle (RACSupport) - -- (RACSignal *)rac_readInBackground { - RACReplaySubject *subject = [RACReplaySubject subject]; - [subject setNameWithFormat:@"%@ -rac_readInBackground", RACDescription(self)]; - - RACSignal *dataNotification = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:NSFileHandleReadCompletionNotification object:self] map:^(NSNotification *note) { - return note.userInfo[NSFileHandleNotificationDataItem]; - }]; - - __block RACDisposable *subscription = [dataNotification subscribeNext:^(NSData *data) { - if (data.length > 0) { - [subject sendNext:data]; - [self readInBackgroundAndNotify]; - } else { - [subject sendCompleted]; - [subscription dispose]; - } - }]; - - [self readInBackgroundAndNotify]; - - return subject; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSIndexSet+RACSequenceAdditions.h b/ReactiveCocoa/Objective-C/NSIndexSet+RACSequenceAdditions.h deleted file mode 100644 index 4fe3820fca..0000000000 --- a/ReactiveCocoa/Objective-C/NSIndexSet+RACSequenceAdditions.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// NSIndexSet+RACSequenceAdditions.h -// ReactiveCocoa -// -// Created by Sergey Gavrilyuk on 12/17/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@class RACSequence; - -@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/ReactiveCocoa/Objective-C/NSIndexSet+RACSequenceAdditions.m b/ReactiveCocoa/Objective-C/NSIndexSet+RACSequenceAdditions.m deleted file mode 100644 index b3557e64b4..0000000000 --- a/ReactiveCocoa/Objective-C/NSIndexSet+RACSequenceAdditions.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSInvocation+RACTypeParsing.h b/ReactiveCocoa/Objective-C/NSInvocation+RACTypeParsing.h deleted file mode 100644 index d1e72bf1ff..0000000000 --- a/ReactiveCocoa/Objective-C/NSInvocation+RACTypeParsing.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// NSInvocation+RACTypeParsing.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 11/17/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSInvocation+RACTypeParsing.m b/ReactiveCocoa/Objective-C/NSInvocation+RACTypeParsing.m deleted file mode 100644 index 9edcdd7c53..0000000000 --- a/ReactiveCocoa/Objective-C/NSInvocation+RACTypeParsing.m +++ /dev/null @@ -1,232 +0,0 @@ -// -// 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 - -@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/ReactiveCocoa/Objective-C/NSNotificationCenter+RACSupport.h b/ReactiveCocoa/Objective-C/NSNotificationCenter+RACSupport.h deleted file mode 100644 index ddb3954d01..0000000000 --- a/ReactiveCocoa/Objective-C/NSNotificationCenter+RACSupport.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// NSNotificationCenter+RACSupport.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 5/10/12. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSNotificationCenter+RACSupport.m b/ReactiveCocoa/Objective-C/NSNotificationCenter+RACSupport.m deleted file mode 100644 index 7147f0b5e1..0000000000 --- a/ReactiveCocoa/Objective-C/NSNotificationCenter+RACSupport.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// NSNotificationCenter+RACSupport.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 5/10/12. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import "NSNotificationCenter+RACSupport.h" -#import -#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 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/ReactiveCocoa/Objective-C/NSObject+RACAppKitBindings.h b/ReactiveCocoa/Objective-C/NSObject+RACAppKitBindings.h deleted file mode 100644 index 5583d4a6cf..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACAppKitBindings.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// NSObject+RACAppKitBindings.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/17/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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 (RACUnavailableAppKitBindings) - -- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath __attribute__((unavailable("Use -rac_bind:options: instead"))); -- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath nilValue:(id)nilValue __attribute__((unavailable("Use -rac_bind:options: instead"))); -- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath transform:(id (^)(id value))transformBlock __attribute__((unavailable("Use -rac_bind:options: instead"))); -- (void)rac_bind:(NSString *)binding toObject:(id)object withNegatedKeyPath:(NSString *)keyPath __attribute__((unavailable("Use -rac_bind:options: instead"))); - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACAppKitBindings.m b/ReactiveCocoa/Objective-C/NSObject+RACAppKitBindings.m deleted file mode 100644 index 3c2e2fc880..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACAppKitBindings.m +++ /dev/null @@ -1,147 +0,0 @@ -// -// 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 -#import -#import "NSObject+RACDeallocating.h" -#import "RACChannel.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACKVOChannel.h" -#import "RACValueTransformer.h" -#import - -// 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 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 diff --git a/ReactiveCocoa/Objective-C/NSObject+RACDeallocating.h b/ReactiveCocoa/Objective-C/NSObject+RACDeallocating.h deleted file mode 100644 index 1daccf8971..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACDeallocating.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// NSObject+RACDeallocating.h -// ReactiveCocoa -// -// Created by Kazuo Koga on 2013/03/15. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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 (RACUnavailableDeallocating) - -- (RACSignal *)rac_didDeallocSignal __attribute__((unavailable("Use -rac_willDeallocSignal"))); -- (void)rac_addDeallocDisposable:(RACDisposable *)disposable __attribute__((unavailable("Add disposables to -rac_deallocDisposable instead"))); - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACDeallocating.m b/ReactiveCocoa/Objective-C/NSObject+RACDeallocating.m deleted file mode 100644 index 01b402a786..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACDeallocating.m +++ /dev/null @@ -1,103 +0,0 @@ -// -// 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 -#import - -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 diff --git a/ReactiveCocoa/Objective-C/NSObject+RACDescription.h b/ReactiveCocoa/Objective-C/NSObject+RACDescription.h deleted file mode 100644 index 761205ea0f..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACDescription.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// NSObject+RACDescription.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-05-13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -// A simplified description of the object, which does not invoke -description -// (and thus should be much faster in many cases). -// -// This is for debugging purposes only, and will return a constant string -// unless the RAC_DEBUG_SIGNAL_NAMES environment variable is set. -NSString *RACDescription(id object); diff --git a/ReactiveCocoa/Objective-C/NSObject+RACDescription.m b/ReactiveCocoa/Objective-C/NSObject+RACDescription.m deleted file mode 100644 index ac7a735fff..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACDescription.m +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 NSValue (RACDescription) - -- (NSString *)rac_description { - return self.description; -} - -@end - -@implementation NSString (RACDescription) - -- (NSString *)rac_description { - return self.description; -} - -@end - -@implementation RACTuple (RACDescription) - -- (NSString *)rac_description { - if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { - return self.allObjects.description; - } else { - return @"(description skipped)"; - } -} - -@end - -NSString *RACDescription(id object) { - if (getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL) { - if ([object respondsToSelector:@selector(rac_description)]) { - return [object rac_description]; - } else { - return [[NSString alloc] initWithFormat:@"<%@: %p>", [object class], object]; - } - } else { - return @"(description skipped)"; - } -} diff --git a/ReactiveCocoa/Objective-C/NSObject+RACKVOWrapper.h b/ReactiveCocoa/Objective-C/NSObject+RACKVOWrapper.h deleted file mode 100644 index 4f7177df6b..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACKVOWrapper.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// NSObject+RACKVOWrapper.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/11/11. -// Copyright (c) 2011 GitHub. All rights reserved. -// - -#import - -@class RACDisposable; -@class RACKVOTrampoline; - -// A private category providing a block based interface to KVO. -@interface NSObject (RACKVOWrapper) - -// Adds the given block as the callbacks for when the key path changes. -// -// Unlike direct KVO observation, this handles deallocation of `weak` properties -// by generating an appropriate notification. This will only occur if there is -// an `@property` declaration visible in the observed class, with the `weak` -// memory management attribute. -// -// The observation does not need to be explicitly removed. It will be removed -// when the observer or the receiver deallocate. -// -// keyPath - The key path to observe. Must not be nil. -// options - The KVO observation options. -// observer - The object that requested the observation. May be nil. -// block - The block called when the value at the key path changes. It is -// passed the current value of the key path and the extended KVO -// change dictionary including RAC-specific keys and values. Must not -// be nil. -// -// Returns a disposable that can be used to stop the observation. -- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer block:(void (^)(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent))block; - -@end - -typedef void (^RACKVOBlock)(id target, id observer, NSDictionary *change); - -@interface NSObject (RACUnavailableKVOWrapper) - -- (RACKVOTrampoline *)rac_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block __attribute((unavailable("Use rac_observeKeyPath:options:observer:block: instead."))); - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACKVOWrapper.m b/ReactiveCocoa/Objective-C/NSObject+RACKVOWrapper.m deleted file mode 100644 index 4a75d8116f..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACKVOWrapper.m +++ /dev/null @@ -1,200 +0,0 @@ -// -// NSObject+RACKVOWrapper.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/11/11. -// Copyright (c) 2011 GitHub. All rights reserved. -// - -#import "NSObject+RACKVOWrapper.h" -#import -#import -#import "NSObject+RACDeallocating.h" -#import "NSString+RACKeyPathUtilities.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACKVOTrampoline.h" -#import "RACSerialDisposable.h" - -@implementation NSObject (RACKVOWrapper) - -- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block { - NSCParameterAssert(block != nil); - NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); - - keyPath = [keyPath copy]; - - NSObject *strongObserver = weakObserver; - - NSArray *keyPathComponents = keyPath.rac_keyPathComponents; - BOOL keyPathHasOneComponent = (keyPathComponents.count == 1); - NSString *keyPathHead = keyPathComponents[0]; - NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent; - - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - - // The disposable that groups all disposal necessary to clean up the callbacks - // added to the value of the first key path component. - RACSerialDisposable *firstComponentSerialDisposable = [RACSerialDisposable serialDisposableWithDisposable:[RACCompoundDisposable compoundDisposable]]; - RACCompoundDisposable * (^firstComponentDisposable)(void) = ^{ - return (RACCompoundDisposable *)firstComponentSerialDisposable.disposable; - }; - - [disposable addDisposable:firstComponentSerialDisposable]; - - BOOL shouldAddDeallocObserver = NO; - - objc_property_t property = class_getProperty(object_getClass(self), keyPathHead.UTF8String); - if (property != NULL) { - rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property); - if (attributes != NULL) { - @onExit { - free(attributes); - }; - - BOOL isObject = attributes->objectClass != nil || strstr(attributes->type, @encode(id)) == attributes->type; - BOOL isProtocol = attributes->objectClass == NSClassFromString(@"Protocol"); - BOOL isBlock = strcmp(attributes->type, @encode(void(^)())) == 0; - BOOL isWeak = attributes->weak; - - // If this property isn't actually an object (or is a Class object), - // no point in observing the deallocation of the wrapper returned by - // KVC. - // - // If this property is an object, but not declared `weak`, we - // don't need to watch for it spontaneously being set to nil. - // - // Attempting to observe non-weak properties will result in - // broken behavior for dynamic getters, so don't even try. - shouldAddDeallocObserver = isObject && isWeak && !isBlock && !isProtocol; - } - } - - // Adds the callback block to the value's deallocation. Also adds the logic to - // clean up the callback to the firstComponentDisposable. - void (^addDeallocObserverToPropertyValue)(NSObject *) = ^(NSObject *value) { - if (!shouldAddDeallocObserver) return; - - // If a key path value is the observer, commonly when a key path begins - // with "self", we prevent deallocation triggered callbacks for any such key - // path components. Thus, the observer's deallocation is not considered a - // change to the key path. - if (value == weakObserver) return; - - NSDictionary *change = @{ - NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), - NSKeyValueChangeNewKey: NSNull.null, - }; - - RACCompoundDisposable *valueDisposable = value.rac_deallocDisposable; - RACDisposable *deallocDisposable = [RACDisposable disposableWithBlock:^{ - block(nil, change, YES, keyPathHasOneComponent); - }]; - - [valueDisposable addDisposable:deallocDisposable]; - [firstComponentDisposable() addDisposable:[RACDisposable disposableWithBlock:^{ - [valueDisposable removeDisposable:deallocDisposable]; - }]]; - }; - - // Adds the callback block to the remaining path components on the value. Also - // adds the logic to clean up the callbacks to the firstComponentDisposable. - void (^addObserverToValue)(NSObject *) = ^(NSObject *value) { - RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block]; - [firstComponentDisposable() addDisposable:observerDisposable]; - }; - - // Observe only the first key path component, when the value changes clean up - // the callbacks on the old value, add callbacks to the new value and call the - // callback block as needed. - // - // Note this does not use NSKeyValueObservingOptionInitial so this only - // handles changes to the value, callbacks to the initial value must be added - // separately. - NSKeyValueObservingOptions trampolineOptions = (options | NSKeyValueObservingOptionPrior) & ~NSKeyValueObservingOptionInitial; - RACKVOTrampoline *trampoline = [[RACKVOTrampoline alloc] initWithTarget:self observer:strongObserver keyPath:keyPathHead options:trampolineOptions block:^(id trampolineTarget, id trampolineObserver, NSDictionary *change) { - // If this is a prior notification, clean up all the callbacks added to the - // previous value and call the callback block. Everything else is deferred - // until after we get the notification after the change. - if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { - [firstComponentDisposable() dispose]; - - if ((options & NSKeyValueObservingOptionPrior) != 0) { - block([trampolineTarget valueForKeyPath:keyPath], change, NO, keyPathHasOneComponent); - } - - return; - } - - // From here the notification is not prior. - NSObject *value = [trampolineTarget valueForKey:keyPathHead]; - - // If the value has changed but is nil, there is no need to add callbacks to - // it, just call the callback block. - if (value == nil) { - block(nil, change, NO, keyPathHasOneComponent); - return; - } - - // From here the notification is not prior and the value is not nil. - - // Create a new firstComponentDisposable while getting rid of the old one at - // the same time, in case this is being called concurrently. - RACDisposable *oldFirstComponentDisposable = [firstComponentSerialDisposable swapInDisposable:[RACCompoundDisposable compoundDisposable]]; - [oldFirstComponentDisposable dispose]; - - addDeallocObserverToPropertyValue(value); - - // If there are no further key path components, there is no need to add the - // other callbacks, just call the callback block with the value itself. - if (keyPathHasOneComponent) { - block(value, change, NO, keyPathHasOneComponent); - return; - } - - // The value has changed, is not nil, and there are more key path components - // to consider. Add the callbacks to the value for the remaining key path - // components and call the callback block with the current value of the full - // key path. - addObserverToValue(value); - block([value valueForKeyPath:keyPathTail], change, NO, keyPathHasOneComponent); - }]; - - // Stop the KVO observation when this one is disposed of. - [disposable addDisposable:trampoline]; - - // Add the callbacks to the initial value if needed. - NSObject *value = [self valueForKey:keyPathHead]; - if (value != nil) { - addDeallocObserverToPropertyValue(value); - - if (!keyPathHasOneComponent) { - addObserverToValue(value); - } - } - - // Call the block with the initial value if needed. - if ((options & NSKeyValueObservingOptionInitial) != 0) { - id initialValue = [self valueForKeyPath:keyPath]; - NSDictionary *initialChange = @{ - NSKeyValueChangeKindKey: @(NSKeyValueChangeSetting), - NSKeyValueChangeNewKey: initialValue ?: NSNull.null, - }; - block(initialValue, initialChange, NO, keyPathHasOneComponent); - } - - - RACCompoundDisposable *observerDisposable = strongObserver.rac_deallocDisposable; - RACCompoundDisposable *selfDisposable = self.rac_deallocDisposable; - // Dispose of this observation if the receiver or the observer deallocate. - [observerDisposable addDisposable:disposable]; - [selfDisposable addDisposable:disposable]; - - return [RACDisposable disposableWithBlock:^{ - [disposable dispose]; - [observerDisposable removeDisposable:disposable]; - [selfDisposable removeDisposable:disposable]; - }]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACLifting.h b/ReactiveCocoa/Objective-C/NSObject+RACLifting.h deleted file mode 100644 index 5c0c29755d..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACLifting.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// NSObject+RACLifting.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/13/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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; - -/// Like -rac_liftSelector:withSignals:, but accepts a signal sending tuples of -/// arguments instead of a variadic list of arguments. -- (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments; - -@end - -@interface NSObject (RACUnavailableLifting) - -- (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... __attribute__((unavailable("Use -rac_liftSelector:withSignals: instead"))); -- (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args __attribute__((unavailable("Use -rac_liftSelector:withSignalsFromArray: instead"))); -- (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... NS_REQUIRES_NIL_TERMINATION __attribute__((unavailable("Use +combineLatest:reduce: instead"))); -- (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args __attribute__((unavailable("Use +combineLatest:reduce: instead"))); - -- (instancetype)rac_lift __attribute__((unavailable("Use -rac_liftSelector:withSignals: instead"))); - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACLifting.m b/ReactiveCocoa/Objective-C/NSObject+RACLifting.m deleted file mode 100644 index 0cb209a0f9..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACLifting.m +++ /dev/null @@ -1,78 +0,0 @@ -// -// NSObject+RACLifting.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/13/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "NSObject+RACLifting.h" -#import -#import "NSInvocation+RACTypeParsing.h" -#import "NSObject+RACDeallocating.h" -#import "NSObject+RACDescription.h" -#import "RACSignal+Operations.h" -#import "RACTuple.h" - -@implementation NSObject (RACLifting) - -- (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments { - NSCParameterAssert(selector != NULL); - NSCParameterAssert(arguments != nil); - - @unsafeify(self); - - NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; - NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); - - return [[[[arguments - takeUntil:self.rac_willDeallocSignal] - map:^(RACTuple *arguments) { - @strongify(self); - - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - invocation.selector = selector; - invocation.rac_argumentsTuple = arguments; - [invocation invokeWithTarget:self]; - - return invocation.rac_returnValue; - }] - replayLast] - setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsOfArguments: %@", RACDescription(self), sel_getName(selector), arguments]; -} - -- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals { - NSCParameterAssert(signals != nil); - NSCParameterAssert(signals.count > 0); - - NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector]; - NSCAssert(methodSignature != nil, @"%@ does not respond to %@", self, NSStringFromSelector(selector)); - - NSUInteger numberOfArguments __attribute__((unused)) = methodSignature.numberOfArguments - 2; - NSCAssert(numberOfArguments == signals.count, @"Wrong number of signals for %@ (expected %lu, got %lu)", NSStringFromSelector(selector), (unsigned long)numberOfArguments, (unsigned long)signals.count); - - return [[self - rac_liftSelector:selector withSignalOfArguments:[RACSignal combineLatest:signals]] - setNameWithFormat:@"%@ -rac_liftSelector: %s withSignalsFromArray: %@", RACDescription(self), sel_getName(selector), signals]; -} - -- (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... { - NSCParameterAssert(firstSignal != nil); - - NSMutableArray *signals = [NSMutableArray array]; - - va_list args; - va_start(args, firstSignal); - for (id currentSignal = firstSignal; currentSignal != nil; currentSignal = va_arg(args, id)) { - NSCAssert([currentSignal isKindOfClass:RACSignal.class], @"Argument %@ is not a RACSignal", currentSignal); - - [signals addObject:currentSignal]; - } - va_end(args); - - return [[self - rac_liftSelector:selector withSignalsFromArray:signals] - setNameWithFormat:@"%@ -rac_liftSelector: %s withSignals: %@", RACDescription(self), sel_getName(selector), signals]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACPropertySubscribing.h b/ReactiveCocoa/Objective-C/NSObject+RACPropertySubscribing.h deleted file mode 100644 index fd64f0c77b..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACPropertySubscribing.h +++ /dev/null @@ -1,117 +0,0 @@ -// -// NSObject+RACPropertySubscribing.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/2/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import -#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) \ - ({ \ - _Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \ - __weak id target_ = (TARGET); \ - [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \ - _Pragma("clang diagnostic pop") \ - }) - -@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. -#if OS_OBJECT_HAVE_OBJC_SUPPORT -- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer; -#else -// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :( -- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer; -#endif - -/// Creates a signal to observe the changes of the given key path. -/// -/// The initial value is sent on subscription if `NSKeyValueObservingOptionInitial` is set. -/// The subsequent values are sent from whichever thread the change occured on, -/// even if it doesn't have a valid scheduler. -/// -/// Returns a signal that sends tuples containing the current value at the key -/// path and the change dictionary for each KVO callback. -#if OS_OBJECT_HAVE_OBJC_SUPPORT -- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer; -#else -- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer; -#endif - -@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 (RACUnavailablePropertySubscribing) - -+ (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((unavailable("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); -+ (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((unavailable("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); -+ (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer __attribute__((unavailable("Use -rac_valuesAndChangesForKeyPath:options:observer: instead."))); -- (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((unavailable("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); -- (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((unavailable("Use -rac_valuesForKeyPath:observer: or RACObserve() instead."))); -- (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal __attribute__((unavailable("Use -[RACSignal setKeyPath:onObject:] instead"))); - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACPropertySubscribing.m b/ReactiveCocoa/Objective-C/NSObject+RACPropertySubscribing.m deleted file mode 100644 index a5fcecdd7f..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACPropertySubscribing.m +++ /dev/null @@ -1,84 +0,0 @@ -// -// 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 -#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 - -@implementation NSObject (RACPropertySubscribing) - -- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer { - return [[[self - rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] - map:^(RACTuple *value) { - // -map: because it doesn't require the block trampoline that -reduceEach: uses - return value[0]; - }] - setNameWithFormat:@"RACObserve(%@, %@)", RACDescription(self), keyPath]; -} - -- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver { - NSObject *strongObserver = weakObserver; - keyPath = [keyPath copy]; - - NSRecursiveLock *objectLock = [[NSRecursiveLock alloc] init]; - objectLock.name = @"org.reactivecocoa.ReactiveCocoa.NSObjectRACPropertySubscribing"; - - __weak NSObject *weakSelf = self; - - RACSignal *deallocSignal = [[RACSignal - zip:@[ - self.rac_willDeallocSignal, - strongObserver.rac_willDeallocSignal ?: [RACSignal never] - ]] - doCompleted:^{ - // Forces deallocation to wait if the object variables are currently - // being read on another thread. - [objectLock lock]; - @onExit { - [objectLock unlock]; - }; - }]; - - return [[[RACSignal - createSignal:^ RACDisposable * (id subscriber) { - // Hold onto the lock the whole time we're setting up the KVO - // observation, because any resurrection that might be caused by our - // retaining below must be balanced out by the time -dealloc returns - // (if another thread is waiting on the lock above). - [objectLock lock]; - @onExit { - [objectLock unlock]; - }; - - __strong NSObject *observer __attribute__((objc_precise_lifetime)) = weakObserver; - __strong NSObject *self __attribute__((objc_precise_lifetime)) = weakSelf; - - if (self == nil) { - [subscriber sendCompleted]; - return nil; - } - - return [self rac_observeKeyPath:keyPath options:options observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - [subscriber sendNext:RACTuplePack(value, change)]; - }]; - }] - takeUntil:deallocSignal] - setNameWithFormat:@"%@ -rac_valueAndChangesForKeyPath: %@ options: %lu observer: %@", RACDescription(self), keyPath, (unsigned long)options, RACDescription(strongObserver)]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSObject+RACSelectorSignal.h b/ReactiveCocoa/Objective-C/NSObject+RACSelectorSignal.h deleted file mode 100644 index c6f3de5d79..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACSelectorSignal.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// NSObject+RACSelectorSignal.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/18/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSObject+RACSelectorSignal.m b/ReactiveCocoa/Objective-C/NSObject+RACSelectorSignal.m deleted file mode 100644 index ed5432ed4f..0000000000 --- a/ReactiveCocoa/Objective-C/NSObject+RACSelectorSignal.m +++ /dev/null @@ -1,329 +0,0 @@ -// -// 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 -#import "NSInvocation+RACTypeParsing.h" -#import "NSObject+RACDeallocating.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACSubject.h" -#import "RACTuple.h" -#import "NSObject+RACDescription.h" -#import -#import - -NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain"; -const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1; - -static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_"; -static NSString * const RACSubclassSuffix = @"_RACSelectorSignal"; -static void *RACSubclassAssociationKey = &RACSubclassAssociationKey; - -static NSMutableSet *swizzledClasses() { - static NSMutableSet *set; - static dispatch_once_t pred; - - dispatch_once(&pred, ^{ - set = [[NSMutableSet alloc] init]; - }); - - return set; -} - -@implementation NSObject (RACSelectorSignal) - -static BOOL RACForwardInvocation(id self, NSInvocation *invocation) { - SEL aliasSelector = RACAliasForSelector(invocation.selector); - RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); - - Class class = object_getClass(invocation.target); - BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector]; - if (respondsToAlias) { - invocation.selector = aliasSelector; - [invocation invoke]; - } - - if (subject == nil) return respondsToAlias; - - [subject sendNext:invocation.rac_argumentsTuple]; - return YES; -} - -static void RACSwizzleForwardInvocation(Class class) { - SEL forwardInvocationSEL = @selector(forwardInvocation:); - Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL); - - // Preserve any existing implementation of -forwardInvocation:. - void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL; - if (forwardInvocationMethod != NULL) { - originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod); - } - - // Set up a new version of -forwardInvocation:. - // - // If the selector has been passed to -rac_signalForSelector:, invoke - // the aliased method, and forward the arguments to any attached signals. - // - // If the selector has not been passed to -rac_signalForSelector:, - // invoke any existing implementation of -forwardInvocation:. If there - // was no existing implementation, throw an unrecognized selector - // exception. - id newForwardInvocation = ^(id self, NSInvocation *invocation) { - BOOL matched = RACForwardInvocation(self, invocation); - if (matched) return; - - if (originalForwardInvocation == NULL) { - [self doesNotRecognizeSelector:invocation.selector]; - } else { - originalForwardInvocation(self, forwardInvocationSEL, invocation); - } - }; - - class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@"); -} - -static void RACSwizzleRespondsToSelector(Class class) { - SEL respondsToSelectorSEL = @selector(respondsToSelector:); - - // Preserve existing implementation of -respondsToSelector:. - Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL); - BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod); - - // Set up a new version of -respondsToSelector: that returns YES for methods - // added by -rac_signalForSelector:. - // - // If the selector has a method defined on the receiver's actual class, and - // if that method's implementation is _objc_msgForward, then returns whether - // the instance has a signal for the selector. - // Otherwise, call the original -respondsToSelector:. - id newRespondsToSelector = ^ BOOL (id self, SEL selector) { - Method method = rac_getImmediateInstanceMethod(class, selector); - - if (method != NULL && method_getImplementation(method) == _objc_msgForward) { - SEL aliasSelector = RACAliasForSelector(selector); - if (objc_getAssociatedObject(self, aliasSelector) != nil) return YES; - } - - return originalRespondsToSelector(self, respondsToSelectorSEL, selector); - }; - - class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod)); -} - -static void RACSwizzleGetClass(Class class, Class statedClass) { - SEL selector = @selector(class); - Method method = class_getInstanceMethod(class, selector); - IMP newIMP = imp_implementationWithBlock(^(id self) { - return statedClass; - }); - class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method)); -} - -static void RACSwizzleMethodSignatureForSelector(Class class) { - IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) { - // Don't send the -class message to the receiver because we've changed - // that to return the original class. - Class actualClass = object_getClass(self); - Method method = class_getInstanceMethod(actualClass, selector); - if (method == NULL) { - // Messages that the original class dynamically implements fall - // here. - // - // Call the original class' -methodSignatureForSelector:. - struct objc_super target = { - .super_class = class_getSuperclass(class), - .receiver = self, - }; - NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper; - return messageSend(&target, @selector(methodSignatureForSelector:), selector); - } - - char const *encoding = method_getTypeEncoding(method); - return [NSMethodSignature signatureWithObjCTypes:encoding]; - }); - - SEL selector = @selector(methodSignatureForSelector:); - Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector); - class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod)); -} - -// It's hard to tell which struct return types use _objc_msgForward, and -// which use _objc_msgForward_stret instead, so just exclude all struct, array, -// union, complex and vector return types. -static void RACCheckTypeEncoding(const char *typeEncoding) { -#if !NS_BLOCK_ASSERTIONS - // Some types, including vector types, are not encoded. In these cases the - // signature starts with the size of the argument frame. - NSCAssert(*typeEncoding < '1' || *typeEncoding > '9', @"unknown method return type not supported in type encoding: %s", typeEncoding); - NSCAssert(strstr(typeEncoding, "(") != typeEncoding, @"union method return type not supported"); - NSCAssert(strstr(typeEncoding, "{") != typeEncoding, @"struct method return type not supported"); - NSCAssert(strstr(typeEncoding, "[") != typeEncoding, @"array method return type not supported"); - NSCAssert(strstr(typeEncoding, @encode(_Complex float)) != typeEncoding, @"complex float method return type not supported"); - NSCAssert(strstr(typeEncoding, @encode(_Complex double)) != typeEncoding, @"complex double method return type not supported"); - NSCAssert(strstr(typeEncoding, @encode(_Complex long double)) != typeEncoding, @"complex long double method return type not supported"); - -#endif // !NS_BLOCK_ASSERTIONS -} - -static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) { - SEL aliasSelector = RACAliasForSelector(selector); - - @synchronized (self) { - RACSubject *subject = objc_getAssociatedObject(self, aliasSelector); - if (subject != nil) return subject; - - Class class = RACSwizzleClass(self); - NSCAssert(class != nil, @"Could not swizzle class of %@", self); - - subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", RACDescription(self), sel_getName(selector)]; - objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN); - - [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - [subject sendCompleted]; - }]]; - - Method targetMethod = class_getInstanceMethod(class, selector); - if (targetMethod == NULL) { - const char *typeEncoding; - if (protocol == NULL) { - typeEncoding = RACSignatureForUndefinedSelector(selector); - } else { - // Look for the selector as an optional instance method. - struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); - - if (methodDescription.name == NULL) { - // Then fall back to looking for a required instance - // method. - methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES); - NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol)); - } - - typeEncoding = methodDescription.types; - } - - RACCheckTypeEncoding(typeEncoding); - - // Define the selector to call -forwardInvocation:. - if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) { - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class], - NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil) - }; - - return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]]; - } - } else if (method_getImplementation(targetMethod) != _objc_msgForward) { - // Make a method alias for the existing method implementation. - const char *typeEncoding = method_getTypeEncoding(targetMethod); - - RACCheckTypeEncoding(typeEncoding); - - BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding); - NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class); - - // Redefine the selector to call -forwardInvocation:. - class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod)); - } - - return subject; - } -} - -static SEL RACAliasForSelector(SEL originalSelector) { - NSString *selectorName = NSStringFromSelector(originalSelector); - return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]); -} - -static const char *RACSignatureForUndefinedSelector(SEL selector) { - const char *name = sel_getName(selector); - NSMutableString *signature = [NSMutableString stringWithString:@"v@:"]; - - while ((name = strchr(name, ':')) != NULL) { - [signature appendString:@"@"]; - name++; - } - - return signature.UTF8String; -} - -static Class RACSwizzleClass(NSObject *self) { - Class statedClass = self.class; - Class baseClass = object_getClass(self); - - // The "known dynamic subclass" is the subclass generated by RAC. - // It's stored as an associated object on every instance that's already - // been swizzled, so that even if something else swizzles the class of - // this instance, we can still access the RAC generated subclass. - Class knownDynamicSubclass = objc_getAssociatedObject(self, RACSubclassAssociationKey); - if (knownDynamicSubclass != Nil) return knownDynamicSubclass; - - NSString *className = NSStringFromClass(baseClass); - - if (statedClass != baseClass) { - // If the class is already lying about what it is, it's probably a KVO - // dynamic subclass or something else that we shouldn't subclass - // ourselves. - // - // Just swizzle -forwardInvocation: in-place. Since the object's class - // was almost certainly dynamically changed, we shouldn't see another of - // these classes in the hierarchy. - // - // Additionally, swizzle -respondsToSelector: because the default - // implementation may be ignorant of methods added to this class. - @synchronized (swizzledClasses()) { - if (![swizzledClasses() containsObject:className]) { - RACSwizzleForwardInvocation(baseClass); - RACSwizzleRespondsToSelector(baseClass); - RACSwizzleGetClass(baseClass, statedClass); - RACSwizzleGetClass(object_getClass(baseClass), statedClass); - RACSwizzleMethodSignatureForSelector(baseClass); - [swizzledClasses() addObject:className]; - } - } - - return baseClass; - } - - const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String; - Class subclass = objc_getClass(subclassName); - - if (subclass == nil) { - subclass = objc_allocateClassPair(baseClass, subclassName, 0); - if (subclass == nil) return nil; - - RACSwizzleForwardInvocation(subclass); - RACSwizzleRespondsToSelector(subclass); - - RACSwizzleGetClass(subclass, statedClass); - RACSwizzleGetClass(object_getClass(subclass), statedClass); - - RACSwizzleMethodSignatureForSelector(subclass); - - objc_registerClassPair(subclass); - } - - object_setClass(self, subclass); - objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, OBJC_ASSOCIATION_ASSIGN); - return subclass; -} - -- (RACSignal *)rac_signalForSelector:(SEL)selector { - NSCParameterAssert(selector != NULL); - - return NSObjectRACSignalForSelector(self, selector, NULL); -} - -- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol { - NSCParameterAssert(selector != NULL); - NSCParameterAssert(protocol != NULL); - - return NSObjectRACSignalForSelector(self, selector, protocol); -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSOrderedSet+RACSequenceAdditions.h b/ReactiveCocoa/Objective-C/NSOrderedSet+RACSequenceAdditions.h deleted file mode 100644 index 8bea2db774..0000000000 --- a/ReactiveCocoa/Objective-C/NSOrderedSet+RACSequenceAdditions.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// NSOrderedSet+RACSequenceAdditions.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-29. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSOrderedSet+RACSequenceAdditions.m b/ReactiveCocoa/Objective-C/NSOrderedSet+RACSequenceAdditions.m deleted file mode 100644 index 55dfd0b7e9..0000000000 --- a/ReactiveCocoa/Objective-C/NSOrderedSet+RACSequenceAdditions.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSSet+RACSequenceAdditions.h b/ReactiveCocoa/Objective-C/NSSet+RACSequenceAdditions.h deleted file mode 100644 index 66655016d2..0000000000 --- a/ReactiveCocoa/Objective-C/NSSet+RACSequenceAdditions.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// NSSet+RACSequenceAdditions.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-29. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSSet+RACSequenceAdditions.m b/ReactiveCocoa/Objective-C/NSSet+RACSequenceAdditions.m deleted file mode 100644 index cc07f75ce6..0000000000 --- a/ReactiveCocoa/Objective-C/NSSet+RACSequenceAdditions.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSString+RACKeyPathUtilities.h b/ReactiveCocoa/Objective-C/NSString+RACKeyPathUtilities.h deleted file mode 100644 index d56cf59e5c..0000000000 --- a/ReactiveCocoa/Objective-C/NSString+RACKeyPathUtilities.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// NSString+RACKeyPathUtilities.h -// ReactiveCocoa -// -// Created by Uri Baghin on 05/05/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -// 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/ReactiveCocoa/Objective-C/NSString+RACKeyPathUtilities.m b/ReactiveCocoa/Objective-C/NSString+RACKeyPathUtilities.m deleted file mode 100644 index 63a100c6cf..0000000000 --- a/ReactiveCocoa/Objective-C/NSString+RACKeyPathUtilities.m +++ /dev/null @@ -1,36 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSString+RACSequenceAdditions.h b/ReactiveCocoa/Objective-C/NSString+RACSequenceAdditions.h deleted file mode 100644 index 0116231f4c..0000000000 --- a/ReactiveCocoa/Objective-C/NSString+RACSequenceAdditions.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// NSString+RACSequenceAdditions.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-29. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSString+RACSequenceAdditions.m b/ReactiveCocoa/Objective-C/NSString+RACSequenceAdditions.m deleted file mode 100644 index eb6a76ef3b..0000000000 --- a/ReactiveCocoa/Objective-C/NSString+RACSequenceAdditions.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSString+RACSupport.h b/ReactiveCocoa/Objective-C/NSString+RACSupport.h deleted file mode 100644 index 4c7fc72baa..0000000000 --- a/ReactiveCocoa/Objective-C/NSString+RACSupport.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// NSString+RACSupport.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 5/11/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@class RACScheduler; -@class RACSignal; - -@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/ReactiveCocoa/Objective-C/NSString+RACSupport.m b/ReactiveCocoa/Objective-C/NSString+RACSupport.m deleted file mode 100644 index ed1058a2f4..0000000000 --- a/ReactiveCocoa/Objective-C/NSString+RACSupport.m +++ /dev/null @@ -1,35 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/NSText+RACSignalSupport.h b/ReactiveCocoa/Objective-C/NSText+RACSignalSupport.h deleted file mode 100644 index e3fc8ed884..0000000000 --- a/ReactiveCocoa/Objective-C/NSText+RACSignalSupport.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// NSText+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-03-08. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSText+RACSignalSupport.m b/ReactiveCocoa/Objective-C/NSText+RACSignalSupport.m deleted file mode 100644 index cdb3c7ab83..0000000000 --- a/ReactiveCocoa/Objective-C/NSText+RACSignalSupport.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// 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 -#import "NSObject+RACDescription.h" -#import "RACDisposable.h" -#import "RACSignal.h" -#import "RACSubscriber.h" - -@implementation NSText (RACSignalSupport) - -- (RACSignal *)rac_textSignal { - @unsafeify(self); - return [[[[RACSignal - createSignal:^(id 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", RACDescription(self)]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSURLConnection+RACSupport.h b/ReactiveCocoa/Objective-C/NSURLConnection+RACSupport.h deleted file mode 100644 index e9bf04f314..0000000000 --- a/ReactiveCocoa/Objective-C/NSURLConnection+RACSupport.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// NSURLConnection+RACSupport.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-10-01. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSURLConnection+RACSupport.m b/ReactiveCocoa/Objective-C/NSURLConnection+RACSupport.m deleted file mode 100644 index ed1102dd29..0000000000 --- a/ReactiveCocoa/Objective-C/NSURLConnection+RACSupport.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// 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 "RACSubscriber.h" -#import "RACTuple.h" - -@implementation NSURLConnection (RACSupport) - -+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request { - NSCParameterAssert(request != nil); - - return [[RACSignal - createSignal:^(id subscriber) { - NSOperationQueue *queue = [[NSOperationQueue alloc] init]; - queue.name = @"org.reactivecocoa.ReactiveCocoa.NSURLConnectionRACSupport"; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { - // The docs say that `nil` data means an error occurred, but - // `nil` responses can also occur in practice (circumstances - // unknown). Consider either to be an error. - // - // Note that _empty_ data is not necessarily erroneous, as there - // may be headers but no HTTP body. - if (response == nil || data == nil) { - [subscriber sendError:error]; - } else { - [subscriber sendNext:RACTuplePack(response, data)]; - [subscriber sendCompleted]; - } - }]; -#pragma clang diagnostic pop - - return [RACDisposable disposableWithBlock:^{ - // It's not clear if this will actually cancel the connection, - // but we can at least prevent _some_ unnecessary work -- - // without writing all the code for a proper delegate, which - // doesn't really belong in RAC. - queue.suspended = YES; - [queue cancelAllOperations]; - }]; - }] - setNameWithFormat:@"+rac_sendAsynchronousRequest: %@", request]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/NSUserDefaults+RACSupport.h b/ReactiveCocoa/Objective-C/NSUserDefaults+RACSupport.h deleted file mode 100644 index 8482fb3fe1..0000000000 --- a/ReactiveCocoa/Objective-C/NSUserDefaults+RACSupport.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// NSUserDefaults+RACSupport.h -// ReactiveCocoa -// -// Created by Matt Diephouse on 12/19/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/NSUserDefaults+RACSupport.m b/ReactiveCocoa/Objective-C/NSUserDefaults+RACSupport.m deleted file mode 100644 index e76f12d75e..0000000000 --- a/ReactiveCocoa/Objective-C/NSUserDefaults+RACSupport.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// 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 -#import "NSNotificationCenter+RACSupport.h" -#import "NSObject+RACDeallocating.h" -#import "RACChannel.h" -#import "RACScheduler.h" -#import "RACSignal+Operations.h" - -@implementation NSUserDefaults (RACSupport) - -- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key { - 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/ReactiveCocoa/Objective-C/RACArraySequence.h b/ReactiveCocoa/Objective-C/RACArraySequence.h deleted file mode 100644 index e9d948e059..0000000000 --- a/ReactiveCocoa/Objective-C/RACArraySequence.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACArraySequence.m b/ReactiveCocoa/Objective-C/RACArraySequence.m deleted file mode 100644 index a94ac2e991..0000000000 --- a/ReactiveCocoa/Objective-C/RACArraySequence.m +++ /dev/null @@ -1,125 +0,0 @@ -// -// 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[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/ReactiveCocoa/Objective-C/RACBehaviorSubject.h b/ReactiveCocoa/Objective-C/RACBehaviorSubject.h deleted file mode 100644 index e95fe6d151..0000000000 --- a/ReactiveCocoa/Objective-C/RACBehaviorSubject.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACBehaviorSubject.m b/ReactiveCocoa/Objective-C/RACBehaviorSubject.m deleted file mode 100644 index dfda2ac073..0000000000 --- a/ReactiveCocoa/Objective-C/RACBehaviorSubject.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// 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)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/ReactiveCocoa/Objective-C/RACBlockTrampoline.h b/ReactiveCocoa/Objective-C/RACBlockTrampoline.h deleted file mode 100644 index 3857d7c85f..0000000000 --- a/ReactiveCocoa/Objective-C/RACBlockTrampoline.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// RACBlockTrampoline.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/21/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/RACBlockTrampoline.m b/ReactiveCocoa/Objective-C/RACBlockTrampoline.m deleted file mode 100644 index 07903dd9c0..0000000000 --- a/ReactiveCocoa/Objective-C/RACBlockTrampoline.m +++ /dev/null @@ -1,156 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACChannel.h b/ReactiveCocoa/Objective-C/RACChannel.h deleted file mode 100644 index ef1d6f1ba9..0000000000 --- a/ReactiveCocoa/Objective-C/RACChannel.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// 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 - -- (id)init __attribute__((unavailable("Instantiate a RACChannel instead"))); - -@end diff --git a/ReactiveCocoa/Objective-C/RACChannel.m b/ReactiveCocoa/Objective-C/RACChannel.m deleted file mode 100644 index afe850a0b1..0000000000 --- a/ReactiveCocoa/Objective-C/RACChannel.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// 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" - -@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 otherTerminal; - -- (id)initWithValues:(RACSignal *)values otherTerminal:(id)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)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)subscriber { - return [self.values subscribe:subscriber]; -} - -#pragma mark - -- (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/ReactiveCocoa/Objective-C/RACCommand.h b/ReactiveCocoa/Objective-C/RACCommand.h deleted file mode 100644 index d59ca67305..0000000000 --- a/ReactiveCocoa/Objective-C/RACCommand.h +++ /dev/null @@ -1,127 +0,0 @@ -// -// RACCommand.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/3/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@class RACSignal; -NS_ASSUME_NONNULL_BEGIN - -/// 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<__contravariant InputType> : 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 * (^)(InputType _Nullable input))signalBlock; - -/// Initializes a command that is conditionally enabled. -/// -/// This is the designated initializer for this class. -/// -/// enabledSignal - A signal of BOOLs which indicate whether the command should -/// be enabled. `enabled` will be based on the latest value sent -/// from this signal. Before any values are sent, `enabled` will -/// default to YES. This argument may be nil. -/// signalBlock - A block which will map each input value (passed to -execute:) -/// to a signal of work. The returned signal will be multicasted -/// to a replay subject, sent on `executionSignals`, then -/// subscribed to synchronously. Neither the block nor the -/// returned signal may be nil. -- (id)initWithEnabled:(nullable RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(InputType _Nullable input))signalBlock; - -/// If the receiver is enabled, this method will: -/// -/// 1. Invoke the `signalBlock` given at the time of initialization. -/// 2. Multicast the returned signal to a RACReplaySubject. -/// 3. Send the multicasted signal on `executionSignals`. -/// 4. Subscribe (connect) to the original signal on the main thread. -/// -/// input - The input value to pass to the receiver's `signalBlock`. This may be -/// nil. -/// -/// Returns the multicasted signal, after subscription. If the receiver is not -/// enabled, returns a signal that will send an error with code -/// RACCommandErrorNotEnabled. -- (RACSignal *)execute:(nullable InputType)input; - -NS_ASSUME_NONNULL_END -@end - -@interface RACCommand (Unavailable) -NS_ASSUME_NONNULL_BEGIN - -@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"))); - -NS_ASSUME_NONNULL_END -@end diff --git a/ReactiveCocoa/Objective-C/RACCommand.m b/ReactiveCocoa/Objective-C/RACCommand.m deleted file mode 100644 index ea79754066..0000000000 --- a/ReactiveCocoa/Objective-C/RACCommand.m +++ /dev/null @@ -1,203 +0,0 @@ -// -// RACCommand.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/3/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACCommand.h" -#import -#import "NSArray+RACSequenceAdditions.h" -#import "NSObject+RACDeallocating.h" -#import "NSObject+RACDescription.h" -#import "NSObject+RACPropertySubscribing.h" -#import "RACMulticastConnection.h" -#import "RACReplaySubject.h" -#import "RACScheduler.h" -#import "RACSequence.h" -#import "RACSignal+Operations.h" -#import - -NSString * const RACCommandErrorDomain = @"RACCommandErrorDomain"; -NSString * const RACUnderlyingCommandErrorKey = @"RACUnderlyingCommandErrorKey"; - -const NSInteger RACCommandErrorNotEnabled = 1; - -@interface RACCommand () { - // Atomic backing variable for `allowsConcurrentExecution`. - volatile uint32_t _allowsConcurrentExecution; -} - -/// A subject that sends added execution signals. -@property (nonatomic, strong, readonly) RACSubject *addedExecutionSignalsSubject; - -/// A subject that sends the new value of `allowsConcurrentExecution` whenever it changes. -@property (nonatomic, strong, readonly) RACSubject *allowsConcurrentExecutionSubject; - -// `enabled`, but without a hop to the main thread. -// -// Values from this signal may arrive on any thread. -@property (nonatomic, strong, readonly) RACSignal *immediateEnabled; - -// The signal block that the receiver was initialized with. -@property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input); - -@end - -@implementation RACCommand - -#pragma mark Properties - -- (BOOL)allowsConcurrentExecution { - return _allowsConcurrentExecution != 0; -} - -- (void)setAllowsConcurrentExecution:(BOOL)allowed { - if (allowed) { - OSAtomicOr32Barrier(1, &_allowsConcurrentExecution); - } else { - OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution); - } - - [self.allowsConcurrentExecutionSubject sendNext:@(_allowsConcurrentExecution)]; -} - -#pragma mark Lifecycle - -- (id)init { - NSCAssert(NO, @"Use -initWithSignalBlock: instead"); - return nil; -} - -- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock { - return [self initWithEnabled:nil signalBlock:signalBlock]; -} - -- (void)dealloc { - [_addedExecutionSignalsSubject sendCompleted]; - [_allowsConcurrentExecutionSubject sendCompleted]; -} - -- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock { - NSCParameterAssert(signalBlock != nil); - - self = [super init]; - if (self == nil) return nil; - - _addedExecutionSignalsSubject = [RACSubject new]; - _allowsConcurrentExecutionSubject = [RACSubject new]; - _signalBlock = [signalBlock copy]; - - _executionSignals = [[[self.addedExecutionSignalsSubject - map:^(RACSignal *signal) { - return [signal catchTo:[RACSignal empty]]; - }] - deliverOn:RACScheduler.mainThreadScheduler] - setNameWithFormat:@"%@ -executionSignals", self]; - - // `errors` needs to be multicasted so that it picks up all - // `activeExecutionSignals` that are added. - // - // In other words, if someone subscribes to `errors` _after_ an execution - // has started, it should still receive any error from that execution. - RACMulticastConnection *errorsConnection = [[[self.addedExecutionSignalsSubject - flattenMap:^(RACSignal *signal) { - return [[signal - ignoreValues] - catch:^(NSError *error) { - return [RACSignal return:error]; - }]; - }] - deliverOn:RACScheduler.mainThreadScheduler] - publish]; - - _errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self]; - [errorsConnection connect]; - - RACSignal *immediateExecuting = [[[[self.addedExecutionSignalsSubject - flattenMap:^(RACSignal *signal) { - return [[[signal - catchTo:[RACSignal empty]] - then:^{ - return [RACSignal return:@-1]; - }] - startWith:@1]; - }] - scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) { - return @(running.integerValue + next.integerValue); - }] - map:^(NSNumber *count) { - return @(count.integerValue > 0); - }] - startWith:@NO]; - - _executing = [[[[[immediateExecuting - deliverOn:RACScheduler.mainThreadScheduler] - // This is useful before the first value arrives on the main thread. - startWith:@NO] - distinctUntilChanged] - replayLast] - setNameWithFormat:@"%@ -executing", self]; - - RACSignal *moreExecutionsAllowed = [RACSignal - if:[self.allowsConcurrentExecutionSubject startWith:@NO] - then:[RACSignal return:@YES] - else:[immediateExecuting not]]; - - if (enabledSignal == nil) { - enabledSignal = [RACSignal return:@YES]; - } else { - enabledSignal = [enabledSignal startWith:@YES]; - } - - _immediateEnabled = [[[[RACSignal - combineLatest:@[ enabledSignal, moreExecutionsAllowed ]] - and] - takeUntil:self.rac_willDeallocSignal] - replayLast]; - - _enabled = [[[[[self.immediateEnabled - take:1] - concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]] - distinctUntilChanged] - replayLast] - setNameWithFormat:@"%@ -enabled", self]; - - return self; -} - -#pragma mark Execution - -- (RACSignal *)execute:(id)input { - // `immediateEnabled` is guaranteed to send a value upon subscription, so - // -first is acceptable here. - BOOL enabled = [[self.immediateEnabled first] boolValue]; - if (!enabled) { - NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{ - NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil), - RACUnderlyingCommandErrorKey: self - }]; - - return [RACSignal error:error]; - } - - RACSignal *signal = self.signalBlock(input); - NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input); - - // We subscribe to the signal on the main thread so that it occurs _after_ - // -addActiveExecutionSignal: completes below. - // - // This means that `executing` and `enabled` will send updated values before - // the signal actually starts performing work. - RACMulticastConnection *connection = [[signal - subscribeOn:RACScheduler.mainThreadScheduler] - multicast:[RACReplaySubject subject]]; - - [self.addedExecutionSignalsSubject sendNext:connection.signal]; - - [connection connect]; - return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(input)]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACCompoundDisposable.h b/ReactiveCocoa/Objective-C/RACCompoundDisposable.h deleted file mode 100644 index bb25f7d2b5..0000000000 --- a/ReactiveCocoa/Objective-C/RACCompoundDisposable.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACCompoundDisposable.m b/ReactiveCocoa/Objective-C/RACCompoundDisposable.m deleted file mode 100644 index 3c3575e69c..0000000000 --- a/ReactiveCocoa/Objective-C/RACCompoundDisposable.m +++ /dev/null @@ -1,252 +0,0 @@ -// -// 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 - -// The number of child disposables for which space will be reserved directly in -// `RACCompoundDisposable`. -// -// This number has been empirically determined to provide a good tradeoff -// between performance, memory usage, and `RACCompoundDisposable` instance size -// in a moderately complex GUI application. -// -// Profile any change! -#define RACCompoundDisposableInlineCount 2 - -static CFMutableArrayRef RACCreateDisposablesArray(void) { - // Compare values using only pointer equality. - CFArrayCallBacks callbacks = kCFTypeArrayCallBacks; - callbacks.equal = NULL; - - return CFArrayCreateMutable(NULL, 0, &callbacks); -} - -@interface RACCompoundDisposable () { - // Used for synchronization. - pthread_mutex_t _mutex; - - #if RACCompoundDisposableInlineCount - // A fast array to the first N of the receiver's disposables. - // - // Once this is full, `_disposables` will be created and used for additional - // disposables. - // - // This array should only be manipulated while _mutex is held. - RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount]; - #endif - - // Contains the receiver's disposables. - // - // This array should only be manipulated while _mutex is held. If - // `_disposed` is YES, this may be NULL. - CFMutableArrayRef _disposables; - - // Whether the receiver has already been disposed. - // - // This ivar should only be accessed while _mutex is held. - BOOL _disposed; -} - -@end - -@implementation RACCompoundDisposable - -#pragma mark Properties - -- (BOOL)isDisposed { - pthread_mutex_lock(&_mutex); - BOOL disposed = _disposed; - pthread_mutex_unlock(&_mutex); - - return disposed; -} - -#pragma mark Lifecycle - -+ (instancetype)compoundDisposable { - return [[self alloc] initWithDisposables:nil]; -} - -+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables { - return [[self alloc] initWithDisposables:disposables]; -} - -- (instancetype)init { - self = [super init]; - if (self == nil) return nil; - - const int result = pthread_mutex_init(&_mutex, NULL); - NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result); - - return self; -} - -- (instancetype)initWithDisposables:(NSArray *)otherDisposables { - self = [self init]; - if (self == nil) return nil; - - #if RACCompoundDisposableInlineCount - [otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) { - self->_inlineDisposables[index] = disposable; - - // Stop after this iteration if we've reached the end of the inlined - // array. - if (index == RACCompoundDisposableInlineCount - 1) *stop = YES; - }]; - #endif - - if (otherDisposables.count > RACCompoundDisposableInlineCount) { - _disposables = RACCreateDisposablesArray(); - - CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount); - CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range); - } - - return self; -} - -- (instancetype)initWithBlock:(void (^)(void))block { - RACDisposable *disposable = [RACDisposable disposableWithBlock:block]; - return [self initWithDisposables:@[ disposable ]]; -} - -- (void)dealloc { - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - _inlineDisposables[i] = nil; - } - #endif - - if (_disposables != NULL) { - CFRelease(_disposables); - _disposables = NULL; - } - - const int result = pthread_mutex_destroy(&_mutex); - NSCAssert(0 == result, @"Failed to destroy mutex with error %d.", result); -} - -#pragma mark Addition and Removal - -- (void)addDisposable:(RACDisposable *)disposable { - NSCParameterAssert(disposable != self); - if (disposable == nil || disposable.disposed) return; - - BOOL shouldDispose = NO; - - pthread_mutex_lock(&_mutex); - { - if (_disposed) { - shouldDispose = YES; - } else { - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - if (_inlineDisposables[i] == nil) { - _inlineDisposables[i] = disposable; - goto foundSlot; - } - } - #endif - - if (_disposables == NULL) _disposables = RACCreateDisposablesArray(); - CFArrayAppendValue(_disposables, (__bridge void *)disposable); - - if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) { - RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); - } - - #if RACCompoundDisposableInlineCount - foundSlot:; - #endif - } - } - pthread_mutex_unlock(&_mutex); - - // Performed outside of the lock in case the compound disposable is used - // recursively. - if (shouldDispose) [disposable dispose]; -} - -- (void)removeDisposable:(RACDisposable *)disposable { - if (disposable == nil) return; - - pthread_mutex_lock(&_mutex); - { - if (!_disposed) { - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil; - } - #endif - - if (_disposables != NULL) { - CFIndex count = CFArrayGetCount(_disposables); - for (CFIndex i = count - 1; i >= 0; i--) { - const void *item = CFArrayGetValueAtIndex(_disposables, i); - if (item == (__bridge void *)disposable) { - CFArrayRemoveValueAtIndex(_disposables, i); - } - } - - if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) { - RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount); - } - } - } - } - pthread_mutex_unlock(&_mutex); -} - -#pragma mark RACDisposable - -static void disposeEach(const void *value, void *context) { - RACDisposable *disposable = (__bridge id)value; - [disposable dispose]; -} - -- (void)dispose { - #if RACCompoundDisposableInlineCount - RACDisposable *inlineCopy[RACCompoundDisposableInlineCount]; - #endif - - CFArrayRef remainingDisposables = NULL; - - pthread_mutex_lock(&_mutex); - { - _disposed = YES; - - #if RACCompoundDisposableInlineCount - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - inlineCopy[i] = _inlineDisposables[i]; - _inlineDisposables[i] = nil; - } - #endif - - remainingDisposables = _disposables; - _disposables = NULL; - } - pthread_mutex_unlock(&_mutex); - - #if RACCompoundDisposableInlineCount - // Dispose outside of the lock in case the compound disposable is used - // recursively. - for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) { - [inlineCopy[i] dispose]; - } - #endif - - if (remainingDisposables == NULL) return; - - CFIndex count = CFArrayGetCount(remainingDisposables); - CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL); - CFRelease(remainingDisposables); -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACCompoundDisposableProvider.d b/ReactiveCocoa/Objective-C/RACCompoundDisposableProvider.d deleted file mode 100644 index 847db19b17..0000000000 --- a/ReactiveCocoa/Objective-C/RACCompoundDisposableProvider.d +++ /dev/null @@ -1,4 +0,0 @@ -provider RACCompoundDisposable { - probe added(char *compoundDisposable, char *disposable, long newTotal); - probe removed(char *compoundDisposable, char *disposable, long newTotal); -}; diff --git a/ReactiveCocoa/Objective-C/RACDelegateProxy.h b/ReactiveCocoa/Objective-C/RACDelegateProxy.h deleted file mode 100644 index 9ec96bd033..0000000000 --- a/ReactiveCocoa/Objective-C/RACDelegateProxy.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// RACDelegateProxy.h -// ReactiveCocoa -// -// Created by Cody Krieger on 5/19/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/RACDelegateProxy.m b/ReactiveCocoa/Objective-C/RACDelegateProxy.m deleted file mode 100644 index 91cac4b58d..0000000000 --- a/ReactiveCocoa/Objective-C/RACDelegateProxy.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// RACDelegateProxy.m -// ReactiveCocoa -// -// Created by Cody Krieger on 5/19/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACDelegateProxy.h" -#import "NSObject+RACSelectorSignal.h" -#import - -@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/ReactiveCocoa/Objective-C/RACDisposable.h b/ReactiveCocoa/Objective-C/RACDisposable.h deleted file mode 100644 index 5b4cf0bd39..0000000000 --- a/ReactiveCocoa/Objective-C/RACDisposable.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// RACDisposable.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/16/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/RACDisposable.m b/ReactiveCocoa/Objective-C/RACDisposable.m deleted file mode 100644 index 08eceb228e..0000000000 --- a/ReactiveCocoa/Objective-C/RACDisposable.m +++ /dev/null @@ -1,92 +0,0 @@ -// -// 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 - -@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/ReactiveCocoa/Objective-C/RACDynamicSequence.h b/ReactiveCocoa/Objective-C/RACDynamicSequence.h deleted file mode 100644 index 1e103e393c..0000000000 --- a/ReactiveCocoa/Objective-C/RACDynamicSequence.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACDynamicSequence.m b/ReactiveCocoa/Objective-C/RACDynamicSequence.m deleted file mode 100644 index 48a977b84b..0000000000 --- a/ReactiveCocoa/Objective-C/RACDynamicSequence.m +++ /dev/null @@ -1,197 +0,0 @@ -// -// RACDynamicSequence.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-29. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import "RACDynamicSequence.h" -#import - -// 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/ReactiveCocoa/Objective-C/RACDynamicSignal.h b/ReactiveCocoa/Objective-C/RACDynamicSignal.h deleted file mode 100644 index 81ac6db8ba..0000000000 --- a/ReactiveCocoa/Objective-C/RACDynamicSignal.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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 subscriber))didSubscribe; - -@end diff --git a/ReactiveCocoa/Objective-C/RACDynamicSignal.m b/ReactiveCocoa/Objective-C/RACDynamicSignal.m deleted file mode 100644 index c51c873c0d..0000000000 --- a/ReactiveCocoa/Objective-C/RACDynamicSignal.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// RACDynamicSignal.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-10-10. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import "RACDynamicSignal.h" -#import -#import "RACCompoundDisposable.h" -#import "RACPassthroughSubscriber.h" -#import "RACScheduler+Private.h" -#import "RACSubscriber.h" -#import - -@interface RACDynamicSignal () - -// The block to invoke for each subscriber. -@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id subscriber); - -@end - -@implementation RACDynamicSignal - -#pragma mark Lifecycle - -+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe { - RACDynamicSignal *signal = [[self alloc] init]; - signal->_didSubscribe = [didSubscribe copy]; - return [signal setNameWithFormat:@"+createSignal:"]; -} - -#pragma mark Managing Subscribers - -- (RACDisposable *)subscribe:(id)subscriber { - NSCParameterAssert(subscriber != nil); - - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; - - if (self.didSubscribe != NULL) { - RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ - RACDisposable *innerDisposable = self.didSubscribe(subscriber); - [disposable addDisposable:innerDisposable]; - }]; - - [disposable addDisposable:schedulingDisposable]; - } - - return disposable; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACEagerSequence.h b/ReactiveCocoa/Objective-C/RACEagerSequence.h deleted file mode 100644 index ab5aa9f925..0000000000 --- a/ReactiveCocoa/Objective-C/RACEagerSequence.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACEagerSequence.m b/ReactiveCocoa/Objective-C/RACEagerSequence.m deleted file mode 100644 index b433990fbd..0000000000 --- a/ReactiveCocoa/Objective-C/RACEagerSequence.m +++ /dev/null @@ -1,66 +0,0 @@ -// -// 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: %@", RACDescription(value)]; -} - -- (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/ReactiveCocoa/Objective-C/RACEmptySequence.h b/ReactiveCocoa/Objective-C/RACEmptySequence.h deleted file mode 100644 index 140c78b7d1..0000000000 --- a/ReactiveCocoa/Objective-C/RACEmptySequence.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACEmptySequence.m b/ReactiveCocoa/Objective-C/RACEmptySequence.m deleted file mode 100644 index e4df150777..0000000000 --- a/ReactiveCocoa/Objective-C/RACEmptySequence.m +++ /dev/null @@ -1,71 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACEmptySignal.h b/ReactiveCocoa/Objective-C/RACEmptySignal.h deleted file mode 100644 index 90b4f00aea..0000000000 --- a/ReactiveCocoa/Objective-C/RACEmptySignal.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACEmptySignal.m b/ReactiveCocoa/Objective-C/RACEmptySignal.m deleted file mode 100644 index 8bc8f41eb5..0000000000 --- a/ReactiveCocoa/Objective-C/RACEmptySignal.m +++ /dev/null @@ -1,62 +0,0 @@ -// -// 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)subscriber { - NSCParameterAssert(subscriber != nil); - - return [RACScheduler.subscriptionScheduler schedule:^{ - [subscriber sendCompleted]; - }]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACErrorSignal.h b/ReactiveCocoa/Objective-C/RACErrorSignal.h deleted file mode 100644 index c942f9e3a8..0000000000 --- a/ReactiveCocoa/Objective-C/RACErrorSignal.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACErrorSignal.m b/ReactiveCocoa/Objective-C/RACErrorSignal.m deleted file mode 100644 index fd6778cb67..0000000000 --- a/ReactiveCocoa/Objective-C/RACErrorSignal.m +++ /dev/null @@ -1,47 +0,0 @@ -// -// 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)subscriber { - NSCParameterAssert(subscriber != nil); - - return [RACScheduler.subscriptionScheduler schedule:^{ - [subscriber sendError:self.error]; - }]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACEvent.h b/ReactiveCocoa/Objective-C/RACEvent.h deleted file mode 100644 index a8f10e11f2..0000000000 --- a/ReactiveCocoa/Objective-C/RACEvent.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// RACEvent.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-01-07. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -/// Describes the type of a RACEvent. -/// -/// RACEventTypeCompleted - A `completed` event. -/// RACEventTypeError - An `error` event. -/// RACEventTypeNext - A `next` event. -typedef NS_ENUM(NSUInteger, RACEventType) { - RACEventTypeCompleted, - RACEventTypeError, - RACEventTypeNext -}; - -/// Represents an event sent by a RACSignal. -/// -/// This corresponds to the `Notification` class in Rx. -@interface RACEvent : NSObject - -/// 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/ReactiveCocoa/Objective-C/RACEvent.m b/ReactiveCocoa/Objective-C/RACEvent.m deleted file mode 100644 index 270a95d76c..0000000000 --- a/ReactiveCocoa/Objective-C/RACEvent.m +++ /dev/null @@ -1,113 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACGroupedSignal.h b/ReactiveCocoa/Objective-C/RACGroupedSignal.h deleted file mode 100644 index 04c151b704..0000000000 --- a/ReactiveCocoa/Objective-C/RACGroupedSignal.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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 key; - -+ (instancetype)signalWithKey:(id)key; - -@end diff --git a/ReactiveCocoa/Objective-C/RACGroupedSignal.m b/ReactiveCocoa/Objective-C/RACGroupedSignal.m deleted file mode 100644 index 00e03784d5..0000000000 --- a/ReactiveCocoa/Objective-C/RACGroupedSignal.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// 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 key; -@end - -@implementation RACGroupedSignal - -#pragma mark API - -+ (instancetype)signalWithKey:(id)key { - RACGroupedSignal *subject = [self subject]; - subject.key = key; - return subject; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACImmediateScheduler.h b/ReactiveCocoa/Objective-C/RACImmediateScheduler.h deleted file mode 100644 index 76b5b5014f..0000000000 --- a/ReactiveCocoa/Objective-C/RACImmediateScheduler.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACImmediateScheduler.m b/ReactiveCocoa/Objective-C/RACImmediateScheduler.m deleted file mode 100644 index 0dfa06095d..0000000000 --- a/ReactiveCocoa/Objective-C/RACImmediateScheduler.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// 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; -} - -- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock { - for (__block NSUInteger remaining = 1; remaining > 0; remaining--) { - recursiveBlock(^{ - remaining++; - }); - } - - return nil; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACIndexSetSequence.h b/ReactiveCocoa/Objective-C/RACIndexSetSequence.h deleted file mode 100644 index eab84a39f3..0000000000 --- a/ReactiveCocoa/Objective-C/RACIndexSetSequence.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACIndexSetSequence.m b/ReactiveCocoa/Objective-C/RACIndexSetSequence.m deleted file mode 100644 index d9393db20e..0000000000 --- a/ReactiveCocoa/Objective-C/RACIndexSetSequence.m +++ /dev/null @@ -1,111 +0,0 @@ -// -// 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; - - if (count == 0) return self.empty; - - NSUInteger sizeInBytes = sizeof(NSUInteger) * count; - - NSMutableData *data = [[NSMutableData alloc] initWithCapacity:sizeInBytes]; - [indexSet getIndexes:data.mutableBytes maxCount:count inIndexRange:NULL]; - - RACIndexSetSequence *seq = [[self alloc] init]; - seq->_data = data; - seq->_indexes = data.bytes; - seq->_count = count; - return seq; -} - -+ (instancetype)sequenceWithIndexSetSequence:(RACIndexSetSequence *)indexSetSequence offset:(NSUInteger)offset { - NSCParameterAssert(offset < indexSetSequence.count); - - RACIndexSetSequence *seq = [[self alloc] init]; - seq->_data = indexSetSequence.data; - seq->_indexes = indexSetSequence.indexes + offset; - seq->_count = indexSetSequence.count - offset; - return seq; -} - -#pragma mark RACSequence - -- (id)head { - return @(self.indexes[0]); -} - -- (RACSequence *)tail { - if (self.count <= 1) return [RACSequence empty]; - - return [self.class sequenceWithIndexSetSequence:self offset:1]; -} - -#pragma mark NSFastEnumeration - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id[])stackbuf count:(NSUInteger)len { - NSCParameterAssert(len > 0); - - if (state->state >= self.count) { - // Enumeration has completed. - return 0; - } - - if (state->state == 0) { - // Enumeration begun, mark the mutation flag. - state->mutationsPtr = state->extra; - } - - state->itemsPtr = stackbuf; - - unsigned long index = 0; - while (index < MIN(self.count - state->state, len)) { - stackbuf[index] = @(self.indexes[index + state->state]); - ++index; - } - - state->state += index; - return index; -} - -#pragma mark NSObject - -- (NSString *)description { - NSMutableString *indexesStr = [NSMutableString string]; - - for (unsigned int i = 0; i < self.count; ++i) { - if (i > 0) [indexesStr appendString:@", "]; - - [indexesStr appendFormat:@"%lu", (unsigned long)self.indexes[i]]; - } - - return [NSString stringWithFormat:@"<%@: %p>{ name = %@, indexes = %@ }", self.class, self, self.name, indexesStr]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACKVOChannel.h b/ReactiveCocoa/Objective-C/RACKVOChannel.h deleted file mode 100644 index 314d68b41a..0000000000 --- a/ReactiveCocoa/Objective-C/RACKVOChannel.h +++ /dev/null @@ -1,102 +0,0 @@ -// -// RACKVOChannel.h -// ReactiveCocoa -// -// Created by Uri Baghin on 27/12/2012. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACChannel.h" -#import -#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`). -#if OS_OBJECT_HAVE_OBJC_SUPPORT -- (id)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue; -#else -// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :( -- (id)initWithTarget:(NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue; -#endif - -- (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/ReactiveCocoa/Objective-C/RACKVOChannel.m b/ReactiveCocoa/Objective-C/RACKVOChannel.m deleted file mode 100644 index 76b6cd191a..0000000000 --- a/ReactiveCocoa/Objective-C/RACKVOChannel.m +++ /dev/null @@ -1,208 +0,0 @@ -// -// RACKVOChannel.m -// ReactiveCocoa -// -// Created by Uri Baghin on 27/12/2012. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACKVOChannel.h" -#import -#import "NSObject+RACDeallocating.h" -#import "NSObject+RACKVOWrapper.h" -#import "NSString+RACKeyPathUtilities.h" -#import "RACChannel.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACSignal+Operations.h" - -// Key for the array of RACKVOChannel's additional thread local -// data in the thread dictionary. -static NSString * const RACKVOChannelDataDictionaryKey = @"RACKVOChannelKey"; - -// Wrapper class for additional thread local data. -@interface RACKVOChannelData : NSObject - -// The flag used to ignore updates the channel itself has triggered. -@property (nonatomic, assign) BOOL ignoreNextUpdate; - -// A pointer to the owner of the data. Only use this for pointer comparison, -// never as an object reference. -@property (nonatomic, assign) void *owner; - -+ (instancetype)dataForChannel:(RACKVOChannel *)channel; - -@end - -@interface RACKVOChannel () - -// The object whose key path the channel is wrapping. -@property (atomic, weak) NSObject *target; - -// The key path the channel is wrapping. -@property (nonatomic, copy, readonly) NSString *keyPath; - -// Returns the existing thread local data container or nil if none exists. -@property (nonatomic, strong, readonly) RACKVOChannelData *currentThreadData; - -// Creates the thread local data container for the channel. -- (void)createCurrentThreadData; - -// Destroy the thread local data container for the channel. -- (void)destroyCurrentThreadData; - -@end - -@implementation RACKVOChannel - -#pragma mark Properties - -- (RACKVOChannelData *)currentThreadData { - NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; - - for (RACKVOChannelData *data in dataArray) { - if (data.owner == (__bridge void *)self) return data; - } - - return nil; -} - -#pragma mark Lifecycle - -- (id)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue { - NSCParameterAssert(keyPath.rac_keyPathComponents.count > 0); - - NSObject *strongTarget = target; - - 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]; - - if (strongTarget == nil) { - [self.leadingTerminal sendCompleted]; - return self; - } - - // Observe the key path on target for changes and forward the changes to the - // terminal. - // - // Intentionally capturing `self` strongly in the blocks below, so the - // channel object stays alive while observing. - RACDisposable *observationDisposable = [strongTarget rac_observeKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - // If the change wasn't triggered by deallocation, only affects the last - // path component, and ignoreNextUpdate is set, then it was triggered by - // this channel and should not be forwarded. - if (!causedByDealloc && affectedOnlyLastComponent && self.currentThreadData.ignoreNextUpdate) { - [self destroyCurrentThreadData]; - return; - } - - [self.leadingTerminal sendNext:value]; - }]; - - NSString *keyPathByDeletingLastKeyPathComponent = keyPath.rac_keyPathByDeletingLastKeyPathComponent; - NSArray *keyPathComponents = keyPath.rac_keyPathComponents; - NSUInteger keyPathComponentsCount = keyPathComponents.count; - NSString *lastKeyPathComponent = keyPathComponents.lastObject; - - // Update the value of the property with the values received. - [[self.leadingTerminal - finally:^{ - [observationDisposable dispose]; - }] - subscribeNext:^(id x) { - // Check the value of the second to last key path component. Since the - // channel can only update the value of a property on an object, and not - // update intermediate objects, it can only update the value of the whole - // key path if this object is not nil. - NSObject *object = (keyPathComponentsCount > 1 ? [self.target valueForKeyPath:keyPathByDeletingLastKeyPathComponent] : self.target); - if (object == nil) return; - - // Set the ignoreNextUpdate flag before setting the value so this channel - // ignores the value in the subsequent -didChangeValueForKey: callback. - [self createCurrentThreadData]; - self.currentThreadData.ignoreNextUpdate = YES; - - [object setValue:x ?: nilValue forKey:lastKeyPathComponent]; - } error:^(NSError *error) { - NSCAssert(NO, @"Received error in %@: %@", self, error); - - // Log the error if we're running with assertions disabled. - NSLog(@"Received error in %@: %@", self, error); - }]; - - // Capture `self` weakly for the target's deallocation disposable, so we can - // freely deallocate if we complete before then. - @weakify(self); - - [strongTarget.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - @strongify(self); - [self.leadingTerminal sendCompleted]; - self.target = nil; - }]]; - - return self; -} - -- (void)createCurrentThreadData { - NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; - if (dataArray == nil) { - dataArray = [NSMutableArray array]; - NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey] = dataArray; - [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; - return; - } - - for (RACKVOChannelData *data in dataArray) { - if (data.owner == (__bridge void *)self) return; - } - - [dataArray addObject:[RACKVOChannelData dataForChannel:self]]; -} - -- (void)destroyCurrentThreadData { - NSMutableArray *dataArray = NSThread.currentThread.threadDictionary[RACKVOChannelDataDictionaryKey]; - NSUInteger index = [dataArray indexOfObjectPassingTest:^ BOOL (RACKVOChannelData *data, NSUInteger idx, BOOL *stop) { - return data.owner == (__bridge void *)self; - }]; - - if (index != NSNotFound) [dataArray removeObjectAtIndex:index]; -} - -@end - -@implementation RACKVOChannel (RACChannelTo) - -- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key { - NSCParameterAssert(key != nil); - - RACChannelTerminal *terminal = [self valueForKey:key]; - NSCAssert([terminal isKindOfClass:RACChannelTerminal.class], @"Key \"%@\" does not identify a channel terminal", key); - - return terminal; -} - -- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key { - NSCParameterAssert(otherTerminal != nil); - - RACChannelTerminal *selfTerminal = [self objectForKeyedSubscript:key]; - [otherTerminal subscribe:selfTerminal]; - [[selfTerminal skip:1] subscribe:otherTerminal]; -} - -@end - -@implementation RACKVOChannelData - -+ (instancetype)dataForChannel:(RACKVOChannel *)channel { - RACKVOChannelData *data = [[self alloc] init]; - data->_owner = (__bridge void *)channel; - return data; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACKVOProxy.h b/ReactiveCocoa/Objective-C/RACKVOProxy.h deleted file mode 100644 index 61adc8cafc..0000000000 --- a/ReactiveCocoa/Objective-C/RACKVOProxy.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// RACKVOProxy.h -// ReactiveCocoa -// -// Created by Richard Speyer on 4/10/14. -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// - -#import - -/// A singleton that can act as a proxy between a KVO observation and a RAC -/// subscriber, in order to protect against KVO lifetime issues. -@interface RACKVOProxy : NSObject - -/// Returns the singleton KVO proxy object. -+ (instancetype)sharedProxy; - -/// Registers an observer with the proxy, such that when the proxy receives a -/// KVO change with the given context, it forwards it to the observer. -/// -/// observer - True observer of the KVO change. Must not be nil. -/// context - Arbitrary context object used to differentiate multiple -/// observations of the same keypath. Must be unique, cannot be nil. -- (void)addObserver:(__weak NSObject *)observer forContext:(void *)context; - -/// Removes an observer from the proxy. Parameters must match those passed to -/// addObserver:forContext:. -/// -/// observer - True observer of the KVO change. Must not be nil. -/// context - Arbitrary context object used to differentiate multiple -/// observations of the same keypath. Must be unique, cannot be nil. -- (void)removeObserver:(NSObject *)observer forContext:(void *)context; - -@end diff --git a/ReactiveCocoa/Objective-C/RACKVOProxy.m b/ReactiveCocoa/Objective-C/RACKVOProxy.m deleted file mode 100644 index 0fde1961fd..0000000000 --- a/ReactiveCocoa/Objective-C/RACKVOProxy.m +++ /dev/null @@ -1,70 +0,0 @@ -// -// RACKVOProxy.m -// ReactiveCocoa -// -// Created by Richard Speyer on 4/10/14. -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// - -#import "RACKVOProxy.h" - -@interface RACKVOProxy() - -@property (strong, nonatomic, readonly) NSMapTable *trampolines; -@property (strong, nonatomic, readonly) dispatch_queue_t queue; - -@end - -@implementation RACKVOProxy - -+ (instancetype)sharedProxy { - static RACKVOProxy *proxy; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - proxy = [[self alloc] init]; - }); - - return proxy; -} - -- (instancetype)init { - self = [super init]; - if (self == nil) return nil; - - _queue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.RACKVOProxy", DISPATCH_QUEUE_SERIAL); - _trampolines = [NSMapTable strongToWeakObjectsMapTable]; - - return self; -} - -- (void)addObserver:(__weak NSObject *)observer forContext:(void *)context { - NSValue *valueContext = [NSValue valueWithPointer:context]; - - dispatch_sync(self.queue, ^{ - [self.trampolines setObject:observer forKey:valueContext]; - }); -} - -- (void)removeObserver:(NSObject *)observer forContext:(void *)context { - NSValue *valueContext = [NSValue valueWithPointer:context]; - - dispatch_sync(self.queue, ^{ - [self.trampolines removeObjectForKey:valueContext]; - }); -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - NSValue *valueContext = [NSValue valueWithPointer:context]; - __block NSObject *trueObserver; - - dispatch_sync(self.queue, ^{ - trueObserver = [self.trampolines objectForKey:valueContext]; - }); - - if (trueObserver != nil) { - [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACKVOTrampoline.h b/ReactiveCocoa/Objective-C/RACKVOTrampoline.h deleted file mode 100644 index 8de99dc5f0..0000000000 --- a/ReactiveCocoa/Objective-C/RACKVOTrampoline.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// RACKVOTrampoline.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 1/15/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#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:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block; - -@end diff --git a/ReactiveCocoa/Objective-C/RACKVOTrampoline.m b/ReactiveCocoa/Objective-C/RACKVOTrampoline.m deleted file mode 100644 index a9194cbf34..0000000000 --- a/ReactiveCocoa/Objective-C/RACKVOTrampoline.m +++ /dev/null @@ -1,110 +0,0 @@ -// -// 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" -#import "RACKVOProxy.h" - -@interface RACKVOTrampoline () - -// The keypath which the trampoline is observing. -@property (nonatomic, readonly, copy) NSString *keyPath; - -// These properties should only be manipulated while synchronized on the -// receiver. -@property (nonatomic, readonly, copy) RACKVOBlock block; -@property (nonatomic, readonly, unsafe_unretained) NSObject *unsafeTarget; -@property (nonatomic, readonly, weak) NSObject *weakTarget; -@property (nonatomic, readonly, weak) NSObject *observer; - -@end - -@implementation RACKVOTrampoline - -#pragma mark Lifecycle - -- (id)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block { - NSCParameterAssert(keyPath != nil); - NSCParameterAssert(block != nil); - - NSObject *strongTarget = target; - if (strongTarget == nil) return nil; - - self = [super init]; - if (self == nil) return nil; - - _keyPath = [keyPath copy]; - - _block = [block copy]; - _weakTarget = target; - _unsafeTarget = strongTarget; - _observer = observer; - - [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self]; - [strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self]; - - [strongTarget.rac_deallocDisposable addDisposable:self]; - [self.observer.rac_deallocDisposable addDisposable:self]; - - return self; -} - -- (void)dealloc { - [self dispose]; -} - -#pragma mark Observation - -- (void)dispose { - NSObject *target; - NSObject *observer; - - @synchronized (self) { - _block = nil; - - // The target should still exist at this point, because we still need to - // tear down its KVO observation. Therefore, we can use the unsafe - // reference (and need to, because the weak one will have been zeroed by - // now). - target = self.unsafeTarget; - observer = self.observer; - - _unsafeTarget = nil; - _observer = nil; - } - - [target.rac_deallocDisposable removeDisposable:self]; - [observer.rac_deallocDisposable removeDisposable:self]; - - [target removeObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath context:(__bridge void *)self]; - [RACKVOProxy.sharedProxy removeObserver:self forContext:(__bridge void *)self]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (context != (__bridge void *)self) { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - return; - } - - RACKVOBlock block; - id observer; - id target; - - @synchronized (self) { - block = self.block; - observer = self.observer; - target = self.weakTarget; - } - - if (block == nil || target == nil) return; - - block(target, observer, change); -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACMulticastConnection+Private.h b/ReactiveCocoa/Objective-C/RACMulticastConnection+Private.h deleted file mode 100644 index 43931f85f5..0000000000 --- a/ReactiveCocoa/Objective-C/RACMulticastConnection+Private.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACMulticastConnection.h b/ReactiveCocoa/Objective-C/RACMulticastConnection.h deleted file mode 100644 index 67beff6bd5..0000000000 --- a/ReactiveCocoa/Objective-C/RACMulticastConnection.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// RACMulticastConnection.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/11/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@class RACDisposable; -@class RACSignal; - -/// 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/ReactiveCocoa/Objective-C/RACMulticastConnection.m b/ReactiveCocoa/Objective-C/RACMulticastConnection.m deleted file mode 100644 index 1534d96f46..0000000000 --- a/ReactiveCocoa/Objective-C/RACMulticastConnection.m +++ /dev/null @@ -1,85 +0,0 @@ -// -// 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 - -@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 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/ReactiveCocoa/Objective-C/RACPassthroughSubscriber.h b/ReactiveCocoa/Objective-C/RACPassthroughSubscriber.h deleted file mode 100644 index 9ae547bc8b..0000000000 --- a/ReactiveCocoa/Objective-C/RACPassthroughSubscriber.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// RACPassthroughSubscriber.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-06-13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import "RACSubscriber.h" - -@class RACCompoundDisposable; -@class RACSignal; - -// A private subscriber that passes through all events to another subscriber -// while not disposed. -@interface RACPassthroughSubscriber : NSObject - -// 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)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable; - -@end diff --git a/ReactiveCocoa/Objective-C/RACPassthroughSubscriber.m b/ReactiveCocoa/Objective-C/RACPassthroughSubscriber.m deleted file mode 100644 index 7857e553c8..0000000000 --- a/ReactiveCocoa/Objective-C/RACPassthroughSubscriber.m +++ /dev/null @@ -1,107 +0,0 @@ -// -// 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" - -#if !defined(DTRACE_PROBES_DISABLED) || !DTRACE_PROBES_DISABLED - -static const char *cleanedDTraceString(NSString *original) { - return [original stringByReplacingOccurrencesOfString:@"\\s+" withString:@" " options:NSRegularExpressionSearch range:NSMakeRange(0, original.length)].UTF8String; -} - -static const char *cleanedSignalDescription(RACSignal *signal) { - NSString *desc = signal.description; - - NSRange range = [desc rangeOfString:@" name:"]; - if (range.location != NSNotFound) { - desc = [desc stringByReplacingCharactersInRange:range withString:@""]; - } - - return cleanedDTraceString(desc); -} - -#endif - -@interface RACPassthroughSubscriber () - -// The subscriber to which events should be forwarded. -@property (nonatomic, strong, readonly) id 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)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/ReactiveCocoa/Objective-C/RACQueueScheduler+Subclass.h b/ReactiveCocoa/Objective-C/RACQueueScheduler+Subclass.h deleted file mode 100644 index b0f5da60f6..0000000000 --- a/ReactiveCocoa/Objective-C/RACQueueScheduler+Subclass.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// RACQueueScheduler+Subclass.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 6/6/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import "RACQueueScheduler.h" -#import "RACScheduler+Subclass.h" - -/// An interface for use by GCD queue-based subclasses. -/// -/// See RACScheduler+Subclass.h for subclassing notes. -@interface RACQueueScheduler () - -/// The queue on which blocks are enqueued. -#if OS_OBJECT_USE_OBJC -@property (nonatomic, strong, readonly) dispatch_queue_t queue; -#else -// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :( -@property (nonatomic, assign, readonly) dispatch_queue_t queue; -#endif - -/// Initializes the receiver with the name of the scheduler and the queue which -/// the scheduler should use. -/// -/// name - The name of the scheduler. If nil, a default name will be used. -/// queue - The queue upon which the receiver should enqueue scheduled blocks. -/// This argument must not be NULL. -/// -/// Returns the initialized object. -- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue; - -/// Converts a date into a GCD time using dispatch_walltime(). -/// -/// date - The date to convert. This must not be nil. -+ (dispatch_time_t)wallTimeWithDate:(NSDate *)date; - -@end diff --git a/ReactiveCocoa/Objective-C/RACQueueScheduler.h b/ReactiveCocoa/Objective-C/RACQueueScheduler.h deleted file mode 100644 index ef42512f9f..0000000000 --- a/ReactiveCocoa/Objective-C/RACQueueScheduler.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACQueueScheduler.m b/ReactiveCocoa/Objective-C/RACQueueScheduler.m deleted file mode 100644 index 7cc1ce4351..0000000000 --- a/ReactiveCocoa/Objective-C/RACQueueScheduler.m +++ /dev/null @@ -1,107 +0,0 @@ -// -// RACQueueScheduler.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 11/30/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACQueueScheduler.h" -#import "RACDisposable.h" -#import "RACQueueScheduler+Subclass.h" -#import "RACScheduler+Private.h" - -@implementation RACQueueScheduler - -#pragma mark Lifecycle - -- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue { - NSCParameterAssert(queue != NULL); - - self = [super initWithName:name]; - if (self == nil) return nil; - - _queue = queue; -#if !OS_OBJECT_USE_OBJC - dispatch_retain(_queue); -#endif - - return self; -} - -#if !OS_OBJECT_USE_OBJC - -- (void)dealloc { - if (_queue != NULL) { - dispatch_release(_queue); - _queue = NULL; - } -} - -#endif - -#pragma mark Date Conversions - -+ (dispatch_time_t)wallTimeWithDate:(NSDate *)date { - NSCParameterAssert(date != nil); - - double seconds = 0; - double frac = modf(date.timeIntervalSince1970, &seconds); - - struct timespec walltime = { - .tv_sec = (time_t)fmin(fmax(seconds, LONG_MIN), LONG_MAX), - .tv_nsec = (long)fmin(fmax(frac * NSEC_PER_SEC, LONG_MIN), LONG_MAX) - }; - - return dispatch_walltime(&walltime, 0); -} - -#pragma mark RACScheduler - -- (RACDisposable *)schedule:(void (^)(void))block { - NSCParameterAssert(block != NULL); - - RACDisposable *disposable = [[RACDisposable alloc] init]; - - dispatch_async(self.queue, ^{ - if (disposable.disposed) return; - [self performAsCurrentScheduler:block]; - }); - - return disposable; -} - -- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(block != NULL); - - RACDisposable *disposable = [[RACDisposable alloc] init]; - - dispatch_after([self.class wallTimeWithDate:date], self.queue, ^{ - if (disposable.disposed) return; - [self performAsCurrentScheduler:block]; - }); - - return disposable; -} - -- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block { - NSCParameterAssert(date != nil); - NSCParameterAssert(interval > 0.0 && interval < INT64_MAX / NSEC_PER_SEC); - NSCParameterAssert(leeway >= 0.0 && leeway < INT64_MAX / NSEC_PER_SEC); - NSCParameterAssert(block != NULL); - - uint64_t intervalInNanoSecs = (uint64_t)(interval * NSEC_PER_SEC); - uint64_t leewayInNanoSecs = (uint64_t)(leeway * NSEC_PER_SEC); - - dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); - dispatch_source_set_timer(timer, [self.class wallTimeWithDate:date], intervalInNanoSecs, leewayInNanoSecs); - dispatch_source_set_event_handler(timer, block); - dispatch_resume(timer); - - return [RACDisposable disposableWithBlock:^{ - dispatch_source_cancel(timer); - }]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACReplaySubject.h b/ReactiveCocoa/Objective-C/RACReplaySubject.h deleted file mode 100644 index 3d429ea47b..0000000000 --- a/ReactiveCocoa/Objective-C/RACReplaySubject.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACReplaySubject.m b/ReactiveCocoa/Objective-C/RACReplaySubject.m deleted file mode 100644 index 4cf1078090..0000000000 --- a/ReactiveCocoa/Objective-C/RACReplaySubject.m +++ /dev/null @@ -1,112 +0,0 @@ -// -// RACReplaySubject.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/14/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACReplaySubject.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACScheduler+Private.h" -#import "RACSubscriber.h" -#import "RACTuple.h" - -const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax; - -@interface RACReplaySubject () - -@property (nonatomic, assign, readonly) NSUInteger capacity; - -// These properties should only be modified while synchronized on self. -@property (nonatomic, strong, readonly) NSMutableArray *valuesReceived; -@property (nonatomic, assign) BOOL hasCompleted; -@property (nonatomic, assign) BOOL hasError; -@property (nonatomic, strong) NSError *error; - -@end - - -@implementation RACReplaySubject - -#pragma mark Lifecycle - -+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity { - return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity]; -} - -- (instancetype)init { - return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity]; -} - -- (instancetype)initWithCapacity:(NSUInteger)capacity { - self = [super init]; - if (self == nil) return nil; - - _capacity = capacity; - _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]); - - return self; -} - -#pragma mark RACSignal - -- (RACDisposable *)subscribe:(id)subscriber { - RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; - - RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ - @synchronized (self) { - for (id value in self.valuesReceived) { - if (compoundDisposable.disposed) return; - - [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)]; - } - - if (compoundDisposable.disposed) return; - - if (self.hasCompleted) { - [subscriber sendCompleted]; - } else if (self.hasError) { - [subscriber sendError:self.error]; - } else { - RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; - [compoundDisposable addDisposable:subscriptionDisposable]; - } - } - }]; - - [compoundDisposable addDisposable:schedulingDisposable]; - - return compoundDisposable; -} - -#pragma mark RACSubscriber - -- (void)sendNext:(id)value { - @synchronized (self) { - [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil]; - [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/ReactiveCocoa/Objective-C/RACReturnSignal.h b/ReactiveCocoa/Objective-C/RACReturnSignal.h deleted file mode 100644 index 73e56746aa..0000000000 --- a/ReactiveCocoa/Objective-C/RACReturnSignal.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACReturnSignal.m b/ReactiveCocoa/Objective-C/RACReturnSignal.m deleted file mode 100644 index 300663457f..0000000000 --- a/ReactiveCocoa/Objective-C/RACReturnSignal.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// 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)subscriber { - NSCParameterAssert(subscriber != nil); - - return [RACScheduler.subscriptionScheduler schedule:^{ - [subscriber sendNext:self.value]; - [subscriber sendCompleted]; - }]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACScheduler+Private.h b/ReactiveCocoa/Objective-C/RACScheduler+Private.h deleted file mode 100644 index 2c91e66c76..0000000000 --- a/ReactiveCocoa/Objective-C/RACScheduler+Private.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACScheduler+Subclass.h b/ReactiveCocoa/Objective-C/RACScheduler+Subclass.h deleted file mode 100644 index b6e8a9e953..0000000000 --- a/ReactiveCocoa/Objective-C/RACScheduler+Subclass.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// RACScheduler.m -// ReactiveCocoa -// -// Created by Miķelis Vindavs on 5/27/14. -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// - -#import -#import "RACScheduler.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 RACScheduler () - -/// Performs the given block with the receiver as the current scheduler for -/// its thread. This should only be called by subclasses to perform their -/// scheduled blocks. -/// -/// block - The block to execute. Cannot be NULL. -- (void)performAsCurrentScheduler:(void (^)(void))block; - -@end diff --git a/ReactiveCocoa/Objective-C/RACScheduler.h b/ReactiveCocoa/Objective-C/RACScheduler.h deleted file mode 100644 index 81e9f1806c..0000000000 --- a/ReactiveCocoa/Objective-C/RACScheduler.h +++ /dev/null @@ -1,148 +0,0 @@ -// -// RACScheduler.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/16/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -/// 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 (Unavailable) - -+ (RACScheduler *)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name __attribute__((unavailable("Use -[RACTargetQueueScheduler initWithName:targetQueue:] instead."))); - -@end diff --git a/ReactiveCocoa/Objective-C/RACScheduler.m b/ReactiveCocoa/Objective-C/RACScheduler.m deleted file mode 100644 index b414041f77..0000000000 --- a/ReactiveCocoa/Objective-C/RACScheduler.m +++ /dev/null @@ -1,212 +0,0 @@ -// -// RACScheduler.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/16/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACScheduler.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]; - } -} - -- (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; - - @autoreleasepool { - block(); - } - - if (previousScheduler != nil) { - NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey] = previousScheduler; - } else { - [NSThread.currentThread.threadDictionary removeObjectForKey:RACSchedulerCurrentSchedulerKey]; - } -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACScopedDisposable.h b/ReactiveCocoa/Objective-C/RACScopedDisposable.h deleted file mode 100644 index 69c4a125e5..0000000000 --- a/ReactiveCocoa/Objective-C/RACScopedDisposable.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACScopedDisposable.m b/ReactiveCocoa/Objective-C/RACScopedDisposable.m deleted file mode 100644 index 91115be5ef..0000000000 --- a/ReactiveCocoa/Objective-C/RACScopedDisposable.m +++ /dev/null @@ -1,32 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACSequence.h b/ReactiveCocoa/Objective-C/RACSequence.h deleted file mode 100644 index bd3b51c8bc..0000000000 --- a/ReactiveCocoa/Objective-C/RACSequence.h +++ /dev/null @@ -1,155 +0,0 @@ -// -// RACSequence.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-29. -// Copyright (c) 2012 GitHub. All rights reserved. -// - -#import -#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 - -/// 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 there are no other -/// objects. -/// -/// 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 (Unavailable) - -- (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine __attribute__((unavailable("Renamed to -foldLeftWithStart:reduce:"))); -- (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine __attribute__((unavailable("Renamed to -foldRightWithStart:reduce:"))); - -@end diff --git a/ReactiveCocoa/Objective-C/RACSequence.m b/ReactiveCocoa/Objective-C/RACSequence.m deleted file mode 100644 index 94491c5e86..0000000000 --- a/ReactiveCocoa/Objective-C/RACSequence.m +++ /dev/null @@ -1,371 +0,0 @@ -// -// 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 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, scheduler]; -} - -- (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:^{ - if (self.tail) { - return [self.tail foldRightWithStart:start reduce:reduce]; - } else { - return start; - } - } 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 selfObj in self) { - id 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 diff --git a/ReactiveCocoa/Objective-C/RACSerialDisposable.h b/ReactiveCocoa/Objective-C/RACSerialDisposable.h deleted file mode 100644 index a3fc1d45eb..0000000000 --- a/ReactiveCocoa/Objective-C/RACSerialDisposable.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACSerialDisposable.m b/ReactiveCocoa/Objective-C/RACSerialDisposable.m deleted file mode 100644 index a6fca221d8..0000000000 --- a/ReactiveCocoa/Objective-C/RACSerialDisposable.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// RACSerialDisposable.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-07-22. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import "RACSerialDisposable.h" -#import - -@interface RACSerialDisposable () { - // The receiver's `disposable`. This variable must only be referenced while - // _mutex is held. - RACDisposable * _disposable; - - // YES if the receiver has been disposed. This variable must only be accessed - // while _mutex is held. - BOOL _disposed; - - // A mutex to protect access to _disposable and _disposed. - pthread_mutex_t _mutex; -} - -@end - -@implementation RACSerialDisposable - -#pragma mark Properties - -- (BOOL)isDisposed { - pthread_mutex_lock(&_mutex); - const BOOL disposed = _disposed; - pthread_mutex_unlock(&_mutex); - - return disposed; -} - -- (RACDisposable *)disposable { - pthread_mutex_lock(&_mutex); - RACDisposable * const result = _disposable; - pthread_mutex_unlock(&_mutex); - - return result; -} - -- (void)setDisposable:(RACDisposable *)disposable { - [self swapInDisposable:disposable]; -} - -#pragma mark Lifecycle - -+ (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable { - RACSerialDisposable *serialDisposable = [[self alloc] init]; - serialDisposable.disposable = disposable; - return serialDisposable; -} - -- (instancetype)init { - self = [super init]; - if (self == nil) return nil; - - const int result = pthread_mutex_init(&_mutex, NULL); - NSCAssert(0 == result, @"Failed to initialize mutex with error %d", result); - - return self; -} - -- (instancetype)initWithBlock:(void (^)(void))block { - self = [self init]; - if (self == nil) return nil; - - self.disposable = [RACDisposable disposableWithBlock:block]; - - return self; -} - -- (void)dealloc { - const int result = pthread_mutex_destroy(&_mutex); - NSCAssert(0 == result, @"Failed to destroy mutex with error %d", result); -} - -#pragma mark Inner Disposable - -- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable { - RACDisposable *existingDisposable; - BOOL alreadyDisposed; - - pthread_mutex_lock(&_mutex); - alreadyDisposed = _disposed; - if (!alreadyDisposed) { - existingDisposable = _disposable; - _disposable = newDisposable; - } - pthread_mutex_unlock(&_mutex); - - if (alreadyDisposed) { - [newDisposable dispose]; - return nil; - } - - return existingDisposable; -} - -#pragma mark Disposal - -- (void)dispose { - RACDisposable *existingDisposable; - - pthread_mutex_lock(&_mutex); - if (!_disposed) { - existingDisposable = _disposable; - _disposed = YES; - _disposable = nil; - } - pthread_mutex_unlock(&_mutex); - - [existingDisposable dispose]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACSignal+Operations.h b/ReactiveCocoa/Objective-C/RACSignal+Operations.h deleted file mode 100644 index ff04a8d634..0000000000 --- a/ReactiveCocoa/Objective-C/RACSignal+Operations.h +++ /dev/null @@ -1,728 +0,0 @@ -// -// RACSignal+Operations.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-09-06. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import "RACSignal.h" - -/// The domain for errors originating in RACSignal operations. -extern NSString * _Nonnull 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 RACCommand; -@class RACDisposable; -@class RACMulticastConnection; -@class RACScheduler; -@class RACSequence; -@class RACSubject; -@class RACTuple; -@protocol RACSubscriber; - -@interface RACSignal (Operations) -NS_ASSUME_NONNULL_BEGIN - -/// Do the given block on `next`. This should be used to inject side effects into -/// the signal. -- (RACSignal *)doNext:(void (^)(id _Nullable x))block; - -/// Do the given block on `error`. This should be used to inject side effects -/// into the signal. -- (RACSignal *)doError:(void (^)(NSError * _Nonnull error))block; - -/// Do the given block on `completed`. This should be used to inject side effects -/// into the signal. -- (RACSignal *)doCompleted:(void (^)(void))block; - -/// Sends `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 _Nullable 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; - -/// Executes 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; - -/// Executes 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; - -/// Collects 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)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)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)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(IObservable>, 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; - -/// Aggregates the `next` values of the receiver into a single combined value. -/// -/// The algorithm proceeds as follows: -/// -/// 1. `start` 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 (`running`) and the next element of the -/// receiver (`next`) is passed into `reduceBlock`. -/// 3. Steps 2 and 3 are repeated until all values have been processed. -/// 4. The last result of `reduceBlock` is sent on the returned signal. -/// -/// This method is similar to -scanWithStart:reduce:, except that only the -/// final result is sent on the returned signal. -/// -/// start - The value to be combined with the first element of the -/// receiver. This value may be `nil`. -/// reduceBlock - The block that describes how to combine values of the -/// receiver. If the receiver is empty, this block will never be -/// invoked. Cannot be nil. -/// -/// Returns a signal that will send the aggregated value when the receiver -/// completes, then itself complete. If the receiver never sends any values, -/// `start` will be sent instead. -- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock; - -/// Aggregates the `next` values of the receiver into a single combined value. -/// This is indexed version of -aggregateWithStart:reduce:. -/// -/// start - The value to be combined with the first element of the -/// receiver. This value may be `nil`. -/// reduceBlock - The block that describes how to combine values of the -/// receiver. This block takes zero-based index value as the last -/// parameter. If the receiver is empty, this block will never be -/// invoked. Cannot be nil. -/// -/// Returns a signal that will send the aggregated value when the receiver -/// completes, then itself complete. If the receiver never sends any values, -/// `start` will be sent instead. -- (RACSignal *)aggregateWithStart:(id)start reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock; - -/// Aggregates the `next` values of the receiver into a single combined value. -/// -/// This invokes `startFactory` block on each subscription, then calls -/// -aggregateWithStart:reduce: with the return value of the block as start value. -/// -/// startFactory - The block that returns start value which will be combined -/// with the first element of the receiver. Cannot be nil. -/// reduceBlock - The block that describes how to combine values of the -/// receiver. If the receiver is empty, this block will never be -/// invoked. Cannot be nil. -/// -/// Returns a signal that will send the aggregated value when the receiver -/// completes, then itself complete. If the receiver never sends any values, -/// the return value of `startFactory` will be sent instead. -- (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:(nullable 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; - -/// Takes `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; - -/// Takes `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; - -/// Subscribes to the returned signal when an error occurs. -- (RACSignal *)catch:(RACSignal * (^)(NSError * _Nonnull error))catchBlock; - -/// Subscribes to the given signal when an error occurs. -- (RACSignal *)catchTo:(RACSignal *)signal; - -/// Returns a signal that will either immediately send the return value of -/// `tryBlock` and complete, or error using the `NSError` passed out from the -/// block. -/// -/// tryBlock - An action that performs some computation that could fail. If the -/// block returns nil, the block must return an error via the -/// `errorPtr` parameter. -/// -/// Example: -/// -/// [RACSignal try:^(NSError **error) { -/// return [NSJSONSerialization JSONObjectWithData:someJSONData options:0 error:error]; -/// }]; -+ (RACSignal *)try:(id (^)(NSError **errorPtr))tryBlock; - -/// 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 _Nullable 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 _Nullable value, NSError **errorPtr))mapBlock; - -/// Returns the first `next`. Note that this is a blocking call. -- (nullable 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. -- (nullable id)firstOrDefault:(nullable 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. -- (nullable id)firstOrDefault:(nullable id)defaultValue success:(nullable BOOL *)success error:(NSError * _Nullable * _Nullable)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 * _Nullable * _Nullable)error; - -/// Defers 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:(nullable 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; - -/// Adds 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. -- (nullable NSArray *)toArray; - -/// Adds 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; - -/// Creates and returns a signal that delivers its events on the main thread. -/// If events are already being sent on the main thread, they may be passed on -/// without delay. An event will instead be queued for later delivery on the main -/// thread if sent on another thread, or if a previous event is already being -/// processed, or has been queued. -/// -/// Any side effects of the receiver will still be performed on the original -/// thread. -/// -/// This can be used when a signal will cause UI updates, to avoid potential -/// flicker caused by delayed delivery of events, such as the first event from -/// a RACObserve at view instantiation. -- (RACSignal *)deliverOnMainThread; - -/// 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 _Nullable (^)(id _Nullable object))keyBlock transform:(nullable id _Nullable (^)(id _Nullable object))transformBlock; - -/// Calls -[RACSignal groupBy:keyBlock transform:nil]. -- (RACSignal *)groupBy:(id _Nullable (^)(id _Nullable 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 _Nullable 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 _Nullable 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; - -/// Sends the result of calling the block with arguments as packed in each RACTuple -/// sent by the receiver. -/// -/// The receiver must send tuple values, where the first element of the tuple is -/// a block, taking a number of parameters equal to the count of the remaining -/// elements of the tuple, and returning an object. Each block must take at least -/// one argument, so each tuple must contain at least 2 elements. -/// -/// Example: -/// -/// RACSignal *adder = [RACSignal return:^(NSNumber *a, NSNumber *b) { -/// return @(a.intValue + b.intValue); -/// }]; -/// RACSignal *sums = [[RACSignal -/// combineLatest:@[ adder, as, bs ]] -/// reduceApply]; -/// -/// Returns a signal of the result of applying the first element of each tuple -/// to the remaining elements. -- (RACSignal *)reduceApply; - -NS_ASSUME_NONNULL_END -@end - -@interface RACSignal (UnavailableOperations) -NS_ASSUME_NONNULL_BEGIN - -- (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock __attribute__((unavailable("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); -- (RACSignal *)buffer:(NSUInteger)bufferCount __attribute__((unavailable("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587"))); -- (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock __attribute__((unavailable("Use -publish instead"))); -+ (RACSignal *)interval:(NSTimeInterval)interval __attribute__((unavailable("Use +interval:onScheduler: instead"))); -+ (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway __attribute__((unavailable("Use +interval:onScheduler:withLeeway: instead"))); -- (RACSignal *)bufferWithTime:(NSTimeInterval)interval __attribute__((unavailable("Use -bufferWithTime:onScheduler: instead"))); -- (RACSignal *)timeout:(NSTimeInterval)interval __attribute__((unavailable("Use -timeout:onScheduler: instead"))); -- (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object __attribute__((unavailable("Renamed to -setKeyPath:onObject:"))); -- (RACSignal *)ignoreElements __attribute__((unavailable("Renamed to -ignoreValues"))); -- (RACSignal *)sequenceNext:(RACSignal * (^)(void))block __attribute__((unavailable("Renamed to -then:"))); -- (RACSignal *)aggregateWithStart:(nullable id)start combine:(id _Nullable (^)(id _Nullable running, id _Nullable next))combineBlock __attribute__((unavailable("Renamed to -aggregateWithStart:reduce:"))); -- (RACSignal *)aggregateWithStartFactory:(id _Nullable (^)(void))startFactory combine:(id _Nullable (^)(id _Nullable running, id _Nullable next))combineBlock __attribute__((unavailable("Renamed to -aggregateWithStartFactory:reduce:"))); -- (RACDisposable *)executeCommand:(RACCommand *)command __attribute__((unavailable("Use -flattenMap: or -subscribeNext: instead"))); - -NS_ASSUME_NONNULL_END -@end diff --git a/ReactiveCocoa/Objective-C/RACSignal+Operations.m b/ReactiveCocoa/Objective-C/RACSignal+Operations.m deleted file mode 100644 index e8c33ca6fe..0000000000 --- a/ReactiveCocoa/Objective-C/RACSignal+Operations.m +++ /dev/null @@ -1,1333 +0,0 @@ -// -// 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 "RACBlockTrampoline.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.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 -#import - -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 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 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 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 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 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 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 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:(id (^)(NSError **errorPtr))tryBlock { - NSCParameterAssert(tryBlock != NULL); - - return [[RACSignal createSignal:^(id subscriber) { - NSError *error; - id value = tryBlock(&error); - RACSignal *signal = (value == nil ? [RACSignal error:error] : [RACSignal return:value]); - return [signal subscribe:subscriber]; - }] setNameWithFormat:@"+try:"]; -} - -- (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 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]; - [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 onScheduler: %@", self.name, (double)interval, scheduler]; -} - -- (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 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 == RACTupleNil.tupleNil ? nil : value]; - } - - [subscriber sendCompleted]; - }]; - }] setNameWithFormat:@"[%@] -takeLast: %lu", self.name, (unsigned long)count]; -} - -- (RACSignal *)combineLatestWith:(RACSignal *)signal { - NSCParameterAssert(signal != nil); - - return [[RACSignal createSignal:^(id 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:RACTuplePack(lastSelfValue, lastOtherValue)]; - } - }; - - 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)signals { - return [[self join:signals block:^(RACSignal *left, RACSignal *right) { - return [left combineLatestWith:right]; - }] setNameWithFormat:@"+combineLatest: %@", signals]; -} - -+ (RACSignal *)combineLatest:(id)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)signals { - NSMutableArray *copiedSignals = [[NSMutableArray alloc] init]; - for (RACSignal *signal in signals) { - [copiedSignals addObject:signal]; - } - - return [[[RACSignal - createSignal:^ RACDisposable * (id 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 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. - __block void (^subscribeToSignal)(RACSignal *); - - // Weak reference to the above, to avoid a leak. - __weak __block void (^recur)(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]; - } - }; - - // The signals waiting to be started. - // - // This array should only be used while synchronized on `subscriber`. - NSMutableArray *queuedSignals = [NSMutableArray array]; - - recur = 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:^{ - __strong void (^subscribeToSignal)(RACSignal *) = recur; - RACSignal *nextSignal; - - @synchronized (subscriber) { - [compoundDisposable removeDisposable:serialDisposable]; - [activeDisposables removeObjectIdenticalTo:serialDisposable]; - - if (queuedSignals.count == 0) { - completeIfAllowed(); - return; - } - - nextSignal = queuedSignals[0]; - [queuedSignals removeObjectAtIndex:0]; - } - - subscribeToSignal(nextSignal); - }]; - }; - - [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(); - } - }]]; - - [compoundDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - // A strong reference is held to `subscribeToSignal` until we're - // done, preventing it from deallocating early. - subscribeToSignal = nil; - }]]; - - 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 defer:^{ - return [self aggregateWithStart:startFactory() reduce:reduceBlock]; - }] setNameWithFormat:@"[%@] -aggregateWithStartFactory:reduce:", self.name]; -} - -- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock { - return [[self - aggregateWithStart:start - reduceWithIndex:^(id running, id next, NSUInteger index) { - return reduceBlock(running, next); - }] - setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduce:", self.name, RACDescription(start)]; -} - -- (RACSignal *)aggregateWithStart:(id)start reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock { - return [[[[self - scanWithStart:start reduceWithIndex:reduceBlock] - startWith:start] - takeLast:1] - setNameWithFormat:@"[%@] -aggregateWithStart: %@ reduceWithIndex:", self.name, RACDescription(start)]; -} - -- (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 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 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 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 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 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 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 onScheduler: %@", self.name, (double)interval, scheduler]; -} - -- (RACSignal *)deliverOn:(RACScheduler *)scheduler { - return [[RACSignal createSignal:^(id 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 subscriber) { - RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; - - RACDisposable *schedulingDisposable = [scheduler schedule:^{ - RACDisposable *subscriptionDisposable = [self subscribe:subscriber]; - - [disposable addDisposable:subscriptionDisposable]; - }]; - - [disposable addDisposable:schedulingDisposable]; - return disposable; - }] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler]; -} - -- (RACSignal *)deliverOnMainThread { - return [[RACSignal createSignal:^(id subscriber) { - __block volatile int32_t queueLength = 0; - - void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) { - int32_t queued = OSAtomicIncrement32(&queueLength); - if (NSThread.isMainThread && queued == 1) { - block(); - OSAtomicDecrement32(&queueLength); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - block(); - OSAtomicDecrement32(&queueLength); - }); - } - }; - - return [self subscribeNext:^(id x) { - performOnMainThread(^{ - [subscriber sendNext:x]; - }); - } error:^(NSError *error) { - performOnMainThread(^{ - [subscriber sendError:error]; - }); - } completed:^{ - performOnMainThread(^{ - [subscriber sendCompleted]; - }); - }]; - }] setNameWithFormat:@"[%@] -deliverOnMainThread", self.name]; -} - -- (RACSignal *)groupBy:(id (^)(id object))keyBlock transform:(id (^)(id object))transformBlock { - NSCParameterAssert(keyBlock != NULL); - - return [[RACSignal createSignal:^(id subscriber) { - NSMutableDictionary *groups = [NSMutableDictionary dictionary]; - NSMutableArray *orderedGroups = [NSMutableArray array]; - - return [self subscribeNext:^(id x) { - id key = keyBlock(x); - RACGroupedSignal *groupSubject = nil; - @synchronized(groups) { - groupSubject = groups[key]; - if (groupSubject == nil) { - groupSubject = [RACGroupedSignal signalWithKey:key]; - groups[key] = groupSubject; - [orderedGroups addObject:groupSubject]; - [subscriber sendNext:groupSubject]; - } - } - - [groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x]; - } error:^(NSError *error) { - [subscriber sendError:error]; - - [orderedGroups makeObjectsPerformSelector:@selector(sendError:) withObject:error]; - } completed:^{ - [subscriber sendCompleted]; - - [orderedGroups makeObjectsPerformSelector:@selector(sendCompleted)]; - }]; - }] setNameWithFormat:@"[%@] -groupBy:transform:", self.name]; -} - -- (RACSignal *)groupBy:(id (^)(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 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 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 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]; -} - -- (RACSignal *)reduceApply { - return [[self map:^(RACTuple *tuple) { - NSCAssert([tuple isKindOfClass:RACTuple.class], @"-reduceApply must only be used on a signal of RACTuples. Instead, received: %@", tuple); - NSCAssert(tuple.count > 1, @"-reduceApply must only be used on a signal of RACTuples, with at least a block in tuple[0] and its first argument in tuple[1]"); - - // We can't use -array, because we need to preserve RACTupleNil - NSMutableArray *tupleArray = [NSMutableArray arrayWithCapacity:tuple.count]; - for (id val in tuple) { - [tupleArray addObject:val]; - } - RACTuple *arguments = [RACTuple tupleWithObjectsFromArray:[tupleArray subarrayWithRange:NSMakeRange(1, tupleArray.count - 1)]]; - - return [RACBlockTrampoline invokeBlock:tuple[0] withArguments:arguments]; - }] setNameWithFormat:@"[%@] -reduceApply", self.name]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACSignal.h b/ReactiveCocoa/Objective-C/RACSignal.h deleted file mode 100644 index 48cdcf2745..0000000000 --- a/ReactiveCocoa/Objective-C/RACSignal.h +++ /dev/null @@ -1,223 +0,0 @@ -// -// RACSignal.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/1/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import "RACStream.h" - -@class RACDisposable; -@class RACScheduler; -@class RACSubject; -@protocol RACSubscriber; - -@interface RACSignal : RACStream -NS_ASSUME_NONNULL_BEGIN - -/// 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 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 * _Nullable (^)(id subscriber))didSubscribe; - -/// Returns a signal that immediately sends the given error. -+ (RACSignal *)error:(nullable 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 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 subscriber))block; - -@end - -@interface RACSignal (RACStream) - -/// Returns a signal that immediately sends the given value and then completes. -+ (RACSignal *)return:(nullable 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)subscriber; - -/// Convenience method to subscribe to the `next` event. -/// -/// This corresponds to `IObserver.OnNext` in Rx. -- (RACDisposable *)subscribeNext:(void (^)(id _Nullable x))nextBlock; - -/// Convenience method to subscribe to the `next` and `completed` events. -- (RACDisposable *)subscribeNext:(void (^)(id _Nullable x))nextBlock completed:(void (^)(void))completedBlock; - -/// Convenience method to subscribe to the `next`, `completed`, and `error` events. -- (RACDisposable *)subscribeNext:(void (^)(id _Nullable x))nextBlock error:(void (^)(NSError * _Nullable error))errorBlock completed:(void (^)(void))completedBlock; - -/// Convenience method to subscribe to `error` events. -/// -/// This corresponds to the `IObserver.OnError` in Rx. -- (RACDisposable *)subscribeError:(void (^)(NSError * _Nullable error))errorBlock; - -/// Convenience method to subscribe to `completed` events. -/// -/// This corresponds to the `IObserver.OnCompleted` in Rx. -- (RACDisposable *)subscribeCompleted:(void (^)(void))completedBlock; - -/// Convenience method to subscribe to `next` and `error` events. -- (RACDisposable *)subscribeNext:(void (^)(id _Nullable x))nextBlock error:(void (^)(NSError * _Nullable error))errorBlock; - -/// Convenience method to subscribe to `error` and `completed` events. -- (RACDisposable *)subscribeError:(void (^)(NSError * _Nullable 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. -- (nullable id)asynchronousFirstOrDefault:(nullable id)defaultValue success:(nullable BOOL *)success error:(NSError * _Nullable * _Nullable)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 * _Nullable * _Nullable)error; - -NS_ASSUME_NONNULL_END -@end - -@interface RACSignal (Unavailable) -NS_ASSUME_NONNULL_BEGIN - -+ (RACSignal *)start:(id (^)(BOOL *success, NSError * _Nullable * _Nullable error))block __attribute__((unavailable("Use +startEagerlyWithScheduler:block: instead"))); -+ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler subjectBlock:(void (^)(RACSubject *subject))block __attribute__((unavailable("Use +startEagerlyWithScheduler:block: instead"))); -+ (RACSignal *)startWithScheduler:(RACScheduler *)scheduler block:(id (^)(BOOL *success, NSError * _Nullable * _Nullable error))block __attribute__((unavailable("Use +startEagerlyWithScheduler:block: instead"))); - -NS_ASSUME_NONNULL_END -@end diff --git a/ReactiveCocoa/Objective-C/RACSignal.m b/ReactiveCocoa/Objective-C/RACSignal.m deleted file mode 100644 index 9102432426..0000000000 --- a/ReactiveCocoa/Objective-C/RACSignal.m +++ /dev/null @@ -1,401 +0,0 @@ -// -// 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" -#import - -@implementation RACSignal - -#pragma mark Lifecycle - -+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe { - return [RACDynamicSignal createSignal:didSubscribe]; -} - -+ (RACSignal *)error:(NSError *)error { - return [RACErrorSignal error:error]; -} - -+ (RACSignal *)never { - return [[self createSignal:^ RACDisposable * (id subscriber) { - return nil; - }] setNameWithFormat:@"+never"]; -} - -+ (RACSignal *)startEagerlyWithScheduler:(RACScheduler *)scheduler block:(void (^)(id 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 subscriber))block { - NSCParameterAssert(scheduler != nil); - NSCParameterAssert(block != NULL); - - RACMulticastConnection *connection = [[RACSignal - createSignal:^ id (id subscriber) { - block(subscriber); - return nil; - }] - multicast:[RACReplaySubject subject]]; - - return [[[RACSignal - createSignal:^ id (id 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 subscriber) { - RACStreamBindBlock bindingBlock = block(); - - __block volatile int32_t signalCount = 1; // indicates self - - RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; - - void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) { - if (OSAtomicDecrement32Barrier(&signalCount) == 0) { - [subscriber sendCompleted]; - [compoundDisposable dispose]; - } else { - [compoundDisposable removeDisposable:finishedDisposable]; - } - }; - - void (^addSignal)(RACSignal *) = ^(RACSignal *signal) { - OSAtomicIncrement32Barrier(&signalCount); - - 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(selfDisposable); - } - }]; - - selfDisposable.disposable = disposable; - }; - - @autoreleasepool { - RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; - [compoundDisposable addDisposable:selfDisposable]; - - RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { - // Manually check disposal to handle synchronous errors. - if (compoundDisposable.disposed) return; - - BOOL stop = NO; - id signal = bindingBlock(x, &stop); - - @autoreleasepool { - if (signal != nil) addSignal(signal); - if (signal == nil || stop) { - [selfDisposable dispose]; - completeSignal(selfDisposable); - } - } - } error:^(NSError *error) { - [compoundDisposable dispose]; - [subscriber sendError:error]; - } completed:^{ - @autoreleasepool { - completeSignal(selfDisposable); - } - }]; - - selfDisposable.disposable = bindingDisposable; - } - - return compoundDisposable; - }] setNameWithFormat:@"[%@] -bind:", self.name]; -} - -- (RACSignal *)concat:(RACSignal *)signal { - return [[RACSignal createSignal:^(id subscriber) { - RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init]; - - RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { - [subscriber sendNext:x]; - } error:^(NSError *error) { - [subscriber sendError:error]; - } completed:^{ - RACDisposable *concattedDisposable = [signal subscribe:subscriber]; - [compoundDisposable addDisposable:concattedDisposable]; - }]; - - [compoundDisposable addDisposable:sourceDisposable]; - return compoundDisposable; - }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal]; -} - -- (RACSignal *)zipWith:(RACSignal *)signal { - NSCParameterAssert(signal != nil); - - return [[RACSignal createSignal:^(id 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 = RACTuplePack(selfValues[0], otherValues[0]); - [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)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 diff --git a/ReactiveCocoa/Objective-C/RACSignalProvider.d b/ReactiveCocoa/Objective-C/RACSignalProvider.d deleted file mode 100644 index 8add9a105d..0000000000 --- a/ReactiveCocoa/Objective-C/RACSignalProvider.d +++ /dev/null @@ -1,5 +0,0 @@ -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/ReactiveCocoa/Objective-C/RACSignalSequence.h b/ReactiveCocoa/Objective-C/RACSignalSequence.h deleted file mode 100644 index e3ab77d85b..0000000000 --- a/ReactiveCocoa/Objective-C/RACSignalSequence.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACSignalSequence.m b/ReactiveCocoa/Objective-C/RACSignalSequence.m deleted file mode 100644 index 52ea1b9104..0000000000 --- a/ReactiveCocoa/Objective-C/RACSignalSequence.m +++ /dev/null @@ -1,79 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACStream+Private.h b/ReactiveCocoa/Objective-C/RACStream+Private.h deleted file mode 100644 index f6c04079b6..0000000000 --- a/ReactiveCocoa/Objective-C/RACStream+Private.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// 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)streams block:(RACStream * (^)(id, id))block; - -@end diff --git a/ReactiveCocoa/Objective-C/RACStream.h b/ReactiveCocoa/Objective-C/RACStream.h deleted file mode 100644 index 50fb18f2bd..0000000000 --- a/ReactiveCocoa/Objective-C/RACStream.h +++ /dev/null @@ -1,335 +0,0 @@ -// -// RACStream.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-10-31. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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; - -/// Replaces 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)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)streams reduce:(id (^)())reduceBlock; - -/// Returns a stream obtained by concatenating `streams` in order. -+ (instancetype)concat:(id)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`. -/// reduceBlock - The block that describes how to combine values of the -/// receiver. If the receiver is empty, this block will never be -/// invoked. Cannot be nil. -/// -/// 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 `reduceBlock`. If the -/// receiver is empty, an empty stream is returned. -- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))reduceBlock; - -/// Combines values in the receiver from left to right using the given block -/// which also takes zero-based index of the values. -/// -/// startingValue - The value to be combined with the first element of the -/// receiver. This value may be `nil`. -/// reduceBlock - The block that describes how to combine values of the -/// receiver. This block takes zero-based index value as the last -/// parameter. If the receiver is empty, this block will never -/// be invoked. Cannot be nil. -/// -/// Returns a new stream that consists of each application of `reduceBlock`. If the -/// receiver is empty, an empty stream is returned. -- (instancetype)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock; - -/// 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 (Unavailable) - -- (instancetype)sequenceMany:(RACStream * (^)(void))block __attribute__((unavailable("Use -flattenMap: instead"))); -- (instancetype)scanWithStart:(id)startingValue combine:(id (^)(id running, id next))block __attribute__((unavailable("Renamed to -scanWithStart:reduce:"))); -- (instancetype)mapPreviousWithStart:(id)start reduce:(id (^)(id previous, id current))combineBlock __attribute__((unavailable("Renamed to -combinePreviousWithStart:reduce:"))); - -@end diff --git a/ReactiveCocoa/Objective-C/RACStream.m b/ReactiveCocoa/Objective-C/RACStream.m deleted file mode 100644 index efac5d3729..0000000000 --- a/ReactiveCocoa/Objective-C/RACStream.m +++ /dev/null @@ -1,351 +0,0 @@ -// -// 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 { - 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, RACDescription(object)]; -} - -- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock { - NSCParameterAssert(reduceBlock != NULL); - return [[[self - scanWithStart:RACTuplePack(start) - reduce:^(RACTuple *previousTuple, id next) { - id value = reduceBlock(previousTuple[0], next); - return RACTuplePack(next, value); - }] - map:^(RACTuple *tuple) { - return tuple[1]; - }] - setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, RACDescription(start)]; -} - -- (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, RACDescription(value)]; -} - -- (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, RACDescription(value)]; -} - -- (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; - - if (count == 0) return class.empty; - - return [[self bind:^{ - __block NSUInteger taken = 0; - - return ^ id (id value, BOOL *stop) { - if (taken < count) { - ++taken; - if (taken == count) *stop = YES; - return [class return:value]; - } else { - return nil; - } - }; - }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count]; -} - -+ (instancetype)join:(id)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)streams { - return [[self join:streams block:^(RACStream *left, RACStream *right) { - return [left zipWith:right]; - }] setNameWithFormat:@"+zip: %@", streams]; -} - -+ (instancetype)zip:(id)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)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))reduceBlock { - NSCParameterAssert(reduceBlock != nil); - - return [[self - scanWithStart:startingValue - reduceWithIndex:^(id running, id next, NSUInteger index) { - return reduceBlock(running, next); - }] - setNameWithFormat:@"[%@] -scanWithStart: %@ reduce:", self.name, RACDescription(startingValue)]; -} - -- (instancetype)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock { - NSCParameterAssert(reduceBlock != nil); - - Class class = self.class; - - return [[self bind:^{ - __block id running = startingValue; - __block NSUInteger index = 0; - - return ^(id value, BOOL *stop) { - running = reduceBlock(running, value, index++); - return [class return:running]; - }; - }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduceWithIndex:", self.name, RACDescription(startingValue)]; -} - -- (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:@"[%@] -skipWhileBlock:", 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 diff --git a/ReactiveCocoa/Objective-C/RACStringSequence.h b/ReactiveCocoa/Objective-C/RACStringSequence.h deleted file mode 100644 index b37ddea89a..0000000000 --- a/ReactiveCocoa/Objective-C/RACStringSequence.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACStringSequence.m b/ReactiveCocoa/Objective-C/RACStringSequence.m deleted file mode 100644 index dd10f3c38b..0000000000 --- a/ReactiveCocoa/Objective-C/RACStringSequence.m +++ /dev/null @@ -1,65 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACSubject.h b/ReactiveCocoa/Objective-C/RACSubject.h deleted file mode 100644 index 30c100bfa5..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubject.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// 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 - -/// Returns a new subject. -+ (instancetype)subject; - -@end diff --git a/ReactiveCocoa/Objective-C/RACSubject.m b/ReactiveCocoa/Objective-C/RACSubject.m deleted file mode 100644 index 9b76fa7d1d..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubject.m +++ /dev/null @@ -1,126 +0,0 @@ -// -// RACSubject.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/9/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACSubject.h" -#import -#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 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)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]; - } - - [disposable addDisposable:[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 obj, NSUInteger index, BOOL *stop) { - return obj == subscriber; - }]; - - if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; - } - }]]; - - return disposable; -} - -- (void)enumerateSubscribersUsingBlock:(void (^)(id subscriber))block { - NSArray *subscribers; - @synchronized (self.subscribers) { - subscribers = [self.subscribers copy]; - } - - for (id subscriber in subscribers) { - block(subscriber); - } -} - -#pragma mark RACSubscriber - -- (void)sendNext:(id)value { - [self enumerateSubscribersUsingBlock:^(id subscriber) { - [subscriber sendNext:value]; - }]; -} - -- (void)sendError:(NSError *)error { - [self.disposable dispose]; - - [self enumerateSubscribersUsingBlock:^(id subscriber) { - [subscriber sendError:error]; - }]; -} - -- (void)sendCompleted { - [self.disposable dispose]; - - [self enumerateSubscribersUsingBlock:^(id 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/ReactiveCocoa/Objective-C/RACSubscriber+Private.h b/ReactiveCocoa/Objective-C/RACSubscriber+Private.h deleted file mode 100644 index 0ad81d2fca..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubscriber+Private.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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 - -// 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/ReactiveCocoa/Objective-C/RACSubscriber.h b/ReactiveCocoa/Objective-C/RACSubscriber.h deleted file mode 100644 index b62ea35548..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubscriber.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// RACSubscriber.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/1/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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 -@required - -/// Sends the next value to subscribers. -/// -/// value - The value to send. This can be `nil`. -- (void)sendNext:(id)value; - -/// Sends 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; - -/// Sends 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/ReactiveCocoa/Objective-C/RACSubscriber.m b/ReactiveCocoa/Objective-C/RACSubscriber.m deleted file mode 100644 index 53a7407dc8..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubscriber.m +++ /dev/null @@ -1,112 +0,0 @@ -// -// 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 -#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; - - @unsafeify(self); - - RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{ - @strongify(self); - - @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 *)otherDisposable { - if (otherDisposable.disposed) return; - - RACCompoundDisposable *selfDisposable = self.disposable; - [selfDisposable addDisposable:otherDisposable]; - - @unsafeify(otherDisposable); - - // If this subscription terminates, purge its disposable to avoid unbounded - // memory growth. - [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - @strongify(otherDisposable); - [selfDisposable removeDisposable:otherDisposable]; - }]]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACSubscriptingAssignmentTrampoline.h b/ReactiveCocoa/Objective-C/RACSubscriptingAssignmentTrampoline.h deleted file mode 100644 index 699dea897a..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubscriptingAssignmentTrampoline.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// RACSubscriptingAssignmentTrampoline.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 9/24/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -@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/ReactiveCocoa/Objective-C/RACSubscriptingAssignmentTrampoline.m b/ReactiveCocoa/Objective-C/RACSubscriptingAssignmentTrampoline.m deleted file mode 100644 index 43fdee008f..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubscriptingAssignmentTrampoline.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// RACSubscriptingAssignmentTrampoline.m -// ReactiveCocoa -// -// 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/ReactiveCocoa/Objective-C/RACSubscriptionScheduler.h b/ReactiveCocoa/Objective-C/RACSubscriptionScheduler.h deleted file mode 100644 index 34f5dc5d0a..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubscriptionScheduler.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACSubscriptionScheduler.m b/ReactiveCocoa/Objective-C/RACSubscriptionScheduler.m deleted file mode 100644 index 2aab9c3e19..0000000000 --- a/ReactiveCocoa/Objective-C/RACSubscriptionScheduler.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACTargetQueueScheduler.h b/ReactiveCocoa/Objective-C/RACTargetQueueScheduler.h deleted file mode 100644 index 429e59554b..0000000000 --- a/ReactiveCocoa/Objective-C/RACTargetQueueScheduler.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACTargetQueueScheduler.m b/ReactiveCocoa/Objective-C/RACTargetQueueScheduler.m deleted file mode 100644 index f90dfd6704..0000000000 --- a/ReactiveCocoa/Objective-C/RACTargetQueueScheduler.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// RACTargetQueueScheduler.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 6/6/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import "RACTargetQueueScheduler.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); - - return [super initWithName:name queue:queue]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/RACTestScheduler.h b/ReactiveCocoa/Objective-C/RACTestScheduler.h deleted file mode 100644 index a790f5bb84..0000000000 --- a/ReactiveCocoa/Objective-C/RACTestScheduler.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACTestScheduler.m b/ReactiveCocoa/Objective-C/RACTestScheduler.m deleted file mode 100644 index 06c0072036..0000000000 --- a/ReactiveCocoa/Objective-C/RACTestScheduler.m +++ /dev/null @@ -1,223 +0,0 @@ -// -// RACTestScheduler.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-07-06. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import "RACTestScheduler.h" -#import -#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:@"org.reactivecocoa.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) { - CFBridgingRelease(_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/ReactiveCocoa/Objective-C/RACTuple.h b/ReactiveCocoa/Objective-C/RACTuple.h deleted file mode 100644 index 647b42c2e5..0000000000 --- a/ReactiveCocoa/Objective-C/RACTuple.h +++ /dev/null @@ -1,159 +0,0 @@ -// -// RACTuple.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/12/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#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 -/// A singleton instance. -+ (RACTupleNil *)tupleNil; -@end - - -/// A tuple is an ordered collection of objects. It may contain nils, represented -/// by RACTupleNil. -@interface RACTuple : NSObject - -@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/ReactiveCocoa/Objective-C/RACTuple.m b/ReactiveCocoa/Objective-C/RACTuple.m deleted file mode 100644 index ca88d12662..0000000000 --- a/ReactiveCocoa/Objective-C/RACTuple.m +++ /dev/null @@ -1,252 +0,0 @@ -// -// RACTuple.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/12/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import "RACTuple.h" -#import -#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[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]; -} - -- (NSUInteger)count { - return self.backingArray.count; -} - -- (id)first { - return self[0]; -} - -- (id)second { - return self[1]; -} - -- (id)third { - return self[2]; -} - -- (id)fourth { - return self[3]; -} - -- (id)fifth { - return self[4]; -} - -- (id)last { - return self[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/ReactiveCocoa/Objective-C/RACTupleSequence.h b/ReactiveCocoa/Objective-C/RACTupleSequence.h deleted file mode 100644 index f6cd021fdc..0000000000 --- a/ReactiveCocoa/Objective-C/RACTupleSequence.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACTupleSequence.m b/ReactiveCocoa/Objective-C/RACTupleSequence.m deleted file mode 100644 index 65c139fcbb..0000000000 --- a/ReactiveCocoa/Objective-C/RACTupleSequence.m +++ /dev/null @@ -1,68 +0,0 @@ -// -// 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[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/ReactiveCocoa/Objective-C/RACUnarySequence.h b/ReactiveCocoa/Objective-C/RACUnarySequence.h deleted file mode 100644 index 86a514ba6e..0000000000 --- a/ReactiveCocoa/Objective-C/RACUnarySequence.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACUnarySequence.m b/ReactiveCocoa/Objective-C/RACUnarySequence.m deleted file mode 100644 index a06f722757..0000000000 --- a/ReactiveCocoa/Objective-C/RACUnarySequence.m +++ /dev/null @@ -1,81 +0,0 @@ -// -// RACUnarySequence.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-05-01. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import "RACUnarySequence.h" -#import -#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: %@", RACDescription(value)]; -} - -#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/ReactiveCocoa/Objective-C/RACUnit.h b/ReactiveCocoa/Objective-C/RACUnit.h deleted file mode 100644 index a04e2b1abe..0000000000 --- a/ReactiveCocoa/Objective-C/RACUnit.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// RACUnit.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/27/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -/// 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/ReactiveCocoa/Objective-C/RACUnit.m b/ReactiveCocoa/Objective-C/RACUnit.m deleted file mode 100644 index 9e31246889..0000000000 --- a/ReactiveCocoa/Objective-C/RACUnit.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/RACValueTransformer.h b/ReactiveCocoa/Objective-C/RACValueTransformer.h deleted file mode 100644 index 740b8611b2..0000000000 --- a/ReactiveCocoa/Objective-C/RACValueTransformer.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// RACValueTransformer.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/6/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -// A private block based transformer. -@interface RACValueTransformer : NSValueTransformer - -+ (instancetype)transformerWithBlock:(id (^)(id value))block; - -@end diff --git a/ReactiveCocoa/Objective-C/RACValueTransformer.m b/ReactiveCocoa/Objective-C/RACValueTransformer.m deleted file mode 100644 index ccec07d457..0000000000 --- a/ReactiveCocoa/Objective-C/RACValueTransformer.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// 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/ReactiveCocoa/Objective-C/ReactiveCocoa-Bridging-Header.h b/ReactiveCocoa/Objective-C/ReactiveCocoa-Bridging-Header.h deleted file mode 100644 index eabf5c9443..0000000000 --- a/ReactiveCocoa/Objective-C/ReactiveCocoa-Bridging-Header.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - -#import "RACCommand.h" -#import "RACDisposable.h" -#import "RACEvent.h" -#import "RACScheduler.h" -#import "RACTargetQueueScheduler.h" -#import "RACSignal.h" -#import "RACSignal+Operations.h" -#import "RACStream.h" -#import "RACSubscriber.h" diff --git a/ReactiveCocoa/Objective-C/UIActionSheet+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UIActionSheet+RACSignalSupport.h deleted file mode 100644 index 3d667e956d..0000000000 --- a/ReactiveCocoa/Objective-C/UIActionSheet+RACSignalSupport.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// UIActionSheet+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Dave Lee on 2013-06-22. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UIActionSheet+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UIActionSheet+RACSignalSupport.m deleted file mode 100644 index 251a5189c4..0000000000 --- a/ReactiveCocoa/Objective-C/UIActionSheet+RACSignalSupport.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// 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 "NSObject+RACDescription.h" -#import - -@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", RACDescription(self)]; - - RACUseDelegateProxy(self); - - return signal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UIAlertView+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UIAlertView+RACSignalSupport.h deleted file mode 100644 index 9a1ecb91cd..0000000000 --- a/ReactiveCocoa/Objective-C/UIAlertView+RACSignalSupport.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// UIAlertView+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Henrik Hodne on 6/16/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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. -/// -/// Note that this signal will not send a value when the alert is dismissed -/// programatically. -/// -/// 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; - -/// Creates a signal for dismissal 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 index of the button associated with the -/// dismissal. The signal will complete itself when the receiver is deallocated. -- (RACSignal *)rac_willDismissSignal; - -@end diff --git a/ReactiveCocoa/Objective-C/UIAlertView+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UIAlertView+RACSignalSupport.m deleted file mode 100644 index a533dd6303..0000000000 --- a/ReactiveCocoa/Objective-C/UIAlertView+RACSignalSupport.m +++ /dev/null @@ -1,63 +0,0 @@ -// -// 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 - -@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", RACDescription(self)]; - - RACUseDelegateProxy(self); - - return signal; -} - -- (RACSignal *)rac_willDismissSignal { - RACSignal *signal = [[[[self.rac_delegateProxy - signalForSelector:@selector(alertView:willDismissWithButtonIndex:)] - reduceEach:^(UIAlertView *alertView, NSNumber *buttonIndex) { - return buttonIndex; - }] - takeUntil:self.rac_willDeallocSignal] - setNameWithFormat:@"%@ -rac_willDismissSignal", RACDescription(self)]; - - RACUseDelegateProxy(self); - - return signal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UIBarButtonItem+RACCommandSupport.h b/ReactiveCocoa/Objective-C/UIBarButtonItem+RACCommandSupport.h deleted file mode 100644 index 759b29816b..0000000000 --- a/ReactiveCocoa/Objective-C/UIBarButtonItem+RACCommandSupport.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// UIBarButtonItem+RACCommandSupport.h -// ReactiveCocoa -// -// Created by Kyle LeNeau on 3/27/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@class RACCommand<__contravariant InputType>; - -@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<__kindof UIBarButtonItem *> *rac_command; - -@end diff --git a/ReactiveCocoa/Objective-C/UIBarButtonItem+RACCommandSupport.m b/ReactiveCocoa/Objective-C/UIBarButtonItem+RACCommandSupport.m deleted file mode 100644 index 3ce99569c4..0000000000 --- a/ReactiveCocoa/Objective-C/UIBarButtonItem+RACCommandSupport.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// 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 -#import "RACCommand.h" -#import "RACDisposable.h" -#import "RACSignal+Operations.h" -#import - -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/ReactiveCocoa/Objective-C/UIButton+RACCommandSupport.h b/ReactiveCocoa/Objective-C/UIButton+RACCommandSupport.h deleted file mode 100644 index 8ec89ce825..0000000000 --- a/ReactiveCocoa/Objective-C/UIButton+RACCommandSupport.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// UIButton+RACCommandSupport.h -// ReactiveCocoa -// -// Created by Ash Furrow on 2013-06-06. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@class RACCommand<__contravariant InputType>; - -@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<__kindof UIButton *> *rac_command; - -@end diff --git a/ReactiveCocoa/Objective-C/UIButton+RACCommandSupport.m b/ReactiveCocoa/Objective-C/UIButton+RACCommandSupport.m deleted file mode 100644 index 05de9e0e85..0000000000 --- a/ReactiveCocoa/Objective-C/UIButton+RACCommandSupport.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// 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 -#import "RACCommand.h" -#import "RACDisposable.h" -#import "RACSignal+Operations.h" -#import - -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/ReactiveCocoa/Objective-C/UICollectionReusableView+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UICollectionReusableView+RACSignalSupport.h deleted file mode 100644 index 96b3dfe182..0000000000 --- a/ReactiveCocoa/Objective-C/UICollectionReusableView+RACSignalSupport.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// UICollectionReusableView+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Kent Wong on 2013-10-04. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UICollectionReusableView+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UICollectionReusableView+RACSignalSupport.m deleted file mode 100644 index 84cac49188..0000000000 --- a/ReactiveCocoa/Objective-C/UICollectionReusableView+RACSignalSupport.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// 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 - -@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", RACDescription(self)]; - - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UIControl+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UIControl+RACSignalSupport.h deleted file mode 100644 index 2de86cf8d9..0000000000 --- a/ReactiveCocoa/Objective-C/UIControl+RACSignalSupport.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// UIControl+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/17/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UIControl+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UIControl+RACSignalSupport.m deleted file mode 100644 index 08ec067cc4..0000000000 --- a/ReactiveCocoa/Objective-C/UIControl+RACSignalSupport.m +++ /dev/null @@ -1,43 +0,0 @@ -// -// 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 -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACSignal.h" -#import "RACSubscriber.h" -#import "NSObject+RACDeallocating.h" -#import "NSObject+RACDescription.h" - -@implementation UIControl (RACSignalSupport) - -- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents { - @weakify(self); - - return [[RACSignal - createSignal:^(id subscriber) { - @strongify(self); - - [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; - - RACDisposable *disposable = [RACDisposable disposableWithBlock:^{ - [subscriber sendCompleted]; - }]; - [self.rac_deallocDisposable addDisposable:disposable]; - - return [RACDisposable disposableWithBlock:^{ - @strongify(self); - [self.rac_deallocDisposable removeDisposable:disposable]; - [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents]; - }]; - }] - setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", RACDescription(self), (unsigned long)controlEvents]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UIControl+RACSignalSupportPrivate.h b/ReactiveCocoa/Objective-C/UIControl+RACSignalSupportPrivate.h deleted file mode 100644 index 7f778a47bf..0000000000 --- a/ReactiveCocoa/Objective-C/UIControl+RACSignalSupportPrivate.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// UIControl+RACSignalSupportPrivate.h -// ReactiveCocoa -// -// Created by Uri Baghin on 06/08/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UIControl+RACSignalSupportPrivate.m b/ReactiveCocoa/Objective-C/UIControl+RACSignalSupportPrivate.m deleted file mode 100644 index 445398cf5c..0000000000 --- a/ReactiveCocoa/Objective-C/UIControl+RACSignalSupportPrivate.m +++ /dev/null @@ -1,48 +0,0 @@ -// -// 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 "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/ReactiveCocoa/Objective-C/UIDatePicker+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UIDatePicker+RACSignalSupport.h deleted file mode 100644 index e620dfc3cc..0000000000 --- a/ReactiveCocoa/Objective-C/UIDatePicker+RACSignalSupport.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// UIDatePicker+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Uri Baghin on 20/07/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UIDatePicker+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UIDatePicker+RACSignalSupport.m deleted file mode 100644 index 50db7985c9..0000000000 --- a/ReactiveCocoa/Objective-C/UIDatePicker+RACSignalSupport.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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 -#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/ReactiveCocoa/Objective-C/UIGestureRecognizer+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UIGestureRecognizer+RACSignalSupport.h deleted file mode 100644 index f93c9d9f8e..0000000000 --- a/ReactiveCocoa/Objective-C/UIGestureRecognizer+RACSignalSupport.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIGestureRecognizer+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Josh Vera on 5/5/13. -// Copyright (c) 2013 GitHub. All rights reserved. -// - -#import - -@class RACSignal; - -@interface UIGestureRecognizer (RACSignalSupport) - -/// Returns a signal that sends the receiver when its gesture occurs. -- (RACSignal *)rac_gestureSignal; - -@end diff --git a/ReactiveCocoa/Objective-C/UIGestureRecognizer+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UIGestureRecognizer+RACSignalSupport.m deleted file mode 100644 index 5b1298f8bc..0000000000 --- a/ReactiveCocoa/Objective-C/UIGestureRecognizer+RACSignalSupport.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// UIGestureRecognizer+RACSignalSupport.m -// ReactiveCocoa -// -// Created by Josh Vera on 5/5/13. -// Copyright (c) 2013 GitHub. All rights reserved. -// - -#import "UIGestureRecognizer+RACSignalSupport.h" -#import -#import "NSObject+RACDeallocating.h" -#import "NSObject+RACDescription.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACSignal.h" -#import "RACSubscriber.h" - -@implementation UIGestureRecognizer (RACSignalSupport) - -- (RACSignal *)rac_gestureSignal { - @weakify(self); - - return [[RACSignal - createSignal:^(id 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", RACDescription(self)]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UIImagePickerController+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UIImagePickerController+RACSignalSupport.h deleted file mode 100644 index 17440774ce..0000000000 --- a/ReactiveCocoa/Objective-C/UIImagePickerController+RACSignalSupport.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// UIImagePickerController+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Timur Kuchkarov on 28.03.14. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -#import - -@class RACDelegateProxy; -@class RACSignal; - -@interface UIImagePickerController (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 every new selected image. -/// -/// 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 dictionary with info for the selected image. -/// Caller is responsible for picker controller dismissal. The signal will complete -/// itself when the receiver is deallocated or when user cancels selection. -- (RACSignal *)rac_imageSelectedSignal; - -@end diff --git a/ReactiveCocoa/Objective-C/UIImagePickerController+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UIImagePickerController+RACSignalSupport.m deleted file mode 100644 index d567be53a6..0000000000 --- a/ReactiveCocoa/Objective-C/UIImagePickerController+RACSignalSupport.m +++ /dev/null @@ -1,53 +0,0 @@ -// -// UIImagePickerController+RACSignalSupport.m -// ReactiveCocoa -// -// Created by Timur Kuchkarov on 28.03.14. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -#import "UIImagePickerController+RACSignalSupport.h" -#import "RACDelegateProxy.h" -#import "RACSignal+Operations.h" -#import "NSObject+RACDeallocating.h" -#import "NSObject+RACDescription.h" -#import - -@implementation UIImagePickerController (RACSignalSupport) - -static void RACUseDelegateProxy(UIImagePickerController *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(UIImagePickerControllerDelegate)]; - objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - - return proxy; -} - -- (RACSignal *)rac_imageSelectedSignal { - RACSignal *pickerCancelledSignal = [[self.rac_delegateProxy - signalForSelector:@selector(imagePickerControllerDidCancel:)] - merge:self.rac_willDeallocSignal]; - - RACSignal *imagePickerSignal = [[[[self.rac_delegateProxy - signalForSelector:@selector(imagePickerController:didFinishPickingMediaWithInfo:)] - reduceEach:^(UIImagePickerController *pickerController, NSDictionary *userInfo) { - return userInfo; - }] - takeUntil:pickerCancelledSignal] - setNameWithFormat:@"%@ -rac_imageSelectedSignal", RACDescription(self)]; - - RACUseDelegateProxy(self); - - return imagePickerSignal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UIRefreshControl+RACCommandSupport.h b/ReactiveCocoa/Objective-C/UIRefreshControl+RACCommandSupport.h deleted file mode 100644 index 460b4cdeb5..0000000000 --- a/ReactiveCocoa/Objective-C/UIRefreshControl+RACCommandSupport.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// UIRefreshControl+RACCommandSupport.h -// ReactiveCocoa -// -// Created by Dave Lee on 2013-10-17. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@class RACCommand<__contravariant InputType>; - -@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<__kindof UIRefreshControl *> *rac_command; - -@end diff --git a/ReactiveCocoa/Objective-C/UIRefreshControl+RACCommandSupport.m b/ReactiveCocoa/Objective-C/UIRefreshControl+RACCommandSupport.m deleted file mode 100644 index ba5a0dbb9a..0000000000 --- a/ReactiveCocoa/Objective-C/UIRefreshControl+RACCommandSupport.m +++ /dev/null @@ -1,59 +0,0 @@ -// -// 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 -#import "RACCommand.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACSignal.h" -#import "RACSignal+Operations.h" -#import "UIControl+RACSignalSupport.h" -#import - -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] - deliverOnMainThread] - subscribeNext:^(UIRefreshControl *x) { - [x endRefreshing]; - }]; - - RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]]; - objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -@end diff --git a/ReactiveCocoa/Objective-C/UISegmentedControl+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UISegmentedControl+RACSignalSupport.h deleted file mode 100644 index 2d3c3e7af4..0000000000 --- a/ReactiveCocoa/Objective-C/UISegmentedControl+RACSignalSupport.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// UISegmentedControl+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Uri Baghin on 20/07/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UISegmentedControl+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UISegmentedControl+RACSignalSupport.m deleted file mode 100644 index baacbab0df..0000000000 --- a/ReactiveCocoa/Objective-C/UISegmentedControl+RACSignalSupport.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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 -#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/ReactiveCocoa/Objective-C/UISlider+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UISlider+RACSignalSupport.h deleted file mode 100644 index 75626ad976..0000000000 --- a/ReactiveCocoa/Objective-C/UISlider+RACSignalSupport.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// UISlider+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Uri Baghin on 20/07/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UISlider+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UISlider+RACSignalSupport.m deleted file mode 100644 index c4b110f506..0000000000 --- a/ReactiveCocoa/Objective-C/UISlider+RACSignalSupport.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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 -#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/ReactiveCocoa/Objective-C/UIStepper+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UIStepper+RACSignalSupport.h deleted file mode 100644 index da6d97b51c..0000000000 --- a/ReactiveCocoa/Objective-C/UIStepper+RACSignalSupport.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// UIStepper+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Uri Baghin on 20/07/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UIStepper+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UIStepper+RACSignalSupport.m deleted file mode 100644 index 1d932817e7..0000000000 --- a/ReactiveCocoa/Objective-C/UIStepper+RACSignalSupport.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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 -#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/ReactiveCocoa/Objective-C/UISwitch+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UISwitch+RACSignalSupport.h deleted file mode 100644 index 1313a2c4f8..0000000000 --- a/ReactiveCocoa/Objective-C/UISwitch+RACSignalSupport.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// UISwitch+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Uri Baghin on 20/07/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UISwitch+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UISwitch+RACSignalSupport.m deleted file mode 100644 index 545d78efc2..0000000000 --- a/ReactiveCocoa/Objective-C/UISwitch+RACSignalSupport.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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 -#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/ReactiveCocoa/Objective-C/UITableViewCell+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UITableViewCell+RACSignalSupport.h deleted file mode 100644 index c29d47cdda..0000000000 --- a/ReactiveCocoa/Objective-C/UITableViewCell+RACSignalSupport.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// UITableViewCell+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-07-22. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UITableViewCell+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UITableViewCell+RACSignalSupport.m deleted file mode 100644 index 8ca2e9bd96..0000000000 --- a/ReactiveCocoa/Objective-C/UITableViewCell+RACSignalSupport.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// 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 - -@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", RACDescription(self)]; - - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UITableViewHeaderFooterView+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UITableViewHeaderFooterView+RACSignalSupport.h deleted file mode 100644 index 6c5d6b8651..0000000000 --- a/ReactiveCocoa/Objective-C/UITableViewHeaderFooterView+RACSignalSupport.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// UITableViewHeaderFooterView+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Syo Ikeda on 12/30/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UITableViewHeaderFooterView+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UITableViewHeaderFooterView+RACSignalSupport.m deleted file mode 100644 index 44728b0468..0000000000 --- a/ReactiveCocoa/Objective-C/UITableViewHeaderFooterView+RACSignalSupport.m +++ /dev/null @@ -1,31 +0,0 @@ -// -// 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 - -@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", RACDescription(self)]; - - objc_setAssociatedObject(self, _cmd, signal, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - return signal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UITextField+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UITextField+RACSignalSupport.h deleted file mode 100644 index 23aca7d35f..0000000000 --- a/ReactiveCocoa/Objective-C/UITextField+RACSignalSupport.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// UITextField+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 4/17/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -@class RACChannelTerminal; -@class RACSignal; - -@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 UIControlEventAllEditingEvents -/// 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 -/// UIControlEventAllEditingEvents control event is fired, and sets the text -/// to the values it receives. -- (RACChannelTerminal *)rac_newTextChannel; - -@end diff --git a/ReactiveCocoa/Objective-C/UITextField+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UITextField+RACSignalSupport.m deleted file mode 100644 index 3c9818f93d..0000000000 --- a/ReactiveCocoa/Objective-C/UITextField+RACSignalSupport.m +++ /dev/null @@ -1,39 +0,0 @@ -// -// 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 -#import -#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:UIControlEventAllEditingEvents]] - map:^(UITextField *x) { - return x.text; - }] - takeUntil:self.rac_willDeallocSignal] - setNameWithFormat:@"%@ -rac_textSignal", RACDescription(self)]; -} - -- (RACChannelTerminal *)rac_newTextChannel { - return [self rac_channelForControlEvents:UIControlEventAllEditingEvents key:@keypath(self.text) nilValue:@""]; -} - -@end diff --git a/ReactiveCocoa/Objective-C/UITextView+RACSignalSupport.h b/ReactiveCocoa/Objective-C/UITextView+RACSignalSupport.h deleted file mode 100644 index 174b1bac6e..0000000000 --- a/ReactiveCocoa/Objective-C/UITextView+RACSignalSupport.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// UITextView+RACSignalSupport.h -// ReactiveCocoa -// -// Created by Cody Krieger on 5/18/12. -// Copyright (c) 2012 Cody Krieger. All rights reserved. -// - -#import - -@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/ReactiveCocoa/Objective-C/UITextView+RACSignalSupport.m b/ReactiveCocoa/Objective-C/UITextView+RACSignalSupport.m deleted file mode 100644 index 8d20c1ca53..0000000000 --- a/ReactiveCocoa/Objective-C/UITextView+RACSignalSupport.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// 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 -#import "NSObject+RACDeallocating.h" -#import "NSObject+RACDescription.h" -#import "RACDelegateProxy.h" -#import "RACSignal+Operations.h" -#import "RACTuple.h" -#import - -@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", RACDescription(self)]; - - RACUseDelegateProxy(self); - - return signal; -} - -@end diff --git a/ReactiveCocoa/Objective-C/extobjc/EXTKeyPathCoding.h b/ReactiveCocoa/Objective-C/extobjc/EXTKeyPathCoding.h deleted file mode 100644 index f34dc4a440..0000000000 --- a/ReactiveCocoa/Objective-C/extobjc/EXTKeyPathCoding.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// EXTKeyPathCoding.h -// extobjc -// -// Created by Justin Spahr-Summers on 19.06.12. -// Copyright (C) 2012 Justin Spahr-Summers. -// Released under the MIT license. -// - -#import -#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/ReactiveCocoa/Objective-C/extobjc/EXTRuntimeExtensions.h b/ReactiveCocoa/Objective-C/extobjc/EXTRuntimeExtensions.h deleted file mode 100644 index ab4e11d0c7..0000000000 --- a/ReactiveCocoa/Objective-C/extobjc/EXTRuntimeExtensions.h +++ /dev/null @@ -1,122 +0,0 @@ -// -// EXTRuntimeExtensions.h -// extobjc -// -// Created by Justin Spahr-Summers on 2011-03-05. -// Copyright (C) 2012 Justin Spahr-Summers. -// Released under the MIT license. -// - -#import - -/** - * 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/ReactiveCocoa/Objective-C/extobjc/EXTRuntimeExtensions.m b/ReactiveCocoa/Objective-C/extobjc/EXTRuntimeExtensions.m deleted file mode 100644 index bf4055cfe9..0000000000 --- a/ReactiveCocoa/Objective-C/extobjc/EXTRuntimeExtensions.m +++ /dev/null @@ -1,234 +0,0 @@ -// -// EXTRuntimeExtensions.m -// extobjc -// -// Created by Justin Spahr-Summers on 2011-03-05. -// Copyright (C) 2012 Justin Spahr-Summers. -// Released under the MIT license. -// - -#import - -#import -#import -#import -#import -#import -#import -#import -#import - -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/ReactiveCocoa/Objective-C/extobjc/EXTScope.h b/ReactiveCocoa/Objective-C/extobjc/EXTScope.h deleted file mode 100644 index 4a16f4ae9b..0000000000 --- a/ReactiveCocoa/Objective-C/extobjc/EXTScope.h +++ /dev/null @@ -1,118 +0,0 @@ -// -// 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 \ - rac_keywordify \ - __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(...) \ - rac_keywordify \ - 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(...) \ - rac_keywordify \ - 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(...) \ - rac_keywordify \ - _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_); - -// Details about the choice of backing keyword: -// -// The use of @try/@catch/@finally can cause the compiler to suppress -// return-type warnings. -// The use of @autoreleasepool {} is not optimized away by the compiler, -// resulting in superfluous creation of autorelease pools. -// -// Since neither option is perfect, and with no other alternatives, the -// compromise is to use @autorelease in DEBUG builds to maintain compiler -// analysis, and to use @try/@catch otherwise to avoid insertion of unnecessary -// autorelease pools. -#if DEBUG -#define rac_keywordify autoreleasepool {} -#else -#define rac_keywordify try {} @catch (...) {} -#endif diff --git a/ReactiveCocoa/Objective-C/extobjc/metamacros.h b/ReactiveCocoa/Objective-C/extobjc/metamacros.h deleted file mode 100644 index 77a77b5f6e..0000000000 --- a/ReactiveCocoa/Objective-C/extobjc/metamacros.h +++ /dev/null @@ -1,666 +0,0 @@ -/** - * 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/ReactiveCocoa/ReactiveCocoa.h b/ReactiveCocoa/ReactiveCocoa.h deleted file mode 100644 index 415ec69d47..0000000000 --- a/ReactiveCocoa/ReactiveCocoa.h +++ /dev/null @@ -1,95 +0,0 @@ -// -// ReactiveCocoa.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/5/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import - -//! Project version number for ReactiveCocoa. -FOUNDATION_EXPORT double ReactiveCocoaVersionNumber; - -//! Project version string for ReactiveCocoa. -FOUNDATION_EXPORT const unsigned char ReactiveCocoaVersionString[]; - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#if TARGET_OS_WATCH -#elif TARGET_OS_IOS || TARGET_OS_TV - #import - #import - #import - #import - #import - #import - #import - #import - #import - #import - - #if TARGET_OS_IOS - #import - #import - #import - #import - #import - #import - #import - #import - #import - #import - #endif -#elif TARGET_OS_MAC - #import - #import - #import - #import - #import -#endif diff --git a/ReactiveCocoa/ReactiveSwift+Lifetime.swift b/ReactiveCocoa/ReactiveSwift+Lifetime.swift new file mode 100644 index 0000000000..ea3f85a619 --- /dev/null +++ b/ReactiveCocoa/ReactiveSwift+Lifetime.swift @@ -0,0 +1,29 @@ +import ReactiveSwift + +extension Signal { + /// Forward events from `self` until `object` deinitializes, at which point the + /// returned signal will complete. + /// + /// - parameters: + /// - object: An object of which the deinitialization would complete the returned + /// `Signal`. Both Objective-C and native Swift objects are supported. + /// + /// - returns: A signal that will deliver events until `object` deinitializes. + public func take(duringLifetimeOf object: AnyObject) -> Signal { + return take(during: Lifetime.of(object)) + } +} + +extension SignalProducer { + /// Forward events from `self` until `object` deinitializes, at which point the + /// returned producer will complete. + /// + /// - parameters: + /// - object: An object of which the deinitialization would complete the returned + /// `Signal`. Both Objective-C and native Swift objects are supported. + /// + /// - returns: A producer that will deliver events until `object` deinitializes. + public func take(duringLifetimeOf object: AnyObject) -> SignalProducer { + return take(during: Lifetime.of(object)) + } +} diff --git a/ReactiveCocoa/Shared/NSLayoutConstraint.swift b/ReactiveCocoa/Shared/NSLayoutConstraint.swift new file mode 100644 index 0000000000..b62aeacd25 --- /dev/null +++ b/ReactiveCocoa/Shared/NSLayoutConstraint.swift @@ -0,0 +1,18 @@ +#if !os(watchOS) +import ReactiveSwift + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#else +import UIKit +#endif + +extension Reactive where Base: NSLayoutConstraint { + + /// Sets the constant. + public var constant: BindingTarget { + return makeBindingTarget { $0.constant = $1 } + } + +} +#endif diff --git a/ReactiveCocoa/Swift/Action.swift b/ReactiveCocoa/Swift/Action.swift deleted file mode 100644 index 2f2b52e159..0000000000 --- a/ReactiveCocoa/Swift/Action.swift +++ /dev/null @@ -1,199 +0,0 @@ -import Foundation -import enum Result.NoError - -/// Represents an action that will do some work when executed with a value of -/// type `Input`, then return zero or more values of type `Output` and/or fail -/// with an error of type `Error`. If no failure should be possible, NoError can -/// be specified for the `Error` parameter. -/// -/// Actions enforce serial execution. Any attempt to execute an action multiple -/// times concurrently will return an error. -public final class Action { - private let executeClosure: (Input) -> SignalProducer - private let eventsObserver: Signal, NoError>.Observer - - /// A signal of all events generated from applications of the Action. - /// - /// In other words, this will send every `Event` from every signal generated - /// by each SignalProducer returned from apply(). - public let events: Signal, NoError> - - /// A signal of all values generated from applications of the Action. - /// - /// In other words, this will send every value from every signal generated - /// by each SignalProducer returned from apply(). - public let values: Signal - - /// A signal of all errors generated from applications of the Action. - /// - /// In other words, this will send errors from every signal generated by - /// each SignalProducer returned from apply(). - public let errors: Signal - - /// Whether the action is currently executing. - public var isExecuting: Property { - return Property(_isExecuting) - } - - private let _isExecuting: MutableProperty = MutableProperty(false) - - /// Whether the action is currently enabled. - public var isEnabled: Property { - return Property(_isEnabled) - } - - private let _isEnabled: MutableProperty = MutableProperty(false) - - /// Whether the instantiator of this action wants it to be enabled. - private let isUserEnabled: Property - - /// This queue is used for read-modify-write operations on the `_executing` - /// property. - private let executingQueue = DispatchQueue( - label: "org.reactivecocoa.ReactiveCocoa.Action.executingQueue", - attributes: [] - ) - - /// Whether the action should be enabled for the given combination of user - /// enabledness and executing status. - private static func shouldBeEnabled(userEnabled: Bool, executing: Bool) -> Bool { - return userEnabled && !executing - } - - /// Initializes an action that will be conditionally enabled, and creates a - /// SignalProducer for each input. - /// - /// - parameters: - /// - enabledIf: Boolean property that shows whether the action is - /// enabled. - /// - execute: A closure that returns the signal producer returned by - /// calling `apply(Input)` on the action. - public init(enabledIf property: P, _ execute: @escaping (Input) -> SignalProducer) where P.Value == Bool { - executeClosure = execute - isUserEnabled = Property(property) - - (events, eventsObserver) = Signal, NoError>.pipe() - - values = events.map { $0.value }.skipNil() - errors = events.map { $0.error }.skipNil() - - _isEnabled <~ property.producer - .combineLatest(with: isExecuting.producer) - .map(Action.shouldBeEnabled) - } - - /// Initializes an action that will be enabled by default, and creates a - /// SignalProducer for each input. - /// - /// - parameters: - /// - execute: A closure that returns the signal producer returned by - /// calling `apply(Input)` on the action. - public convenience init(_ execute: @escaping (Input) -> SignalProducer) { - self.init(enabledIf: Property(value: true), execute) - } - - deinit { - eventsObserver.sendCompleted() - } - - /// Creates a SignalProducer that, when started, will execute the action - /// with the given input, then forward the results upon the produced Signal. - /// - /// - note: If the action is disabled when the returned SignalProducer is - /// started, the produced signal will send `ActionError.NotEnabled`, - /// and nothing will be sent upon `values` or `errors` for that - /// particular signal. - /// - /// - parameters: - /// - input: A value that will be passed to the closure creating the signal - /// producer. - public func apply(_ input: Input) -> SignalProducer> { - return SignalProducer { observer, disposable in - var startedExecuting = false - - self.executingQueue.sync { - if self._isEnabled.value { - self._isExecuting.value = true - startedExecuting = true - } - } - - if !startedExecuting { - observer.sendFailed(.disabled) - return - } - - self.executeClosure(input).startWithSignal { signal, signalDisposable in - disposable += signalDisposable - - signal.observe { event in - observer.action(event.mapError(ActionError.producerFailed)) - self.eventsObserver.sendNext(event) - } - } - - disposable += { - self._isExecuting.value = false - } - } - } -} - -public protocol ActionProtocol { - /// The type of argument to apply the action to. - associatedtype Input - /// The type of values returned by the action. - associatedtype Output - /// The type of error when the action fails. If errors aren't possible then - /// `NoError` can be used. - associatedtype Error: Swift.Error - - /// Whether the action is currently enabled. - var isEnabled: Property { get } - - /// Extracts an action from the receiver. - var action: Action { get } - - /// Creates a SignalProducer that, when started, will execute the action - /// with the given input, then forward the results upon the produced Signal. - /// - /// - note: If the action is disabled when the returned SignalProducer is - /// started, the produced signal will send `ActionError.NotEnabled`, - /// and nothing will be sent upon `values` or `errors` for that - /// particular signal. - /// - /// - parameters: - /// - input: A value that will be passed to the closure creating the signal - /// producer. - func apply(_ input: Input) -> SignalProducer> -} - -extension Action: ActionProtocol { - public var action: Action { - return self - } -} - -/// The type of error that can occur from Action.apply, where `Error` is the -/// type of error that can be generated by the specific Action instance. -public enum ActionError: Swift.Error { - /// The producer returned from apply() was started while the Action was - /// disabled. - case disabled - - /// The producer returned from apply() sent the given error. - case producerFailed(Error) -} - -public func == (lhs: ActionError, rhs: ActionError) -> Bool { - switch (lhs, rhs) { - case (.disabled, .disabled): - return true - - case let (.producerFailed(left), .producerFailed(right)): - return left == right - - default: - return false - } -} diff --git a/ReactiveCocoa/Swift/Atomic.swift b/ReactiveCocoa/Swift/Atomic.swift deleted file mode 100644 index 1f0029c916..0000000000 --- a/ReactiveCocoa/Swift/Atomic.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -// Atomic.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-06-10. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Foundation - -final class PosixThreadMutex: NSLocking { - private var mutex = pthread_mutex_t() - - init() { - let result = pthread_mutex_init(&mutex, nil) - precondition(result == 0, "Failed to initialize mutex with error \(result).") - } - - deinit { - let result = pthread_mutex_destroy(&mutex) - precondition(result == 0, "Failed to destroy mutex with error \(result).") - } - - func lock() { - let result = pthread_mutex_lock(&mutex) - precondition(result == 0, "Failed to lock \(self) with error \(result).") - } - - func unlock() { - let result = pthread_mutex_unlock(&mutex) - precondition(result == 0, "Failed to unlock \(self) with error \(result).") - } -} - -/// An atomic variable. -public final class Atomic: AtomicProtocol { - private let lock: PosixThreadMutex - private var _value: Value - - /// Initialize the variable with the given initial value. - /// - /// - parameters: - /// - value: Initial value for `self`. - public init(_ value: Value) { - _value = value - lock = PosixThreadMutex() - } - - /// Atomically modifies the variable. - /// - /// - parameters: - /// - action: A closure that takes the current value. - /// - /// - returns: The result of the action. - @discardableResult - public func modify(_ action: (inout Value) throws -> Result) rethrows -> Result { - lock.lock() - defer { lock.unlock() } - - return try action(&_value) - } - - /// Atomically perform an arbitrary action using the current value of the - /// variable. - /// - /// - parameters: - /// - action: A closure that takes the current value. - /// - /// - returns: The result of the action. - @discardableResult - public func withValue(_ action: (Value) throws -> Result) rethrows -> Result { - lock.lock() - defer { lock.unlock() } - - return try action(_value) - } -} - - -/// An atomic variable which uses a recursive lock. -internal final class RecursiveAtomic: AtomicProtocol { - private let lock: NSRecursiveLock - private var _value: Value - private let didSetObserver: ((Value) -> Void)? - - /// Initialize the variable with the given initial value. - /// - /// - parameters: - /// - value: Initial value for `self`. - /// - name: An optional name used to create the recursive lock. - /// - action: An optional closure which would be invoked every time the - /// value of `self` is mutated. - internal init(_ value: Value, name: StaticString? = nil, didSet action: ((Value) -> Void)? = nil) { - _value = value - lock = NSRecursiveLock() - lock.name = name.map(String.init(describing:)) - didSetObserver = action - } - - /// Atomically modifies the variable. - /// - /// - parameters: - /// - action: A closure that takes the current value. - /// - /// - returns: The result of the action. - @discardableResult - func modify(_ action: (inout Value) throws -> Result) rethrows -> Result { - lock.lock() - defer { - didSetObserver?(_value) - lock.unlock() - } - - return try action(&_value) - } - - /// Atomically perform an arbitrary action using the current value of the - /// variable. - /// - /// - parameters: - /// - action: A closure that takes the current value. - /// - /// - returns: The result of the action. - @discardableResult - func withValue(_ action: (Value) throws -> Result) rethrows -> Result { - lock.lock() - defer { lock.unlock() } - - return try action(_value) - } -} - -public protocol AtomicProtocol: class { - associatedtype Value - - @discardableResult - func withValue(_ action: (Value) throws -> Result) rethrows -> Result - - @discardableResult - func modify(_ action: (inout Value) throws -> Result) rethrows -> Result -} - -extension AtomicProtocol { - /// Atomically get or set the value of the variable. - public var value: Value { - get { - return withValue { $0 } - } - - set(newValue) { - swap(newValue) - } - } - - /// Atomically replace the contents of the variable. - /// - /// - parameters: - /// - newValue: A new value for the variable. - /// - /// - returns: The old value. - @discardableResult - public func swap(_ newValue: Value) -> Value { - return modify { (value: inout Value) in - let oldValue = value - value = newValue - return oldValue - } - } -} diff --git a/ReactiveCocoa/Swift/Bag.swift b/ReactiveCocoa/Swift/Bag.swift deleted file mode 100644 index 3053d162f1..0000000000 --- a/ReactiveCocoa/Swift/Bag.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// Bag.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-07-10. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -/// A uniquely identifying token for removing a value that was inserted into a -/// Bag. -public final class RemovalToken { - fileprivate var identifier: UInt? - - fileprivate init(identifier: UInt) { - self.identifier = identifier - } -} - -/// An unordered, non-unique collection of values of type `Element`. -public struct Bag { - fileprivate var elements: [BagElement] = [] - private var currentIdentifier: UInt = 0 - - public init() { - } - - /// Insert the given value into `self`, and return a token that can - /// later be passed to `removeValueForToken()`. - /// - /// - parameters: - /// - value: A value that will be inserted. - @discardableResult - public mutating func insert(_ value: Element) -> RemovalToken { - let (nextIdentifier, overflow) = UInt.addWithOverflow(currentIdentifier, 1) - if overflow { - reindex() - } - - let token = RemovalToken(identifier: currentIdentifier) - let element = BagElement(value: value, identifier: currentIdentifier, token: token) - - elements.append(element) - currentIdentifier = nextIdentifier - - return token - } - - /// Remove a value, given the token returned from `insert()`. - /// - /// - note: If the value has already been removed, nothing happens. - /// - /// - parameters: - /// - token: A token returned from a call to `insert()`. - public mutating func remove(using token: RemovalToken) { - if let identifier = token.identifier { - // Removal is more likely for recent objects than old ones. - for i in elements.indices.reversed() { - if elements[i].identifier == identifier { - elements.remove(at: i) - token.identifier = nil - break - } - } - } - } - - /// In the event of an identifier overflow (highly, highly unlikely), reset - /// all current identifiers to reclaim a contiguous set of available - /// identifiers for the future. - private mutating func reindex() { - for i in elements.indices { - currentIdentifier = UInt(i) - - elements[i].identifier = currentIdentifier - elements[i].token.identifier = currentIdentifier - } - } -} - -extension Bag: Collection { - public typealias Index = Array.Index - - public var startIndex: Index { - return elements.startIndex - } - - public var endIndex: Index { - return elements.endIndex - } - - public subscript(index: Index) -> Element { - return elements[index].value - } - - public func index(after i: Index) -> Index { - return i + 1 - } -} - -private struct BagElement { - let value: Value - var identifier: UInt - let token: RemovalToken -} - -extension BagElement: CustomStringConvertible { - var description: String { - return "BagElement(\(value))" - } -} diff --git a/ReactiveCocoa/Swift/CocoaAction.swift b/ReactiveCocoa/Swift/CocoaAction.swift deleted file mode 100644 index dac51075ce..0000000000 --- a/ReactiveCocoa/Swift/CocoaAction.swift +++ /dev/null @@ -1,102 +0,0 @@ -import Foundation - -/// Wraps an Action for use by a GUI control (such as `NSControl` or -/// `UIControl`), with KVO, or with Cocoa Bindings. -public final class CocoaAction: NSObject { - /// The selector that a caller should invoke upon a CocoaAction in order to - /// execute it. - public static let selector: Selector = #selector(CocoaAction.execute(_:)) - - /// Whether the action is enabled. - /// - /// This property will only change on the main thread, and will generate a - /// KVO notification for every change. - public private(set) var isEnabled: Bool = false - - /// Whether the action is executing. - /// - /// This property will only change on the main thread, and will generate a - /// KVO notification for every change. - public private(set) var isExecuting: Bool = false - - private let _execute: (AnyObject?) -> Void - private let disposable = CompositeDisposable() - - /// Initializes a Cocoa action that will invoke the given Action by - /// transforming the object given to execute(). - /// - /// - note: You must cast the passed in object to the control type you need - /// since there is no way to know where this cocoa action will be - /// added as a target. - /// - /// - parameters: - /// - action: Executable action. - /// - inputTransform: Closure that accepts the UI control performing the - /// action and returns a value (e.g. - /// `(UISwitch) -> (Bool)` to reflect whether a provided - /// switch is currently on. - public init(_ action: Action, _ inputTransform: @escaping (AnyObject?) -> Input) { - _execute = { input in - let producer = action.apply(inputTransform(input)) - producer.start() - } - - super.init() - - disposable += action.isEnabled.producer - .observe(on: UIScheduler()) - .startWithNext { [weak self] value in - self?.willChangeValue(forKey: #keyPath(CocoaAction.isEnabled)) - self?.isEnabled = value - self?.didChangeValue(forKey: #keyPath(CocoaAction.isEnabled)) - } - - disposable += action.isExecuting.producer - .observe(on: UIScheduler()) - .startWithNext { [weak self] value in - self?.willChangeValue(forKey: #keyPath(CocoaAction.isExecuting)) - self?.isExecuting = value - self?.didChangeValue(forKey: #keyPath(CocoaAction.isExecuting)) - } - } - - /// Initializes a Cocoa action that will invoke the given Action by always - /// providing the given input. - /// - /// - parameters: - /// - action: Executable action. - /// - input: A value given as input to the action. - public convenience init(_ action: Action, input: Input) { - self.init(action, { _ in input }) - } - - deinit { - disposable.dispose() - } - - /// Attempts to execute the underlying action with the given input, subject - /// to the behavior described by the initializer that was used. - /// - /// - parameters: - /// - input: A value for the action passed during initialization. - @IBAction public func execute(_ input: AnyObject?) { - _execute(input) - } - - public override class func automaticallyNotifiesObservers(forKey key: String) -> Bool { - return false - } -} - -extension Action { - /// A UI bindable `CocoaAction`. - /// - /// - warning: The default behavior force casts the `AnyObject?` input to - /// match the action's `Input` type. This makes it unsafe for use - /// when the action is parameterized for something like `Void` - /// input. In those cases, explicitly assign a value to this - /// property that transforms the input to suit your needs. - public var unsafeCocoaAction: CocoaAction { - return CocoaAction(self) { $0 as! Input } - } -} diff --git a/ReactiveCocoa/Swift/Deprecations+Removals.swift b/ReactiveCocoa/Swift/Deprecations+Removals.swift deleted file mode 100644 index 13c460e809..0000000000 --- a/ReactiveCocoa/Swift/Deprecations+Removals.swift +++ /dev/null @@ -1,263 +0,0 @@ -import Foundation -import enum Result.NoError - -// MARK: Removed Types and APIs in ReactiveCocoa 5.0. - -// Renamed Protocols -@available(*, unavailable, renamed:"ActionProtocol") -public enum ActionType {} - -@available(*, unavailable, renamed:"SignalProtocol") -public enum SignalType {} - -@available(*, unavailable, renamed:"SignalProducerProtocol") -public enum SignalProducerType {} - -@available(*, unavailable, renamed:"PropertyProtocol") -public enum PropertyType {} - -@available(*, unavailable, renamed:"MutablePropertyProtocol") -public enum MutablePropertyType {} - -@available(*, unavailable, renamed:"ObserverProtocol") -public enum ObserverType {} - -@available(*, unavailable, renamed:"SchedulerProtocol") -public enum SchedulerType {} - -@available(*, unavailable, renamed:"DateSchedulerProtocol") -public enum DateSchedulerType {} - -@available(*, unavailable, renamed:"OptionalProtocol") -public enum OptionalType {} - -@available(*, unavailable, renamed:"EventLoggerProtocol") -public enum EventLoggerType {} - -@available(*, unavailable, renamed:"EventProtocol") -public enum EventType {} - -// Renamed and Removed Types - -@available(*, unavailable, renamed:"Property") -public struct AnyProperty {} - -@available(*, unavailable, message:"Use 'Property(value:)' to create a constant property instead. 'ConstantProperty' is removed in RAC 5.0.") -public struct ConstantProperty {} - -// Renamed Properties - -extension Disposable { - @available(*, unavailable, renamed:"isDisposed") - public var disposed: Bool { fatalError() } -} - -extension ActionProtocol { - @available(*, unavailable, renamed:"isEnabled") - public var enabled: Bool { fatalError() } - - @available(*, unavailable, renamed:"isExecuting") - public var executing: Bool { fatalError() } -} - -extension CocoaAction { - @available(*, unavailable, renamed:"isEnabled") - @nonobjc public var enabled: Bool { fatalError() } - - @available(*, unavailable, renamed:"isExecuting") - @nonobjc public var executing: Bool { fatalError() } -} - -// Renamed Enum cases - -extension Event { - @available(*, unavailable, renamed:"next") - public static var Next: Event { fatalError() } - - @available(*, unavailable, renamed:"failed") - public static var Failed: Event { fatalError() } - - @available(*, unavailable, renamed:"completed") - public static var Completed: Event { fatalError() } - - @available(*, unavailable, renamed:"interrupted") - public static var Interrupted: Event { fatalError() } -} - -extension ActionError { - @available(*, unavailable, renamed:"producerFailed") - public static var ProducerError: ActionError { fatalError() } - - @available(*, unavailable, renamed:"disabled") - public static var NotEnabled: ActionError { fatalError() } -} - -extension FlattenStrategy { - @available(*, unavailable, renamed:"latest") - public static var Latest: FlattenStrategy { fatalError() } - - @available(*, unavailable, renamed:"concat") - public static var Concat: FlattenStrategy { fatalError() } - - @available(*, unavailable, renamed:"merge") - public static var Merge: FlattenStrategy { fatalError() } -} - -// Methods - -extension Bag { - @available(*, unavailable, renamed:"remove(using:)") - public func removeValueForToken(_ token: RemovalToken) { fatalError() } -} - -extension CompositeDisposable { - @available(*, unavailable, renamed:"add(_:)") - public func addDisposable(_ d: Disposable) -> DisposableHandle { fatalError() } -} - -extension SignalProtocol { - @available(*, unavailable, renamed:"take(first:)") - public func take(_ count: Int) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"take(last:)") - public func takeLast(_ count: Int) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"skip(first:)") - public func skip(_ count: Int) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"observe(on:)") - public func observeOn(_ scheduler: UIScheduler) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"combineLatest(with:)") - public func combineLatestWith(_ otherSignal: S) -> Signal<(Value, S.Value), Error> { fatalError() } - - @available(*, unavailable, renamed:"zip(with:)") - public func zipWith(_ otherSignal: S) -> Signal<(Value, S.Value), Error> { fatalError() } - - @available(*, unavailable, renamed:"take(until:)") - public func takeUntil(_ trigger: Signal<(), NoError>) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"take(untilReplacement:)") - public func takeUntilReplacement(_ replacement: Signal) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"skip(until:)") - public func skipUntil(_ trigger: Signal<(), NoError>) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"skip(while:)") - public func skipWhile(_ predicate: (Value) -> Bool) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"take(while:)") - public func takeWhile(_ predicate: (Value) -> Bool) -> Signal { fatalError() } - - @available(*, unavailable, renamed:"timeout(after:raising:on:)") - public func timeoutWithError(_ error: Error, afterInterval: TimeInterval, onScheduler: SchedulerProtocol) -> Signal { fatalError() } - - @available(*, unavailable, message: "This Signal may emit errors which must be handled explicitly, or observed using `observeResult(_:)`") - public func observeNext(_ next: (Value) -> Void) -> Disposable? { fatalError() } -} - -extension SignalProtocol where Value: OptionalProtocol { - @available(*, unavailable, renamed:"skipNil()") - public func ignoreNil() -> SignalProducer { fatalError() } -} - -extension SignalProducerProtocol { - @available(*, unavailable, renamed:"take(first:)") - public func take(_ count: Int) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"take(last:)") - public func takeLast(_ count: Int) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"skip(first:)") - public func skip(_ count: Int) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"observe(on:)") - public func observeOn(_ scheduler: UIScheduler) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"start(on:)") - public func startOn(_ scheduler: UIScheduler) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"combineLatest(with:)") - public func combineLatestWith(_ otherProducer: SignalProducer) -> SignalProducer<(Value, U), Error> { fatalError() } - - @available(*, unavailable, renamed:"combineLatest(with:)") - public func combineLatestWith(_ otherSignal: Signal) -> SignalProducer<(Value, U), Error> { fatalError() } - - @available(*, unavailable, renamed:"zip(with:)") - public func zipWith(_ otherProducer: SignalProducer) -> SignalProducer<(Value, U), Error> { fatalError() } - - @available(*, unavailable, renamed:"zip(with:)") - public func zipWith(_ otherSignal: Signal) -> SignalProducer<(Value, U), Error> { fatalError() } - - @available(*, unavailable, renamed:"take(until:)") - public func takeUntil(_ trigger: Signal<(), NoError>) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"take(until:)") - public func takeUntil(_ trigger: SignalProducer<(), NoError>) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"take(untilReplacement:)") - public func takeUntilReplacement(_ replacement: Signal) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"take(untilReplacement:)") - public func takeUntilReplacement(_ replacement: SignalProducer) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"skip(until:)") - public func skipUntil(_ trigger: Signal<(), NoError>) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"skip(until:)") - public func skipUntil(_ trigger: SignalProducer<(), NoError>) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"skip(while:)") - public func skipWhile(_ predicate: (Value) -> Bool) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"take(while:)") - public func takeWhile(_ predicate: (Value) -> Bool) -> SignalProducer { fatalError() } - - @available(*, unavailable, renamed:"timeout(after:raising:on:)") - public func timeoutWithError(_ error: Error, afterInterval: TimeInterval, onScheduler: SchedulerProtocol) -> SignalProducer { fatalError() } - - @available(*, unavailable, message:"This SignalProducer may emit errors which must be handled explicitly, or observed using `startWithResult(_:)`.") - public func startWithNext(_ next: (Value) -> Void) -> Disposable { fatalError() } -} - -extension SignalProducerProtocol where Value: OptionalProtocol { - @available(*, unavailable, renamed:"skipNil()") - public func ignoreNil() -> SignalProducer { fatalError() } -} - -extension SignalProducer { - @available(*, unavailable, message:"Use properties instead. `buffer(_:)` is removed in RAC 5.0.") - public static func buffer(_ capacity: Int) -> (SignalProducer, Signal.Observer) { fatalError() } -} - -extension PropertyProtocol { - @available(*, unavailable, renamed:"combineLatest(with:)") - public func combineLatestWith(_ otherProperty: P) -> Property<(Value, P.Value)> { fatalError() } - - @available(*, unavailable, renamed:"zip(with:)") - public func zipWith(_ otherProperty: P) -> Property<(Value, P.Value)> { fatalError() } -} - -extension Property { - @available(*, unavailable, renamed:"AnyProperty(initial:then:)") - public convenience init(initialValue: Value, producer: SignalProducer) { fatalError() } - - @available(*, unavailable, renamed:"AnyProperty(initial:then:)") - public convenience init(initialValue: Value, signal: Signal) { fatalError() } -} - -extension DateSchedulerProtocol { - @available(*, unavailable, renamed:"schedule(after:action:)") - func scheduleAfter(date: Date, _ action: () -> Void) -> Disposable? { fatalError() } - - @available(*, unavailable, renamed:"schedule(after:interval:leeway:)") - func scheduleAfter(date: Date, repeatingEvery: TimeInterval, withLeeway: TimeInterval, action: () -> Void) -> Disposable? { fatalError() } -} - -extension TestScheduler { - @available(*, unavailable, renamed:"advanced(by:)") - public func advanceByInterval(_ interval: TimeInterval) { fatalError() } - - @available(*, unavailable, renamed:"advanced(to:)") - public func advanceToDate(_ date: Date) { fatalError() } -} diff --git a/ReactiveCocoa/Swift/Disposable.swift b/ReactiveCocoa/Swift/Disposable.swift deleted file mode 100644 index 94efb71b41..0000000000 --- a/ReactiveCocoa/Swift/Disposable.swift +++ /dev/null @@ -1,357 +0,0 @@ -// -// Disposable.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-06-02. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -/// Represents something that can be “disposed”, usually associated with freeing -/// resources or canceling work. -public protocol Disposable: class { - /// Whether this disposable has been disposed already. - var isDisposed: Bool { get } - - /// Method for disposing of resources when appropriate. - func dispose() -} - -/// A type-erased disposable that forwards operations to an underlying disposable. -public final class AnyDisposable: Disposable { - private let disposable: Disposable - - public var isDisposed: Bool { - return disposable.isDisposed - } - - public init(_ disposable: Disposable) { - self.disposable = disposable - } - - public func dispose() { - disposable.dispose() - } -} - -/// A disposable that only flips `isDisposed` upon disposal, and performs no other -/// work. -public final class SimpleDisposable: Disposable { - private let _isDisposed = Atomic(false) - - public var isDisposed: Bool { - return _isDisposed.value - } - - public init() {} - - public func dispose() { - _isDisposed.value = true - } -} - -/// A disposable that will run an action upon disposal. -public final class ActionDisposable: Disposable { - private let action: Atomic<(() -> Void)?> - - public var isDisposed: Bool { - return action.value == nil - } - - /// Initialize the disposable to run the given action upon disposal. - /// - /// - parameters: - /// - action: A closure to run when calling `dispose()`. - public init(action: @escaping () -> Void) { - self.action = Atomic(action) - } - - public func dispose() { - let oldAction = action.swap(nil) - oldAction?() - } -} - -/// A disposable that will dispose of any number of other disposables. -public final class CompositeDisposable: Disposable { - private let disposables: Atomic?> - - /// Represents a handle to a disposable previously added to a - /// CompositeDisposable. - public final class DisposableHandle { - private let bagToken: Atomic - private weak var disposable: CompositeDisposable? - - fileprivate static let empty = DisposableHandle() - - fileprivate init() { - self.bagToken = Atomic(nil) - } - - fileprivate init(bagToken: RemovalToken, disposable: CompositeDisposable) { - self.bagToken = Atomic(bagToken) - self.disposable = disposable - } - - /// Remove the pointed-to disposable from its `CompositeDisposable`. - /// - /// - note: This is useful to minimize memory growth, by removing - /// disposables that are no longer needed. - public func remove() { - if let token = bagToken.swap(nil) { - _ = disposable?.disposables.modify { - $0?.remove(using: token) - } - } - } - } - - public var isDisposed: Bool { - return disposables.value == nil - } - - /// Initialize a `CompositeDisposable` containing the given sequence of - /// disposables. - /// - /// - parameters: - /// - disposables: A collection of objects conforming to the `Disposable` - /// protocol - public init(_ disposables: S) - where S.Iterator.Element == Disposable - { - var bag: Bag = Bag() - - for disposable in disposables { - bag.insert(disposable) - } - - self.disposables = Atomic(bag) - } - - /// Initialize a `CompositeDisposable` containing the given sequence of - /// disposables. - /// - /// - parameters: - /// - disposables: A collection of objects conforming to the `Disposable` - /// protocol - public convenience init(_ disposables: S) - where S.Iterator.Element == Disposable? - { - self.init(disposables.flatMap { $0 }) - } - - /// Initializes an empty `CompositeDisposable`. - public convenience init() { - self.init([Disposable]()) - } - - public func dispose() { - if let ds = disposables.swap(nil) { - for d in ds.reversed() { - d.dispose() - } - } - } - - /// Add the given disposable to the list, then return a handle which can - /// be used to opaquely remove the disposable later (if desired). - /// - /// - parameters: - /// - d: Optional disposable. - /// - /// - returns: An instance of `DisposableHandle` that can be used to - /// opaquely remove the disposable later (if desired). - @discardableResult - public func add(_ d: Disposable?) -> DisposableHandle { - guard let d = d else { - return DisposableHandle.empty - } - - let handle: DisposableHandle? = disposables.modify { - return ($0?.insert(d)).map { DisposableHandle(bagToken: $0, disposable: self) } - } - - if let handle = handle { - return handle - } else { - d.dispose() - return DisposableHandle.empty - } - } - - /// Add an ActionDisposable to the list. - /// - /// - parameters: - /// - action: A closure that will be invoked when `dispose()` is called. - /// - /// - returns: An instance of `DisposableHandle` that can be used to - /// opaquely remove the disposable later (if desired). - public func add(_ action: @escaping () -> Void) -> DisposableHandle { - return add(ActionDisposable(action: action)) - } -} - -/// A disposable that, upon deinitialization, will automatically dispose of -/// another disposable. -public final class ScopedDisposable: Disposable { - /// The disposable which will be disposed when the ScopedDisposable - /// deinitializes. - public let innerDisposable: InnerDisposable - - public var isDisposed: Bool { - return innerDisposable.isDisposed - } - - /// Initialize the receiver to dispose of the argument upon - /// deinitialization. - /// - /// - parameters: - /// - disposable: A disposable to dispose of when deinitializing. - public init(_ disposable: InnerDisposable) { - innerDisposable = disposable - } - - deinit { - dispose() - } - - public func dispose() { - innerDisposable.dispose() - } -} - -extension ScopedDisposable where InnerDisposable: AnyDisposable { - /// Initialize the receiver to dispose of the argument upon - /// deinitialization. - /// - /// - parameters: - /// - disposable: A disposable to dispose of when deinitializing, which - /// will be wrapped in an `AnyDisposable`. - public convenience init(_ disposable: Disposable) { - self.init(AnyDisposable(disposable)) - } -} - -/// A disposable that will optionally dispose of another disposable. -public final class SerialDisposable: Disposable { - private struct State { - var innerDisposable: Disposable? = nil - var isDisposed = false - } - - private let state = Atomic(State()) - - public var isDisposed: Bool { - return state.value.isDisposed - } - - /// The inner disposable to dispose of. - /// - /// Whenever this property is set (even to the same value!), the previous - /// disposable is automatically disposed. - public var innerDisposable: Disposable? { - get { - return state.value.innerDisposable - } - - set(d) { - let oldState: State = state.modify { state in - defer { state.innerDisposable = d } - return state - } - - oldState.innerDisposable?.dispose() - if oldState.isDisposed { - d?.dispose() - } - } - } - - /// Initializes the receiver to dispose of the argument when the - /// SerialDisposable is disposed. - /// - /// - parameters: - /// - disposable: Optional disposable. - public init(_ disposable: Disposable? = nil) { - innerDisposable = disposable - } - - public func dispose() { - let orig = state.swap(State(innerDisposable: nil, isDisposed: true)) - orig.innerDisposable?.dispose() - } -} - -/// Adds the right-hand-side disposable to the left-hand-side -/// `CompositeDisposable`. -/// -/// ```` -/// disposable += producer -/// .filter { ... } -/// .map { ... } -/// .start(observer) -/// ```` -/// -/// - parameters: -/// - lhs: Disposable to add to. -/// - rhs: Disposable to add. -/// -/// - returns: An instance of `DisposableHandle` that can be used to opaquely -/// remove the disposable later (if desired). -@discardableResult -public func +=(lhs: CompositeDisposable, rhs: Disposable?) -> CompositeDisposable.DisposableHandle { - return lhs.add(rhs) -} - -/// Adds the right-hand-side `ActionDisposable` to the left-hand-side -/// `CompositeDisposable`. -/// -/// ```` -/// disposable += { ... } -/// ```` -/// -/// - parameters: -/// - lhs: Disposable to add to. -/// - rhs: Closure to add as a disposable. -/// -/// - returns: An instance of `DisposableHandle` that can be used to opaquely -/// remove the disposable later (if desired). -@discardableResult -public func +=(lhs: CompositeDisposable, rhs: @escaping () -> ()) -> CompositeDisposable.DisposableHandle { - return lhs.add(rhs) -} - -/// Adds the right-hand-side disposable to the left-hand-side -/// `ScopedDisposable`. -/// -/// ```` -/// disposable += { ... } -/// ```` -/// -/// - parameters: -/// - lhs: Disposable to add to. -/// - rhs: Disposable to add. -/// -/// - returns: An instance of `DisposableHandle` that can be used to opaquely -/// remove the disposable later (if desired). -@discardableResult -public func +=(lhs: ScopedDisposable, rhs: Disposable?) -> CompositeDisposable.DisposableHandle { - return lhs.innerDisposable.add(rhs) -} - -/// Adds the right-hand-side disposable to the left-hand-side -/// `ScopedDisposable`. -/// -/// ```` -/// disposable += { ... } -/// ```` -/// -/// - parameters: -/// - lhs: Disposable to add to. -/// - rhs: Closure to add as a disposable. -/// -/// - returns: An instance of `DisposableHandle` that can be used to opaquely -/// remove the disposable later (if desired). -@discardableResult -public func +=(lhs: ScopedDisposable, rhs: @escaping () -> ()) -> CompositeDisposable.DisposableHandle { - return lhs.innerDisposable.add(rhs) -} diff --git a/ReactiveCocoa/Swift/DynamicProperty.swift b/ReactiveCocoa/Swift/DynamicProperty.swift deleted file mode 100644 index 0cae822ee6..0000000000 --- a/ReactiveCocoa/Swift/DynamicProperty.swift +++ /dev/null @@ -1,143 +0,0 @@ -import Foundation -import enum Result.NoError - -/// Models types that can be represented in Objective-C (i.e., reference -/// types, including generic types when boxed via `AnyObject`). -private protocol ObjectiveCRepresentable { - associatedtype Value - static func extract(from representation: Any) -> Value - static func represent(_ value: Value) -> Any -} - -/// Wraps a `dynamic` property, or one defined in Objective-C, using Key-Value -/// Coding and Key-Value Observing. -/// -/// Use this class only as a last resort! `MutableProperty` is generally better -/// unless KVC/KVO is required by the API you're using (for example, -/// `NSOperation`). -public final class DynamicProperty: MutablePropertyProtocol { - private weak var object: NSObject? - private let keyPath: String - - private let extractValue: (_ from: Any) -> Value - private let represent: (Value) -> Any - - private var property: MutableProperty? - - /// The current value of the property, as read and written using Key-Value - /// Coding. - public var value: Value? { - get { - return object?.value(forKeyPath: keyPath).map(extractValue) - } - - set(newValue) { - object?.setValue(newValue.map(represent), forKeyPath: keyPath) - } - } - - /// The lifetime of the property. - public var lifetime: Lifetime { - return object?.rac_lifetime ?? .empty - } - - /// A producer that will create a Key-Value Observer for the given object, - /// send its initial value then all changes over time, and then complete - /// when the observed object has deallocated. - /// - /// - important: This only works if the object given to init() is KVO-compliant. - /// Most UI controls are not! - public var producer: SignalProducer { - return (object.map { $0.values(forKeyPath: keyPath) } ?? .empty) - .map { [extractValue = self.extractValue] in $0.map(extractValue) } - } - - public lazy var signal: Signal = { [unowned self] in - var signal: Signal! - self.producer.startWithSignal { innerSignal, _ in signal = innerSignal } - return signal - }() - - /// Initializes a property that will observe and set the given key path of - /// the given object, using the supplied representation. - /// - /// - important: `object` must support weak references! - /// - /// - parameters: - /// - object: An object to be observed. - /// - keyPath: Key path to observe on the object. - /// - representable: A representation that bridges the values across the - /// language boundary. - fileprivate init( - object: NSObject?, - keyPath: String, - representable: Representatable.Type - ) - where Representatable.Value == Value - { - self.object = object - self.keyPath = keyPath - - self.extractValue = Representatable.extract(from:) - self.represent = Representatable.represent - - /// A DynamicProperty will stay alive as long as its object is alive. - /// This is made possible by strong reference cycles. - _ = object?.rac_lifetime.ended.observeCompleted { _ = self } - } -} - -extension DynamicProperty where Value: _ObjectiveCBridgeable { - /// Initializes a property that will observe and set the given key path of - /// the given object, where `Value` is a value type that is bridgeable - /// to Objective-C. - /// - /// - important: `object` must support weak references! - /// - /// - parameters: - /// - object: An object to be observed. - /// - keyPath: Key path to observe on the object. - public convenience init(object: NSObject?, keyPath: String) { - self.init(object: object, keyPath: keyPath, representable: BridgeableRepresentation.self) - } -} - -extension DynamicProperty where Value: AnyObject { - /// Initializes a property that will observe and set the given key path of - /// the given object, where `Value` is a reference type that can be - /// represented directly in Objective-C via `AnyObject`. - /// - /// - important: `object` must support weak references! - /// - /// - parameters: - /// - object: An object to be observed. - /// - keyPath: Key path to observe on the object. - public convenience init(object: NSObject?, keyPath: String) { - self.init(object: object, keyPath: keyPath, representable: DirectRepresentation.self) - } -} - -/// Represents values in Objective-C directly, via `AnyObject`. -private struct DirectRepresentation: ObjectiveCRepresentable { - static func extract(from representation: Any) -> Value { - return representation as! Value - } - - static func represent(_ value: Value) -> Any { - return value - } -} - -/// Represents values in Objective-C indirectly, via bridging. -private struct BridgeableRepresentation: ObjectiveCRepresentable { - static func extract(from representation: Any) -> Value { - let object = representation as! Value._ObjectiveCType - var result: Value? - Value._forceBridgeFromObjectiveC(object, result: &result) - return result! - } - - static func represent(_ value: Value) -> Any { - return value._bridgeToObjectiveC() - } -} diff --git a/ReactiveCocoa/Swift/Event.swift b/ReactiveCocoa/Swift/Event.swift deleted file mode 100644 index 3d6d94f6ba..0000000000 --- a/ReactiveCocoa/Swift/Event.swift +++ /dev/null @@ -1,165 +0,0 @@ -// -// Event.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2015-01-16. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -/// Represents a signal event. -/// -/// Signals must conform to the grammar: -/// `Next* (Failed | Completed | Interrupted)?` -public enum Event { - /// A value provided by the signal. - case next(Value) - - /// The signal terminated because of an error. No further events will be - /// received. - case failed(Error) - - /// The signal successfully terminated. No further events will be received. - case completed - - /// Event production on the signal has been interrupted. No further events - /// will be received. - /// - /// - important: This event does not signify the successful or failed - /// completion of the signal. - case interrupted - - /// Whether this event indicates signal termination (i.e., that no further - /// events will be received). - public var isTerminating: Bool { - switch self { - case .next: - return false - - case .failed, .completed, .interrupted: - return true - } - } - - /// Lift the given closure over the event's value. - /// - /// - important: The closure is called only on `next` type events. - /// - /// - parameters: - /// - f: A closure that accepts a value and returns a new value - /// - /// - returns: An event with function applied to a value in case `self` is a - /// `next` type of event. - public func map(_ f: (Value) -> U) -> Event { - switch self { - case let .next(value): - return .next(f(value)) - - case let .failed(error): - return .failed(error) - - case .completed: - return .completed - - case .interrupted: - return .interrupted - } - } - - /// Lift the given closure over the event's error. - /// - /// - important: The closure is called only on failed type event. - /// - /// - parameters: - /// - f: A closure that accepts an error object and returns - /// a new error object - /// - /// - returns: An event with function applied to an error object in case - /// `self` is a `.Failed` type of event. - public func mapError(_ f: (Error) -> F) -> Event { - switch self { - case let .next(value): - return .next(value) - - case let .failed(error): - return .failed(f(error)) - - case .completed: - return .completed - - case .interrupted: - return .interrupted - } - } - - /// Unwrap the contained `next` value. - public var value: Value? { - if case let .next(value) = self { - return value - } else { - return nil - } - } - - /// Unwrap the contained `Error` value. - public var error: Error? { - if case let .failed(error) = self { - return error - } else { - return nil - } - } -} - -public func == (lhs: Event, rhs: Event) -> Bool { - switch (lhs, rhs) { - case let (.next(left), .next(right)): - return left == right - - case let (.failed(left), .failed(right)): - return left == right - - case (.completed, .completed): - return true - - case (.interrupted, .interrupted): - return true - - default: - return false - } -} - -extension Event: CustomStringConvertible { - public var description: String { - switch self { - case let .next(value): - return "NEXT \(value)" - - case let .failed(error): - return "FAILED \(error)" - - case .completed: - return "COMPLETED" - - case .interrupted: - return "INTERRUPTED" - } - } -} - -/// Event protocol for constraining signal extensions -public protocol EventProtocol { - /// The value type of an event. - associatedtype Value - /// The error type of an event. If errors aren't possible then `NoError` can - /// be used. - associatedtype Error: Swift.Error - /// Extracts the event from the receiver. - var event: Event { get } -} - -extension Event: EventProtocol { - public var event: Event { - return self - } -} diff --git a/ReactiveCocoa/Swift/EventLogger.swift b/ReactiveCocoa/Swift/EventLogger.swift deleted file mode 100644 index 13cef16458..0000000000 --- a/ReactiveCocoa/Swift/EventLogger.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// EventLogger.swift -// ReactiveCocoa -// -// Created by Rui Peres on 30/04/2016. -// Copyright © 2016 GitHub. All rights reserved. -// - -import Foundation - -/// A namespace for logging event types. -public enum LoggingEvent { - public enum Signal: String { - case next, completed, failed, terminated, disposed, interrupted - - public static let allEvents: Set = [ - .next, .completed, .failed, .terminated, .disposed, .interrupted, - ] - } - - public enum SignalProducer: String { - case started, next, completed, failed, terminated, disposed, interrupted - - public static let allEvents: Set = [ - .started, .next, .completed, .failed, .terminated, .disposed, .interrupted, - ] - } -} - -private func defaultEventLog(identifier: String, event: String, fileName: String, functionName: String, lineNumber: Int) { - print("[\(identifier)] \(event) fileName: \(fileName), functionName: \(functionName), lineNumber: \(lineNumber)") -} - -/// A type that represents an event logging function. -public typealias EventLogger = ( - _ identifier: String, - _ event: String, - _ fileName: String, - _ functionName: String, - _ lineNumber: Int -) -> Void - -extension SignalProtocol { - /// Logs all events that the receiver sends. By default, it will print to - /// the standard output. - /// - /// - parameters: - /// - identifier: a string to identify the Signal firing events. - /// - events: Types of events to log. - /// - fileName: Name of the file containing the code which fired the - /// event. - /// - functionName: Function where event was fired. - /// - lineNumber: Line number where event was fired. - /// - logger: Logger that logs the events. - /// - /// - returns: Signal that, when observed, logs the fired events. - public func logEvents(identifier: String = "", events: Set = LoggingEvent.Signal.allEvents, fileName: String = #file, functionName: String = #function, lineNumber: Int = #line, logger: EventLogger = defaultEventLog) -> Signal { - func log(_ event: LoggingEvent.Signal) -> ((T) -> Void)? { - return event.logIfNeeded(events: events) { event in - logger(identifier, event, fileName, functionName, lineNumber) - } - } - - return self.on( - failed: log(.failed), - completed: log(.completed), - interrupted: log(.interrupted), - terminated: log(.terminated), - disposed: log(.disposed), - next: log(.next) - ) - } -} - -extension SignalProducerProtocol { - /// Logs all events that the receiver sends. By default, it will print to - /// the standard output. - /// - /// - parameters: - /// - identifier: a string to identify the SignalProducer firing events. - /// - events: Types of events to log. - /// - fileName: Name of the file containing the code which fired the - /// event. - /// - functionName: Function where event was fired. - /// - lineNumber: Line number where event was fired. - /// - logger: Logger that logs the events. - /// - /// - returns: Signal producer that, when started, logs the fired events. - public func logEvents(identifier: String = "", - events: Set = LoggingEvent.SignalProducer.allEvents, - fileName: String = #file, - functionName: String = #function, - lineNumber: Int = #line, - logger: EventLogger = defaultEventLog - ) -> SignalProducer { - func log(_ event: LoggingEvent.SignalProducer) -> ((T) -> Void)? { - return event.logIfNeeded(events: events) { event in - logger(identifier, event, fileName, functionName, lineNumber) - } - } - - return self.on( - started: log(.started), - next: log(.next), - failed: log(.failed), - completed: log(.completed), - interrupted: log(.interrupted), - terminated: log(.terminated), - disposed: log(.disposed) - ) - } -} - -private protocol LoggingEventProtocol: Hashable, RawRepresentable {} -extension LoggingEvent.Signal: LoggingEventProtocol {} -extension LoggingEvent.SignalProducer: LoggingEventProtocol {} - -private extension LoggingEventProtocol { - func logIfNeeded(events: Set, logger: @escaping (String) -> Void) -> ((T) -> Void)? { - guard events.contains(self) else { - return nil - } - - return { value in - if value is Void { - logger("\(self.rawValue)") - } else { - logger("\(self.rawValue) \(value)") - } - } - } -} diff --git a/ReactiveCocoa/Swift/Flatten.swift b/ReactiveCocoa/Swift/Flatten.swift deleted file mode 100644 index d51991c653..0000000000 --- a/ReactiveCocoa/Swift/Flatten.swift +++ /dev/null @@ -1,928 +0,0 @@ -// -// Flatten.swift -// ReactiveCocoa -// -// Created by Neil Pankey on 11/30/15. -// Copyright © 2015 GitHub. All rights reserved. -// - -import enum Result.NoError - -/// Describes how multiple producers should be joined together. -public enum FlattenStrategy: Equatable { - /// The producers should be merged, so that any value received on any of the - /// input producers will be forwarded immediately to the output producer. - /// - /// The resulting producer will complete only when all inputs have - /// completed. - case merge - - /// The producers should be concatenated, so that their values are sent in - /// the order of the producers themselves. - /// - /// The resulting producer will complete only when all inputs have - /// completed. - case concat - - /// Only the events from the latest input producer should be considered for - /// the output. Any producers received before that point will be disposed - /// of. - /// - /// The resulting producer will complete only when the producer-of-producers - /// and the latest producer has completed. - case latest -} - - -extension SignalProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Flattens the inner producers sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - note: If `signal` or an active inner producer fails, the returned - /// signal will forward that failure immediately. - /// - /// - note: `interrupted` events on inner producers will be treated like - /// `Completed events on inner producers. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - switch strategy { - case .merge: - return self.merge() - - case .concat: - return self.concat() - - case .latest: - return self.switchToLatest() - } - } -} - -extension SignalProtocol where Value: SignalProducerProtocol, Error == NoError { - /// Flattens the inner producers sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - note: If an active inner producer fails, the returned signal will - /// forward that failure immediately. - /// - /// - warning: `interrupted` events on inner producers will be treated like - /// `completed` events on inner producers. - /// - /// - parameters: - /// - strategy: Strategy used when flattening signals. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - return self - .promoteErrors(Value.Error.self) - .flatten(strategy) - } -} - -extension SignalProtocol where Value: SignalProducerProtocol, Error == NoError, Value.Error == NoError { - /// Flattens the inner producers sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - warning: `interrupted` events on inner producers will be treated like - /// `completed` events on inner producers. - /// - /// - parameters: - /// - strategy: Strategy used when flattening signals. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - switch strategy { - case .merge: - return self.merge() - - case .concat: - return self.concat() - - case .latest: - return self.switchToLatest() - } - } -} - -extension SignalProtocol where Value: SignalProducerProtocol, Value.Error == NoError { - /// Flattens the inner producers sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - note: If `signal` fails, the returned signal will forward that failure - /// immediately. - /// - /// - warning: `interrupted` events on inner producers will be treated like - /// `completed` events on inner producers. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - return self.flatMap(strategy) { $0.promoteErrors(Error.self) } - } -} - -extension SignalProducerProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Flattens the inner producers sent upon `producer` (into a single - /// producer of values), according to the semantics of the given strategy. - /// - /// - note: If `producer` or an active inner producer fails, the returned - /// producer will forward that failure immediately. - /// - /// - warning: `interrupted` events on inner producers will be treated like - /// `completed` events on inner producers. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - switch strategy { - case .merge: - return self.merge() - - case .concat: - return self.concat() - - case .latest: - return self.switchToLatest() - } - } -} - -extension SignalProducerProtocol where Value: SignalProducerProtocol, Error == NoError { - /// Flattens the inner producers sent upon `producer` (into a single - /// producer of values), according to the semantics of the given strategy. - /// - /// - note: If an active inner producer fails, the returned producer will - /// forward that failure immediately. - /// - /// - warning: `interrupted` events on inner producers will be treated like - /// `completed` events on inner producers. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - return self - .promoteErrors(Value.Error.self) - .flatten(strategy) - } -} - -extension SignalProducerProtocol where Value: SignalProducerProtocol, Error == NoError, Value.Error == NoError { - /// Flattens the inner producers sent upon `producer` (into a single - /// producer of values), according to the semantics of the given strategy. - /// - /// - warning: `interrupted` events on inner producers will be treated like - /// `completed` events on inner producers. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - switch strategy { - case .merge: - return self.merge() - - case .concat: - return self.concat() - - case .latest: - return self.switchToLatest() - } - } -} - -extension SignalProducerProtocol where Value: SignalProducerProtocol, Value.Error == NoError { - /// Flattens the inner producers sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - note: If `signal` fails, the returned signal will forward that failure - /// immediately. - /// - /// - warning: `interrupted` events on inner producers will be treated like - /// `completed` events on inner producers. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - return self.flatMap(strategy) { $0.promoteErrors(Error.self) } - } -} - -extension SignalProtocol where Value: SignalProtocol, Error == Value.Error { - /// Flattens the inner signals sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - note: If `signal` or an active inner signal emits an error, the - /// returned signal will forward that error immediately. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - return self - .map(SignalProducer.init) - .flatten(strategy) - } -} - -extension SignalProtocol where Value: SignalProtocol, Error == NoError { - /// Flattens the inner signals sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - note: If an active inner signal emits an error, the returned signal - /// will forward that error immediately. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - return self - .promoteErrors(Value.Error.self) - .flatten(strategy) - } -} - -extension SignalProtocol where Value: SignalProtocol, Error == NoError, Value.Error == NoError { - /// Flattens the inner signals sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - return self - .map(SignalProducer.init) - .flatten(strategy) - } -} - -extension SignalProtocol where Value: SignalProtocol, Value.Error == NoError { - /// Flattens the inner signals sent upon `signal` (into a single signal of - /// values), according to the semantics of the given strategy. - /// - /// - note: If `signal` emits an error, the returned signal will forward - /// that error immediately. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - return self.flatMap(strategy) { $0.promoteErrors(Error.self) } - } -} - -extension SignalProtocol where Value: Sequence, Error == NoError { - /// Flattens the `sequence` value sent by `signal` according to - /// the semantics of the given strategy. - public func flatten(_ strategy: FlattenStrategy) -> Signal { - return self.flatMap(strategy) { .init(values: $0) } - } -} - -extension SignalProducerProtocol where Value: SignalProtocol, Error == Value.Error { - /// Flattens the inner signals sent upon `producer` (into a single producer - /// of values), according to the semantics of the given strategy. - /// - /// - note: If `producer` or an active inner signal emits an error, the - /// returned producer will forward that error immediately. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - return self - .map(SignalProducer.init) - .flatten(strategy) - } -} - -extension SignalProducerProtocol where Value: SignalProtocol, Error == NoError { - /// Flattens the inner signals sent upon `producer` (into a single producer - /// of values), according to the semantics of the given strategy. - /// - /// - note: If an active inner signal emits an error, the returned producer - /// will forward that error immediately. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - return self - .promoteErrors(Value.Error.self) - .flatten(strategy) - } -} - -extension SignalProducerProtocol where Value: SignalProtocol, Error == NoError, Value.Error == NoError { - /// Flattens the inner signals sent upon `producer` (into a single producer - /// of values), according to the semantics of the given strategy. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - return self - .map(SignalProducer.init) - .flatten(strategy) - } -} - -extension SignalProducerProtocol where Value: SignalProtocol, Value.Error == NoError { - /// Flattens the inner signals sent upon `producer` (into a single producer - /// of values), according to the semantics of the given strategy. - /// - /// - note: If `producer` emits an error, the returned producer will forward - /// that error immediately. - /// - /// - warning: `interrupted` events on inner signals will be treated like - /// `completed` events on inner signals. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - return self.flatMap(strategy) { $0.promoteErrors(Error.self) } - } -} - -extension SignalProducerProtocol where Value: Sequence, Error == NoError { - /// Flattens the `sequence` value sent by `producer` according to - /// the semantics of the given strategy. - public func flatten(_ strategy: FlattenStrategy) -> SignalProducer { - return self.flatMap(strategy) { .init(values: $0) } - } -} - -extension SignalProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Returns a signal which sends all the values from producer signal emitted - /// from `signal`, waiting until each inner producer completes before - /// beginning to send the values from the next inner producer. - /// - /// - note: If any of the inner producers fail, the returned signal will - /// forward that failure immediately - /// - /// - note: The returned signal completes only when `signal` and all - /// producers emitted from `signal` complete. - fileprivate func concat() -> Signal { - return Signal { relayObserver in - let disposable = CompositeDisposable() - let relayDisposable = CompositeDisposable() - - disposable += relayDisposable - disposable += self.observeConcat(relayObserver, relayDisposable) - - return disposable - } - } - - fileprivate func observeConcat(_ observer: Observer, _ disposable: CompositeDisposable? = nil) -> Disposable? { - let state = ConcatState(observer: observer, disposable: disposable) - - return self.observe { event in - switch event { - case let .next(value): - state.enqueueSignalProducer(value.producer) - - case let .failed(error): - observer.sendFailed(error) - - case .completed: - // Add one last producer to the queue, whose sole job is to - // "turn out the lights" by completing `observer`. - state.enqueueSignalProducer(SignalProducer.empty.on(completed: { - observer.sendCompleted() - })) - - case .interrupted: - observer.sendInterrupted() - } - } - } -} - -extension SignalProducerProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Returns a producer which sends all the values from each producer emitted - /// from `producer`, waiting until each inner producer completes before - /// beginning to send the values from the next inner producer. - /// - /// - note: If any of the inner producers emit an error, the returned - /// producer will emit that error. - /// - /// - note: The returned producer completes only when `producer` and all - /// producers emitted from `producer` complete. - fileprivate func concat() -> SignalProducer { - return SignalProducer { observer, disposable in - self.startWithSignal { signal, signalDisposable in - disposable += signalDisposable - _ = signal.observeConcat(observer, disposable) - } - } - } -} - -extension SignalProducerProtocol { - /// `concat`s `next` onto `self`. - public func concat(_ next: SignalProducer) -> SignalProducer { - return SignalProducer, Error>(values: [ self.producer, next ]).flatten(.concat) - } - - /// `concat`s `value` onto `self`. - public func concat(value: Value) -> SignalProducer { - return self.concat(SignalProducer(value: value)) - } - - /// `concat`s `self` onto initial `previous`. - public func prefix(_ previous: P) -> SignalProducer - where P.Value == Value, P.Error == Error - { - return previous.concat(self.producer) - } - - /// `concat`s `self` onto initial `value`. - public func prefix(value: Value) -> SignalProducer { - return self.prefix(SignalProducer(value: value)) - } -} - -private final class ConcatState { - /// The observer of a started `concat` producer. - let observer: Observer - - /// The top level disposable of a started `concat` producer. - let disposable: CompositeDisposable? - - /// The active producer, if any, and the producers waiting to be started. - let queuedSignalProducers: Atomic<[SignalProducer]> = Atomic([]) - - init(observer: Signal.Observer, disposable: CompositeDisposable?) { - self.observer = observer - self.disposable = disposable - } - - func enqueueSignalProducer(_ producer: SignalProducer) { - if let d = disposable, d.isDisposed { - return - } - - let shouldStart: Bool = queuedSignalProducers.modify { queue in - // An empty queue means the concat is idle, ready & waiting to start - // the next producer. - defer { queue.append(producer) } - return queue.isEmpty - } - - if shouldStart { - startNextSignalProducer(producer) - } - } - - func dequeueSignalProducer() -> SignalProducer? { - if let d = disposable, d.isDisposed { - return nil - } - - return queuedSignalProducers.modify { queue in - // Active producers remain in the queue until completed. Since - // dequeueing happens at completion of the active producer, the - // first producer in the queue can be removed. - if !queue.isEmpty { queue.remove(at: 0) } - return queue.first - } - } - - /// Subscribes to the given signal producer. - func startNextSignalProducer(_ signalProducer: SignalProducer) { - signalProducer.startWithSignal { signal, disposable in - let handle = self.disposable?.add(disposable) ?? nil - - signal.observe { event in - switch event { - case .completed, .interrupted: - handle?.remove() - - if let nextSignalProducer = self.dequeueSignalProducer() { - self.startNextSignalProducer(nextSignalProducer) - } - - case .next, .failed: - self.observer.action(event) - } - } - } - } -} - -extension SignalProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Merges a `signal` of SignalProducers down into a single signal, biased - /// toward the producer added earlier. Returns a Signal that will forward - /// events from the inner producers as they arrive. - fileprivate func merge() -> Signal { - return Signal { relayObserver in - let disposable = CompositeDisposable() - let relayDisposable = CompositeDisposable() - - disposable += relayDisposable - disposable += self.observeMerge(relayObserver, relayDisposable) - - return disposable - } - } - - fileprivate func observeMerge(_ observer: Observer, _ disposable: CompositeDisposable) -> Disposable? { - let inFlight = Atomic(1) - let decrementInFlight = { - let shouldComplete: Bool = inFlight.modify { - $0 -= 1 - return $0 == 0 - } - - if shouldComplete { - observer.sendCompleted() - } - } - - return self.observe { event in - switch event { - case let .next(producer): - producer.startWithSignal { innerSignal, innerDisposable in - inFlight.modify { $0 += 1 } - let handle = disposable.add(innerDisposable) - - innerSignal.observe { event in - switch event { - case .completed, .interrupted: - handle.remove() - decrementInFlight() - - case .next, .failed: - observer.action(event) - } - } - } - - case let .failed(error): - observer.sendFailed(error) - - case .completed: - decrementInFlight() - - case .interrupted: - observer.sendInterrupted() - } - } - } -} - -extension SignalProducerProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Merges a `signal` of SignalProducers down into a single signal, biased - /// toward the producer added earlier. Returns a Signal that will forward - /// events from the inner producers as they arrive. - fileprivate func merge() -> SignalProducer { - return SignalProducer { relayObserver, disposable in - self.startWithSignal { signal, signalDisposable in - disposable += signalDisposable - - _ = signal.observeMerge(relayObserver, disposable) - } - - } - } -} - -extension SignalProtocol { - /// Merges the given signals into a single `Signal` that will emit all - /// values from each of them, and complete when all of them have completed. - public static func merge(_ signals: Seq) -> Signal - where S.Value == Value, S.Error == Error, Seq.Iterator.Element == S - { - let producer = SignalProducer(values: signals) - var result: Signal! - - producer.startWithSignal { signal, _ in - result = signal.flatten(.merge) - } - - return result - } - - /// Merges the given signals into a single `Signal` that will emit all - /// values from each of them, and complete when all of them have completed. - public static func merge(_ signals: S...) -> Signal - where S.Value == Value, S.Error == Error - { - return Signal.merge(signals) - } -} - -extension SignalProducerProtocol { - /// Merges the given producers into a single `SignalProducer` that will emit - /// all values from each of them, and complete when all of them have - /// completed. - public static func merge(_ producers: Seq) -> SignalProducer - where S.Value == Value, S.Error == Error, Seq.Iterator.Element == S - { - return SignalProducer(values: producers).flatten(.merge) - } - - /// Merges the given producers into a single `SignalProducer` that will emit - /// all values from each of them, and complete when all of them have - /// completed. - public static func merge(_ producers: S...) -> SignalProducer - where S.Value == Value, S.Error == Error - { - return SignalProducer.merge(producers) - } -} - -extension SignalProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Returns a signal that forwards values from the latest signal sent on - /// `signal`, ignoring values sent on previous inner signal. - /// - /// An error sent on `signal` or the latest inner signal will be sent on the - /// returned signal. - /// - /// The returned signal completes when `signal` and the latest inner - /// signal have both completed. - fileprivate func switchToLatest() -> Signal { - return Signal { observer in - let composite = CompositeDisposable() - let serial = SerialDisposable() - - composite += serial - composite += self.observeSwitchToLatest(observer, serial) - - return composite - } - } - - fileprivate func observeSwitchToLatest(_ observer: Observer, _ latestInnerDisposable: SerialDisposable) -> Disposable? { - let state = Atomic(LatestState()) - - return self.observe { event in - switch event { - case let .next(innerProducer): - innerProducer.startWithSignal { innerSignal, innerDisposable in - state.modify { - // When we replace the disposable below, this prevents - // the generated Interrupted event from doing any work. - $0.replacingInnerSignal = true - } - - latestInnerDisposable.innerDisposable = innerDisposable - - state.modify { - $0.replacingInnerSignal = false - $0.innerSignalComplete = false - } - - innerSignal.observe { event in - switch event { - case .interrupted: - // If interruption occurred as a result of a new - // producer arriving, we don't want to notify our - // observer. - let shouldComplete: Bool = state.modify { state in - if !state.replacingInnerSignal { - state.innerSignalComplete = true - } - return !state.replacingInnerSignal && state.outerSignalComplete - } - - if shouldComplete { - observer.sendCompleted() - } - - case .completed: - let shouldComplete: Bool = state.modify { - $0.innerSignalComplete = true - return $0.outerSignalComplete - } - - if shouldComplete { - observer.sendCompleted() - } - - case .next, .failed: - observer.action(event) - } - } - } - - case let .failed(error): - observer.sendFailed(error) - - case .completed: - let shouldComplete: Bool = state.modify { - $0.outerSignalComplete = true - return $0.innerSignalComplete - } - - if shouldComplete { - observer.sendCompleted() - } - - case .interrupted: - observer.sendInterrupted() - } - } - } -} - -extension SignalProducerProtocol where Value: SignalProducerProtocol, Error == Value.Error { - /// Returns a signal that forwards values from the latest signal sent on - /// `signal`, ignoring values sent on previous inner signal. - /// - /// An error sent on `signal` or the latest inner signal will be sent on the - /// returned signal. - /// - /// The returned signal completes when `signal` and the latest inner - /// signal have both completed. - fileprivate func switchToLatest() -> SignalProducer { - return SignalProducer { observer, disposable in - let latestInnerDisposable = SerialDisposable() - disposable += latestInnerDisposable - - self.startWithSignal { signal, signalDisposable in - disposable += signalDisposable - disposable += signal.observeSwitchToLatest(observer, latestInnerDisposable) - } - } - } -} - -private struct LatestState { - var outerSignalComplete: Bool = false - var innerSignalComplete: Bool = true - - var replacingInnerSignal: Bool = false -} - - -extension SignalProtocol { - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting producers (into a signal of values), according to the - /// semantics of the given strategy. - /// - /// If `signal` or any of the created producers fail, the returned signal - /// will forward that failure immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> Signal { - return map(transform).flatten(strategy) - } - - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting producers (into a signal of values), according to the - /// semantics of the given strategy. - /// - /// If `signal` fails, the returned signal will forward that failure - /// immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> Signal { - return map(transform).flatten(strategy) - } - - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting signals (into a signal of values), according to the - /// semantics of the given strategy. - /// - /// If `signal` or any of the created signals emit an error, the returned - /// signal will forward that error immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> Signal { - return map(transform).flatten(strategy) - } - - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting signals (into a signal of values), according to the - /// semantics of the given strategy. - /// - /// If `signal` emits an error, the returned signal will forward that - /// error immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> Signal { - return map(transform).flatten(strategy) - } -} - -extension SignalProtocol where Error == NoError { - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting signals (into a signal of values), according to the - /// semantics of the given strategy. - /// - /// If any of the created signals emit an error, the returned signal - /// will forward that error immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> Signal { - return map(transform).flatten(strategy) - } - - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting signals (into a signal of values), according to the - /// semantics of the given strategy. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> Signal { - return map(transform).flatten(strategy) - } - - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting signals (into a signal of values), according to the - /// semantics of the given strategy. - /// - /// If any of the created signals emit an error, the returned signal - /// will forward that error immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> Signal { - return map(transform).flatten(strategy) - } - - /// Maps each event from `signal` to a new signal, then flattens the - /// resulting signals (into a signal of values), according to the - /// semantics of the given strategy. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> Signal { - return map(transform).flatten(strategy) - } -} - -extension SignalProducerProtocol { - /// Maps each event from `self` to a new producer, then flattens the - /// resulting producers (into a producer of values), according to the - /// semantics of the given strategy. - /// - /// If `self` or any of the created producers fail, the returned producer - /// will forward that failure immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> SignalProducer { - return map(transform).flatten(strategy) - } - - /// Maps each event from `self` to a new producer, then flattens the - /// resulting producers (into a producer of values), according to the - /// semantics of the given strategy. - /// - /// If `self` fails, the returned producer will forward that failure - /// immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> SignalProducer { - return map(transform).flatten(strategy) - } - - /// Maps each event from `self` to a new producer, then flattens the - /// resulting signals (into a producer of values), according to the - /// semantics of the given strategy. - /// - /// If `self` or any of the created signals emit an error, the returned - /// producer will forward that error immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> SignalProducer { - return map(transform).flatten(strategy) - } - - /// Maps each event from `self` to a new producer, then flattens the - /// resulting signals (into a producer of values), according to the - /// semantics of the given strategy. - /// - /// If `self` emits an error, the returned producer will forward that - /// error immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> SignalProducer { - return map(transform).flatten(strategy) - } -} - -extension SignalProducerProtocol where Error == NoError { - /// Maps each event from `self` to a new producer, then flattens the - /// resulting producers (into a producer of values), according to the - /// semantics of the given strategy. - /// - /// If any of the created producers fail, the returned producer will - /// forward that failure immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> SignalProducer { - return map(transform).flatten(strategy) - } - - /// Maps each event from `self` to a new producer, then flattens the - /// resulting producers (into a producer of values), according to the - /// semantics of the given strategy. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> SignalProducer) -> SignalProducer { - return map(transform).flatten(strategy) - } - - /// Maps each event from `self` to a new producer, then flattens the - /// resulting signals (into a producer of values), according to the - /// semantics of the given strategy. - /// - /// If any of the created signals emit an error, the returned - /// producer will forward that error immediately. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> SignalProducer { - return map(transform).flatten(strategy) - } - - /// Maps each event from `self` to a new producer, then flattens the - /// resulting signals (into a producer of values), according to the - /// semantics of the given strategy. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> Signal) -> SignalProducer { - return map(transform).flatten(strategy) - } -} - - -extension SignalProtocol { - /// Catches any failure that may occur on the input signal, mapping to a new - /// producer that starts in its place. - public func flatMapError(_ handler: @escaping (Error) -> SignalProducer) -> Signal { - return Signal { observer in - self.observeFlatMapError(handler, observer, SerialDisposable()) - } - } - - fileprivate func observeFlatMapError(_ handler: @escaping (Error) -> SignalProducer, _ observer: Observer, _ serialDisposable: SerialDisposable) -> Disposable? { - return self.observe { event in - switch event { - case let .next(value): - observer.sendNext(value) - case let .failed(error): - handler(error).startWithSignal { signal, disposable in - serialDisposable.innerDisposable = disposable - signal.observe(observer) - } - case .completed: - observer.sendCompleted() - case .interrupted: - observer.sendInterrupted() - } - } - } -} - -extension SignalProducerProtocol { - /// Catches any failure that may occur on the input producer, mapping to a - /// new producer that starts in its place. - public func flatMapError(_ handler: @escaping (Error) -> SignalProducer) -> SignalProducer { - return SignalProducer { observer, disposable in - let serialDisposable = SerialDisposable() - disposable += serialDisposable - - self.startWithSignal { signal, signalDisposable in - serialDisposable.innerDisposable = signalDisposable - - _ = signal.observeFlatMapError(handler, observer, serialDisposable) - } - } - } -} diff --git a/ReactiveCocoa/Swift/FoundationExtensions.swift b/ReactiveCocoa/Swift/FoundationExtensions.swift deleted file mode 100644 index 8f36b3f343..0000000000 --- a/ReactiveCocoa/Swift/FoundationExtensions.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// FoundationExtensions.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-10-19. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Foundation -import enum Result.NoError - -extension NotificationCenter { - /// Returns a SignalProducer to observe posting of the specified - /// notification. - /// - /// - parameters: - /// - name: name of the notification to observe - /// - object: an instance which sends the notifications - /// - /// - returns: A SignalProducer of notifications posted that match the given - /// criteria. - /// - /// - note: If the `object` is deallocated before starting the producer, it - /// will terminate immediately with an `interrupted` event. - /// Otherwise, the producer will not terminate naturally, so it must - /// be explicitly disposed to avoid leaks. - public func rac_notifications(forName name: Notification.Name?, object: AnyObject? = nil) -> SignalProducer { - // We're weakly capturing an optional reference here, which makes destructuring awkward. - let objectWasNil = (object == nil) - return SignalProducer { [weak object] observer, disposable in - guard object != nil || objectWasNil else { - observer.sendInterrupted() - return - } - - let notificationObserver = self.addObserver(forName: name, object: object, queue: nil) { notification in - observer.sendNext(notification) - } - - disposable += { - self.removeObserver(notificationObserver) - } - } - } -} - -private let defaultSessionError = NSError(domain: "org.reactivecocoa.ReactiveCocoa.rac_dataWithRequest", code: 1, userInfo: nil) - -extension URLSession { - /// Returns a SignalProducer which performs the work associated with an - /// `NSURLSession` - /// - /// - parameters: - /// - request: A request that will be performed when the producer is - /// started - /// - /// - returns: A producer that will execute the given request once for each - /// invocation of `start()`. - /// - /// - note: This method will not send an error event in the case of a server - /// side error (i.e. when a response with status code other than - /// 200...299 is received). - public func rac_data(with request: URLRequest) -> SignalProducer<(Data, URLResponse), NSError> { - return SignalProducer { observer, disposable in - let task = self.dataTask(with: request) { data, response, error in - if let data = data, let response = response { - observer.sendNext((data, response)) - observer.sendCompleted() - } else { - observer.sendFailed(error as NSError? ?? defaultSessionError) - } - } - - disposable += { - task.cancel() - } - task.resume() - } - } -} diff --git a/ReactiveCocoa/Swift/Lifetime.swift b/ReactiveCocoa/Swift/Lifetime.swift deleted file mode 100644 index 671f9e7178..0000000000 --- a/ReactiveCocoa/Swift/Lifetime.swift +++ /dev/null @@ -1,96 +0,0 @@ -import Foundation -import enum Result.NoError - -/// Represents the lifetime of an object, and provides a hook to observe when -/// the object deinitializes. -public final class Lifetime { - /// MARK: Type properties and methods - - /// A `Lifetime` that has already ended. - public static var empty: Lifetime { - return Lifetime(ended: .empty) - } - - /// MARK: Instance properties - - /// A signal that sends a `completed` event when the lifetime ends. - public let ended: Signal<(), NoError> - - /// MARK: Initializers - - /// Initialize a `Lifetime` object with the supplied ended signal. - /// - /// - parameters: - /// - signal: The ended signal. - private init(ended signal: Signal<(), NoError>) { - ended = signal - } - - /// Initialize a `Lifetime` from a lifetime token, which is expected to be - /// associated with an object. - /// - /// - important: The resulting lifetime object does not retain the lifetime - /// token. - /// - /// - parameters: - /// - token: A lifetime token for detecting the deinitialization of the - /// associated object. - public convenience init(_ token: Token) { - self.init(ended: token.ended) - } - - /// A token object which completes its signal when it deinitializes. - /// - /// It is generally used in conjuncion with `Lifetime` as a private - /// deinitialization trigger. - /// - /// ``` - /// class MyController { - /// private let token = Lifetime.Token() - /// public var lifetime: Lifetime { - /// return Lifetime(token) - /// } - /// } - /// ``` - public final class Token { - /// A signal that sends a Completed event when the lifetime ends. - fileprivate let ended: Signal<(), NoError> - - private let endedObserver: Signal<(), NoError>.Observer - - public init() { - (ended, endedObserver) = Signal.pipe() - } - - deinit { - endedObserver.sendCompleted() - } - } -} - -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - -private var lifetimeKey: UInt8 = 0 -private var lifetimeTokenKey: UInt8 = 0 - -extension NSObject { - /// Returns a lifetime that ends when the receiver is deallocated. - @nonobjc public var rac_lifetime: Lifetime { - objc_sync_enter(self) - defer { objc_sync_exit(self) } - - if let lifetime = objc_getAssociatedObject(self, &lifetimeKey) as! Lifetime? { - return lifetime - } - - let token = Lifetime.Token() - let lifetime = Lifetime(token) - - objc_setAssociatedObject(self, &lifetimeTokenKey, token, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - objc_setAssociatedObject(self, &lifetimeKey, lifetime, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - - return lifetime - } -} - -#endif diff --git a/ReactiveCocoa/Swift/NSObject+KeyValueObserving.swift b/ReactiveCocoa/Swift/NSObject+KeyValueObserving.swift deleted file mode 100644 index 824b315696..0000000000 --- a/ReactiveCocoa/Swift/NSObject+KeyValueObserving.swift +++ /dev/null @@ -1,355 +0,0 @@ -import Foundation -import enum Result.NoError - -extension NSObject { - /// Create a producer which sends the current value and all the subsequent - /// changes of the property specified by the key path. - /// - /// The producer completes when `self` deinitializes. - /// - /// - parameters: - /// - keyPath: The key path of the property to be observed. - /// - /// - returns: - /// A producer emitting values of the property specified by the key path. - public func values(forKeyPath keyPath: String) -> SignalProducer { - return SignalProducer { observer, disposable in - disposable += KeyValueObserver.observe( - self, - keyPath: keyPath, - options: [.initial, .new], - action: observer.sendNext - ) - disposable += self.rac_lifetime.ended.observeCompleted(observer.sendCompleted) - } - } -} - -internal final class KeyValueObserver: NSObject { - typealias Action = (_ object: AnyObject?) -> Void - private static let context = UnsafeMutableRawPointer.allocate(bytes: 1, alignedTo: 0) - - unowned(unsafe) let unsafeObject: NSObject - let key: String - let action: Action - - fileprivate init(observing object: NSObject, key: String, options: NSKeyValueObservingOptions, action: Action) { - self.unsafeObject = object - self.key = key - self.action = action - - super.init() - - object.addObserver( - self, - forKeyPath: key, - options: options, - context: KeyValueObserver.context - ) - } - - func detach() { - unsafeObject.removeObserver(self, forKeyPath: key, context: KeyValueObserver.context) - } - - override func observeValue( - forKeyPath keyPath: String?, - of object: Any?, - change: [NSKeyValueChangeKey : Any]?, - context: UnsafeMutableRawPointer? - ) { - if context == KeyValueObserver.context { - action(object as! NSObject) - } - } -} - -extension KeyValueObserver { - /// Establish an observation to the property specified by the key path - /// of `object`. - /// - /// - warning: The observation would not be automatically removed when - /// `object` deinitializes. You must manually dispose of the - /// returned disposable before `object` completes its - /// deinitialization. - /// - /// - parameters: - /// - object: The object to be observed. - /// - keyPath: The key path of the property to be observed. - /// - options: The desired configuration of the observation. - /// - action: The action to be invoked upon arrival of changes. - /// - /// - returns: - /// A disposable that would tear down the observation upon disposal. - static func observe( - _ object: NSObject, - keyPath: String, - options: NSKeyValueObservingOptions, - action: @escaping (_ value: AnyObject?) -> Void - ) -> ActionDisposable { - // Compute the key path head and tail. - let components = keyPath.components(separatedBy: ".") - precondition(!components.isEmpty, "Received an empty key path.") - - let isNested = components.count > 1 - let keyPathHead = components[0] - let keyPathTail = components[1 ..< components.endIndex].joined(separator: ".") - - // The serial disposable for the head key. - // - // The inner disposable would be disposed of, and replaced with a new one - // when the value of the head key changes. - let headSerialDisposable = SerialDisposable() - - // If the property of the head key isn't actually an object (or is a Class - // object), there is no point in observing the deallocation. - // - // If this property is not a weak reference to an object, we don't need to - // watch for it spontaneously being set to nil. - // - // Attempting to observe non-weak properties using dynamic getters will - // result in broken behavior, so don't even try. - let shouldObserveDeinit = keyPathHead.withCString { cString -> Bool in - if let propertyPointer = class_getProperty(type(of: object), cString) { - let attributes = PropertyAttributes(property: propertyPointer) - return attributes.isObject && attributes.isWeak && attributes.objectClass != NSClassFromString("Protocol") && !attributes.isBlock - } - - return false - } - - // Establish the observation. - // - // The initial value is also handled by the closure below, if `Initial` has - // been specified in the observation options. - let observer: KeyValueObserver - - if isNested { - observer = KeyValueObserver(observing: object, key: keyPathHead, options: options) { object in - guard let value = object?.value(forKey: keyPathHead) as! NSObject? else { - action(nil) - return - } - - let headDisposable = CompositeDisposable() - headSerialDisposable.innerDisposable = headDisposable - - if shouldObserveDeinit { - let disposable = value.rac_lifetime.ended.observeCompleted { - action(nil) - } - headDisposable += disposable - } - - // Recursively add observers along the key path tail. - let disposable = KeyValueObserver.observe( - value, - keyPath: keyPathTail, - options: options.subtracting(.initial), - action: action - ) - headDisposable += disposable - - // Send the latest value of the key path tail. - action(value.value(forKeyPath: keyPathTail) as AnyObject?) - } - } else { - observer = KeyValueObserver(observing: object, key: keyPathHead, options: options) { object in - guard let value = object?.value(forKey: keyPathHead) as! NSObject? else { - action(nil) - return - } - - if shouldObserveDeinit { - let disposable = value.rac_lifetime.ended.observeCompleted { - action(nil) - } - headSerialDisposable.innerDisposable = disposable - } - - // Send the latest value of the key. - action(value) - } - } - - return ActionDisposable { - observer.detach() - headSerialDisposable.dispose() - } - } -} - -/// A descriptor of the attributes and type information of a property in -/// Objective-C. -internal struct PropertyAttributes { - struct Code { - static let start = Int8(UInt8(ascii: "T")) - static let quote = Int8(UInt8(ascii: "\"")) - static let nul = Int8(UInt8(ascii: "\0")) - static let comma = Int8(UInt8(ascii: ",")) - - struct ContainingType { - static let object = Int8(UInt8(ascii: "@")) - static let block = Int8(UInt8(ascii: "?")) - } - - struct Attribute { - static let readonly = Int8(UInt8(ascii: "R")) - static let copy = Int8(UInt8(ascii: "C")) - static let retain = Int8(UInt8(ascii: "&")) - static let nonatomic = Int8(UInt8(ascii: "N")) - static let getter = Int8(UInt8(ascii: "G")) - static let setter = Int8(UInt8(ascii: "S")) - static let dynamic = Int8(UInt8(ascii: "D")) - static let ivar = Int8(UInt8(ascii: "V")) - static let weak = Int8(UInt8(ascii: "W")) - static let collectable = Int8(UInt8(ascii: "P")) - static let oldTypeEncoding = Int8(UInt8(ascii: "t")) - } - } - - /// The class of the property. - let objectClass: AnyClass? - - /// Indicate whether the property is a weak reference. - let isWeak: Bool - - /// Indicate whether the property is an object. - let isObject: Bool - - /// Indicate whether the property is a block. - let isBlock: Bool - - init(property: objc_property_t) { - guard let attrString = property_getAttributes(property) else { - preconditionFailure("Could not get attribute string from property.") - } - - precondition(attrString[0] == Code.start, "Expected attribute string to start with 'T'.") - - let typeString = attrString + 1 - - let _next = NSGetSizeAndAlignment(typeString, nil, nil) - guard _next != typeString else { - let string = String(validatingUTF8: attrString) - preconditionFailure("Could not read past type in attribute string: \(string).") - } - var next = UnsafeMutablePointer(mutating: _next) - - let typeLength = typeString.distance(to: next) - precondition(typeLength > 0, "Invalid type in attribute string.") - - var objectClass: AnyClass? = nil - - // if this is an object type, and immediately followed by a quoted string... - if typeString[0] == Code.ContainingType.object && typeString[1] == Code.quote { - // we should be able to extract a class name - let className = typeString + 2; - - // fast forward the `next` pointer. - guard let endQuote = strchr(className, Int32(Code.quote)) else { - preconditionFailure("Could not read class name in attribute string.") - } - next = endQuote - - if className != UnsafePointer(next) { - let length = className.distance(to: next) - let name = UnsafeMutablePointer.allocate(capacity: length + 1) - name.initialize(from: UnsafeMutablePointer(mutating: className), count: length) - (name + length).initialize(to: Code.nul) - - // attempt to look up the class in the runtime - objectClass = objc_getClass(name) as! AnyClass? - - name.deinitialize(count: length + 1) - name.deallocate(capacity: length + 1) - } - } - - if next.pointee != Code.nul { - // skip past any junk before the first flag - next = strchr(next, Int32(Code.comma)) - } - - let emptyString = UnsafeMutablePointer.allocate(capacity: 1) - emptyString.initialize(to: Code.nul) - defer { - emptyString.deinitialize() - emptyString.deallocate(capacity: 1) - } - - var isWeak = false - - while next.pointee == Code.comma { - let flag = next[1] - next += 2 - - switch flag { - case Code.nul: - break; - - case Code.Attribute.readonly: - break; - - case Code.Attribute.copy: - break; - - case Code.Attribute.retain: - break; - - case Code.Attribute.nonatomic: - break; - - case Code.Attribute.getter: - fallthrough - - case Code.Attribute.setter: - next = strchr(next, Int32(Code.comma)) ?? emptyString - - case Code.Attribute.dynamic: - break - - case Code.Attribute.ivar: - // assume that the rest of the string (if present) is the ivar name - if next.pointee != Code.nul { - next = emptyString - } - - case Code.Attribute.weak: - isWeak = true - - case Code.Attribute.collectable: - break - - case Code.Attribute.oldTypeEncoding: - let string = String(validatingUTF8: attrString) - assertionFailure("Old-style type encoding is unsupported in attribute string \"\(string)\"") - - // skip over this type encoding - while next.pointee != Code.comma && next.pointee != Code.nul { - next += 1 - } - - default: - let pointer = UnsafeMutablePointer.allocate(capacity: 2) - pointer.initialize(to: flag) - (pointer + 1).initialize(to: Code.nul) - - let flag = String(validatingUTF8: pointer) - let string = String(validatingUTF8: attrString) - preconditionFailure("ERROR: Unrecognized attribute string flag '\(flag)' in attribute string \"\(string)\".") - } - } - - if next.pointee != Code.nul { - let unparsedData = String(validatingUTF8: next) - let string = String(validatingUTF8: attrString) - assertionFailure("Warning: Unparsed data \"\(unparsedData)\" in attribute string \"\(string)\".") - } - - self.objectClass = objectClass - self.isWeak = isWeak - self.isObject = typeString[0] == Code.ContainingType.object - self.isBlock = isObject && typeString[1] == Code.ContainingType.block - } -} diff --git a/ReactiveCocoa/Swift/ObjectiveCBridging.swift b/ReactiveCocoa/Swift/ObjectiveCBridging.swift deleted file mode 100644 index c8a893d836..0000000000 --- a/ReactiveCocoa/Swift/ObjectiveCBridging.swift +++ /dev/null @@ -1,281 +0,0 @@ -// -// ObjectiveCBridging.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-07-02. -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// - -import Result - -extension RACDisposable: Disposable {} -extension RACScheduler: DateSchedulerProtocol { - /// The current date, as determined by this scheduler. - public var currentDate: Date { - return Date() - } - - /// Schedule an action for immediate execution. - /// - /// - note: This method calls the Objective-C implementation of `schedule:` - /// method. - /// - /// - parameters: - /// - action: Closure to perform. - /// - /// - returns: Disposable that can be used to cancel the work before it - /// begins. - @discardableResult - public func schedule(_ action: @escaping () -> Void) -> Disposable? { - let disposable: RACDisposable = self.schedule(action) // Call the Objective-C implementation - return disposable as Disposable? - } - - /// Schedule an action for execution at or after the given date. - /// - /// - parameters: - /// - date: Starting date. - /// - action: Closure to perform. - /// - /// - returns: Optional disposable that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(after date: Date, action: @escaping () -> Void) -> Disposable? { - return self.after(date, schedule: action) - } - - /// Schedule a recurring action at the given interval, beginning at the - /// given start time. - /// - /// - parameters: - /// - date: Starting date. - /// - repeatingEvery: Repetition interval. - /// - withLeeway: Some delta for repetition. - /// - action: Closure of the action to perform. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(after date: Date, interval: TimeInterval, leeway: TimeInterval, action: @escaping () -> Void) -> Disposable? { - return self.after(date, repeatingEvery: interval, withLeeway: leeway, schedule: action) - } -} - -extension ImmediateScheduler { - /// Create `RACScheduler` that performs actions instantly. - /// - /// - returns: `RACScheduler` that instantly performs actions. - public func toRACScheduler() -> RACScheduler { - return RACScheduler.immediate() - } -} - -extension UIScheduler { - /// Create `RACScheduler` for `UIScheduler` - /// - /// - returns: `RACScheduler` instance that queues events on main thread. - public func toRACScheduler() -> RACScheduler { - return RACScheduler.mainThread() - } -} - -extension QueueScheduler { - /// Create `RACScheduler` backed with own queue - /// - /// - returns: Instance `RACScheduler` that queues events on - /// `QueueScheduler`'s queue. - public func toRACScheduler() -> RACScheduler { - return RACTargetQueueScheduler(name: "org.reactivecocoa.ReactiveCocoa.QueueScheduler.toRACScheduler()", targetQueue: queue) - } -} - -private func defaultNSError(_ message: String, file: String, line: Int) -> NSError { - return Result<(), NSError>.error(message, file: file, line: line) -} - -extension RACSignal { - /// Create a `SignalProducer` which will subscribe to the receiver once for - /// each invocation of `start()`. - /// - /// - parameters: - /// - file: Current file name. - /// - line: Current line in file. - /// - /// - returns: Signal producer created from `self`. - public func toSignalProducer(file: String = #file, line: Int = #line) -> SignalProducer { - return SignalProducer { observer, disposable in - let next = { obj in - observer.sendNext(obj) - } - - let failed: (_ nsError: Swift.Error?) -> () = { - observer.sendFailed(($0 as? NSError) ?? defaultNSError("Nil RACSignal error", file: file, line: line)) - } - - let completed = { - observer.sendCompleted() - } - - disposable += self.subscribeNext(next, error: failed, completed: completed) - } - } -} - -extension SignalProducerProtocol { - /// Create a `RACSignal` that will `start()` the producer once for each - /// subscription. - /// - /// - note: Any `interrupted` events will be silently discarded. - /// - /// - returns: `RACSignal` instantiated from `self`. - public func toRACSignal() -> RACSignal { - return RACSignal.createSignal { subscriber in - let selfDisposable = self.start { event in - switch event { - case let .next(value): - subscriber.sendNext(value) - case let .failed(error): - subscriber.sendError(error) - case .completed: - subscriber.sendCompleted() - case .interrupted: - break - } - } - - return RACDisposable { - selfDisposable.dispose() - } - } - } -} - -extension SignalProtocol { - /// Create a `RACSignal` that will observe the given signal. - /// - /// - note: Any `interrupted` events will be silently discarded. - /// - /// - returns: `RACSignal` instantiated from `self`. - public func toRACSignal() -> RACSignal { - return RACSignal.createSignal { subscriber in - let selfDisposable = self.observe { event in - switch event { - case let .next(value): - subscriber.sendNext(value) - case let .failed(error): - subscriber.sendError(error) - case .completed: - subscriber.sendCompleted() - case .interrupted: - break - } - } - - return RACDisposable { - selfDisposable?.dispose() - } - } - } -} - -// MARK: - - -// FIXME: Reintroduce `RACCommand.toAction` when compiler no longer segfault -// on extensions to parameterized ObjC classes. -/** -extension RACCommand { - /// Creates an Action that will execute the receiver. - /// - /// - note: The returned Action will not necessarily be marked as executing - /// when the command is. However, the reverse is always true: the - /// RACCommand will always be marked as executing when the action - /// is. - /// - /// - parameters: - /// - file: Current file name. - /// - line: Current line in file. - /// - /// - returns: Action created from `self`. - public func toAction(file: String = #file, line: Int = #line) -> Action { - let enabledProperty = MutableProperty(true) - - enabledProperty <~ self.enabled.toSignalProducer() - .map { $0 as! Bool } - .flatMapError { _ in SignalProducer(value: false) } - - return Action(enabledIf: enabledProperty) { input -> SignalProducer in - let executionSignal = RACSignal.`defer` { - return self.execute(input) - } -**/ - -extension ActionProtocol { - fileprivate var isCommandEnabled: RACSignal { - return self.isEnabled.producer - .map { $0 as NSNumber } - .toRACSignal() - } -} - -/// Creates an Action that will execute the receiver. -/// -/// - note: The returned Action will not necessarily be marked as executing -/// when the command is. However, the reverse is always true: the -/// RACCommand will always be marked as executing when the action -/// is. -/// -/// - parameters: -/// - file: Current file name. -/// - line: Current line in file. -/// -/// - returns: Action created from `self`. -public func bridgedAction(from command: RACCommand, file: String = #file, line: Int = #line) -> Action { - let command = command as! RACCommand - let enabledProperty = MutableProperty(true) - - enabledProperty <~ command.enabled.toSignalProducer() - .map { $0 as! Bool } - .flatMapError { _ in SignalProducer(value: false) } - - return Action(enabledIf: enabledProperty) { input -> SignalProducer in - let executionSignal = RACSignal.`defer` { - return command.execute(input as AnyObject?) - } - - return executionSignal.toSignalProducer(file: file, line: line) - } -} - -extension ActionProtocol where Input: AnyObject { - /// Creates a RACCommand that will execute the action. - /// - /// - note: The returned command will not necessarily be marked as executing - /// when the action is. However, the reverse is always true: the Action - /// will always be marked as executing when the RACCommand is. - /// - /// - returns: `RACCommand` with bound action. - public func toRACCommand() -> RACCommand { - return RACCommand(enabled: action.isCommandEnabled) { input -> RACSignal in - return self - .apply(input!) - .toRACSignal() - } - } -} - -extension ActionProtocol where Input: OptionalProtocol, Input.Wrapped: AnyObject { - /// Creates a RACCommand that will execute the action. - /// - /// - note: The returned command will not necessarily be marked as executing - /// when the action is. However, the reverse is always true: the Action - /// will always be marked as executing when the RACCommand is. - /// - /// - returns: `RACCommand` with bound action. - public func toRACCommand() -> RACCommand { - return RACCommand(enabled: action.isCommandEnabled) { input -> RACSignal in - return self - .apply(Input(reconstructing: input)) - .toRACSignal() - } - } -} diff --git a/ReactiveCocoa/Swift/Observer.swift b/ReactiveCocoa/Swift/Observer.swift deleted file mode 100644 index a73d346f67..0000000000 --- a/ReactiveCocoa/Swift/Observer.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// Observer.swift -// ReactiveCocoa -// -// Created by Andy Matuschak on 10/2/15. -// Copyright © 2015 GitHub. All rights reserved. -// - -/// A protocol for type-constrained extensions of `Observer`. -public protocol ObserverProtocol { - associatedtype Value - associatedtype Error: Swift.Error - - /// Puts a `next` event into `self`. - func sendNext(_ value: Value) - - /// Puts a failed event into `self`. - func sendFailed(_ error: Error) - - /// Puts a `completed` event into `self`. - func sendCompleted() - - /// Puts an `interrupted` event into `self`. - func sendInterrupted() -} - -/// An Observer is a simple wrapper around a function which can receive Events -/// (typically from a Signal). -public final class Observer { - public typealias Action = @escaping (Event) -> Void - - /// An action that will be performed upon arrival of the event. - public let action: Action - - /// An initializer that accepts a closure accepting an event for the - /// observer. - /// - /// - parameters: - /// - action: A closure to lift over received event. - public init(_ action: Action) { - self.action = action - } - - /// An initializer that accepts closures for different event types. - /// - /// - parameters: - /// - next: Optional closure executed when a `next` event is observed. - /// - failed: Optional closure that accepts an `Error` parameter when a - /// failed event is observed. - /// - completed: Optional closure executed when a `completed` event is - /// observed. - /// - interruped: Optional closure executed when an `interrupted` event is - /// observed. - public convenience init( - next: ((Value) -> Void)? = nil, - failed: ((Error) -> Void)? = nil, - completed: (() -> Void)? = nil, - interrupted: (() -> Void)? = nil - ) { - self.init { event in - switch event { - case let .next(value): - next?(value) - - case let .failed(error): - failed?(error) - - case .completed: - completed?() - - case .interrupted: - interrupted?() - } - } - } -} - -extension Observer: ObserverProtocol { - /// Puts a `next` event into `self`. - /// - /// - parameters: - /// - value: A value sent with the `next` event. - public func sendNext(_ value: Value) { - action(.next(value)) - } - - /// Puts a failed event into `self`. - /// - /// - parameters: - /// - error: An error object sent with failed event. - public func sendFailed(_ error: Error) { - action(.failed(error)) - } - - /// Puts a `completed` event into `self`. - public func sendCompleted() { - action(.completed) - } - - /// Puts an `interrupted` event into `self`. - public func sendInterrupted() { - action(.interrupted) - } -} diff --git a/ReactiveCocoa/Swift/Optional.swift b/ReactiveCocoa/Swift/Optional.swift deleted file mode 100644 index a32a029014..0000000000 --- a/ReactiveCocoa/Swift/Optional.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Optional.swift -// ReactiveCocoa -// -// Created by Neil Pankey on 6/24/15. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -/// An optional protocol for use in type constraints. -public protocol OptionalProtocol { - /// The type contained in the otpional. - associatedtype Wrapped - - init(reconstructing value: Wrapped?) - - /// Extracts an optional from the receiver. - var optional: Wrapped? { get } -} - -extension Optional: OptionalProtocol { - public var optional: Wrapped? { - return self - } - - public init(reconstructing value: Wrapped?) { - self = value - } -} - -extension SignalProtocol { - /// Turns each value into an Optional. - internal func optionalize() -> Signal { - return map(Optional.init) - } -} - -extension SignalProducerProtocol { - /// Turns each value into an Optional. - internal func optionalize() -> SignalProducer { - return lift { $0.optionalize() } - } -} diff --git a/ReactiveCocoa/Swift/Property.swift b/ReactiveCocoa/Swift/Property.swift deleted file mode 100644 index 8a7ba674ea..0000000000 --- a/ReactiveCocoa/Swift/Property.swift +++ /dev/null @@ -1,643 +0,0 @@ -import Foundation -import enum Result.NoError - -/// Represents a property that allows observation of its changes. -/// -/// Only classes can conform to this protocol, because having a signal -/// for changes over time implies the origin must have a unique identity. -public protocol PropertyProtocol: class { - associatedtype Value - - /// The current value of the property. - var value: Value { get } - - /// The values producer of the property. - /// - /// It produces a signal that sends the property's current value, - /// followed by all changes over time. It completes when the property - /// has deinitialized, or has no further change. - var producer: SignalProducer { get } - - /// A signal that will send the property's changes over time. It - /// completes when the property has deinitialized, or has no further - /// change. - var signal: Signal { get } -} - -/// Represents an observable property that can be mutated directly. -public protocol MutablePropertyProtocol: PropertyProtocol, BindingTarget { - /// The current value of the property. - var value: Value { get set } -} - -/// Default implementation of `MutablePropertyProtocol` for `BindingTarget`. -extension MutablePropertyProtocol { - public func consume(_ value: Value) { - self.value = value - } -} - -/// Protocol composition operators -/// -/// The producer and the signal of transformed properties would complete -/// only when its source properties have deinitialized. -/// -/// A composed property would retain its ultimate source, but not -/// any intermediate property during the composition. -extension PropertyProtocol { - /// Lifts a unary SignalProducer operator to operate upon PropertyProtocol instead. - fileprivate func lift(_ transform: @escaping (SignalProducer) -> SignalProducer) -> Property { - return Property(self, transform: transform) - } - - /// Lifts a binary SignalProducer operator to operate upon PropertyProtocol instead. - fileprivate func lift(_ transform: @escaping (SignalProducer) -> (SignalProducer) -> SignalProducer) -> (P) -> Property { - return { otherProperty in - return Property(self, otherProperty, transform: transform) - } - } - - /// Maps the current value and all subsequent values to a new property. - /// - /// - parameters: - /// - transform: A closure that will map the current `value` of this - /// `Property` to a new value. - /// - /// - returns: A new instance of `AnyProperty` who's holds a mapped value - /// from `self`. - public func map(_ transform: @escaping (Value) -> U) -> Property { - return lift { $0.map(transform) } - } - - /// Combines the current value and the subsequent values of two `Property`s in - /// the manner described by `Signal.combineLatestWith:`. - /// - /// - parameters: - /// - other: A property to combine `self`'s value with. - /// - /// - returns: A property that holds a tuple containing values of `self` and - /// the given property. - public func combineLatest(with other: P) -> Property<(Value, P.Value)> { - return lift(SignalProducer.combineLatest(with:))(other) - } - - /// Zips the current value and the subsequent values of two `Property`s in - /// the manner described by `Signal.zipWith`. - /// - /// - parameters: - /// - other: A property to zip `self`'s value with. - /// - /// - returns: A property that holds a tuple containing values of `self` and - /// the given property. - public func zip(with other: P) -> Property<(Value, P.Value)> { - return lift(SignalProducer.zip(with:))(other) - } - - /// Forward events from `self` with history: values of the returned property - /// are a tuple whose first member is the previous value and whose second - /// member is the current value. `initial` is supplied as the first member - /// when `self` sends its first value. - /// - /// - parameters: - /// - initial: A value that will be combined with the first value sent by - /// `self`. - /// - /// - returns: A property that holds tuples that contain previous and - /// current values of `self`. - public func combinePrevious(_ initial: Value) -> Property<(Value, Value)> { - return lift { $0.combinePrevious(initial) } - } - - /// Forward only those values from `self` which do not pass `isRepeat` with - /// respect to the previous value. - /// - /// - parameters: - /// - isRepeat: A predicate to determine if the two given values are equal. - /// - /// - returns: A property that does not emit events for two equal values - /// sequentially. - public func skipRepeats(_ isRepeat: @escaping (Value, Value) -> Bool) -> Property { - return lift { $0.skipRepeats(isRepeat) } - } -} - -extension PropertyProtocol where Value: Equatable { - /// Forward only those values from `self` which do not pass `isRepeat` with - /// respect to the previous value. - /// - /// - returns: A property that does not emit events for two equal values - /// sequentially. - public func skipRepeats() -> Property { - return lift { $0.skipRepeats() } - } -} - -extension PropertyProtocol where Value: PropertyProtocol { - /// Flattens the inner property held by `self` (into a single property of - /// values), according to the semantics of the given strategy. - /// - /// - parameters: - /// - strategy: The preferred flatten strategy. - /// - /// - returns: A property that sends the values of its inner properties. - public func flatten(_ strategy: FlattenStrategy) -> Property { - return lift { $0.flatMap(strategy) { $0.producer } } - } -} - -extension PropertyProtocol { - /// Maps each property from `self` to a new property, then flattens the - /// resulting properties (into a single property), according to the - /// semantics of the given strategy. - /// - /// - parameters: - /// - strategy: The preferred flatten strategy. - /// - transform: The transform to be applied on `self` before flattening. - /// - /// - returns: A property that sends the values of its inner properties. - public func flatMap(_ strategy: FlattenStrategy, transform: @escaping (Value) -> P) -> Property { - return lift { $0.flatMap(strategy) { transform($0).producer } } - } - - /// Forward only those values from `self` that have unique identities across - /// the set of all values that have been held. - /// - /// - note: This causes the identities to be retained to check for - /// uniqueness. - /// - /// - parameters: - /// - transform: A closure that accepts a value and returns identity - /// value. - /// - /// - returns: A property that sends unique values during its lifetime. - public func uniqueValues(_ transform: @escaping (Value) -> Identity) -> Property { - return lift { $0.uniqueValues(transform) } - } -} - -extension PropertyProtocol where Value: Hashable { - /// Forwards only those values from `self` that are unique across the set of - /// all values that have been seen. - /// - /// - note: This causes the identities to be retained to check for uniqueness. - /// Providing a function that returns a unique value for each sent - /// value can help you reduce the memory footprint. - /// - /// - returns: A property that sends unique values during its lifetime. - public func uniqueValues() -> Property { - return lift { $0.uniqueValues() } - } -} - -extension PropertyProtocol { - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B) -> Property<(A.Value, B.Value)> where Value == A.Value { - return a.combineLatest(with: b) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C) -> Property<(A.Value, B.Value, C.Value)> where Value == A.Value { - return combineLatest(a, b) - .combineLatest(with: c) - .map(repack) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C, _ d: D) -> Property<(A.Value, B.Value, C.Value, D.Value)> where Value == A.Value { - return combineLatest(a, b, c) - .combineLatest(with: d) - .map(repack) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value)> where Value == A.Value { - return combineLatest(a, b, c, d) - .combineLatest(with: e) - .map(repack) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value)> where Value == A.Value { - return combineLatest(a, b, c, d, e) - .combineLatest(with: f) - .map(repack) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value)> where Value == A.Value { - return combineLatest(a, b, c, d, e, f) - .combineLatest(with: g) - .map(repack) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value)> where Value == A.Value { - return combineLatest(a, b, c, d, e, f, g) - .combineLatest(with: h) - .map(repack) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value)> where Value == A.Value { - return combineLatest(a, b, c, d, e, f, g, h) - .combineLatest(with: i) - .map(repack) - } - - /// Combines the values of all the given properties, in the manner described - /// by `combineLatest(with:)`. - public static func combineLatest(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value, J.Value)> where Value == A.Value { - return combineLatest(a, b, c, d, e, f, g, h, i) - .combineLatest(with: j) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatest(with:)`. Returns nil if the sequence is empty. - public static func combineLatest(_ properties: S) -> Property<[S.Iterator.Element.Value]>? where S.Iterator.Element: PropertyProtocol { - var generator = properties.makeIterator() - if let first = generator.next() { - let initial = first.map { [$0] } - return IteratorSequence(generator).reduce(initial) { property, next in - property.combineLatest(with: next).map { $0.0 + [$0.1] } - } - } - - return nil - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B) -> Property<(A.Value, B.Value)> where Value == A.Value { - return a.zip(with: b) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C) -> Property<(A.Value, B.Value, C.Value)> where Value == A.Value { - return zip(a, b) - .zip(with: c) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C, _ d: D) -> Property<(A.Value, B.Value, C.Value, D.Value)> where Value == A.Value { - return zip(a, b, c) - .zip(with: d) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value)> where Value == A.Value { - return zip(a, b, c, d) - .zip(with: e) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value)> where Value == A.Value { - return zip(a, b, c, d, e) - .zip(with: f) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value)> where Value == A.Value { - return zip(a, b, c, d, e, f) - .zip(with: g) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value)> where Value == A.Value { - return zip(a, b, c, d, e, f, g) - .zip(with: h) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value)> where Value == A.Value { - return zip(a, b, c, d, e, f, g, h) - .zip(with: i) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. - public static func zip(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) -> Property<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value, J.Value)> where Value == A.Value { - return zip(a, b, c, d, e, f, g, h, i) - .zip(with: j) - .map(repack) - } - - /// Zips the values of all the given properties, in the manner described by - /// `zip(with:)`. Returns nil if the sequence is empty. - public static func zip(_ properties: S) -> Property<[S.Iterator.Element.Value]>? where S.Iterator.Element: PropertyProtocol { - var generator = properties.makeIterator() - if let first = generator.next() { - let initial = first.map { [$0] } - return IteratorSequence(generator).reduce(initial) { property, next in - property.zip(with: next).map { $0.0 + [$0.1] } - } - } - - return nil - } -} - -/// A read-only property that can be observed for its changes over time. There are -/// three categories of read-only property: -/// -/// # Constant property -/// Created by `Property(value:)`, the producer and signal of a constant -/// property would complete immediately when it is initialized. -/// -/// # Existential property -/// Created by `Property(_:)`, an existential property passes through the -/// behavior of the wrapped property. -/// -/// # Composed property -/// Created by either the compositional operators in `PropertyProtocol`, or -/// `Property(initial:followingBy:)`, a composed property presents a -/// composed view of its source, which can be a set of properties, -/// a producer, or a signal. -/// -/// A composed property respects the lifetime of its source rather than its own. -/// In other words, its producer and signal can outlive the property itself, if -/// its source outlives it too. -public final class Property: PropertyProtocol { - private let sources: [AnyObject] - private let disposable: Disposable? - - private let _value: () -> Value - private let _producer: () -> SignalProducer - private let _signal: () -> Signal - - /// The current value of the property. - public var value: Value { - return _value() - } - - /// A producer for Signals that will send the property's current - /// value, followed by all changes over time, then complete when the - /// property has deinitialized or has no further changes. - public var producer: SignalProducer { - return _producer() - } - - /// A signal that will send the property's changes over time, then - /// complete when the property has deinitialized or has no further changes. - public var signal: Signal { - return _signal() - } - - /// Initializes a constant property. - /// - /// - parameters: - /// - property: A value of the constant property. - public init(value: Value) { - sources = [] - disposable = nil - _value = { value } - _producer = { SignalProducer(value: value) } - _signal = { Signal.empty } - } - - /// Initializes an existential property which wraps the given property. - /// - /// - parameters: - /// - property: A property to be wrapped. - public init(_ property: P) where P.Value == Value { - sources = Property.capture(property) - disposable = nil - _value = { property.value } - _producer = { property.producer } - _signal = { property.signal } - } - - /// Initializes a composed property that first takes on `initial`, then each - /// value sent on a signal created by `producer`. - /// - /// - parameters: - /// - initial: Starting value for the property. - /// - producer: A producer that will start immediately and send values to - /// the property. - public convenience init(initial: Value, then producer: SignalProducer) { - self.init(unsafeProducer: producer.prefix(value: initial), - capturing: []) - } - - /// Initialize a composed property that first takes on `initial`, then each - /// value sent on `signal`. - /// - /// - parameters: - /// - initialValue: Starting value for the property. - /// - signal: A signal that will send values to the property. - public convenience init(initial: Value, then signal: Signal) { - self.init(unsafeProducer: SignalProducer(signal: signal).prefix(value: initial), - capturing: []) - } - - /// Initialize a composed property by applying the unary `SignalProducer` - /// transform on `property`. - /// - /// - parameters: - /// - property: The source property. - /// - transform: A unary `SignalProducer` transform to be applied on - /// `property`. - fileprivate convenience init( - _ property: P, - transform: @escaping (SignalProducer) -> SignalProducer - ) { - self.init( - unsafeProducer: transform(property.producer), - capturing: Property.capture(property) - ) - } - - /// Initialize a composed property by applying the binary `SignalProducer` - /// transform on `firstProperty` and `secondProperty`. - /// - /// - parameters: - /// - firstProperty: The first source property. - /// - secondProperty: The first source property. - /// - transform: A binary `SignalProducer` transform to be applied on - /// `firstProperty` and `secondProperty`. - fileprivate convenience init(_ firstProperty: P1, _ secondProperty: P2, transform: @escaping (SignalProducer) -> (SignalProducer) -> SignalProducer) { - self.init(unsafeProducer: transform(firstProperty.producer)(secondProperty.producer), - capturing: Property.capture(firstProperty) + Property.capture(secondProperty)) - } - - /// Initialize a composed property from a producer that promises to send - /// at least one value synchronously in its start handler before sending any - /// subsequent event. - /// - /// - important: The producer and the signal of the created property would - /// complete only when the `unsafeProducer` completes. - /// - /// - warning: If the producer fails its promise, a fatal error would be - /// raised. - /// - /// - parameters: - /// - unsafeProducer: The composed producer for creating the property. - /// - sources: The property sources to be captured. - private init(unsafeProducer: SignalProducer, capturing sources: [AnyObject]) { - // Share a replayed producer with `self.producer` and `self.signal` so - // they see a consistent view of the `self.value`. - // https://github.com/ReactiveCocoa/ReactiveCocoa/pull/3042 - let producer = unsafeProducer.replayLazily(upTo: 1) - - let atomic = Atomic(nil) - disposable = producer.startWithNext { atomic.value = $0 } - - // Verify that an initial is sent. This is friendlier than deadlocking - // in the event that one isn't. - guard atomic.value != nil else { - fatalError("A producer promised to send at least one value. Received none.") - } - - self.sources = sources - _value = { atomic.value! } - _producer = { producer } - _signal = { - var extractedSignal: Signal! - producer.startWithSignal { signal, _ in extractedSignal = signal } - return extractedSignal - } - } - - deinit { - disposable?.dispose() - } - - /// Inspect if `property` is an `AnyProperty` and has already captured its - /// sources using a closure. Returns that closure if it does. Otherwise, - /// returns a closure which captures `property`. - /// - /// - parameters: - /// - property: The property to be insepcted. - private static func capture(_ property: P) -> [AnyObject] { - if let property = property as? Property { - return property.sources - } else { - return [property] - } - } -} - -/// A mutable property of type `Value` that allows observation of its changes. -/// -/// Instances of this class are thread-safe. -public final class MutableProperty: MutablePropertyProtocol { - private let token: Lifetime.Token - private let observer: Signal.Observer - private let atomic: RecursiveAtomic - - /// The current value of the property. - /// - /// Setting this to a new value will notify all observers of `signal`, or - /// signals created using `producer`. - public var value: Value { - get { - return atomic.withValue { $0 } - } - - set { - swap(newValue) - } - } - - /// The lifetime of the property. - public let lifetime: Lifetime - - /// A signal that will send the property's changes over time, - /// then complete when the property has deinitialized. - public let signal: Signal - - /// A producer for Signals that will send the property's current value, - /// followed by all changes over time, then complete when the property has - /// deinitialized. - public var producer: SignalProducer { - return SignalProducer { [atomic, weak self] producerObserver, producerDisposable in - atomic.withValue { value in - if let strongSelf = self { - producerObserver.sendNext(value) - producerDisposable += strongSelf.signal.observe(producerObserver) - } else { - producerObserver.sendNext(value) - producerObserver.sendCompleted() - } - } - } - } - - /// Initializes a mutable property that first takes on `initialValue` - /// - /// - parameters: - /// - initialValue: Starting value for the mutable property. - public init(_ initialValue: Value) { - (signal, observer) = Signal.pipe() - token = Lifetime.Token() - lifetime = Lifetime(token) - - /// Need a recursive lock around `value` to allow recursive access to - /// `value`. Note that recursive sets will still deadlock because the - /// underlying producer prevents sending recursive events. - atomic = RecursiveAtomic(initialValue, - name: "org.reactivecocoa.ReactiveCocoa.MutableProperty", - didSet: observer.sendNext) - } - - /// Atomically replaces the contents of the variable. - /// - /// - parameters: - /// - newValue: New property value. - /// - /// - returns: The previous property value. - @discardableResult - public func swap(_ newValue: Value) -> Value { - return atomic.swap(newValue) - } - - /// Atomically modifies the variable. - /// - /// - parameters: - /// - action: A closure that accepts old property value and returns a new - /// property value. - /// - /// - returns: The result of the action. - @discardableResult - public func modify(_ action: (inout Value) throws -> Result) rethrows -> Result { - return try atomic.modify(action) - } - - /// Atomically performs an arbitrary action using the current value of the - /// variable. - /// - /// - parameters: - /// - action: A closure that accepts current property value. - /// - /// - returns: the result of the action. - @discardableResult - public func withValue(action: (Value) throws -> Result) rethrows -> Result { - return try atomic.withValue(action) - } - - deinit { - observer.sendCompleted() - } -} diff --git a/ReactiveCocoa/Swift/Scheduler.swift b/ReactiveCocoa/Swift/Scheduler.swift deleted file mode 100644 index 0b19ab6560..0000000000 --- a/ReactiveCocoa/Swift/Scheduler.swift +++ /dev/null @@ -1,493 +0,0 @@ -// -// Scheduler.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-06-02. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Foundation - -/// Represents a serial queue of work items. -public protocol SchedulerProtocol { - /// Enqueues an action on the scheduler. - /// - /// When the work is executed depends on the scheduler in use. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - func schedule(_ action: @escaping () -> Void) -> Disposable? -} - -/// A particular kind of scheduler that supports enqueuing actions at future -/// dates. -public protocol DateSchedulerProtocol: SchedulerProtocol { - /// The current date, as determined by this scheduler. - /// - /// This can be implemented to deterministically return a known date (e.g., - /// for testing purposes). - var currentDate: Date { get } - - /// Schedules an action for execution at or after the given date. - /// - /// - parameters: - /// - date: Starting time. - /// - action: Closure of the action to perform. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - func schedule(after date: Date, action: @escaping () -> Void) -> Disposable? - - /// Schedules a recurring action at the given interval, beginning at the - /// given date. - /// - /// - parameters: - /// - date: Starting time. - /// - repeatingEvery: Repetition interval. - /// - withLeeway: Some delta for repetition. - /// - action: Closure of the action to perform. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - func schedule(after date: Date, interval: TimeInterval, leeway: TimeInterval, action: @escaping () -> Void) -> Disposable? -} - -/// A scheduler that performs all work synchronously. -public final class ImmediateScheduler: SchedulerProtocol { - public init() {} - - /// Immediately calls passed in `action`. - /// - /// - parameters: - /// - action: Closure of the action to perform. - /// - /// - returns: `nil`. - @discardableResult - public func schedule(_ action: @escaping () -> Void) -> Disposable? { - action() - return nil - } -} - -/// A scheduler that performs all work on the main queue, as soon as possible. -/// -/// If the caller is already running on the main queue when an action is -/// scheduled, it may be run synchronously. However, ordering between actions -/// will always be preserved. -public final class UIScheduler: SchedulerProtocol { - private static let dispatchSpecificKey = DispatchSpecificKey() - private static let dispatchSpecificValue = UInt8.max - private static var __once: () = { - DispatchQueue.main.setSpecific(key: UIScheduler.dispatchSpecificKey, - value: dispatchSpecificValue) - }() - - private var queueLength: Int32 = 0 - - /// Initializes `UIScheduler` - public init() { - /// This call is to ensure the main queue has been setup appropriately - /// for `UIScheduler`. It is only called once during the application - /// lifetime, since Swift has a `dispatch_once` like mechanism to - /// lazily initialize global variables and static variables. - _ = UIScheduler.__once - } - - /// Queues an action to be performed on main queue. If the action is called - /// on the main thread and no work is queued, no scheduling takes place and - /// the action is called instantly. - /// - /// - parameters: - /// - action: Closure of the action to perform on the main thread. - /// - /// - returns: `Disposable` that can be used to cancel the work before it - /// begins. - @discardableResult - public func schedule(_ action: @escaping () -> Void) -> Disposable? { - let disposable = SimpleDisposable() - let actionAndDecrement = { - if !disposable.isDisposed { - action() - } - - OSAtomicDecrement32(&self.queueLength) - } - - let queued = OSAtomicIncrement32(&queueLength) - - // If we're already running on the main queue, and there isn't work - // already enqueued, we can skip scheduling and just execute directly. - if queued == 1 && DispatchQueue.getSpecific(key: UIScheduler.dispatchSpecificKey) == UIScheduler.dispatchSpecificValue { - actionAndDecrement() - } else { - DispatchQueue.main.async(execute: actionAndDecrement) - } - - return disposable - } -} - -/// A scheduler backed by a serial GCD queue. -public final class QueueScheduler: DateSchedulerProtocol { - /// A singleton `QueueScheduler` that always targets the main thread's GCD - /// queue. - /// - /// - note: Unlike `UIScheduler`, this scheduler supports scheduling for a - /// future date, and will always schedule asynchronously (even if - /// already running on the main thread). - public static let main = QueueScheduler(internalQueue: DispatchQueue.main) - - public var currentDate: Date { - return Date() - } - - internal let queue: DispatchQueue - - internal init(internalQueue: DispatchQueue) { - queue = internalQueue - } - - /// Initializes a scheduler that will target the given queue with its - /// work. - /// - /// - note: Even if the queue is concurrent, all work items enqueued with - /// the `QueueScheduler` will be serial with respect to each other. - /// - /// - warning: Obsoleted in OS X 10.11 - @available(OSX, deprecated:10.10, obsoleted:10.11, message:"Use init(qos:, name:) instead") - @available(iOS, deprecated:8.0, obsoleted:9.0, message:"Use init(qos:, name:) instead.") - public convenience init(queue: DispatchQueue, name: String = "org.reactivecocoa.ReactiveCocoa.QueueScheduler") { - self.init(internalQueue: DispatchQueue(label: name, attributes: [], target: queue)) - } - - /// Initializes a scheduler that will target a new serial queue with the - /// given quality of service class. - /// - /// - parameters: - /// - qos: Dispatch queue's QoS value. - /// - name: Name for the queue in the form of reverse domain. - @available(OSX 10.10, *) - public convenience init( - qos: DispatchQoS = .default, - name: String = "org.reactivecocoa.ReactiveCocoa.QueueScheduler" - ) { - self.init(internalQueue: DispatchQueue( - label: name, - qos: qos - )) - } - - /// Schedules action for dispatch on internal queue - /// - /// - parameters: - /// - action: Closure of the action to schedule. - /// - /// - returns: `Disposable` that can be used to cancel the work before it - /// begins. - @discardableResult - public func schedule(_ action: @escaping () -> Void) -> Disposable? { - let d = SimpleDisposable() - - queue.async { - if !d.isDisposed { - action() - } - } - - return d - } - - private func wallTime(with date: Date) -> DispatchWallTime { - let (seconds, frac) = modf(date.timeIntervalSince1970) - - let nsec: Double = frac * Double(NSEC_PER_SEC) - let walltime = timespec(tv_sec: Int(seconds), tv_nsec: Int(nsec)) - - return DispatchWallTime(timespec: walltime) - } - - /// Schedules an action for execution at or after the given date. - /// - /// - parameters: - /// - date: Starting time. - /// - action: Closure of the action to perform. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(after date: Date, action: @escaping () -> Void) -> Disposable? { - let d = SimpleDisposable() - - queue.asyncAfter(wallDeadline: wallTime(with: date)) { - if !d.isDisposed { - action() - } - } - - return d - } - - /// Schedules a recurring action at the given interval and beginning at the - /// given start time. A reasonable default timer interval leeway is - /// provided. - /// - /// - parameters: - /// - date: Date to schedule the first action for. - /// - repeatingEvery: Repetition interval. - /// - action: Closure of the action to repeat. - /// - /// - returns: Optional disposable that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(after date: Date, interval: TimeInterval, action: @escaping () -> Void) -> Disposable? { - // Apple's "Power Efficiency Guide for Mac Apps" recommends a leeway of - // at least 10% of the timer interval. - return schedule(after: date, interval: interval, leeway: interval * 0.1, action: action) - } - - /// Schedules a recurring action at the given interval with provided leeway, - /// beginning at the given start time. - /// - /// - parameters: - /// - date: Date to schedule the first action for. - /// - repeatingEvery: Repetition interval. - /// - leeway: Some delta for repetition interval. - /// - action: Closure of the action to repeat. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(after date: Date, interval: TimeInterval, leeway: TimeInterval, action: @escaping () -> Void) -> Disposable? { - precondition(interval >= 0) - precondition(leeway >= 0) - - let nsecInterval = interval * Double(NSEC_PER_SEC) - let nsecLeeway = leeway * Double(NSEC_PER_SEC) - - let timer = DispatchSource.makeTimerSource( - flags: DispatchSource.TimerFlags(rawValue: UInt(0)), - queue: queue - ) - timer.scheduleRepeating(wallDeadline: wallTime(with: date), - interval: .nanoseconds(Int(nsecInterval)), - leeway: .nanoseconds(Int(nsecLeeway))) - timer.setEventHandler(handler: action) - timer.resume() - - return ActionDisposable { - timer.cancel() - } - } -} - -/// A scheduler that implements virtualized time, for use in testing. -public final class TestScheduler: DateSchedulerProtocol { - private final class ScheduledAction { - let date: Date - let action: () -> Void - - init(date: Date, action: @escaping () -> Void) { - self.date = date - self.action = action - } - - func less(_ rhs: ScheduledAction) -> Bool { - return date.compare(rhs.date) == .orderedAscending - } - } - - private let lock = NSRecursiveLock() - private var _currentDate: Date - - /// The virtual date that the scheduler is currently at. - public var currentDate: Date { - let d: Date - - lock.lock() - d = _currentDate - lock.unlock() - - return d - } - - private var scheduledActions: [ScheduledAction] = [] - - /// Initializes a TestScheduler with the given start date. - /// - /// - parameters: - /// - startDate: The start date of the scheduler. - public init(startDate: Date = Date(timeIntervalSinceReferenceDate: 0)) { - lock.name = "org.reactivecocoa.ReactiveCocoa.TestScheduler" - _currentDate = startDate - } - - private func schedule(_ action: ScheduledAction) -> Disposable { - lock.lock() - scheduledActions.append(action) - scheduledActions.sort { $0.less($1) } - lock.unlock() - - return ActionDisposable { - self.lock.lock() - self.scheduledActions = self.scheduledActions.filter { $0 !== action } - self.lock.unlock() - } - } - - /// Enqueues an action on the scheduler. - /// - /// - note: The work is executed on `currentDate` as it is understood by the - /// scheduler. - /// - /// - parameters: - /// - action: An action that will be performed on scheduler's - /// `currentDate`. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(_ action: @escaping () -> Void) -> Disposable? { - return schedule(ScheduledAction(date: currentDate, action: action)) - } - - /// Schedules an action for execution at or after the given date. - /// - /// - parameters: - /// - date: Starting date. - /// - action: Closure of the action to perform. - /// - /// - returns: Optional disposable that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(after delay: TimeInterval, action: @escaping () -> Void) -> Disposable? { - return schedule(after: currentDate.addingTimeInterval(delay), action: action) - } - - @discardableResult - public func schedule(after date: Date, action: @escaping () -> Void) -> Disposable? { - return schedule(ScheduledAction(date: date, action: action)) - } - - /// Schedules a recurring action at the given interval, beginning at the - /// given start time - /// - /// - parameters: - /// - date: Date to schedule the first action for. - /// - repeatingEvery: Repetition interval. - /// - action: Closure of the action to repeat. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - private func schedule(after date: Date, interval: TimeInterval, disposable: SerialDisposable, action: @escaping () -> Void) { - precondition(interval >= 0) - - disposable.innerDisposable = schedule(after: date) { [unowned self] in - action() - self.schedule(after: date.addingTimeInterval(interval), interval: interval, disposable: disposable, action: action) - } - } - - /// Schedules a recurring action at the given interval, beginning at the - /// given interval (counted from `currentDate`). - /// - /// - parameters: - /// - interval: Interval to add to `currentDate`. - /// - repeatingEvery: Repetition interval. - /// - leeway: Some delta for repetition interval. - /// - action: Closure of the action to repeat. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - @discardableResult - public func schedule(after delay: TimeInterval, interval: TimeInterval, leeway: TimeInterval = 0, action: @escaping () -> Void) -> Disposable? { - return schedule(after: currentDate.addingTimeInterval(delay), interval: interval, leeway: leeway, action: action) - } - - /// Schedules a recurring action at the given interval with - /// provided leeway, beginning at the given start time. - /// - /// - parameters: - /// - date: Date to schedule the first action for. - /// - repeatingEvery: Repetition interval. - /// - leeway: Some delta for repetition interval. - /// - action: Closure of the action to repeat. - /// - /// - returns: Optional `Disposable` that can be used to cancel the work - /// before it begins. - public func schedule(after date: Date, interval: TimeInterval, leeway: TimeInterval = 0, action: @escaping () -> Void) -> Disposable? { - let disposable = SerialDisposable() - schedule(after: date, interval: interval, disposable: disposable, action: action) - return disposable - } - - /// Advances the virtualized clock by an extremely tiny interval, dequeuing - /// and executing any actions along the way. - /// - /// This is intended to be used as a way to execute actions that have been - /// scheduled to run as soon as possible. - public func advance() { - advance(by: DBL_EPSILON) - } - - /// Advances the virtualized clock by the given interval, dequeuing and - /// executing any actions along the way. - /// - /// - parameters: - /// - interval: Interval by which the current date will be advanced. - public func advance(by interval: TimeInterval) { - lock.lock() - advance(to: currentDate.addingTimeInterval(interval)) - lock.unlock() - } - - /// Advances the virtualized clock to the given future date, dequeuing and - /// executing any actions up until that point. - /// - /// - parameters: - /// - newDate: Future date to which the virtual clock will be advanced. - public func advance(to newDate: Date) { - lock.lock() - - assert(currentDate.compare(newDate) != .orderedDescending) - - while scheduledActions.count > 0 { - if newDate.compare(scheduledActions[0].date) == .orderedAscending { - break - } - - _currentDate = scheduledActions[0].date - - let scheduledAction = scheduledActions.remove(at: 0) - scheduledAction.action() - } - - _currentDate = newDate - - lock.unlock() - } - - /// Dequeues and executes all scheduled actions, leaving the scheduler's - /// date at `NSDate.distantFuture()`. - public func run() { - advance(to: Date.distantFuture) - } - - /// Rewinds the virtualized clock by the given interval. - /// This simulates that user changes device date. - /// - /// - parameters: - /// - interval: Interval by which the current date will be retreated. - public func rewind(by interval: TimeInterval) { - lock.lock() - - let newDate = currentDate.addingTimeInterval(-interval) - assert(currentDate.compare(newDate) != .orderedAscending) - _currentDate = newDate - - lock.unlock() - - } -} diff --git a/ReactiveCocoa/Swift/Signal.swift b/ReactiveCocoa/Swift/Signal.swift deleted file mode 100644 index 9c13cd1e8c..0000000000 --- a/ReactiveCocoa/Swift/Signal.swift +++ /dev/null @@ -1,1843 +0,0 @@ -import Foundation -import Result - -/// A push-driven stream that sends Events over time, parameterized by the type -/// of values being sent (`Value`) and the type of failure that can occur -/// (`Error`). If no failures should be possible, NoError can be specified for -/// `Error`. -/// -/// An observer of a Signal will see the exact same sequence of events as all -/// other observers. In other words, events will be sent to all observers at the -/// same time. -/// -/// Signals are generally used to represent event streams that are already “in -/// progress,” like notifications, user input, etc. To represent streams that -/// must first be _started_, see the SignalProducer type. -/// -/// A Signal is kept alive until either of the following happens: -/// 1. its input observer receives a terminating event; or -/// 2. it has no active observers, and is not being retained. -public final class Signal { - public typealias Observer = ReactiveCocoa.Observer - - /// The disposable returned by the signal generator. It would be disposed of - /// when the signal terminates. - private var generatorDisposable: Disposable? - - /// The state of the signal. `nil` if the signal has terminated. - private let state: Atomic?> - - /// Initialize a Signal that will immediately invoke the given generator, - /// then forward events sent to the given observer. - /// - /// - note: The disposable returned from the closure will be automatically - /// disposed if a terminating event is sent to the observer. The - /// Signal itself will remain alive until the observer is released. - /// - /// - parameters: - /// - generator: A closure that accepts an implicitly created observer - /// that will act as an event emitter for the signal. - public init(_ generator: (Observer) -> Disposable?) { - state = Atomic(SignalState()) - - /// Used to ensure that events are serialized during delivery to observers. - let sendLock = NSLock() - sendLock.name = "org.reactivecocoa.ReactiveCocoa.Signal" - - /// When set to `true`, the Signal should interrupt as soon as possible. - let interrupted = Atomic(false) - - let observer = Observer { [weak self] event in - guard let signal = self else { - return - } - - func interrupt() { - if let state = signal.state.swap(nil) { - for observer in state.observers { - observer.sendInterrupted() - } - } - } - - if case .interrupted = event { - // Normally we disallow recursive events, but `interrupted` is - // kind of a special snowflake, since it can inadvertently be - // sent by downstream consumers. - // - // So we'll flag Interrupted events specially, and if it - // happened to occur while we're sending something else, we'll - // wait to deliver it. - interrupted.value = true - - if sendLock.try() { - interrupt() - sendLock.unlock() - - signal.generatorDisposable?.dispose() - } - } else { - if let state = (event.isTerminating ? signal.state.swap(nil) : signal.state.value) { - sendLock.lock() - - for observer in state.observers { - observer.action(event) - } - - let shouldInterrupt = !event.isTerminating && interrupted.value - if shouldInterrupt { - interrupt() - } - - sendLock.unlock() - - if event.isTerminating || shouldInterrupt { - // Dispose only after notifying observers, so disposal - // logic is consistently the last thing to run. - signal.generatorDisposable?.dispose() - } - } - } - } - - generatorDisposable = generator(observer) - } - - deinit { - if state.swap(nil) != nil { - // As the signal can deinitialize only when it has no observers attached, - // only the generator disposable has to be disposed of at this point. - generatorDisposable?.dispose() - } - } - - /// A Signal that never sends any events to its observers. - public static var never: Signal { - return self.init { _ in nil } - } - - /// A Signal that completes immediately without emitting any value. - public static var empty: Signal { - return self.init { observer in - observer.sendCompleted() - return nil - } - } - - /// Create a Signal that will be controlled by sending events to the given - /// observer. - /// - /// - note: The Signal will remain alive until a terminating event is sent - /// to the observer. - /// - /// - returns: A tuple made of signal and observer. - public static func pipe() -> (Signal, Observer) { - var observer: Observer! - let signal = self.init { innerObserver in - observer = innerObserver - return nil - } - - return (signal, observer) - } - - /// Observe the Signal by sending any future events to the given observer. - /// - /// - note: If the Signal has already terminated, the observer will - /// immediately receive an `interrupted` event. - /// - /// - parameters: - /// - observer: An observer to forward the events to. - /// - /// - returns: An optional `Disposable` which can be used to disconnect the - /// observer. - @discardableResult - public func observe(_ observer: Observer) -> Disposable? { - var token: RemovalToken? - state.modify { - $0?.retainedSignal = self - token = $0?.observers.insert(observer) - } - - if let token = token { - return ActionDisposable { [weak self] in - if let strongSelf = self { - strongSelf.state.modify { state in - state?.observers.remove(using: token) - if state?.observers.isEmpty ?? false { - state!.retainedSignal = nil - } - } - } - } - } else { - observer.sendInterrupted() - return nil - } - } -} - -private struct SignalState { - var observers: Bag.Observer> = Bag() - var retainedSignal: Signal? -} - -public protocol SignalProtocol { - /// The type of values being sent on the signal. - associatedtype Value - - /// The type of error that can occur on the signal. If errors aren't - /// possible then `NoError` can be used. - associatedtype Error: Swift.Error - - /// Extracts a signal from the receiver. - var signal: Signal { get } - - /// Observes the Signal by sending any future events to the given observer. - @discardableResult - func observe(_ observer: Signal.Observer) -> Disposable? -} - -extension Signal: SignalProtocol { - public var signal: Signal { - return self - } -} - -extension SignalProtocol { - /// Convenience override for observe(_:) to allow trailing-closure style - /// invocations. - /// - /// - parameters: - /// - action: A closure that will accept an event of the signal - /// - /// - returns: An optional `Disposable` which can be used to stop the - /// invocation of the callback. Disposing of the Disposable will - /// have no effect on the Signal itself. - @discardableResult - public func observe(_ action: Signal.Observer.Action) -> Disposable? { - return observe(Observer(action)) - } - - /// Observe the `Signal` by invoking the given callback when `next` or - /// `failed` event are received. - /// - /// - parameters: - /// - result: A closure that accepts instance of `Result` - /// enum that contains either a `Success(Value)` or - /// `Failure` case. - /// - /// - returns: An optional `Disposable` which can be used to stop the - /// invocation of the callback. Disposing of the Disposable will - /// have no effect on the Signal itself. - @discardableResult - public func observeResult(_ result: @escaping (Result) -> Void) -> Disposable? { - return observe( - Observer( - next: { result(.success($0)) }, - failed: { result(.failure($0)) } - ) - ) - } - - /// Observe the `Signal` by invoking the given callback when a `completed` - /// event is received. - /// - /// - parameters: - /// - completed: A closure that is called when `completed` event is - /// received. - /// - /// - returns: An optional `Disposable` which can be used to stop the - /// invocation of the callback. Disposing of the Disposable will - /// have no effect on the Signal itself. - @discardableResult - public func observeCompleted(_ completed: @escaping () -> Void) -> Disposable? { - return observe(Observer(completed: completed)) - } - - /// Observe the `Signal` by invoking the given callback when a `failed` - /// event is received. - /// - /// - parameters: - /// - error: A closure that is called when failed event is received. It - /// accepts an error parameter. - /// - /// Returns a Disposable which can be used to stop the invocation of the - /// callback. Disposing of the Disposable will have no effect on the Signal - /// itself. - @discardableResult - public func observeFailed(_ error: @escaping (Error) -> Void) -> Disposable? { - return observe(Observer(failed: error)) - } - - /// Observe the `Signal` by invoking the given callback when an - /// `interrupted` event is received. If the Signal has already terminated, - /// the callback will be invoked immediately. - /// - /// - parameters: - /// - interrupted: A closure that is invoked when `interrupted` event is - /// received - /// - /// - returns: An optional `Disposable` which can be used to stop the - /// invocation of the callback. Disposing of the Disposable will - /// have no effect on the Signal itself. - @discardableResult - public func observeInterrupted(_ interrupted: @escaping () -> Void) -> Disposable? { - return observe(Observer(interrupted: interrupted)) - } -} - -extension SignalProtocol where Error == NoError { - /// Observe the Signal by invoking the given callback when `next` events are - /// received. - /// - /// - parameters: - /// - next: A closure that accepts a value when `next` event is received. - /// - /// - returns: An optional `Disposable` which can be used to stop the - /// invocation of the callback. Disposing of the Disposable will - /// have no effect on the Signal itself. - @discardableResult - public func observeNext(_ next: @escaping (Value) -> Void) -> Disposable? { - return observe(Observer(next: next)) - } -} - -extension SignalProtocol { - /// Map each value in the signal to a new value. - /// - /// - parameters: - /// - transform: A closure that accepts a value from the `next` event and - /// returns a new value. - /// - /// - returns: A signal that will send new values. - public func map(_ transform: @escaping (Value) -> U) -> Signal { - return Signal { observer in - return self.observe { event in - observer.action(event.map(transform)) - } - } - } - - /// Map errors in the signal to a new error. - /// - /// - parameters: - /// - transform: A closure that accepts current error object and returns - /// a new type of error object. - /// - /// - returns: A signal that will send new type of errors. - public func mapError(_ transform: @escaping (Error) -> F) -> Signal { - return Signal { observer in - return self.observe { event in - observer.action(event.mapError(transform)) - } - } - } - - /// Preserve only the values of the signal that pass the given predicate. - /// - /// - parameters: - /// - predicate: A closure that accepts value and returns `Bool` denoting - /// whether value has passed the test. - /// - /// - returns: A signal that will send only the values passing the given - /// predicate. - public func filter(_ predicate: @escaping (Value) -> Bool) -> Signal { - return Signal { observer in - return self.observe { (event: Event) -> Void in - guard let value = event.value else { - observer.action(event) - return - } - - if predicate(value) { - observer.sendNext(value) - } - } - } - } -} - -extension SignalProtocol where Value: OptionalProtocol { - /// Unwrap non-`nil` values and forward them on the returned signal, `nil` - /// values are dropped. - /// - /// - returns: A signal that sends only non-nil values. - public func skipNil() -> Signal { - return filter { $0.optional != nil }.map { $0.optional! } - } -} - -extension SignalProtocol { - /// Take up to `n` values from the signal and then complete. - /// - /// - precondition: `count` must be non-negative number. - /// - /// - parameters: - /// - count: A number of values to take from the signal. - /// - /// - returns: A signal that will yield the first `count` values from `self` - public func take(first count: Int) -> Signal { - precondition(count >= 0) - - return Signal { observer in - if count == 0 { - observer.sendCompleted() - return nil - } - - var taken = 0 - - return self.observe { event in - guard let value = event.value else { - observer.action(event) - return - } - - if taken < count { - taken += 1 - observer.sendNext(value) - } - - if taken == count { - observer.sendCompleted() - } - } - } - } -} - -/// A reference type which wraps an array to auxiliate the collection of values -/// for `collect` operator. -private final class CollectState { - var values: [Value] = [] - - /// Collects a new value. - func append(_ value: Value) { - values.append(value) - } - - /// Check if there are any items remaining. - /// - /// - note: This method also checks if there weren't collected any values - /// and, in that case, it means an empty array should be sent as the - /// result of collect. - var isEmpty: Bool { - /// We use capacity being zero to determine if we haven't collected any - /// value since we're keeping the capacity of the array to avoid - /// unnecessary and expensive allocations). This also guarantees - /// retro-compatibility around the original `collect()` operator. - return values.isEmpty && values.capacity > 0 - } - - /// Removes all values previously collected if any. - func flush() { - // Minor optimization to avoid consecutive allocations. Can - // be useful for sequences of regular or similar size and to - // track if any value was ever collected. - values.removeAll(keepingCapacity: true) - } -} - -extension SignalProtocol { - /// Collect all values sent by the signal then forward them as a single - /// array and complete. - /// - /// - note: When `self` completes without collecting any value, it will send - /// an empty array of values. - /// - /// - returns: A signal that will yield an array of values when `self` - /// completes. - public func collect() -> Signal<[Value], Error> { - return collect { _,_ in false } - } - - /// Collect at most `count` values from `self`, forward them as a single - /// array and complete. - /// - /// - note: When the count is reached the array is sent and the signal - /// starts over yielding a new array of values. - /// - /// - note: When `self` completes any remaining values will be sent, the - /// last array may not have `count` values. Alternatively, if were - /// not collected any values will sent an empty array of values. - /// - /// - precondition: `count` should be greater than zero. - /// - public func collect(count: Int) -> Signal<[Value], Error> { - precondition(count > 0) - return collect { values in values.count == count } - } - - /// Collect values that pass the given predicate then forward them as a - /// single array and complete. - /// - /// - note: When `self` completes any remaining values will be sent, the - /// last array may not match `predicate`. Alternatively, if were not - /// collected any values will sent an empty array of values. - /// - /// ```` - /// let (signal, observer) = Signal.pipe() - /// - /// signal - /// .collect { values in values.reduce(0, combine: +) == 8 } - /// .observeNext { print($0) } - /// - /// observer.sendNext(1) - /// observer.sendNext(3) - /// observer.sendNext(4) - /// observer.sendNext(7) - /// observer.sendNext(1) - /// observer.sendNext(5) - /// observer.sendNext(6) - /// observer.sendCompleted() - /// - /// // Output: - /// // [1, 3, 4] - /// // [7, 1] - /// // [5, 6] - /// ```` - /// - /// - parameters: - /// - predicate: Predicate to match when values should be sent (returning - /// `true`) or alternatively when they should be collected - /// (where it should return `false`). The most recent value - /// (`next`) is included in `values` and will be the end of - /// the current array of values if the predicate returns - /// `true`. - /// - /// - returns: A signal that collects values passing the predicate and, when - /// `self` completes, forwards them as a single array and - /// complets. - public func collect(_ predicate: @escaping (_ values: [Value]) -> Bool) -> Signal<[Value], Error> { - return Signal { observer in - let state = CollectState() - - return self.observe { event in - switch event { - case let .next(value): - state.append(value) - if predicate(state.values) { - observer.sendNext(state.values) - state.flush() - } - case .completed: - if !state.isEmpty { - observer.sendNext(state.values) - } - observer.sendCompleted() - case let .failed(error): - observer.sendFailed(error) - case .interrupted: - observer.sendInterrupted() - } - } - } - } - - /// Repeatedly collect an array of values up to a matching `next` value. - /// Then forward them as single array and wait for next events. - /// - /// - note: When `self` completes any remaining values will be sent, the - /// last array may not match `predicate`. Alternatively, if no - /// values were collected an empty array will be sent. - /// - /// ```` - /// let (signal, observer) = Signal.pipe() - /// - /// signal - /// .collect { values, next in next == 7 } - /// .observeNext { print($0) } - /// - /// observer.sendNext(1) - /// observer.sendNext(1) - /// observer.sendNext(7) - /// observer.sendNext(7) - /// observer.sendNext(5) - /// observer.sendNext(6) - /// observer.sendCompleted() - /// - /// // Output: - /// // [1, 1] - /// // [7] - /// // [7, 5, 6] - /// ```` - /// - /// - parameters: - /// - predicate: Predicate to match when values should be sent (returning - /// `true`) or alternatively when they should be collected - /// (where it should return `false`). The most recent value - /// (`next`) is not included in `values` and will be the - /// start of the next array of values if the predicate - /// returns `true`. - /// - /// - returns: A signal that will yield an array of values based on a - /// predicate which matches the values collected and the next - /// value. - public func collect(_ predicate: @escaping (_ values: [Value], _ next: Value) -> Bool) -> Signal<[Value], Error> { - return Signal { observer in - let state = CollectState() - - return self.observe { event in - switch event { - case let .next(value): - if predicate(state.values, value) { - observer.sendNext(state.values) - state.flush() - } - state.append(value) - case .completed: - if !state.isEmpty { - observer.sendNext(state.values) - } - observer.sendCompleted() - case let .failed(error): - observer.sendFailed(error) - case .interrupted: - observer.sendInterrupted() - } - } - } - } - - /// Forward all events onto the given scheduler, instead of whichever - /// scheduler they originally arrived upon. - /// - /// - parameters: - /// - scheduler: A scheduler to deliver events on. - /// - /// - returns: A signal that will yield `self` values on provided scheduler. - public func observe(on scheduler: SchedulerProtocol) -> Signal { - return Signal { observer in - return self.observe { event in - scheduler.schedule { - observer.action(event) - } - } - } - } -} - -private final class CombineLatestState { - var latestValue: Value? - var isCompleted = false -} - -extension SignalProtocol { - private func observeWithStates(_ signalState: CombineLatestState, _ otherState: CombineLatestState, _ lock: NSLock, _ observer: Signal<(), Error>.Observer) -> Disposable? { - return self.observe { event in - switch event { - case let .next(value): - lock.lock() - - signalState.latestValue = value - if otherState.latestValue != nil { - observer.sendNext() - } - - lock.unlock() - - case let .failed(error): - observer.sendFailed(error) - - case .completed: - lock.lock() - - signalState.isCompleted = true - if otherState.isCompleted { - observer.sendCompleted() - } - - lock.unlock() - - case .interrupted: - observer.sendInterrupted() - } - } - } - - /// Combine the latest value of the receiver with the latest value from the - /// given signal. - /// - /// - note: The returned signal will not send a value until both inputs have - /// sent at least one value each. - /// - /// - note: If either signal is interrupted, the returned signal will also - /// be interrupted. - /// - /// - parameters: - /// - otherSignal: A signal to combine `self`'s value with. - /// - /// - returns: A signal that will yield a tuple containing values of `self` - /// and given signal. - public func combineLatest(with other: Signal) -> Signal<(Value, U), Error> { - return Signal { observer in - let lock = NSLock() - lock.name = "org.reactivecocoa.ReactiveCocoa.combineLatestWith" - - let signalState = CombineLatestState() - let otherState = CombineLatestState() - - let onBothNext = { - observer.sendNext((signalState.latestValue!, otherState.latestValue!)) - } - - let observer = Signal<(), Error>.Observer(next: onBothNext, failed: observer.sendFailed, completed: observer.sendCompleted, interrupted: observer.sendInterrupted) - - let disposable = CompositeDisposable() - disposable += self.observeWithStates(signalState, otherState, lock, observer) - disposable += other.observeWithStates(otherState, signalState, lock, observer) - - return disposable - } - } - - /// Delay `next` and `completed` events by the given interval, forwarding - /// them on the given scheduler. - /// - /// - note: failed and `interrupted` events are always scheduled - /// immediately. - /// - /// - parameters: - /// - interval: Interval to delay `next` and `completed` events by. - /// - scheduler: A scheduler to deliver delayed events on. - /// - /// - returns: A signal that will delay `next` and `completed` events and - /// will yield them on given scheduler. - public func delay(_ interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> Signal { - precondition(interval >= 0) - - return Signal { observer in - return self.observe { event in - switch event { - case .failed, .interrupted: - scheduler.schedule { - observer.action(event) - } - - case .next, .completed: - let date = scheduler.currentDate.addingTimeInterval(interval) - scheduler.schedule(after: date) { - observer.action(event) - } - } - } - } - } - - /// Skip first `count` number of values then act as usual. - /// - /// - parameters: - /// - count: A number of values to skip. - /// - /// - returns: A signal that will skip the first `count` values, then - /// forward everything afterward. - public func skip(first count: Int) -> Signal { - precondition(count >= 0) - - if count == 0 { - return signal - } - - return Signal { observer in - var skipped = 0 - - return self.observe { event in - if case .next = event, skipped < count { - skipped += 1 - } else { - observer.action(event) - } - } - } - } - - /// Treat all Events from `self` as plain values, allowing them to be - /// manipulated just like any other value. - /// - /// In other words, this brings Events “into the monad”. - /// - /// - note: When a Completed or Failed event is received, the resulting - /// signal will send the Event itself and then complete. When an - /// Interrupted event is received, the resulting signal will send - /// the Event itself and then interrupt. - /// - /// - returns: A signal that sends events as its values. - public func materialize() -> Signal, NoError> { - return Signal { observer in - return self.observe { event in - observer.sendNext(event) - - switch event { - case .interrupted: - observer.sendInterrupted() - - case .completed, .failed: - observer.sendCompleted() - - case .next: - break - } - } - } - } -} - -extension SignalProtocol where Value: EventProtocol, Error == NoError { - /// Translate a signal of `Event` _values_ into a signal of those events - /// themselves. - /// - /// - returns: A signal that sends values carried by `self` events. - public func dematerialize() -> Signal { - return Signal { observer in - return self.observe { event in - switch event { - case let .next(innerEvent): - observer.action(innerEvent.event) - - case .failed: - fatalError("NoError is impossible to construct") - - case .completed: - observer.sendCompleted() - - case .interrupted: - observer.sendInterrupted() - } - } - } - } -} - -extension SignalProtocol { - /// Inject side effects to be performed upon the specified signal events. - /// - /// - parameters: - /// - event: A closure that accepts an event and is invoked on every - /// received event. - /// - next: A closure that accepts a value from `next` event. - /// - failed: A closure that accepts error object and is invoked for - /// failed event. - /// - completed: A closure that is invoked for `completed` event. - /// - interrupted: A closure that is invoked for `interrupted` event. - /// - terminated: A closure that is invoked for any terminating event. - /// - disposed: A closure added as disposable when signal completes. - /// - /// - returns: A signal with attached side-effects for given event cases. - public func on( - event: ((Event) -> Void)? = nil, - failed: ((Error) -> Void)? = nil, - completed: (() -> Void)? = nil, - interrupted: (() -> Void)? = nil, - terminated: (() -> Void)? = nil, - disposed: (() -> Void)? = nil, - next: ((Value) -> Void)? = nil - ) -> Signal { - return Signal { observer in - let disposable = CompositeDisposable() - - _ = disposed.map(disposable.add) - - disposable += signal.observe { receivedEvent in - event?(receivedEvent) - - switch receivedEvent { - case let .next(value): - next?(value) - - case let .failed(error): - failed?(error) - - case .completed: - completed?() - - case .interrupted: - interrupted?() - } - - if receivedEvent.isTerminating { - terminated?() - } - - observer.action(receivedEvent) - } - - return disposable - } - } -} - -private struct SampleState { - var latestValue: Value? = nil - var isSignalCompleted: Bool = false - var isSamplerCompleted: Bool = false -} - -extension SignalProtocol { - /// Forward the latest value from `self` with the value from `sampler` as a - /// tuple, only when`sampler` sends a `next` event. - /// - /// - note: If `sampler` fires before a value has been observed on `self`, - /// nothing happens. - /// - /// - parameters: - /// - sampler: A signal that will trigger the delivery of `next` event - /// from `self`. - /// - /// - returns: A signal that will send values from `self` and `sampler`, - /// sampled (possibly multiple times) by `sampler`, then complete - /// once both input signals have completed, or interrupt if - /// either input signal is interrupted. - public func sample(with sampler: Signal) -> Signal<(Value, T), Error> { - return Signal { observer in - let state = Atomic(SampleState()) - let disposable = CompositeDisposable() - - disposable += self.observe { event in - switch event { - case let .next(value): - state.modify { - $0.latestValue = value - } - - case let .failed(error): - observer.sendFailed(error) - - case .completed: - let shouldComplete: Bool = state.modify { - $0.isSignalCompleted = true - return $0.isSamplerCompleted - } - - if shouldComplete { - observer.sendCompleted() - } - - case .interrupted: - observer.sendInterrupted() - } - } - - disposable += sampler.observe { event in - switch event { - case .next(let samplerValue): - if let value = state.value.latestValue { - observer.sendNext((value, samplerValue)) - } - - case .completed: - let shouldComplete: Bool = state.modify { - $0.isSamplerCompleted = true - return $0.isSignalCompleted - } - - if shouldComplete { - observer.sendCompleted() - } - - case .interrupted: - observer.sendInterrupted() - - case .failed: - break - } - } - - return disposable - } - } - - /// Forward the latest value from `self` whenever `sampler` sends a `next` - /// event. - /// - /// - note: If `sampler` fires before a value has been observed on `self`, - /// nothing happens. - /// - /// - parameters: - /// - sampler: A signal that will trigger the delivery of `next` event - /// from `self`. - /// - /// - returns: A signal that will send values from `self`, sampled (possibly - /// multiple times) by `sampler`, then complete once both input - /// signals have completed, or interrupt if either input signal - /// is interrupted. - public func sample(on sampler: Signal<(), NoError>) -> Signal { - return sample(with: sampler) - .map { $0.0 } - } - - /// Forwards events from `self` until `lifetime` ends, at which point the - /// returned signal will complete. - /// - /// - parameters: - /// - lifetime: A lifetime whose `ended` signal will cause the returned - /// signal to complete. - /// - /// - returns: A signal that will deliver events until `lifetime` ends. - public func take(during lifetime: Lifetime) -> Signal { - return take(until: lifetime.ended) - } - - /// Forward events from `self` until `trigger` sends a `next` or - /// `completed` event, at which point the returned signal will complete. - /// - /// - parameters: - /// - trigger: A signal whose `next` or `completed` events will stop the - /// delivery of `next` events from `self`. - /// - /// - returns: A signal that will deliver events until `trigger` sends - /// `next` or `completed` events. - public func take(until trigger: Signal<(), NoError>) -> Signal { - return Signal { observer in - let disposable = CompositeDisposable() - disposable += self.observe(observer) - - disposable += trigger.observe { event in - switch event { - case .next, .completed: - observer.sendCompleted() - - case .failed, .interrupted: - break - } - } - - return disposable - } - } - - /// Do not forward any values from `self` until `trigger` sends a `next` or - /// `completed` event, at which point the returned signal behaves exactly - /// like `signal`. - /// - /// - parameters: - /// - trigger: A signal whose `next` or `completed` events will start the - /// deliver of events on `self`. - /// - /// - returns: A signal that will deliver events once the `trigger` sends - /// `next` or `completed` events. - public func skip(until trigger: Signal<(), NoError>) -> Signal { - return Signal { observer in - let disposable = SerialDisposable() - - disposable.innerDisposable = trigger.observe { event in - switch event { - case .next, .completed: - disposable.innerDisposable = self.observe(observer) - - case .failed, .interrupted: - break - } - } - - return disposable - } - } - - /// Forward events from `self` with history: values of the returned signal - /// are a tuples whose first member is the previous value and whose second member - /// is the current value. `initial` is supplied as the first member when `self` - /// sends its first value. - /// - /// - parameters: - /// - initial: A value that will be combined with the first value sent by - /// `self`. - /// - /// - returns: A signal that sends tuples that contain previous and current - /// sent values of `self`. - public func combinePrevious(_ initial: Value) -> Signal<(Value, Value), Error> { - return scan((initial, initial)) { previousCombinedValues, newValue in - return (previousCombinedValues.1, newValue) - } - } - - - /// Send only the final value and then immediately completes. - /// - /// - parameters: - /// - initial: Initial value for the accumulator. - /// - combine: A closure that accepts accumulator and sent value of - /// `self`. - /// - /// - returns: A signal that sends accumulated value after `self` completes. - public func reduce(_ initial: U, _ combine: @escaping (U, Value) -> U) -> Signal { - // We need to handle the special case in which `signal` sends no values. - // We'll do that by sending `initial` on the output signal (before - // taking the last value). - let (scannedSignalWithInitialValue, outputSignalObserver) = Signal.pipe() - let outputSignal = scannedSignalWithInitialValue.take(last: 1) - - // Now that we've got takeLast() listening to the piped signal, send - // that initial value. - outputSignalObserver.sendNext(initial) - - // Pipe the scanned input signal into the output signal. - self.scan(initial, combine) - .observe(outputSignalObserver) - - return outputSignal - } - - /// Aggregate values into a single combined value. When `self` emits its - /// first value, `combine` is invoked with `initial` as the first argument - /// and that emitted value as the second argument. The result is emitted - /// from the signal returned from `scan`. That result is then passed to - /// `combine` as the first argument when the next value is emitted, and so - /// on. - /// - /// - parameters: - /// - initial: Initial value for the accumulator. - /// - combine: A closure that accepts accumulator and sent value of - /// `self`. - /// - /// - returns: A signal that sends accumulated value each time `self` emits - /// own value. - public func scan(_ initial: U, _ combine: @escaping (U, Value) -> U) -> Signal { - return Signal { observer in - var accumulator = initial - - return self.observe { event in - observer.action(event.map { value in - accumulator = combine(accumulator, value) - return accumulator - }) - } - } - } -} - -extension SignalProtocol where Value: Equatable { - /// Forward only those values from `self` which are not duplicates of the - /// immedately preceding value. - /// - /// - note: The first value is always forwarded. - /// - /// - returns: A signal that does not send two equal values sequentially. - public func skipRepeats() -> Signal { - return skipRepeats(==) - } -} - -extension SignalProtocol { - /// Forward only those values from `self` which do not pass `isRepeat` with - /// respect to the previous value. - /// - /// - note: The first value is always forwarded. - /// - /// - parameters: - /// - isRepeate: A closure that accepts previous and current values of - /// `self` and returns `Bool` whether these values are - /// repeating. - /// - /// - returns: A signal that forwards only those values that fail given - /// `isRepeat` predicate. - public func skipRepeats(_ isRepeat: @escaping (Value, Value) -> Bool) -> Signal { - return self - .scan((nil, false)) { (accumulated: (Value?, Bool), next: Value) -> (value: Value?, repeated: Bool) in - switch accumulated.0 { - case nil: - return (next, false) - case let prev? where isRepeat(prev, next): - return (prev, true) - case _?: - return (Optional(next), false) - } - } - .filter { !$0.repeated } - .map { $0.value } - .skipNil() - } - - /// Do not forward any values from `self` until `predicate` returns false, - /// at which point the returned signal behaves exactly like `signal`. - /// - /// - parameters: - /// - predicate: A closure that accepts a value and returns whether `self` - /// should still not forward that value to a `signal`. - /// - /// - returns: A signal that sends only forwarded values from `self`. - public func skip(while predicate: @escaping (Value) -> Bool) -> Signal { - return Signal { observer in - var shouldSkip = true - - return self.observe { event in - switch event { - case let .next(value): - shouldSkip = shouldSkip && predicate(value) - if !shouldSkip { - fallthrough - } - - case .failed, .completed, .interrupted: - observer.action(event) - } - } - } - } - - /// Forward events from `self` until `replacement` begins sending events. - /// - /// - parameters: - /// - replacement: A signal to wait to wait for values from and start - /// sending them as a replacement to `self`'s values. - /// - /// - returns: A signal which passes through `next`, failed, and - /// `interrupted` events from `self` 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 `self` has sent events - /// already. - public func take(untilReplacement signal: Signal) -> Signal { - return Signal { observer in - let disposable = CompositeDisposable() - - let signalDisposable = self.observe { event in - switch event { - case .completed: - break - - case .next, .failed, .interrupted: - observer.action(event) - } - } - - disposable += signalDisposable - disposable += signal.observe { event in - signalDisposable?.dispose() - observer.action(event) - } - - return disposable - } - } - - /// Wait until `self` completes and then forward the final `count` values - /// on the returned signal. - /// - /// - parameters: - /// - count: Number of last events to send after `self` completes. - /// - /// - returns: A signal that receives up to `count` values from `self` - /// after `self` completes. - public func take(last count: Int) -> Signal { - return Signal { observer in - var buffer: [Value] = [] - buffer.reserveCapacity(count) - - return self.observe { event in - switch event { - case let .next(value): - // To avoid exceeding the reserved capacity of the buffer, - // we remove then add. Remove elements until we have room to - // add one more. - while (buffer.count + 1) > count { - buffer.remove(at: 0) - } - - buffer.append(value) - case let .failed(error): - observer.sendFailed(error) - case .completed: - buffer.forEach(observer.sendNext) - - observer.sendCompleted() - case .interrupted: - observer.sendInterrupted() - } - } - } - } - - /// Forward any values from `self` until `predicate` returns false, at which - /// point the returned signal will complete. - /// - /// - parameters: - /// - predicate: A closure that accepts value and returns `Bool` value - /// whether `self` should forward it to `signal` and continue - /// sending other events. - /// - /// - returns: A signal that sends events until the values sent by `self` - /// pass the given `predicate`. - public func take(while predicate: @escaping (Value) -> Bool) -> Signal { - return Signal { observer in - return self.observe { event in - if let value = event.value, !predicate(value) { - observer.sendCompleted() - } else { - observer.action(event) - } - } - } - } -} - -private struct ZipState { - var values: (left: [Left], right: [Right]) = ([], []) - var isCompleted: (left: Bool, right: Bool) = (false, false) - - var isFinished: Bool { - return (isCompleted.left && values.left.isEmpty) || (isCompleted.right && values.right.isEmpty) - } -} - -extension SignalProtocol { - /// Zip elements of two signals into pairs. The elements of any Nth pair - /// are the Nth elements of the two input signals. - /// - /// - parameters: - /// - otherSignal: A signal to zip values with. - /// - /// - returns: A signal that sends tuples of `self` and `otherSignal`. - public func zip(with other: Signal) -> Signal<(Value, U), Error> { - return Signal { observer in - let state = Atomic(ZipState()) - let disposable = CompositeDisposable() - - let flush = { - var tuple: (Value, U)? - var isFinished = false - - state.modify { state in - guard !state.values.left.isEmpty && !state.values.right.isEmpty else { - isFinished = state.isFinished - return - } - - tuple = (state.values.left.removeFirst(), state.values.right.removeFirst()) - isFinished = state.isFinished - } - - if let tuple = tuple { - observer.sendNext(tuple) - } - - if isFinished { - observer.sendCompleted() - } - } - - let onFailed = observer.sendFailed - let onInterrupted = observer.sendInterrupted - - disposable += self.observe { event in - switch event { - case let .next(value): - state.modify { - $0.values.left.append(value) - } - flush() - - case let .failed(error): - onFailed(error) - - case .completed: - state.modify { - $0.isCompleted.left = true - } - flush() - - case .interrupted: - onInterrupted() - } - } - - disposable += other.observe { event in - switch event { - case let .next(value): - state.modify { - $0.values.right.append(value) - } - flush() - - case let .failed(error): - onFailed(error) - - case .completed: - state.modify { - $0.isCompleted.right = true - } - flush() - - case .interrupted: - onInterrupted() - } - } - - return disposable - } - } - - /// Apply `operation` to values from `self` with `Success`ful results - /// forwarded on the returned signal and `Failure`s sent as failed events. - /// - /// - parameters: - /// - operation: A closure that accepts a value and returns a `Result`. - /// - /// - returns: A signal that receives `Success`ful `Result` as `next` event - /// and `Failure` as failed event. - public func attempt(_ operation: @escaping (Value) -> Result<(), Error>) -> Signal { - return attemptMap { value in - return operation(value).map { - return value - } - } - } - - /// Apply `operation` to values from `self` with `Success`ful results mapped - /// on the returned signal and `Failure`s sent as failed events. - /// - /// - parameters: - /// - operation: A closure that accepts a value and returns a result of - /// a mapped value as `Success`. - /// - /// - returns: A signal that sends mapped values from `self` if returned - /// `Result` is `Success`ful, failed events otherwise. - public func attemptMap(_ operation: @escaping (Value) -> Result) -> Signal { - return Signal { observer in - self.observe { event in - switch event { - case let .next(value): - operation(value).analysis( - ifSuccess: observer.sendNext, - ifFailure: observer.sendFailed - ) - case let .failed(error): - observer.sendFailed(error) - case .completed: - observer.sendCompleted() - case .interrupted: - observer.sendInterrupted() - } - } - } - } - - /// Throttle values sent by the receiver, so that at least `interval` - /// seconds pass between each, then forwards them on the given scheduler. - /// - /// - note: If multiple values are received before the interval has elapsed, - /// the latest value is the one that will be passed on. - /// - /// - note: If the input signal terminates while a value is being throttled, - /// that value will be discarded and the returned signal will - /// terminate immediately. - /// - /// - note: If the device time changed backwords before previous date while - /// a value is being throttled, and if there is a new value sent, - /// the new value will be passed anyway. - /// - /// - parameters: - /// - interval: Number of seconds to wait between sent values. - /// - scheduler: A scheduler to deliver events on. - /// - /// - returns: A signal that sends values at least `interval` seconds - /// appart on a given scheduler. - public func throttle(_ interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> Signal { - precondition(interval >= 0) - - return Signal { observer in - let state: Atomic> = Atomic(ThrottleState()) - let schedulerDisposable = SerialDisposable() - - let disposable = CompositeDisposable() - disposable += schedulerDisposable - - disposable += self.observe { event in - guard let value = event.value else { - schedulerDisposable.innerDisposable = scheduler.schedule { - observer.action(event) - } - return - } - - var scheduleDate: Date! - state.modify { - $0.pendingValue = value - - let proposedScheduleDate: Date - if let previousDate = $0.previousDate, previousDate.compare(scheduler.currentDate) != .orderedDescending { - proposedScheduleDate = previousDate.addingTimeInterval(interval) - } else { - proposedScheduleDate = scheduler.currentDate - } - - switch proposedScheduleDate.compare(scheduler.currentDate) { - case .orderedAscending: - scheduleDate = scheduler.currentDate - - case .orderedSame: fallthrough - case .orderedDescending: - scheduleDate = proposedScheduleDate - } - } - - schedulerDisposable.innerDisposable = scheduler.schedule(after: scheduleDate) { - let pendingValue: Value? = state.modify { state in - defer { - if state.pendingValue != nil { - state.pendingValue = nil - state.previousDate = scheduleDate - } - } - return state.pendingValue - } - - if let pendingValue = pendingValue { - observer.sendNext(pendingValue) - } - } - } - - return disposable - } - } - - /// Debounce values sent by the receiver, such that at least `interval` - /// seconds pass after the receiver has last sent a value, then forward the - /// latest value on the given scheduler. - /// - /// - note: If multiple values are received before the interval has elapsed, - /// the latest value is the one that will be passed on. - /// - /// - note: If the input signal terminates while a value is being debounced, - /// that value will be discarded and the returned signal will - /// terminate immediately. - /// - /// - parameters: - /// - interval: A number of seconds to wait before sending a value. - /// - scheduler: A scheduler to send values on. - /// - /// - returns: A signal that sends values that are sent from `self` at least - /// `interval` seconds apart. - public func debounce(_ interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> Signal { - precondition(interval >= 0) - - return self - .materialize() - .flatMap(.latest) { event -> SignalProducer, NoError> in - if event.isTerminating { - return SignalProducer(value: event).observe(on: scheduler) - } else { - return SignalProducer(value: event).delay(interval, on: scheduler) - } - } - .dematerialize() - } -} - -extension SignalProtocol { - /// Forward only those values from `self` that have unique identities across - /// the set of all values that have been seen. - /// - /// - note: This causes the identities to be retained to check for - /// uniqueness. - /// - /// - parameters: - /// - transform: A closure that accepts a value and returns identity - /// value. - /// - /// - returns: A signal that sends unique values during its lifetime. - public func uniqueValues(_ transform: @escaping (Value) -> Identity) -> Signal { - return Signal { observer in - var seenValues: Set = [] - - return self - .observe { event in - switch event { - case let .next(value): - let identity = transform(value) - if !seenValues.contains(identity) { - seenValues.insert(identity) - fallthrough - } - - case .failed, .completed, .interrupted: - observer.action(event) - } - } - } - } -} - -extension SignalProtocol where Value: Hashable { - /// Forward only those values from `self` that are unique across the set of - /// all values that have been seen. - /// - /// - note: This causes the values to be retained to check for uniqueness. - /// Providing a function that returns a unique value for each sent - /// value can help you reduce the memory footprint. - /// - /// - returns: A signal that sends unique values during its lifetime. - public func uniqueValues() -> Signal { - return uniqueValues { $0 } - } -} - -private struct ThrottleState { - var previousDate: Date? = nil - var pendingValue: Value? = nil -} - -extension SignalProtocol { - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal) -> Signal<(Value, B), Error> { - return a.combineLatest(with: b) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal) -> Signal<(Value, B, C), Error> { - return combineLatest(a, b) - .combineLatest(with: c) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal) -> Signal<(Value, B, C, D), Error> { - return combineLatest(a, b, c) - .combineLatest(with: d) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal) -> Signal<(Value, B, C, D, E), Error> { - return combineLatest(a, b, c, d) - .combineLatest(with: e) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal) -> Signal<(Value, B, C, D, E, F), Error> { - return combineLatest(a, b, c, d, e) - .combineLatest(with: f) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal) -> Signal<(Value, B, C, D, E, F, G), Error> { - return combineLatest(a, b, c, d, e, f) - .combineLatest(with: g) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal, _ h: Signal) -> Signal<(Value, B, C, D, E, F, G, H), Error> { - return combineLatest(a, b, c, d, e, f, g) - .combineLatest(with: h) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal, _ h: Signal, _ i: Signal) -> Signal<(Value, B, C, D, E, F, G, H, I), Error> { - return combineLatest(a, b, c, d, e, f, g, h) - .combineLatest(with: i) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal, _ h: Signal, _ i: Signal, _ j: Signal) -> Signal<(Value, B, C, D, E, F, G, H, I, J), Error> { - return combineLatest(a, b, c, d, e, f, g, h, i) - .combineLatest(with: j) - .map(repack) - } - - /// Combines the values of all the given signals, in the manner described by - /// `combineLatestWith`. No events will be sent if the sequence is empty. - public static func combineLatest(_ signals: S) -> Signal<[Value], Error> - where S.Iterator.Element == Signal - { - var generator = signals.makeIterator() - if let first = generator.next() { - let initial = first.map { [$0] } - return IteratorSequence(generator).reduce(initial) { signal, next in - signal.combineLatest(with: next).map { $0.0 + [$0.1] } - } - } - - return .never - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal) -> Signal<(Value, B), Error> { - return a.zip(with: b) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal) -> Signal<(Value, B, C), Error> { - return zip(a, b) - .zip(with: c) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal) -> Signal<(Value, B, C, D), Error> { - return zip(a, b, c) - .zip(with: d) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal) -> Signal<(Value, B, C, D, E), Error> { - return zip(a, b, c, d) - .zip(with: e) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal) -> Signal<(Value, B, C, D, E, F), Error> { - return zip(a, b, c, d, e) - .zip(with: f) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal) -> Signal<(Value, B, C, D, E, F, G), Error> { - return zip(a, b, c, d, e, f) - .zip(with: g) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal, _ h: Signal) -> Signal<(Value, B, C, D, E, F, G, H), Error> { - return zip(a, b, c, d, e, f, g) - .zip(with: h) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal, _ h: Signal, _ i: Signal) -> Signal<(Value, B, C, D, E, F, G, H, I), Error> { - return zip(a, b, c, d, e, f, g, h) - .zip(with: i) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. - public static func zip(_ a: Signal, _ b: Signal, _ c: Signal, _ d: Signal, _ e: Signal, _ f: Signal, _ g: Signal, _ h: Signal, _ i: Signal, _ j: Signal) -> Signal<(Value, B, C, D, E, F, G, H, I, J), Error> { - return zip(a, b, c, d, e, f, g, h, i) - .zip(with: j) - .map(repack) - } - - /// Zips the values of all the given signals, in the manner described by - /// `zipWith`. No events will be sent if the sequence is empty. - public static func zip(_ signals: S) -> Signal<[Value], Error> - where S.Iterator.Element == Signal - { - var generator = signals.makeIterator() - if let first = generator.next() { - let initial = first.map { [$0] } - return IteratorSequence(generator).reduce(initial) { signal, next in - signal.zip(with: next).map { $0.0 + [$0.1] } - } - } - - return .never - } -} - -extension SignalProtocol { - /// Forward events from `self` until `interval`. Then if signal isn't - /// completed yet, fails with `error` on `scheduler`. - /// - /// - note: If the interval is 0, the timeout will be scheduled immediately. - /// The signal must complete synchronously (or on a faster - /// scheduler) to avoid the timeout. - /// - /// - parameters: - /// - error: Error to send with failed event if `self` is not completed - /// when `interval` passes. - /// - interval: Number of seconds to wait for `self` to complete. - /// - scheudler: A scheduler to deliver error on. - /// - /// - returns: A signal that sends events for at most `interval` seconds, - /// then, if not `completed` - sends `error` with failed event - /// on `scheduler`. - public func timeout(after interval: TimeInterval, raising error: Error, on scheduler: DateSchedulerProtocol) -> Signal { - precondition(interval >= 0) - - return Signal { observer in - let disposable = CompositeDisposable() - let date = scheduler.currentDate.addingTimeInterval(interval) - - disposable += scheduler.schedule(after: date) { - observer.sendFailed(error) - } - - disposable += self.observe(observer) - return disposable - } - } -} - -extension SignalProtocol where Error == NoError { - /// Promote a signal that does not generate failures into one that can. - /// - /// - note: This does not actually cause failures to be generated for the - /// given signal, but makes it easier to combine with other signals - /// that may fail; for example, with operators like - /// `combineLatestWith`, `zipWith`, `flatten`, etc. - /// - /// - parameters: - /// - _ An `ErrorType`. - /// - /// - returns: A signal that has an instantiatable `ErrorType`. - public func promoteErrors(_: F.Type) -> Signal { - return Signal { observer in - return self.observe { event in - switch event { - case let .next(value): - observer.sendNext(value) - case .failed: - fatalError("NoError is impossible to construct") - case .completed: - observer.sendCompleted() - case .interrupted: - observer.sendInterrupted() - } - } - } - } - - /// Forward events from `self` until `interval`. Then if signal isn't - /// completed yet, fails with `error` on `scheduler`. - /// - /// - note: If the interval is 0, the timeout will be scheduled immediately. - /// The signal must complete synchronously (or on a faster - /// scheduler) to avoid the timeout. - /// - /// - parameters: - /// - interval: Number of seconds to wait for `self` to complete. - /// - error: Error to send with `failed` event if `self` is not completed - /// when `interval` passes. - /// - scheudler: A scheduler to deliver error on. - /// - /// - returns: A signal that sends events for at most `interval` seconds, - /// then, if not `completed` - sends `error` with `failed` event - /// on `scheduler`. - public func timeout( - after interval: TimeInterval, - raising error: NewError, - on scheduler: DateSchedulerProtocol - ) -> Signal { - return self - .promoteErrors(NewError.self) - .timeout(after: interval, raising: error, on: scheduler) - } -} diff --git a/ReactiveCocoa/Swift/SignalProducer.swift b/ReactiveCocoa/Swift/SignalProducer.swift deleted file mode 100644 index 6a70eeecb1..0000000000 --- a/ReactiveCocoa/Swift/SignalProducer.swift +++ /dev/null @@ -1,1943 +0,0 @@ -import Foundation -import Result - -/// A SignalProducer creates Signals that can produce values of type `Value` -/// and/or fail with errors of type `Error`. If no failure should be possible, -/// `NoError` can be specified for `Error`. -/// -/// SignalProducers can be used to represent operations or tasks, like network -/// requests, where each invocation of `start()` will create a new underlying -/// operation. This ensures that consumers will receive the results, versus a -/// plain Signal, where the results might be sent before any observers are -/// attached. -/// -/// Because of the behavior of `start()`, different Signals created from the -/// producer may see a different version of Events. The Events may arrive in a -/// different order between Signals, or the stream might be completely -/// different! -public struct SignalProducer { - public typealias ProducedSignal = Signal - - private let startHandler: (Signal.Observer, CompositeDisposable) -> Void - - /// Initializes a `SignalProducer` that will emit the same events as the - /// given signal. - /// - /// If the Disposable returned from `start()` is disposed or a terminating - /// event is sent to the observer, the given signal will be disposed. - /// - /// - parameters: - /// - signal: A signal to observe after starting the producer. - public init(signal: S) where S.Value == Value, S.Error == Error { - self.init { observer, disposable in - disposable += signal.observe(observer) - } - } - - /// Initializes a SignalProducer that will invoke the given closure once for - /// each invocation of `start()`. - /// - /// The events that the closure puts into the given observer will become - /// the events sent by the started `Signal` to its observers. - /// - /// - note: If the `Disposable` returned from `start()` is disposed or a - /// terminating event is sent to the observer, the given - /// `CompositeDisposable` will be disposed, at which point work - /// should be interrupted and any temporary resources cleaned up. - /// - /// - parameters: - /// - startHandler: A closure that accepts observer and a disposable. - public init(_ startHandler: @escaping (Signal.Observer, CompositeDisposable) -> Void) { - self.startHandler = startHandler - } - - /// Creates a producer for a `Signal` that will immediately send one value - /// then complete. - /// - /// - parameters: - /// - value: A value that should be sent by the `Signal` in a `next` - /// event. - public init(value: Value) { - self.init { observer, disposable in - observer.sendNext(value) - observer.sendCompleted() - } - } - - /// Creates a producer for a `Signal` that will immediately fail with the - /// given error. - /// - /// - parameters: - /// - error: An error that should be sent by the `Signal` in a `failed` - /// event. - public init(error: Error) { - self.init { observer, disposable in - observer.sendFailed(error) - } - } - - /// Creates a producer for a Signal that will immediately send one value - /// then complete, or immediately fail, depending on the given Result. - /// - /// - parameters: - /// - result: A `Result` instance that will send either `next` event if - /// `result` is `Success`ful or `failed` event if `result` is a - /// `Failure`. - public init(result: Result) { - switch result { - case let .success(value): - self.init(value: value) - - case let .failure(error): - self.init(error: error) - } - } - - /// Creates a producer for a Signal that will immediately send the values - /// from the given sequence, then complete. - /// - /// - parameters: - /// - values: A sequence of values that a `Signal` will send as separate - /// `next` events and then complete. - public init(values: S) where S.Iterator.Element == Value { - self.init { observer, disposable in - for value in values { - observer.sendNext(value) - - if disposable.isDisposed { - break - } - } - - observer.sendCompleted() - } - } - - /// Creates a producer for a Signal that will immediately send the values - /// from the given sequence, then complete. - /// - /// - parameters: - /// - first: First value for the `Signal` to send. - /// - second: Second value for the `Signal` to send. - /// - tail: Rest of the values to be sent by the `Signal`. - public init(values first: Value, _ second: Value, _ tail: Value...) { - self.init(values: [ first, second ] + tail) - } - - /// A producer for a Signal that will immediately complete without sending - /// any values. - public static var empty: SignalProducer { - return self.init { observer, disposable in - observer.sendCompleted() - } - } - - /// A producer for a Signal that never sends any events to its observers. - public static var never: SignalProducer { - return self.init { _ in return } - } - - /// Create a `SignalProducer` that will attempt the given operation once for - /// each invocation of `start()`. - /// - /// Upon success, the started signal will send the resulting value then - /// complete. Upon failure, the started signal will fail with the error that - /// occurred. - /// - /// - parameters: - /// - operation: A closure that returns instance of `Result`. - /// - /// - returns: A `SignalProducer` that will forward `Success`ful `result` as - /// `next` event and then complete or `failed` event if `result` - /// is a `Failure`. - public static func attempt(_ operation: @escaping () -> Result) -> SignalProducer { - return self.init { observer, disposable in - operation().analysis(ifSuccess: { value in - observer.sendNext(value) - observer.sendCompleted() - }, ifFailure: { error in - observer.sendFailed(error) - }) - } - } - - /// Create a Signal from the producer, pass it into the given closure, - /// then start sending events on the Signal when the closure has returned. - /// - /// The closure will also receive a disposable which can be used to - /// interrupt the work associated with the signal and immediately send an - /// `interrupted` event. - /// - /// - parameters: - /// - setUp: A closure that accepts a `signal` and `interrupter`. - public func startWithSignal(_ setup: (_ signal: Signal, _ interrupter: Disposable) -> Void) { - let (signal, observer) = Signal.pipe() - - // Disposes of the work associated with the SignalProducer and any - // upstream producers. - let producerDisposable = CompositeDisposable() - - // Directly disposed of when `start()` or `startWithSignal()` is - // disposed. - let cancelDisposable = ActionDisposable { - observer.sendInterrupted() - producerDisposable.dispose() - } - - setup(signal, cancelDisposable) - - if cancelDisposable.isDisposed { - return - } - - let wrapperObserver: Signal.Observer = Observer { event in - observer.action(event) - - if event.isTerminating { - // Dispose only after notifying the Signal, so disposal - // logic is consistently the last thing to run. - producerDisposable.dispose() - } - } - - startHandler(wrapperObserver, producerDisposable) - } -} - -public protocol SignalProducerProtocol { - /// The type of values being sent on the producer - associatedtype Value - /// The type of error that can occur on the producer. If errors aren't possible - /// then `NoError` can be used. - associatedtype Error: Swift.Error - - /// Extracts a signal producer from the receiver. - var producer: SignalProducer { get } - - /// Initialize a signal - init(_ startHandler: @escaping (Signal.Observer, CompositeDisposable) -> Void) - - /// Creates a Signal from the producer, passes it into the given closure, - /// then starts sending events on the Signal when the closure has returned. - func startWithSignal(_ setup: (_ signal: Signal, _ interrupter: Disposable) -> Void) -} - -extension SignalProducer: SignalProducerProtocol { - public var producer: SignalProducer { - return self - } -} - -extension SignalProducerProtocol { - /// Create a Signal from the producer, then attach the given observer to - /// the `Signal` as an observer. - /// - /// - parameters: - /// - observer: An observer to attach to produced signal. - /// - /// - returns: A `Disposable` which can be used to interrupt the work - /// associated with the signal and immediately send an - /// `interrupted` event. - @discardableResult - public func start(_ observer: Signal.Observer = .init()) -> Disposable { - var disposable: Disposable! - - startWithSignal { signal, innerDisposable in - signal.observe(observer) - disposable = innerDisposable - } - - return disposable - } - - /// Convenience override for start(_:) to allow trailing-closure style - /// invocations. - /// - /// - parameters: - /// - observerAction: A closure that accepts `Event` sent by the produced - /// signal. - /// - /// - returns: A `Disposable` which can be used to interrupt the work - /// associated with the signal and immediately send an - /// `interrupted` event. - @discardableResult - public func start(_ observerAction: Signal.Observer.Action) -> Disposable { - return start(Observer(observerAction)) - } - - /// Create a Signal from the producer, then add an observer to the `Signal`, - /// which will invoke the given callback when `next` or `failed` events are - /// received. - /// - /// - parameters: - /// - result: A closure that accepts a `result` that contains a `Success` - /// case for `next` events or `Failure` case for `failed` event. - /// - /// - returns: A Disposable which can be used to interrupt the work - /// associated with the Signal, and prevent any future callbacks - /// from being invoked. - @discardableResult - public func startWithResult(_ result: @escaping (Result) -> Void) -> Disposable { - return start( - Observer( - next: { result(.success($0)) }, - failed: { result(.failure($0)) } - ) - ) - } - - /// Create a Signal from the producer, then add exactly one observer to the - /// Signal, which will invoke the given callback when a `completed` event is - /// received. - /// - /// - parameters: - /// - completed: A closure that will be envoked when produced signal sends - /// `completed` event. - /// - /// - returns: A `Disposable` which can be used to interrupt the work - /// associated with the signal. - @discardableResult - public func startWithCompleted(_ completed: @escaping () -> Void) -> Disposable { - return start(Observer(completed: completed)) - } - - /// Creates a Signal from the producer, then adds exactly one observer to - /// the Signal, which will invoke the given callback when a `failed` event - /// is received. - /// - /// - parameters: - /// - failed: A closure that accepts an error object. - /// - /// - returns: A `Disposable` which can be used to interrupt the work - /// associated with the signal. - @discardableResult - public func startWithFailed(_ failed: @escaping (Error) -> Void) -> Disposable { - return start(Observer(failed: failed)) - } - - /// Creates a Signal from the producer, then adds exactly one observer to - /// the Signal, which will invoke the given callback when an `interrupted` - /// event is received. - /// - /// - parameters: - /// - interrupted: A closure that is invoked when `interrupted` event is - /// received. - /// - /// - returns: A `Disposable` which can be used to interrupt the work - /// associated with the signal. - @discardableResult - public func startWithInterrupted(_ interrupted: @escaping () -> Void) -> Disposable { - return start(Observer(interrupted: interrupted)) - } -} - -extension SignalProducerProtocol where Error == NoError { - /// Create a Signal from the producer, then add exactly one observer to - /// the Signal, which will invoke the given callback when `next` events are - /// received. - /// - /// - parameters: - /// - next: A closure that accepts a value carried by `next` event. - /// - /// - returns: A `Disposable` which can be used to interrupt the work - /// associated with the Signal, and prevent any future callbacks - /// from being invoked. - @discardableResult - public func startWithNext(_ next: @escaping (Value) -> Void) -> Disposable { - return start(Observer(next: next)) - } -} - -extension SignalProducerProtocol { - /// Lift an unary Signal operator to operate upon SignalProducers instead. - /// - /// In other words, this will create a new `SignalProducer` which will apply - /// the given `Signal` operator to _every_ created `Signal`, just as if the - /// operator had been applied to each `Signal` yielded from `start()`. - /// - /// - parameters: - /// - transform: An unary operator to lift. - /// - /// - returns: A signal producer that applies signal's operator to every - /// created signal. - public func lift(_ transform: @escaping (Signal) -> Signal) -> SignalProducer { - return SignalProducer { observer, outerDisposable in - self.startWithSignal { signal, innerDisposable in - outerDisposable += innerDisposable - - transform(signal).observe(observer) - } - } - } - - - /// Lift a binary Signal operator to operate upon SignalProducers instead. - /// - /// In other words, this will create a new `SignalProducer` which will apply - /// the given `Signal` operator to _every_ `Signal` created from the two - /// producers, just as if the operator had been applied to each `Signal` - /// yielded from `start()`. - /// - /// - note: starting the returned producer will start the receiver of the - /// operator, which may not be adviseable for some operators. - /// - /// - parameters: - /// - transform: A binary operator to lift. - /// - /// - returns: A binary operator that operates on two signal producers. - public func lift(_ transform: @escaping (Signal) -> (Signal) -> Signal) -> (SignalProducer) -> SignalProducer { - return liftRight(transform) - } - - /// Right-associative lifting of a binary signal operator over producers. - /// That is, the argument producer will be started before the receiver. When - /// both producers are synchronous this order can be important depending on - /// the operator to generate correct results. - private func liftRight(_ transform: @escaping (Signal) -> (Signal) -> Signal) -> (SignalProducer) -> SignalProducer { - return { otherProducer in - return SignalProducer { observer, outerDisposable in - self.startWithSignal { signal, disposable in - outerDisposable.add(disposable) - - otherProducer.startWithSignal { otherSignal, otherDisposable in - outerDisposable += otherDisposable - - transform(signal)(otherSignal).observe(observer) - } - } - } - } - } - - /// Left-associative lifting of a binary signal operator over producers. - /// That is, the receiver will be started before the argument producer. When - /// both producers are synchronous this order can be important depending on - /// the operator to generate correct results. - private func liftLeft(_ transform: @escaping (Signal) -> (Signal) -> Signal) -> (SignalProducer) -> SignalProducer { - return { otherProducer in - return SignalProducer { observer, outerDisposable in - otherProducer.startWithSignal { otherSignal, otherDisposable in - outerDisposable += otherDisposable - - self.startWithSignal { signal, disposable in - outerDisposable.add(disposable) - - transform(signal)(otherSignal).observe(observer) - } - } - } - } - } - - - /// Lift a binary Signal operator to operate upon a Signal and a - /// SignalProducer instead. - /// - /// In other words, this will create a new `SignalProducer` which will apply - /// the given `Signal` operator to _every_ `Signal` created from the two - /// producers, just as if the operator had been applied to each `Signal` - /// yielded from `start()`. - /// - /// - parameters: - /// - transform: A binary operator to lift. - /// - /// - returns: A binary operator that works on `Signal` and returns - /// `SignalProducer`. - public func lift(_ transform: @escaping (Signal) -> (Signal) -> Signal) -> (Signal) -> SignalProducer { - return { otherSignal in - return SignalProducer { observer, outerDisposable in - let (wrapperSignal, otherSignalObserver) = Signal.pipe() - - // Avoid memory leak caused by the direct use of the given - // signal. - // - // See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/2758 - // for the details. - outerDisposable += ActionDisposable { - otherSignalObserver.sendInterrupted() - } - outerDisposable += otherSignal.observe(otherSignalObserver) - - self.startWithSignal { signal, disposable in - outerDisposable += disposable - outerDisposable += transform(signal)(wrapperSignal).observe(observer) - } - } - } - } - - - /// Map each value in the producer to a new value. - /// - /// - parameters: - /// - transform: A closure that accepts a value and returns a different - /// value. - /// - /// - returns: A signal producer that, when started, will send a mapped - /// value of `self.` - public func map(_ transform: @escaping (Value) -> U) -> SignalProducer { - return lift { $0.map(transform) } - } - - /// Map errors in the producer to a new error. - /// - /// - parameters: - /// - transform: A closure that accepts an error object and returns a - /// different error. - /// - /// - returns: A producer that emits errors of new type. - public func mapError(_ transform: @escaping (Error) -> F) -> SignalProducer { - return lift { $0.mapError(transform) } - } - - /// Preserve only the values of the producer that pass the given predicate. - /// - /// - parameters: - /// - predicate: A closure that accepts value and returns `Bool` denoting - /// whether value has passed the test. - /// - /// - returns: A producer that, when started, will send only the values - /// passing the given predicate. - public func filter(_ predicate: @escaping (Value) -> Bool) -> SignalProducer { - return lift { $0.filter(predicate) } - } - - /// Yield the first `count` values from the input producer. - /// - /// - precondition: `count` must be non-negative number. - /// - /// - parameters: - /// - count: A number of values to take from the signal. - /// - /// - returns: A producer that, when started, will yield the first `count` - /// values from `self`. - public func take(first count: Int) -> SignalProducer { - return lift { $0.take(first: count) } - } - - /// Yield an array of values when `self` completes. - /// - /// - note: When `self` completes without collecting any value, it will send - /// an empty array of values. - /// - /// - returns: A producer that, when started, will yield an array of values - /// when `self` completes. - public func collect() -> SignalProducer<[Value], Error> { - return lift { $0.collect() } - } - - /// Yield an array of values until it reaches a certain count. - /// - /// - precondition: `count` should be greater than zero. - /// - /// - note: When the count is reached the array is sent and the signal - /// starts over yielding a new array of values. - /// - /// - note: When `self` completes any remaining values will be sent, the - /// last array may not have `count` values. Alternatively, if were - /// not collected any values will sent an empty array of values. - /// - /// - returns: A producer that, when started, collects at most `count` - /// values from `self`, forwards them as a single array and - /// completes. - public func collect(count: Int) -> SignalProducer<[Value], Error> { - precondition(count > 0) - return lift { $0.collect(count: count) } - } - - /// Yield an array of values based on a predicate which matches the values - /// collected. - /// - /// - note: When `self` completes any remaining values will be sent, the - /// last array may not match `predicate`. Alternatively, if were not - /// collected any values will sent an empty array of values. - /// - /// ```` - /// let (producer, observer) = SignalProducer.buffer(1) - /// - /// producer - /// .collect { values in values.reduce(0, combine: +) == 8 } - /// .startWithNext { print($0) } - /// - /// observer.sendNext(1) - /// observer.sendNext(3) - /// observer.sendNext(4) - /// observer.sendNext(7) - /// observer.sendNext(1) - /// observer.sendNext(5) - /// observer.sendNext(6) - /// observer.sendCompleted() - /// - /// // Output: - /// // [1, 3, 4] - /// // [7, 1] - /// // [5, 6] - /// ```` - /// - /// - parameters: - /// - predicate: Predicate to match when values should be sent (returning - /// `true`) or alternatively when they should be collected - /// (where it should return `false`). The most recent value - /// (`next`) is included in `values` and will be the end of - /// the current array of values if the predicate returns - /// `true`. - /// - /// - returns: A producer that, when started, collects values passing the - /// predicate and, when `self` completes, forwards them as a - /// single array and complets. - public func collect(_ predicate: @escaping (_ values: [Value]) -> Bool) -> SignalProducer<[Value], Error> { - return lift { $0.collect(predicate) } - } - - /// Yield an array of values based on a predicate which matches the values - /// collected and the next value. - /// - /// - note: When `self` completes any remaining values will be sent, the - /// last array may not match `predicate`. Alternatively, if no - /// values were collected an empty array will be sent. - /// - /// ```` - /// let (producer, observer) = SignalProducer.buffer(1) - /// - /// producer - /// .collect { values, next in next == 7 } - /// .startWithNext { print($0) } - /// - /// observer.sendNext(1) - /// observer.sendNext(1) - /// observer.sendNext(7) - /// observer.sendNext(7) - /// observer.sendNext(5) - /// observer.sendNext(6) - /// observer.sendCompleted() - /// - /// // Output: - /// // [1, 1] - /// // [7] - /// // [7, 5, 6] - /// ```` - /// - /// - parameters: - /// - predicate: Predicate to match when values should be sent (returning - /// `true`) or alternatively when they should be collected - /// (where it should return `false`). The most recent value - /// (`next`) is not included in `values` and will be the - /// start of the next array of values if the predicate - /// returns `true`. - /// - /// - returns: A signal that will yield an array of values based on a - /// predicate which matches the values collected and the next - /// value. - public func collect(_ predicate: @escaping (_ values: [Value], _ next: Value) -> Bool) -> SignalProducer<[Value], Error> { - return lift { $0.collect(predicate) } - } - - /// Forward all events onto the given scheduler, instead of whichever - /// scheduler they originally arrived upon. - /// - /// - parameters: - /// - scheduler: A scheduler to deliver events on. - /// - /// - returns: A producer that, when started, will yield `self` values on - /// provided scheduler. - public func observe(on scheduler: SchedulerProtocol) -> SignalProducer { - return lift { $0.observe(on: scheduler) } - } - - /// Combine the latest value of the receiver with the latest value from the - /// given producer. - /// - /// - note: The returned producer will not send a value until both inputs - /// have sent at least one value each. - /// - /// - note: If either producer is interrupted, the returned producer will - /// also be interrupted. - /// - /// - parameters: - /// - other: A producer to combine `self`'s value with. - /// - /// - returns: A producer that, when started, will yield a tuple containing - /// values of `self` and given producer. - public func combineLatest(with other: SignalProducer) -> SignalProducer<(Value, U), Error> { - return liftLeft(Signal.combineLatest)(other) - } - - /// Combine the latest value of the receiver with the latest value from - /// the given signal. - /// - /// - note: The returned producer will not send a value until both inputs - /// have sent at least one value each. - /// - /// - note: If either input is interrupted, the returned producer will also - /// be interrupted. - /// - /// - parameters: - /// - other: A signal to combine `self`'s value with. - /// - /// - returns: A producer that, when started, will yield a tuple containing - /// values of `self` and given signal. - public func combineLatest(with other: Signal) -> SignalProducer<(Value, U), Error> { - return lift(Signal.combineLatest(with:))(other) - } - - /// Delay `next` and `completed` events by the given interval, forwarding - /// them on the given scheduler. - /// - /// - note: `failed` and `interrupted` events are always scheduled - /// immediately. - /// - /// - parameters: - /// - interval: Interval to delay `next` and `completed` events by. - /// - scheduler: A scheduler to deliver delayed events on. - /// - /// - returns: A producer that, when started, will delay `next` and - /// `completed` events and will yield them on given scheduler. - public func delay(_ interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> SignalProducer { - return lift { $0.delay(interval, on: scheduler) } - } - - /// Skip the first `count` values, then forward everything afterward. - /// - /// - parameters: - /// - count: A number of values to skip. - /// - /// - returns: A producer that, when started, will skip the first `count` - /// values, then forward everything afterward. - public func skip(first count: Int) -> SignalProducer { - return lift { $0.skip(first: count) } - } - - /// Treats all Events from the input producer as plain values, allowing them - /// to be manipulated just like any other value. - /// - /// In other words, this brings Events “into the monad.” - /// - /// - note: When a Completed or Failed event is received, the resulting - /// producer will send the Event itself and then complete. When an - /// `interrupted` event is received, the resulting producer will - /// send the `Event` itself and then interrupt. - /// - /// - returns: A producer that sends events as its values. - public func materialize() -> SignalProducer, NoError> { - return lift { $0.materialize() } - } - - /// Forward the latest value from `self` with the value from `sampler` as a - /// tuple, only when `sampler` sends a `next` event. - /// - /// - note: If `sampler` fires before a value has been observed on `self`, - /// nothing happens. - /// - /// - parameters: - /// - sampler: A producer that will trigger the delivery of `next` event - /// from `self`. - /// - /// - returns: A producer that will send values from `self` and `sampler`, - /// sampled (possibly multiple times) by `sampler`, then complete - /// once both input producers have completed, or interrupt if - /// either input producer is interrupted. - public func sample(with sampler: SignalProducer) -> SignalProducer<(Value, T), Error> { - return liftLeft(Signal.sample(with:))(sampler) - } - - /// Forward the latest value from `self` with the value from `sampler` as a - /// tuple, only when `sampler` sends a `next` event. - /// - /// - note: If `sampler` fires before a value has been observed on `self`, - /// nothing happens. - /// - /// - parameters: - /// - sampler: A signal that will trigger the delivery of `next` event - /// from `self`. - /// - /// - returns: A producer that, when started, will send values from `self` - /// and `sampler`, sampled (possibly multiple times) by - /// `sampler`, then complete once both input producers have - /// completed, or interrupt if either input producer is - /// interrupted. - public func sample(with sampler: Signal) -> SignalProducer<(Value, T), Error> { - return lift(Signal.sample(with:))(sampler) - } - - /// Forward the latest value from `self` whenever `sampler` sends a `next` - /// event. - /// - /// - note: If `sampler` fires before a value has been observed on `self`, - /// nothing happens. - /// - /// - parameters: - /// - sampler: A producer that will trigger the delivery of `next` event - /// from `self`. - /// - /// - returns: A producer that, when started, will send values from `self`, - /// sampled (possibly multiple times) by `sampler`, then complete - /// once both input producers have completed, or interrupt if - /// either input producer is interrupted. - public func sample(on sampler: SignalProducer<(), NoError>) -> SignalProducer { - return liftLeft(Signal.sample(on:))(sampler) - } - - /// Forward the latest value from `self` whenever `sampler` sends a `next` - /// event. - /// - /// - note: If `sampler` fires before a value has been observed on `self`, - /// nothing happens. - /// - /// - parameters: - /// - trigger: A signal whose `next` or `completed` events will start the - /// deliver of events on `self`. - /// - /// - returns: A producer that will send values from `self`, sampled - /// (possibly multiple times) by `sampler`, then complete once - /// both inputs have completed, or interrupt if either input is - /// interrupted. - public func sample(on sampler: Signal<(), NoError>) -> SignalProducer { - return lift(Signal.sample(on:))(sampler) - } - - /// Forwards events from `self` until `lifetime` ends, at which point the - /// returned producer will complete. - /// - /// - parameters: - /// - lifetime: A lifetime whose `ended` signal will cause the returned - /// producer to complete. - /// - /// - returns: A producer that will deliver events until `lifetime` ends. - public func take(during lifetime: Lifetime) -> SignalProducer { - return take(until: lifetime.ended) - } - - /// Forward events from `self` until `trigger` sends a `next` or `completed` - /// event, at which point the returned producer will complete. - /// - /// - parameters: - /// - trigger: A producer whose `next` or `completed` events will stop the - /// delivery of `next` events from `self`. - /// - /// - returns: A producer that will deliver events until `trigger` sends - /// `next` or `completed` events. - public func take(until trigger: SignalProducer<(), NoError>) -> SignalProducer { - // This should be the implementation of this method: - // return liftRight(Signal.takeUntil)(trigger) - // - // However, due to a Swift miscompilation (with `-O`) we need to inline - // `liftRight` here. - // - // See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/2751 for - // more details. - // - // This can be reverted once tests with -O work correctly. - return SignalProducer { observer, outerDisposable in - self.startWithSignal { signal, disposable in - outerDisposable.add(disposable) - - trigger.startWithSignal { triggerSignal, triggerDisposable in - outerDisposable += triggerDisposable - - signal.take(until: triggerSignal).observe(observer) - } - } - } - } - - /// Forward events from `self` until `trigger` sends a Next or Completed - /// event, at which point the returned producer will complete. - /// - /// - parameters: - /// - trigger: A signal whose `next` or `completed` events will stop the - /// delivery of `next` events from `self`. - /// - /// - returns: A producer that will deliver events until `trigger` sends - /// `next` or `completed` events. - public func take(until trigger: Signal<(), NoError>) -> SignalProducer { - return lift(Signal.take(until:))(trigger) - } - - /// Do not forward any values from `self` until `trigger` sends a `next` - /// or `completed`, at which point the returned producer behaves exactly - /// like `producer`. - /// - /// - parameters: - /// - trigger: A producer whose `next` or `completed` events will start - /// the deliver of events on `self`. - /// - /// - returns: A producer that will deliver events once the `trigger` sends - /// `next` or `completed` events. - public func skip(until trigger: SignalProducer<(), NoError>) -> SignalProducer { - return liftRight(Signal.skip(until:))(trigger) - } - - /// Do not forward any values from `self` until `trigger` sends a `next` - /// or `completed`, at which point the returned signal behaves exactly like - /// `signal`. - /// - /// - parameters: - /// - trigger: A signal whose `next` or `completed` events will start the - /// deliver of events on `self`. - /// - /// - returns: A producer that will deliver events once the `trigger` sends - /// `next` or `completed` events. - public func skip(until trigger: Signal<(), NoError>) -> SignalProducer { - return lift(Signal.skip(until:))(trigger) - } - - /// Forward events from `self` with history: values of the returned producer - /// are a tuple whose first member is the previous value and whose second - /// member is the current value. `initial` is supplied as the first member - /// when `self` sends its first value. - /// - /// - parameters: - /// - initial: A value that will be combined with the first value sent by - /// `self`. - /// - /// - returns: A producer that sends tuples that contain previous and - /// current sent values of `self`. - public func combinePrevious(_ initial: Value) -> SignalProducer<(Value, Value), Error> { - return lift { $0.combinePrevious(initial) } - } - - /// Send only the final value and then immediately completes. - /// - /// - parameters: - /// - initial: Initial value for the accumulator. - /// - combine: A closure that accepts accumulator and sent value of - /// `self`. - /// - /// - returns: A producer that sends accumulated value after `self` - /// completes. - public func reduce(_ initial: U, _ combine: @escaping (U, Value) -> U) -> SignalProducer { - return lift { $0.reduce(initial, combine) } - } - - /// Aggregate `self`'s values into a single combined value. When `self` - /// emits its first value, `combine` is invoked with `initial` as the first - /// argument and that emitted value as the second argument. The result is - /// emitted from the producer returned from `scan`. That result is then - /// passed to `combine` as the first argument when the next value is - /// emitted, and so on. - /// - /// - parameters: - /// - initial: Initial value for the accumulator. - /// - combine: A closure that accepts accumulator and sent value of - /// `self`. - /// - /// - returns: A producer that sends accumulated value each time `self` - /// emits own value. - public func scan(_ initial: U, _ combine: @escaping (U, Value) -> U) -> SignalProducer { - return lift { $0.scan(initial, combine) } - } - - /// Forward only those values from `self` which do not pass `isRepeat` with - /// respect to the previous value. - /// - /// - note: The first value is always forwarded. - /// - /// - returns: A producer that does not send two equal values sequentially. - public func skipRepeats(_ isRepeat: @escaping (Value, Value) -> Bool) -> SignalProducer { - return lift { $0.skipRepeats(isRepeat) } - } - - /// Do not forward any values from `self` until `predicate` returns false, - /// at which point the returned producer behaves exactly like `self`. - /// - /// - parameters: - /// - predicate: A closure that accepts a value and returns whether `self` - /// should still not forward that value to a `producer`. - /// - /// - returns: A producer that sends only forwarded values from `self`. - public func skip(while predicate: @escaping (Value) -> Bool) -> SignalProducer { - return lift { $0.skip(while: predicate) } - } - - /// Forward events from `self` until `replacement` begins sending events. - /// - /// - parameters: - /// - replacement: A producer to wait to wait for values from and start - /// sending them as a replacement to `self`'s values. - /// - /// - returns: A producer which passes through `next`, `failed`, and - /// `interrupted` events from `self` until `replacement` sends an - /// event, at which point the returned producer will send that - /// event and switch to passing through events from `replacement` - /// instead, regardless of whether `self` has sent events - /// already. - public func take(untilReplacement signal: SignalProducer) -> SignalProducer { - return liftRight(Signal.take(untilReplacement:))(signal) - } - - /// Forwards events from `self` until `replacement` begins sending events. - /// - /// - parameters: - /// - replacement: A signal to wait to wait for values from and start - /// sending them as a replacement to `self`'s values. - /// - /// - returns: A producer which passes through `next`, `failed`, and - /// `interrupted` events from `self` until `replacement` sends an - /// event, at which point the returned producer will send that - /// event and switch to passing through events from `replacement` - /// instead, regardless of whether `self` has sent events - /// already. - public func take(untilReplacement signal: Signal) -> SignalProducer { - return lift(Signal.take(untilReplacement:))(signal) - } - - /// Wait until `self` completes and then forward the final `count` values - /// on the returned producer. - /// - /// - parameters: - /// - count: Number of last events to send after `self` completes. - /// - /// - returns: A producer that receives up to `count` values from `self` - /// after `self` completes. - public func take(last count: Int) -> SignalProducer { - return lift { $0.take(last: count) } - } - - /// Forward any values from `self` until `predicate` returns false, at which - /// point the returned producer will complete. - /// - /// - parameters: - /// - predicate: A closure that accepts value and returns `Bool` value - /// whether `self` should forward it to `signal` and continue - /// sending other events. - /// - /// - returns: A producer that sends events until the values sent by `self` - /// pass the given `predicate`. - public func take(while predicate: @escaping (Value) -> Bool) -> SignalProducer { - return lift { $0.take(while: predicate) } - } - - /// Zip elements of two producers into pairs. The elements of any Nth pair - /// are the Nth elements of the two input producers. - /// - /// - parameters: - /// - other: A producer to zip values with. - /// - /// - returns: A producer that sends tuples of `self` and `otherProducer`. - public func zip(with other: SignalProducer) -> SignalProducer<(Value, U), Error> { - return liftLeft(Signal.zip(with:))(other) - } - - /// Zip elements of this producer and a signal into pairs. The elements of - /// any Nth pair are the Nth elements of the two. - /// - /// - parameters: - /// - other: A signal to zip values with. - /// - /// - returns: A producer that sends tuples of `self` and `otherSignal`. - public func zip(with other: Signal) -> SignalProducer<(Value, U), Error> { - return lift(Signal.zip(with:))(other) - } - - /// Apply `operation` to values from `self` with `Success`ful results - /// forwarded on the returned producer and `Failure`s sent as `failed` - /// events. - /// - /// - parameters: - /// - operation: A closure that accepts a value and returns a `Result`. - /// - /// - returns: A producer that receives `Success`ful `Result` as `next` - /// event and `Failure` as `failed` event. - public func attempt(operation: @escaping (Value) -> Result<(), Error>) -> SignalProducer { - return lift { $0.attempt(operation) } - } - - /// Apply `operation` to values from `self` with `Success`ful results - /// mapped on the returned producer and `Failure`s sent as `failed` events. - /// - /// - parameters: - /// - operation: A closure that accepts a value and returns a result of - /// a mapped value as `Success`. - /// - /// - returns: A producer that sends mapped values from `self` if returned - /// `Result` is `Success`ful, `failed` events otherwise. - public func attemptMap(_ operation: @escaping (Value) -> Result) -> SignalProducer { - return lift { $0.attemptMap(operation) } - } - - /// Throttle values sent by the receiver, so that at least `interval` - /// seconds pass between each, then forwards them on the given scheduler. - /// - /// - note: If multiple values are received before the interval has elapsed, - /// the latest value is the one that will be passed on. - /// - /// - norw: If `self` terminates while a value is being throttled, that - /// value will be discarded and the returned producer will terminate - /// immediately. - /// - /// - parameters: - /// - interval: Number of seconds to wait between sent values. - /// - scheduler: A scheduler to deliver events on. - /// - /// - returns: A producer that sends values at least `interval` seconds - /// appart on a given scheduler. - public func throttle(_ interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> SignalProducer { - return lift { $0.throttle(interval, on: scheduler) } - } - - /// Debounce values sent by the receiver, such that at least `interval` - /// seconds pass after the receiver has last sent a value, then - /// forward the latest value on the given scheduler. - /// - /// - note: If multiple values are received before the interval has elapsed, - /// the latest value is the one that will be passed on. - /// - /// - note: If `self` terminates while a value is being debounced, - /// that value will be discarded and the returned producer will - /// terminate immediately. - /// - /// - parameters: - /// - interval: A number of seconds to wait before sending a value. - /// - scheduler: A scheduler to send values on. - /// - /// - returns: A producer that sends values that are sent from `self` at - /// least `interval` seconds apart. - public func debounce(_ interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> SignalProducer { - return lift { $0.debounce(interval, on: scheduler) } - } - - /// Forward events from `self` until `interval`. Then if producer isn't - /// completed yet, fails with `error` on `scheduler`. - /// - /// - note: If the interval is 0, the timeout will be scheduled immediately. - /// The producer must complete synchronously (or on a faster - /// scheduler) to avoid the timeout. - /// - /// - parameters: - /// - interval: Number of seconds to wait for `self` to complete. - /// - error: Error to send with `failed` event if `self` is not completed - /// when `interval` passes. - /// - scheduler: A scheduler to deliver error on. - /// - /// - returns: A producer that sends events for at most `interval` seconds, - /// then, if not `completed` - sends `error` with `failed` event - /// on `scheduler`. - public func timeout(after interval: TimeInterval, raising error: Error, on scheduler: DateSchedulerProtocol) -> SignalProducer { - return lift { $0.timeout(after: interval, raising: error, on: scheduler) } - } -} - -extension SignalProducerProtocol where Value: OptionalProtocol { - /// Unwraps non-`nil` values and forwards them on the returned signal, `nil` - /// values are dropped. - /// - /// - returns: A producer that sends only non-nil values. - public func skipNil() -> SignalProducer { - return lift { $0.skipNil() } - } -} - -extension SignalProducerProtocol where Value: EventProtocol, Error == NoError { - /// The inverse of materialize(), this will translate a producer of `Event` - /// _values_ into a producer of those events themselves. - /// - /// - returns: A producer that sends values carried by `self` events. - public func dematerialize() -> SignalProducer { - return lift { $0.dematerialize() } - } -} - -extension SignalProducerProtocol where Error == NoError { - /// Promote a producer that does not generate failures into one that can. - /// - /// - note: This does not actually cause failers to be generated for the - /// given producer, but makes it easier to combine with other - /// producers that may fail; for example, with operators like - /// `combineLatestWith`, `zipWith`, `flatten`, etc. - /// - /// - parameters: - /// - _ An `ErrorType`. - /// - /// - returns: A producer that has an instantiatable `ErrorType`. - public func promoteErrors(_: F.Type) -> SignalProducer { - return lift { $0.promoteErrors(F.self) } - } - - /// Forward events from `self` until `interval`. Then if producer isn't - /// completed yet, fails with `error` on `scheduler`. - /// - /// - note: If the interval is 0, the timeout will be scheduled immediately. - /// The producer must complete synchronously (or on a faster - /// scheduler) to avoid the timeout. - /// - /// - parameters: - /// - interval: Number of seconds to wait for `self` to complete. - /// - error: Error to send with `failed` event if `self` is not completed - /// when `interval` passes. - /// - scheudler: A scheduler to deliver error on. - /// - /// - returns: A producer that sends events for at most `interval` seconds, - /// then, if not `completed` - sends `error` with `failed` event - /// on `scheduler`. - public func timeout( - after interval: TimeInterval, - raising error: NewError, - on scheduler: DateSchedulerProtocol - ) -> SignalProducer { - return lift { $0.timeout(after: interval, raising: error, on: scheduler) } - } - - /// Wait for completion of `self`, *then* forward all events from - /// `replacement`. - /// - /// - note: All values sent from `self` are ignored. - /// - /// - parameters: - /// - replacement: A producer to start when `self` completes. - /// - /// - returns: A producer that sends events from `self` and then from - /// `replacement` when `self` completes. - public func then(_ replacement: SignalProducer) -> SignalProducer { - return self - .promoteErrors(NewError.self) - .then(replacement) - } -} - -extension SignalProducerProtocol where Value: Equatable { - /// Forward only those values from `self` which are not duplicates of the - /// immedately preceding value. - /// - /// - note: The first value is always forwarded. - /// - /// - returns: A producer that does not send two equal values sequentially. - public func skipRepeats() -> SignalProducer { - return lift { $0.skipRepeats() } - } -} - -extension SignalProducerProtocol { - /// Forward only those values from `self` that have unique identities across - /// the set of all values that have been seen. - /// - /// - note: This causes the identities to be retained to check for - /// uniqueness. - /// - /// - parameters: - /// - transform: A closure that accepts a value and returns identity - /// value. - /// - /// - returns: A producer that sends unique values during its lifetime. - public func uniqueValues(_ transform: @escaping (Value) -> Identity) -> SignalProducer { - return lift { $0.uniqueValues(transform) } - } -} - -extension SignalProducerProtocol where Value: Hashable { - /// Forward only those values from `self` that are unique across the set of - /// all values that have been seen. - /// - /// - note: This causes the values to be retained to check for uniqueness. - /// Providing a function that returns a unique value for each sent - /// value can help you reduce the memory footprint. - /// - /// - returns: A producer that sends unique values during its lifetime. - public func uniqueValues() -> SignalProducer { - return lift { $0.uniqueValues() } - } -} - -extension SignalProducerProtocol { - /// Injects side effects to be performed upon the specified producer events. - /// - /// - note: In a composed producer, `starting` is invoked in the reverse - /// direction of the flow of events. - /// - /// - parameters: - /// - starting: A closure that is invoked before the producer is started. - /// - started: A closure that is invoked after the producer is started. - /// - event: A closure that accepts an event and is invoked on every - /// received event. - /// - next: A closure that accepts a value from `next` event. - /// - failed: A closure that accepts error object and is invoked for - /// `failed` event. - /// - completed: A closure that is invoked for `completed` event. - /// - interrupted: A closure that is invoked for `interrupted` event. - /// - terminated: A closure that is invoked for any terminating event. - /// - disposed: A closure added as disposable when signal completes. - /// - /// - returns: A producer with attached side-effects for given event cases. - public func on( - starting: (() -> Void)? = nil, - started: (() -> Void)? = nil, - event: ((Event) -> Void)? = nil, - next: ((Value) -> Void)? = nil, - failed: ((Error) -> Void)? = nil, - completed: (() -> Void)? = nil, - interrupted: (() -> Void)? = nil, - terminated: (() -> Void)? = nil, - disposed: (() -> Void)? = nil - ) -> SignalProducer { - return SignalProducer { observer, compositeDisposable in - starting?() - defer { started?() } - - self.startWithSignal { signal, disposable in - compositeDisposable += disposable - compositeDisposable += signal - .on( - event: event, - failed: failed, - completed: completed, - interrupted: interrupted, - terminated: terminated, - disposed: disposed, - next: next - ) - .observe(observer) - } - } - } - - /// Start the returned producer on the given `Scheduler`. - /// - /// - note: This implies that any side effects embedded in the producer will - /// be performed on the given scheduler as well. - /// - /// - note: Events may still be sent upon other schedulers — this merely - /// affects where the `start()` method is run. - /// - /// - parameters: - /// - scheduler: A scheduler to deliver events on. - /// - /// - returns: A producer that will deliver events on given `scheduler` when - /// started. - public func start(on scheduler: SchedulerProtocol) -> SignalProducer { - return SignalProducer { observer, compositeDisposable in - compositeDisposable += scheduler.schedule { - self.startWithSignal { signal, signalDisposable in - compositeDisposable += signalDisposable - signal.observe(observer) - } - } - } - } -} - -extension SignalProducerProtocol { - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer) -> SignalProducer<(Value, B), Error> { - return a.combineLatest(with: b) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer) -> SignalProducer<(Value, B, C), Error> { - return combineLatest(a, b) - .combineLatest(with: c) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer) -> SignalProducer<(Value, B, C, D), Error> { - return combineLatest(a, b, c) - .combineLatest(with: d) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer) -> SignalProducer<(Value, B, C, D, E), Error> { - return combineLatest(a, b, c, d) - .combineLatest(with: e) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F), Error> { - return combineLatest(a, b, c, d, e) - .combineLatest(with: f) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G), Error> { - return combineLatest(a, b, c, d, e, f) - .combineLatest(with: g) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer, _ h: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G, H), Error> { - return combineLatest(a, b, c, d, e, f, g) - .combineLatest(with: h) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer, _ h: SignalProducer, _ i: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G, H, I), Error> { - return combineLatest(a, b, c, d, e, f, g, h) - .combineLatest(with: i) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. - public static func combineLatest(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer, _ h: SignalProducer, _ i: SignalProducer, _ j: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G, H, I, J), Error> { - return combineLatest(a, b, c, d, e, f, g, h, i) - .combineLatest(with: j) - .map(repack) - } - - /// Combines the values of all the given producers, in the manner described by - /// `combineLatestWith`. Will return an empty `SignalProducer` if the sequence is empty. - public static func combineLatest(_ producers: S) -> SignalProducer<[Value], Error> - where S.Iterator.Element == SignalProducer - { - var generator = producers.makeIterator() - if let first = generator.next() { - let initial = first.map { [$0] } - return IteratorSequence(generator).reduce(initial) { producer, next in - producer.combineLatest(with: next).map { $0.0 + [$0.1] } - } - } - - return .empty - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer) -> SignalProducer<(Value, B), Error> { - return a.zip(with: b) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer) -> SignalProducer<(Value, B, C), Error> { - return zip(a, b) - .zip(with: c) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer) -> SignalProducer<(Value, B, C, D), Error> { - return zip(a, b, c) - .zip(with: d) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer) -> SignalProducer<(Value, B, C, D, E), Error> { - return zip(a, b, c, d) - .zip(with: e) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F), Error> { - return zip(a, b, c, d, e) - .zip(with: f) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G), Error> { - return zip(a, b, c, d, e, f) - .zip(with: g) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer, _ h: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G, H), Error> { - return zip(a, b, c, d, e, f, g) - .zip(with: h) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer, _ h: SignalProducer, _ i: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G, H, I), Error> { - return zip(a, b, c, d, e, f, g, h) - .zip(with: i) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. - public static func zip(_ a: SignalProducer, _ b: SignalProducer, _ c: SignalProducer, _ d: SignalProducer, _ e: SignalProducer, _ f: SignalProducer, _ g: SignalProducer, _ h: SignalProducer, _ i: SignalProducer, _ j: SignalProducer) -> SignalProducer<(Value, B, C, D, E, F, G, H, I, J), Error> { - return zip(a, b, c, d, e, f, g, h, i) - .zip(with: j) - .map(repack) - } - - /// Zips the values of all the given producers, in the manner described by - /// `zipWith`. Will return an empty `SignalProducer` if the sequence is empty. - public static func zip(_ producers: S) -> SignalProducer<[Value], Error> - where S.Iterator.Element == SignalProducer - { - var generator = producers.makeIterator() - if let first = generator.next() { - let initial = first.map { [$0] } - return IteratorSequence(generator).reduce(initial) { producer, next in - producer.zip(with: next).map { $0.0 + [$0.1] } - } - } - - return .empty - } -} - -extension SignalProducerProtocol { - /// Repeat `self` a total of `count` times. In other words, start producer - /// `count` number of times, each one after previously started producer - /// completes. - /// - /// - note: Repeating `1` time results in an equivalent signal producer. - /// - /// - note: Repeating `0` times results in a producer that instantly - /// completes. - /// - /// - parameters: - /// - count: Number of repetitions. - /// - /// - returns: A signal producer start sequentially starts `self` after - /// previously started producer completes. - public func times(_ count: Int) -> SignalProducer { - precondition(count >= 0) - - if count == 0 { - return .empty - } else if count == 1 { - return producer - } - - return SignalProducer { observer, disposable in - let serialDisposable = SerialDisposable() - disposable += serialDisposable - - func iterate(_ current: Int) { - self.startWithSignal { signal, signalDisposable in - serialDisposable.innerDisposable = signalDisposable - - signal.observe { event in - if case .completed = event { - let remainingTimes = current - 1 - if remainingTimes > 0 { - iterate(remainingTimes) - } else { - observer.sendCompleted() - } - } else { - observer.action(event) - } - } - } - } - - iterate(count) - } - } - - /// Ignore failures up to `count` times. - /// - /// - precondition: `count` must be non-negative integer. - /// - /// - parameters: - /// - count: Number of retries. - /// - /// - returns: A signal producer that restarts up to `count` times. - public func retry(upTo count: Int) -> SignalProducer { - precondition(count >= 0) - - if count == 0 { - return producer - } else { - return flatMapError { _ in - self.retry(upTo: count - 1) - } - } - } - - /// Wait for completion of `self`, *then* forward all events from - /// `replacement`. Any failure or interruption sent from `self` is - /// forwarded immediately, in which case `replacement` will not be started, - /// and none of its events will be be forwarded. - /// - /// - note: All values sent from `self` are ignored. - /// - /// - parameters: - /// - replacement: A producer to start when `self` completes. - /// - /// - returns: A producer that sends events from `self` and then from - /// `replacement` when `self` completes. - public func then(_ replacement: SignalProducer) -> SignalProducer { - return SignalProducer { observer, observerDisposable in - self.startWithSignal { signal, signalDisposable in - observerDisposable += signalDisposable - - signal.observe { event in - switch event { - case let .failed(error): - observer.sendFailed(error) - case .completed: - observerDisposable += replacement.start(observer) - case .interrupted: - observer.sendInterrupted() - case .next: - break - } - } - } - } - } - - /// Wait for completion of `self`, *then* forward all events from - /// `replacement`. Any failure or interruption sent from `self` is - /// forwarded immediately, in which case `replacement` will not be started, - /// and none of its events will be be forwarded. - /// - /// - note: All values sent from `self` are ignored. - /// - /// - parameters: - /// - replacement: A producer to start when `self` completes. - /// - /// - returns: A producer that sends events from `self` and then from - /// `replacement` when `self` completes. - public func then(_ replacement: SignalProducer) -> SignalProducer { - return self.then(replacement.promoteErrors(Error.self)) - } - - /// Start the producer, then block, waiting for the first value. - /// - /// When a single value or error is sent, the returned `Result` will - /// represent those cases. However, when no values are sent, `nil` will be - /// returned. - /// - /// - returns: Result when single `next` or `failed` event is received. - /// `nil` when no events are received. - public func first() -> Result? { - return take(first: 1).single() - } - - /// Start the producer, then block, waiting for events: Next and - /// Completed. - /// - /// When a single value or error is sent, the returned `Result` will - /// represent those cases. However, when no values are sent, or when more - /// than one value is sent, `nil` will be returned. - /// - /// - returns: Result when single `next` or `failed` event is received. - /// `nil` when 0 or more than 1 events are received. - public func single() -> Result? { - let semaphore = DispatchSemaphore(value: 0) - var result: Result? - - take(first: 2).start { event in - switch event { - case let .next(value): - if result != nil { - // Move into failure state after recieving another value. - result = nil - return - } - result = .success(value) - case let .failed(error): - result = .failure(error) - semaphore.signal() - case .completed, .interrupted: - semaphore.signal() - } - } - - semaphore.wait() - return result - } - - /// Start the producer, then block, waiting for the last value. - /// - /// When a single value or error is sent, the returned `Result` will - /// represent those cases. However, when no values are sent, `nil` will be - /// returned. - /// - /// - returns: Result when single `next` or `failed` event is received. - /// `nil` when no events are received. - public func last() -> Result? { - return take(last: 1).single() - } - - /// Starts the producer, then blocks, waiting for completion. - /// - /// When a completion or error is sent, the returned `Result` will represent - /// those cases. - /// - /// - returns: Result when single `Completion` or `failed` event is - /// received. - public func wait() -> Result<(), Error> { - return then(SignalProducer<(), Error>(value: ())).last() ?? .success(()) - } - - /// Creates a new `SignalProducer` that will multicast values emitted by - /// the underlying producer, up to `capacity`. - /// This means that all clients of this `SignalProducer` will see the same - /// version of the emitted values/errors. - /// - /// The underlying `SignalProducer` will not be started until `self` is - /// started for the first time. When subscribing to this producer, all - /// previous values (up to `capacity`) will be emitted, followed by any new - /// values. - /// - /// If you find yourself needing *the current value* (the last buffered - /// value) you should consider using `PropertyType` instead, which, unlike - /// this operator, will guarantee at compile time that there's always a - /// buffered value. This operator is not recommended in most cases, as it - /// will introduce an implicit relationship between the original client and - /// the rest, so consider alternatives like `PropertyType`, or representing - /// your stream using a `Signal` instead. - /// - /// This operator is only recommended when you absolutely need to introduce - /// a layer of caching in front of another `SignalProducer`. - /// - /// - precondtion: `capacity` must be non-negative integer. - /// - /// - parameters: - /// - capcity: Number of values to hold. - /// - /// - returns: A caching producer that will hold up to last `capacity` - /// values. - public func replayLazily(upTo capacity: Int) -> SignalProducer { - precondition(capacity >= 0, "Invalid capacity: \(capacity)") - - // This will go "out of scope" when the returned `SignalProducer` goes - // out of scope. This lets us know when we're supposed to dispose the - // underlying producer. This is necessary because `struct`s don't have - // `deinit`. - let lifetimeToken = Lifetime.Token() - let lifetime = Lifetime(lifetimeToken) - - let state = Atomic(ReplayState(upTo: capacity)) - - let start: Atomic<(() -> Void)?> = Atomic { - // Start the underlying producer. - self - .take(during: lifetime) - .start { event in - let observers: Bag.Observer>? = state.modify { state in - defer { state.enqueue(event) } - return state.observers - } - observers?.forEach { $0.action(event) } - } - } - - return SignalProducer { observer, disposable in - // Don't dispose of the original producer until all observers - // have terminated. - disposable += { _ = lifetimeToken } - - while true { - var result: Result>! - state.modify { - result = $0.observe(observer) - } - - switch result! { - case let .success(token): - if let token = token { - disposable += { - state.modify { - $0.removeObserver(using: token) - } - } - } - - // Start the underlying producer if it has never been started. - start.swap(nil)?() - - // Terminate the replay loop. - return - - case let .failure(error): - error.values.forEach(observer.sendNext) - } - } - } - } -} - -/// Represents a recoverable error of an observer not being ready for an -/// attachment to a `ReplayState`, and the observer should replay the supplied -/// values before attempting to observe again. -private struct ReplayError: Error { - /// The values that should be replayed by the observer. - let values: [Value] -} - -private struct ReplayState { - let capacity: Int - - /// All cached values. - var values: [Value] = [] - - /// A termination event emitted by the underlying producer. - /// - /// This will be nil if termination has not occurred. - var terminationEvent: Event? - - /// The observers currently attached to the caching producer, or `nil` if the - /// caching producer was terminated. - var observers: Bag.Observer>? = Bag() - - /// The set of in-flight replay buffers. - var replayBuffers: [ObjectIdentifier: [Value]] = [:] - - /// Initialize the replay state. - /// - /// - parameters: - /// - capacity: The maximum amount of values which can be cached by the - /// replay state. - init(upTo capacity: Int) { - self.capacity = capacity - } - - /// Attempt to observe the replay state. - /// - /// - warning: Repeatedly observing the replay state with the same observer - /// should be avoided. - /// - /// - parameters: - /// - observer: The observer to be registered. - /// - /// - returns: - /// If the observer is successfully attached, a `Result.success` with the - /// corresponding removal token would be returned. Otherwise, a - /// `Result.failure` with a `ReplayError` would be returned. - mutating func observe(_ observer: Signal.Observer) -> Result> { - // Since the only use case is `replayLazily`, which always creates a unique - // `Observer` for every produced signal, we can use the ObjectIdentifier of - // the `Observer` to track them directly. - let id = ObjectIdentifier(observer) - - switch replayBuffers[id] { - case .none where !values.isEmpty: - // No in-flight replay buffers was found, but the `ReplayState` has one or - // more cached values in the `ReplayState`. The observer should replay - // them before attempting to observe again. - replayBuffers[id] = [] - return .failure(ReplayError(values: values)) - - case let .some(buffer) where !buffer.isEmpty: - // An in-flight replay buffer was found with one or more buffered values. - // The observer should replay them before attempting to observe again. - defer { replayBuffers[id] = [] } - return .failure(ReplayError(values: buffer)) - - case let .some(buffer) where buffer.isEmpty: - // Since an in-flight but empty replay buffer was found, the observer is - // ready to be attached to the `ReplayState`. - replayBuffers.removeValue(forKey: id) - - default: - // No values has to be replayed. The observer is ready to be attached to - // the `ReplayState`. - break - } - - if let event = terminationEvent { - observer.action(event) - } - - return .success(observers?.insert(observer)) - } - - /// Enqueue the supplied event to the replay state. - /// - /// - parameter: - /// - event: The event to be cached. - mutating func enqueue(_ event: Event) { - switch event { - case let .next(value): - for key in replayBuffers.keys { - replayBuffers[key]!.append(value) - } - - switch capacity { - case 0: - // With a capacity of zero, `state.values` can never be filled. - break - - case 1: - values = [value] - - default: - values.append(value) - - let overflow = values.count - capacity - if overflow > 0 { - values.removeFirst(overflow) - } - } - - case .completed, .failed, .interrupted: - // Disconnect all observers and prevent future attachments. - terminationEvent = event - observers = nil - } - } - - /// Remove the observer represented by the supplied token. - /// - /// - parameters: - /// - token: The token of the observer to be removed. - mutating func removeObserver(using token: RemovalToken) { - observers?.remove(using: token) - } -} - -/// Create a repeating timer of the given interval, with a reasonable default -/// leeway, sending updates on the given scheduler. -/// -/// - note: This timer will never complete naturally, so all invocations of -/// `start()` must be disposed to avoid leaks. -/// -/// - precondition: Interval must be non-negative number. -/// -/// - parameters: -/// - interval: An interval between invocations. -/// - scheduler: A scheduler to deliver events on. -/// -/// - returns: A producer that sends `NSDate` values every `interval` seconds. -public func timer(interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> SignalProducer { - // Apple's "Power Efficiency Guide for Mac Apps" recommends a leeway of - // at least 10% of the timer interval. - return timer(interval: interval, on: scheduler, leeway: interval * 0.1) -} - -/// Creates a repeating timer of the given interval, sending updates on the -/// given scheduler. -/// -/// - note: This timer will never complete naturally, so all invocations of -/// `start()` must be disposed to avoid leaks. -/// -/// - precondition: Interval must be non-negative number. -/// -/// - precondition: Leeway must be non-negative number. -/// -/// - parameters: -/// - interval: An interval between invocations. -/// - scheduler: A scheduler to deliver events on. -/// - leeway: Interval leeway. Apple's "Power Efficiency Guide for Mac Apps" -/// recommends a leeway of at least 10% of the timer interval. -/// -/// - returns: A producer that sends `NSDate` values every `interval` seconds. -public func timer(interval: TimeInterval, on scheduler: DateSchedulerProtocol, leeway: TimeInterval) -> SignalProducer { - precondition(interval >= 0) - precondition(leeway >= 0) - - return SignalProducer { observer, compositeDisposable in - compositeDisposable += scheduler.schedule(after: scheduler.currentDate.addingTimeInterval(interval), - interval: interval, - leeway: leeway, - action: { observer.sendNext(scheduler.currentDate) }) - } -} diff --git a/ReactiveCocoa/Swift/TupleExtensions.swift b/ReactiveCocoa/Swift/TupleExtensions.swift deleted file mode 100644 index 31e096c30e..0000000000 --- a/ReactiveCocoa/Swift/TupleExtensions.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// TupleExtensions.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-12-20. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -/// Adds a value into an N-tuple, returning an (N+1)-tuple. -/// -/// Supports creating tuples up to 10 elements long. -internal func repack(_ t: (A, B), value: C) -> (A, B, C) { - return (t.0, t.1, value) -} - -internal func repack(_ t: (A, B, C), value: D) -> (A, B, C, D) { - return (t.0, t.1, t.2, value) -} - -internal func repack(_ t: (A, B, C, D), value: E) -> (A, B, C, D, E) { - return (t.0, t.1, t.2, t.3, value) -} - -internal func repack(_ t: (A, B, C, D, E), value: F) -> (A, B, C, D, E, F) { - return (t.0, t.1, t.2, t.3, t.4, value) -} - -internal func repack(_ t: (A, B, C, D, E, F), value: G) -> (A, B, C, D, E, F, G) { - return (t.0, t.1, t.2, t.3, t.4, t.5, value) -} - -internal func repack(_ t: (A, B, C, D, E, F, G), value: H) -> (A, B, C, D, E, F, G, H) { - return (t.0, t.1, t.2, t.3, t.4, t.5, t.6, value) -} - -internal func repack(_ t: (A, B, C, D, E, F, G, H), value: I) -> (A, B, C, D, E, F, G, H, I) { - return (t.0, t.1, t.2, t.3, t.4, t.5, t.6, t.7, value) -} - -internal func repack(_ t: (A, B, C, D, E, F, G, H, I), value: J) -> (A, B, C, D, E, F, G, H, I, J) { - return (t.0, t.1, t.2, t.3, t.4, t.5, t.6, t.7, t.8, value) -} diff --git a/ReactiveCocoa/Swift/UnidirectionalBinding.swift b/ReactiveCocoa/Swift/UnidirectionalBinding.swift deleted file mode 100644 index a782f31414..0000000000 --- a/ReactiveCocoa/Swift/UnidirectionalBinding.swift +++ /dev/null @@ -1,278 +0,0 @@ -import enum Result.NoError - -precedencegroup BindingPrecedence { - associativity: right - - // Binds tighter than assignment but looser than everything else - higherThan: AssignmentPrecedence -} - -infix operator <~ : BindingPrecedence - -/// Describes a target to which can be bound. -public protocol BindingTarget: class { - associatedtype Value - - /// The lifetime of `self`. The binding operators use this to determine when - /// the binding should be teared down. - var lifetime: Lifetime { get } - - /// Consume a value from the binding. - func consume(_ value: Value) - - /// Binds a signal to a target, updating the target's value to the latest - /// value sent by the signal. - /// - /// - note: The binding will automatically terminate when the target is - /// deinitialized, or when the signal sends a `completed` event. - /// - /// ```` - /// let property = MutableProperty(0) - /// let signal = Signal({ /* do some work after some time */ }) - /// property <~ signal - /// ```` - /// - /// ```` - /// let property = MutableProperty(0) - /// let signal = Signal({ /* do some work after some time */ }) - /// let disposable = property <~ signal - /// ... - /// // Terminates binding before property dealloc or signal's - /// // `completed` event. - /// disposable.dispose() - /// ```` - /// - /// - parameters: - /// - target: A target to be bond to. - /// - signal: A signal to bind. - /// - /// - returns: A disposable that can be used to terminate binding before the - /// deinitialization of the target or the signal's `completed` - /// event. - @discardableResult - static func <~ (target: Self, signal: Source) -> Disposable? where Source.Value == Value, Source.Error == NoError -} - -extension BindingTarget { - /// Binds a signal to a target, updating the target's value to the latest - /// value sent by the signal. - /// - /// - note: The binding will automatically terminate when the target is - /// deinitialized, or when the signal sends a `completed` event. - /// - /// ```` - /// let property = MutableProperty(0) - /// let signal = Signal({ /* do some work after some time */ }) - /// property <~ signal - /// ```` - /// - /// ```` - /// let property = MutableProperty(0) - /// let signal = Signal({ /* do some work after some time */ }) - /// let disposable = property <~ signal - /// ... - /// // Terminates binding before property dealloc or signal's - /// // `completed` event. - /// disposable.dispose() - /// ```` - /// - /// - parameters: - /// - target: A target to be bond to. - /// - signal: A signal to bind. - /// - /// - returns: A disposable that can be used to terminate binding before the - /// deinitialization of the target or the signal's `completed` - /// event. - @discardableResult - public static func <~ (target: Self, signal: Source) -> Disposable? where Source.Value == Value, Source.Error == NoError { - return signal - .take(during: target.lifetime) - .observeNext { [weak target] value in - target?.consume(value) - } - } - - /// Binds a producer to a target, updating the target's value to the latest - /// value sent by the producer. - /// - /// - note: The binding will automatically terminate when the target is - /// deinitialized, or when the producer sends a `completed` event. - /// - /// ```` - /// let property = MutableProperty(0) - /// let producer = SignalProducer(value: 1) - /// property <~ producer - /// print(property.value) // prints `1` - /// ```` - /// - /// ```` - /// let property = MutableProperty(0) - /// let producer = SignalProducer({ /* do some work after some time */ }) - /// let disposable = (property <~ producer) - /// ... - /// // Terminates binding before property dealloc or - /// // signal's `completed` event. - /// disposable.dispose() - /// ```` - /// - /// - parameters: - /// - target: A target to be bond to. - /// - producer: A producer to bind. - /// - /// - returns: A disposable that can be used to terminate binding before the - /// deinitialization of the target or the producer's `completed - /// event. - @discardableResult - public static func <~ (target: Self, producer: Source) -> Disposable where Source.Value == Value, Source.Error == NoError { - var disposable: Disposable! - - producer - .take(during: target.lifetime) - .startWithSignal { signal, signalDisposable in - disposable = signalDisposable - target <~ signal - } - - return disposable - } - - /// Binds a property to a target, updating the target's value to the latest - /// value sent by the property. - /// - /// - note: The binding will automatically terminate when either the target or - /// the property deinitializes. - /// - /// ```` - /// let dstProperty = MutableProperty(0) - /// let srcProperty = ConstantProperty(10) - /// dstProperty <~ srcProperty - /// print(dstProperty.value) // prints 10 - /// ```` - /// - /// ```` - /// let dstProperty = MutableProperty(0) - /// let srcProperty = ConstantProperty(10) - /// let disposable = (dstProperty <~ srcProperty) - /// ... - /// disposable.dispose() // terminate the binding earlier if - /// // needed - /// ```` - /// - /// - parameters: - /// - target: A target to be bond to. - /// - property: A property to bind. - /// - /// - returns: A disposable that can be used to terminate binding before the - /// deinitialization of the target or the source property. - @discardableResult - public static func <~ (target: Self, property: Source) -> Disposable where Source.Value == Value { - return target <~ property.producer - } -} - -extension BindingTarget where Value: OptionalProtocol { - /// Binds a signal to a target, updating the target's value to the latest - /// value sent by the signal. - /// - /// - note: The binding will automatically terminate when the target is - /// deinitialized, or when the signal sends a `completed` event. - /// - /// ```` - /// let property = MutableProperty(0) - /// let signal = Signal({ /* do some work after some time */ }) - /// property <~ signal - /// ```` - /// - /// ```` - /// let property = MutableProperty(0) - /// let signal = Signal({ /* do some work after some time */ }) - /// let disposable = property <~ signal - /// ... - /// // Terminates binding before property dealloc or signal's - /// // `completed` event. - /// disposable.dispose() - /// ```` - /// - /// - parameters: - /// - target: A target to be bond to. - /// - signal: A signal to bind. - /// - /// - returns: A disposable that can be used to terminate binding before the - /// deinitialization of the target or the signal's `completed` - /// event. - @discardableResult - public static func <~ (target: Self, signal: Source) -> Disposable? where Source.Value == Value.Wrapped, Source.Error == NoError { - return target <~ signal.map(Value.init(reconstructing:)) - } - - /// Binds a producer to a target, updating the target's value to the latest - /// value sent by the producer. - /// - /// - note: The binding will automatically terminate when the target is - /// deinitialized, or when the producer sends a `completed` event. - /// - /// ```` - /// let property = MutableProperty(0) - /// let producer = SignalProducer(value: 1) - /// property <~ producer - /// print(property.value) // prints `1` - /// ```` - /// - /// ```` - /// let property = MutableProperty(0) - /// let producer = SignalProducer({ /* do some work after some time */ }) - /// let disposable = (property <~ producer) - /// ... - /// // Terminates binding before property dealloc or - /// // signal's `completed` event. - /// disposable.dispose() - /// ```` - /// - /// - parameters: - /// - target: A target to be bond to. - /// - producer: A producer to bind. - /// - /// - returns: A disposable that can be used to terminate binding before the - /// deinitialization of the target or the producer's `completed` - /// event. - @discardableResult - public static func <~ (target: Self, producer: Source) -> Disposable where Source.Value == Value.Wrapped, Source.Error == NoError { - return target <~ producer.map(Value.init(reconstructing:)) - } - - /// Binds a property to a target, updating the target's value to the latest - /// value sent by the property. - /// - /// - note: The binding will automatically terminate when either the target or - /// the property deinitializes. - /// - /// ```` - /// let dstProperty = MutableProperty(0) - /// let srcProperty = ConstantProperty(10) - /// dstProperty <~ srcProperty - /// print(dstProperty.value) // prints 10 - /// ```` - /// - /// ```` - /// let dstProperty = MutableProperty(0) - /// let srcProperty = ConstantProperty(10) - /// let disposable = (dstProperty <~ srcProperty) - /// ... - /// disposable.dispose() // terminate the binding earlier if - /// // needed - /// ```` - /// - /// - note: The binding will automatically terminate when either property is - /// deinitialized. - /// - /// - parameters: - /// - target: A target to be bond to. - /// - property: A property to bind. - /// - /// - returns: A disposable that can be used to terminate binding before the - /// deinitialization of the target or the source property. - @discardableResult - public static func <~ (target: Self, property: Source) -> Disposable where Source.Value == Value.Wrapped { - return target <~ property.producer - } -} diff --git a/ReactiveCocoa/Synchronizing.swift b/ReactiveCocoa/Synchronizing.swift new file mode 100644 index 0000000000..5302308b67 --- /dev/null +++ b/ReactiveCocoa/Synchronizing.swift @@ -0,0 +1,7 @@ +import Foundation + +internal func synchronized(_ token: AnyObject, execute: () throws -> Result) rethrows -> Result { + objc_sync_enter(token) + defer { objc_sync_exit(token) } + return try execute() +} diff --git a/ReactiveCocoa/UIKit/UIActivityIndicatorView.swift b/ReactiveCocoa/UIKit/UIActivityIndicatorView.swift new file mode 100644 index 0000000000..fa3ad35949 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIActivityIndicatorView.swift @@ -0,0 +1,11 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIActivityIndicatorView { + /// Sets whether the activity indicator should be animating. + public var isAnimating: BindingTarget { + return makeBindingTarget { $1 ? $0.startAnimating() : $0.stopAnimating() } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIApplication.swift b/ReactiveCocoa/UIKit/UIApplication.swift new file mode 100644 index 0000000000..9a2cb1bac4 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIApplication.swift @@ -0,0 +1,11 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIApplication { + /// Sets the number as the badge of the app icon in Springboard. + public var applicationIconBadgeNumber: BindingTarget { + return makeBindingTarget({ $0.applicationIconBadgeNumber = $1 }) + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIBarButtonItem.swift b/ReactiveCocoa/UIKit/UIBarButtonItem.swift new file mode 100644 index 0000000000..879d927bc6 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIBarButtonItem.swift @@ -0,0 +1,51 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIBarButtonItem { + /// The current associated action of `self`. + private var associatedAction: Atomic<(action: CocoaAction, disposable: Disposable?)?> { + return associatedValue { _ in Atomic(nil) } + } + + /// The action to be triggered when the button is pressed. It also controls + /// the enabled state of the button. + public var pressed: CocoaAction? { + get { + return associatedAction.value?.action + } + + nonmutating set { + base.target = newValue + base.action = newValue.map { _ in CocoaAction.selector } + + associatedAction + .swap(newValue.map { action in + let disposable = isEnabled <~ action.isEnabled + return (action, disposable) + })? + .disposable?.dispose() + } + } + + /// Sets the style of the bar button item. + public var style: BindingTarget { + return makeBindingTarget { $0.style = $1 } + } + + /// Sets the width of the bar button item. + public var width: BindingTarget { + return makeBindingTarget { $0.width = $1 } + } + + /// Sets the possible titles of the bar button item. + public var possibleTitles: BindingTarget?> { + return makeBindingTarget { $0.possibleTitles = $1 } + } + + /// Sets the custom view of the bar button item. + public var customView: BindingTarget { + return makeBindingTarget { $0.customView = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIBarItem.swift b/ReactiveCocoa/UIKit/UIBarItem.swift new file mode 100644 index 0000000000..bfae00054d --- /dev/null +++ b/ReactiveCocoa/UIKit/UIBarItem.swift @@ -0,0 +1,21 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIBarItem { + /// Sets whether the bar item is enabled. + public var isEnabled: BindingTarget { + return makeBindingTarget { $0.isEnabled = $1 } + } + + /// Sets image of bar item. + public var image: BindingTarget { + return makeBindingTarget { $0.image = $1 } + } + + /// Sets the title of bar item. + public var title: BindingTarget { + return makeBindingTarget { $0.title = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIButton.swift b/ReactiveCocoa/UIKit/UIButton.swift new file mode 100644 index 0000000000..d3a23f6d56 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIButton.swift @@ -0,0 +1,50 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIButton { + /// The action to be triggered when the button is pressed. It also controls + /// the enabled state of the button. + public var pressed: CocoaAction? { + get { + return associatedAction.withValue { info in + return info.flatMap { info in + return info.controlEvents == pressEvent ? info.action : nil + } + } + } + + nonmutating set { + setAction(newValue, for: pressEvent) + } + } + + private var pressEvent: UIControl.Event { + if #available(iOS 9.0, tvOS 9.0, *) { + return .primaryActionTriggered + } else { + return .touchUpInside + } + } + + /// Sets the title of the button for its normal state. + public var title: BindingTarget { + return makeBindingTarget { $0.setTitle($1, for: .normal) } + } + + /// Sets the title of the button for the specified state. + public func title(for state: UIControl.State) -> BindingTarget { + return makeBindingTarget { $0.setTitle($1, for: state) } + } + + /// Sets the image of the button for the specified state. + public func image(for state: UIControl.State) -> BindingTarget { + return makeBindingTarget { $0.setImage($1, for: state) } + } + + /// Sets the image of the button for the .normal state + public var image: BindingTarget { + return image(for: .normal) + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UICollectionView.swift b/ReactiveCocoa/UIKit/UICollectionView.swift new file mode 100644 index 0000000000..18261905b0 --- /dev/null +++ b/ReactiveCocoa/UIKit/UICollectionView.swift @@ -0,0 +1,10 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UICollectionView { + public var reloadData: BindingTarget<()> { + return makeBindingTarget { base, _ in base.reloadData() } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIControl.swift b/ReactiveCocoa/UIKit/UIControl.swift new file mode 100644 index 0000000000..f25b728db1 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIControl.swift @@ -0,0 +1,113 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIControl { + /// The current associated action of `self`, with its registered event mask + /// and its disposable. + internal var associatedAction: Atomic<(action: CocoaAction, controlEvents: UIControl.Event, disposable: Disposable)?> { + return associatedValue { _ in Atomic(nil) } + } + + /// Set the associated action of `self` to `action`, and register it for the + /// control events specified by `controlEvents`. + /// + /// - parameters: + /// - action: The action to be associated. + /// - controlEvents: The control event mask. + /// - disposable: An outside disposable that will be bound to the scope of + /// the given `action`. + internal func setAction(_ action: CocoaAction?, for controlEvents: UIControl.Event, disposable: Disposable? = nil) { + associatedAction.modify { associatedAction in + associatedAction?.disposable.dispose() + + if let action = action { + base.addTarget(action, action: CocoaAction.selector, for: controlEvents) + + let compositeDisposable = CompositeDisposable() + compositeDisposable += isEnabled <~ action.isEnabled + compositeDisposable += { [weak base = self.base] in + base?.removeTarget(action, action: CocoaAction.selector, for: controlEvents) + } + compositeDisposable += disposable + + associatedAction = (action, controlEvents, ScopedDisposable(compositeDisposable)) + } else { + associatedAction = nil + } + } + } + + /// Create a signal which sends a `value` event for each of the specified + /// control events. + /// + /// - note: If you mean to observe the **value** of `self` with regard to a particular + /// control event, `mapControlEvents(_:_:)` should be used instead. + /// + /// - parameters: + /// - controlEvents: The control event mask. + /// + /// - returns: A signal that sends the control each time the control event occurs. + public func controlEvents(_ controlEvents: UIControl.Event) -> Signal { + return mapControlEvents(controlEvents, { $0 }) + } + + /// Create a signal which sends a `value` event for each of the specified + /// control events. + /// + /// - important: You should use `mapControlEvents` in general unless the state of + /// the control — e.g. `text`, `state` — is not concerned. In other + /// words, you should avoid using `map` on a control event signal to + /// extract the state from the control. + /// + /// - note: For observations that could potentially manipulate the first responder + /// status of `base`, `mapControlEvents(_:_:)` is made aware of the potential + /// recursion induced by UIKit and would collect the values for the control + /// events accordingly using the given transform. + /// + /// - parameters: + /// - controlEvents: The control event mask. + /// - transform: A transform to reduce `Base`. + /// + /// - returns: A signal that sends the reduced value from the control each time the + /// control event occurs. + public func mapControlEvents(_ controlEvents: UIControl.Event, _ transform: @escaping (Base) -> Value) -> Signal { + return Signal { observer, signalLifetime in + let receiver = CocoaTarget(observer) { transform($0 as! Base) } + base.addTarget(receiver, + action: #selector(receiver.invoke), + for: controlEvents) + + let disposable = lifetime.ended.observeCompleted(observer.sendCompleted) + + signalLifetime.observeEnded { [weak base] in + disposable?.dispose() + + base?.removeTarget(receiver, + action: #selector(receiver.invoke), + for: controlEvents) + } + } + } + + @available(*, unavailable, renamed: "controlEvents(_:)") + public func trigger(for controlEvents: UIControl.Event) -> Signal<(), Never> { + fatalError() + } + + /// Sets whether the control is enabled. + public var isEnabled: BindingTarget { + return makeBindingTarget { $0.isEnabled = $1 } + } + + /// Sets whether the control is selected. + public var isSelected: BindingTarget { + return makeBindingTarget { $0.isSelected = $1 } + } + + /// Sets whether the control is highlighted. + public var isHighlighted: BindingTarget { + return makeBindingTarget { $0.isHighlighted = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIGestureRecognizer.swift b/ReactiveCocoa/UIKit/UIGestureRecognizer.swift new file mode 100644 index 0000000000..4d02b645d7 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIGestureRecognizer.swift @@ -0,0 +1,25 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIGestureRecognizer { + /// Create a signal which sends a `next` event for each gesture event + /// + /// - returns: A trigger signal. + public var stateChanged: Signal { + return Signal { observer, signalLifetime in + let receiver = CocoaTarget(observer) { gestureRecognizer in + return gestureRecognizer as! Base + } + base.addTarget(receiver, action: #selector(receiver.invoke)) + + let disposable = lifetime.ended.observeCompleted(observer.sendCompleted) + + signalLifetime.observeEnded { [weak base] in + disposable?.dispose() + base?.removeTarget(receiver, action: #selector(receiver.invoke)) + } + } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIImageView.swift b/ReactiveCocoa/UIKit/UIImageView.swift new file mode 100644 index 0000000000..ca37327e2b --- /dev/null +++ b/ReactiveCocoa/UIKit/UIImageView.swift @@ -0,0 +1,16 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIImageView { + /// Sets the image of the image view. + public var image: BindingTarget { + return makeBindingTarget { $0.image = $1 } + } + + /// Sets the image of the image view for its highlighted state. + public var highlightedImage: BindingTarget { + return makeBindingTarget { $0.highlightedImage = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIKitReusableComponents.swift b/ReactiveCocoa/UIKit/UIKitReusableComponents.swift new file mode 100644 index 0000000000..9108df6fa6 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIKitReusableComponents.swift @@ -0,0 +1,18 @@ +#if canImport(UIKit) && !os(watchOS) +import UIKit +import ReactiveSwift + +@objc public protocol Reusable: AnyObject { + func prepareForReuse() +} + +extension Reactive where Base: NSObject, Base: Reusable { + public var prepareForReuse: Signal<(), Never> { + return trigger(for: #selector(base.prepareForReuse)) + } +} + +extension UITableViewCell: Reusable {} +extension UITableViewHeaderFooterView: Reusable {} +extension UICollectionReusableView: Reusable {} +#endif diff --git a/ReactiveCocoa/UIKit/UILabel.swift b/ReactiveCocoa/UIKit/UILabel.swift new file mode 100644 index 0000000000..4877eff964 --- /dev/null +++ b/ReactiveCocoa/UIKit/UILabel.swift @@ -0,0 +1,21 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UILabel { + /// Sets the text of the label. + public var text: BindingTarget { + return makeBindingTarget { $0.text = $1 } + } + + /// Sets the attributed text of the label. + public var attributedText: BindingTarget { + return makeBindingTarget { $0.attributedText = $1 } + } + + /// Sets the color of the text of the label. + public var textColor: BindingTarget { + return makeBindingTarget { $0.textColor = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UINavigationBar.swift b/ReactiveCocoa/UIKit/UINavigationBar.swift new file mode 100644 index 0000000000..9888216721 --- /dev/null +++ b/ReactiveCocoa/UIKit/UINavigationBar.swift @@ -0,0 +1,11 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UINavigationBar { + /// Sets the barTintColor of the navigation bar. + public var barTintColor: BindingTarget { + return makeBindingTarget { $0.barTintColor = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UINavigationItem.swift b/ReactiveCocoa/UIKit/UINavigationItem.swift new file mode 100644 index 0000000000..5df555ebc3 --- /dev/null +++ b/ReactiveCocoa/UIKit/UINavigationItem.swift @@ -0,0 +1,79 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UINavigationItem { + /// Sets the title of the navigation item. + public var title: BindingTarget { + return makeBindingTarget { $0.title = $1 } + } + + /// Sets the title view of the navigation item. + public var titleView: BindingTarget { + return makeBindingTarget { $0.titleView = $1 } + } + +#if os(iOS) + /// Sets the prompt of the navigation item. + public var prompt: BindingTarget { + return makeBindingTarget { $0.prompt = $1 } + } + + /// Sets the back button item of the navigation item. + public var backBarButtonItem: BindingTarget { + return makeBindingTarget { $0.backBarButtonItem = $1 } + } + + /// Sets the `hidesBackButton` property of the navigation item. + public var hidesBackButton: BindingTarget { + return makeBindingTarget { $0.hidesBackButton = $1 } + } +#endif + + /// Sets the left bar button items of the navigation item. + public var leftBarButtonItems: BindingTarget<[UIBarButtonItem]?> { + return makeBindingTarget { $0.leftBarButtonItems = $1 } + } + + /// Sets the right bar button items of the navigation item. + public var rightBarButtonItems: BindingTarget<[UIBarButtonItem]?> { + return makeBindingTarget { $0.rightBarButtonItems = $1 } + } + + /// Sets the left bar button item of the navigation item. + public var leftBarButtonItem: BindingTarget { + return makeBindingTarget { $0.leftBarButtonItem = $1 } + } + + /// Sets the right bar button item of the navigation item. + public var rightBarButtonItem: BindingTarget { + return makeBindingTarget { $0.rightBarButtonItem = $1 } + } + +#if os(iOS) + /// Sets the `leftItemsSupplementBackButton` property of the navigation item. + @available(iOS 5.0, *) + public var leftItemsSupplementBackButton: BindingTarget { + return makeBindingTarget { $0.leftItemsSupplementBackButton = $1 } + } + + /// Sets the large title display mode of the navigation item. + @available(iOS 11.0, *) + public var largeTitleDisplayMode: BindingTarget { + return makeBindingTarget { $0.largeTitleDisplayMode = $1 } + } + + /// Sets the search controller of the navigation item. + @available(iOS 11.0, *) + public var searchController: BindingTarget { + return makeBindingTarget { $0.searchController = $1 } + } + + /// Sets the `hidesSearchBarWhenScrolling` property of the navigation item. + @available(iOS 11.0, *) + public var hidesSearchBarWhenScrolling: BindingTarget { + return makeBindingTarget { $0.hidesSearchBarWhenScrolling = $1 } + } +#endif +} +#endif diff --git a/ReactiveCocoa/UIKit/UIProgressView.swift b/ReactiveCocoa/UIKit/UIProgressView.swift new file mode 100644 index 0000000000..2d175c58e3 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIProgressView.swift @@ -0,0 +1,11 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIProgressView { + /// Sets the relative progress to be reflected by the progress view. + public var progress: BindingTarget { + return makeBindingTarget { $0.progress = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIResponder.swift b/ReactiveCocoa/UIKit/UIResponder.swift new file mode 100644 index 0000000000..2b00709673 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIResponder.swift @@ -0,0 +1,16 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIResponder { + /// Asks UIKit to make this object the first responder in its window. + public var becomeFirstResponder: BindingTarget<()> { + return makeBindingTarget { base, _ in base.becomeFirstResponder() } + } + + /// Notifies this object that it has been asked to relinquish its status as first responder in its window. + public var resignFirstResponder: BindingTarget<()> { + return makeBindingTarget { base, _ in base.resignFirstResponder() } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIScrollView.swift b/ReactiveCocoa/UIKit/UIScrollView.swift new file mode 100644 index 0000000000..66ed1486e9 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIScrollView.swift @@ -0,0 +1,43 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIScrollView { + /// Sets the content inset of the scroll view. + public var contentInset: BindingTarget { + return makeBindingTarget { $0.contentInset = $1 } + } + + /// Sets the scroll indicator insets of the scroll view. + public var scrollIndicatorInsets: BindingTarget { + return makeBindingTarget { $0.scrollIndicatorInsets = $1 } + } + + /// Sets whether scrolling the scroll view is enabled. + public var isScrollEnabled: BindingTarget { + return makeBindingTarget { $0.isScrollEnabled = $1 } + } + + /// Sets the zoom scale of the scroll view. + public var zoomScale: BindingTarget { + return makeBindingTarget { $0.zoomScale = $1 } + } + + /// Sets the minimum zoom scale of the scroll view. + public var minimumZoomScale: BindingTarget { + return makeBindingTarget { $0.minimumZoomScale = $1 } + } + + /// Sets the maximum zoom scale of the scroll view. + public var maximumZoomScale: BindingTarget { + return makeBindingTarget { $0.maximumZoomScale = $1 } + } + + #if os(iOS) + /// Sets whether the scroll view scrolls to the top when the menu is tapped. + public var scrollsToTop: BindingTarget { + return makeBindingTarget { $0.scrollsToTop = $1 } + } + #endif +} +#endif diff --git a/ReactiveCocoa/UIKit/UISegmentedControl.swift b/ReactiveCocoa/UIKit/UISegmentedControl.swift new file mode 100644 index 0000000000..25e9c7919b --- /dev/null +++ b/ReactiveCocoa/UIKit/UISegmentedControl.swift @@ -0,0 +1,16 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UISegmentedControl { + /// Changes the selected segment of the segmented control. + public var selectedSegmentIndex: BindingTarget { + return makeBindingTarget { $0.selectedSegmentIndex = $1 } + } + + /// A signal of indexes of selections emitted by the segmented control. + public var selectedSegmentIndexes: Signal { + return mapControlEvents(.valueChanged) { $0.selectedSegmentIndex } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UITabBarItem.swift b/ReactiveCocoa/UIKit/UITabBarItem.swift new file mode 100644 index 0000000000..9d34f58c7e --- /dev/null +++ b/ReactiveCocoa/UIKit/UITabBarItem.swift @@ -0,0 +1,19 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UITabBarItem { + /// Sets the badge value of the tab bar item. + public var badgeValue: BindingTarget { + return makeBindingTarget { $0.badgeValue = $1 } + } + + + /// Sets the badge color of the tab bar item. + @available(iOS 10, *) + @available(tvOS 10, *) + public var badgeColor: BindingTarget { + return makeBindingTarget { $0.badgeColor = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UITableView.swift b/ReactiveCocoa/UIKit/UITableView.swift new file mode 100644 index 0000000000..d6be837820 --- /dev/null +++ b/ReactiveCocoa/UIKit/UITableView.swift @@ -0,0 +1,10 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UITableView { + public var reloadData: BindingTarget<()> { + return makeBindingTarget { base, _ in base.reloadData() } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UITextField.swift b/ReactiveCocoa/UIKit/UITextField.swift new file mode 100644 index 0000000000..0116cd6515 --- /dev/null +++ b/ReactiveCocoa/UIKit/UITextField.swift @@ -0,0 +1,61 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UITextField { + /// Sets the text of the text field. + public var text: BindingTarget { + return makeBindingTarget { $0.text = $1 } + } + + /// A signal of text values emitted by the text field upon end of editing. + /// + /// - note: To observe text values that change on all editing events, + /// see `continuousTextValues`. + public var textValues: Signal { + return mapControlEvents([.editingDidEnd, .editingDidEndOnExit]) { $0.text ?? "" } + } + + /// A signal of text values emitted by the text field upon any changes. + /// + /// - note: To observe text values only when editing ends, see `textValues`. + public var continuousTextValues: Signal { + return mapControlEvents(.allEditingEvents) { $0.text ?? "" } + } + + /// Sets the attributed text of the text field. + public var attributedText: BindingTarget { + return makeBindingTarget { $0.attributedText = $1 } + } + + /// Sets the placeholder text of the text field. + public var placeholder: BindingTarget { + return makeBindingTarget { $0.placeholder = $1 } + } + + /// Sets the textColor of the text field. + public var textColor: BindingTarget { + return makeBindingTarget { $0.textColor = $1 } + } + + /// A signal of attributed text values emitted by the text field upon end of editing. + /// + /// - note: To observe attributed text values that change on all editing events, + /// see `continuousAttributedTextValues`. + public var attributedTextValues: Signal { + return mapControlEvents([.editingDidEnd, .editingDidEndOnExit]) { $0.attributedText ?? NSAttributedString() } + } + + /// A signal of attributed text values emitted by the text field upon any changes. + /// + /// - note: To observe attributed text values only when editing ends, see `attributedTextValues`. + public var continuousAttributedTextValues: Signal { + return mapControlEvents(.allEditingEvents) { $0.attributedText ?? NSAttributedString() } + } + + /// Sets the secure text entry attribute on the text field. + public var isSecureTextEntry: BindingTarget { + return makeBindingTarget { $0.isSecureTextEntry = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UITextView.swift b/ReactiveCocoa/UIKit/UITextView.swift new file mode 100644 index 0000000000..b92dcc45e7 --- /dev/null +++ b/ReactiveCocoa/UIKit/UITextView.swift @@ -0,0 +1,80 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +private class TextViewDelegateProxy: DelegateProxy, UITextViewDelegate { + @objc func textViewDidChangeSelection(_ textView: UITextView) { + forwardee?.textViewDidChangeSelection?(textView) + } +} + +extension Reactive where Base: UITextView { + private var proxy: TextViewDelegateProxy { + return .proxy(for: base, + setter: #selector(setter: base.delegate), + getter: #selector(getter: base.delegate)) + } + + /// Sets the text of the text view. + public var text: BindingTarget { + return makeBindingTarget { $0.text = $1 } + } + + private func textValues(forName name: NSNotification.Name) -> Signal { + return NotificationCenter.default + .reactive + .notifications(forName: name, object: base) + .take(during: lifetime) + .map { ($0.object as! UITextView).text! } + } + + /// A signal of text values emitted by the text view upon end of editing. + /// + /// - note: To observe text values that change on all editing events, + /// see `continuousTextValues`. + public var textValues: Signal { + return textValues(forName: UITextView.textDidEndEditingNotification) + } + + /// A signal of text values emitted by the text view upon any changes. + /// + /// - note: To observe text values only when editing ends, see `textValues`. + public var continuousTextValues: Signal { + return textValues(forName: UITextView.textDidChangeNotification) + } + + /// Sets the attributed text of the text view. + public var attributedText: BindingTarget { + return makeBindingTarget { $0.attributedText = $1 } + } + + private func attributedTextValues(forName name: NSNotification.Name) -> Signal { + return NotificationCenter.default + .reactive + .notifications(forName: name, object: base) + .take(during: lifetime) + .map { ($0.object as! UITextView).attributedText! } + } + + /// A signal of attributed text values emitted by the text view upon end of editing. + /// + /// - note: To observe attributed text values that change on all editing events, + /// see `continuousAttributedTextValues`. + public var attributedTextValues: Signal { + return attributedTextValues(forName: UITextView.textDidEndEditingNotification) + } + + /// A signal of attributed text values emitted by the text view upon any changes. + /// + /// - note: To observe text values only when editing ends, see `attributedTextValues`. + public var continuousAttributedTextValues: Signal { + return attributedTextValues(forName: UITextView.textDidChangeNotification) + } + + /// A signal of range values emitted by the text view upon any selection change. + public var selectedRangeValues: Signal { + return proxy.intercept(#selector(UITextViewDelegate.textViewDidChangeSelection)) + .map { [unowned base] in base.selectedRange } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIView.swift b/ReactiveCocoa/UIKit/UIView.swift new file mode 100644 index 0000000000..85628b866d --- /dev/null +++ b/ReactiveCocoa/UIKit/UIView.swift @@ -0,0 +1,31 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIView { + /// Sets the alpha value of the view. + public var alpha: BindingTarget { + return makeBindingTarget { $0.alpha = $1 } + } + + /// Sets whether the view is hidden. + public var isHidden: BindingTarget { + return makeBindingTarget { $0.isHidden = $1 } + } + + /// Sets whether the view accepts user interactions. + public var isUserInteractionEnabled: BindingTarget { + return makeBindingTarget { $0.isUserInteractionEnabled = $1 } + } + + /// Sets the background color of the view. + public var backgroundColor: BindingTarget { + return makeBindingTarget { $0.backgroundColor = $1 } + } + + /// Sets the tintColor of the view + public var tintColor: BindingTarget { + return makeBindingTarget { $0.tintColor = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/UIViewController.swift b/ReactiveCocoa/UIKit/UIViewController.swift new file mode 100644 index 0000000000..272e04f2d5 --- /dev/null +++ b/ReactiveCocoa/UIKit/UIViewController.swift @@ -0,0 +1,41 @@ +#if canImport(UIKit) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIViewController { + /// Set's the title of the view controller. + public var title: BindingTarget { + return makeBindingTarget({ $0.title = $1 }) + } + + /// A signal that sends a value event every time `viewWillAppear` is invoked. + public var viewWillAppear: Signal { + return trigger(for: #selector(Base.viewWillAppear)) + } + + /// A signal that sends a value event every time `viewDidAppear` is invoked. + public var viewDidAppear: Signal { + return trigger(for: #selector(Base.viewDidAppear)) + } + + /// A signal that sends a value event every time `viewWillDisappear` is invoked. + public var viewWillDisappear: Signal { + return trigger(for: #selector(Base.viewWillDisappear)) + } + + /// A signal that sends a value event every time `viewDidDisappear` is invoked. + public var viewDidDisappear: Signal { + return trigger(for: #selector(Base.viewDidDisappear)) + } + + /// A signal that sends a value event every time `viewWillLayoutSubviews` is invoked. + public var viewWillLayoutSubviews: Signal { + return trigger(for: #selector(Base.viewWillLayoutSubviews)) + } + + /// A signal that sends a value event every time `viewDidLayoutSubviews` is invoked. + public var viewDidLayoutSubviews: Signal { + return trigger(for: #selector(Base.viewDidLayoutSubviews)) + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UIDatePicker.swift b/ReactiveCocoa/UIKit/iOS/UIDatePicker.swift new file mode 100644 index 0000000000..13497588fc --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UIDatePicker.swift @@ -0,0 +1,16 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIDatePicker { + /// Sets the date of the date picker. + public var date: BindingTarget { + return makeBindingTarget { $0.date = $1 } + } + + /// A signal of dates emitted by the date picker. + public var dates: Signal { + return mapControlEvents(.valueChanged) { $0.date } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UIFeedbackGenerator.swift b/ReactiveCocoa/UIKit/iOS/UIFeedbackGenerator.swift new file mode 100644 index 0000000000..e18e40c3a8 --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UIFeedbackGenerator.swift @@ -0,0 +1,14 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +@available(iOS 10.0, *) +extension Reactive where Base: UIFeedbackGenerator { + /// Prepares the feedback generator. + public var prepare: BindingTarget<()> { + return makeBindingTarget { generator, _ in + generator.prepare() + } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UIImpactFeedbackGenerator.swift b/ReactiveCocoa/UIKit/iOS/UIImpactFeedbackGenerator.swift new file mode 100644 index 0000000000..b8ea887165 --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UIImpactFeedbackGenerator.swift @@ -0,0 +1,14 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +@available(iOS 10.0, *) +extension Reactive where Base: UIImpactFeedbackGenerator { + /// Triggers the feedback. + public var impactOccurred: BindingTarget<()> { + return makeBindingTarget { generator, _ in + generator.impactOccurred() + } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UIKeyboard.swift b/ReactiveCocoa/UIKit/iOS/UIKeyboard.swift new file mode 100644 index 0000000000..8148adb904 --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UIKeyboard.swift @@ -0,0 +1,111 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import UIKit +import ReactiveSwift + +/// The type of system keyboard events. +public enum KeyboardEvent { + case willShow + case didShow + case willHide + case didHide + case willChangeFrame + case didChangeFrame + + /// The name of the notification to observe system keyboard events. + fileprivate var notificationName: Notification.Name { + switch self { + case .willShow: + return UIResponder.keyboardWillShowNotification + case .didShow: + return UIResponder.keyboardDidShowNotification + case .willHide: + return UIResponder.keyboardWillHideNotification + case .didHide: + return UIResponder.keyboardDidHideNotification + case .willChangeFrame: + return UIResponder.keyboardWillChangeFrameNotification + case .didChangeFrame: + return UIResponder.keyboardDidChangeFrameNotification + } + } +} + +/// The context of an upcoming change in the frame of the system keyboard. +public struct KeyboardChangeContext { + private let base: [AnyHashable: Any] + + /// The event type of the system keyboard. + public let event: KeyboardEvent + + /// The current frame of the system keyboard. + public var beginFrame: CGRect { + return base[UIResponder.keyboardFrameBeginUserInfoKey] as! CGRect + } + + /// The final frame of the system keyboard. + public var endFrame: CGRect { + return base[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect + } + + /// The animation curve which the system keyboard will use to animate the + /// change in its frame. + public var animationCurve: UIView.AnimationCurve { + let value = base[UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber + return UIView.AnimationCurve(rawValue: value.intValue)! + } + + /// The duration in which the system keyboard expects to animate the change in + /// its frame. + public var animationDuration: Double { + return base[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double + } + + /// Indicates whether the change is triggered locally. Used in iPad + /// multitasking, where all foreground apps would be notified of any changes + /// in the system keyboard's frame. + @available(iOS 9.0, *) + public var isLocal: Bool { + return base[UIResponder.keyboardIsLocalUserInfoKey] as! Bool + } + + fileprivate init(userInfo: [AnyHashable: Any], event: KeyboardEvent) { + base = userInfo + self.event = event + } +} + +extension Reactive where Base: NotificationCenter { + /// Create a `Signal` that notifies whenever the system keyboard announce specified event. + /// + /// - parameters: + /// - event: The type of system keyboard event to observe. + /// + /// - returns: A `Signal` that emits the context of system keyboard event. + public func keyboard(_ event: KeyboardEvent) -> Signal { + return notifications(forName: event.notificationName) + .map { notification in KeyboardChangeContext(userInfo: notification.userInfo!, event: event) } + } + + /// Create a `Signal` that notifies whenever the system keyboard announces specified events. + /// + /// - parameters: + /// - first: First type of system keyboard event to observe. + /// - second: Second type of system keyboard event to observe. + /// - tail: Rest of the types of system keyboard events to observe. + /// + /// - returns: A `Signal` that emits the context of system keyboard events. + public func keyboard(_ first: KeyboardEvent, _ second: KeyboardEvent, _ tail: KeyboardEvent...) -> Signal { + let events = [first, second] + tail + return .merge(events.map(keyboard)) + } + + /// Create a `Signal` that notifies whenever the system keyboard announces an + /// upcoming change in its frame. + /// + /// - returns: A `Signal` that emits the context of every change in the + /// system keyboard's frame. + public var keyboardChange: Signal { + return keyboard(.willChangeFrame) + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UINotificationFeedbackGenerator.swift b/ReactiveCocoa/UIKit/iOS/UINotificationFeedbackGenerator.swift new file mode 100644 index 0000000000..aa6405529e --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UINotificationFeedbackGenerator.swift @@ -0,0 +1,12 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +@available(iOS 10.0, *) +extension Reactive where Base: UINotificationFeedbackGenerator { + /// Triggers the feedback. + public var notificationOccurred: BindingTarget { + return makeBindingTarget { $0.notificationOccurred($1) } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UIPickerView.swift b/ReactiveCocoa/UIKit/iOS/UIPickerView.swift new file mode 100644 index 0000000000..b1431e0773 --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UIPickerView.swift @@ -0,0 +1,44 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import Foundation +import ReactiveSwift +import UIKit + +private class PickerViewDelegateProxy: DelegateProxy, UIPickerViewDelegate { + @objc func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + forwardee?.pickerView?(pickerView, didSelectRow: row, inComponent: component) + } +} + +extension Reactive where Base: UIPickerView { + + private var proxy: PickerViewDelegateProxy { + return .proxy(for: base, + setter: #selector(setter: base.delegate), + getter: #selector(getter: base.delegate)) + } + + /// Sets the selected row in the specified component, without animating the + /// position. + public func selectedRow(inComponent component: Int) -> BindingTarget { + return makeBindingTarget { $0.selectRow($1, inComponent: component, animated: false) } + } + + /// Reloads all components + public var reloadAllComponents: BindingTarget<()> { + return makeBindingTarget { base, _ in base.reloadAllComponents() } + } + + /// Reloads the specified component + public var reloadComponent: BindingTarget { + return makeBindingTarget { $0.reloadComponent($1) } + } + + /// Create a signal which sends a `value` event for each row selection + /// + /// - returns: A trigger signal. + public var selections: Signal<(row: Int, component: Int), Never> { + return proxy.intercept(#selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:))) + .map { (row: $0[1] as! Int, component: $0[2] as! Int) } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UIRefreshControl.swift b/ReactiveCocoa/UIKit/iOS/UIRefreshControl.swift new file mode 100644 index 0000000000..8d180df8dc --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UIRefreshControl.swift @@ -0,0 +1,33 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UIRefreshControl { + /// Sets whether the refresh control should be refreshing. + public var isRefreshing: BindingTarget { + return makeBindingTarget { $1 ? $0.beginRefreshing() : $0.endRefreshing() } + } + + /// Sets the attributed title of the refresh control. + public var attributedTitle: BindingTarget { + return makeBindingTarget { $0.attributedTitle = $1 } + } + + /// The action to be triggered when the refresh control is refreshed. It + /// also controls the enabled and refreshing states of the refresh control. + public var refresh: CocoaAction? { + get { + return associatedAction.withValue { info in + return info.flatMap { info in + return info.controlEvents == .valueChanged ? info.action : nil + } + } + } + + nonmutating set { + let disposable = newValue.flatMap { isRefreshing <~ $0.isExecuting } + setAction(newValue, for: .valueChanged, disposable: disposable) + } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UISearchBar.swift b/ReactiveCocoa/UIKit/iOS/UISearchBar.swift new file mode 100644 index 0000000000..2ec4d5a98d --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UISearchBar.swift @@ -0,0 +1,118 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +private class SearchBarDelegateProxy: DelegateProxy, UISearchBarDelegate { + @objc func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + forwardee?.searchBarTextDidBeginEditing?(searchBar) + } + + @objc func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + forwardee?.searchBarTextDidEndEditing?(searchBar) + } + + @objc func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + forwardee?.searchBar?(searchBar, textDidChange: searchText) + } + + @objc func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + forwardee?.searchBarCancelButtonClicked?(searchBar) + } + + @objc func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + forwardee?.searchBarSearchButtonClicked?(searchBar) + } + + @objc func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) { + forwardee?.searchBarBookmarkButtonClicked?(searchBar) + } + + @objc func searchBarResultsListButtonClicked(_ searchBar: UISearchBar) { + forwardee?.searchBarResultsListButtonClicked?(searchBar) + } + + @objc func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { + forwardee?.searchBar?(searchBar, selectedScopeButtonIndexDidChange: selectedScope) + } +} + +extension Reactive where Base: UISearchBar { + private var proxy: SearchBarDelegateProxy { + // TODO: Mac Catalyst UISearchBarDelegate issue + // Related: https://github.com/ReactiveX/RxSwift/issues/2161 + _ = DelegateProxy.self + + return .proxy(for: base, + setter: #selector(setter: base.delegate), + getter: #selector(getter: base.delegate)) + } + + /// Sets the text of the search bar. + public var text: BindingTarget { + return makeBindingTarget { $0.text = $1 } + } + + /// Sets the selected scope button index of the search bar. + public var selectedScopeButtonIndex: BindingTarget { + return makeBindingTarget { $0.selectedScopeButtonIndex = $1 } + } + + /// A signal of text values emitted by the search bar upon end of editing. + /// + /// - note: To observe text values that change on all editing events, + /// see `continuousTextValues`. + public var textValues: Signal { + return proxy.intercept(#selector(UISearchBarDelegate.searchBarTextDidEndEditing)) + .map { [unowned base] in base.text } + } + + /// A signal of text values emitted by the search bar upon any changes. + /// + /// - note: To observe text values only when editing ends, see `textValues`. + public var continuousTextValues: Signal { + return proxy.intercept(#selector(proxy.searchBar(_:textDidChange:))) + .map { [unowned base] in base.text } + } + + /// A signal of the latest selected scope button index upon any user selection. + public var selectedScopeButtonIndices: Signal { + return proxy.intercept(#selector(proxy.searchBar(_:selectedScopeButtonIndexDidChange:))) + .map { $0[1] as! Int } + } + + /// A void signal emitted by the search bar upon any click on the cancel button + public var cancelButtonClicked: Signal { + return proxy.intercept(#selector(proxy.searchBarCancelButtonClicked)) + } + + /// A void signal emitted by the search bar upon any click on the search button + public var searchButtonClicked: Signal { + return proxy.intercept(#selector(proxy.searchBarSearchButtonClicked(_:))) + } + + /// A void signal emitted by the search bar upon any click on the bookmark button + public var bookmarkButtonClicked: Signal { + return proxy.intercept(#selector(proxy.searchBarBookmarkButtonClicked)) + } + + /// A void signal emitted by the search bar upon any click on the bookmark button + public var resultsListButtonClicked: Signal { + return proxy.intercept(#selector(proxy.searchBarResultsListButtonClicked)) + } + + /// A void signal emitted by the search bar upon start of editing + public var textDidBeginEditing: Signal { + return proxy.intercept(#selector(proxy.searchBarTextDidBeginEditing)) + } + + /// A void signal emitted by the search bar upon end of editing + public var textDidEndEditing: Signal { + return proxy.intercept(#selector(proxy.searchBarTextDidEndEditing)) + } + + /// Shows and hides the cancel button of the search bar + public var showsCancelButton: BindingTarget { + return makeBindingTarget { $0.showsCancelButton = $1 } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UISelectionFeedbackGenerator.swift b/ReactiveCocoa/UIKit/iOS/UISelectionFeedbackGenerator.swift new file mode 100644 index 0000000000..ffedaac423 --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UISelectionFeedbackGenerator.swift @@ -0,0 +1,14 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +@available(iOS 10.0, *) +extension Reactive where Base: UISelectionFeedbackGenerator { + /// Triggers the feedback. + public var selectionChanged: BindingTarget<()> { + return makeBindingTarget { generator, _ in + generator.selectionChanged() + } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UISlider.swift b/ReactiveCocoa/UIKit/iOS/UISlider.swift new file mode 100644 index 0000000000..d6b6396cef --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UISlider.swift @@ -0,0 +1,31 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import UIKit +import ReactiveSwift + +extension Reactive where Base: UISlider { + + /// Sets slider's value. + public var value: BindingTarget { + return makeBindingTarget { $0.value = $1 } + } + + /// Sets slider's minimum value. + public var minimumValue: BindingTarget { + return makeBindingTarget { $0.minimumValue = $1 } + } + + /// Sets slider's maximum value. + public var maximumValue: BindingTarget { + return makeBindingTarget { $0.maximumValue = $1 } + } + + /// A signal of float values emitted by the slider while being dragged by + /// the user. + /// + /// - note: If slider's `isContinuous` property is `false` then values are + /// sent only when user releases the slider. + public var values: Signal { + return mapControlEvents(.valueChanged) { $0.value } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UIStepper.swift b/ReactiveCocoa/UIKit/iOS/UIStepper.swift new file mode 100644 index 0000000000..f89ebf121f --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UIStepper.swift @@ -0,0 +1,28 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import UIKit +import ReactiveSwift + +extension Reactive where Base: UIStepper { + + /// Sets the stepper's value. + public var value: BindingTarget { + return makeBindingTarget { $0.value = $1 } + } + + /// Sets stepper's minimum value. + public var minimumValue: BindingTarget { + return makeBindingTarget { $0.minimumValue = $1 } + } + + /// Sets stepper's maximum value. + public var maximumValue: BindingTarget { + return makeBindingTarget { $0.maximumValue = $1 } + } + + /// A signal of double values emitted by the stepper upon each user's + /// interaction. + public var values: Signal { + return mapControlEvents(.valueChanged) { $0.value } + } +} +#endif diff --git a/ReactiveCocoa/UIKit/iOS/UISwitch.swift b/ReactiveCocoa/UIKit/iOS/UISwitch.swift new file mode 100644 index 0000000000..a2e9f679f5 --- /dev/null +++ b/ReactiveCocoa/UIKit/iOS/UISwitch.swift @@ -0,0 +1,31 @@ +#if canImport(UIKit) && !os(tvOS) && !os(watchOS) +import ReactiveSwift +import UIKit + +extension Reactive where Base: UISwitch { + /// The action to be triggered when the switch is changed. It also controls + /// the enabled state of the switch + public var toggled: CocoaAction? { + get { + return associatedAction.withValue { info in + return info.flatMap { info in + return info.controlEvents == .valueChanged ? info.action : nil + } + } + } + + nonmutating set { + setAction(newValue, for: .valueChanged) + } + } + /// Sets the on-off state of the switch. + public var isOn: BindingTarget { + return makeBindingTarget { $0.isOn = $1 } + } + + /// A signal of on-off states in `Bool` emitted by the switch. + public var isOnValues: Signal { + return mapControlEvents(.valueChanged) { $0.isOn } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceButton.swift b/ReactiveCocoa/WatchKit/WKInterfaceButton.swift new file mode 100644 index 0000000000..582bc90fa0 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceButton.swift @@ -0,0 +1,41 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceButton { + /// Sets the title of the button. + public var title: BindingTarget { + return makeBindingTarget { $0.setTitle($1) } + } + + /// Sets the attributed title of the button. + public var attributedTitle: BindingTarget { + return makeBindingTarget { $0.setAttributedTitle($1) } + } + + /// Sets the background color of the button. + public var backgroundColor: BindingTarget { + return makeBindingTarget { $0.setBackgroundColor($1) } + } + + /// Sets the background image of the button. + public var backgroundImage: BindingTarget { + return makeBindingTarget { $0.setBackgroundImage($1) } + } + + /// Sets the background image data of the button. + public var backgroundImageData: BindingTarget { + return makeBindingTarget { $0.setBackgroundImageData($1) } + } + + /// Sets the background named image of the button. + public var backgroundImageNamed: BindingTarget { + return makeBindingTarget { $0.setBackgroundImageNamed($1) } + } + + /// Sets whether the button is enabled. + public var isEnabled: BindingTarget { + return makeBindingTarget { $0.setEnabled($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceController.swift b/ReactiveCocoa/WatchKit/WKInterfaceController.swift new file mode 100644 index 0000000000..e8e0627768 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceController.swift @@ -0,0 +1,11 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceController { + /// Sets the title of the controller. + public var title: BindingTarget { + return makeBindingTarget { $0.setTitle($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceDate.swift b/ReactiveCocoa/WatchKit/WKInterfaceDate.swift new file mode 100644 index 0000000000..9b25a6a2b4 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceDate.swift @@ -0,0 +1,11 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceDate { + /// Sets the color of the text of the date. + public var textColor: BindingTarget { + return makeBindingTarget { $0.setTextColor($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceGroup.swift b/ReactiveCocoa/WatchKit/WKInterfaceGroup.swift new file mode 100644 index 0000000000..eaa78f1cb8 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceGroup.swift @@ -0,0 +1,36 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceGroup { + /// Sets the background color of the group. + public var backgroundColor: BindingTarget { + return makeBindingTarget { $0.setBackgroundColor($1) } + } + + /// Sets the background image of the group. + public var backgroundImage: BindingTarget { + return makeBindingTarget { $0.setBackgroundImage($1) } + } + + /// Sets the background image data of the group. + public var backgroundImageData: BindingTarget { + return makeBindingTarget { $0.setBackgroundImageData($1) } + } + + /// Sets the background named image of the group. + public var backgroundImageNamed: BindingTarget { + return makeBindingTarget { $0.setBackgroundImageNamed($1) } + } + + /// Sets the corner radius of the group. + public var cornerRadius: BindingTarget { + return makeBindingTarget { $0.setCornerRadius($1) } + } + + /// Sets the content inset of the group. + public var contentInset: BindingTarget { + return makeBindingTarget { $0.setContentInset($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceImage.swift b/ReactiveCocoa/WatchKit/WKInterfaceImage.swift new file mode 100644 index 0000000000..57367caf94 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceImage.swift @@ -0,0 +1,26 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceImage { + /// Sets the image. + public var image: BindingTarget { + return makeBindingTarget { $0.setImage($1) } + } + + /// Sets the data of the image. + public var imageData: BindingTarget { + return makeBindingTarget { $0.setImageData($1) } + } + + /// Sets the name of the image. + public var imageNamed: BindingTarget { + return makeBindingTarget { $0.setImageNamed($1) } + } + + /// Sets the tint color of the template image. + public var tintColor: BindingTarget { + return makeBindingTarget { $0.setTintColor($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceInlineMovie.swift b/ReactiveCocoa/WatchKit/WKInterfaceInlineMovie.swift new file mode 100644 index 0000000000..ded534658d --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceInlineMovie.swift @@ -0,0 +1,32 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +@available(watchOSApplicationExtension 3.0, *) +extension Reactive where Base: WKInterfaceInlineMovie { + /// Sets the url of the movie. + public var movieURL: BindingTarget { + return makeBindingTarget { $0.setMovieURL($1) } + } + + /// Sets the video gravity of the movie. + public var videoGravity: BindingTarget { + return makeBindingTarget { $0.setVideoGravity($1) } + } + + /// Sets the poster image of the movie. + public var posterImage: BindingTarget { + return makeBindingTarget { $0.setPosterImage($1) } + } + + /// Whether the movie loops. + public var loops: BindingTarget { + return makeBindingTarget { $0.setLoops($1) } + } + + /// Whether the movie autoplays. + public var autoplays: BindingTarget { + return makeBindingTarget { $0.setAutoplays($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceLabel.swift b/ReactiveCocoa/WatchKit/WKInterfaceLabel.swift new file mode 100644 index 0000000000..e2a259b847 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceLabel.swift @@ -0,0 +1,21 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceLabel { + /// Sets the text of the label. + public var text: BindingTarget { + return makeBindingTarget { $0.setText($1) } + } + + /// Sets the attributed text of the label. + public var attributedText: BindingTarget { + return makeBindingTarget { $0.setAttributedText($1) } + } + + /// Sets the color of the text of the label. + public var textColor: BindingTarget { + return makeBindingTarget { $0.setTextColor($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceMovie.swift b/ReactiveCocoa/WatchKit/WKInterfaceMovie.swift new file mode 100644 index 0000000000..84325b56b8 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceMovie.swift @@ -0,0 +1,26 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceMovie { + /// Sets the url of the movie. + public var movieURL: BindingTarget { + return makeBindingTarget { $0.setMovieURL($1) } + } + + /// Sets the video gravity of the movie. + public var videoGravity: BindingTarget { + return makeBindingTarget { $0.setVideoGravity($1) } + } + + /// Sets the poster image of the movie. + public var posterImage: BindingTarget { + return makeBindingTarget { $0.setPosterImage($1) } + } + + /// Whether the movie loops. + public var loops: BindingTarget { + return makeBindingTarget { $0.setLoops($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceObject.swift b/ReactiveCocoa/WatchKit/WKInterfaceObject.swift new file mode 100644 index 0000000000..8ff6d73a86 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceObject.swift @@ -0,0 +1,16 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceObject { + /// Sets the alpha value of the object. + public var alpha: BindingTarget { + return makeBindingTarget { $0.setAlpha($1) } + } + + /// Sets whether the object is hidden. + public var isHidden: BindingTarget { + return makeBindingTarget { $0.setHidden($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfacePicker.swift b/ReactiveCocoa/WatchKit/WKInterfacePicker.swift new file mode 100644 index 0000000000..609a4ccd7d --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfacePicker.swift @@ -0,0 +1,21 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfacePicker { + /// Sets the list of items of the picker. + public var items: BindingTarget<[WKPickerItem]?> { + return makeBindingTarget { $0.setItems($1) } + } + + /// Sets the selected item index of the picker. + public var selectedItemIndex: BindingTarget { + return makeBindingTarget { $0.setSelectedItemIndex($1) } + } + + /// Sets whether the picker is enabled. + public var isEnabled: BindingTarget { + return makeBindingTarget { $0.setEnabled($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceSeparator.swift b/ReactiveCocoa/WatchKit/WKInterfaceSeparator.swift new file mode 100644 index 0000000000..675b136a35 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceSeparator.swift @@ -0,0 +1,11 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceSeparator { + /// Sets the color of the separator. + public var color: BindingTarget { + return makeBindingTarget { $0.setColor($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceSlider.swift b/ReactiveCocoa/WatchKit/WKInterfaceSlider.swift new file mode 100644 index 0000000000..39dbb1ea83 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceSlider.swift @@ -0,0 +1,26 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceSlider { + /// Sets the value of the slider. + public var value: BindingTarget { + return makeBindingTarget { $0.setValue($1) } + } + + /// Sets the number of steps of the slider. + public var numberOfSteps: BindingTarget { + return makeBindingTarget { $0.setNumberOfSteps($1) } + } + + /// Sets the color of the slider. + public var color: BindingTarget { + return makeBindingTarget { $0.setColor($1) } + } + + /// Sets whether the slider is enabled. + public var isEnabled: BindingTarget { + return makeBindingTarget { $0.setEnabled($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceSwitch.swift b/ReactiveCocoa/WatchKit/WKInterfaceSwitch.swift new file mode 100644 index 0000000000..2e61d13340 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceSwitch.swift @@ -0,0 +1,31 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceSwitch { + /// Sets the title of the switch. + public var title: BindingTarget { + return makeBindingTarget { $0.setTitle($1) } + } + + /// Sets the attributed title of the switch. + public var attributedTitle: BindingTarget { + return makeBindingTarget { $0.setAttributedTitle($1) } + } + + /// Sets the color of the switch. + public var color: BindingTarget { + return makeBindingTarget { $0.setColor($1) } + } + + /// Sets whether the switch is on. + public var isOn: BindingTarget { + return makeBindingTarget { $0.setOn($1) } + } + + /// Sets whether the switch is enabled. + public var isEnabled: BindingTarget { + return makeBindingTarget { $0.setEnabled($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceTimer.swift b/ReactiveCocoa/WatchKit/WKInterfaceTimer.swift new file mode 100644 index 0000000000..21220da634 --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceTimer.swift @@ -0,0 +1,16 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +extension Reactive where Base: WKInterfaceTimer { + /// Sets the start date of the timer. + public var date: BindingTarget { + return makeBindingTarget { $0.setDate($1) } + } + + /// Sets the color of the text of the timer. + public var textColor: BindingTarget { + return makeBindingTarget { $0.setTextColor($1) } + } +} +#endif diff --git a/ReactiveCocoa/WatchKit/WKInterfaceVolumeControl.swift b/ReactiveCocoa/WatchKit/WKInterfaceVolumeControl.swift new file mode 100644 index 0000000000..a85dcfb49c --- /dev/null +++ b/ReactiveCocoa/WatchKit/WKInterfaceVolumeControl.swift @@ -0,0 +1,12 @@ +#if canImport(WatchKit) +import ReactiveSwift +import WatchKit + +@available(watchOSApplicationExtension 5.0, *) +extension Reactive where Base: WKInterfaceVolumeControl { + /// Sets the tint color of the volume control. + public var tintColor: BindingTarget { + return makeBindingTarget { $0.setTintColor($1) } + } +} +#endif diff --git a/ReactiveCocoa/include/module.modulemap b/ReactiveCocoa/include/module.modulemap new file mode 100644 index 0000000000..77dc79fb5d --- /dev/null +++ b/ReactiveCocoa/include/module.modulemap @@ -0,0 +1,7 @@ +framework module ReactiveCocoa { + umbrella header "ReactiveCocoa.h" + private header "ObjCRuntimeAliases.h" + + export * + module * { export * } +} diff --git a/ReactiveCocoaObjC/ObjCRuntimeAliases.m b/ReactiveCocoaObjC/ObjCRuntimeAliases.m new file mode 100644 index 0000000000..d4428ac6b1 --- /dev/null +++ b/ReactiveCocoaObjC/ObjCRuntimeAliases.m @@ -0,0 +1,9 @@ +#import +#import + +const IMP _rac_objc_msgForward = _objc_msgForward; + +void _rac_objc_setAssociatedObject(const void* object, const void* key, id value, objc_AssociationPolicy policy) { + __unsafe_unretained id obj = (__bridge typeof(obj)) object; + objc_setAssociatedObject(obj, key, value, policy); +} diff --git a/ReactiveCocoaObjC/include/ObjCRuntimeAliases.h b/ReactiveCocoaObjC/include/ObjCRuntimeAliases.h new file mode 100644 index 0000000000..da296a3803 --- /dev/null +++ b/ReactiveCocoaObjC/include/ObjCRuntimeAliases.h @@ -0,0 +1,13 @@ +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +extern const IMP _rac_objc_msgForward; + +/// A trampoline of `objc_setAssociatedObject` that is made to circumvent the +/// reference counting calls in the imported version in Swift. +void _rac_objc_setAssociatedObject(const void* object, const void* key, id _Nullable value, objc_AssociationPolicy policy); + +NS_ASSUME_NONNULL_END diff --git a/ReactiveCocoaObjC/include/ReactiveCocoa.h b/ReactiveCocoaObjC/include/ReactiveCocoa.h new file mode 100644 index 0000000000..841a3109d1 --- /dev/null +++ b/ReactiveCocoaObjC/include/ReactiveCocoa.h @@ -0,0 +1,7 @@ +#import + +//! Project version number for ReactiveCocoa. +FOUNDATION_EXPORT double ReactiveCocoaVersionNumber; + +//! Project version string for ReactiveCocoa. +FOUNDATION_EXPORT const unsigned char ReactiveCocoaVersionString[]; diff --git a/ReactiveCocoaObjCTestSupport/MessageForwardingEntity.m b/ReactiveCocoaObjCTestSupport/MessageForwardingEntity.m new file mode 100644 index 0000000000..261c779d5f --- /dev/null +++ b/ReactiveCocoaObjCTestSupport/MessageForwardingEntity.m @@ -0,0 +1,35 @@ +#import "MessageForwardingEntity.h" +#pragma GCC diagnostic ignored "-Wundeclared-selector" + +@implementation MessageForwardingEntity + +- (instancetype) init { + if (self = [super init]) { + self.hasInvoked = NO; + } + return self; +} + +- (BOOL) respondsToSelector:(SEL)aSelector { + if (aSelector == @selector(_rac_test_forwarding)) { + return YES; + } + return [super respondsToSelector:aSelector]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + if (aSelector == @selector(_rac_test_forwarding)) { + return [NSMethodSignature signatureWithObjCTypes:"v@:"]; + } + return [super methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation { + if (anInvocation.selector == @selector(_rac_test_forwarding)) { + [self setHasInvoked:YES]; + return; + } + return [super forwardInvocation:anInvocation]; +} + +@end diff --git a/ReactiveCocoaObjCTestSupport/include/MessageForwardingEntity.h b/ReactiveCocoaObjCTestSupport/include/MessageForwardingEntity.h new file mode 100644 index 0000000000..f807fa6821 --- /dev/null +++ b/ReactiveCocoaObjCTestSupport/include/MessageForwardingEntity.h @@ -0,0 +1,5 @@ +#import + +@interface MessageForwardingEntity : NSObject +@property(nonatomic) BOOL hasInvoked; +@end diff --git a/ReactiveCocoaTests/AppKit/ActionProxySpec.swift b/ReactiveCocoaTests/AppKit/ActionProxySpec.swift new file mode 100644 index 0000000000..5c8d20d5b5 --- /dev/null +++ b/ReactiveCocoaTests/AppKit/ActionProxySpec.swift @@ -0,0 +1,303 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Foundation +import Quick +import Nimble +import ReactiveSwift +@testable import ReactiveCocoa + +private class Object: NSObject, ActionMessageSending { + @objc dynamic var objectValue: AnyObject? = nil + + @objc dynamic weak var target: AnyObject? + @objc dynamic var action: Selector? + + deinit { + target = nil + action = nil + } +} + +private class Receiver: NSObject { + var counter = 0 + + @objc func foo() { + counter += 1 + } +} + +class ActionProxySpec: QuickSpec { + override func spec() { + describe("ActionProxy") { + var object: Object! + var proxy: ActionProxy! + + beforeEach { + object = Object() + proxy = object.reactive.proxy + } + + afterEach { + weak var weakObject = object + + object = nil + expect(weakObject).to(beNil()) + } + + func sendMessage() { + _ = object.action.map { object.target?.perform($0, with: nil) } + } + + it("should be automatically set as the object's delegate.") { + expect(object.target).to(beIdenticalTo(proxy)) + expect(object.action) == #selector(proxy.invoke(_:)) + } + + it("should not be erased when the delegate is set with a new one.") { + object.target = nil + object.action = nil + + expect(object.target).to(beIdenticalTo(proxy)) + expect(object.action) == #selector(proxy.invoke(_:)) + + expect(proxy.target).to(beNil()) + expect(proxy.action).to(beNil()) + + let counter = Receiver() + object.target = counter + object.action = #selector(counter.foo) + + expect(object.target).to(beIdenticalTo(proxy)) + expect(object.action) == #selector(proxy.invoke(_:)) + + expect(proxy.target).to(beIdenticalTo(counter)) + expect(proxy.action) == #selector(counter.foo) + } + + it("should complete its signals when the object deinitializes") { + var isCompleted = false + proxy.invoked.observeCompleted { isCompleted = true } + + expect(isCompleted) == false + + object = nil + expect(isCompleted) == true + } + + it("should interrupt the observers if the object has already deinitialized") { + object = nil + + var isInterrupted = false + proxy.invoked.observeInterrupted { isInterrupted = true } + + expect(isInterrupted) == true + } + + it("should emit a `value` event whenever an action message is sent.") { + var fooCount = 0 + proxy.invoked.observeValues { _ in fooCount += 1 } + + expect(fooCount) == 0 + + sendMessage() + expect(fooCount) == 1 + + sendMessage() + expect(fooCount) == 2 + } + + it("should pass through the action message to the forwardee.") { + let receiver = Receiver() + proxy.target = receiver + proxy.action = #selector(receiver.foo) + + var fooCount = 0 + proxy.invoked.observeValues { _ in fooCount += 1 } + + expect(fooCount) == 0 + expect(receiver.counter) == 0 + + sendMessage() + expect(fooCount) == 1 + expect(receiver.counter) == 1 + + sendMessage() + expect(fooCount) == 2 + expect(receiver.counter) == 2 + } + + describe("interoperability") { + var object: Object! + var proxy: ActionProxy! + var invocationCount = 0 + + beforeEach { + object = Object() + invocationCount = 0 + } + + func setProxy() { + proxy = object.reactive.proxy + proxy.invoked.observeValues { _ in invocationCount += 1 } + } + + func sendMessage() { + _ = object.action.map { object.target?.perform($0, with: nil) } + } + + it("should not affect instances sharing the same runtime subclass") { + _ = object.reactive.producer(forKeyPath: #keyPath(Object.objectValue)).start() + setProxy() + expect(object.target).to(beIdenticalTo(proxy)) + + // Another object without RAC swizzling. + let object2 = Object() + _ = object2.reactive.producer(forKeyPath: #keyPath(Object.objectValue)).start() + + expect(object.objcClass).to(beIdenticalTo(object2.objcClass)) + + let className = NSStringFromClass(object_getClass(object)!) + expect(className).to(beginWith("NSKVONotifying_")) + expect(className).toNot(endWith("_RACSwift")) + + object2.target = object + object2.action = #selector(AnyObject.perform(_:with:)) + + expect(object2.target).to(beIdenticalTo(object)) + expect(object2.action) == #selector(AnyObject.perform(_:with:)) + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by KVO.") { + _ = object.reactive.producer(forKeyPath: #keyPath(Object.objectValue)).start() + expect(object.target).to(beNil()) + + setProxy() + expect(object.target).to(beIdenticalTo(proxy)) + + sendMessage() + expect(invocationCount) == 1 + + object.target = nil + expect(object.target).to(beIdenticalTo(proxy)) + + sendMessage() + expect(invocationCount) == 2 + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by RAC.") { + _ = object.reactive.trigger(for: #selector(getter: Object.objectValue)) + expect(object.target).to(beNil()) + + setProxy() + expect(object.target).to(beIdenticalTo(proxy)) + + sendMessage() + expect(invocationCount) == 1 + + object.target = nil + expect(object.target).to(beIdenticalTo(proxy)) + + sendMessage() + expect(invocationCount) == 2 + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by RAC for intercepting the delegate setter.") { + var counter = 0 + + object.reactive + .trigger(for: #selector(setter: Object.target)) + .observeValues { counter += 1 } + expect(object.target).to(beNil()) + + setProxy() + + // The assignment of the proxy should not be captured by the method + // interception logic. + expect(object.target).to(beIdenticalTo(proxy)) + expect(counter) == 0 + + sendMessage() + expect(invocationCount) == 1 + + object.target = nil + expect(object.target).to(beIdenticalTo(proxy)) + expect(counter) == 1 + + sendMessage() + expect(invocationCount) == 2 + } + + it("should be automatically set as the object's delegate even if it is subsequently isa-swizzled by RAC for intercepting the delegate setter.") { + expect(object.target).to(beNil()) + + setProxy() + expect(object.target).to(beIdenticalTo(proxy)) + + sendMessage() + expect(invocationCount) == 1 + + var counter = 0 + + object.reactive + .trigger(for: #selector(setter: Object.target)) + .observeValues { counter += 1 } + + object.target = nil + expect(object.target).to(beIdenticalTo(proxy)) + expect(counter) == 1 + + sendMessage() + expect(invocationCount) == 2 + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by KVO for observing the delegate key path.") { + var counter = 0 + + object.reactive + .signal(forKeyPath: #keyPath(Object.target)) + .observeValues { _ in counter += 1 } + expect(object.target).to(beNil()) + + setProxy() + + // The assignment of the proxy should not be captured by KVO. + expect(object.target).to(beIdenticalTo(proxy)) + expect(counter) == 0 + + sendMessage() + expect(invocationCount) == 1 + + object.target = nil + expect(object.target).to(beIdenticalTo(proxy)) + expect(counter) == 0 + + sendMessage() + expect(invocationCount) == 2 + } + + it("should be automatically set as the object's delegate even if it is subsequently isa-swizzled by KVO for observing the delegate key path.") { + expect(object.target).to(beNil()) + + setProxy() + expect(object.target).to(beIdenticalTo(proxy)) + + sendMessage() + expect(invocationCount) == 1 + + var counter = 0 + + object.reactive + .signal(forKeyPath: #keyPath(Object.target)) + .observeValues { _ in counter += 1 } + + object.target = nil + expect(object.target).to(beIdenticalTo(proxy)) + expect(counter) == 1 + + sendMessage() + expect(invocationCount) == 2 + } + } + } + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/AppKitReusableComponentsSpec.swift b/ReactiveCocoaTests/AppKit/AppKitReusableComponentsSpec.swift new file mode 100644 index 0000000000..6c153c8f14 --- /dev/null +++ b/ReactiveCocoaTests/AppKit/AppKitReusableComponentsSpec.swift @@ -0,0 +1,49 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import AppKit + +class ReusableComponentsSpec: QuickSpec { + override func spec() { + describe("NSTableCellView") { + it("should send a `value` event when `prepareForReuse` is triggered") { + let cell = NSTableCellView() + + var isTriggered = false + cell.reactive.prepareForReuse.observeValues { + isTriggered = true + } + + expect(isTriggered) == false + + cell.prepareForReuse() + expect(isTriggered) == true + } + } + + describe("NSCollectionViewItem") { + it("should send a `value` event when `prepareForReuse` is triggered") { + let item = TestViewItem() + + var isTriggered = false + item.reactive.prepareForReuse.observeValues { + isTriggered = true + } + + expect(isTriggered) == false + + item.prepareForReuse() + expect(isTriggered) == true + } + } + } +} + +private class TestViewItem: NSCollectionViewItem { + override func loadView() { + view = NSView() + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/NSButtonSpec.swift b/ReactiveCocoaTests/AppKit/NSButtonSpec.swift new file mode 100644 index 0000000000..0029d0009d --- /dev/null +++ b/ReactiveCocoaTests/AppKit/NSButtonSpec.swift @@ -0,0 +1,151 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import AppKit + +class NSButtonSpec: QuickSpec { + override func spec() { + var button: NSButton! + weak var _button: NSButton? + + var window: NSWindow! + + beforeEach { + button = NSButton(frame: .zero) + _button = button + window = NSWindow() + window.contentView?.addSubview(button) + } + + afterEach { + autoreleasepool { + button.removeFromSuperview() + button = nil + } + expect(_button).to(beNil()) + } + + it("should accept changes from bindings to its enabling state") { + button.isEnabled = false + + let (pipeSignal, observer) = Signal.pipe() + button.reactive.isEnabled <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(button.isEnabled) == true + + observer.send(value: false) + expect(button.isEnabled) == false + } + + it("should accept changes from bindings to its state") { + button.allowsMixedState = true + button.state = RACNSOffState + + let (pipeSignal, observer) = Signal.pipe() + button.reactive.state <~ SignalProducer(pipeSignal) + + observer.send(value: RACNSOffState) + expect(button.state) == RACNSOffState + + observer.send(value: RACNSMixedState) + expect(button.state) == RACNSMixedState + + observer.send(value: RACNSOnState) + expect(button.state) == RACNSOnState + } + + it("should send along state changes") { + button.setButtonType(.pushOnPushOff) + button.allowsMixedState = false + button.state = RACNSOffState + + let state = MutableProperty(RACNSOffState) + state <~ button.reactive.states + + button.performClick(nil) + expect(state.value) == RACNSOnState + + button.performClick(nil) + expect(state.value) == RACNSOffState + + button.allowsMixedState = true + + button.performClick(nil) + expect(state.value) == RACNSMixedState + + button.performClick(nil) + expect(state.value) == RACNSOnState + + button.performClick(nil) + expect(state.value) == RACNSOffState + + } + + it("should send along state changes embedded within NSStackView") { + let window = NSWindow() + let button1 = NSButton() + let button2 = NSButton() + + button1.setButtonType(.pushOnPushOff) + button1.allowsMixedState = false + button1.state = RACNSOffState + + button2.setButtonType(.pushOnPushOff) + button2.allowsMixedState = false + button2.state = RACNSOnState + + let stackView = NSStackView() + stackView.addArrangedSubview(button1) + stackView.addArrangedSubview(button2) + + // This is required to avoid crashing as of 10.15, see https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3690 + stackView.detachesHiddenViews = false + + window.contentView?.addSubview(stackView) + + let state = MutableProperty(RACNSOffState) + state <~ button1.reactive.states + state <~ button2.reactive.states + + button1.performClick(nil) + expect(state.value) == RACNSOnState + + button2.performClick(nil) + expect(state.value) == RACNSOffState + + autoreleasepool { + button1.removeFromSuperview() + button2.removeFromSuperview() + stackView.removeFromSuperview() + } + } + + it("should execute the `pressed` action upon receiving a click") { + button.isEnabled = true + + let pressed = MutableProperty(false) + + let (executionSignal, observer) = Signal.pipe() + let action = Action<(), Bool, Never> { _ in + SignalProducer(executionSignal) + } + + pressed <~ SignalProducer(action.values) + button.reactive.pressed = CocoaAction(action) + expect(pressed.value) == false + + button.performClick(nil) + expect(button.isEnabled) == false + + observer.send(value: true) + observer.sendCompleted() + + expect(button.isEnabled) == true + expect(pressed.value) == true + } + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/NSCollectionViewSpec.swift b/ReactiveCocoaTests/AppKit/NSCollectionViewSpec.swift new file mode 100644 index 0000000000..91e480edb8 --- /dev/null +++ b/ReactiveCocoaTests/AppKit/NSCollectionViewSpec.swift @@ -0,0 +1,64 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveCocoa +import ReactiveSwift +import AppKit + +final class NSCollectionViewSpec: QuickSpec { + override func spec() { + var collectionView: TestCollectionView! + + beforeEach { + collectionView = TestCollectionView() + } + + describe("reloadData") { + var bindingSignal: Signal<(), Never>! + var bindingObserver: Signal<(), Never>.Observer! + + var reloadDataCount = 0 + + beforeEach { + let (signal, observer) = Signal<(), Never>.pipe() + (bindingSignal, bindingObserver) = (signal, observer) + + reloadDataCount = 0 + + collectionView.reloadDataSignal.observeValues { + reloadDataCount += 1 + } + } + + it("invokes reloadData whenever the bound signal sends a value") { + collectionView.reactive.reloadData <~ bindingSignal + + bindingObserver.send(value: ()) + bindingObserver.send(value: ()) + + expect(reloadDataCount) == 2 + } + } + } +} + +private final class TestCollectionView: NSCollectionView { + let reloadDataSignal: Signal<(), Never> + private let reloadDataObserver: Signal<(), Never>.Observer + + override init(frame: CGRect) { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(frame: frame) + } + + required init?(coder aDecoder: NSCoder) { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(coder: aDecoder) + } + + override func reloadData() { + super.reloadData() + reloadDataObserver.send(value: ()) + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/NSControlSpec.swift b/ReactiveCocoaTests/AppKit/NSControlSpec.swift new file mode 100644 index 0000000000..e2b03757ea --- /dev/null +++ b/ReactiveCocoaTests/AppKit/NSControlSpec.swift @@ -0,0 +1,190 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import AppKit + +class NSControlSpec: QuickSpec { + override func spec() { + describe("NSControl") { + var window: NSWindow! + var control: NSButton! + weak var _control: NSControl? + + beforeEach { + window = NSWindow() + + control = NSButton(frame: .zero) + control.setButtonType(.onOff) + #if swift(>=4.0) + control.state = .off + #else + control.state = RACNSOffState + #endif + + _control = control + + window.contentView!.addSubview(control) + } + + afterEach { + autoreleasepool { + control.removeFromSuperview() + control = nil + } + + expect(_control).to(beNil()) + } + + it("should emit changes in Int") { + var values = [Int]() + control.reactive.integerValues.observeValues { values.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == [1, 0] + } + + it("should emit changes in Bool") { + var values = [Bool]() + control.reactive.boolValues.observeValues { values.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == [true, false] + } + + it("should emit changes in Int32") { + var values = [Int32]() + control.reactive.intValues.observeValues { values.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == [1, 0] + } + + it("should emit changes in Double") { + var values = [Double]() + control.reactive.doubleValues.observeValues { values.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == [1.0, 0.0] + } + + it("should emit changes in Float") { + var values = [Float]() + control.reactive.floatValues.observeValues { values.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == [1.0, 0.0] + } + + it("should emit changes in String") { + var values = [String]() + control.reactive.stringValues.observeValues { values.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == ["1", "0"] + } + + it("should emit changes in AttributedString") { + var values = [NSAttributedString]() + control.reactive.attributedStringValues.observeValues { values.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == [NSAttributedString(string: "1"), NSAttributedString(string: "0")] + } + + it("should emit changes as objects") { + let values = NSMutableArray() + control.reactive.objectValues.observeValues { values.add($0!) } + + control.performClick(nil) + control.performClick(nil) + + expect(values) == [NSNumber(value: 1), NSNumber(value: 0)] + } + + it("should emit changes for multiple signals") { + var valuesA = [String]() + control.reactive.stringValues.observeValues { valuesA.append($0) } + + var valuesB = [Bool]() + control.reactive.boolValues.observeValues { valuesB.append($0) } + + var valuesC = [Int]() + control.reactive.integerValues.observeValues { valuesC.append($0) } + + control.performClick(nil) + control.performClick(nil) + + expect(valuesA) == ["1", "0"] + expect(valuesB) == [true, false] + expect(valuesC) == [1, 0] + } + + it("should not overwrite the existing target") { + let target = TestTarget() + control.target = target + control.action = #selector(target.execute) + + control.performClick(nil) + expect(target.counter) == 1 + + var signalCounter = 0 + control.reactive.integerValues.observeValues { _ in signalCounter += 1 } + expect(control.target).toNot(beIdenticalTo(target)) + + control.performClick(nil) + expect(signalCounter) == 1 + expect(target.counter) == 2 + + control.performClick(nil) + expect(signalCounter) == 2 + expect(target.counter) == 3 + } + + it("should not overwrite the proxy") { + var signalCounter = 0 + control.reactive.integerValues.observeValues { _ in signalCounter += 1 } + + control.performClick(nil) + expect(signalCounter) == 1 + + let target = TestTarget() + control.target = target + control.action = #selector(target.execute) + + control.performClick(nil) + expect(signalCounter) == 2 + expect(target.counter) == 1 + + + control.performClick(nil) + expect(signalCounter) == 3 + expect(target.counter) == 2 + } + } + } +} + +private final class TestTarget { + var counter = 0 + + @objc func execute(_ sender: Any?) { + counter += 1 + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/NSImageViewSpec.swift b/ReactiveCocoaTests/AppKit/NSImageViewSpec.swift new file mode 100644 index 0000000000..f24fc66792 --- /dev/null +++ b/ReactiveCocoaTests/AppKit/NSImageViewSpec.swift @@ -0,0 +1,51 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import AppKit + +class NSImageViewSpec: QuickSpec { + override func spec() { + var imageView: NSImageView! + weak var _imageView: NSImageView? + + beforeEach { + imageView = NSImageView(frame: .zero) + _imageView = imageView + } + + afterEach { + imageView = nil + expect(_imageView).to(beNil()) + } + + it("should accept changes from bindings to its enabling state") { + imageView.isEnabled = false + + let (pipeSignal, observer) = Signal.pipe() + imageView.reactive.isEnabled <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(imageView.isEnabled) == true + + observer.send(value: false) + expect(imageView.isEnabled) == false + } + + it("should accept changes from bindings to its image") { + + let (pipeSignal, observer) = Signal.pipe() + imageView.reactive.image <~ SignalProducer(pipeSignal) + + let theImage = NSImage(named: NSImage.userName) + + observer.send(value: theImage) + expect(imageView.image) == theImage + + observer.send(value: nil) + expect(imageView.image).to(beNil()) + } + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/NSPopUpButtonSpec.swift b/ReactiveCocoaTests/AppKit/NSPopUpButtonSpec.swift new file mode 100644 index 0000000000..7cf9e58b3c --- /dev/null +++ b/ReactiveCocoaTests/AppKit/NSPopUpButtonSpec.swift @@ -0,0 +1,133 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveCocoa +import ReactiveSwift +import AppKit + +final class NSPopUpButtonSpec: QuickSpec { + override func spec() { + describe("NSPopUpButton") { + var button: NSPopUpButton! + var window: NSWindow! + weak var _button: NSButton? + let testTitles = (0..<100).map { $0.description } + + beforeEach { + window = NSWindow() + button = NSPopUpButton(frame: .zero) + _button = button + for (i, title) in testTitles.enumerated() { + let item = NSMenuItem(title: title, action: nil, keyEquivalent: "") + item.tag = 1000 + i + + button.menu?.addItem(item) + } + + window.contentView?.addSubview(button) + } + + afterEach { + autoreleasepool { + button.removeFromSuperview() + button = nil + } + expect(_button).to(beNil()) + } + + it("should emit selected index changes") { + var values = [Int]() + button.reactive.selectedIndexes.observeValues { values.append($0) } + + button.menu?.performActionForItem(at: 1) + button.menu?.performActionForItem(at: 99) + + expect(values) == [1, 99] + } + + it("should emit selected title changes") { + var values = [String]() + button.reactive.selectedTitles.observeValues { values.append($0) } + + button.menu?.performActionForItem(at: 1) + button.menu?.performActionForItem(at: 99) + + expect(values) == ["1", "99"] + } + + it("should accept changes from its bindings to its index values") { + let (signal, observer) = Signal.pipe() + button.reactive.selectedIndex <~ SignalProducer(signal) + + observer.send(value: 1) + expect(button.indexOfSelectedItem) == 1 + + observer.send(value: 99) + expect(button.indexOfSelectedItem) == 99 + + observer.send(value: nil) + expect(button.indexOfSelectedItem) == -1 + expect(button.selectedItem?.title).to(beNil()) + } + + it("should accept changes from its bindings to its title values") { + let (signal, observer) = Signal.pipe() + button.reactive.selectedTitle <~ SignalProducer(signal) + + observer.send(value: "1") + expect(button.selectedItem?.title) == "1" + + observer.send(value: "99") + expect(button.selectedItem?.title) == "99" + + observer.send(value: nil) + expect(button.selectedItem?.title).to(beNil()) + expect(button.indexOfSelectedItem) == -1 + } + + it("should emit selected item changes") { + var values = [NSMenuItem]() + button.reactive.selectedItems.observeValues { values.append($0) } + + button.menu?.performActionForItem(at: 1) + button.menu?.performActionForItem(at: 99) + + let titles = values.map { $0.title } + expect(titles) == ["1", "99"] + } + + it("should emit selected tag changes") { + var values = [Int]() + button.reactive.selectedTags.observeValues { values.append($0) } + + button.menu?.performActionForItem(at: 1) + button.menu?.performActionForItem(at: 99) + + expect(values) == [1001, 1099] + } + + it("should accept changes from its bindings to its tag values") { + let (signal, observer) = Signal.pipe() + button.reactive.selectedTag <~ SignalProducer(signal) + + observer.send(value: 1001) + expect(button.selectedItem?.tag) == 1001 + expect(button.indexOfSelectedItem) == 1 + + observer.send(value: 1099) + expect(button.selectedItem?.tag) == 1099 + expect(button.indexOfSelectedItem) == 99 + + observer.send(value: 1042) + expect(button.selectedItem?.tag) == 1042 + expect(button.indexOfSelectedItem) == 42 + + // Sending an invalid tag number doesn't change the selection + observer.send(value: testTitles.count + 1) + expect(button.selectedItem?.tag) == 1042 + expect(button.indexOfSelectedItem) == 42 + } + } + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/NSTableViewSpec.swift b/ReactiveCocoaTests/AppKit/NSTableViewSpec.swift new file mode 100644 index 0000000000..37b582b57f --- /dev/null +++ b/ReactiveCocoaTests/AppKit/NSTableViewSpec.swift @@ -0,0 +1,64 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveCocoa +import ReactiveSwift +import AppKit + +final class NSTableViewSpec: QuickSpec { + override func spec() { + var tableView: TestTableView! + + beforeEach { + tableView = TestTableView() + } + + describe("reloadData") { + var bindingSignal: Signal<(), Never>! + var bindingObserver: Signal<(), Never>.Observer! + + var reloadDataCount = 0 + + beforeEach { + let (signal, observer) = Signal<(), Never>.pipe() + (bindingSignal, bindingObserver) = (signal, observer) + + reloadDataCount = 0 + + tableView.reloadDataSignal.observeValues { + reloadDataCount += 1 + } + } + + it("invokes reloadData whenever the bound signal sends a value") { + tableView.reactive.reloadData <~ bindingSignal + + bindingObserver.send(value: ()) + bindingObserver.send(value: ()) + + expect(reloadDataCount) == 2 + } + } + } +} + +private final class TestTableView: NSTableView { + let reloadDataSignal: Signal<(), Never> + private let reloadDataObserver: Signal<(), Never>.Observer + + override init(frame: CGRect) { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(frame: frame) + } + + required init?(coder aDecoder: NSCoder) { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(coder: aDecoder) + } + + override func reloadData() { + super.reloadData() + reloadDataObserver.send(value: ()) + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/NSViewSpec.swift b/ReactiveCocoaTests/AppKit/NSViewSpec.swift new file mode 100644 index 0000000000..6c53ca6919 --- /dev/null +++ b/ReactiveCocoaTests/AppKit/NSViewSpec.swift @@ -0,0 +1,39 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import AppKit + +class NSViewSpec: QuickSpec { + override func spec() { + describe("NSView") { + var view: NSView! + + beforeEach { + view = NSView() + } + + it("should allow binding of `isHidden` property") { + let (hSignal, hSink) = Signal.pipe() + expect(view.isHidden) == false + + view.reactive.isHidden <~ hSignal + hSink.send(value: true) + + expect(view.isHidden) == true + } + + it("should allow binding of `alphaValue` property") { + let (avSignal, avSink) = Signal.pipe() + expect(view.alphaValue) == 1.0 + + view.reactive.alphaValue <~ avSignal + avSink.send(value: 0.5) + + expect(view.alphaValue) == 0.5 + } + } + } +} +#endif diff --git a/ReactiveCocoaTests/AppKit/Swift4TestInteroperability.swift b/ReactiveCocoaTests/AppKit/Swift4TestInteroperability.swift new file mode 100644 index 0000000000..58cb112ca9 --- /dev/null +++ b/ReactiveCocoaTests/AppKit/Swift4TestInteroperability.swift @@ -0,0 +1,15 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit + +#if swift(>=4.0) +internal typealias RACNSControlState = NSControl.StateValue +internal let RACNSOnState = NSControl.StateValue.on +internal let RACNSOffState = NSControl.StateValue.off +internal let RACNSMixedState = NSControl.StateValue.mixed +#else +internal typealias RACNSControlState = Int +internal let RACNSOnState = NSOnState +internal let RACNSOffState = NSOffState +internal let RACNSMixedState = NSMixedState +#endif +#endif diff --git a/ReactiveCocoaTests/AssociationSpec.swift b/ReactiveCocoaTests/AssociationSpec.swift new file mode 100644 index 0000000000..773c66f829 --- /dev/null +++ b/ReactiveCocoaTests/AssociationSpec.swift @@ -0,0 +1,80 @@ +import Foundation +import ReactiveSwift +import Nimble +import Quick +@testable import ReactiveCocoa + +class AssociationSpec: QuickSpec { + override func spec() { + it("should create and retrieve the same object") { + let object = NSObject() + let token = NSObject() + + var counter = 0 + + func retrieve() -> NSObject { + return object.reactive.associatedValue { _ in + counter += 1 + return token + } + } + + expect(counter) == 0 + + let firstResult = retrieve() + expect(counter) == 1 + + let secondResult = retrieve() + expect(counter) == 1 + + expect(firstResult).to(beIdenticalTo(secondResult)) + expect(firstResult).to(beIdenticalTo(token)) + } + + it("should support multiple keys per object") { + let object = NSObject() + let token = NSObject() + let token2 = NSObject() + + var counter = 0 + var counter2 = 0 + + + func retrieve() -> NSObject { + return object.reactive.associatedValue { _ in + counter += 1 + return token + } + } + + func retrieve2() -> NSObject { + return object.reactive.associatedValue(forKey: "customKey") { _ in + counter2 += 1 + return token2 + } + } + + expect(counter) == 0 + + let firstResult = retrieve() + expect(counter) == 1 + + let secondResult = retrieve() + expect(counter) == 1 + + expect(firstResult).to(beIdenticalTo(secondResult)) + expect(firstResult).to(beIdenticalTo(token)) + + expect(counter2) == 0 + + let thirdResult = retrieve2() + expect(counter2) == 1 + + let forthResult = retrieve2() + expect(counter2) == 1 + + expect(thirdResult).to(beIdenticalTo(forthResult)) + expect(thirdResult).to(beIdenticalTo(token2)) + } + } +} diff --git a/ReactiveCocoaTests/BindingTargetSpec.swift b/ReactiveCocoaTests/BindingTargetSpec.swift new file mode 100644 index 0000000000..9c6ad96b1b --- /dev/null +++ b/ReactiveCocoaTests/BindingTargetSpec.swift @@ -0,0 +1,160 @@ +import Foundation +import ReactiveSwift +import ReactiveCocoa +import Quick +import Nimble + +private class Object: NSObject { + var value: Int = 0 + + func increment() { + value += 1 + } +} + +private class NativeObject: ReactiveExtensionsProvider { + var value: Int = 0 + + func increment() { + value += 1 + } +} + +class BindingTargetSpec: QuickSpec { + override func spec() { + describe("arbitrary binding target on Objective-C object") { + it("should call the action") { + let object = Object() + let target = object.reactive.makeBindingTarget { (object: Object, nothing: Void) -> Void in + object.increment() + } + expect(object.value) == 0 + + let (signal, observer) = Signal<(), Never>.pipe() + target <~ signal + expect(object.value) == 0 + + observer.send(value: ()) + expect(object.value) == 1 + + observer.send(value: ()) + expect(object.value) == 2 + } + + it("should call the action on the given scheduler") { + let scheduler = TestScheduler() + + let object = Object() + let target = object.reactive.makeBindingTarget(on: scheduler) { (object: Object, nothing: Void) -> Void in + object.increment() + } + expect(object.value) == 0 + + let (signal, observer) = Signal<(), Never>.pipe() + target <~ signal + observer.send(value: ()) + expect(object.value) == 0 + + scheduler.run() + expect(object.value) == 1 + + observer.send(value: ()) + expect(object.value) == 1 + + scheduler.run() + expect(object.value) == 2 + } + } + + describe("arbitrary binding target on native object") { + it("should call the action") { + let object = NativeObject() + let target = object.reactive.makeBindingTarget { (object: NativeObject, nothing: Void) -> Void in + object.increment() + } + expect(object.value) == 0 + + let (signal, observer) = Signal<(), Never>.pipe() + target <~ signal + expect(object.value) == 0 + + observer.send(value: ()) + expect(object.value) == 1 + + observer.send(value: ()) + expect(object.value) == 2 + } + + it("should call the action on the given scheduler") { + let scheduler = TestScheduler() + + let object = NativeObject() + let target = object.reactive.makeBindingTarget(on: scheduler) { (object: NativeObject, nothing: Void) -> Void in + object.increment() + } + expect(object.value) == 0 + + let (signal, observer) = Signal<(), Never>.pipe() + target <~ signal + observer.send(value: ()) + expect(object.value) == 0 + + scheduler.run() + expect(object.value) == 1 + + observer.send(value: ()) + expect(object.value) == 1 + + scheduler.run() + expect(object.value) == 2 + } + } + + + #if swift(>=3.2) + describe("key path binding target") { + it("should update the value") { + let object = Object() + expect(object.value) == 0 + + let property = MutableProperty(1) + object.reactive[\.value] <~ property + expect(object.value) == 1 + + property.value = 2 + expect(object.value) == 2 + } + + it("should update the value on the given scheduler") { + let scheduler = TestScheduler() + + let object = Object() + let property = MutableProperty(1) + object.reactive[\.value, on: scheduler] <~ property + expect(object.value) == 0 + + scheduler.run() + expect(object.value) == 1 + + property.value = 2 + expect(object.value) == 1 + + scheduler.run() + expect(object.value) == 2 + } + + it("should work for native swift objects") { + let object = NativeObject() + expect(object.value) == 0 + + let property = MutableProperty(1) + object.reactive[\.value] <~ property + expect(object.value) == 1 + + property.value = 2 + expect(object.value) == 2 + } + } + #endif + } +} diff --git a/ReactiveCocoaTests/CocoaActionSpec.swift b/ReactiveCocoaTests/CocoaActionSpec.swift new file mode 100644 index 0000000000..f46fe2d0dc --- /dev/null +++ b/ReactiveCocoaTests/CocoaActionSpec.swift @@ -0,0 +1,112 @@ +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#endif +import ReactiveSwift +import Nimble +import Quick +import ReactiveCocoa + +class CocoaActionSpec: QuickSpec { + override func spec() { + var action: Action! + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + var cocoaAction: CocoaAction! + #else + var cocoaAction: CocoaAction! + #endif + + beforeEach { + action = Action { value in SignalProducer(value: value + 1) } + expect(action.isEnabled.value) == true + + cocoaAction = CocoaAction(action) { _ in 1 } + expect(cocoaAction.isEnabled.value).toEventually(beTruthy()) + } + + #if canImport(AppKit) && !targetEnvironment(macCatalyst) + it("should be compatible with AppKit") { + let control = NSControl(frame: NSZeroRect) + control.target = cocoaAction + control.action = CocoaAction.selector + control.performClick(nil) + } + #elseif os(iOS) + it("should be compatible with UIKit") { + let control = UIControl(frame: .zero) + control.addTarget(cocoaAction, action: CocoaAction.selector, for: .touchDown) + control.sendActions(for: .touchDown) + } + #endif + + it("should emit changes for enabled") { + var values: [Bool] = [] + + cocoaAction.isEnabled.producer + .startWithValues { values.append($0) } + + expect(values) == [ true ] + + let result = action.apply(0).first() + expect { try result?.get() } == 1 + expect(values).toEventually(equal([ true, false, true ])) + + _ = cocoaAction + } + + it("should generate KVO notifications for executing") { + var values: [Bool] = [] + + cocoaAction.isExecuting.producer + .startWithValues { values.append($0) } + + expect(values) == [ false ] + + let result = action.apply(0).first() + expect { try result?.get() } == 1 + expect(values).toEventually(equal([ false, true, false ])) + + _ = cocoaAction + } + + it("should emit `isExecuting` changes only on the main thread") { + var counter = 0 + + cocoaAction.isExecuting.producer + .startWithValues { _ in + counter += Thread.current.isMainThread ? 1 : 0 + } + + expect(counter) == 1 + + action.apply(0).start() + expect(counter) == 3 + } + + it("should emit `isEnabled` changes only on the main thread") { + var counter = 0 + + cocoaAction.isEnabled.producer + .startWithValues { _ in + counter += Thread.current.isMainThread ? 1 : 0 + } + + expect(counter) == 1 + + action.apply(0).start() + expect(counter) == 3 + } + + context("lifetime") { + it("CocoaAction should not create a retain cycle") { + weak var weakAction = action + expect(weakAction).notTo(beNil()) + + action = nil + expect(weakAction).toNot(beNil()) + + cocoaAction = nil + expect(weakAction).to(beNil()) + } + } + } +} diff --git a/ReactiveCocoaTests/DelegateProxySpec.swift b/ReactiveCocoaTests/DelegateProxySpec.swift new file mode 100644 index 0000000000..8c28c0e5fb --- /dev/null +++ b/ReactiveCocoaTests/DelegateProxySpec.swift @@ -0,0 +1,398 @@ +import Foundation +import Quick +import Nimble +import ReactiveSwift +@testable import ReactiveCocoa + +@objc private protocol ObjectDelegate: NSObjectProtocol { + func foo() + @objc optional func bar() + @objc optional func nop() +} + +private class Object: NSObject { + @objc var delegateSetCount = 0 + var delegateSelectors: [Selector] = [] + + @objc dynamic weak var delegate: ObjectDelegate? { + didSet { + delegateSetCount += 1 + delegateSelectors = Array() + + if delegate?.responds(to: #selector(ObjectDelegate.foo)) ?? false { + delegateSelectors.append(#selector(ObjectDelegate.foo)) + } + + if delegate?.responds(to: #selector(ObjectDelegate.bar)) ?? false { + delegateSelectors.append(#selector(ObjectDelegate.bar)) + } + + if delegate?.responds(to: #selector(ObjectDelegate.nop)) ?? false { + delegateSelectors.append(#selector(ObjectDelegate.nop)) + } + } + } + + deinit { + // Mimic the behavior of clearing delegates of some Cocoa classes. + delegate = nil + } +} + +private class ObjectDelegateCounter: NSObject, ObjectDelegate { + var fooCounter = 0 + var nopCounter = 0 + + func foo() { + fooCounter += 1 + } + + func nop() { + nopCounter += 1 + } +} + +private class ObjectDelegateProxy: DelegateProxy, ObjectDelegate { + func foo() { + forwardee?.foo() + } + + func bar() { + forwardee?.bar?() + } +} + +class DelegateProxySpec: QuickSpec { + override func spec() { + describe("DelegateProxy") { + var object: Object! + var proxy: DelegateProxy! + + beforeEach { + object = Object() + proxy = ObjectDelegateProxy.proxy(for: object, + setter: #selector(setter: object.delegate), + getter: #selector(getter: object.delegate)) + } + + afterEach { + weak var weakObject = object + + object = nil + expect(weakObject).to(beNil()) + } + + it("should be automatically set as the object's delegate.") { + expect(object.delegate).to(beIdenticalTo(proxy)) + } + + it("should not be erased when the delegate is set with a new one.") { + object.delegate = nil + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(proxy.forwardee).to(beNil()) + + let counter = ObjectDelegateCounter() + object.delegate = counter + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(proxy.forwardee).to(beIdenticalTo(counter)) + } + + it("should respond to the protocol requirement checks.") { + expect(proxy.responds(to: #selector(ObjectDelegate.foo))) == true + expect(proxy.responds(to: #selector(ObjectDelegate.bar))) == true + expect(proxy.responds(to: #selector(ObjectDelegate.nop))) == false + } + + it("should complete its signals when the object deinitializes") { + var isCompleted = false + + let foo: Signal<(), Never> = proxy.intercept(#selector(ObjectDelegate.foo)) + foo.observeCompleted { isCompleted = true } + + expect(isCompleted) == false + + object = nil + expect(isCompleted) == true + } + + it("should interrupt the observers if the object has already deinitialized") { + var isInterrupted = false + + object = nil + + let foo: Signal<(), Never> = proxy.intercept(#selector(ObjectDelegate.foo)) + foo.observeInterrupted { isInterrupted = true } + + expect(isInterrupted) == true + } + + it("should emit a `value` event whenever any delegate method is invoked.") { + var fooCount = 0 + var barCount = 0 + + let foo: Signal<(), Never> = proxy.intercept(#selector(ObjectDelegate.foo)) + foo.observeValues { fooCount += 1 } + + let bar: Signal<(), Never> = proxy.intercept(#selector(ObjectDelegate.bar)) + bar.observeValues { barCount += 1 } + + expect(fooCount) == 0 + expect(barCount) == 0 + + object.delegate?.foo() + object.delegate?.bar?() + expect(fooCount) == 1 + expect(barCount) == 1 + + object.delegate?.foo() + expect(fooCount) == 2 + + object.delegate?.bar?() + expect(barCount) == 2 + } + + it("should accomodate forwardee changes when responding to the protocol requirement checks.") { + expect(proxy.responds(to: #selector(ObjectDelegate.nop))) == false + + let forwardee = ObjectDelegateCounter() + proxy.forwardee = forwardee + expect(proxy.responds(to: #selector(ObjectDelegate.nop))) == true + + proxy.forwardee = nil + expect(proxy.responds(to: #selector(ObjectDelegate.nop))) == false + } + + it("should invoke the method on the forwardee.") { + let forwardee = ObjectDelegateCounter() + proxy.forwardee = forwardee + + var fooCount = 0 + + let foo: Signal<(), Never> = proxy.intercept(#selector(ObjectDelegate.foo)) + foo.observeValues { fooCount += 1 } + + expect(fooCount) == 0 + expect(forwardee.fooCounter) == 0 + + object.delegate?.foo() + expect(fooCount) == 1 + expect(forwardee.fooCounter) == 1 + + object.delegate?.foo() + expect(fooCount) == 2 + expect(forwardee.fooCounter) == 2 + } + + it("should emit a `value` event for an optional requirement even if the forwardee does not implement it.") { + let forwardee = ObjectDelegateCounter() + proxy.forwardee = forwardee + + var barCount = 0 + + let bar: Signal<(), Never> = proxy.intercept(#selector(ObjectDelegate.bar)) + bar.observeValues { barCount += 1 } + + object.delegate?.bar?() + expect(barCount) == 1 + } + + it("should invoke an optional requirement on the forwardee even if it does not implement it.") { + let forwardee = ObjectDelegateCounter() + proxy.forwardee = forwardee + + expect(forwardee.nopCounter) == 0 + + object.delegate?.nop?() + expect(forwardee.nopCounter) == 1 + } + + it("should invoke the original delegate setter whenever the forwardee is updated.") { + // The expected initial count is accounted for the proxy setup. + expect(object.delegateSetCount) == 1 + expect(object.delegateSelectors) == [#selector(ObjectDelegate.foo), #selector(ObjectDelegate.bar)] + + let forwardee = ObjectDelegateCounter() + proxy.forwardee = forwardee + expect(object.delegateSetCount) == 2 + expect(object.delegateSelectors) == [#selector(ObjectDelegate.foo), #selector(ObjectDelegate.bar), #selector(ObjectDelegate.nop)] + expect(object.delegate).to(beIdenticalTo(proxy)) + + proxy.forwardee = nil + expect(object.delegateSetCount) == 3 + expect(object.delegateSelectors) == [#selector(ObjectDelegate.foo), #selector(ObjectDelegate.bar)] + expect(object.delegate).to(beIdenticalTo(proxy)) + } + + describe("interoperability") { + var object: Object! + var proxy: DelegateProxy! + var fooCounter = 0 + + beforeEach { + object = Object() + fooCounter = 0 + } + + func setProxy() { + proxy = ObjectDelegateProxy.proxy(for: object, + setter: #selector(setter: object.delegate), + getter: #selector(getter: object.delegate)) + + let signal: Signal<(), Never> = proxy.intercept(#selector(ObjectDelegate.foo)) + signal.observeValues { fooCounter += 1 } + } + + it("should not affect instances sharing the same runtime subclass") { + _ = object.reactive.producer(forKeyPath: #keyPath(Object.delegateSetCount)).start() + setProxy() + expect(object.delegate).to(beIdenticalTo(proxy)) + + // Another object without RAC swizzling. + let object2 = Object() + _ = object2.reactive.producer(forKeyPath: #keyPath(Object.delegateSetCount)).start() + + expect(object.objcClass).to(beIdenticalTo(object2.objcClass)) + + let className = NSStringFromClass(object_getClass(object)!) + expect(className).to(beginWith("NSKVONotifying_")) + expect(className).toNot(endWith("_RACSwift")) + + let delegate = ObjectDelegateCounter() + object2.delegate = delegate + expect(object2.delegate).to(beIdenticalTo(delegate)) + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by KVO.") { + _ = object.reactive.producer(forKeyPath: #keyPath(Object.delegateSetCount)).start() + expect(object.delegate).to(beNil()) + + setProxy() + expect(object.delegate).to(beIdenticalTo(proxy)) + + object.delegate?.foo() + expect(fooCounter) == 1 + + object.delegate = nil + expect(object.delegate).to(beIdenticalTo(proxy)) + + object.delegate?.foo() + expect(fooCounter) == 2 + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by RAC.") { + _ = object.reactive.trigger(for: #selector(getter: Object.delegateSetCount)) + expect(object.delegate).to(beNil()) + + setProxy() + expect(object.delegate).to(beIdenticalTo(proxy)) + + object.delegate?.foo() + expect(fooCounter) == 1 + + object.delegate = nil + expect(object.delegate).to(beIdenticalTo(proxy)) + + object.delegate?.foo() + expect(fooCounter) == 2 + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by RAC for intercepting the delegate setter.") { + var counter = 0 + + object.reactive + .trigger(for: #selector(setter: Object.delegate)) + .observeValues { counter += 1 } + expect(object.delegate).to(beNil()) + + setProxy() + + // The assignment of the proxy should not be captured by the method + // interception logic. + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(counter) == 0 + + object.delegate?.foo() + expect(fooCounter) == 1 + + object.delegate = nil + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(counter) == 1 + + object.delegate?.foo() + expect(fooCounter) == 2 + } + + it("should be automatically set as the object's delegate even if it is subsequently isa-swizzled by RAC for intercepting the delegate setter.") { + expect(object.delegate).to(beNil()) + + setProxy() + expect(object.delegate).to(beIdenticalTo(proxy)) + + object.delegate?.foo() + expect(fooCounter) == 1 + + var counter = 0 + + object.reactive + .trigger(for: #selector(setter: Object.delegate)) + .observeValues { counter += 1 } + + object.delegate = nil + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(counter) == 1 + + object.delegate?.foo() + expect(fooCounter) == 2 + } + + it("should be automatically set as the object's delegate even if it has already been isa-swizzled by KVO for observing the delegate key path.") { + var counter = 0 + + object.reactive + .signal(forKeyPath: #keyPath(Object.delegate)) + .observeValues { _ in counter += 1 } + expect(object.delegate).to(beNil()) + + setProxy() + + // The assignment of the proxy should not be captured by KVO. + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(counter) == 0 + + object.delegate?.foo() + expect(fooCounter) == 1 + + object.delegate = nil + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(counter) == 0 + + object.delegate?.foo() + expect(fooCounter) == 2 + } + + it("should be automatically set as the object's delegate even if it is subsequently isa-swizzled by KVO for observing the delegate key path.") { + expect(object.delegate).to(beNil()) + + setProxy() + expect(object.delegate).to(beIdenticalTo(proxy)) + + object.delegate?.foo() + expect(fooCounter) == 1 + + var counter = 0 + + object.reactive + .signal(forKeyPath: #keyPath(Object.delegate)) + .observeValues { _ in counter += 1 } + + object.delegate = nil + expect(object.delegate).to(beIdenticalTo(proxy)) + expect(counter) == 1 + + object.delegate?.foo() + expect(fooCounter) == 2 + } + } + } + } +} diff --git a/ReactiveCocoaTests/DeprecationsSpec.swift b/ReactiveCocoaTests/DeprecationsSpec.swift new file mode 100644 index 0000000000..69b44009ee --- /dev/null +++ b/ReactiveCocoaTests/DeprecationsSpec.swift @@ -0,0 +1,29 @@ +import Foundation +import ReactiveCocoa +import ReactiveSwift +import Quick +import Nimble + +class DeprecationsSpec: QuickSpec { + override func spec() { + describe("NSObject.reactive.values(forKeyPath:)") { + class TestKVOObject: NSObject { + @objc dynamic var value: Int = 0 + } + + it("should observe the initial value and changes for the key path") { + let object = TestKVOObject() + var values: [Int] = [] + + object.reactive.producer(forKeyPath: #keyPath(TestKVOObject.value)).startWithValues { value in + values.append(value as! Int) + } + + expect(values) == [0] + + object.value = 1 + expect(values) == [0, 1] + } + } + } +} diff --git a/ReactiveCocoaTests/DynamicPropertySpec.swift b/ReactiveCocoaTests/DynamicPropertySpec.swift new file mode 100644 index 0000000000..a499a659c6 --- /dev/null +++ b/ReactiveCocoaTests/DynamicPropertySpec.swift @@ -0,0 +1,327 @@ +import Foundation +import ReactiveSwift +import Nimble +import Quick +import ReactiveCocoa + +private let initialPropertyValue = "InitialValue" +private let subsequentPropertyValue = "SubsequentValue" +private let finalPropertyValue = "FinalValue" + +private let initialOtherPropertyValue = "InitialOtherValue" +private let subsequentOtherPropertyValue = "SubsequentOtherValue" +private let finalOtherPropertyValue = "FinalOtherValue" + +class DynamicPropertySpec: QuickSpec { + override func spec() { + + describe("DynamicProperty with optional value") { + var object: ObservableObject! + var property: DynamicProperty! + + beforeEach { + object = ObservableObject() + expect(object.rac_optional_value) == 0 + + property = DynamicProperty(object: object, keyPath: "rac_optional_value") + } + + afterEach { + object = nil + } + + let propertyValue: () -> NSNumber? = { + if let value: Any = property?.value { + return value as? NSNumber + } else { + return nil + } + } + + it("should read the underlying object") { + expect(propertyValue()) == 0 + + object.rac_optional_value = nil + expect(propertyValue()).to(beNil()) + } + + it("should write the underlying object") { + property.value = nil + expect(object.rac_optional_value).to(beNil()) + expect(propertyValue()).to(beNil()) + } + + it("should yield a producer that sends the current value and then the changes for the key path of the underlying object") { + var values: [NSNumber?] = [] + property.producer.startWithValues { value in + values.append(value) + } + + expect(values).to(equal([0])) + + property.value = nil + expect(values).to(equal([0, nil])) + + object.rac_optional_value = 2 + expect(values).to(equal([0, nil, 2])) + + object.rac_optional_value = nil + expect(values).to(equal([0, nil, 2, nil])) + print(values) + } + } + + describe("DynamicProperty") { + var object: ObservableObject! + var property: DynamicProperty! + + let propertyValue: () -> Int? = { + if let value: Any = property?.value { + return value as? Int + } else { + return nil + } + } + + beforeEach { + object = ObservableObject() + expect(object.rac_value) == 0 + + property = DynamicProperty(object: object, keyPath: "rac_value") + } + + afterEach { + object = nil + } + + it("should read the underlying object") { + expect(propertyValue()) == 0 + + object.rac_value = 1 + expect(propertyValue()) == 1 + } + + it("should write the underlying object") { + property.value = 1 + expect(object.rac_value) == 1 + expect(propertyValue()) == 1 + } + + it("should yield a producer that sends the current value and then the changes for the key path of the underlying object") { + var values: [Int] = [] + property.producer.startWithValues { value in + values.append(value) + } + + expect(values) == [ 0 ] + + property.value = 1 + expect(values) == [ 0, 1 ] + + object.rac_value = 2 + expect(values) == [ 0, 1, 2 ] + } + + it("should yield a producer that sends the current value and then the changes for the key path of the underlying object, even if the value actually remains unchanged") { + var values: [Int] = [] + property.producer.startWithValues { value in + values.append(value) + } + + expect(values) == [ 0 ] + + property.value = 0 + expect(values) == [ 0, 0 ] + + object.rac_value = 0 + expect(values) == [ 0, 0, 0 ] + } + + it("should yield a signal that emits subsequent values for the key path of the underlying object") { + var values: [Int] = [] + property.signal.observeValues { value in + expect(value).notTo(beNil()) + values.append(value) + } + + expect(values) == [] + + property.value = 1 + expect(values) == [ 1 ] + + object.rac_value = 2 + expect(values) == [ 1, 2 ] + } + + it("should yield a signal that emits subsequent values for the key path of the underlying object, even if the value actually remains unchanged") { + var values: [Int] = [] + property.signal.observeValues { value in + values.append(value) + } + + expect(values) == [] + + property.value = 0 + expect(values) == [ 0 ] + + object.rac_value = 0 + expect(values) == [ 0, 0 ] + } + + it("should have a completed producer when the underlying object deallocates") { + var completed = false + + property = { + // Use a closure so this object has a shorter lifetime. + let object = ObservableObject() + let property = DynamicProperty(object: object, keyPath: "rac_value") + + property.producer.startWithCompleted { + completed = true + } + + expect(completed) == false + expect(property.value).notTo(beNil()) + return property + }() + + expect(completed).toEventually(beTruthy()) + } + + it("should have a completed signal when the underlying object deallocates") { + var completed = false + + property = { + // Use a closure so this object has a shorter lifetime. + let object = ObservableObject() + let property = DynamicProperty(object: object, keyPath: "rac_value") + + property.signal.observeCompleted { + completed = true + } + + expect(completed) == false + expect(property.value).notTo(beNil()) + return property + }() + + expect(completed).toEventually(beTruthy()) + } + + it("should not be retained by its underlying object"){ + weak var dynamicProperty: DynamicProperty? = property + + property = nil + expect(dynamicProperty).to(beNil()) + } + + it("should support un-bridged reference types") { + let dynamicProperty = DynamicProperty(object: object, keyPath: "rac_reference") + dynamicProperty.value = UnbridgedObject("foo") + expect(object.rac_reference.value) == "foo" + } + + it("should support un-bridged value types") { + let dynamicProperty = DynamicProperty(object: object, keyPath: "rac_unbridged") + dynamicProperty.value = .changed + expect(object.rac_unbridged as? UnbridgedValue) == UnbridgedValue.changed + } + + it("should expose a lifetime that ends upon the deinitialization of its underlying object") { + var isEnded = false + property!.lifetime.ended.observeCompleted { + isEnded = true + } + + expect(isEnded) == false + + property = nil + expect(isEnded) == false + + object = nil + expect(isEnded) == true + } + } + + describe("binding") { + describe("to a dynamic property") { + var object: ObservableObject! + var property: DynamicProperty! + + beforeEach { + object = ObservableObject() + expect(object.rac_value) == 0 + + property = DynamicProperty(object: object, keyPath: "rac_value") + } + + afterEach { + object = nil + } + + it("should bridge values sent on a signal to Objective-C") { + let (signal, observer) = Signal.pipe() + property <~ signal + observer.send(value: 1) + expect(object.rac_value) == 1 + } + + it("should bridge values sent on a signal producer to Objective-C") { + let producer = SignalProducer(value: 1) + property <~ producer + expect(object.rac_value) == 1 + } + + it("should bridge values from a source property to Objective-C") { + let source = MutableProperty(1) + property <~ source + expect(object.rac_value) == 1 + } + + it("should bridge values sent on a signal to Objective-C, even if the view has deinitialized") { + let (signal, observer) = Signal.pipe() + property <~ signal + property = nil + + observer.send(value: 1) + expect(object.rac_value) == 1 + } + + it("should bridge values sent on a signal producer to Objective-C, even if the view has deinitialized") { + let producer = SignalProducer(value: 1) + property <~ producer + property = nil + + expect(object.rac_value) == 1 + } + + it("should bridge values from a source property to Objective-C, even if the view has deinitialized") { + let source = MutableProperty(1) + property <~ source + property = nil + + expect(object.rac_value) == 1 + } + } + } + } +} + +private class ObservableObject: NSObject { + @objc dynamic var rac_value: Int = 0 + @objc dynamic var rac_optional_value: NSNumber? = 0 + @objc dynamic var rac_reference: UnbridgedObject = UnbridgedObject("") + @objc dynamic var rac_unbridged: Any = UnbridgedValue.starting +} + +private class UnbridgedObject: NSObject { + let value: String + init(_ value: String) { + self.value = value + } +} + +private enum UnbridgedValue { + case starting + case changed +} diff --git a/ReactiveCocoaTests/Info.plist b/ReactiveCocoaTests/Info.plist index ba72822e87..1554da65db 100644 --- a/ReactiveCocoaTests/Info.plist +++ b/ReactiveCocoaTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.0 + 6.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/ReactiveCocoaTests/InterceptingPerformanceTests.swift b/ReactiveCocoaTests/InterceptingPerformanceTests.swift new file mode 100644 index 0000000000..df1a5f5d4a --- /dev/null +++ b/ReactiveCocoaTests/InterceptingPerformanceTests.swift @@ -0,0 +1,148 @@ +import XCTest +@testable import ReactiveCocoa +import ReactiveSwift + +private let iterationCount = 500 + +private final class Receiver1: NSObject { + @objc dynamic func message() {} +} + +private final class Receiver2: NSObject { + @objc dynamic var value: Int = 0 +} + +private final class Receiver3: NSObject { + @objc dynamic var value: Int = 0 +} + +private final class Receiver4: NSObject { + @objc dynamic var value: Int = 0 +} + +private final class Receiver5: NSObject { + @objc dynamic var value: Int = 0 +} + +private final class Receiver6: NSObject { + @objc dynamic var value: Int = 0 +} + +class InterceptingTests: XCTestCase { + override class func setUp() { + // Swizzle all classes ahead. + + // General interception. + let receiver1 = Receiver1() + _ = receiver1.reactive.trigger(for: #selector(receiver1.message)) + + // Setter: RAC then KVO + let receiver2 = Receiver2() + _ = receiver2.reactive.trigger(for: #selector(setter: receiver2.value)) + receiver2.reactive.producer(forKeyPath: #keyPath(Receiver2.value)).start() + + // Setter: KVO then RAC + let receiver3 = Receiver3() + receiver3.reactive.producer(forKeyPath: #keyPath(Receiver3.value)).start() + _ = receiver3.reactive.trigger(for: #selector(setter: receiver3.value)) + + // RAC swizzled getter, and then KVO swizzled setter. + let receiver4 = Receiver4() + _ = receiver4.reactive.trigger(for: #selector(getter: receiver4.value)) + receiver4.reactive.producer(forKeyPath: #keyPath(Receiver4.value)).start() + + // KVO swizzled setter, and then RAC swizzled getter. + let receiver5 = Receiver5() + receiver5.reactive.producer(forKeyPath: #keyPath(Receiver5.value)).start() + _ = receiver5.reactive.trigger(for: #selector(getter: receiver5.value)) + + // Normal KVO + let receiver6 = Receiver6() + receiver6.reactive.producer(forKeyPath: #keyPath(Receiver6.value)).start() + } + + func testInterceptedMessage() { + let receiver = Receiver1() + _ = receiver.reactive.trigger(for: #selector(receiver.message)) + + measure { + for _ in 0 ..< iterationCount { + receiver.message() + } + } + } + + func testRACKVOInterceptSetterThenSet() { + let receiver = Receiver2() + + _ = receiver.reactive.trigger(for: #selector(setter: receiver.value)) + receiver.reactive.producer(forKeyPath: #keyPath(Receiver2.value)).start() + + measure { + for i in 0 ..< iterationCount { + receiver.value = i + } + } + } + + func testKVORACInterceptSetterThenSet() { + let receiver = Receiver3() + + receiver.reactive.producer(forKeyPath: #keyPath(Receiver3.value)).start() + _ = receiver.reactive.trigger(for: #selector(setter: receiver.value)) + + measure { + for i in 0 ..< iterationCount { + receiver.value = i + } + } + } + + func testRACKVOInterceptSetterThenGet() { + let receiver = Receiver4() + + _ = receiver.reactive.trigger(for: #selector(getter: receiver.value)) + receiver.reactive.producer(forKeyPath: #keyPath(Receiver4.value)).start() + + measure { + for _ in 0 ..< iterationCount { + _ = receiver.value + } + } + } + + func testKVORACInterceptSetterThenGet() { + let receiver = Receiver5() + + receiver.reactive.producer(forKeyPath: #keyPath(Receiver5.value)).start() + _ = receiver.reactive.trigger(for: #selector(getter: receiver.value)) + + measure { + for _ in 0 ..< iterationCount { + _ = receiver.value + } + } + } + + func testJustKVOInterceptSetterThenSet() { + let receiver = Receiver6() + receiver.reactive.producer(forKeyPath: #keyPath(Receiver6.value)).start() + + measure { + for i in 0 ..< iterationCount { + receiver.value = i + } + } + } + + func testJustKVOInterceptSetterThenGet() { + let receiver = Receiver6() + receiver.reactive.producer(forKeyPath: #keyPath(Receiver6.value)).start() + + measure { + for _ in 0 ..< iterationCount { + _ = receiver.value + } + } + } +} diff --git a/ReactiveCocoaTests/InterceptingSpec.swift b/ReactiveCocoaTests/InterceptingSpec.swift new file mode 100644 index 0000000000..b1ce061409 --- /dev/null +++ b/ReactiveCocoaTests/InterceptingSpec.swift @@ -0,0 +1,952 @@ +import Foundation +#if SWIFT_PACKAGE +import ReactiveCocoaObjC +import ReactiveCocoaObjCTestSupport +#endif +@testable import ReactiveCocoa +import ReactiveSwift +import Quick +import Nimble +import CoreGraphics + +class InterceptingSpec: QuickSpec { + override func spec() { + beforeSuite { + ForwardInvocationTestObject._initialize() + } + + describe("trigger(for:)") { + var object: InterceptedObject! + weak var _object: InterceptedObject? + + beforeEach { + object = InterceptedObject() + _object = object + } + + afterEach { + object = nil + expect(_object).to(beNil()) + } + + it("should send a value when the selector is invoked") { + let signal = object.reactive.trigger(for: #selector(object.increment)) + + var counter = 0 + signal.observeValues { counter += 1 } + + expect(counter) == 0 + expect(object.counter) == 0 + + object.increment() + expect(counter) == 1 + expect(object.counter) == 1 + + object.increment() + expect(counter) == 2 + expect(object.counter) == 2 + } + + it("should complete when the object deinitializes") { + let signal = object.reactive.trigger(for: #selector(object.increment)) + + var isCompleted = false + signal.observeCompleted { isCompleted = true } + expect(isCompleted) == false + + object = nil + expect(_object).to(beNil()) + expect(isCompleted) == true + } + + it("should multicast") { + let signal1 = object.reactive.trigger(for: #selector(object.increment)) + let signal2 = object.reactive.trigger(for: #selector(object.increment)) + + var counter1 = 0 + var counter2 = 0 + signal1.observeValues { counter1 += 1 } + signal2.observeValues { counter2 += 1 } + + expect(counter1) == 0 + expect(counter2) == 0 + + object.increment() + expect(counter1) == 1 + expect(counter2) == 1 + + object.increment() + expect(counter1) == 2 + expect(counter2) == 2 + } + + it("should not deadlock") { + for _ in 1 ... 10 { + var isDeadlocked = true + + func createQueue() -> DispatchQueue { + .global(qos: .userInitiated) + } + + createQueue().async { + _ = object.reactive.trigger(for: #selector(object.increment)) + + createQueue().async { + _ = object.reactive.trigger(for: #selector(object.increment)) + + isDeadlocked = false + } + } + + expect(isDeadlocked).toEventually(beFalsy()) + } + } + + it("should send completed on deallocation") { + var completed = false + var deallocated = false + + autoreleasepool { + let object = InterceptedObject() + + object.reactive.lifetime.ended.observeCompleted { + deallocated = true + } + + object.reactive.trigger(for: #selector(object.lifeIsGood)).observeCompleted { + completed = true + } + + expect(deallocated) == false + expect(completed) == false + } + + expect(deallocated) == true + expect(completed) == true + } + + it("should send arguments for invocation and invoke the original method on previously KVO'd receiver") { + var latestValue: Bool? + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .startWithValues { objectValue in + latestValue = objectValue as! Bool? + } + + expect(latestValue).to(beNil()) + + var firstValue: Bool? + var secondValue: String? + + object.reactive + .signal(for: #selector(object.set(first:second:))) + .observeValues { x in + firstValue = x[0] as! Bool? + secondValue = x[1] as! String? + } + + object.set(first: true, second: "Winner") + + expect(object.hasInvokedSetObjectValueAndSecondObjectValue) == true + expect(object.objectValue as! Bool?) == true + expect(object.secondObjectValue as! String?) == "Winner" + + expect(latestValue) == true + + expect(firstValue) == true + expect(secondValue) == "Winner" + } + + it("should send arguments for invocation and invoke the a KVO-swizzled then RAC-swizzled setter") { + var latestValue: Bool? + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .startWithValues { objectValue in + latestValue = objectValue as! Bool? + } + + expect(latestValue).to(beNil()) + + var value: Bool? + object.reactive.signal(for: #selector(setter: object.objectValue)).observeValues { x in + value = x[0] as! Bool? + } + + object.objectValue = true + + expect(object.objectValue as! Bool?) == true + expect(latestValue) == true + expect(value) == true + } + + it("should send arguments for invocation and invoke the a RAC-swizzled then KVO-swizzled setter") { + let object = InterceptedObject() + + var value: Bool? + object.reactive.signal(for: #selector(setter: object.objectValue)).observeValues { x in + value = x[0] as! Bool? + } + + var latestValue: Bool? + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .startWithValues { objectValue in + latestValue = objectValue as! Bool? + } + + expect(latestValue).to(beNil()) + + object.objectValue = true + + expect(object.objectValue as! Bool?) == true + expect(latestValue) == true + expect(value) == true + } + + it("should send arguments for invocation and invoke the original method when receiver is subsequently KVO'd") { + let object = InterceptedObject() + + var firstValue: Bool? + var secondValue: String? + + object.reactive.signal(for: #selector(object.set(first:second:))).observeValues { x in + firstValue = x[0] as! Bool? + secondValue = x[1] as! String? + } + + var latestValue: Bool? + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .startWithValues { objectValue in + latestValue = objectValue as! Bool? + } + + expect(latestValue).to(beNil()) + + object.set(first: true, second: "Winner") + + expect(object.hasInvokedSetObjectValueAndSecondObjectValue) == true + expect(object.objectValue as! Bool?) == true + expect(object.secondObjectValue as! String?) == "Winner" + + expect(latestValue) == true + + expect(firstValue) == true + expect(secondValue) == "Winner" + } + + it("should send a value event for every invocation of a method on a receiver that is subsequently KVO'd twice") { + var counter = 0 + + object.reactive.trigger(for: #selector(setter: InterceptedObject.objectValue)) + .observeValues { counter += 1 } + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + .dispose() + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + + expect(counter) == 0 + + object.objectValue = 1 + expect(counter) == 1 + + object.objectValue = 1 + expect(counter) == 2 + } + + it("should send a value event for every invocation of a method on a receiver that is KVO'd twice while being swizzled by RAC in between") { + var counter = 0 + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + .dispose() + + object.reactive.trigger(for: #selector(setter: InterceptedObject.objectValue)) + .observeValues { counter += 1 } + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + + expect(counter) == 0 + + object.objectValue = 1 + expect(counter) == 1 + + object.objectValue = 1 + expect(counter) == 2 + } + + it("should call the right signal for two instances of the same class, adding signals for the same selector") { + let object1 = InterceptedObject() + let object2 = InterceptedObject() + + let selector = NSSelectorFromString("lifeIsGood:") + + var value1: Int? + object1.reactive.signal(for: selector).observeValues { x in + value1 = x[0] as! Int? + } + + var value2: Int? + object2.reactive.signal(for: selector).observeValues { x in + value2 = x[0] as! Int? + } + + object1.lifeIsGood(42) + expect(value1) == 42 + expect(value2).to(beNil()) + + object2.lifeIsGood(420) + expect(value1) == 42 + expect(value2) == 420 + } + + it("should send on signal after the original method is invoked") { + let object = InterceptedObject() + + var invokedMethodBefore = false + object.reactive.trigger(for: #selector(object.set(first:second:))).observeValues { + invokedMethodBefore = object.hasInvokedSetObjectValueAndSecondObjectValue + } + + object.set(first: true, second: "Winner") + expect(invokedMethodBefore) == true + } + } + + describe("interoperability") { + var invoked: Bool! + var object: InterceptedObject! + var originalClass: AnyClass! + + beforeEach { + invoked = false + object = InterceptedObject() + originalClass = InterceptedObject.self + } + + it("should invoke the swizzled `forwardInvocation:` on an instance isa-swizzled by both RAC and KVO.") { + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + + _ = object.reactive.trigger(for: #selector(object.lifeIsGood)) + + let swizzledSelector = #selector(object.lifeIsGood) + + // Redirect `swizzledSelector` to the forwarding machinery. + let method = class_getInstanceMethod(originalClass, swizzledSelector)! + let typeEncoding = method_getTypeEncoding(method) + + let original = class_replaceMethod(originalClass, + swizzledSelector, + _rac_objc_msgForward, + typeEncoding) ?? noImplementation + defer { + _ = class_replaceMethod(originalClass, + swizzledSelector, + original, + typeEncoding) + } + + // Swizzle `forwardInvocation:` to intercept `swizzledSelector`. + let forwardInvocationBlock: @convention(block) (AnyObject, AnyObject) -> Void = { _, invocation in + if (invocation.selector == swizzledSelector) { + expect(invoked) == false + invoked = true + } + } + + let method2 = class_getInstanceMethod(originalClass, ObjCSelector.forwardInvocation)! + let typeEncoding2 = method_getTypeEncoding(method2) + + let original2 = class_replaceMethod(originalClass, + ObjCSelector.forwardInvocation, + imp_implementationWithBlock(forwardInvocationBlock as Any), + typeEncoding2) ?? noImplementation + defer { + _ = class_replaceMethod(originalClass, + ObjCSelector.forwardInvocation, + original2, + typeEncoding2) + } + + object.lifeIsGood(nil) + expect(invoked) == true + } + + it("should invoke the swizzled `forwardInvocation:` on an instance isa-swizzled by RAC.") { + _ = object.reactive.trigger(for: #selector(object.lifeIsGood)) + + let swizzledSelector = #selector(object.lifeIsGood) + + // Redirect `swizzledSelector` to the forwarding machinery. + let method = class_getInstanceMethod(originalClass, swizzledSelector)! + let typeEncoding = method_getTypeEncoding(method) + + let original = class_replaceMethod(originalClass, + swizzledSelector, + _rac_objc_msgForward, + typeEncoding) ?? noImplementation + defer { + _ = class_replaceMethod(originalClass, + swizzledSelector, + original, + typeEncoding) + } + + // Swizzle `forwardInvocation:` to intercept `swizzledSelector`. + let forwardInvocationBlock: @convention(block) (AnyObject, AnyObject) -> Void = { _, invocation in + if (invocation.selector == swizzledSelector) { + expect(invoked) == false + invoked = true + } + } + + let method2 = class_getInstanceMethod(originalClass, ObjCSelector.forwardInvocation)! + let typeEncoding2 = method_getTypeEncoding(method2) + + let original2 = class_replaceMethod(originalClass, + ObjCSelector.forwardInvocation, + imp_implementationWithBlock(forwardInvocationBlock as Any), + typeEncoding2) ?? noImplementation + defer { + _ = class_replaceMethod(originalClass, + ObjCSelector.forwardInvocation, + original2, + typeEncoding2) + } + + object.lifeIsGood(nil) + expect(invoked) == true + } + + it("should invoke the swizzled selector on an instance isa-swizzled by RAC.") { + _ = object.reactive.trigger(for: #selector(object.lifeIsGood)) + + let swizzledSelector = #selector(object.lifeIsGood) + + let lifeIsGoodBlock: @convention(block) (AnyObject, AnyObject) -> Void = { _, _ in + expect(invoked) == false + invoked = true + } + + let method = class_getInstanceMethod(originalClass, swizzledSelector)! + let typeEncoding = method_getTypeEncoding(method) + + let original = class_replaceMethod(originalClass, + swizzledSelector, + imp_implementationWithBlock(lifeIsGoodBlock as Any), + typeEncoding) ?? noImplementation + defer { + _ = class_replaceMethod(originalClass, + swizzledSelector, + original, + typeEncoding) + } + + object.lifeIsGood(nil) + expect(invoked) == true + } + } + + it("should swizzle an NSObject method") { + let object = NSObject() + + var value: [Any?]? + + object.reactive + .signal(for: #selector(getter: object.description)) + .observeValues { x in + value = x + } + + expect(value).to(beNil()) + + expect(object.description).notTo(beNil()) + expect(value).toNot(beNil()) + } + + describe("a class that already overrides -forwardInvocation:") { + it("should invoke the superclass' implementation") { + let object = ForwardInvocationTestObject() + + var value: Int? + object.reactive + .signal(for: #selector(object.lifeIsGood)) + .observeValues { x in + value = x[0] as! Int? + } + + object.lifeIsGood(42) + expect(value) == 42 + + expect(object.forwardedCount) == 0 + + object.perform(ForwardInvocationTestObject.forwardedSelector) + + expect(object.forwardedCount) == 1 + expect(object.forwardedSelector) == ForwardInvocationTestObject.forwardedSelector + } + + it("should not infinite recurse when KVO'd after RAC swizzled") { + let object = ForwardInvocationTestObject() + + var value: Int? + + object.reactive + .signal(for: #selector(object.lifeIsGood)) + .observeValues { x in + value = x[0] as! Int? + } + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + + object.lifeIsGood(42) + expect(value) == 42 + + expect(object.forwardedCount) == 0 + + object.perform(ForwardInvocationTestObject.forwardedSelector) + + expect(object.forwardedCount) == 1 + expect(object.forwardedSelector) == ForwardInvocationTestObject.forwardedSelector + } + } + + describe("two classes in the same hierarchy") { + var superclassObj: InterceptedObject! + var superclassTuple: [Any]! + + var subclassObj: InterceptedObjectSubclass! + var subclassTuple: [Any]! + + beforeEach { + superclassObj = InterceptedObject() + expect(superclassObj).notTo(beNil()) + + subclassObj = InterceptedObjectSubclass() + expect(subclassObj).notTo(beNil()) + } + + it("should not collide") { + superclassObj.reactive + .signal(for: #selector(InterceptedObject.foo)) + .observeValues { args in + superclassTuple = args.map { $0 ?? NSNull() } + } + + subclassObj + .reactive + .signal(for: #selector(InterceptedObject.foo)) + .observeValues { args in + subclassTuple = args.map { $0 ?? NSNull() } + } + + expect(superclassObj.foo(40, "foo")) == "Not Subclass 40 foo" + + let expectedValues = [40, "foo"] as NSArray + expect(superclassTuple as NSArray) == expectedValues + + expect(subclassObj.foo(40, "foo")) == "Subclass 40 foo" + + expect(subclassTuple as NSArray) == expectedValues + } + + it("should not collide when the superclass is invoked asynchronously") { + superclassObj.reactive + .signal(for: #selector(InterceptedObject.set(first:second:))) + .observeValues { args in + superclassTuple = args.map { $0 ?? NSNull() } + } + + subclassObj + .reactive + .signal(for: #selector(InterceptedObject.set(first:second:))) + .observeValues { args in + subclassTuple = args.map { $0 ?? NSNull() } + } + + superclassObj.set(first: "foo", second:"42") + expect(superclassObj.hasInvokedSetObjectValueAndSecondObjectValue) == true + + let expectedValues = ["foo", "42"] as NSArray + expect(superclassTuple as NSArray) == expectedValues + + subclassObj.set(first: "foo", second:"42") + expect(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue) == false + expect(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue).toEventually(beTruthy()) + + expect(subclassTuple as NSArray) == expectedValues + } + } + + describe("class reporting") { + var object: InterceptedObject! + var originalClass: AnyClass! + + beforeEach { + object = InterceptedObject() + originalClass = InterceptedObject.self + } + + it("should report the original class") { + _ = object.reactive.trigger(for: #selector(object.lifeIsGood)) + expect((object as AnyObject).objcClass!).to(beIdenticalTo(originalClass)) + } + + it("should report the original class when it's KVO'd after dynamically subclassing") { + _ = object.reactive.trigger(for: #selector(object.lifeIsGood)) + + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + + expect((object as AnyObject).objcClass!).to(beIdenticalTo(originalClass)) + } + + it("should report the original class when it's KVO'd before dynamically subclassing") { + object.reactive + .producer(forKeyPath: #keyPath(InterceptedObject.objectValue)) + .start() + + _ = object.reactive.trigger(for: #selector(object.lifeIsGood)) + expect((object as AnyObject).objcClass!).to(beIdenticalTo(originalClass)) + } + } + + describe("signal(for:)") { + var object: InterceptedObject! + weak var _object: InterceptedObject? + + beforeEach { + object = InterceptedObject() + _object = object + } + + afterEach { + object = nil + expect(_object).to(beNil()) + } + + it("should send a value with bridged numeric arguments") { + let signal = object.reactive.signal(for: #selector(object.testNumericValues(c:s:i:l:ll:uc:us:ui:ul:ull:f:d:b:))) + + var arguments = [[Any?]]() + signal.observeValues { arguments.append($0) } + + expect(arguments.count) == 0 + + func call(offset: UInt) { + object.testNumericValues(c: CChar.max - CChar(offset), + s: CShort.max - CShort(offset), + i: CInt.max - CInt(offset), + l: CLong.max - CLong(offset), + ll: CLongLong.max - CLongLong(offset), + uc: CUnsignedChar.max - CUnsignedChar(offset), + us: CUnsignedShort.max - CUnsignedShort(offset), + ui: CUnsignedInt.max - CUnsignedInt(offset), + ul: CUnsignedLong.max - CUnsignedLong(offset), + ull: CUnsignedLongLong.max - CUnsignedLongLong(offset), + f: CFloat.greatestFiniteMagnitude - CFloat(offset), + d: CDouble.greatestFiniteMagnitude - CDouble(offset), + b: offset % 2 == 0 ? true : false) + } + + func validate(arguments: [Any?], offset: UInt) { + #if swift(>=3.1) + expect((arguments[0] as! CChar)) == CChar.max - CChar(offset) + expect((arguments[1] as! CShort)) == CShort.max - CShort(offset) + expect((arguments[2] as! CInt)) == CInt.max - CInt(offset) + expect((arguments[3] as! CLong)) == CLong.max - CLong(offset) + expect((arguments[4] as! CLongLong)) == CLongLong.max - CLongLong(offset) + expect((arguments[5] as! CUnsignedChar)) == CUnsignedChar.max - CUnsignedChar(offset) + expect((arguments[6] as! CUnsignedShort)) == CUnsignedShort.max - CUnsignedShort(offset) + expect((arguments[7] as! CUnsignedInt)) == CUnsignedInt.max - CUnsignedInt(offset) + expect((arguments[8] as! CUnsignedLong)) == CUnsignedLong.max - CUnsignedLong(offset) + expect((arguments[9] as! CUnsignedLongLong)) == CUnsignedLongLong.max - CUnsignedLongLong(offset) + expect((arguments[10] as! CFloat)) == CFloat.greatestFiniteMagnitude - CFloat(offset) + expect((arguments[11] as! CDouble)) == CDouble.greatestFiniteMagnitude - CDouble(offset) + expect((arguments[12] as! Bool)) == (offset % 2 == 0 ? true : false) + #else + expect((arguments[0] as! NSNumber).int8Value) == CChar.max - CChar(offset) + expect((arguments[1] as! NSNumber).int16Value) == CShort.max - CShort(offset) + expect((arguments[2] as! NSNumber).int32Value) == CInt.max - CInt(offset) + expect((arguments[3] as! NSNumber).intValue) == CLong.max - CLong(offset) + expect((arguments[4] as! NSNumber).int64Value) == CLongLong.max - CLongLong(offset) + expect((arguments[5] as! NSNumber).uint8Value) == CUnsignedChar.max - CUnsignedChar(offset) + expect((arguments[6] as! NSNumber).uint16Value) == CUnsignedShort.max - CUnsignedShort(offset) + expect((arguments[7] as! NSNumber).uint32Value) == CUnsignedInt.max - CUnsignedInt(offset) + expect((arguments[8] as! NSNumber).uintValue) == CUnsignedLong.max - CUnsignedLong(offset) + expect((arguments[9] as! NSNumber).uint64Value) == CUnsignedLongLong.max - CUnsignedLongLong(offset) + expect((arguments[10] as! NSNumber).floatValue) == CFloat.greatestFiniteMagnitude - CFloat(offset) + expect((arguments[11] as! NSNumber).doubleValue) == CDouble.greatestFiniteMagnitude - CDouble(offset) + expect((arguments[12] as! NSNumber).boolValue) == (offset % 2 == 0 ? true : false) + #endif + } + + call(offset: 0) + expect(arguments.count) == 1 + validate(arguments: arguments[0], offset: 0) + + call(offset: 1) + expect(arguments.count) == 2 + validate(arguments: arguments[1], offset: 1) + + call(offset: 2) + expect(arguments.count) == 3 + validate(arguments: arguments[2], offset: 2) + } + + it("should send a value with bridged reference arguments") { + let signal = object.reactive.signal(for: #selector(object.testReferences(nonnull:nullable:iuo:class:nullableClass:iuoClass:))) + + var arguments = [[Any?]]() + signal.observeValues { arguments.append($0) } + + expect(arguments.count) == 0 + + let token = NSObject() + + object.testReferences(nonnull: token, + nullable: token, + iuo: token, + class: InterceptingSpec.self, + nullableClass: InterceptingSpec.self, + iuoClass: InterceptingSpec.self) + + expect(arguments.count) == 1 + + expect((arguments[0][0] as! NSObject)) == token + expect(arguments[0][1] as! NSObject?) == token + expect(arguments[0][2] as! NSObject?) == token + expect(arguments[0][3] as! AnyClass is InterceptingSpec.Type) == true + expect(arguments[0][4] as! AnyClass? is InterceptingSpec.Type) == true + expect(arguments[0][5] as! AnyClass? is InterceptingSpec.Type) == true + + object.testReferences(nonnull: token, + nullable: nil, + iuo: nil, + class: InterceptingSpec.self, + nullableClass: nil, + iuoClass: nil) + + expect(arguments.count) == 2 + + expect((arguments[1][0] as! NSObject)) == token + expect(arguments[1][1] as! NSObject?).to(beNil()) + expect(arguments[1][2] as! NSObject?).to(beNil()) + expect(arguments[1][3] as! AnyClass is InterceptingSpec.Type) == true + expect(arguments[1][4] as! AnyClass?).to(beNil()) + expect(arguments[1][5] as! AnyClass?).to(beNil()) + } + + it("should send a value with bridged struct arguments") { + let signal = object.reactive.signal(for: #selector(object.testBridgedStructs(p:s:r:a:))) + + var arguments = [[Any?]]() + signal.observeValues { arguments.append($0) } + + expect(arguments.count) == 0 + + func call(offset: CGFloat) { + object.testBridgedStructs(p: CGPoint(x: offset, y: offset), + s: CGSize(width: offset, height: offset), + r: CGRect(x: offset, y: offset, width: offset, height: offset), + a: CGAffineTransform(translationX: offset, y: offset)) + } + + func validate(arguments: [Any?], offset: CGFloat) { + #if swift(>=3.1) + expect((arguments[0] as! CGPoint)) == CGPoint(x: offset, y: offset) + expect((arguments[1] as! CGSize)) == CGSize(width: offset, height: offset) + expect((arguments[2] as! CGRect)) == CGRect(x: offset, y: offset, width: offset, height: offset) + expect((arguments[3] as! CGAffineTransform)) == CGAffineTransform(translationX: offset, y: offset) + #elseif os(macOS) + expect((arguments[0] as! NSValue).pointValue) == CGPoint(x: offset, y: offset) + expect((arguments[1] as! NSValue).sizeValue) == CGSize(width: offset, height: offset) + expect((arguments[2] as! NSValue).rectValue) == CGRect(x: offset, y: offset, width: offset, height: offset) + #else + expect((arguments[0] as! NSValue).cgPointValue) == CGPoint(x: offset, y: offset) + expect((arguments[1] as! NSValue).cgSizeValue) == CGSize(width: offset, height: offset) + expect((arguments[2] as! NSValue).cgRectValue) == CGRect(x: offset, y: offset, width: offset, height: offset) + expect((arguments[3] as! NSValue).cgAffineTransformValue) == CGAffineTransform(translationX: offset, y: offset) + #endif + } + + call(offset: 0) + expect(arguments.count) == 1 + validate(arguments: arguments[0], offset: 0) + + call(offset: 1) + expect(arguments.count) == 2 + validate(arguments: arguments[1], offset: 1) + + call(offset: 2) + expect(arguments.count) == 3 + validate(arguments: arguments[2], offset: 2) + } + + it("should complete when the object deinitializes") { + let signal = object.reactive.signal(for: #selector(object.increment)) + + var isCompleted = false + signal.observeCompleted { isCompleted = true } + expect(isCompleted) == false + + object = nil + expect(_object).to(beNil()) + expect(isCompleted) == true + } + + it("should multicast") { + let signal1 = object.reactive.signal(for: #selector(object.increment)) + let signal2 = object.reactive.signal(for: #selector(object.increment)) + + var counter1 = 0 + var counter2 = 0 + signal1.observeValues { _ in counter1 += 1 } + signal2.observeValues { _ in counter2 += 1 } + + expect(counter1) == 0 + expect(counter2) == 0 + + object.increment() + expect(counter1) == 1 + expect(counter2) == 1 + + object.increment() + expect(counter1) == 2 + expect(counter2) == 2 + } + + it("should not deadlock") { + let queue = DispatchQueue.global() + for _ in 1 ... 10 { + var isDeadlocked = true + + queue.async { + _ = object.reactive.signal(for: #selector(object.increment)) + + queue.async { + _ = object.reactive.signal(for: #selector(object.increment)) + + isDeadlocked = false + } + } + + expect(isDeadlocked).toEventually(beFalsy()) + } + } + } + + describe("classes utilising the message forwarding mechanism") { + it("should receive the message without needing to implement it in the runtime") { + let entity = MessageForwardingEntity() + expect(entity.hasInvoked) == false + + var latestValue: Bool? + entity.reactive + .signal(for: #selector(setter: entity.hasInvoked)) + .observeValues { latestValue = $0[0] as? Bool } + + expect(entity.hasInvoked) == false + expect(latestValue).to(beNil()) + + entity.perform(Selector(("_rac_test_forwarding"))) + expect(entity.hasInvoked) == true + expect(latestValue) == true + } + } + } +} + +private class ForwardInvocationTestObject: InterceptedObject { + static let forwardedSelector = Selector((("forwarded"))) + + var forwardedCount = 0 + var forwardedSelector: Selector? + + fileprivate static func _initialize() { + let impl: @convention(c) (Any, Selector, AnyObject) -> Void = { object, _, invocation in + let object = object as! ForwardInvocationTestObject + object.forwardedCount += 1 + object.forwardedSelector = invocation.selector + } + + let success = class_addMethod(ForwardInvocationTestObject.self, + ObjCSelector.forwardInvocation, + unsafeBitCast(impl, to: IMP.self), + ObjCMethodEncoding.forwardInvocation) + + assert(success) + assert(ForwardInvocationTestObject.instancesRespond(to: ObjCSelector.forwardInvocation)) + + let success2 = class_addMethod(ForwardInvocationTestObject.self, + ForwardInvocationTestObject.forwardedSelector, + _rac_objc_msgForward, + ObjCMethodEncoding.forwardInvocation) + + assert(success2) + assert(ForwardInvocationTestObject.instancesRespond(to: ForwardInvocationTestObject.forwardedSelector)) + } +} + +@objc private protocol NSInvocationProtocol { + var target: NSObject { get } + var selector: Selector { get } + + func invoke() +} + +private class InterceptedObjectSubclass: InterceptedObject { + dynamic override func foo(_ number: Int, _ string: String) -> String { + return "Subclass \(number) \(string)" + } + + dynamic override func set(first: Any?, second: Any?) { + DispatchQueue.main.async { + super.set(first: first, second: second) + } + } +} + +private class InterceptedObject: NSObject { + var counter = 0 + @objc dynamic var hasInvokedSetObjectValueAndSecondObjectValue = false + @objc dynamic var objectValue: Any? + @objc dynamic var secondObjectValue: Any? + + @objc dynamic func increment() { + counter += 1 + } + + @objc dynamic func foo(_ number: Int, _ string: String) -> String { + return "Not Subclass \(number) \(string)" + } + + @objc dynamic func lifeIsGood(_ value: Any?) {} + @objc dynamic func set(first: Any?, second: Any?) { + objectValue = first + secondObjectValue = second + + hasInvokedSetObjectValueAndSecondObjectValue = true + } + + @objc dynamic func testNumericValues(c: CChar, s: CShort, i: CInt, l: CLong, ll: CLongLong, uc: CUnsignedChar, us: CUnsignedShort, ui: CUnsignedInt, ul: CUnsignedLong, ull: CUnsignedLongLong, f: CFloat, d: CDouble, b: CBool) {} + @objc dynamic func testReferences(nonnull: NSObject, nullable: NSObject?, iuo: NSObject!, class: AnyClass, nullableClass: AnyClass?, iuoClass: AnyClass!) {} + @objc dynamic func testBridgedStructs(p: CGPoint, s: CGSize, r: CGRect, a: CGAffineTransform) {} +} diff --git a/ReactiveCocoaTests/KVOKVCExtensionSpec.swift b/ReactiveCocoaTests/KVOKVCExtensionSpec.swift new file mode 100644 index 0000000000..1ba4cfeda4 --- /dev/null +++ b/ReactiveCocoaTests/KVOKVCExtensionSpec.swift @@ -0,0 +1,192 @@ +import Foundation +import ReactiveSwift +import Nimble +import Quick +import ReactiveCocoa + +private let initialPropertyValue = "InitialValue" +private let subsequentPropertyValue = "SubsequentValue" +private let finalPropertyValue = "FinalValue" + +private let initialOtherPropertyValue = "InitialOtherValue" +private let subsequentOtherPropertyValue = "SubsequentOtherValue" +private let finalOtherPropertyValue = "FinalOtherValue" + +class KVOKVCExtensionSpec: QuickSpec { + override func spec() { + describe("Property(object:keyPath:)") { + var object: ObservableObject! + var property: Property! + + beforeEach { + object = ObservableObject() + expect(object.rac_value) == 0 + + property = Property(object: object, keyPath: "rac_value") + } + + afterEach { + object = nil + } + + it("should read the underlying object") { + expect(property.value) == 0 + + object.rac_value = 1 + expect(property.value) == 1 + } + + it("should yield a producer that sends the current value and then the changes for the key path of the underlying object") { + var values: [Int] = [] + property.producer.startWithValues { value in + values.append(value) + } + + expect(values) == [ 0 ] + + object.rac_value = 1 + expect(values) == [ 0, 1 ] + + object.rac_value = 2 + expect(values) == [ 0, 1, 2 ] + } + + it("should yield a producer that sends the current value and then the changes for the key path of the underlying object, even if the value actually remains unchanged") { + var values: [Int] = [] + property.producer.startWithValues { value in + values.append(value) + } + + expect(values) == [ 0 ] + + object.rac_value = 0 + expect(values) == [ 0, 0 ] + + object.rac_value = 0 + expect(values) == [ 0, 0, 0 ] + } + + it("should yield a signal that emits subsequent values for the key path of the underlying object") { + var values: [Int] = [] + property.signal.observeValues { value in + values.append(value) + } + + expect(values) == [] + + object.rac_value = 1 + expect(values) == [ 1 ] + + object.rac_value = 2 + expect(values) == [ 1, 2 ] + } + + it("should yield a signal that emits subsequent values for the key path of the underlying object, even if the value actually remains unchanged") { + var values: [Int] = [] + property.signal.observeValues { value in + values.append(value) + } + + expect(values) == [] + + object.rac_value = 0 + expect(values) == [ 0 ] + + object.rac_value = 0 + expect(values) == [ 0, 0 ] + } + + it("should have a completed producer when the underlying object deallocates") { + var completed = false + + property = { + // Use a closure so this object has a shorter lifetime. + let object = ObservableObject() + let property = Property(object: object, keyPath: "rac_value") + + property.producer.startWithCompleted { + completed = true + } + + expect(completed) == false + expect(property.value) == 0 + return property + }() + + expect(completed).toEventually(beTruthy()) + } + + it("should have a completed signal when the underlying object deallocates") { + var completed = false + + property = { + // Use a closure so this object has a shorter lifetime. + let object = ObservableObject() + let property = Property(object: object, keyPath: "rac_value") + + property.signal.observeCompleted { + completed = true + } + + expect(completed) == false + expect(property.value).notTo(beNil()) + return property + }() + + expect(completed).toEventually(beTruthy()) + } + + it("should not be retained by its underlying object"){ + weak var weakProperty: Property? = property + + property = nil + expect(weakProperty).to(beNil()) + } + + it("should be accessible even if the underlying object has deinitialized") { + object.rac_value = .max + object = nil + + expect(property.value) == Int.max + + var latestValue: Int? + property.producer.startWithValues { latestValue = $0 } + expect(latestValue) == .max + + var isInterrupted = false + property.signal.observeInterrupted { isInterrupted = true } + expect(isInterrupted) == true + } + + it("should support un-bridged reference types") { + let dynamicProperty = DynamicProperty(object: object, keyPath: "rac_reference") + dynamicProperty.value = UnbridgedObject("foo") + expect(object.rac_reference.value) == "foo" + } + + it("should support un-bridged value types") { + let dynamicProperty = DynamicProperty(object: object, keyPath: "rac_unbridged") + dynamicProperty.value = .changed + expect(object.rac_unbridged as? UnbridgedValue) == UnbridgedValue.changed + } + } + } +} + +private class ObservableObject: NSObject { + @objc dynamic var rac_value: Int = 0 + @objc dynamic var rac_reference: UnbridgedObject = UnbridgedObject("") + @objc dynamic var rac_unbridged: Any = UnbridgedValue.starting +} + +private class UnbridgedObject: NSObject { + let value: String + init(_ value: String) { + self.value = value + } +} + +private enum UnbridgedValue { + case starting + case changed +} diff --git a/ReactiveCocoaTests/KeyValueObservingSpec+Swift4.swift b/ReactiveCocoaTests/KeyValueObservingSpec+Swift4.swift new file mode 100644 index 0000000000..e9cc1eba5e --- /dev/null +++ b/ReactiveCocoaTests/KeyValueObservingSpec+Swift4.swift @@ -0,0 +1,637 @@ +import Foundation +@testable import ReactiveCocoa +import ReactiveSwift +import Quick +import Nimble + +#if swift(>=3.2) +class KeyValueObservingSwift4Spec: QuickSpec { + override func spec() { + describe("NSObject.signal(for:)") { + it("should not send the initial value") { + let object = ObservableObject() + var values: [Int] = [] + + object.reactive + .signal(for: \.rac_value) + .observeValues { values.append($0) } + + expect(values) == [] + } + + it("automatically converts enums") { + let object = ObservableObject() + var values: [ObservableEnum] = [] + + object.reactive + .signal(for: \.rac_enum) + .observeValues { values.append($0) } + + object.rac_enum = .two + + expect(values) == [.two] + } + + itBehavesLike("a reactive key value observer using Swift 4 Smart Key Path") { + ["observe": "signal"] + } + } + + describe("NSObject.producer(for:)") { + it("should send the initial value") { + let object = ObservableObject() + var values: [Int] = [] + + object.reactive + .producer(for: \.rac_value) + .startWithValues { value in + values.append(value) + } + + expect(values) == [0] + } + + it("should send the initial value for nested key path") { + let parentObject = NestedObservableObject() + var values: [Int] = [] + + parentObject + .reactive + .producer(for: \.rac_object.rac_value) + .startWithValues { values.append($0) } + + expect(values) == [0] + } + + it("should send the initial value for weak nested key path") { + let parentObject = NestedObservableObject() + let innerObject = Optional(ObservableObject()) + parentObject.rac_weakObject = innerObject + var values: [Int] = [] + + parentObject + .reactive + .producer(forKeyPath: "rac_weakObject.rac_value") + .startWithValues { values.append(($0 as! NSNumber).intValue) } + + expect(values) == [0] + } + + it("should automatically convert enums") { + let object = ObservableObject() + var values: [ObservableEnum] = [] + + object.reactive + .producer(for: \.rac_enum) + .startWithValues { value in + values.append(value) + } + + object.rac_enum = .one + + expect(values) == [.zero, .one] + } + + itBehavesLike("a reactive key value observer using Swift 4 Smart Key Path") { + ["observe": "producer"] + } + } + } +} + +// Shared examples to ensure both `signal(forKeyPath:)` and `producer(forKeyPath:)` +// share common behavior. +fileprivate class KeyValueObservingSwift4SpecConfiguration: QuickConfiguration { + class Context { + let context: [String: Any] + + init(_ context: [String: Any]) { + self.context = context + } + + func observe(_ object: Object, _ keyPath: KeyPath) -> SignalProducer { + switch context["observe"] { + case let context as String where context == "signal": + return SignalProducer(object.reactive.signal(for: keyPath)) + + case let context as String where context == "producer": + return object.reactive.producer(for: keyPath).skip(first: 1) + + default: + fatalError("Unknown test config.") + } + } + + func observe(_ object: Object, _ keyPath: KeyPath) -> SignalProducer { + switch context["observe"] { + case let context as String where context == "signal": + return SignalProducer(object.reactive.signal(for: keyPath)) + + case let context as String where context == "producer": + return object.reactive.producer(for: keyPath).skip(first: 1) + + default: + fatalError("Unknown test config.") + } + } + + func isFinished(_ object: Operation) -> SignalProducer { + return observe(object, \.isFinished) + } + + func changes(_ object: ObservableObject) -> SignalProducer { + return observe(object, \.rac_value) + } + + func nestedChanges(_ object: NestedObservableObject) -> SignalProducer { + return observe(object, \.rac_object.rac_value) + } + + func weakNestedChanges(_ object: NestedObservableObject) -> SignalProducer { + return observe(object, \.rac_weakObject?.rac_value) + } + + func strongReferenceChanges(_ object: ObservableObject) -> SignalProducer { + return observe(object, \.target) + } + + func weakReferenceChanges(_ object: ObservableObject) -> SignalProducer { + return observe(object, \.weakTarget) + } + + func dependentKeyChanges(_ object: ObservableObject) -> SignalProducer { + return observe(object, \.rac_value_plusOne) + } + } + + override class func configure(_ configuration: Configuration) { + sharedExamples("a reactive key value observer using Swift 4 Smart Key Path") { (sharedExampleContext: @escaping SharedExampleContext) in + var context: Context! + + beforeEach { context = Context(sharedExampleContext()) } + afterEach { context = nil } + + it("should send new values for the key path (even if the value remains unchanged)") { + let object = ObservableObject() + var values: [Int] = [] + + context.changes(object).startWithValues { + values.append($0) + } + + expect(values) == [] + + object.rac_value = 0 + expect(values) == [0] + + object.rac_value = 1 + expect(values) == [0, 1] + + object.rac_value = 1 + expect(values) == [0, 1, 1] + } + + it("should send new values for the dependent key path") { + // This variant wraps the setter invocations with an autoreleasepool, and + // intentionally avoids retaining the emitted value, so that a bug that + // emits `nil` inappropriately can be caught. + // + // Related: https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3443#issuecomment-292721863 + // Fixed in https://github.com/ReactiveCocoa/ReactiveCocoa/pull/3439. + + let object = ObservableObject() + var expectedResults = [1, 2, 2] + var unexpectedResults: [NSDecimalNumber?] = [] + + var matches = true + + context.dependentKeyChanges(object).startWithValues { number in + if number != NSDecimalNumber(value: expectedResults.removeFirst()) { + matches = false + unexpectedResults.append(number) + } + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + + autoreleasepool { + object.rac_value = 0 + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + + + autoreleasepool { + object.rac_value = 1 + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + + autoreleasepool { + object.rac_value = 1 + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + } + + it("should send new values for the dependent key path (even if the value remains unchanged)") { + let object = ObservableObject() + var values: [NSDecimalNumber] = [] + + context.dependentKeyChanges(object).startWithValues { + values.append($0) + } + + expect(values) == [] + + object.rac_value = 0 + expect(values) == [1] + + object.rac_value = 1 + expect(values) == [1, 2] + + object.rac_value = 1 + expect(values) == [1, 2, 2] + } + + it("should not crash an Operation") { + // Related issue: + // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3329 + func invoke() { + let op = Operation() + context.isFinished(op).start() + } + + invoke() + } + + describe("signal behavior") { + it("should complete when the object deallocates") { + var completed = false + + _ = { + // Use a closure so this object has a shorter lifetime. + let object = ObservableObject() + + context.changes(object).startWithCompleted { + completed = true + } + + expect(completed) == false + }() + + expect(completed).toEventually(beTruthy()) + } + + it("should support native Swift objects") { + let object = ObservableObject() + var value: Any? + + context + .strongReferenceChanges(object) + .startWithValues { value = $0 } + + expect(value).to(beNil()) + + let token = Token() + object.target = token + expect(value).to(beIdenticalTo(token)) + } + + // NOTE: Compiler segfault with key path literals refering to weak + // properties. + it("should emit a `nil` when the key path is being cleared due to the deallocation of the Objective-C object it held.") { + let object = ObservableObject() + let null = ObjectIdentifier(NSNull()) + var ids: [ObjectIdentifier] = [] + + context + .weakReferenceChanges(object) + .startWithValues { ids.append($0.map { ObjectIdentifier($0 as AnyObject) } ?? null) } + + expect(ids) == [] + + var token: NSObject? = NSObject() + let tokenId = ObjectIdentifier(token!) + + // KVO would create autoreleasing references of the values being + // passed. So we have to ensure that they are cleared before + // we move on. + autoreleasepool { + object.weakTarget = token + } + + expect(ids) == [tokenId] + + token = nil + + expect(ids) == [tokenId, null] + expect(object.weakTarget).to(beNil()) + } + + it("should emit a `nil` when the key path is being cleared due to the deallocation of the native Swift object it held.") { + let object = ObservableObject() + let null = ObjectIdentifier(NSNull()) + var ids: [ObjectIdentifier] = [] + + context + .weakReferenceChanges(object) + .startWithValues { ids.append($0.map { ObjectIdentifier($0 as AnyObject) } ?? null) } + + expect(ids) == [] + + var token: Token? = Token() + let tokenId = ObjectIdentifier(token!) + + // KVO would create autoreleasing references of the values being + // passed. So we have to ensure that they are cleared before + // we move on. + autoreleasepool { + object.weakTarget = token + } + + expect(ids) == [tokenId] + + token = nil + + expect(ids) == [tokenId, null] + expect(object.weakTarget).to(beNil()) + } + } + + describe("nested key paths") { + it("should observe changes in a nested key path") { + let parentObject = NestedObservableObject() + var values: [Int] = [] + + context.nestedChanges(parentObject).startWithValues { + values.append($0) + } + + expect(values) == [] + + parentObject.rac_object.rac_value = 1 + expect(values) == [1] + + let oldInnerObject = parentObject.rac_object + + let newInnerObject = ObservableObject() + parentObject.rac_object = newInnerObject + expect(values) == [1, 0] + + parentObject.rac_object.rac_value = 10 + oldInnerObject.rac_value = 2 + expect(values) == [1, 0, 10] + } + + it("should observe changes in a nested weak key path") { + let parentObject = NestedObservableObject() + var innerObject = Optional(ObservableObject()) + parentObject.rac_weakObject = innerObject + var values: [Int?] = [] + + context.weakNestedChanges(parentObject).startWithValues { + values.append($0) + } + + expect(values) == [] + + innerObject?.rac_value = 1 + expect(values) == [1] + + autoreleasepool { + innerObject = nil + } + + // NOTE: [1] or [Optional(1), nil]? + expect(values) == [1] + + innerObject = ObservableObject() + parentObject.rac_weakObject = innerObject + expect(values) == [1, 0] + + innerObject?.rac_value = 10 + expect(values) == [1, 0, 10] + } + + it("should not retain replaced value in a nested key path") { + // NOTE: The producer version of this test cases somehow + // fails when the spec is being run alone. + let parentObject = NestedObservableObject() + + weak var weakOriginalInner: ObservableObject? = parentObject.rac_object + expect(weakOriginalInner).toNot(beNil()) + + autoreleasepool { + _ = context + .nestedChanges(parentObject) + .start() + } + + autoreleasepool { + parentObject.rac_object = ObservableObject() + } + + expect(weakOriginalInner).to(beNil()) + } + } + + describe("thread safety") { + var concurrentQueue: DispatchQueue! + + beforeEach { + concurrentQueue = DispatchQueue( + label: "org.reactivecocoa.ReactiveCocoa.DynamicPropertySpec.concurrentQueue", + attributes: .concurrent + ) + } + + it("should handle changes being made on another queue") { + let object = ObservableObject() + var observedValue = 0 + + context.changes(object) + .take(first: 1) + .startWithValues { observedValue = $0 } + + concurrentQueue.async { + object.rac_value = 2 + } + + concurrentQueue.sync(flags: .barrier) {} + expect(observedValue).toEventually(equal(2)) + } + + it("should handle changes being made on another queue using deliverOn") { + let object = ObservableObject() + var observedValue = 0 + + context.changes(object) + .take(first: 1) + .observe(on: UIScheduler()) + .startWithValues { observedValue = $0 } + + concurrentQueue.async { + object.rac_value = 2 + } + + concurrentQueue.sync(flags: .barrier) {} + expect(observedValue).toEventually(equal(2)) + } + + it("async disposal of target") { + var object: ObservableObject? = ObservableObject() + var observedValue = 0 + + context.changes(object!) + .observe(on: UIScheduler()) + .startWithValues { observedValue = $0 } + + concurrentQueue.async { + object!.rac_value = 2 + object = nil + } + + concurrentQueue.sync(flags: .barrier) {} + expect(observedValue).toEventually(equal(2)) + } + } + + describe("stress tests") { + let numIterations = 5000 + + var testObject: ObservableObject! + var iterationQueue: DispatchQueue! + var concurrentQueue: DispatchQueue! + + beforeEach { + testObject = ObservableObject() + iterationQueue = DispatchQueue( + label: "org.reactivecocoa.ReactiveCocoa.RACKVOProxySpec.iterationQueue", + attributes: .concurrent + ) + concurrentQueue = DispatchQueue( + label: "org.reactivecocoa.ReactiveCocoa.DynamicPropertySpec.concurrentQueue", + attributes: .concurrent + ) + } + + it("attach observers") { + let deliveringObserver = QueueScheduler.makeForTesting() + + var atomicCounter = Int64(0) + + DispatchQueue.concurrentPerform(iterations: numIterations) { index in + context.changes(testObject) + .observe(on: deliveringObserver) + .startWithValues { value in + OSAtomicAdd64(Int64(value), &atomicCounter) + } + } + + testObject.rac_value = 2 + + expect(atomicCounter).toEventually(equal(Int64(numIterations * 2)), timeout: .seconds(30)) + } + + // ReactiveCocoa/ReactiveCocoa#1122 + it("async disposal of observer") { + let serialDisposable = SerialDisposable() + + iterationQueue.async { + DispatchQueue.concurrentPerform(iterations: numIterations) { index in + let disposable = context.changes(testObject) + .startWithCompleted {} + + serialDisposable.inner = disposable + + concurrentQueue.async { + testObject.rac_value = index + } + } + } + + iterationQueue.sync(flags: .barrier) { + serialDisposable.dispose() + } + } + + it("async disposal of signal with in-flight changes") { + let otherScheduler = QueueScheduler.makeForTesting() + + var token = Optional(Lifetime.Token()) + let lifetime = Lifetime(token!) + + let replayProducer = context.changes(testObject) + .map { $0 % 2 == 0 } + .observe(on: otherScheduler) + .take(during: lifetime) + .replayLazily(upTo: 1) + + replayProducer.start() + + iterationQueue.suspend() + + let half = numIterations / 2 + + for index in 0 ..< numIterations { + iterationQueue.async { + testObject.rac_value = index + } + + if index == half { + iterationQueue.async { + token = nil + expect(replayProducer.last()).toNot(beNil()) + } + } + } + + iterationQueue.resume() + + waitUntil(timeout: .seconds(3)) { done in + iterationQueue.async(flags: .barrier, execute: done) + } + } + } + } + } +} + +private final class Token {} + +@objc private enum ObservableEnum: Int { + case zero + case one + case two +} + +private class ObservableObject: NSObject { + @objc dynamic var rac_value: Int = 0 + @objc dynamic var rac_enum: ObservableEnum = .zero + + @objc dynamic var target: AnyObject? + @objc dynamic weak var weakTarget: AnyObject? + + @objc dynamic var rac_value_plusOne: NSDecimalNumber { + return NSDecimalNumber(value: rac_value + 1) + } + + override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { + if key == "rac_value_plusOne" { + return Set([#keyPath(ObservableObject.rac_value)]) + } else { + return Set() + } + } +} + +private class NestedObservableObject: NSObject { + @objc dynamic var rac_object: ObservableObject = ObservableObject() + @objc dynamic weak var rac_weakObject: ObservableObject? +} +#endif diff --git a/ReactiveCocoaTests/KeyValueObservingSpec.swift b/ReactiveCocoaTests/KeyValueObservingSpec.swift new file mode 100644 index 0000000000..07b9d9df47 --- /dev/null +++ b/ReactiveCocoaTests/KeyValueObservingSpec.swift @@ -0,0 +1,694 @@ +import Foundation +@testable import ReactiveCocoa +@testable import ReactiveSwift +import Quick +import Nimble + +class KeyValueObservingSpec: QuickSpec { + override func spec() { + describe("NSObject.signal(forKeyPath:)") { + it("should not send the initial value") { + let object = ObservableObject() + var values: [Int] = [] + + object.reactive + .signal(forKeyPath: #keyPath(ObservableObject.rac_value)) + .observeValues { values.append(($0 as! NSNumber).intValue) } + + expect(values) == [] + } + + itBehavesLike("a reactive key value observer") { + [ + "observe": { (object: NSObject, keyPath: String) in + return object.reactive.signal(forKeyPath: keyPath) + } + ] + } + } + + describe("NSObject.producer(forKeyPath:)") { + it("should send the initial value") { + let object = ObservableObject() + var values: [Int] = [] + + object.reactive + .producer(forKeyPath: #keyPath(ObservableObject.rac_value)) + .startWithValues { value in + values.append(value as! Int) + } + + expect(values) == [0] + } + + it("should send the initial value for nested key path") { + let parentObject = NestedObservableObject() + var values: [Int] = [] + + parentObject + .reactive + .producer(forKeyPath: #keyPath(NestedObservableObject.rac_object.rac_value)) + .startWithValues { values.append(($0 as! NSNumber).intValue) } + + expect(values) == [0] + } + + it("should send the initial value for weak nested key path") { + let parentObject = NestedObservableObject() + let innerObject = Optional(ObservableObject()) + parentObject.rac_weakObject = innerObject + var values: [Int] = [] + + parentObject + .reactive + .producer(forKeyPath: "rac_weakObject.rac_value") + .startWithValues { values.append(($0 as! NSNumber).intValue) } + + expect(values) == [0] + } + + itBehavesLike("a reactive key value observer") { + [ + "observe": { (object: NSObject, keyPath: String) in + return object.reactive.producer(forKeyPath: keyPath) + } + ] + } + } + + describe("property type and attribute query") { + let object = TestAttributeQueryObject() + + it("should be able to classify weak references") { + "weakProperty".withCString { cString in + let propertyPointer = class_getProperty(type(of: object), cString) + expect(propertyPointer).toNot(beNil()) + + if let pointer = propertyPointer { + let attributes = PropertyAttributes(property: pointer) + expect(attributes.isWeak) == true + expect(attributes.isObject) == true + expect(attributes.isBlock) == false + expect(attributes.objectClass).to(beIdenticalTo(NSObject.self)) + } + } + } + + it("should be able to classify blocks") { + "block".withCString { cString in + let propertyPointer = class_getProperty(type(of: object), cString) + expect(propertyPointer).toNot(beNil()) + + if let pointer = propertyPointer { + let attributes = PropertyAttributes(property: pointer) + expect(attributes.isWeak) == false + expect(attributes.isObject) == true + expect(attributes.isBlock) == true + expect(attributes.objectClass).to(beNil()) + } + } + } + + it("should be able to classify non object properties") { + "integer".withCString { cString in + let propertyPointer = class_getProperty(type(of: object), cString) + expect(propertyPointer).toNot(beNil()) + + if let pointer = propertyPointer { + let attributes = PropertyAttributes(property: pointer) + expect(attributes.isWeak) == false + expect(attributes.isObject) == false + expect(attributes.isBlock) == false + expect(attributes.objectClass).to(beNil()) + } + } + } + } + } +} + +// Shared examples to ensure both `signal(forKeyPath:)` and `producer(forKeyPath:)` +// share common behavior. +fileprivate class KeyValueObservingSpecConfiguration: QuickConfiguration { + class Context { + let context: [String: Any] + + init(_ context: [String: Any]) { + self.context = context + } + + func observe(_ object: NSObject, _ keyPath: String) -> SignalProducer { + if let block = context["observe"] as? (NSObject, String) -> Signal { + return SignalProducer(block(object, keyPath)) + } else if let block = context["observe"] as? (NSObject, String) -> SignalProducer { + return block(object, keyPath).skip(first: 1) + } else { + fatalError("What is this?") + } + } + + func isFinished(_ object: Operation) -> SignalProducer { + return observe(object, #keyPath(Operation.isFinished)) + } + + func changes(_ object: NSObject) -> SignalProducer { + return observe(object, #keyPath(ObservableObject.rac_value)) + } + + func nestedChanges(_ object: NSObject) -> SignalProducer { + return observe(object, #keyPath(NestedObservableObject.rac_object.rac_value)) + } + + func weakNestedChanges(_ object: NSObject) -> SignalProducer { + // `#keyPath` does not work with weak relationships. + return observe(object, "rac_weakObject.rac_value") + } + + func strongReferenceChanges(_ object: NSObject) -> SignalProducer { + return observe(object, #keyPath(ObservableObject.target)) + } + + func weakReferenceChanges(_ object: NSObject) -> SignalProducer { + return observe(object, #keyPath(ObservableObject.weakTarget)) + } + + func dependentKeyChanges(_ object: NSObject) -> SignalProducer { + return observe(object, #keyPath(ObservableObject.rac_value_plusOne)) + } + } + + override class func configure(_ configuration: Configuration) { + sharedExamples("a reactive key value observer") { (sharedExampleContext: @escaping SharedExampleContext) in + var context: Context! + + beforeEach { context = Context(sharedExampleContext()) } + afterEach { context = nil } + + it("should send new values for the key path (even if the value remains unchanged)") { + let object = ObservableObject() + var values: [Int] = [] + + context.changes(object).startWithValues { + values.append(($0 as! NSNumber).intValue) + } + + expect(values) == [] + + object.rac_value = 0 + expect(values) == [0] + + object.rac_value = 1 + expect(values) == [0, 1] + + object.rac_value = 1 + expect(values) == [0, 1, 1] + } + + it("should send new values for the dependent key path") { + // This variant wraps the setter invocations with an autoreleasepool, and + // intentionally avoids retaining the emitted value, so that a bug that + // emits `nil` inappropriately can be caught. + // + // Related: https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3443#issuecomment-292721863 + // Fixed in https://github.com/ReactiveCocoa/ReactiveCocoa/pull/3439. + + let object = ObservableObject() + var expectedResults = [1, 2, 2] + var unexpectedResults: [NSDecimalNumber?] = [] + + var matches = true + + context.dependentKeyChanges(object).startWithValues { number in + let number = number as? NSDecimalNumber + + if number != NSDecimalNumber(value: expectedResults.removeFirst()) { + matches = false + unexpectedResults.append(number) + } + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + + autoreleasepool { + object.rac_value = 0 + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + + + autoreleasepool { + object.rac_value = 1 + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + + autoreleasepool { + object.rac_value = 1 + } + + expect(matches) == true + expect(unexpectedResults as NSArray) == [] + } + + it("should send new values for the dependent key path (even if the value remains unchanged)") { + let object = ObservableObject() + var values: [NSDecimalNumber] = [] + + context.dependentKeyChanges(object).startWithValues { + values.append($0 as! NSDecimalNumber) + } + + expect(values) == [] + + object.rac_value = 0 + expect(values) == [1] + + object.rac_value = 1 + expect(values) == [1, 2] + + object.rac_value = 1 + expect(values) == [1, 2, 2] + } + + it("should not crash an Operation") { + // Related issue: + // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/3329 + func invoke() { + let op = Operation() + context.isFinished(op).start() + } + + invoke() + } + + describe("signal behavior") { + it("should complete when the object deallocates") { + var completed = false + + _ = { + // Use a closure so this object has a shorter lifetime. + let object = ObservableObject() + + context.changes(object).startWithCompleted { + completed = true + } + + expect(completed) == false + }() + + expect(completed).toEventually(beTruthy()) + } + + it("should support native Swift objects") { + let object = ObservableObject() + var value: Any? + + context + .strongReferenceChanges(object) + .startWithValues { value = $0 } + + expect(value).to(beNil()) + + let token = Token() + object.target = token + expect(value).to(beIdenticalTo(token)) + } + + it("should emit a `nil` when the key path is being cleared due to the deallocation of the Objective-C object it held.") { + let object = ObservableObject() + let null = ObjectIdentifier(NSNull()) + var ids: [ObjectIdentifier] = [] + + context + .weakReferenceChanges(object) + .startWithValues { ids.append($0.map { ObjectIdentifier($0 as AnyObject) } ?? null) } + + expect(ids) == [] + + var token: NSObject? = NSObject() + let tokenId = ObjectIdentifier(token!) + + // KVO would create autoreleasing references of the values being + // passed. So we have to ensure that they are cleared before + // we move on. + autoreleasepool { + object.weakTarget = token + } + + expect(ids) == [tokenId] + + token = nil + + expect(ids) == [tokenId, null] + expect(object.weakTarget).to(beNil()) + } + + it("should emit a `nil` when the key path is being cleared due to the deallocation of the native Swift object it held.") { + let object = ObservableObject() + let null = ObjectIdentifier(NSNull()) + var ids: [ObjectIdentifier] = [] + + context + .weakReferenceChanges(object) + .startWithValues { ids.append($0.map { ObjectIdentifier($0 as AnyObject) } ?? null) } + + expect(ids) == [] + + var token: Token? = Token() + let tokenId = ObjectIdentifier(token!) + + // KVO would create autoreleasing references of the values being + // passed. So we have to ensure that they are cleared before + // we move on. + autoreleasepool { + object.weakTarget = token + } + + expect(ids) == [tokenId] + + token = nil + + expect(ids) == [tokenId, null] + expect(object.weakTarget).to(beNil()) + } + } + + describe("nested key paths") { + it("should observe changes in a nested key path") { + let parentObject = NestedObservableObject() + var values: [Int] = [] + + context.nestedChanges(parentObject).startWithValues { + values.append(($0 as! NSNumber).intValue) + } + + expect(values) == [] + + parentObject.rac_object.rac_value = 1 + expect(values) == [1] + + let oldInnerObject = parentObject.rac_object + + let newInnerObject = ObservableObject() + parentObject.rac_object = newInnerObject + expect(values) == [1, 0] + + parentObject.rac_object.rac_value = 10 + oldInnerObject.rac_value = 2 + expect(values) == [1, 0, 10] + } + + it("should observe changes in a nested weak key path") { + let parentObject = NestedObservableObject() + var innerObject = Optional(ObservableObject()) + parentObject.rac_weakObject = innerObject + var values: [Int] = [] + + context.weakNestedChanges(parentObject).startWithValues { + values.append(($0 as! NSNumber).intValue) + } + + expect(values) == [] + + innerObject?.rac_value = 1 + expect(values) == [1] + + autoreleasepool { + innerObject = nil + } + + // NOTE: [1] or [Optional(1), nil]? + expect(values) == [1] + + innerObject = ObservableObject() + parentObject.rac_weakObject = innerObject + expect(values) == [1, 0] + + innerObject?.rac_value = 10 + expect(values) == [1, 0, 10] + } + + it("should not retain replaced value in a nested key path") { + weak var weakOriginalInner: ObservableObject? + let parentObject = NestedObservableObject() + + autoreleasepool { + parentObject.rac_object = ObservableObject() + weakOriginalInner = parentObject.rac_object + + expect(weakOriginalInner).toNot(beNil()) + + _ = context + .nestedChanges(parentObject) + .start() + + parentObject.rac_object = ObservableObject() + } + + expect(weakOriginalInner).to(beNil()) + } + + it("should not observe changes on a replaced inner object in a nested key path") { + let parentObject = NestedObservableObject() + + // This test case requires a nil value which `rac_object` doesn't + // allow, so we are going to use `rac_weakObject` instead. + // The tested inner objects are not meant to be weak in any way. + let oldInnerObject = ObservableObject() + parentObject.rac_weakObject = oldInnerObject + + var values: [Int?] = [] + + context.weakNestedChanges(parentObject).startWithValues { + values.append($0 as! Int?) + } + + expect(values) == [] + + oldInnerObject.rac_value = 1 + expect(values) == [1] + + parentObject.rac_weakObject = nil + expect(values) == [1, nil] + + oldInnerObject.rac_value = 2 + expect(values) == [1, nil] + + let newInnerObject = ObservableObject() + parentObject.rac_weakObject = newInnerObject + + expect(values) == [1, nil, 0] + + oldInnerObject.rac_value = 3 + expect(values) == [1, nil, 0] + + newInnerObject.rac_value = 4 + expect(values) == [1, nil, 0, 4] + } + } + + describe("thread safety") { + var concurrentQueue: DispatchQueue! + + beforeEach { + concurrentQueue = DispatchQueue( + label: "org.reactivecocoa.ReactiveCocoa.DynamicPropertySpec.concurrentQueue", + attributes: .concurrent + ) + } + + it("should handle changes being made on another queue") { + let object = ObservableObject() + var observedValue = 0 + + context.changes(object) + .take(first: 1) + .startWithValues { observedValue = ($0 as! NSNumber).intValue } + + concurrentQueue.async { + object.rac_value = 2 + } + + concurrentQueue.sync(flags: .barrier) {} + expect(observedValue).toEventually(equal(2)) + } + + it("should handle changes being made on another queue using deliverOn") { + let object = ObservableObject() + var observedValue = 0 + + context.changes(object) + .take(first: 1) + .observe(on: UIScheduler()) + .startWithValues { observedValue = ($0 as! NSNumber).intValue } + + concurrentQueue.async { + object.rac_value = 2 + } + + concurrentQueue.sync(flags: .barrier) {} + expect(observedValue).toEventually(equal(2)) + } + + it("async disposal of target") { + var object: ObservableObject? = ObservableObject() + var observedValue = 0 + + context.changes(object!) + .observe(on: UIScheduler()) + .startWithValues { observedValue = ($0 as! NSNumber).intValue } + + concurrentQueue.async { + object!.rac_value = 2 + object = nil + } + + concurrentQueue.sync(flags: .barrier) {} + expect(observedValue).toEventually(equal(2)) + } + } + + describe("stress tests") { + let numIterations = 5000 + + var testObject: ObservableObject! + var iterationQueue: DispatchQueue! + var concurrentQueue: DispatchQueue! + + beforeEach { + testObject = ObservableObject() + iterationQueue = DispatchQueue( + label: "org.reactivecocoa.ReactiveCocoa.RACKVOProxySpec.iterationQueue", + attributes: .concurrent + ) + concurrentQueue = DispatchQueue( + label: "org.reactivecocoa.ReactiveCocoa.DynamicPropertySpec.concurrentQueue", + attributes: .concurrent + ) + } + + it("attach observers") { + let deliveringObserver = QueueScheduler.makeForTesting() + + var atomicCounter = Int64(0) + + DispatchQueue.concurrentPerform(iterations: numIterations) { index in + context.changes(testObject) + .observe(on: deliveringObserver) + .map { $0 as! NSNumber } + .map { $0.int64Value } + .startWithValues { value in + OSAtomicAdd64(value, &atomicCounter) + } + } + + testObject.rac_value = 2 + + expect(atomicCounter).toEventually(equal(Int64(numIterations * 2)), timeout: .seconds(30)) + } + + // Direct port of https://github.com/ReactiveCocoa/ReactiveObjC/blob/3.1.0/ReactiveObjCTests/RACKVOProxySpec.m#L196 + it("async disposal of observer") { + let serialDisposable = SerialDisposable() + let lock = Lock.make() + + iterationQueue.async { + DispatchQueue.concurrentPerform(iterations: numIterations) { index in + let disposable = context.changes(testObject) + .startWithCompleted {} + + serialDisposable.inner = disposable + + concurrentQueue.async { + // TestObject in the ObjC version has manual getter, setter and KVO notification. Here + // we just wrap the call with a `Lock` to emulate the effect. + lock.lock() + testObject.rac_value = index + lock.unlock() + } + } + } + + iterationQueue.sync(flags: .barrier) { + serialDisposable.dispose() + } + } + + // Direct port of https://github.com/ReactiveCocoa/ReactiveObjC/blob/3.1.0/ReactiveObjCTests/RACKVOProxySpec.m#L196 + it("async disposal of signal with in-flight changes") { + let otherScheduler = QueueScheduler.makeForTesting() + + var token = Optional(Lifetime.Token()) + let lifetime = Lifetime(token!) + + let replayProducer = context.changes(testObject) + .map { ($0 as! NSNumber).intValue } + .map { $0 % 2 == 0 } + .observe(on: otherScheduler) + .take(during: lifetime) + .replayLazily(upTo: 1) + + replayProducer.start() + + iterationQueue.suspend() + + let half = numIterations / 2 + + for index in 0 ..< numIterations { + iterationQueue.async { + testObject.rac_value = index + } + + if index == half { + iterationQueue.async { + token = nil + expect(replayProducer.last()).toNot(beNil()) + } + } + } + + iterationQueue.resume() + + waitUntil(timeout: .seconds(3)) { done in + iterationQueue.async(flags: .barrier, execute: done) + } + } + } + } + } +} + +private final class Token {} + +private class ObservableObject: NSObject { + @objc dynamic var rac_value: Int = 0 + + @objc dynamic var target: AnyObject? + @objc dynamic weak var weakTarget: AnyObject? + + @objc dynamic var rac_value_plusOne: NSDecimalNumber { + return NSDecimalNumber(value: rac_value + 1) + } + + override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { + if key == "rac_value_plusOne" { + return Set([#keyPath(ObservableObject.rac_value)]) + } else { + return Set() + } + } +} + +private class NestedObservableObject: NSObject { + @objc dynamic var rac_object: ObservableObject = ObservableObject() + @objc dynamic weak var rac_weakObject: ObservableObject? +} + +private class TestAttributeQueryObject: NSObject { + @objc weak var weakProperty: NSObject? = nil + @objc var block: @convention(block) (NSObject) -> NSObject? = { _ in nil } + @objc let integer = 0 +} diff --git a/ReactiveCocoaTests/LifetimeSpec.swift b/ReactiveCocoaTests/LifetimeSpec.swift new file mode 100644 index 0000000000..34e08858a4 --- /dev/null +++ b/ReactiveCocoaTests/LifetimeSpec.swift @@ -0,0 +1,177 @@ +import Foundation +import ReactiveCocoa +import ReactiveSwift +import Quick +import Nimble + +private final class Token {} + +class LifetimeSpec: QuickSpec { + override func spec() { + describe("NSObject.reactive.lifetime") { + var object: NSObject! + weak var weakObject: NSObject? + + beforeEach { + object = NSObject() + weakObject = object + } + + afterEach { + object = nil + expect(weakObject).to(beNil()) + } + + it("should not deadlock") { + for _ in 1 ... 10 { + var isDeadlocked = true + + func createQueue() -> DispatchQueue { + .global(qos: .userInitiated) + } + + createQueue().async { + _ = object.reactive.lifetime + + createQueue().async { + _ = object.reactive.lifetime + + isDeadlocked = false + } + } + + expect(isDeadlocked).toEventually(beFalsy()) + } + } + } + + describe("Lifetime.of(_:)") { + var object: Token! + weak var weakObject: Token? + + beforeEach { + object = Token() + weakObject = object + } + + afterEach { + object = nil + expect(weakObject).to(beNil()) + } + + it("should not deadlock") { + for _ in 1 ... 10 { + var isDeadlocked = true + + func createQueue() -> DispatchQueue { + return .global(qos: .userInitiated) + } + + createQueue().async { + _ = Lifetime.of(object) + + createQueue().async { + _ = Lifetime.of(object) + + isDeadlocked = false + } + } + + expect(isDeadlocked).toEventually(beFalsy()) + } + } + } + + describe("Signal.take(duringLifetimeOf:)") { + it("should work with Objective-C objects") { + var object: NSObject? = NSObject() + weak var weakObject = object + var isCompleted = false + + let (signal, observer) = Signal<(), Never>.pipe() + + withExtendedLifetime(observer) { + signal + .take(duringLifetimeOf: object!) + .observeCompleted { isCompleted = true } + + expect(weakObject).toNot(beNil()) + expect(isCompleted) == false + + object = nil + + expect(weakObject).to(beNil()) + expect(isCompleted) == true + } + } + + it("should work with native Swift objects") { + var object: Token? = Token() + weak var weakObject = object + var isCompleted = false + + let (signal, observer) = Signal<(), Never>.pipe() + + withExtendedLifetime(observer) { + signal + .take(duringLifetimeOf: object!) + .observeCompleted { isCompleted = true } + + expect(weakObject).toNot(beNil()) + expect(isCompleted) == false + + object = nil + + expect(weakObject).to(beNil()) + expect(isCompleted) == true + } + } + } + + describe("SignalProducer.take(duringLifetimeOf:)") { + it("should work with Objective-C objects") { + var object: NSObject? = NSObject() + weak var weakObject = object + var isCompleted = false + + let (signal, observer) = Signal<(), Never>.pipe() + + withExtendedLifetime(observer) { + SignalProducer(signal) + .take(duringLifetimeOf: object!) + .startWithCompleted { isCompleted = true } + + expect(weakObject).toNot(beNil()) + expect(isCompleted) == false + + object = nil + + expect(weakObject).to(beNil()) + expect(isCompleted) == true + } + } + + it("should work with native Swift objects") { + var object: Token? = Token() + weak var weakObject = object + var isCompleted = false + + let (signal, observer) = Signal<(), Never>.pipe() + + withExtendedLifetime(observer) { + SignalProducer(signal) + .take(duringLifetimeOf: object!) + .startWithCompleted { isCompleted = true } + + expect(weakObject).toNot(beNil()) + expect(isCompleted) == false + + object = nil + + expect(weakObject).to(beNil()) + expect(isCompleted) == true + } + } + } + } +} diff --git a/ReactiveCocoaTests/Objective-C/NSControllerRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/NSControllerRACSupportSpec.m deleted file mode 100644 index 97baede437..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSControllerRACSupportSpec.m +++ /dev/null @@ -1,47 +0,0 @@ -// -// NSControllerRACSupportSpec.m -// ReactiveCocoa -// -// Created by Uri Baghin on 26/10/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import -#import "RACKVOChannel.h" - -@interface RACTestController : NSController - -@property (nonatomic, strong) id object; - -@end - -@implementation RACTestController - -@end - -QuickSpecBegin(NSControllerRACSupportSpec) - -qck_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()); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSEnumeratorRACSequenceAdditionsSpec.m b/ReactiveCocoaTests/Objective-C/NSEnumeratorRACSequenceAdditionsSpec.m deleted file mode 100644 index 04c141aaa0..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSEnumeratorRACSequenceAdditionsSpec.m +++ /dev/null @@ -1,28 +0,0 @@ -// -// NSEnumeratorRACSequenceAdditionsSpec.m -// ReactiveCocoa -// -// Created by Uri Baghin on 07/01/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACSequenceExamples.h" - -#import "NSEnumerator+RACSequenceAdditions.h" - -QuickSpecBegin(NSEnumeratorRACSequenceAdditionsSpec) - -qck_describe(@"-rac_sequence", ^{ - NSArray *values = @[ @0, @1, @2, @3, @4 ]; - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: values.objectEnumerator.rac_sequence, - RACSequenceExampleExpectedValues: values - }; - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSNotificationCenterRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/NSNotificationCenterRACSupportSpec.m deleted file mode 100644 index e1ad5476a9..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSNotificationCenterRACSupportSpec.m +++ /dev/null @@ -1,87 +0,0 @@ -// -// NSNotificationCenterRACSupportSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-12-07. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "NSNotificationCenter+RACSupport.h" -#import "RACSignal.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "NSObject+RACDeallocating.h" - -static NSString * const TestNotification = @"TestNotification"; - -QuickSpecBegin(NSNotificationCenterRACSupportSpec) - -__block NSNotificationCenter *notificationCenter; - -qck_beforeEach(^{ - // The compiler gets confused and thinks you might be messaging - // NSDistributedNotificationCenter otherwise. Wtf? - notificationCenter = NSNotificationCenter.defaultCenter; -}); - -qck_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(beAKindOf(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)); -}); - -qck_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(beAKindOf(NSNotification.class)); - expect(notification.name).to(equal(TestNotification)); - expect(notification.object).to(beIdenticalTo(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)); -}); - -qck_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()); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSObjectRACAppKitBindingsSpec.m b/ReactiveCocoaTests/Objective-C/NSObjectRACAppKitBindingsSpec.m deleted file mode 100644 index f940cd5778..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSObjectRACAppKitBindingsSpec.m +++ /dev/null @@ -1,39 +0,0 @@ -// -// NSObjectRACAppKitBindingsSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-07-01. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACChannelExamples.h" - -#import -#import "NSObject+RACAppKitBindings.h" - -QuickSpecBegin(NSObjectRACAppKitBindingsSpec) - -qck_itBehavesLike(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]]; - } - }; -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSObjectRACDeallocatingSpec.m b/ReactiveCocoaTests/Objective-C/NSObjectRACDeallocatingSpec.m deleted file mode 100644 index 5d162ac6af..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSObjectRACDeallocatingSpec.m +++ /dev/null @@ -1,198 +0,0 @@ -// -// NSObject+RACDeallocating.m -// ReactiveCocoa -// -// Created by Kazuo Koga on 2013/03/15. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACTestObject.h" - -#import "NSObject+RACDeallocating.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACSignal+Operations.h" -#import - -@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 - -QuickSpecBegin(NSObjectRACDeallocatingSpec) - -qck_describe(@"-dealloc swizzling", ^{ - SEL selector = NSSelectorFromString(@"dealloc"); - - qck_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)); - }); - - qck_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()); - }); -}); - -qck_describe(@"-rac_deallocDisposable", ^{ - qck_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()); - }); - - qck_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")); - }]]; - } - }); -}); - -qck_describe(@"-rac_willDeallocSignal", ^{ - qck_it(@"should complete on dealloc", ^{ - __block BOOL completed = NO; - @autoreleasepool { - [[[[RACTestObject alloc] init] rac_willDeallocSignal] subscribeCompleted:^{ - completed = YES; - }]; - } - - expect(@(completed)).to(beTruthy()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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")); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSObjectRACLiftingSpec.m b/ReactiveCocoaTests/Objective-C/NSObjectRACLiftingSpec.m deleted file mode 100644 index d730a811ba..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSObjectRACLiftingSpec.m +++ /dev/null @@ -1,410 +0,0 @@ -// -// NSObjectRACLifting.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/2/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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" - -QuickSpecBegin(NSObjectRACLiftingSpec) - -qck_describe(@"-rac_liftSelector:withSignals:", ^{ - __block RACTestObject *object; - - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - }); - - qck_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)); - }); -}); - -qck_describe(@"-rac_liftSelector:withSignalsFromArray:", ^{ - __block RACTestObject *object; - - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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(beNil()); - - [subject sendNext:RACTupleNil.tupleNil]; - expect(object.objectValue).to(beNil()); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_it(@"should work with class objects", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; - - expect(object.objectValue).to(beNil()); - - [subject sendNext:self.class]; - expect(object.objectValue).to(equal(self.class)); - }); - - qck_it(@"should work for char pointer", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setCharPointerValue:) withSignalsFromArray:@[ subject ]]; - - expect(@((size_t)object.charPointerValue)).to(equal(@0)); - - NSString *string = @"blah blah blah"; - [subject sendNext:string]; - expect(@(object.charPointerValue)).to(equal(string)); - }); - - qck_it(@"should work for const char pointer", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setConstCharPointerValue:) withSignalsFromArray:@[ subject ]]; - - expect(@((size_t)object.constCharPointerValue)).to(equal(@0)); - - NSString *string = @"blah blah blah"; - [subject sendNext:string]; - expect(@(object.constCharPointerValue)).to(equal(string)); - }); - - qck_it(@"should work for CGRect", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setRectValue:) withSignalsFromArray:@[ subject ]]; - - expect(@(CGRectEqualToRect(object.rectValue, CGRectZero))).to(beTruthy()); - - CGRect value = CGRectMake(10, 20, 30, 40); - [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGRect)]]; - expect(@(CGRectEqualToRect(object.rectValue, value))).to(beTruthy()); - }); - - qck_it(@"should work for CGSize", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setSizeValue:) withSignalsFromArray:@[ subject ]]; - - expect(@(CGSizeEqualToSize(object.sizeValue, CGSizeZero))).to(beTruthy()); - - CGSize value = CGSizeMake(10, 20); - [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGSize)]]; - expect(@(CGSizeEqualToSize(object.sizeValue, value))).to(beTruthy()); - }); - - qck_it(@"should work for CGPoint", ^{ - RACSubject *subject = [RACSubject subject]; - [object rac_liftSelector:@selector(setPointValue:) withSignalsFromArray:@[ subject ]]; - - expect(@(CGPointEqualToPoint(object.pointValue, CGPointZero))).to(beTruthy()); - - CGPoint value = CGPointMake(10, 20); - [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGPoint)]]; - expect(@(CGPointEqualToPoint(object.pointValue, value))).to(beTruthy()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_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))); - }); - - qck_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)); - }); - - qck_describe(@"the returned signal", ^{ - qck_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")); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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")); - }); - - qck_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")); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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")); - }); - }); - - qck_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()); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingExamples.h b/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingExamples.h deleted file mode 100644 index 694ce597f6..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingExamples.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingExamples.m b/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingExamples.m deleted file mode 100644 index e73b6ad428..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingExamples.m +++ /dev/null @@ -1,224 +0,0 @@ -// -// NSObjectRACPropertySubscribingExamples.m -// ReactiveCocoa -// -// Created by Josh Vera on 4/10/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACTestObject.h" -#import "NSObjectRACPropertySubscribingExamples.h" - -#import -#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"; - -QuickConfigurationBegin(NSObjectRACPropertySubscribingExamples) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACPropertySubscribingExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block RACSignal *(^signalBlock)(RACTestObject *object, NSString *keyPath, id observer); - - qck_beforeEach(^{ - signalBlock = exampleContext()[RACPropertySubscribingExamplesSetupBlock]; - }); - - qck_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 ]))); - }); - - qck_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 ]))); - - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(objectDealloced)).to(beTruthy()); - }); - - qck_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).toEventually(equal(@1)); - }); - - qck_describe(@"mutating collections", ^{ - __block RACTestObject *object; - __block NSMutableOrderedSet *lastValue; - __block NSMutableOrderedSet *proxySet; - - qck_beforeEach(^{ - 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]; - }); - - qck_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)); - }); - - qck_it(@"sends the newest object when removing values in an observed object", ^{ - NSMutableOrderedSet *expected = [NSMutableOrderedSet orderedSet]; - - [proxySet removeAllObjects]; - expect(lastValue).to(equal(expected)); - }); - - qck_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)); - }); - }); - }); -} - -QuickConfigurationEnd diff --git a/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingSpec.m b/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingSpec.m deleted file mode 100644 index bbd7de457e..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSObjectRACPropertySubscribingSpec.m +++ /dev/null @@ -1,158 +0,0 @@ -// -// NSObjectRACPropertySubscribingSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 9/28/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "NSObjectRACPropertySubscribingExamples.h" -#import "RACTestObject.h" - -#import "NSObject+RACPropertySubscribing.h" -#import "RACDisposable.h" -#import "RACSignal.h" - -QuickSpecBegin(NSObjectRACPropertySubscribingSpec) - -qck_describe(@"-rac_valuesForKeyPath:observer:", ^{ - id (^setupBlock)(id, id, id) = ^(RACTestObject *object, NSString *keyPath, id observer) { - return [object rac_valuesForKeyPath:keyPath observer:observer]; - }; - - qck_itBehavesLike(RACPropertySubscribingExamples, ^{ - return @{ RACPropertySubscribingExamplesSetupBlock: setupBlock }; - }); - -}); - -qck_describe(@"+rac_signalWithChangesFor:keyPath:options:observer:", ^{ - qck_describe(@"KVO options argument", ^{ - __block RACTestObject *object; - __block id actual; - __block RACSignal *(^objectValueSignal)(NSKeyValueObservingOptions); - - qck_beforeEach(^{ - 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; - }]; - }; - }); - - qck_it(@"sends a KVO dictionary", ^{ - [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { - actual = x; - }]; - - object.objectValue = @1; - - expect(actual).to(beAKindOf(NSDictionary.class)); - }); - - qck_it(@"sends a kind key by default", ^{ - [objectValueSignal(0) subscribeNext:^(NSDictionary *x) { - actual = x[NSKeyValueChangeKindKey]; - }]; - - object.objectValue = @1; - - expect(actual).notTo(beNil()); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_it(@"sends the initial value with NSKeyValueObservingOptionInitial", ^{ - [objectValueSignal(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) subscribeNext:^(NSDictionary *x) { - actual = x[NSKeyValueChangeNewKey]; - }]; - - expect(actual).to(equal(NSNull.null)); - }); - }); -}); - -qck_describe(@"-rac_valuesAndChangesForKeyPath:options:observer:", ^{ - qck_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()); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSObjectRACSelectorSignalSpec.m b/ReactiveCocoaTests/Objective-C/NSObjectRACSelectorSignalSpec.m deleted file mode 100644 index 926bd858d9..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSObjectRACSelectorSignalSpec.m +++ /dev/null @@ -1,501 +0,0 @@ -// -// NSObjectRACSelectorSignalSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/18/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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 - -QuickSpecBegin(NSObjectRACSelectorSignalSpec) - -qck_describe(@"RACTestObject", ^{ - qck_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)); - }); - - qck_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()); - }); - - qck_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; - }]; - - (void)[object objectValue]; - expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]])); - }); - - qck_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")); - }); - - qck_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")); - }); - - qck_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")); - }); - - qck_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")); - }); - - qck_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")); - }); - - qck_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()); - }); - - qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - - SEL selector = NSSelectorFromString(@"anyOldSelector:"); - - // Implement -anyOldSelector: on the object first - [object rac_signalForSelector:selector]; - - expect(@([object respondsToSelector:selector])).to(beTruthy()); - - // Then KVO the object - [[RACObserve(object, objectValue) publish] connect]; - - expect(@([object respondsToSelector:selector])).to(beTruthy()); - }); - - qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd, then signalForSelector'd again", ^{ - RACTestObject *object = [[RACTestObject alloc] init]; - - SEL selector = NSSelectorFromString(@"anyOldSelector:"); - - // Implement -anyOldSelector: on the object first - [object rac_signalForSelector:selector]; - - expect(@([object respondsToSelector:selector])).to(beTruthy()); - - // Then KVO the object - [[RACObserve(object, objectValue) publish] connect]; - - expect(@([object respondsToSelector:selector])).to(beTruthy()); - - SEL selector2 = NSSelectorFromString(@"anotherSelector:"); - - // Then implement -anotherSelector: on the object - [object rac_signalForSelector:selector2]; - - expect(@([object respondsToSelector:selector2])).to(beTruthy()); - }); - - qck_it(@"should call the right signal for two instances of the same class, adding signals for the same selector", ^{ - RACTestObject *object1 = [[RACTestObject alloc] init]; - RACTestObject *object2 = [[RACTestObject alloc] init]; - - SEL selector = NSSelectorFromString(@"lifeIsGood:"); - - __block id value1 = nil; - [[object1 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) { - value1 = x.first; - }]; - - __block id value2 = nil; - [[object2 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) { - value2 = x.first; - }]; - - [object1 lifeIsGood:@42]; - expect(value1).to(equal(@42)); - expect(value2).to(beNil()); - - [object2 lifeIsGood:@420]; - expect(value1).to(equal(@42)); - expect(value2).to(equal(@420)); - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_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()); - }); -}); - -qck_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:@[]])); -}); - -qck_describe(@"a class that already overrides -forwardInvocation:", ^{ - qck_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(@((size_t)(void*)object.forwardedSelector)).to(equal(@0)); - - [object performSelector:@selector(allObjects)]; - - expect(value).to(equal(@42)); - expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects")); - }); - - qck_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(@((size_t)(void*)object.forwardedSelector)).to(equal(@0)); - [object performSelector:@selector(allObjects)]; - expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects")); - }); -}); - -qck_describe(@"two classes in the same hierarchy", ^{ - __block RACTestObject *superclassObj; - __block RACTuple *superclassTuple; - - __block RACSubclassObject *subclassObj; - __block RACTuple *subclassTuple; - - qck_beforeEach(^{ - superclassObj = [[RACTestObject alloc] init]; - expect(superclassObj).notTo(beNil()); - - subclassObj = [[RACSubclassObject alloc] init]; - expect(subclassObj).notTo(beNil()); - }); - - qck_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)); - }); - - qck_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)).toEventually(beTruthy()); - - expectedValues = @[ @"foo", @"42" ]; - expect(subclassTuple.allObjects).to(equal(expectedValues)); - }); -}); - -qck_describe(@"-rac_signalForSelector:fromProtocol", ^{ - __block RACTestObject *object; - __block Protocol *protocol; - - qck_beforeEach(^{ - object = (id)[[RACTestObject alloc] init]; - expect(object).notTo(beNil()); - - protocol = @protocol(TestProtocol); - expect(protocol).notTo(beNil()); - }); - - qck_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)); - }); - - qck_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:@[]])); - }); - - qck_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)); - }); - - qck_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))); - }); -}); - -qck_describe(@"class reporting", ^{ - __block RACTestObject *object; - __block Class originalClass; - - qck_beforeEach(^{ - object = [[RACTestObject alloc] init]; - originalClass = object.class; - }); - - qck_it(@"should report the original class", ^{ - [object rac_signalForSelector:@selector(lifeIsGood:)]; - expect(object.class).to(beIdenticalTo(originalClass)); - }); - - qck_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)); - }); - - qck_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)); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSStringRACKeyPathUtilitiesSpec.m b/ReactiveCocoaTests/Objective-C/NSStringRACKeyPathUtilitiesSpec.m deleted file mode 100644 index f39497a5ed..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSStringRACKeyPathUtilitiesSpec.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// NSStringRACKeyPathUtilitiesSpec.m -// ReactiveCocoa -// -// Created by Uri Baghin on 05/05/2013. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "NSString+RACKeyPathUtilities.h" - -QuickSpecBegin(NSStringRACKeyPathUtilitiesSpec) - -qck_describe(@"-keyPathComponents", ^{ - qck_it(@"should return components in the key path", ^{ - expect(@"self.test.key.path".rac_keyPathComponents).to(equal((@[@"self", @"test", @"key", @"path"]))); - }); - - qck_it(@"should return nil if given an empty string", ^{ - expect(@"".rac_keyPathComponents).to(beNil()); - }); -}); - -qck_describe(@"-keyPathByDeletingLastKeyPathComponent", ^{ - qck_it(@"should return the parent key path", ^{ - expect(@"grandparent.parent.child".rac_keyPathByDeletingLastKeyPathComponent).to(equal(@"grandparent.parent")); - }); - - qck_it(@"should return nil if given an empty string", ^{ - expect(@"".rac_keyPathByDeletingLastKeyPathComponent).to(beNil()); - }); - - qck_it(@"should return nil if given a key path with only one component", ^{ - expect(@"self".rac_keyPathByDeletingLastKeyPathComponent).to(beNil()); - }); -}); - -qck_describe(@"-keyPathByDeletingFirstKeyPathComponent", ^{ - qck_it(@"should return the remaining key path", ^{ - expect(@"first.second.third".rac_keyPathByDeletingFirstKeyPathComponent).to(equal(@"second.third")); - }); - - qck_it(@"should return nil if given an empty string", ^{ - expect(@"".rac_keyPathByDeletingFirstKeyPathComponent).to(beNil()); - }); - - qck_it(@"should return nil if given a key path with only one component", ^{ - expect(@"self".rac_keyPathByDeletingFirstKeyPathComponent).to(beNil()); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSURLConnectionRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/NSURLConnectionRACSupportSpec.m deleted file mode 100644 index 68c2e0af7b..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSURLConnectionRACSupportSpec.m +++ /dev/null @@ -1,39 +0,0 @@ -// -// NSURLConnectionRACSupportSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-10-01. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "NSURLConnection+RACSupport.h" -#import "RACSignal+Operations.h" -#import "RACTuple.h" - -QuickSpecBegin(NSURLConnectionRACSupportSpec) - -qck_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(beAKindOf(RACTuple.class)); - - NSURLResponse *response = result.first; - expect(response).to(beAKindOf(NSURLResponse.class)); - - NSData *data = result.second; - expect(data).to(beAKindOf(NSData.class)); - expect(data).to(equal([NSData dataWithContentsOfURL:fileURL])); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/NSUserDefaultsRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/NSUserDefaultsRACSupportSpec.m deleted file mode 100644 index 01d2cdf0fd..0000000000 --- a/ReactiveCocoaTests/Objective-C/NSUserDefaultsRACSupportSpec.m +++ /dev/null @@ -1,137 +0,0 @@ -// -// NSUserDefaultsRACSupportSpec.m -// ReactiveCocoa -// -// Created by Matt Diephouse on 12/19/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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 - -QuickSpecBegin(NSUserDefaultsRACSupportSpec) - -__block NSUserDefaults *defaults = nil; -__block TestObserver *observer = nil; - -qck_beforeEach(^{ - defaults = NSUserDefaults.standardUserDefaults; - - observer = [TestObserver new]; -}); - -qck_afterEach(^{ - [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecStringDefault]; - [defaults removeObjectForKey:NSUserDefaultsRACSupportSpecBoolDefault]; - - observer = nil; -}); - -qck_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]).toEventually(equal(@"A string")); - expect([defaults objectForKey:NSUserDefaultsRACSupportSpecBoolDefault]).toEventually(equal(@YES)); -}); - -qck_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)); -}); - -qck_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")); -}); - -qck_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)); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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")); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACBlockTrampolineSpec.m b/ReactiveCocoaTests/Objective-C/RACBlockTrampolineSpec.m deleted file mode 100644 index 06744ef800..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACBlockTrampolineSpec.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// RACBlockTrampolineSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/28/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACBlockTrampoline.h" -#import "RACTuple.h" - -QuickSpecBegin(RACBlockTrampolineSpec) - -qck_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)); -}); - -qck_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")); -}); - -qck_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()); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACChannelExamples.h b/ReactiveCocoaTests/Objective-C/RACChannelExamples.h deleted file mode 100644 index 0952fedbba..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACChannelExamples.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACChannelExamples.m b/ReactiveCocoaTests/Objective-C/RACChannelExamples.m deleted file mode 100644 index 1ddb0d9bad..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACChannelExamples.m +++ /dev/null @@ -1,299 +0,0 @@ -// -// RACChannelExamples.m -// ReactiveCocoa -// -// Created by Uri Baghin on 30/12/2012. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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"; - -QuickConfigurationBegin(RACChannelExampleGroups) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACChannelExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __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 ]; - - qck_beforeEach(^{ - getChannel = exampleContext()[RACChannelExampleCreateBlock]; - channel = getChannel(); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - }); - - sharedExamples(RACViewChannelExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block NSString *keyPath; - __block NSObject * (^getView)(void); - __block RACChannelTerminal * (^getTerminal)(NSObject *); - __block void (^setViewValue)(NSObject *view, NSNumber *value); - - __block NSObject *testView; - __block RACChannelTerminal *endpoint; - - qck_beforeEach(^{ - keyPath = exampleContext()[RACViewChannelExampleKeyPath]; - getTerminal = exampleContext()[RACViewChannelExampleCreateTerminalBlock]; - getView = exampleContext()[RACViewChannelExampleCreateViewBlock]; - setViewValue = exampleContext()[RACViewChannelExampleSetViewValueBlock]; - - testView = getView(); - endpoint = getTerminal(testView); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_it(@"should not have a starting value", ^{ - __block BOOL receivedNext = NO; - [endpoint subscribeNext:^(id x) { - receivedNext = YES; - }]; - - expect(@(receivedNext)).to(beFalsy()); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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)).toEventually(beTruthy()); - }); - }); -} - -QuickConfigurationEnd diff --git a/ReactiveCocoaTests/Objective-C/RACChannelSpec.m b/ReactiveCocoaTests/Objective-C/RACChannelSpec.m deleted file mode 100644 index 9f6cb7acc9..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACChannelSpec.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// RACChannelSpec.m -// ReactiveCocoa -// -// Created by Uri Baghin on 30/12/2012. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACChannelExamples.h" - -#import "NSObject+RACDeallocating.h" -#import "RACChannel.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACSignal.h" - -QuickSpecBegin(RACChannelSpec) - -qck_describe(@"RACChannel", ^{ - qck_itBehavesLike(RACChannelExamples, ^{ - return @{ - RACChannelExampleCreateBlock: [^{ - return [[RACChannel alloc] init]; - } copy] - }; - }); - - qck_describe(@"memory management", ^{ - qck_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)).toEventually(beTruthy()); - }); - - qck_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)).toEventually(beTruthy()); - }); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACCommandSpec.m b/ReactiveCocoaTests/Objective-C/RACCommandSpec.m deleted file mode 100644 index 81a02f2561..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACCommandSpec.m +++ /dev/null @@ -1,545 +0,0 @@ -// -// RACCommandSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 8/31/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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" - -QuickSpecBegin(RACCommandSpec) - -RACSignal * (^emptySignalBlock)(id) = ^(id _) { - return [RACSignal empty]; -}; - -qck_describe(@"with a simple signal block", ^{ - __block RACCommand *command; - - qck_beforeEach(^{ - command = [[RACCommand alloc] initWithSignalBlock:^(id value) { - return [RACSignal return:value]; - }]; - - expect(command).notTo(beNil()); - expect(@(command.allowsConcurrentExecution)).to(beFalsy()); - }); - - qck_it(@"should be enabled by default", ^{ - expect([command.enabled first]).to(equal(@YES)); - }); - - qck_it(@"should not be executing by default", ^{ - expect([command.executing first]).to(equal(@NO)); - }); - - qck_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)).toEventually(equal(@1)); - expect(@(completed)).to(beTruthy()); - }); - - qck_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)).toEventually(beTruthy()); - }); - - qck_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).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_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).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_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)).toEventually(beTruthy()); - }); - - qck_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).toEventually(equal(RACScheduler.mainThreadScheduler)); - expect(executingScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - expect(enabledScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - expect(errorsScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); -}); - -qck_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" ]))); -}); - -qck_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])).to(beTruthy()); - - NSArray *expectedValues = @[ @"foo", @"bar", @"buzz", @"baz" ]; - expect(valuesReceived).to(equal(expectedValues)); -}); - -qck_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]).toEventually(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]).toEventually(equal(@NO)); -}); - -qck_it(@"should have allowsConcurrentExecution be observable", ^{ - RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) { - return signal; - }]; - - RACSubject *completion = [RACSubject subject]; - RACSignal *allowsConcurrentExecution = [[RACObserve(command, allowsConcurrentExecution) - takeUntil:completion] - replayLast]; - - command.allowsConcurrentExecution = YES; - - expect([allowsConcurrentExecution first]).to(beTrue()); - [completion sendCompleted]; -}); - -qck_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]).toEventually(equal(@YES)); - - [subject sendNext:RACUnit.defaultUnit]; - - NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).to(equal(@YES)); - - [subject sendNext:@"foo"]; - - expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).to(equal(@YES)); - - NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil]; - [subject sendError:error]; - - expect([command.executing first]).toEventually(equal(@NO)); - expect(receivedEvents).to(equal(expectedEvents)); -}); - -qck_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]).toEventually(equal(@YES)); - - [subject sendNext:RACUnit.defaultUnit]; - - NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ]; - expect(receivedEvents).toEventually(equal(expectedEvents)); - expect([command.executing first]).to(equal(@YES)); - - [subject sendNext:@"foo"]; - - expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ]; - expect(receivedEvents).toEventually(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).toEventually(equal(expectedEvents)); - expect([command.executing first]).toEventually(equal(@NO)); -}); - -qck_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]).toEventually(equal(@YES)); - - [firstSubject sendError:firstError]; - expect([command.executing first]).toEventually(equal(@YES)); - - NSArray *expected = @[ firstError ]; - expect(receivedErrors).toEventually(equal(expected)); - - [secondSubject sendError:secondError]; - expect([command.executing first]).toEventually(equal(@NO)); - - expected = @[ firstError, secondError ]; - expect(receivedErrors).toEventually(equal(expected)); -}); - -qck_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]).toEventually(equal(@YES)); - - [subject sendNext:RACUnit.defaultUnit]; - [subject sendCompleted]; - - expect([command.executing first]).toEventually(equal(@NO)); - expect(@(receivedEvent)).to(beFalsy()); -}); - -qck_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:^{ - [command execute:errorSignal]; - }]; - - expect(receivedScheduler).to(beNil()); - expect(receivedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler)); -}); - -qck_describe(@"enabled signal", ^{ - __block RACSubject *enabledSubject; - __block RACCommand *command; - - qck_beforeEach(^{ - enabledSubject = [RACSubject subject]; - command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id _) { - return [RACSignal return:RACUnit.defaultUnit]; - }]; - }); - - qck_it(@"should send YES by default", ^{ - expect([command.enabled first]).to(equal(@YES)); - }); - - qck_it(@"should send whatever the enabledSignal has sent most recently", ^{ - [enabledSubject sendNext:@NO]; - expect([command.enabled first]).toEventually(equal(@NO)); - - [enabledSubject sendNext:@YES]; - expect([command.enabled first]).toEventually(equal(@YES)); - - [enabledSubject sendNext:@NO]; - expect([command.enabled first]).toEventually(equal(@NO)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(innerExecuted)).toEventually(beTruthy()); - - expect([command.enabled first]).to(equal(@YES)); - }); - - qck_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)); - }); - - qck_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]).toEventually(equal(@NO)); - expect(updatedScheduler).to(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(completed)).toEventually(beTruthy()); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACCompoundDisposableSpec.m b/ReactiveCocoaTests/Objective-C/RACCompoundDisposableSpec.m deleted file mode 100644 index c135018a45..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACCompoundDisposableSpec.m +++ /dev/null @@ -1,112 +0,0 @@ -// -// RACCompoundDisposableSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 11/30/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACCompoundDisposable.h" - -QuickSpecBegin(RACCompoundDisposableSpec) - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACControlCommandExamples.h b/ReactiveCocoaTests/Objective-C/RACControlCommandExamples.h deleted file mode 100644 index 3fbaa34ca6..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACControlCommandExamples.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACControlCommandExamples.m b/ReactiveCocoaTests/Objective-C/RACControlCommandExamples.m deleted file mode 100644 index 6749bd481f..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACControlCommandExamples.m +++ /dev/null @@ -1,86 +0,0 @@ -// -// RACControlCommandExamples.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-08-15. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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 - -QuickConfigurationBegin(RACControlCommandExampleGroups) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACControlCommandExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block id control; - __block void (^activate)(id); - - __block RACSubject *enabledSubject; - __block RACCommand *command; - - qck_beforeEach(^{ - control = exampleContext()[RACControlCommandExampleControl]; - activate = [exampleContext()[RACControlCommandExampleActivateBlock] copy]; - - enabledSubject = [RACSubject subject]; - command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id sender) { - return [RACSignal return:sender]; - }]; - - [control setRac_command:command]; - }); - - qck_it(@"should bind the control's enabledness to the command", ^{ - expect(@([control isEnabled])).toEventually(beTruthy()); - - [enabledSubject sendNext:@NO]; - expect(@([control isEnabled])).toEventually(beFalsy()); - - [enabledSubject sendNext:@YES]; - expect(@([control isEnabled])).toEventually(beTruthy()); - }); - - qck_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)).toEventually(beTruthy()); - }); - - qck_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)); - }); - }); -} - -QuickConfigurationEnd diff --git a/ReactiveCocoaTests/Objective-C/RACDelegateProxySpec.m b/ReactiveCocoaTests/Objective-C/RACDelegateProxySpec.m deleted file mode 100644 index 9b8d2e4c30..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACDelegateProxySpec.m +++ /dev/null @@ -1,92 +0,0 @@ -// -// RACDelegateProxySpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-06-22. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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 -@property (nonatomic, assign) BOOL lengthOfStringInvoked; -@end - -QuickSpecBegin(RACDelegateProxySpec) - -__block id proxy; -__block TestDelegate *delegate; -__block Protocol *protocol; - -qck_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()); -}); - -qck_it(@"should not respond to selectors at first", ^{ - expect(@([proxy respondsToSelector:@selector(lengthOfString:)])).to(beFalsy()); -}); - -qck_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"))); -}); - -qck_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()); -}); - -qck_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()); -}); - -QuickSpecEnd - -@implementation TestDelegate - -- (NSUInteger)lengthOfString:(NSString *)str { - self.lengthOfStringInvoked = YES; - return str.length; -} - -@end diff --git a/ReactiveCocoaTests/Objective-C/RACDisposableSpec.m b/ReactiveCocoaTests/Objective-C/RACDisposableSpec.m deleted file mode 100644 index 6d6e374c35..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACDisposableSpec.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// RACDisposableSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-06-13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACDisposable.h" -#import "RACScopedDisposable.h" - -QuickSpecBegin(RACDisposableSpec) - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACEventSpec.m b/ReactiveCocoaTests/Objective-C/RACEventSpec.m deleted file mode 100644 index 0a1d3e6a21..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACEventSpec.m +++ /dev/null @@ -1,83 +0,0 @@ -// -// RACEventSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-01-07. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACEvent.h" - -QuickSpecBegin(RACEventSpec) - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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)); -}); - -qck_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()); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACKVOChannelSpec.m b/ReactiveCocoaTests/Objective-C/RACKVOChannelSpec.m deleted file mode 100644 index 70e94df285..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACKVOChannelSpec.m +++ /dev/null @@ -1,394 +0,0 @@ -// -// RACKVOChannelSpec.m -// ReactiveCocoa -// -// Created by Uri Baghin on 16/12/2012. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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" - -QuickSpecBegin(RACKVOChannelSpec) - -qck_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 ]; - - qck_beforeEach(^{ - 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]; - }; - - qck_itBehavesLike(RACPropertySignalExamples, ^{ - return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; - }); - - qck_itBehavesLike(RACChannelExamples, ^{ - return @{ - RACChannelExampleCreateBlock: [^{ - return [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil]; - } copy] - }; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - [receivedValues addObject:value]; - }]; - - RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:value1]; - [subscriber sendNext:value2]; - [subscriber sendNext:value3]; - return nil; - }]; - - [signal subscribe:channel.followingTerminal]; - expect(receivedValues).to(equal(values)); - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_describe(@"RACChannelTo", ^{ - __block RACTestObject *a; - __block RACTestObject *b; - __block RACTestObject *c; - __block NSString *testName1; - __block NSString *testName2; - __block NSString *testName3; - - qck_beforeEach(^{ - a = [[RACTestObject alloc] init]; - b = [[RACTestObject alloc] init]; - c = [[RACTestObject alloc] init]; - testName1 = @"sync it!"; - testName2 = @"sync it again!"; - testName3 = @"sync it once more!"; - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)).toEventually(beTruthy()); - expect(b.strongTestObjectValue.stringValue).to(beNil()); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(b.strongTestObjectValue.integerValue)).to(equal(@5)); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACKVOProxySpec.m b/ReactiveCocoaTests/Objective-C/RACKVOProxySpec.m deleted file mode 100644 index c08a75a244..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACKVOProxySpec.m +++ /dev/null @@ -1,237 +0,0 @@ -// -// RACKVOProxySpec.m -// ReactiveCocoa -// -// Created by Richard Speyer on 4/24/14. -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// - -#import -#import -#import - -#import "RACKVOProxy.h" - -#import "NSObject+RACKVOWrapper.h" -#import "NSObject+RACPropertySubscribing.h" -#import "RACSerialDisposable.h" -#import "RACSignal+Operations.h" -#import "RACScheduler.h" -#import "RACSubject.h" - -@interface TestObject : NSObject { - volatile int _testInt; - pthread_mutex_t _mutex; -} - -@property (assign, atomic) int testInt; - -@end - -@implementation TestObject - -- (instancetype)init { - if ((self = [super init])) { - pthread_mutex_init(&_mutex, nil); - } - - return self; -} - -- (int)testInt { - int test = 0; - pthread_mutex_lock(&_mutex); - test = _testInt; - pthread_mutex_unlock(&_mutex); - return test; -} - -// Use manual KVO notifications to avoid any possible race conditions within the -// automatic KVO implementation. -- (void)setTestInt:(int)value { - [self willChangeValueForKey:@keypath(self.testInt)]; - pthread_mutex_lock(&_mutex); - _testInt = value; - pthread_mutex_unlock(&_mutex); - [self didChangeValueForKey:@keypath(self.testInt)]; -} - -+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { - return NO; -} - -@end - -QuickSpecBegin(RACKVOProxySpec) - -qck_describe(@"RACKVOProxy", ^{ - __block TestObject *testObject; - __block dispatch_queue_t concurrentQueue; - - qck_beforeEach(^{ - testObject = [[TestObject alloc] init]; - concurrentQueue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.RACKVOProxySpec.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); - }); - - qck_afterEach(^{ - dispatch_barrier_sync(concurrentQueue, ^{ - testObject = nil; - }); - }); - - qck_describe(@"basic", ^{ - qck_it(@"should handle multiple observations on the same value", ^{ - __block int observedValue1 = 0; - __block int observedValue2 = 0; - - [[[RACObserve(testObject, testInt) - skip:1] - take:1] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue1 = wrappedInt.intValue; - }]; - - [[[RACObserve(testObject, testInt) - skip:1] - take:1] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue2 = wrappedInt.intValue; - }]; - - testObject.testInt = 2; - - expect(@(observedValue1)).toEventually(equal(@2)); - expect(@(observedValue2)).toEventually(equal(@2)); - }); - - qck_it(@"can remove individual observation", ^{ - __block int observedValue1 = 0; - __block int observedValue2 = 0; - - RACDisposable *disposable1 = [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) { - observedValue1 = wrappedInt.intValue; - }]; - - [RACObserve(testObject, testInt) subscribeNext:^(NSNumber *wrappedInt) { - observedValue2 = wrappedInt.intValue; - }]; - - testObject.testInt = 2; - - expect(@(observedValue1)).toEventually(equal(@2)); - expect(@(observedValue2)).toEventually(equal(@2)); - - [disposable1 dispose]; - testObject.testInt = 3; - - expect(@(observedValue2)).toEventually(equal(@3)); - expect(@(observedValue1)).to(equal(@2)); - }); - }); - - qck_describe(@"async", ^{ - qck_it(@"should handle changes being made on another queue", ^{ - __block int observedValue = 0; - [[[RACObserve(testObject, testInt) - skip:1] - take:1] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue = wrappedInt.intValue; - }]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = 2; - }); - - dispatch_barrier_sync(concurrentQueue, ^{}); - expect(@(observedValue)).toEventually(equal(@2)); - }); - - qck_it(@"should handle changes being made on another queue using deliverOn", ^{ - __block int observedValue = 0; - [[[[RACObserve(testObject, testInt) - skip:1] - take:1] - deliverOn:[RACScheduler mainThreadScheduler]] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue = wrappedInt.intValue; - }]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = 2; - }); - - dispatch_barrier_sync(concurrentQueue, ^{}); - expect(@(observedValue)).toEventually(equal(@2)); - }); - - qck_it(@"async disposal of target", ^{ - __block int observedValue; - [[RACObserve(testObject, testInt) - deliverOn:RACScheduler.mainThreadScheduler] - subscribeNext:^(NSNumber *wrappedInt) { - observedValue = wrappedInt.intValue; - }]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = 2; - testObject = nil; - }); - - dispatch_barrier_sync(concurrentQueue, ^{}); - expect(@(observedValue)).toEventually(equal(@2)); - }); - }); - - qck_describe(@"stress", ^{ - static const size_t numIterations = 5000; - - __block dispatch_queue_t iterationQueue; - - beforeEach(^{ - iterationQueue = dispatch_queue_create("org.reactivecocoa.ReactiveCocoa.RACKVOProxySpec.iterationQueue", DISPATCH_QUEUE_CONCURRENT); - }); - - // ReactiveCocoa/ReactiveCocoa#1122 - qck_it(@"async disposal of observer", ^{ - RACSerialDisposable *disposable = [[RACSerialDisposable alloc] init]; - - dispatch_apply(numIterations, iterationQueue, ^(size_t index) { - RACDisposable *newDisposable = [RACObserve(testObject, testInt) subscribeCompleted:^{}]; - [[disposable swapInDisposable:newDisposable] dispose]; - - dispatch_async(concurrentQueue, ^{ - testObject.testInt = (int)index; - }); - }); - - dispatch_barrier_sync(iterationQueue, ^{ - [disposable dispose]; - }); - }); - - qck_it(@"async disposal of signal with in-flight changes", ^{ - RACSubject *teardown = [RACSubject subject]; - - RACSignal *isEvenSignal = [[[[RACObserve(testObject, testInt) - map:^(NSNumber *wrappedInt) { - return @((wrappedInt.intValue % 2) == 0); - }] - deliverOn:RACScheduler.mainThreadScheduler] - takeUntil:teardown] - replayLast]; - - dispatch_apply(numIterations, iterationQueue, ^(size_t index) { - testObject.testInt = (int)index; - }); - - dispatch_barrier_async(iterationQueue, ^{ - [teardown sendNext:nil]; - }); - - expect(@([isEvenSignal asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - }); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACKVOWrapperSpec.m b/ReactiveCocoaTests/Objective-C/RACKVOWrapperSpec.m deleted file mode 100644 index d013ccc8d4..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACKVOWrapperSpec.m +++ /dev/null @@ -1,675 +0,0 @@ -// -// RACKVOWrapperSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-08-07. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "NSObject+RACKVOWrapper.h" - -#import -#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"; - -QuickConfigurationBegin(RACKVOWrapperExampleGroups) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACKVOWrapperExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __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 *, BOOL, BOOL) = nil; - - qck_beforeEach(^{ - NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperExamplesTargetBlock]; - target = targetBlock(); - keyPath = exampleContext()[RACKVOWrapperExamplesKeyPath]; - changeBlock = exampleContext()[RACKVOWrapperExamplesChangeBlock]; - valueBlock = exampleContext()[RACKVOWrapperExamplesValueBlock]; - changesValueDirectly = [exampleContext()[RACKVOWrapperExamplesChangesValueDirectly] boolValue]; - - priorCallCount = 0; - posteriorCallCount = 0; - - callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) { - priorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent; - ++priorCallCount; - return; - } - posteriorTriggeredByLastKeyPathComponent = affectedOnlyLastComponent; - posteriorTriggeredByDeallocation = causedByDealloc; - ++posteriorCallCount; - } copy]; - }); - - qck_afterEach(^{ - target = nil; - keyPath = nil; - changeBlock = nil; - valueBlock = nil; - changesValueDirectly = NO; - - callbackBlock = nil; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - 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)); - }); - - qck_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)); - }); - - qck_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()); - }); - }); - - qck_sharedExamples(RACKVOWrapperCollectionExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block NSObject *target = nil; - __block NSString *keyPath = nil; - __block NSMutableOrderedSet *mutableKeyPathProxy = nil; - __block void (^callbackBlock)(id, NSDictionary *, BOOL, BOOL) = nil; - - __block id priorValue = nil; - __block id posteriorValue = nil; - __block NSDictionary *priorChange = nil; - __block NSDictionary *posteriorChange = nil; - - qck_beforeEach(^{ - NSObject * (^targetBlock)(void) = exampleContext()[RACKVOWrapperCollectionExamplesTargetBlock]; - target = targetBlock(); - keyPath = exampleContext()[RACKVOWrapperCollectionExamplesKeyPath]; - - callbackBlock = [^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - 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]; - }); - - qck_afterEach(^{ - target = nil; - keyPath = nil; - callbackBlock = nil; - - priorValue = nil; - priorChange = nil; - posteriorValue = nil; - posteriorChange = nil; - }); - - qck_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])); - }); - - qck_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])); - }); - - qck_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])); - }); - - qck_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 ])); - }); - - qck_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()); - }); - }); -} - -QuickConfigurationEnd - -QuickSpecBegin(RACKVOWrapperSpec) - -qck_describe(@"-rac_observeKeyPath:options:observer:block:", ^{ - qck_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]; - }; - - qck_itBehavesLike(RACKVOWrapperExamples, ^{ - return @{ - RACKVOWrapperExamplesTargetBlock: targetBlock, - RACKVOWrapperExamplesKeyPath: @keypath(RACTestObject.new, weakTestObjectValue), - RACKVOWrapperExamplesChangeBlock: changeBlock, - RACKVOWrapperExamplesValueBlock: valueBlock, - RACKVOWrapperExamplesChangesValueDirectly: @YES - }; - }); - - qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{ - return @{ - RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, - RACKVOWrapperCollectionExamplesKeyPath: @keypath(RACTestObject.new, orderedSetValue) - }; - }); - }); - - qck_describe(@"on composite key paths'", ^{ - qck_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]; - }; - - qck_itBehavesLike(RACKVOWrapperExamples, ^{ - return @{ - RACKVOWrapperExamplesTargetBlock: targetBlock, - RACKVOWrapperExamplesKeyPath: @keypath(RACTestObject.new, strongTestObjectValue.weakTestObjectValue), - RACKVOWrapperExamplesChangeBlock: changeBlock, - RACKVOWrapperExamplesValueBlock: valueBlock, - RACKVOWrapperExamplesChangesValueDirectly: @YES - }; - }); - - qck_itBehavesLike(RACKVOWrapperCollectionExamples, ^{ - return @{ - RACKVOWrapperCollectionExamplesTargetBlock: targetBlock, - RACKVOWrapperCollectionExamplesKeyPath: @keypath(RACTestObject.new, strongTestObjectValue.orderedSetValue) - }; - }); - }); - - qck_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; - }; - - qck_itBehavesLike(RACKVOWrapperExamples, ^{ - return @{ - RACKVOWrapperExamplesTargetBlock: targetBlock, - RACKVOWrapperExamplesKeyPath: @keypath([[RACTestObject alloc] init], weakTestObjectValue.strongTestObjectValue), - RACKVOWrapperExamplesChangeBlock: changeBlock, - RACKVOWrapperExamplesValueBlock: valueBlock, - RACKVOWrapperExamplesChangesValueDirectly: @NO - }; - }); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - lastValue = value; - }]; - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - } - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - lastValue = value; - }]; - - expect(lastValue).to(equal(@42)); - } - - expect(lastValue).to(equal(@42)); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - lastValue = value; - }]; - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - } - - expect(lastValue).to(beAKindOf(RACTestObject.class)); - }); - }); - - qck_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 value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - observerDeallocationTriggeredChange = YES; - }]; - - [target rac_observeKeyPath:@keypath(target.weakTestObjectValue) options:0 observer:observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - targetDeallocationTriggeredChange = YES; - }]; - } - - expect(@(observerDisposed)).to(beTruthy()); - expect(@(observerDeallocationTriggeredChange)).to(beFalsy()); - - expect(@(targetDisposed)).to(beTruthy()); - expect(@(targetDeallocationTriggeredChange)).to(beTruthy()); - }); - - qck_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 value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - objectDeallocationTriggeredChange = YES; - }]; - } - - expect(@(objectDisposed)).to(beTruthy()); - expect(@(objectDeallocationTriggeredChange)).to(beTruthy()); - }); - - qck_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 value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - objectDeallocationTriggeredChange = YES; - }]; - } - - expect(@(objectDisposed)).to(beTruthy()); - expect(@(objectDeallocationTriggeredChange)).to(beTruthy()); - }); -}); - -qck_describe(@"rac_addObserver:forKeyPath:options:block:", ^{ - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - expect([change objectForKey:NSKeyValueChangeNewKey]).to(equal(@YES)); - - expect(@(notified)).to(beFalsy()); - notified = YES; - }]; - - expect(disposable).notTo(beNil()); - - [operation start]; - [operation waitUntilFinished]; - - expect(@(notified)).toEventually(beTruthy()); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; - - expect(disposable).notTo(beNil()); - }); - - qck_context(@"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((__bridge id)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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; - expect(identifier).notTo(beNil()); - - CFRelease(target); - } - - expect(weakTarget).to(beNil()); - expect(identifier).to(beNil()); - }; - - qck_it(@"stops KVO on NSObject subclasses", ^{ - testKVOOnSubclass(NSOperation.class, self); - }); - - qck_it(@"stops KVO on subclasses of already-swizzled classes", ^{ - testKVOOnSubclass(RACTestOperation.class, self); - }); - - qck_it(@"stops KVO on NSObject subclasses even with a nil observer", ^{ - testKVOOnSubclass(NSOperation.class, nil); - }); - - qck_it(@"stops KVO on subclasses of already-swizzled classes even with a nil observer", ^{ - testKVOOnSubclass(RACTestOperation.class, nil); - }); - }); - - qck_it(@"should automatically stop KVO when the observer deallocates", ^{ - __weak id weakObserver = nil; - __weak id weakIdentifier = nil; - - NSOperation *operation = [[NSOperation alloc] init]; - - @autoreleasepool { - // Create an observer that we control the memory management of. - CFTypeRef observer = CFBridgingRetain([[NSOperation alloc] init]); - expect((__bridge id)observer).notTo(beNil()); - - weakObserver = (__bridge id)observer; - expect(weakObserver).notTo(beNil()); - - id identifier = [operation rac_observeKeyPath:@"isFinished" options:0 observer:(__bridge id)observer block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {}]; - expect(identifier).notTo(beNil()); - - weakIdentifier = identifier; - expect(weakIdentifier).notTo(beNil()); - - CFRelease(observer); - } - - expect(weakObserver).to(beNil()); - expect(weakIdentifier).to(beNil()); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - name = queue.name; - }]; - - queue.name = @"1"; - expect(name).to(equal(@"1")); - [disposable dispose]; - queue.name = @"2"; - expect(name).to(equal(@"1")); - }); - - qck_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, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - name1 = queue.name; - }]; - [queue rac_observeKeyPath:@"name" options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) { - 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")); - }); -}); - -QuickSpecEnd - -@implementation RACTestOperation -@end diff --git a/ReactiveCocoaTests/Objective-C/RACMulticastConnectionSpec.m b/ReactiveCocoaTests/Objective-C/RACMulticastConnectionSpec.m deleted file mode 100644 index 7a50ca2093..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACMulticastConnectionSpec.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// RACMulticastConnectionSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 10/8/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACMulticastConnection.h" -#import "RACDisposable.h" -#import "RACSignal+Operations.h" -#import "RACSubscriber.h" -#import "RACReplaySubject.h" -#import "RACScheduler.h" -#import - -QuickSpecBegin(RACMulticastConnectionSpec) - -__block NSUInteger subscriptionCount = 0; -__block RACMulticastConnection *connection; - -qck_beforeEach(^{ - subscriptionCount = 0; - connection = [[RACSignal createSignal:^(id subscriber) { - subscriptionCount++; - return (RACDisposable *)nil; - }] publish]; - - expect(@(subscriptionCount)).to(equal(@0)); -}); - -qck_describe(@"-connect", ^{ - qck_it(@"should subscribe to the underlying signal", ^{ - [connection connect]; - expect(@(subscriptionCount)).to(equal(@1)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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).toEventuallyNot(beNil()); - }); -}); - -qck_describe(@"-autoconnect", ^{ - __block RACSignal *autoconnectedSignal; - - qck_beforeEach(^{ - autoconnectedSignal = [connection autoconnect]; - }); - - qck_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)); - }); - - qck_it(@"should dispose of the multicasted subscription when the signal has no subscribers", ^{ - __block BOOL disposed = NO; - __block id connectionSubscriber = nil; - RACSignal *signal = [[[RACSignal createSignal:^(id subscriber) { - // Keep the subscriber alive so it doesn't trigger disposal on dealloc - connectionSubscriber = subscriber; - subscriptionCount++; - return [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - }] publish] autoconnect]; - RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; - - expect(@(disposed)).to(beFalsy()); - [disposable dispose]; - expect(@(disposed)).to(beTruthy()); - }); - - qck_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]; - }); - - qck_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).toEventually(equal((@[ @1, @2 ]))); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACPropertySignalExamples.h b/ReactiveCocoaTests/Objective-C/RACPropertySignalExamples.h deleted file mode 100644 index 59a2b4305b..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACPropertySignalExamples.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACPropertySignalExamples.m b/ReactiveCocoaTests/Objective-C/RACPropertySignalExamples.m deleted file mode 100644 index 092e015370..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACPropertySignalExamples.m +++ /dev/null @@ -1,144 +0,0 @@ -// -// RACPropertySignalExamples.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 9/28/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACTestObject.h" - -#import -#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"; - -QuickConfigurationBegin(RACPropertySignalExampleGroups) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACPropertySignalExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block RACTestObject *testObject = nil; - __block void (^setupBlock)(RACTestObject *, NSString *keyPath, id nilValue, RACSignal *); - - qck_beforeEach(^{ - setupBlock = exampleContext()[RACPropertySignalExamplesSetupBlock]; - testObject = [[RACTestObject alloc] init]; - }); - - qck_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()); - }); - - qck_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")); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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()); - }); - }); -} - -QuickConfigurationEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSchedulerSpec.m b/ReactiveCocoaTests/Objective-C/RACSchedulerSpec.m deleted file mode 100644 index a2a1f3c740..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSchedulerSpec.m +++ /dev/null @@ -1,439 +0,0 @@ -// -// RACSchedulerSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 11/29/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACScheduler.h" -#import "RACScheduler+Private.h" -#import "RACQueueScheduler+Subclass.h" -#import "RACDisposable.h" -#import -#import "RACTestExampleScheduler.h" -#import - -// 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); - }]; - } -} - -QuickSpecBegin(RACSchedulerSpec) - -qck_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).toEventually(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); -}); - -qck_describe(@"+mainThreadScheduler", ^{ - qck_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)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); - - qck_it(@"should schedule future blocks", ^{ - __block BOOL done = NO; - - [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ - done = YES; - }]; - - expect(@(done)).to(beFalsy()); - expect(@(done)).toEventually(beTruthy()); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); - - qck_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)).toEventually(beGreaterThanOrEqualTo(@1)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3)); - - [disposable dispose]; - [NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - expect(@(count)).to(beGreaterThanOrEqualTo(@3)); - }); -}); - -qck_describe(@"+scheduler", ^{ - __block RACScheduler *scheduler; - __block NSDate * (^futureDate)(void); - - qck_beforeEach(^{ - scheduler = [RACScheduler scheduler]; - - futureDate = ^{ - return [NSDate dateWithTimeIntervalSinceNow:0.01]; - }; - }); - - qck_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)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); - - qck_it(@"should schedule future blocks", ^{ - __block BOOL done = NO; - - [scheduler after:futureDate() schedule:^{ - done = YES; - }]; - - expect(@(done)).to(beFalsy()); - expect(@(done)).toEventually(beTruthy()); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(firstBlockRan)).to(beFalsy()); - }); - - qck_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(beGreaterThanOrEqualTo(@0)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@1)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@2)); - expect(@(count)).toEventually(beGreaterThanOrEqualTo(@3)); - - [disposable dispose]; - [NSThread sleepForTimeInterval:0.1]; - - expect(@(count)).to(beGreaterThanOrEqualTo(@3)); - }); -}); - -qck_describe(@"+subscriptionScheduler", ^{ - qck_describe(@"setting +currentScheduler", ^{ - __block RACScheduler *currentScheduler; - - qck_beforeEach(^{ - currentScheduler = nil; - }); - - qck_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).toEventually(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_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).toEventuallyNot(beNil()); - expect(currentScheduler).notTo(equal(RACScheduler.mainThreadScheduler)); - }); - - qck_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).toEventually(equal(backgroundScheduler)); - }); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(executedImmediately)).to(beTruthy()); - }); -}); - -qck_describe(@"+immediateScheduler", ^{ - qck_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()); - }); - - qck_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()); - }); -}); - -qck_describe(@"-scheduleRecursiveBlock:", ^{ - qck_describe(@"with a synchronous scheduler", ^{ - qck_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()); - }); - - qck_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)); - }); - - qck_it(@"should unroll deep recursion", ^{ - static const NSUInteger depth = 100000; - __block NSUInteger scheduleCount = 0; - [RACScheduler.immediateScheduler scheduleRecursiveBlock:^(void (^recurse)(void)) { - scheduleCount++; - - if (scheduleCount < depth) recurse(); - }]; - - expect(@(scheduleCount)).to(equal(@(depth))); - }); - }); - - qck_describe(@"with an asynchronous scheduler", ^{ - qck_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)).toEventually(beTruthy()); - }); - - qck_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)).toEventually(equal(@3)); - }); - - qck_it(@"should reschedule when invoked asynchronously", ^{ - __block NSUInteger count = 0; - - RACScheduler *asynchronousScheduler = [RACScheduler scheduler]; - [RACScheduler.mainThreadScheduler 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)).toEventually(equal(@3)); - }); - - qck_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)).toEventually(equal(@1)); - }); - }); -}); - -qck_describe(@"subclassing", ^{ - __block RACTestExampleScheduler *scheduler; - - qck_beforeEach(^{ - scheduler = [[RACTestExampleScheduler alloc] initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; - }); - - qck_it(@"should invoke blocks scheduled with -schedule:", ^{ - __block BOOL invoked = NO; - [scheduler schedule:^{ - invoked = YES; - }]; - - expect(@(invoked)).toEventually(beTruthy()); - }); - - qck_it(@"should invoke blocks scheduled with -after:schedule:", ^{ - __block BOOL invoked = NO; - [scheduler after:[NSDate dateWithTimeIntervalSinceNow:0.01] schedule:^{ - invoked = YES; - }]; - - expect(@(invoked)).toEventually(beTruthy()); - }); - - qck_it(@"should set a valid current scheduler", ^{ - __block RACScheduler *currentScheduler; - [scheduler schedule:^{ - currentScheduler = RACScheduler.currentScheduler; - }]; - - expect(currentScheduler).toEventually(equal(scheduler)); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSequenceAdditionsSpec.m b/ReactiveCocoaTests/Objective-C/RACSequenceAdditionsSpec.m deleted file mode 100644 index 6cb744509b..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSequenceAdditionsSpec.m +++ /dev/null @@ -1,359 +0,0 @@ -// -// RACSequenceAdditionsSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-11-01. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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" - -QuickSpecBegin(RACSequenceAdditionsSpec) - -__block NSArray *numbers; - -qck_beforeEach(^{ - NSMutableArray *mutableNumbers = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++) { - [mutableNumbers addObject:@(i)]; - } - - numbers = [mutableNumbers copy]; -}); - -qck_describe(@"NSArray sequences", ^{ - __block NSMutableArray *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - values = [numbers mutableCopy]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = [values copy]; - [values addObject:@6]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); - - qck_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)); - }); -}); - -qck_describe(@"NSDictionary sequences", ^{ - __block NSMutableDictionary *dict; - - __block NSMutableArray *tuples; - __block RACSequence *tupleSequence; - - __block NSArray *keys; - __block RACSequence *keySequence; - - __block NSArray *values; - __block RACSequence *valueSequence; - - qck_beforeEach(^{ - dict = [@{ - @"foo": @"bar", - @"baz": @"buzz", - @5: NSNull.null - } mutableCopy]; - - tuples = [NSMutableArray array]; - for (id key in dict) { - RACTuple *tuple = RACTuplePack(key, dict[key]); - [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()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: tupleSequence, - RACSequenceExampleExpectedValues: tuples - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: keySequence, - RACSequenceExampleExpectedValues: keys - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: valueSequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_describe(@"should be immutable", ^{ - qck_beforeEach(^{ - dict[@"foo"] = @"rab"; - dict[@6] = @7; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: tupleSequence, - RACSequenceExampleExpectedValues: tuples - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: keySequence, - RACSequenceExampleExpectedValues: keys - }; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: valueSequence, - RACSequenceExampleExpectedValues: values - }; - }); - }); -}); - -qck_describe(@"NSOrderedSet sequences", ^{ - __block NSMutableOrderedSet *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - values = [NSMutableOrderedSet orderedSetWithArray:numbers]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values.array - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = [values.array copy]; - [values addObject:@6]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); -}); - -qck_describe(@"NSSet sequences", ^{ - __block NSMutableSet *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - values = [NSMutableSet setWithArray:numbers]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values.allObjects - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = [values.allObjects copy]; - [values addObject:@6]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); -}); - -qck_describe(@"NSString sequences", ^{ - __block NSMutableString *string; - __block NSArray *values; - __block RACSequence *sequence; - - qck_beforeEach(^{ - string = [@"foobar" mutableCopy]; - values = @[ @"f", @"o", @"o", @"b", @"a", @"r" ]; - sequence = string.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_describe(@"should be immutable", ^{ - qck_beforeEach(^{ - [string appendString:@"buzz"]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: values - }; - }); - }); - - qck_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)); - }); -}); - -qck_describe(@"RACTuple sequences", ^{ - __block RACTuple *tuple; - __block RACSequence *sequence; - - qck_beforeEach(^{ - tuple = RACTuplePack(@"foo", nil, @"bar", NSNull.null, RACTupleNil.tupleNil); - - sequence = tuple.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: @[ @"foo", NSNull.null, @"bar", NSNull.null, NSNull.null ] - }; - }); -}); - -qck_describe(@"NSIndexSet sequences", ^{ - __block NSMutableIndexSet *values; - __block RACSequence *sequence; - - NSArray * (^valuesFromIndexSet)(NSIndexSet *indexSet) = ^NSArray *(NSIndexSet *indexSet) { - NSMutableArray *arr = [NSMutableArray array]; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - [arr addObject:@(idx)]; - }]; - - return [arr copy]; - }; - - qck_beforeEach(^{ - values = [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 10)]; - sequence = values.rac_sequence; - expect(sequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: valuesFromIndexSet(values) - }; - }); - - qck_describe(@"should be immutable", ^{ - __block NSArray *unchangedValues; - - qck_beforeEach(^{ - unchangedValues = valuesFromIndexSet(values); - [values addIndex:20]; - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: unchangedValues - }; - }); - }); - - qck_describe(@"should not fire if empty", ^{ - __block NSIndexSet *emptyIndexSet; - __block RACSequence *emptySequence; - - qck_beforeEach(^{ - emptyIndexSet = [NSIndexSet indexSet]; - emptySequence = emptyIndexSet.rac_sequence; - expect(emptySequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: emptySequence, - RACSequenceExampleExpectedValues: valuesFromIndexSet(emptyIndexSet) - }; - }); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSequenceExamples.h b/ReactiveCocoaTests/Objective-C/RACSequenceExamples.h deleted file mode 100644 index 922b056e74..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSequenceExamples.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACSequenceExamples.m b/ReactiveCocoaTests/Objective-C/RACSequenceExamples.m deleted file mode 100644 index 469c1d44f0..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSequenceExamples.m +++ /dev/null @@ -1,131 +0,0 @@ -// -// RACSequenceExamples.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-11-01. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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"; - -QuickConfigurationBegin(RACSequenceExampleGroups) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACSequenceExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block RACSequence *sequence; - __block NSArray *values; - - qck_beforeEach(^{ - sequence = exampleContext()[RACSequenceExampleSequence]; - values = [exampleContext()[RACSequenceExampleExpectedValues] copy]; - }); - - qck_it(@"should implement ", ^{ - NSMutableArray *collectedValues = [NSMutableArray array]; - for (id value in sequence) { - [collectedValues addObject:value]; - } - - expect(collectedValues).to(equal(values)); - }); - - qck_it(@"should return an array", ^{ - expect(sequence.array).to(equal(values)); - }); - - qck_describe(@"-signalWithScheduler:", ^{ - qck_it(@"should return an immediately scheduled signal", ^{ - RACSignal *signal = [sequence signalWithScheduler:RACScheduler.immediateScheduler]; - expect(signal.toArray).to(equal(values)); - }); - - qck_it(@"should return a background scheduled signal", ^{ - RACSignal *signal = [sequence signalWithScheduler:[RACScheduler scheduler]]; - expect(signal.toArray).to(equal(values)); - }); - - qck_it(@"should only evaluate one value per scheduling", ^{ - RACScheduler* scheduler = [RACScheduler schedulerWithPriority:RACSchedulerPriorityHigh]; - RACSignal *signal = [sequence signalWithScheduler:scheduler]; - - __block BOOL flag = YES; - __block BOOL completed = NO; - [signal subscribeNext:^(id x) { - expect(@(flag)).to(beTruthy()); - flag = NO; - - [scheduler schedule:^{ - // This should get executed before the next value (which - // verifies that it's YES). - flag = YES; - }]; - } completed:^{ - completed = YES; - }]; - - expect(@(completed)).toEventually(beTruthy()); - }); - }); - - qck_it(@"should be equal to itself", ^{ - expect(sequence).to(equal(sequence)); - }); - - qck_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))); - }); - - qck_it(@"should not be equal to a different sequence of values", ^{ - RACSequence *anotherSequence = [RACSequence return:@(-1)]; - expect(sequence).notTo(equal(anotherSequence)); - }); - - qck_it(@"should return an identical object for -copy", ^{ - expect([sequence copy]).to(beIdenticalTo(sequence)); - }); - - qck_it(@"should archive", ^{ - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:sequence]; - expect(data).notTo(beNil()); - - RACSequence *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:data]; - expect(unarchived).to(equal(sequence)); - }); - - qck_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)); - }); - - qck_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)); - }); - }); -} - -QuickConfigurationEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSequenceSpec.m b/ReactiveCocoaTests/Objective-C/RACSequenceSpec.m deleted file mode 100644 index efbd781b0b..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSequenceSpec.m +++ /dev/null @@ -1,443 +0,0 @@ -// -// RACSequenceSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-11-01. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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" - -QuickSpecBegin(RACSequenceSpec) - -qck_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; - }]; - - qck_itBehavesLike(RACStreamExamples, ^{ - return @{ - RACStreamExamplesClass: RACSequence.class, - RACStreamExamplesVerifyValuesBlock: verifyValues, - RACStreamExamplesInfiniteStream: infiniteSequence - }; - }); -}); - -qck_describe(@"+sequenceWithHeadBlock:tailBlock:", ^{ - __block RACSequence *sequence; - __block BOOL headInvoked; - __block BOOL tailInvoked; - - qck_beforeEach(^{ - headInvoked = NO; - tailInvoked = NO; - - sequence = [RACSequence sequenceWithHeadBlock:^{ - headInvoked = YES; - return @0; - } tailBlock:^{ - tailInvoked = YES; - return [RACSequence return:@1]; - }]; - - expect(sequence).notTo(beNil()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_context(@"behaves like a sequence", ^{ - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: sequence, - RACSequenceExampleExpectedValues: @[ @0, @1 ] - }; - }); - }); -}); - -qck_describe(@"empty sequences", ^{ - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: [RACSequence empty], - RACSequenceExampleExpectedValues: @[] - }; - }); -}); - -qck_describe(@"non-empty sequences", ^{ - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]], - RACSequenceExampleExpectedValues: @[ @0, @1, @2 ] - }; - }); -}); - -qck_describe(@"eager sequences", ^{ - __block RACSequence *lazySequence; - __block BOOL headInvoked; - __block BOOL tailInvoked; - - NSArray *values = @[ @0, @1 ]; - - qck_beforeEach(^{ - headInvoked = NO; - tailInvoked = NO; - - lazySequence = [RACSequence sequenceWithHeadBlock:^{ - headInvoked = YES; - return @0; - } tailBlock:^{ - tailInvoked = YES; - return [RACSequence return:@1]; - }]; - - expect(lazySequence).notTo(beNil()); - }); - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: lazySequence.eagerSequence, - RACSequenceExampleExpectedValues: values - }; - }); - - qck_it(@"should evaluate all values immediately", ^{ - RACSequence *eagerSequence = lazySequence.eagerSequence; - expect(@(headInvoked)).to(beTruthy()); - expect(@(tailInvoked)).to(beTruthy()); - expect(eagerSequence.array).to(equal(values)); - }); -}); - -qck_describe(@"-take:", ^{ - qck_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)); - }); -}); - -qck_describe(@"-bind:", ^{ - qck_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()); - }); -}); - -qck_describe(@"-objectEnumerator", ^{ - qck_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()); - }); - - qck_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)).toEventually(beTruthy()); - - @autoreleasepool { - expect([enumerator nextObject]).to(equal(@3)); - } - expect(@(secondSequenceDeallocd)).toEventually(beTruthy()); - - @autoreleasepool { - expect([enumerator nextObject]).to(beNil()); - } - expect(@(thirdSequenceDeallocd)).toEventually(beTruthy()); - }); -}); - -qck_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 { - (void)[[values.rac_sequence map:^(id value) { - return value; - }] array]; - } - - finished = YES; - }); - - expect(@(finished)).toEventually(beTruthy()); -}); - -qck_describe(@"-foldLeftWithStart:reduce:", ^{ - qck_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)); - }); - - qck_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)); - }); -}); - -qck_describe(@"-foldRightWithStart:reduce:", ^{ - qck_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()); - }); - - qck_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)); - }); - - qck_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)); - }); -}); - -qck_describe(@"-any", ^{ - __block RACSequence *sequence; - qck_beforeEach(^{ - sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - }); - - qck_it(@"should return true when at least one exists", ^{ - BOOL result = [sequence any:^ BOOL (NSNumber *value) { - return value.integerValue > 0; - }]; - expect(@(result)).to(beTruthy()); - }); - - qck_it(@"should return false when no such thing exists", ^{ - BOOL result = [sequence any:^ BOOL (NSNumber *value) { - return value.integerValue == 3; - }]; - expect(@(result)).to(beFalsy()); - }); -}); - -qck_describe(@"-all", ^{ - __block RACSequence *sequence; - qck_beforeEach(^{ - sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - }); - - qck_it(@"should return true when all values pass", ^{ - BOOL result = [sequence all:^ BOOL (NSNumber *value) { - return value.integerValue >= 0; - }]; - expect(@(result)).to(beTruthy()); - }); - - qck_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()); - }); -}); - -qck_describe(@"-objectPassingTest:", ^{ - __block RACSequence *sequence; - qck_beforeEach(^{ - sequence = [[[RACSequence return:@0] concat:[RACSequence return:@1]] concat:[RACSequence return:@2]]; - }); - - qck_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)); - }); - - qck_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()); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSerialDisposableSpec.m b/ReactiveCocoaTests/Objective-C/RACSerialDisposableSpec.m deleted file mode 100644 index 896fd04fed..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSerialDisposableSpec.m +++ /dev/null @@ -1,162 +0,0 @@ -// -// RACSerialDisposableSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-07-22. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACSerialDisposable.h" - -QuickSpecBegin(RACSerialDisposableSpec) - -qck_it(@"should initialize with -init", ^{ - RACSerialDisposable *serial = [[RACSerialDisposable alloc] init]; - expect(serial).notTo(beNil()); - expect(serial.disposable).to(beNil()); -}); - -qck_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()); -}); - -qck_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)); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_it(@"should not crash when racing between swapInDisposable and disposable", ^{ - __block BOOL stop = NO; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long long)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - stop = YES; - }); - - RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - while (!stop) { - [serialDisposable swapInDisposable:[RACDisposable disposableWithBlock:^{}]]; - } - }); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - while (!stop) { - (void)[serialDisposable disposable]; - } - }); - - expect(@(stop)).toEventually(beTruthy()); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSignalSpec.m b/ReactiveCocoaTests/Objective-C/RACSignalSpec.m deleted file mode 100644 index 66c8628d7c..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSignalSpec.m +++ /dev/null @@ -1,4080 +0,0 @@ -// -// RACSignalSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/2/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACPropertySignalExamples.h" -#import "RACSequenceExamples.h" -#import "RACStreamExamples.h" -#import "RACTestObject.h" - -#import -#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 "RACSubject.h" -#import "RACSubscriber+Private.h" -#import "RACSubscriber.h" -#import "RACTestScheduler.h" -#import "RACTuple.h" -#import "RACUnit.h" -#import - -// Set in a beforeAll below. -static NSError *RACSignalTestError; - -static NSString * const RACSignalMergeConcurrentCompletionExampleGroup = @"RACSignalMergeConcurrentCompletionExampleGroup"; -static NSString * const RACSignalMaxConcurrent = @"RACSignalMaxConcurrent"; - -QuickConfigurationBegin(mergeConcurrentCompletionName) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACSignalMergeConcurrentCompletionExampleGroup, ^(QCKDSLSharedExampleContext exampleContext) { - qck_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:[exampleContext()[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()); - }); - }); -} - -QuickConfigurationEnd - -QuickSpecBegin(RACSignalSpec) - -qck_beforeSuite(^{ - // 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]; -}); - -qck_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)).toEventually(beTruthy()); - expect(error).to(beNil()); - expect(collectedValues).to(equal(expectedValues)); - }; - - RACSignal *infiniteSignal = [RACSignal createSignal:^(id subscriber) { - __block volatile int32_t done = 0; - - [RACScheduler.mainThreadScheduler schedule:^{ - while (!done) { - [subscriber sendNext:RACUnit.defaultUnit]; - } - }]; - - return [RACDisposable disposableWithBlock:^{ - OSAtomicIncrement32Barrier(&done); - }]; - }]; - - qck_itBehavesLike(RACStreamExamples, ^{ - return @{ - RACStreamExamplesClass: RACSignal.class, - RACStreamExamplesVerifyValuesBlock: verifyValues, - RACStreamExamplesInfiniteStream: infiniteSignal - }; - }); -}); - -qck_describe(@"-bind:", ^{ - __block RACSubject *signals; - __block BOOL disposed; - __block id lastValue; - __block RACSubject *values; - - qck_beforeEach(^{ - // Tests send a (RACSignal, BOOL) pair that are used below in -bind:. - signals = [RACSubject subject]; - - disposed = NO; - RACSignal *source = [RACSignal createSignal:^(id 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()); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_it(@"should properly stop subscribing to new signals after error", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id subscriber) { - [subscriber sendNext:@0]; - [subscriber sendNext:@1]; - return nil; - }]; - - __block BOOL subscribedAfterError = NO; - RACSignal *bind = [signal bind:^{ - return ^(NSNumber *x, BOOL *stop) { - if (x.integerValue == 0) return [RACSignal error:nil]; - - return [RACSignal defer:^{ - subscribedAfterError = YES; - return [RACSignal empty]; - }]; - }; - }]; - - [bind subscribeCompleted:^{}]; - expect(@(subscribedAfterError)).to(beFalsy()); - }); - - qck_it(@"should not subscribe to signals following error in +merge:", ^{ - __block BOOL firstSubscribed = NO; - __block BOOL secondSubscribed = NO; - __block BOOL errored = NO; - - RACSignal *signal = [[RACSignal - merge:@[ - [RACSignal defer:^{ - firstSubscribed = YES; - return [RACSignal error:nil]; - }], - [RACSignal defer:^{ - secondSubscribed = YES; - return [RACSignal return:nil]; - }] - ]] - doError:^(NSError *error) { - errored = YES; - }]; - - [signal subscribeCompleted:^{}]; - - expect(@(firstSubscribed)).to(beTruthy()); - expect(@(secondSubscribed)).to(beFalsy()); - expect(@(errored)).to(beTruthy()); - }); - - qck_it(@"should not retain signals that are subscribed", ^{ - __weak RACSignal *weakSignal; - @autoreleasepool { - RACSignal *delaySignal = [[RACSignal return:@123] delay:1]; - [[delaySignal map:^id(id value) { - return @456; - }] subscribeNext:^(id x) { - }]; - weakSignal = delaySignal; - } - expect(weakSignal).to(beNil()); - }); -}); - -qck_describe(@"subscribing", ^{ - __block RACSignal *signal = nil; - id nextValueSent = @"1"; - - qck_beforeEach(^{ - signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:nextValueSent]; - [subscriber sendCompleted]; - return nil; - }]; - }); - - qck_it(@"should get next values", ^{ - __block id nextValueReceived = nil; - [signal subscribeNext:^(id x) { - nextValueReceived = x; - } error:^(NSError *error) { - - } completed:^{ - - }]; - - expect(nextValueReceived).to(equal(nextValueSent)); - }); - - qck_it(@"should get completed", ^{ - __block BOOL didGetCompleted = NO; - [signal subscribeNext:^(id x) { - - } error:^(NSError *error) { - - } completed:^{ - didGetCompleted = YES; - }]; - - expect(@(didGetCompleted)).to(beTruthy()); - }); - - qck_it(@"should not get an error", ^{ - __block BOOL didGetError = NO; - [signal subscribeNext:^(id x) { - - } error:^(NSError *error) { - didGetError = YES; - } completed:^{ - - }]; - - expect(@(didGetError)).to(beFalsy()); - }); - - qck_it(@"shouldn't get anything after dispose", ^{ - RACTestScheduler *scheduler = [[RACTestScheduler alloc] init]; - NSMutableArray *receivedValues = [NSMutableArray array]; - - RACSignal *signal = [RACSignal createSignal:^ id (id 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)); - }); - - qck_it(@"should have a current scheduler in didSubscribe block", ^{ - __block RACScheduler *currentScheduler; - RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id 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).toEventuallyNot(beNil()); - }); - - qck_it(@"should automatically dispose of other subscriptions from +createSignal:", ^{ - __block BOOL innerDisposed = NO; - __block id innerSubscriber = nil; - - RACSignal *innerSignal = [RACSignal createSignal:^(id subscriber) { - // Keep the subscriber alive so it doesn't trigger disposal on dealloc - innerSubscriber = subscriber; - return [RACDisposable disposableWithBlock:^{ - innerDisposed = YES; - }]; - }]; - - RACSignal *outerSignal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { - [innerSignal subscribe:subscriber]; - return nil; - }]; - - RACDisposable *disposable = [outerSignal subscribeCompleted:^{}]; - expect(disposable).notTo(beNil()); - expect(@(innerDisposed)).to(beFalsy()); - - [disposable dispose]; - expect(@(innerDisposed)).to(beTruthy()); - }); -}); - -qck_describe(@"-takeUntil:", ^{ - qck_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"]; - }); - - qck_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"]; - }); - - qck_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()); - }); -}); - -qck_describe(@"-takeUntilReplacement:", ^{ - qck_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 ]))); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_it(@"should not forward values from the receiver if both send synchronously", ^{ - RACSignal *receiver = [RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:@1]; - [subscriber sendNext:@2]; - [subscriber sendNext:@3]; - return nil; - }]; - RACSignal *replacement = [RACSignal createSignal:^ RACDisposable * (id 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 ]))); - }); - - qck_it(@"should dispose of the receiver when it's disposed of", ^{ - __block BOOL receiverDisposed = NO; - RACSignal *receiver = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - receiverDisposed = YES; - }]; - }]; - - [[[receiver takeUntilReplacement:RACSignal.never] subscribeCompleted:^{}] dispose]; - - expect(@(receiverDisposed)).to(beTruthy()); - }); - - qck_it(@"should dispose of the replacement signal when it's disposed of", ^{ - __block BOOL replacementDisposed = NO; - RACSignal *replacement = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - replacementDisposed = YES; - }]; - }]; - - [[[RACSignal.never takeUntilReplacement:replacement] subscribeCompleted:^{}] dispose]; - - expect(@(replacementDisposed)).to(beTruthy()); - }); - - qck_it(@"should dispose of the receiver when the replacement signal sends an event", ^{ - __block BOOL receiverDisposed = NO; - __block id receiverSubscriber = nil; - RACSignal *receiver = [RACSignal createSignal:^(id subscriber) { - // Keep the subscriber alive so it doesn't trigger disposal on dealloc - receiverSubscriber = 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()); - }); -}); - -qck_describe(@"disposal", ^{ - qck_it(@"should dispose of the didSubscribe disposable", ^{ - __block BOOL innerDisposed = NO; - RACSignal *signal = [RACSignal createSignal:^(id 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()); - }); - - qck_it(@"should dispose of the didSubscribe disposable asynchronously", ^{ - __block BOOL innerDisposed = NO; - RACSignal *signal = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - innerDisposed = YES; - }]; - }]; - - [[RACScheduler scheduler] schedule:^{ - RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; - [disposable dispose]; - }]; - - expect(@(innerDisposed)).toEventually(beTruthy()); - }); -}); - -qck_describe(@"querying", ^{ - __block RACSignal *signal = nil; - id nextValueSent = @"1"; - - qck_beforeEach(^{ - signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:nextValueSent]; - [subscriber sendNext:@"other value"]; - [subscriber sendCompleted]; - return nil; - }]; - }); - - qck_it(@"should return first 'next' value with -firstOrDefault:success:error:", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id 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()); - }); - - qck_it(@"should return first default value with -firstOrDefault:success:error:", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id 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()); - }); - - qck_it(@"should return error with -firstOrDefault:success:error:", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id 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)); - }); - - qck_it(@"shouldn't crash when returning an error from a background scheduler", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id 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)); - }); - - qck_it(@"should terminate the subscription after returning from -firstOrDefault:success:error:", ^{ - __block BOOL disposed = NO; - RACSignal *signal = [RACSignal createSignal:^(id 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()); - }); - - qck_it(@"should return YES from -waitUntilCompleted: when successful", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id subscriber) { - [subscriber sendNext:RACUnit.defaultUnit]; - [subscriber sendCompleted]; - return nil; - }]; - - __block NSError *error = nil; - expect(@([signal waitUntilCompleted:&error])).to(beTruthy()); - expect(error).to(beNil()); - }); - - qck_it(@"should return NO from -waitUntilCompleted: upon error", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id 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)); - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_it(@"should return a delayed error from -asynchronousFirstOrDefault:success:error:", ^{ - RACSignal *signal = [[RACSignal - createSignal:^(id 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)); - }); - - qck_it(@"should terminate the subscription after returning from -asynchronousFirstOrDefault:success:error:", ^{ - __block BOOL disposed = NO; - RACSignal *signal = [RACSignal createSignal:^(id 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)).toEventually(beTruthy()); - }); - - qck_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()); - }); -}); - -qck_describe(@"continuation", ^{ - qck_it(@"should repeat after completion", ^{ - __block NSUInteger numberOfSubscriptions = 0; - RACScheduler *scheduler = [RACScheduler scheduler]; - - RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id 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)).toEventually(equal(@3)); - expect(@(gotCompleted)).to(beFalsy()); - }); - - qck_it(@"should stop repeating when disposed", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id subscriber) { - [subscriber sendNext:@1]; - [subscriber sendCompleted]; - return nil; - }]; - - NSMutableArray *values = [NSMutableArray array]; - - __block BOOL completed = NO; - __block RACDisposable *disposable = [[[signal - repeat] - subscribeOn:RACScheduler.mainThreadScheduler] - subscribeNext:^(id x) { - [values addObject:x]; - [disposable dispose]; - } completed:^{ - completed = YES; - }]; - - expect(values).toEventually(equal(@[ @1 ])); - expect(@(completed)).to(beFalsy()); - }); - - qck_it(@"should stop repeating when disposed by -take:", ^{ - RACSignal *signal = [RACSignal createSignal:^ id (id 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).toEventually(equal(@[ @1 ])); - expect(@(completed)).to(beTruthy()); - }); -}); - -qck_describe(@"+combineLatestWith:", ^{ - __block RACSubject *subject1 = nil; - __block RACSubject *subject2 = nil; - __block RACSignal *combined = nil; - - qck_beforeEach(^{ - subject1 = [RACSubject subject]; - subject2 = [RACSubject subject]; - combined = [RACSignal combineLatest:@[ subject1, subject2 ]]; - }); - - qck_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"))); - }); - - qck_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"))); - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(signalDeallocd)).toEventually(beTruthy()); - }); - - qck_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"))); - }); - - qck_it(@"should combine the same side-effecting signal", ^{ - __block NSUInteger counter = 0; - RACSignal *sideEffectingSignal = [RACSignal createSignal:^ RACDisposable * (id 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)); - }); -}); - -qck_describe(@"+combineLatest:", ^{ - qck_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"))); - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_describe(@"+combineLatest:reduce:", ^{ - __block RACSubject *subject1; - __block RACSubject *subject2; - __block RACSubject *subject3; - - qck_beforeEach(^{ - subject1 = [RACSubject subject]; - subject2 = [RACSubject subject]; - subject3 = [RACSubject subject]; - }); - - qck_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()); - }); - - qck_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")); - }); - - qck_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")); - }); - - qck_it(@"should handle multiples of the same side-effecting signal", ^{ - __block NSUInteger counter = 0; - RACSignal *sideEffectingSignal = [RACSignal createSignal:^ id (id 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" ])); - }); -}); - -qck_describe(@"distinctUntilChanged", ^{ - qck_it(@"should only send values that are distinct from the previous value", ^{ - RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id 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)); - }); - - qck_it(@"shouldn't consider nils to always be distinct", ^{ - RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id 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)); - }); - - qck_it(@"should consider initial nil to be distinct", ^{ - RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id 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)); - }); -}); - -qck_describe(@"RACObserve", ^{ - __block RACTestObject *testObject; - - qck_beforeEach(^{ - testObject = [[RACTestObject alloc] init]; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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")); - }); -}); - -qck_describe(@"-setKeyPath:onObject:", ^{ - id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { - [signal setKeyPath:keyPath onObject:testObject nilValue:nilValue]; - }; - - qck_itBehavesLike(RACPropertySignalExamples, ^{ - return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; - }); - - qck_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]; - }); - - qck_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]; - }); - - qck_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)); - }); - - qck_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)); - }); -}); - -qck_describe(@"memory management", ^{ - qck_it(@"should dealloc signals if the signal does nothing", ^{ - __block BOOL deallocd = NO; - @autoreleasepool { - RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id subscriber) { - return nil; - }]; - - [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocd = YES; - }]]; - } - - expect(@(deallocd)).toEventually(beTruthy()); - }); - - qck_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 subscriber) { - [subscriber sendCompleted]; - return nil; - }]; - - [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocd = YES; - }]]; - - [signal subscribeCompleted:^{ - done = YES; - }]; - - expect(@(done)).toEventually(beTruthy()); - } - - expect(@(deallocd)).toEventually(beTruthy()); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(deallocd)).toEventually(beTruthy()); - }); - - qck_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 subscriber) { - [subscriber sendCompleted]; - return nil; - }]; - - [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocd = YES; - }]]; - - [signal subscribeCompleted:^{ - completed = YES; - }]; - }]; - } - - expect(@(completed)).toEventually(beTruthy()); - expect(@(deallocd)).toEventually(beTruthy()); - }); - - qck_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; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - @autoreleasepool { - [[RACScheduler scheduler] schedule:^{ - RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id subscriber) { - return nil; - }]; - - [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ - deallocd = YES; - dispatch_semaphore_signal(semaphore); - }]]; - - [NSThread sleepForTimeInterval:1]; - - expect(@(deallocd)).to(beFalsy()); - }]; - } - - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - expect(@(deallocd)).to(beTruthy()); - }); - - qck_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 { - RACSignal *intermediateSignal = [subject doNext:^(id _) { - gotNext = YES; - }]; - - expect(intermediateSignal).notTo(beNil()); - - disposable = [intermediateSignal subscribeCompleted:^{ - completed = YES; - }]; - } - - [subject sendNext:@5]; - expect(@(gotNext)).to(beTruthy()); - - [subject sendCompleted]; - expect(@(completed)).to(beTruthy()); - - [disposable dispose]; - }); -}); - -qck_describe(@"-merge:", ^{ - __block RACSubject *sub1; - __block RACSubject *sub2; - __block RACSignal *merged; - qck_beforeEach(^{ - sub1 = [RACSubject subject]; - sub2 = [RACSubject subject]; - merged = [sub1 merge:sub2]; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); -}); - -qck_describe(@"+merge:", ^{ - __block RACSubject *sub1; - __block RACSubject *sub2; - __block RACSignal *merged; - qck_beforeEach(^{ - sub1 = [RACSubject subject]; - sub2 = [RACSubject subject]; - merged = [RACSignal merge:@[ sub1, sub2 ].objectEnumerator]; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_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; - - qck_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]; - }); - - qck_describe(@"when its max is 0", ^{ - qck_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)); - }); - - qck_itBehavesLike(RACSignalMergeConcurrentCompletionExampleGroup, ^{ - return @{ RACSignalMaxConcurrent: @0 }; - }); - }); - - qck_describe(@"when its max is > 0", ^{ - qck_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)); - }); - - qck_itBehavesLike(RACSignalMergeConcurrentCompletionExampleGroup, ^{ - return @{ RACSignalMaxConcurrent: @1 }; - }); - }); - - qck_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)).toEventually(beTruthy()); - expect(@(signalDeallocd)).toEventually(beTruthy()); - }); - - qck_it(@"should not crash when disposing while subscribing", ^{ - RACDisposable *disposable = [[signalsSubject flatten:0] subscribeCompleted:^{ - }]; - - [signalsSubject sendNext:[RACSignal createSignal:^ RACDisposable * (id subscriber) { - [disposable dispose]; - [subscriber sendCompleted]; - return nil; - }]]; - - [signalsSubject sendCompleted]; - }); - - qck_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 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)).toEventually(beTruthy()); - }); - - qck_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 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)).toEventually(beTruthy()); - } - }); -}); - -qck_describe(@"-switchToLatest", ^{ - __block RACSubject *subject; - - __block NSMutableArray *values; - __block NSError *lastError = nil; - __block BOOL completed = NO; - - qck_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; - }]; - }); - - qck_it(@"should send values from the most recent signal", ^{ - [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:@1]; - [subscriber sendNext:@2]; - return nil; - }]]; - - [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:@3]; - [subscriber sendNext:@4]; - return nil; - }]]; - - NSArray *expected = @[ @1, @2, @3, @4 ]; - expect(values).to(equal(expected)); - }); - - qck_it(@"should send errors from the most recent signal", ^{ - [subject sendNext:[RACSignal createSignal:^ id (id subscriber) { - [subscriber sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; - return nil; - }]]; - - expect(lastError).notTo(beNil()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_it(@"should accept nil signals", ^{ - [subject sendNext:nil]; - [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:@1]; - [subscriber sendNext:@2]; - return nil; - }]]; - - NSArray *expected = @[ @1, @2 ]; - expect(values).to(equal(expected)); - }); - - qck_it(@"should return a cold signal", ^{ - __block NSUInteger subscriptions = 0; - RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id 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)); - }); -}); - -qck_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; - - qck_beforeEach(^{ - keySubject = [RACSubject subject]; - - subjectZero = [RACSubject subject]; - subjectOne = [RACSubject subject]; - subjectTwo = [RACSubject subject]; - - defaultSubject = [RACSubject subject]; - - values = [NSMutableArray array]; - lastError = nil; - completed = NO; - }); - - qck_describe(@"switching between values with a default", ^{ - __block RACSignal *switchSignal; - - qck_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; - }]; - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - }); - - qck_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" ]))); - }); - - qck_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))); - }); - - qck_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" ])); - }); -}); - -qck_describe(@"+if:then:else", ^{ - __block RACSubject *boolSubject; - __block RACSubject *trueSubject; - __block RACSubject *falseSubject; - - __block NSMutableArray *values; - __block NSError *lastError = nil; - __block BOOL completed = NO; - - qck_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; - }]; - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_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; - - qck_beforeEach(^{ - 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.1; - - expect(@(currentTime - startTime)).to(beGreaterThanOrEqualTo(@(expectedMinInterval))); - expect(@(currentTime - startTime)).to(beLessThanOrEqualTo(@(expectedMaxInterval))); - }]; - - expect(@(nextsReceived)).toEventually(equal(@3)); - } copy]; - }); - - qck_describe(@"+interval:onScheduler:", ^{ - qck_it(@"should work on the main thread scheduler", ^{ - testTimer([RACSignal interval:interval onScheduler:RACScheduler.mainThreadScheduler], @(interval), @0); - }); - - qck_it(@"should work on a background scheduler", ^{ - testTimer([RACSignal interval:interval onScheduler:[RACScheduler scheduler]], @(interval), @0); - }); - }); - - qck_describe(@"+interval:onScheduler:withLeeway:", ^{ - qck_it(@"should work on the main thread scheduler", ^{ - testTimer([RACSignal interval:interval onScheduler:RACScheduler.mainThreadScheduler withLeeway:leeway], @(interval), @(leeway)); - }); - - qck_it(@"should work on a background scheduler", ^{ - testTimer([RACSignal interval:interval onScheduler:[RACScheduler scheduler] withLeeway:leeway], @(interval), @(leeway)); - }); - }); -}); - -qck_describe(@"-timeout:onScheduler:", ^{ - __block RACSubject *subject; - - qck_beforeEach(^{ - subject = [RACSubject subject]; - }); - - qck_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).toEventuallyNot(beNil()); - expect(receivedError.domain).to(equal(RACSignalErrorDomain)); - expect(@(receivedError.code)).to(equal(@(RACSignalErrorTimedOut))); - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_describe(@"-delay:", ^{ - __block RACSubject *subject; - __block RACSignal *delayedSignal; - - qck_beforeEach(^{ - subject = [RACSubject subject]; - delayedSignal = [subject delay:0]; - }); - - qck_it(@"should delay nexts", ^{ - __block id next = nil; - [delayedSignal subscribeNext:^(id x) { - next = x; - }]; - - [subject sendNext:@"foo"]; - expect(next).to(beNil()); - expect(next).toEventually(equal(@"foo")); - }); - - qck_it(@"should delay completed", ^{ - __block BOOL completed = NO; - [delayedSignal subscribeCompleted:^{ - completed = YES; - }]; - - [subject sendCompleted]; - expect(@(completed)).to(beFalsy()); - expect(@(completed)).toEventually(beTruthy()); - }); - - qck_it(@"should not delay errors", ^{ - __block NSError *error = nil; - [delayedSignal subscribeError:^(NSError *e) { - error = e; - }]; - - [subject sendError:RACSignalTestError]; - expect(error).to(equal(RACSignalTestError)); - }); - - qck_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)).toEventually(beTruthy()); - expect(next).to(beNil()); - }); -}); - -qck_describe(@"-catch:", ^{ - qck_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)); - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_it(@"should dispose ensuing signal", ^{ - RACSubject *subject = [RACSubject subject]; - - __block BOOL disposed = NO; - RACSignal *signal = [subject catch:^(NSError *error) { - return [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - }]; - }]; - - RACDisposable *disposable = [signal subscribeCompleted:^{}]; - [subject sendError:RACSignalTestError]; - [disposable dispose]; - - expect(@(disposed)).toEventually(beTruthy()); - }); -}); - -qck_describe(@"+try:", ^{ - __block id value; - __block NSError *receivedError; - - qck_beforeEach(^{ - value = nil; - receivedError = nil; - }); - - qck_it(@"should pass the value if it is non-nil", ^{ - RACSignal *signal = [RACSignal try:^(NSError **error) { - return @"foo"; - }]; - - [signal subscribeNext:^(id x) { - value = x; - } error:^(NSError *error) { - receivedError = error; - }]; - - expect(value).to(equal(@"foo")); - expect(receivedError).to(beNil()); - }); - - qck_it(@"should ignore the error if the value is non-nil", ^{ - RACSignal *signal = [RACSignal try:^(NSError **error) { - if (error != nil) *error = RACSignalTestError; - - return @"foo"; - }]; - - [signal subscribeNext:^(id x) { - value = x; - } error:^(NSError *error) { - receivedError = error; - }]; - - expect(receivedError).to(beNil()); - expect(value).to(equal(@"foo")); - }); - - qck_it(@"should send the error if the return value is nil", ^{ - RACSignal *signal = [RACSignal try:^id(NSError **error) { - if (error) *error = RACSignalTestError; - - return nil; - }]; - - [signal subscribeNext:^(id x) { - value = x; - } error:^(NSError *error) { - receivedError = error; - }]; - - expect(value).to(beNil()); - expect(receivedError).to(equal(RACSignalTestError)); - }); -}); - -qck_describe(@"-try:", ^{ - __block RACSubject *subject; - __block NSError *receivedError; - __block NSMutableArray *nextValues; - __block BOOL completed; - - qck_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; - }]; - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_describe(@"-tryMap:", ^{ - __block RACSubject *subject; - __block NSError *receivedError; - __block NSMutableArray *nextValues; - __block BOOL completed; - - qck_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; - }]; - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_describe(@"throttling", ^{ - __block RACSubject *subject; - - qck_beforeEach(^{ - subject = [RACSubject subject]; - }); - - qck_describe(@"-throttle:", ^{ - __block RACSignal *throttledSignal; - - qck_beforeEach(^{ - throttledSignal = [subject throttle:0]; - }); - - qck_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).toEventually(equal(expected)); - - [subject sendNext:@"buzz"]; - expect(valuesReceived).to(equal(expected)); - - expected = @[ @"bar", @"buzz" ]; - expect(valuesReceived).toEventually(equal(expected)); - }); - - qck_it(@"should forward completed immediately", ^{ - __block BOOL completed = NO; - [throttledSignal subscribeCompleted:^{ - completed = YES; - }]; - - [subject sendCompleted]; - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should forward errors immediately", ^{ - __block NSError *error = nil; - [throttledSignal subscribeError:^(NSError *e) { - error = e; - }]; - - [subject sendError:RACSignalTestError]; - expect(error).to(equal(RACSignalTestError)); - }); - - qck_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)).toEventually(beTruthy()); - expect(next).to(beNil()); - }); - }); - - qck_describe(@"-throttle:valuesPassingTest:", ^{ - __block RACSignal *throttledSignal; - __block BOOL shouldThrottle; - - qck_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()); - }); - - qck_describe(@"nexts", ^{ - __block NSMutableArray *valuesReceived; - __block NSMutableArray *expected; - - qck_beforeEach(^{ - expected = [[NSMutableArray alloc] init]; - valuesReceived = [[NSMutableArray alloc] init]; - - [throttledSignal subscribeNext:^(id x) { - [valuesReceived addObject:x]; - }]; - }); - - qck_it(@"should forward unthrottled values immediately", ^{ - shouldThrottle = NO; - [subject sendNext:@"foo"]; - - [expected addObject:@"foo"]; - expect(valuesReceived).to(equal(expected)); - }); - - qck_it(@"should delay throttled values", ^{ - [subject sendNext:@"bar"]; - expect(valuesReceived).to(equal(expected)); - - [expected addObject:@"bar"]; - expect(valuesReceived).toEventually(equal(expected)); - }); - - qck_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).toEventually(equal(expected)); - }); - - qck_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).toEventually(equal(expected)); - }); - - qck_it(@"should not be resent upon completion", ^{ - [subject sendNext:@"bar"]; - [expected addObject:@"bar"]; - expect(valuesReceived).toEventually(equal(expected)); - - [subject sendCompleted]; - expect(valuesReceived).to(equal(expected)); - }); - }); - - qck_it(@"should forward completed immediately", ^{ - __block BOOL completed = NO; - [throttledSignal subscribeCompleted:^{ - completed = YES; - }]; - - [subject sendCompleted]; - expect(@(completed)).to(beTruthy()); - }); - - qck_it(@"should forward errors immediately", ^{ - __block NSError *error = nil; - [throttledSignal subscribeError:^(NSError *e) { - error = e; - }]; - - [subject sendError:RACSignalTestError]; - expect(error).to(equal(RACSignalTestError)); - }); - - qck_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)).toEventually(beTruthy()); - expect(next).to(beNil()); - }); - }); -}); - -qck_describe(@"-then:", ^{ - qck_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)); - }); - - qck_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)); - }); -}); - -qck_describe(@"-sequence", ^{ - RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) { - [subscriber sendNext:@1]; - [subscriber sendNext:@2]; - [subscriber sendNext:@3]; - [subscriber sendNext:@4]; - [subscriber sendCompleted]; - return nil; - }]; - - qck_itBehavesLike(RACSequenceExamples, ^{ - return @{ - RACSequenceExampleSequence: signal.sequence, - RACSequenceExampleExpectedValues: @[ @1, @2, @3, @4 ] - }; - }); -}); - -qck_it(@"should complete take: even if the original signal doesn't", ^{ - RACSignal *sendOne = [RACSignal createSignal:^ RACDisposable * (id 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()); -}); - -qck_it(@"should complete take: even if the signal is recursive", ^{ - RACSubject *subject = [RACSubject subject]; - const NSUInteger number = 3; - const NSUInteger guard = number + 1; - - NSMutableArray *values = NSMutableArray.array; - __block BOOL completed = NO; - - [[subject take:number] subscribeNext:^(NSNumber* received) { - [values addObject:received]; - if (values.count >= guard) { - [subject sendError:RACSignalTestError]; - } - [subject sendNext:@(received.integerValue + 1)]; - } completed:^{ - completed = YES; - }]; - [subject sendNext:@0]; - - NSMutableArray* expectedValues = [NSMutableArray arrayWithCapacity:number]; - for (NSUInteger i = 0 ; i < number ; ++i) { - [expectedValues addObject:@(i)]; - } - - expect(values).to(equal(expectedValues)); - expect(@(completed)).to(beTruthy()); -}); - -qck_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; - - qck_beforeEach(^{ - 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; - }]; - }); - - qck_afterEach(^{ - [disposable dispose]; - }); - - qck_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()); - }); - - qck_it(@"outcome should not be dependent on order of signals", ^{ - [subject2 sendCompleted]; - expect(@(hasSentCompleted)).to(beTruthy()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_it(@"should handle multiples of the same side-effecting signal", ^{ - __block NSUInteger counter = 0; - RACSignal *sideEffectingSignal = [RACSignal createSignal:^ RACDisposable * (id 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" ])); - }); -}); - -qck_describe(@"-sample:", ^{ - qck_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)); - }); -}); - -qck_describe(@"-collect", ^{ - __block RACSubject *subject; - __block RACSignal *collected; - - __block id value; - __block BOOL hasCompleted; - - qck_beforeEach(^{ - subject = [RACSubject subject]; - collected = [subject collect]; - - value = nil; - hasCompleted = NO; - - [collected subscribeNext:^(id x) { - value = x; - } completed:^{ - hasCompleted = YES; - }]; - }); - - qck_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()); - }); - - qck_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()); - }); -}); - -qck_describe(@"-bufferWithTime:onScheduler:", ^{ - __block RACTestScheduler *scheduler; - - __block RACSubject *input; - __block RACSignal *bufferedInput; - __block RACTuple *latestValue; - - qck_beforeEach(^{ - scheduler = [[RACTestScheduler alloc] init]; - - input = [RACSubject subject]; - bufferedInput = [input bufferWithTime:1 onScheduler:scheduler]; - latestValue = nil; - - [bufferedInput subscribeNext:^(RACTuple *x) { - latestValue = x; - }]; - }); - - qck_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))); - }); - - qck_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))); - }); - - qck_it(@"should flush any buffered nexts upon completion", ^{ - [input sendNext:@1]; - [input sendCompleted]; - [scheduler stepAll]; - expect(latestValue).to(equal(RACTuplePack(@1))); - }); - - qck_it(@"should support NSNull values", ^{ - [input sendNext:NSNull.null]; - [scheduler stepAll]; - expect(latestValue).to(equal(RACTuplePack(NSNull.null))); - }); - - qck_it(@"should buffer nil values", ^{ - [input sendNext:nil]; - [scheduler stepAll]; - expect(latestValue).to(equal(RACTuplePack(nil))); - }); -}); - -qck_describe(@"-concat", ^{ - __block RACSubject *subject; - - __block RACSignal *oneSignal; - __block RACSignal *twoSignal; - __block RACSignal *threeSignal; - - __block RACSignal *errorSignal; - __block RACSignal *completedSignal; - - qck_beforeEach(^{ - subject = [RACReplaySubject subject]; - - oneSignal = [RACSignal return:@1]; - twoSignal = [RACSignal return:@2]; - threeSignal = [RACSignal return:@3]; - - errorSignal = [RACSignal error:RACSignalTestError]; - completedSignal = RACSignal.empty; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_it(@"should pass through errors", ^{ - [subject sendNext:errorSignal]; - - NSError *error = nil; - [[subject concat] firstOrDefault:nil success:NULL error:&error]; - expect(error).to(equal(RACSignalTestError)); - }); - - qck_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).toEventually(equal(expected)); - - [subject sendNext:threeSignal]; - - expected = @[ @1, @2, @3 ]; - expect(values).to(equal(expected)); - }); - - qck_it(@"should dispose the current signal", ^{ - __block BOOL disposed = NO; - __block id innerSubscriber = nil; - RACSignal *innerSignal = [RACSignal createSignal:^(id subscriber) { - // Keep the subscriber alive so it doesn't trigger disposal on dealloc - innerSubscriber = subscriber; - return [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - }]; - - RACDisposable *concatDisposable = [[subject concat] subscribeCompleted:^{}]; - - [subject sendNext:innerSignal]; - expect(@(disposed)).notTo(beTruthy()); - - [concatDisposable dispose]; - expect(@(disposed)).to(beTruthy()); - }); - - qck_it(@"should dispose later signals", ^{ - __block BOOL disposed = NO; - __block id laterSubscriber = nil; - RACSignal *laterSignal = [RACSignal createSignal:^(id subscriber) { - // Keep the subscriber alive so it doesn't trigger disposal on dealloc - laterSubscriber = subscriber; - return [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - }]; - - RACSubject *firstSignal = [RACSubject subject]; - RACSignal *outerSignal = [RACSignal createSignal:^ id (id 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()); - }); -}); - -qck_describe(@"-initially:", ^{ - __block RACSubject *subject; - - __block NSUInteger initiallyInvokedCount; - __block RACSignal *signal; - - qck_beforeEach(^{ - subject = [RACSubject subject]; - - initiallyInvokedCount = 0; - signal = [subject initially:^{ - ++initiallyInvokedCount; - }]; - }); - - qck_it(@"should not run without a subscription", ^{ - [subject sendCompleted]; - expect(@(initiallyInvokedCount)).to(equal(@0)); - }); - - qck_it(@"should run on subscription", ^{ - [signal subscribe:[RACSubscriber new]]; - expect(@(initiallyInvokedCount)).to(equal(@1)); - }); - - qck_it(@"should re-run for each subscription", ^{ - [signal subscribe:[RACSubscriber new]]; - [signal subscribe:[RACSubscriber new]]; - expect(@(initiallyInvokedCount)).to(equal(@2)); - }); -}); - -qck_describe(@"-finally:", ^{ - __block RACSubject *subject; - - __block BOOL finallyInvoked; - __block RACSignal *signal; - - qck_beforeEach(^{ - subject = [RACSubject subject]; - - finallyInvoked = NO; - signal = [subject finally:^{ - finallyInvoked = YES; - }]; - }); - - qck_it(@"should not run finally without a subscription", ^{ - [subject sendCompleted]; - expect(@(finallyInvoked)).to(beFalsy()); - }); - - qck_describe(@"with a subscription", ^{ - __block RACDisposable *disposable; - - qck_beforeEach(^{ - disposable = [signal subscribeCompleted:^{}]; - }); - - qck_afterEach(^{ - [disposable dispose]; - }); - - qck_it(@"should not run finally upon next", ^{ - [subject sendNext:RACUnit.defaultUnit]; - expect(@(finallyInvoked)).to(beFalsy()); - }); - - qck_it(@"should run finally upon completed", ^{ - [subject sendCompleted]; - expect(@(finallyInvoked)).to(beTruthy()); - }); - - qck_it(@"should run finally upon error", ^{ - [subject sendError:nil]; - expect(@(finallyInvoked)).to(beTruthy()); - }); - }); -}); - -qck_describe(@"-ignoreValues", ^{ - __block RACSubject *subject; - - __block BOOL gotNext; - __block BOOL gotCompleted; - __block NSError *receivedError; - - qck_beforeEach(^{ - subject = [RACSubject subject]; - - gotNext = NO; - gotCompleted = NO; - receivedError = nil; - - [[subject ignoreValues] subscribeNext:^(id _) { - gotNext = YES; - } error:^(NSError *error) { - receivedError = error; - } completed:^{ - gotCompleted = YES; - }]; - }); - - qck_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()); - }); - - qck_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)); - }); -}); - -qck_describe(@"-materialize", ^{ - qck_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)); - }); - - qck_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)); - }); -}); - -qck_describe(@"-dematerialize", ^{ - qck_it(@"should convert nexts from RACEvents", ^{ - RACSignal *events = [RACSignal createSignal:^ id (id 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)); - }); - - qck_it(@"should convert completed from a RACEvent", ^{ - RACSignal *events = [RACSignal createSignal:^ id (id 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)); - }); - - qck_it(@"should convert error from a RACEvent", ^{ - RACSignal *events = [RACSignal createSignal:^ id (id 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)); - }); -}); - -qck_describe(@"-not", ^{ - qck_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)); - }); -}); - -qck_describe(@"-and", ^{ - qck_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)); - }); -}); - -qck_describe(@"-or", ^{ - qck_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)); - }); -}); - -qck_describe(@"-groupBy:", ^{ - qck_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))); - }); - - qck_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))); - }); - - - qck_it(@"should send completed in the order grouped signals were created.", ^{ - RACSubject *subject = [RACReplaySubject subject]; - - NSMutableArray *startedSignals = [NSMutableArray array]; - NSMutableArray *completedSignals = [NSMutableArray array]; - [[subject groupBy:^(NSNumber *number) { - return @(number.integerValue % 4); - }] subscribeNext:^(RACGroupedSignal *groupedSignal) { - [startedSignals addObject:groupedSignal]; - - [groupedSignal subscribeCompleted:^{ - [completedSignals addObject:groupedSignal]; - }]; - }]; - - for (NSInteger i = 0; i < 20; i++) - { - [subject sendNext:@(i)]; - } - [subject sendCompleted]; - - expect(completedSignals).to(equal(startedSignals)); - }); -}); - -qck_describe(@"starting signals", ^{ - qck_describe(@"+startLazilyWithScheduler:block:", ^{ - __block NSUInteger invokedCount = 0; - __block void (^subscribe)(void); - - qck_beforeEach(^{ - invokedCount = 0; - - RACSignal *signal = [RACSignal startLazilyWithScheduler:RACScheduler.immediateScheduler block:^(id subscriber) { - invokedCount++; - [subscriber sendNext:@42]; - [subscriber sendCompleted]; - }]; - - subscribe = [^{ - [signal subscribe:[RACSubscriber subscriberWithNext:nil error:nil completed:nil]]; - } copy]; - }); - - qck_it(@"should only invoke the block on subscription", ^{ - expect(@(invokedCount)).to(equal(@0)); - subscribe(); - expect(@(invokedCount)).to(equal(@1)); - }); - - qck_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)); - }); - - qck_it(@"should invoke the block on the given scheduler", ^{ - RACScheduler *scheduler = [RACScheduler scheduler]; - __block RACScheduler *currentScheduler; - [[[RACSignal - startLazilyWithScheduler:scheduler block:^(id subscriber) { - currentScheduler = RACScheduler.currentScheduler; - }] - publish] - connect]; - - expect(currentScheduler).toEventually(equal(scheduler)); - }); - }); - - qck_describe(@"+startEagerlyWithScheduler:block:", ^{ - qck_it(@"should immediately invoke the block", ^{ - __block BOOL blockInvoked = NO; - [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id subscriber) { - blockInvoked = YES; - }]; - - expect(@(blockInvoked)).toEventually(beTruthy()); - }); - - qck_it(@"should only invoke the block once", ^{ - __block NSUInteger invokedCount = 0; - RACSignal *signal = [RACSignal startEagerlyWithScheduler:RACScheduler.immediateScheduler block:^(id subscriber) { - invokedCount++; - }]; - - expect(@(invokedCount)).to(equal(@1)); - - [[signal publish] connect]; - expect(@(invokedCount)).to(equal(@1)); - - [[signal publish] connect]; - expect(@(invokedCount)).to(equal(@1)); - }); - - qck_it(@"should invoke the block on the given scheduler", ^{ - RACScheduler *scheduler = [RACScheduler scheduler]; - __block RACScheduler *currentScheduler; - [RACSignal startEagerlyWithScheduler:scheduler block:^(id subscriber) { - currentScheduler = RACScheduler.currentScheduler; - }]; - - expect(currentScheduler).toEventually(equal(scheduler)); - }); - }); -}); - -qck_describe(@"-toArray", ^{ - __block RACSubject *subject; - - qck_beforeEach(^{ - subject = [RACReplaySubject subject]; - }); - - qck_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)); - }); - - qck_it(@"should return nil upon error", ^{ - [subject sendError:nil]; - expect([subject toArray]).to(beNil()); - }); - - qck_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()); - }); -}); - -qck_describe(@"-ignore:", ^{ - qck_it(@"should ignore nil", ^{ - RACSignal *signal = [[RACSignal - createSignal:^ id (id 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)); - }); -}); - -qck_describe(@"-replayLazily", ^{ - __block NSUInteger subscriptionCount; - __block BOOL disposed; - - __block RACSignal *signal; - __block RACSubject *disposeSubject; - __block RACSignal *replayedSignal; - - qck_beforeEach(^{ - subscriptionCount = 0; - disposed = NO; - - signal = [RACSignal createSignal:^ RACDisposable * (id 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]; - }); - - qck_it(@"should forward the input signal upon subscription", ^{ - expect(@(subscriptionCount)).to(equal(@0)); - - expect(@([replayedSignal asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy()); - expect(@(subscriptionCount)).to(equal(@1)); - }); - - qck_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)); - }); - - qck_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))); - }); -}); - -qck_describe(@"-reduceApply", ^{ - qck_it(@"should apply a block to the rest of a tuple", ^{ - RACSubject *subject = [RACReplaySubject subject]; - - id sum = ^(NSNumber *a, NSNumber *b) { - return @(a.intValue + b.intValue); - }; - id madd = ^(NSNumber *a, NSNumber *b, NSNumber *c) { - return @(a.intValue * b.intValue + c.intValue); - }; - - [subject sendNext:RACTuplePack(sum, @1, @2)]; - [subject sendNext:RACTuplePack(madd, @2, @3, @1)]; - [subject sendCompleted]; - - NSArray *results = [[subject reduceApply] toArray]; - NSArray *expected = @[ @3, @7 ]; - - expect(results).to(equal(expected)); - }); -}); - -describe(@"-deliverOnMainThread", ^{ - void (^dispatchSyncInBackground)(dispatch_block_t) = ^(dispatch_block_t block) { - dispatch_group_t group = dispatch_group_create(); - dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block); - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - }; - - beforeEach(^{ - expect(@(NSThread.isMainThread)).to(beTruthy()); - }); - - it(@"should deliver events immediately when on the main thread", ^{ - RACSubject *subject = [RACSubject subject]; - NSMutableArray *values = [NSMutableArray array]; - - [[subject deliverOnMainThread] subscribeNext:^(id value) { - [values addObject:value]; - }]; - - [subject sendNext:@0]; - expect(values).to(equal(@[ @0 ])); - - [subject sendNext:@1]; - [subject sendNext:@2]; - expect(values).to(equal(@[ @0, @1, @2 ])); - }); - - it(@"should enqueue events sent from the background", ^{ - RACSubject *subject = [RACSubject subject]; - NSMutableArray *values = [NSMutableArray array]; - - [[subject deliverOnMainThread] subscribeNext:^(id value) { - [values addObject:value]; - }]; - - dispatchSyncInBackground(^{ - [subject sendNext:@0]; - }); - - expect(values).to(equal(@[])); - expect(values).toEventually(equal(@[ @0 ])); - - dispatchSyncInBackground(^{ - [subject sendNext:@1]; - [subject sendNext:@2]; - }); - - expect(values).to(equal(@[ @0 ])); - expect(values).toEventually(equal(@[ @0, @1, @2 ])); - }); - - it(@"should enqueue events sent from the main thread after events from the background", ^{ - RACSubject *subject = [RACSubject subject]; - NSMutableArray *values = [NSMutableArray array]; - - [[subject deliverOnMainThread] subscribeNext:^(id value) { - [values addObject:value]; - }]; - - dispatchSyncInBackground(^{ - [subject sendNext:@0]; - }); - - [subject sendNext:@1]; - [subject sendNext:@2]; - - expect(values).to(equal(@[])); - expect(values).toEventually(equal(@[ @0, @1, @2 ])); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACStreamExamples.h b/ReactiveCocoaTests/Objective-C/RACStreamExamples.h deleted file mode 100644 index 318a6a85e8..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACStreamExamples.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACStreamExamples.m b/ReactiveCocoaTests/Objective-C/RACStreamExamples.m deleted file mode 100644 index 7e23f50e67..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACStreamExamples.m +++ /dev/null @@ -1,670 +0,0 @@ -// -// RACStreamExamples.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-11-01. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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"; - -QuickConfigurationBegin(RACStreamExampleGroups) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACStreamExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block Class streamClass; - __block void (^verifyValues)(RACStream *, NSArray *); - __block RACStream *infiniteStream; - - __block RACStream *(^streamWithValues)(NSArray *); - - qck_beforeEach(^{ - streamClass = exampleContext()[RACStreamExamplesClass]; - verifyValues = exampleContext()[RACStreamExamplesVerifyValuesBlock]; - infiniteStream = exampleContext()[RACStreamExamplesInfiniteStream]; - streamWithValues = [^(NSArray *values) { - RACStream *stream = [streamClass empty]; - - for (id value in values) { - stream = [stream concat:[streamClass return:value]]; - } - - return stream; - } copy]; - }); - - qck_it(@"should return an empty stream", ^{ - RACStream *stream = [streamClass empty]; - verifyValues(stream, @[]); - }); - - qck_it(@"should lift a value into a stream", ^{ - RACStream *stream = [streamClass return:RACUnit.defaultUnit]; - verifyValues(stream, @[ RACUnit.defaultUnit ]); - }); - - qck_describe(@"-concat:", ^{ - qck_it(@"should concatenate two streams", ^{ - RACStream *stream = [[streamClass return:@0] concat:[streamClass return:@1]]; - verifyValues(stream, @[ @0, @1 ]); - }); - - qck_it(@"should concatenate three streams", ^{ - RACStream *stream = [[[streamClass return:@0] concat:[streamClass return:@1]] concat:[streamClass return:@2]]; - verifyValues(stream, @[ @0, @1, @2 ]); - }); - - qck_it(@"should concatenate around an empty stream", ^{ - RACStream *stream = [[[streamClass return:@0] concat:[streamClass empty]] concat:[streamClass return:@2]]; - verifyValues(stream, @[ @0, @2 ]); - }); - }); - - qck_it(@"should flatten", ^{ - RACStream *stream = [[streamClass return:[streamClass return:RACUnit.defaultUnit]] flatten]; - verifyValues(stream, @[ RACUnit.defaultUnit ]); - }); - - qck_describe(@"-bind:", ^{ - qck_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 ]); - }); - - qck_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 ]); - }); - - qck_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 ]); - }); - - qck_it(@"should terminate immediately when returning nil", ^{ - RACStream *stream = [infiniteStream bind:^{ - return ^ id (id _, BOOL *stop) { - return nil; - }; - }]; - - verifyValues(stream, @[]); - }); - - qck_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 ]); - }); - - qck_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, @[]); - }); - - qck_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 ]); - }); - - qck_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 ]); - }); - }); - - qck_describe(@"-flattenMap:", ^{ - qck_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 ]); - }); - - qck_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 ]); - }); - - qck_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 ]); - }); - - qck_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 ]); - }); - }); - - qck_it(@"should map", ^{ - RACStream *baseStream = streamWithValues(@[ @0, @1, @2 ]); - RACStream *stream = [baseStream map:^(NSNumber *value) { - return @(value.integerValue + 1); - }]; - - verifyValues(stream, @[ @1, @2, @3 ]); - }); - - qck_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 ]); - }); - - qck_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 ]); - }); - - qck_describe(@"-ignore:", ^{ - qck_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 ]); - }); - - qck_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" ]); - }); - }); - - qck_it(@"should start with a value", ^{ - RACStream *stream = [[streamClass return:@1] startWith:@0]; - verifyValues(stream, @[ @0, @1 ]); - }); - - qck_describe(@"-skip:", ^{ - __block NSArray *values; - __block RACStream *stream; - - qck_beforeEach(^{ - values = @[ @0, @1, @2 ]; - stream = streamWithValues(values); - }); - - qck_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)]); - } - }); - - qck_it(@"should return an empty stream when skipping too many values", ^{ - verifyValues([stream skip:4], @[]); - }); - }); - - qck_describe(@"-take:", ^{ - qck_describe(@"with three values", ^{ - __block NSArray *values; - __block RACStream *stream; - - qck_beforeEach(^{ - values = @[ @0, @1, @2 ]; - stream = streamWithValues(values); - }); - - qck_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)]); - } - }); - - qck_it(@"should return the same stream when taking too many values", ^{ - verifyValues([stream take:4], values); - }); - }); - - qck_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 ]); - }); - - qck_it(@"should take and terminate from a single-item stream", ^{ - NSArray *values = @[ RACUnit.defaultUnit ]; - RACStream *stream = streamWithValues(values); - verifyValues([stream take:1], values); - }); - }); - - qck_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; - - qck_beforeEach(^{ - 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]), - ]; - }); - - qck_describe(@"-zipWith:", ^{ - qck_it(@"should make a stream of tuples", ^{ - RACStream *stream = [streamOne zipWith:streamTwo]; - verifyValues(stream, twoStreamTuples); - }); - - qck_it(@"should truncate streams", ^{ - RACStream *shortStream = streamWithValues(@[ @"now", @"later" ]); - RACStream *stream = [streamOne zipWith:shortStream]; - - verifyValues(stream, @[ - RACTuplePack(valuesOne[0], @"now"), - RACTuplePack(valuesOne[1], @"later") - ]); - }); - - qck_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) - ]); - }); - - qck_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]), - ]); - }); - }); - - qck_describe(@"+zip:reduce:", ^{ - qck_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" ]); - }); - - qck_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" ]); - }); - - qck_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" ]); - }); - - qck_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" ]); - }); - }); - - qck_describe(@"+zip:", ^{ - qck_it(@"should make a stream of tuples out of single value", ^{ - RACStream *stream = [streamClass zip:@[ streamOne ]]; - verifyValues(stream, oneStreamTuples); - }); - - qck_it(@"should make a stream of tuples out of an array of streams", ^{ - RACStream *stream = [streamClass zip:threeStreams]; - verifyValues(stream, threeStreamTuples); - }); - - qck_it(@"should make an empty stream if given an empty array", ^{ - RACStream *stream = [streamClass zip:@[]]; - verifyValues(stream, @[]); - }); - - qck_it(@"should make a stream of tuples out of an enumerator of streams", ^{ - RACStream *stream = [streamClass zip:threeStreams.objectEnumerator]; - verifyValues(stream, threeStreamTuples); - }); - - qck_it(@"should make an empty stream if given an empty enumerator", ^{ - RACStream *stream = [streamClass zip:@[].objectEnumerator]; - verifyValues(stream, @[]); - }); - }); - }); - - qck_describe(@"+concat:", ^{ - __block NSArray *streams = nil; - __block NSArray *result = nil; - - qck_beforeEach(^{ - 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 ]; - }); - - qck_it(@"should concatenate an array of streams", ^{ - RACStream *stream = [streamClass concat:streams]; - verifyValues(stream, result); - }); - - qck_it(@"should concatenate an enumerator of streams", ^{ - RACStream *stream = [streamClass concat:streams.objectEnumerator]; - verifyValues(stream, result); - }); - }); - - qck_describe(@"scanning", ^{ - NSArray *values = @[ @1, @2, @3, @4 ]; - - __block RACStream *stream; - - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_it(@"should scan", ^{ - RACStream *scanned = [stream scanWithStart:@0 reduce:^(NSNumber *running, NSNumber *next) { - return @(running.integerValue + next.integerValue); - }]; - - verifyValues(scanned, @[ @1, @3, @6, @10 ]); - }); - - qck_it(@"should scan with index", ^{ - RACStream *scanned = [stream scanWithStart:@0 reduceWithIndex:^(NSNumber *running, NSNumber *next, NSUInteger index) { - return @(running.integerValue + next.integerValue + (NSInteger)index); - }]; - - verifyValues(scanned, @[ @1, @4, @9, @16 ]); - }); - }); - - qck_describe(@"taking with a predicate", ^{ - NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; - - __block RACStream *stream; - - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_it(@"should take until a predicate is true", ^{ - RACStream *taken = [stream takeUntilBlock:^ BOOL (NSNumber *x) { - return x.integerValue >= 3; - }]; - - verifyValues(taken, @[ @0, @1, @2 ]); - }); - - qck_it(@"should take while a predicate is true", ^{ - RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 1; - }]; - - verifyValues(taken, @[ @0, @1 ]); - }); - - qck_it(@"should take a full stream", ^{ - RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 10; - }]; - - verifyValues(taken, values); - }); - - qck_it(@"should return an empty stream", ^{ - RACStream *taken = [stream takeWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue < 0; - }]; - - verifyValues(taken, @[]); - }); - - qck_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 ]); - }); - }); - - qck_describe(@"skipping with a predicate", ^{ - NSArray *values = @[ @0, @1, @2, @3, @0, @2, @4 ]; - - __block RACStream *stream; - - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_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 ]); - }); - - qck_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 ]); - }); - - qck_it(@"should skip a full stream", ^{ - RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue <= 10; - }]; - - verifyValues(taken, @[]); - }); - - qck_it(@"should finish skipping immediately", ^{ - RACStream *taken = [stream skipWhileBlock:^ BOOL (NSNumber *x) { - return x.integerValue < 0; - }]; - - verifyValues(taken, values); - }); - }); - - qck_describe(@"-combinePreviousWithStart:reduce:", ^{ - NSArray *values = @[ @1, @2, @3 ]; - __block RACStream *stream; - qck_beforeEach(^{ - stream = streamWithValues(values); - }); - - qck_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)); - }); - - qck_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" ]); - }); - }); - - qck_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", @"_" ]); - }); - }); -} - -QuickConfigurationEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSubclassObject.h b/ReactiveCocoaTests/Objective-C/RACSubclassObject.h deleted file mode 100644 index 062caffb88..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSubclassObject.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// RACSubclassObject.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 3/18/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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/ReactiveCocoaTests/Objective-C/RACSubclassObject.m b/ReactiveCocoaTests/Objective-C/RACSubclassObject.m deleted file mode 100644 index 41e61f7d64..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSubclassObject.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACSubjectSpec.m b/ReactiveCocoaTests/Objective-C/RACSubjectSpec.m deleted file mode 100644 index 88e588f87e..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSubjectSpec.m +++ /dev/null @@ -1,366 +0,0 @@ -// -// RACSubjectSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 6/24/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -@import Quick; -@import Nimble; - -#import "RACSubscriberExamples.h" - -#import -#import -#import "RACBehaviorSubject.h" -#import "RACCompoundDisposable.h" -#import "RACDisposable.h" -#import "RACReplaySubject.h" -#import "RACScheduler.h" -#import "RACSignal+Operations.h" -#import "RACSubject.h" -#import "RACUnit.h" - -@interface RACTestSubscriber : NSObject -@property (nonatomic, strong, readonly) RACDisposable *disposable; -@end - -@implementation RACTestSubscriber - -- (instancetype)init { - self = [super init]; - _disposable = [RACDisposable new]; - return self; -} - -- (void)sendNext:(id)value {} -- (void)sendError:(NSError *)error {} -- (void)sendCompleted {} - -- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable { - [disposable addDisposable:self.disposable]; -} - -@end - -QuickSpecBegin(RACSubjectSpec) - -qck_describe(@"RACSubject", ^{ - __block RACSubject *subject; - __block NSMutableArray *values; - - __block BOOL success; - __block NSError *error; - - qck_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; - }]; - }); - - qck_it(@"should dispose the paired disposable when a subscription terminates", ^{ - RACSubject* subject = [RACSubject new]; - RACTestSubscriber* subscriber = [RACTestSubscriber new]; - - [[subject subscribe:subscriber] dispose]; - - expect(@(subscriber.disposable.disposed)).to(beTruthy()); - }); - - qck_itBehavesLike(RACSubscriberExamples, ^{ - return @{ - RACSubscriberExampleSubscriber: subject, - RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], - RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], - RACSubscriberExampleSuccessBlock: [^{ return success; } copy] - }; - }); -}); - -qck_describe(@"RACReplaySubject", ^{ - __block RACReplaySubject *subject = nil; - - qck_describe(@"with a capacity of 1", ^{ - qck_beforeEach(^{ - subject = [RACReplaySubject replaySubjectWithCapacity:1]; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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()); - }); - - qck_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()); - }); - }); - - qck_describe(@"with an unlimited capacity", ^{ - qck_beforeEach(^{ - subject = [RACReplaySubject subject]; - }); - - qck_itBehavesLike(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] - }; - }); - - qck_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).to(haveCount(@2)); - NSArray *expected = [NSArray arrayWithObjects:firstValue, secondValue, nil]; - expect(valuesReceived).to(equal(expected)); - expect(@(completed)).to(beTruthy()); - }); - - qck_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("org.reactivecocoa.ReactiveCocoa.RACSubjectSpec", DISPATCH_QUEUE_CONCURRENT); - 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).to(haveCount(@(count))); - - NSArray *replayedValues = subject.toArray; - expect(replayedValues).to(haveCount(@(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)); - }]; - }); - - qck_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).toEventuallyNot(beNil()); - }); - - qck_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).toEventually(equal(@[ @0 ])); - }); - - qck_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).toEventually(equal(@1)); - }); - - qck_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).toEventually(equal(@1)); - }); - - qck_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).toEventually(equal(expected)); - }); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSubscriberExamples.h b/ReactiveCocoaTests/Objective-C/RACSubscriberExamples.h deleted file mode 100644 index edc9e5abf9..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSubscriberExamples.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// 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 . -extern NSString * const RACSubscriberExamples; - -// id -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/ReactiveCocoaTests/Objective-C/RACSubscriberExamples.m b/ReactiveCocoaTests/Objective-C/RACSubscriberExamples.m deleted file mode 100644 index c24a74a0f5..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSubscriberExamples.m +++ /dev/null @@ -1,190 +0,0 @@ -// -// RACSubscriberExamples.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-11-27. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#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"; - -QuickConfigurationBegin(RACSubscriberExampleGroups) - -+ (void)configure:(Configuration *)configuration { - sharedExamples(RACSubscriberExamples, ^(QCKDSLSharedExampleContext exampleContext) { - __block NSArray * (^valuesReceived)(void); - __block NSError * (^errorReceived)(void); - __block BOOL (^success)(void); - __block id subscriber; - - qck_beforeEach(^{ - valuesReceived = exampleContext()[RACSubscriberExampleValuesReceivedBlock]; - errorReceived = exampleContext()[RACSubscriberExampleErrorReceivedBlock]; - success = exampleContext()[RACSubscriberExampleSuccessBlock]; - subscriber = exampleContext()[RACSubscriberExampleSubscriber]; - expect(subscriber).notTo(beNil()); - }); - - qck_it(@"should accept a nil error", ^{ - [subscriber sendError:nil]; - - expect(@(success())).to(beFalsy()); - expect(errorReceived()).to(beNil()); - expect(valuesReceived()).to(equal(@[])); - }); - - qck_describe(@"with values", ^{ - __block NSSet *values; - - qck_beforeEach(^{ - NSMutableSet *mutableValues = [NSMutableSet set]; - for (NSUInteger i = 0; i < 20; i++) { - [mutableValues addObject:@(i)]; - } - - values = [mutableValues copy]; - }); - - qck_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)); - }); - }); - - qck_describe(@"multiple subscriptions", ^{ - __block RACSubject *first; - __block RACSubject *second; - - qck_beforeEach(^{ - first = [RACSubject subject]; - [first subscribe:subscriber]; - - second = [RACSubject subject]; - [second subscribe:subscriber]; - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_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)); - }); - - qck_it(@"should dispose of all current subscriptions upon termination", ^{ - __block BOOL firstDisposed = NO; - RACSignal *firstDisposableSignal = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - firstDisposed = YES; - }]; - }]; - - __block BOOL secondDisposed = NO; - RACSignal *secondDisposableSignal = [RACSignal createSignal:^(id 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()); - }); - - qck_it(@"should dispose of future subscriptions upon termination", ^{ - __block BOOL disposed = NO; - RACSignal *disposableSignal = [RACSignal createSignal:^(id subscriber) { - return [RACDisposable disposableWithBlock:^{ - disposed = YES; - }]; - }]; - - [first sendCompleted]; - expect(@(disposed)).to(beFalsy()); - - [disposableSignal subscribe:subscriber]; - expect(@(disposed)).to(beTruthy()); - }); - }); - - qck_describe(@"memory management", ^{ - qck_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()); - }); - }); - }); -} - -QuickConfigurationEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSubscriberSpec.m b/ReactiveCocoaTests/Objective-C/RACSubscriberSpec.m deleted file mode 100644 index 8da595a8cc..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSubscriberSpec.m +++ /dev/null @@ -1,130 +0,0 @@ -// -// RACSubscriberSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-11-27. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACSubscriberExamples.h" - -#import "RACSubscriber.h" -#import "RACSubscriber+Private.h" -#import - -QuickSpecBegin(RACSubscriberSpec) - -__block RACSubscriber *subscriber; -__block NSMutableArray *values; - -__block volatile BOOL finished; -__block volatile int32_t nextsAfterFinished; - -__block BOOL success; -__block NSError *error; - -qck_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; - }]; -}); - -qck_itBehavesLike(RACSubscriberExamples, ^{ - return @{ - RACSubscriberExampleSubscriber: subscriber, - RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], - RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], - RACSubscriberExampleSuccessBlock: [^{ return success; } copy] - }; -}); - -qck_describe(@"finishing", ^{ - __block void (^sendValues)(void); - __block BOOL expectedSuccess; - - __block dispatch_group_t dispatchGroup; - __block dispatch_queue_t concurrentQueue; - - qck_beforeEach(^{ - dispatchGroup = dispatch_group_create(); - expect(dispatchGroup).notTo(beNil()); - - concurrentQueue = dispatch_queue_create("org.reactivecocoa.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(); - }); - - qck_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)); - - dispatchGroup = NULL; - concurrentQueue = NULL; - - expect(@(nextsAfterFinished)).to(equal(@0)); - - if (expectedSuccess) { - expect(@(success)).to(beTruthy()); - expect(error).to(beNil()); - } else { - expect(@(success)).to(beFalsy()); - } - }); - - qck_it(@"should never invoke next after sending completed", ^{ - expectedSuccess = YES; - - dispatch_group_async(dispatchGroup, concurrentQueue, ^{ - [subscriber sendCompleted]; - - finished = YES; - OSMemoryBarrier(); - }); - }); - - qck_it(@"should never invoke next after sending error", ^{ - expectedSuccess = NO; - - dispatch_group_async(dispatchGroup, concurrentQueue, ^{ - [subscriber sendError:nil]; - - finished = YES; - OSMemoryBarrier(); - }); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACSubscriptingAssignmentTrampolineSpec.m b/ReactiveCocoaTests/Objective-C/RACSubscriptingAssignmentTrampolineSpec.m deleted file mode 100644 index e83d02bcad..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACSubscriptingAssignmentTrampolineSpec.m +++ /dev/null @@ -1,36 +0,0 @@ -// -// RACSubscriptingAssignmentTrampolineSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 9/24/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACSubscriptingAssignmentTrampoline.h" -#import "RACPropertySignalExamples.h" -#import "RACTestObject.h" -#import "RACSubject.h" - -QuickSpecBegin(RACSubscriptingAssignmentTrampolineSpec) - -id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { - [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:testObject nilValue:nilValue][keyPath] = signal; -}; - -qck_itBehavesLike(RACPropertySignalExamples, ^{ - return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; -}); - -qck_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)); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACTargetQueueSchedulerSpec.m b/ReactiveCocoaTests/Objective-C/RACTargetQueueSchedulerSpec.m deleted file mode 100644 index cdf0b9663c..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTargetQueueSchedulerSpec.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// RACTargetQueueSchedulerSpec.m -// ReactiveCocoa -// -// Created by Josh Abernathy on 6/7/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACTargetQueueScheduler.h" -#import - -QuickSpecBegin(RACTargetQueueSchedulerSpec) - -qck_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).toEventually(equal(scheduler)); -}); - -qck_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)).toEventually(equal(@1)); - - OSAtomicAnd32Barrier(0, &waitInFirst); - - expect(@(startedCount)).toEventually(equal(@2)); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACTestExampleScheduler.h b/ReactiveCocoaTests/Objective-C/RACTestExampleScheduler.h deleted file mode 100644 index 0470d98c44..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTestExampleScheduler.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// RACTestExampleScheduler.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 6/7/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@interface RACTestExampleScheduler : RACQueueScheduler - -- (id)initWithQueue:(dispatch_queue_t)queue; - -@end diff --git a/ReactiveCocoaTests/Objective-C/RACTestExampleScheduler.m b/ReactiveCocoaTests/Objective-C/RACTestExampleScheduler.m deleted file mode 100644 index 859055c9bb..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTestExampleScheduler.m +++ /dev/null @@ -1,39 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACTestObject.h b/ReactiveCocoaTests/Objective-C/RACTestObject.h deleted file mode 100644 index 06ae31d900..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTestObject.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// RACTestObject.h -// ReactiveCocoa -// -// Created by Josh Abernathy on 9/18/12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -typedef struct { - long long integerField; - double doubleField; -} RACTestStruct; - -@protocol RACTestProtocol - -@optional -- (void)optionalProtocolMethodWithObjectValue:(id)objectValue; - -@end - -@interface RACTestObject : NSObject - -@property (nonatomic, strong) id objectValue; -@property (nonatomic, strong) id secondObjectValue; -@property (nonatomic, strong) RACTestObject *strongTestObjectValue; -@property (nonatomic, weak) RACTestObject *weakTestObjectValue; -@property (nonatomic, weak) id 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/ReactiveCocoaTests/Objective-C/RACTestObject.m b/ReactiveCocoaTests/Objective-C/RACTestObject.m deleted file mode 100644 index f590703f2b..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTestObject.m +++ /dev/null @@ -1,121 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACTestSchedulerSpec.m b/ReactiveCocoaTests/Objective-C/RACTestSchedulerSpec.m deleted file mode 100644 index 4c5460bc4b..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTestSchedulerSpec.m +++ /dev/null @@ -1,178 +0,0 @@ -// -// RACTestSchedulerSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-07-06. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACTestScheduler.h" - -QuickSpecBegin(RACTestSchedulerSpec) - -__block RACTestScheduler *scheduler; - -qck_beforeEach(^{ - scheduler = [[RACTestScheduler alloc] init]; - expect(scheduler).notTo(beNil()); -}); - -qck_it(@"should do nothing when stepping while empty", ^{ - [scheduler step]; - [scheduler step:5]; - [scheduler stepAll]; -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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)); -}); - -qck_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()); -}); - -qck_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()); -}); - -qck_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)); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/RACTestUIButton.h b/ReactiveCocoaTests/Objective-C/RACTestUIButton.h deleted file mode 100644 index 03e97161e1..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTestUIButton.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// RACTestUIButton.h -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2013-06-15. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -// Enables use of -sendActionsForControlEvents: in unit tests. -@interface RACTestUIButton : UIButton - -+ (instancetype)button; - -@end diff --git a/ReactiveCocoaTests/Objective-C/RACTestUIButton.m b/ReactiveCocoaTests/Objective-C/RACTestUIButton.m deleted file mode 100644 index 48b2674908..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTestUIButton.m +++ /dev/null @@ -1,27 +0,0 @@ -// -// 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/ReactiveCocoaTests/Objective-C/RACTupleSpec.m b/ReactiveCocoaTests/Objective-C/RACTupleSpec.m deleted file mode 100644 index 5cbd49e922..0000000000 --- a/ReactiveCocoaTests/Objective-C/RACTupleSpec.m +++ /dev/null @@ -1,123 +0,0 @@ -// -// RACTupleSpec.m -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2012-12-12. -// Copyright (c) 2012 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACTuple.h" -#import "RACUnit.h" - -QuickSpecBegin(RACTupleSpec) - -qck_describe(@"RACTupleUnpack", ^{ - qck_it(@"should unpack a single value", ^{ - RACTupleUnpack(RACUnit *value) = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; - expect(value).to(equal(RACUnit.defaultUnit)); - }); - - qck_it(@"should translate RACTupleNil", ^{ - RACTupleUnpack(id value) = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; - expect(value).to(beNil()); - }); - - qck_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)); - }); - - qck_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()); - }); - - qck_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)); - }); - - qck_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)); - }); -}); - -qck_describe(@"RACTuplePack", ^{ - qck_it(@"should pack a single value", ^{ - RACTuple *tuple = [RACTuple tupleWithObjects:RACUnit.defaultUnit, nil]; - expect(RACTuplePack(RACUnit.defaultUnit)).to(equal(tuple)); - }); - - qck_it(@"should translate nil", ^{ - RACTuple *tuple = [RACTuple tupleWithObjects:RACTupleNil.tupleNil, nil]; - expect(RACTuplePack(nil)).to(equal(tuple)); - }); - - qck_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)); - }); -}); - -qck_describe(@"-tupleByAddingObject:", ^{ - __block RACTuple *tuple; - - qck_beforeEach(^{ - tuple = RACTuplePack(@"foo", nil, @"bar"); - }); - - qck_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")); - }); - - qck_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()); - }); - - qck_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)); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/UIActionSheetRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/UIActionSheetRACSupportSpec.m deleted file mode 100644 index 7bac73c926..0000000000 --- a/ReactiveCocoaTests/Objective-C/UIActionSheetRACSupportSpec.m +++ /dev/null @@ -1,39 +0,0 @@ -// -// UIActionSheetRACSupportSpec.m -// ReactiveCocoa -// -// Created by Dave Lee on 2013-06-22. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACSignal.h" -#import "RACSignal+Operations.h" -#import "UIActionSheet+RACSignalSupport.h" - -QuickSpecBegin(UIActionSheetRACSupportSpec) - -qck_describe(@"-rac_buttonClickedSignal", ^{ - __block UIActionSheet *actionSheet; - - qck_beforeEach(^{ - actionSheet = [[UIActionSheet alloc] init]; - [actionSheet addButtonWithTitle:@"Button 0"]; - [actionSheet addButtonWithTitle:@"Button 1"]; - expect(actionSheet).notTo(beNil()); - }); - - qck_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)); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/UIAlertViewRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/UIAlertViewRACSupportSpec.m deleted file mode 100644 index 29bab43b6a..0000000000 --- a/ReactiveCocoaTests/Objective-C/UIAlertViewRACSupportSpec.m +++ /dev/null @@ -1,47 +0,0 @@ -// -// UIAlertViewRACSupportSpec.m -// ReactiveCocoa -// -// Created by Henrik Hodne on 6/16/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import -#import "RACSignal.h" -#import "UIAlertView+RACSignalSupport.h" - -QuickSpecBegin(UIAlertViewRACSupportSpec) - -qck_describe(@"UIAlertView", ^{ - __block UIAlertView *alertView; - - qck_beforeEach(^{ - alertView = [[UIAlertView alloc] initWithFrame:CGRectZero]; - expect(alertView).notTo(beNil()); - }); - - qck_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)); - }); - - qck_it(@"sends the index of the appropriate button to the willDismissSignal when dismissed programatically", ^{ - __block NSInteger index = -1; - [alertView.rac_willDismissSignal subscribeNext:^(NSNumber *sentIndex) { - index = sentIndex.integerValue; - }]; - - [alertView.delegate alertView:alertView willDismissWithButtonIndex:2]; - expect(@(index)).to(equal(@2)); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/UIBarButtonItemRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/UIBarButtonItemRACSupportSpec.m deleted file mode 100644 index 783dbbd27f..0000000000 --- a/ReactiveCocoaTests/Objective-C/UIBarButtonItemRACSupportSpec.m +++ /dev/null @@ -1,43 +0,0 @@ -// -// UIBarButtonItemRACSupportSpec.m -// ReactiveCocoa -// -// Created by Kyle LeNeau on 4/13/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACControlCommandExamples.h" - -#import "UIBarButtonItem+RACCommandSupport.h" -#import "RACCommand.h" -#import "RACDisposable.h" - -QuickSpecBegin(UIBarButtonItemRACSupportSpec) - -qck_describe(@"UIBarButtonItem", ^{ - __block UIBarButtonItem *button; - - qck_beforeEach(^{ - button = [[UIBarButtonItem alloc] init]; - expect(button).notTo(beNil()); - }); - - qck_itBehavesLike(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]; - } - }; - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/UIButtonRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/UIButtonRACSupportSpec.m deleted file mode 100644 index 10094c4def..0000000000 --- a/ReactiveCocoaTests/Objective-C/UIButtonRACSupportSpec.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// UIButtonRACSupportSpec.m -// ReactiveCocoa -// -// Created by Ash Furrow on 2013-06-06. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "RACControlCommandExamples.h" -#import "RACTestUIButton.h" - -#import "UIButton+RACCommandSupport.h" -#import "RACCommand.h" -#import "RACDisposable.h" - -QuickSpecBegin(UIButtonRACSupportSpec) - -qck_describe(@"UIButton", ^{ - __block UIButton *button; - - qck_beforeEach(^{ - button = [RACTestUIButton button]; - expect(button).notTo(beNil()); - }); - - qck_itBehavesLike(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 - } - }; - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/Objective-C/UIImagePickerControllerRACSupportSpec.m b/ReactiveCocoaTests/Objective-C/UIImagePickerControllerRACSupportSpec.m deleted file mode 100644 index beafc0589a..0000000000 --- a/ReactiveCocoaTests/Objective-C/UIImagePickerControllerRACSupportSpec.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// UIImagePickerControllerRACSupportSpec.m -// ReactiveCocoa -// -// Created by Timur Kuchkarov on 17.04.14. -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// - -#import -#import - -#import "UIImagePickerController+RACSignalSupport.h" -#import "RACSignal.h" - -QuickSpecBegin(UIImagePickerControllerRACSupportSpec) - -qck_describe(@"UIImagePickerController", ^{ - __block UIImagePickerController *imagePicker; - - qck_beforeEach(^{ - imagePicker = [[UIImagePickerController alloc] init]; - expect(imagePicker).notTo(beNil()); - }); - - qck_it(@"sends the user info dictionary after confirmation", ^{ - __block NSDictionary *selectedImageUserInfo = nil; - [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { - selectedImageUserInfo = userInfo; - }]; - - NSDictionary *info = @{ - UIImagePickerControllerMediaType: @"public.image", - UIImagePickerControllerMediaMetadata: @{} - }; - [imagePicker.delegate imagePickerController:imagePicker didFinishPickingMediaWithInfo:info]; - expect(selectedImageUserInfo).to(equal(info)); - }); - - qck_it(@"cancels image picking process", ^{ - __block BOOL didSend = NO; - __block BOOL didComplete = NO; - [imagePicker.rac_imageSelectedSignal subscribeNext:^(NSDictionary *userInfo) { - didSend = YES; - } completed:^{ - didComplete = YES; - }]; - - [imagePicker.delegate imagePickerControllerDidCancel:imagePicker]; - expect(@(didSend)).to(beFalsy()); - expect(@(didComplete)).to(beTruthy()); - }); -}); - -QuickSpecEnd diff --git a/ReactiveCocoaTests/QueueScheduler+Factory.swift b/ReactiveCocoaTests/QueueScheduler+Factory.swift new file mode 100644 index 0000000000..f988051c54 --- /dev/null +++ b/ReactiveCocoaTests/QueueScheduler+Factory.swift @@ -0,0 +1,10 @@ +import ReactiveSwift +import Foundation + +extension QueueScheduler { + static func makeForTesting(file: String = #file, line: UInt = #line) -> QueueScheduler { + let file = URL(string: file)?.lastPathComponent ?? "" + let label = "reactiveswift:\(file):\(line)" + return QueueScheduler(name: label) + } +} diff --git a/ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h b/ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h new file mode 100644 index 0000000000..d8af552026 --- /dev/null +++ b/ReactiveCocoaTests/ReactiveCocoaTests-Bridging-Header.h @@ -0,0 +1 @@ +#import "MessageForwardingEntity.h" diff --git a/ReactiveCocoaTests/ReactiveCocoaTestsConfiguration.swift b/ReactiveCocoaTests/ReactiveCocoaTestsConfiguration.swift new file mode 100644 index 0000000000..6b32096f49 --- /dev/null +++ b/ReactiveCocoaTests/ReactiveCocoaTestsConfiguration.swift @@ -0,0 +1,14 @@ +import Quick +#if canImport(UIKit) +import UIKit +#endif + +class ReactiveCocoaTestsConfiguration: QuickConfiguration { + override class func configure(_ configuration: Configuration) { + #if canImport(UIKit) + configuration.beforeSuite { + UIControl._initialize() + } + #endif + } +} diff --git a/ReactiveCocoaTests/Shared/NSLayoutConstraintSpec.swift b/ReactiveCocoaTests/Shared/NSLayoutConstraintSpec.swift new file mode 100644 index 0000000000..d0b629574c --- /dev/null +++ b/ReactiveCocoaTests/Shared/NSLayoutConstraintSpec.swift @@ -0,0 +1,44 @@ +#if canImport(AppKit) || canImport(UIKit) + +#if canImport(AppKit) && !targetEnvironment(macCatalyst) +import AppKit +#elseif canImport(UIKit) +import UIKit +#endif + +import ReactiveSwift +import ReactiveCocoa +import Quick +import Nimble + +class NSLayoutConstraintSpec: QuickSpec { + override func spec() { + var constraint: NSLayoutConstraint! + weak var _constraint: NSLayoutConstraint? + + beforeEach { + constraint = NSLayoutConstraint() + _constraint = constraint + } + + afterEach { + constraint = nil + expect(_constraint).to(beNil()) + } + + it("should accept changes from bindings to its constant") { + expect(constraint.constant).to(equal(0.0)) + + let (pipeSignal, observer) = Signal.pipe() + + constraint.reactive.constant <~ pipeSignal + + observer.send(value: 5.0) + expect(constraint.constant) ≈ 5.0 + + observer.send(value: -3.0) + expect(constraint.constant) ≈ -3.0 + } + } +} +#endif diff --git a/ReactiveCocoaTests/SignalProducerNimbleMatchers.swift b/ReactiveCocoaTests/SignalProducerNimbleMatchers.swift new file mode 100644 index 0000000000..4d4b70aacd --- /dev/null +++ b/ReactiveCocoaTests/SignalProducerNimbleMatchers.swift @@ -0,0 +1,66 @@ +// +// SignalProducerNimbleMatchers.swift +// ReactiveSwift +// +// Created by Javier Soto on 1/25/15. +// Copyright (c) 2015 GitHub. All rights reserved. +// + +import Foundation + +import ReactiveSwift +import Nimble + +public func sendValue(_ value: T?, sendError: E?, complete: Bool) -> Predicate> { + return sendValues(value.map { [$0] } ?? [], sendError: sendError, complete: complete) +} + +public func sendValues(_ values: [T], sendError maybeSendError: E?, complete: Bool) -> Predicate> { + return Predicate> { actualExpression in + precondition(maybeSendError == nil || !complete, "Signals can't both send an error and complete") + guard let signalProducer = try actualExpression.evaluate() else { + let message = ExpectationMessage.fail("The SignalProducer was not created.") + .appendedBeNilHint() + return PredicateResult(status: .fail, message: message) + } + + var sentValues: [T] = [] + var sentError: E? + var signalCompleted = false + + signalProducer.start { event in + switch event { + case let .value(value): + sentValues.append(value) + case .completed: + signalCompleted = true + case let .failed(error): + sentError = error + default: + break + } + } + + if sentValues != values { + let message = ExpectationMessage.expectedCustomValueTo( + "send values <\(values)>", + actual: "<\(sentValues)>" + ) + return PredicateResult(status: .doesNotMatch, message: message) + } + + if sentError != maybeSendError { + let message = ExpectationMessage.expectedCustomValueTo( + "send error <\(String(describing: maybeSendError))>", + actual: "<\(String(describing: sentError))>" + ) + return PredicateResult(status: .doesNotMatch, message: message) + } + + let completeMessage = complete ? + "complete, but the producer did not complete" : + "not to complete, but the producer completed" + let message = ExpectationMessage.expectedTo(completeMessage) + return PredicateResult(bool: signalCompleted == complete, message: message) + } +} diff --git a/ReactiveCocoaTests/Swift/ActionSpec.swift b/ReactiveCocoaTests/Swift/ActionSpec.swift deleted file mode 100755 index fc6c7fbfd6..0000000000 --- a/ReactiveCocoaTests/Swift/ActionSpec.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// ActionSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-12-11. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -import ReactiveCocoa - -class ActionSpec: QuickSpec { - override func spec() { - describe("Action") { - var action: Action! - var enabled: MutableProperty! - - var executionCount = 0 - var values: [String] = [] - var errors: [NSError] = [] - - var scheduler: TestScheduler! - let testError = NSError(domain: "ActionSpec", code: 1, userInfo: nil) - - beforeEach { - executionCount = 0 - values = [] - errors = [] - enabled = MutableProperty(false) - - scheduler = TestScheduler() - action = Action(enabledIf: enabled) { number in - return SignalProducer { observer, disposable in - executionCount += 1 - - if number % 2 == 0 { - observer.sendNext("\(number)") - observer.sendNext("\(number)\(number)") - - scheduler.schedule { - observer.sendCompleted() - } - } else { - scheduler.schedule { - observer.sendFailed(testError) - } - } - } - } - - action.values.observeNext { values.append($0) } - action.errors.observeNext { errors.append($0) } - } - - it("should be disabled and not executing after initialization") { - expect(action.isEnabled.value) == false - expect(action.isExecuting.value) == false - } - - it("should error if executed while disabled") { - var receivedError: ActionError? - action.apply(0).startWithFailed { - receivedError = $0 - } - - expect(receivedError).notTo(beNil()) - if let error = receivedError { - let expectedError = ActionError.disabled - expect(error == expectedError) == true - } - } - - it("should enable and disable based on the given property") { - enabled.value = true - expect(action.isEnabled.value) == true - expect(action.isExecuting.value) == false - - enabled.value = false - expect(action.isEnabled.value) == false - expect(action.isExecuting.value) == false - } - - describe("execution") { - beforeEach { - enabled.value = true - } - - it("should execute successfully") { - var receivedValue: String? - - action.apply(0) - .assumeNoErrors() - .startWithNext { - receivedValue = $0 - } - - expect(executionCount) == 1 - expect(action.isExecuting.value) == true - expect(action.isEnabled.value) == false - - expect(receivedValue) == "00" - expect(values) == [ "0", "00" ] - expect(errors) == [] - - scheduler.run() - expect(action.isExecuting.value) == false - expect(action.isEnabled.value) == true - - expect(values) == [ "0", "00" ] - expect(errors) == [] - } - - it("should execute with an error") { - var receivedError: ActionError? - - action.apply(1).startWithFailed { - receivedError = $0 - } - - expect(executionCount) == 1 - expect(action.isExecuting.value) == true - expect(action.isEnabled.value) == false - - scheduler.run() - expect(action.isExecuting.value) == false - expect(action.isEnabled.value) == true - - expect(receivedError).notTo(beNil()) - if let error = receivedError { - let expectedError = ActionError.producerFailed(testError) - expect(error == expectedError) == true - } - - expect(values) == [] - expect(errors) == [ testError ] - } - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/AtomicSpec.swift b/ReactiveCocoaTests/Swift/AtomicSpec.swift deleted file mode 100644 index 6fbf8e69d7..0000000000 --- a/ReactiveCocoaTests/Swift/AtomicSpec.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// AtomicSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-07-13. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Nimble -import Quick -import ReactiveCocoa - -class AtomicSpec: QuickSpec { - override func spec() { - var atomic: Atomic! - - beforeEach { - atomic = Atomic(1) - } - - it("should read and write the value directly") { - expect(atomic.value) == 1 - - atomic.value = 2 - expect(atomic.value) == 2 - } - - it("should swap the value atomically") { - expect(atomic.swap(2)) == 1 - expect(atomic.value) == 2 - } - - it("should modify the value atomically") { - atomic.modify { $0 += 1 } - expect(atomic.value) == 2 - } - - it("should perform an action with the value") { - let result: Bool = atomic.withValue { $0 == 1 } - expect(result) == true - expect(atomic.value) == 1 - } - } -} diff --git a/ReactiveCocoaTests/Swift/BagSpec.swift b/ReactiveCocoaTests/Swift/BagSpec.swift deleted file mode 100644 index b7841486c8..0000000000 --- a/ReactiveCocoaTests/Swift/BagSpec.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// BagSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-07-13. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Nimble -import Quick -import ReactiveCocoa - -class BagSpec: QuickSpec { - override func spec() { - var bag = Bag() - - beforeEach { - bag = Bag() - } - - it("should insert values") { - bag.insert("foo") - bag.insert("bar") - bag.insert("buzz") - - expect(bag).to(contain("foo")) - expect(bag).to(contain("bar")) - expect(bag).to(contain("buzz")) - expect(bag).toNot(contain("fuzz")) - expect(bag).toNot(contain("foobar")) - } - - it("should remove values given the token from insertion") { - let a = bag.insert("foo") - let b = bag.insert("bar") - let c = bag.insert("buzz") - - bag.remove(using: b) - expect(bag).to(contain("foo")) - expect(bag).toNot(contain("bar")) - expect(bag).to(contain("buzz")) - - bag.remove(using: a) - expect(bag).toNot(contain("foo")) - expect(bag).toNot(contain("bar")) - expect(bag).to(contain("buzz")) - - bag.remove(using: c) - expect(bag).toNot(contain("foo")) - expect(bag).toNot(contain("bar")) - expect(bag).toNot(contain("buzz")) - } - } -} diff --git a/ReactiveCocoaTests/Swift/CocoaActionSpec.swift b/ReactiveCocoaTests/Swift/CocoaActionSpec.swift deleted file mode 100644 index a11db898e5..0000000000 --- a/ReactiveCocoaTests/Swift/CocoaActionSpec.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Result -import Nimble -import Quick -import ReactiveCocoa - -class CocoaActionSpec: QuickSpec { - override func spec() { - var action: Action! - - beforeEach { - action = Action { value in SignalProducer(value: value + 1) } - expect(action.isEnabled.value) == true - - expect(action.unsafeCocoaAction.isEnabled).toEventually(beTruthy()) - } - - #if os(OSX) - it("should be compatible with AppKit") { - let control = NSControl(frame: NSZeroRect) - control.target = action.unsafeCocoaAction - control.action = CocoaAction.selector - control.performClick(nil) - } - #elseif os(iOS) - it("should be compatible with UIKit") { - let control = UIControl(frame: .zero) - control.addTarget(action.unsafeCocoaAction, action: CocoaAction.selector, for: .touchDown) - control.sendActions(for: .touchDown) - } - #endif - - it("should generate KVO notifications for enabled") { - var values: [Bool] = [] - - let cocoaAction = action.unsafeCocoaAction - cocoaAction - .rac_values(forKeyPath: #keyPath(CocoaAction.isEnabled), observer: nil) - .toSignalProducer() - .map { $0! as! Bool } - .start(Observer(next: { values.append($0) })) - - expect(values) == [ true ] - - let result = action.apply(0).first() - expect(result?.value) == 1 - expect(values).toEventually(equal([ true, false, true ])) - - _ = cocoaAction - } - - it("should generate KVO notifications for executing") { - var values: [Bool] = [] - - let cocoaAction = action.unsafeCocoaAction - cocoaAction - .rac_values(forKeyPath: #keyPath(CocoaAction.isExecuting), observer: nil) - .toSignalProducer() - .map { $0! as! Bool } - .start(Observer(next: { values.append($0) })) - - expect(values) == [ false ] - - let result = action.apply(0).first() - expect(result?.value) == 1 - expect(values).toEventually(equal([ false, true, false ])) - - _ = cocoaAction - } - - context("lifetime") { - it("unsafeCocoaAction should not create a retain cycle") { - weak var weakAction: Action? - var action: Action? = Action { _ in - return SignalProducer(value: 42) - } - weakAction = action - expect(weakAction).notTo(beNil()) - - _ = action!.unsafeCocoaAction - action = nil - expect(weakAction).to(beNil()) - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/DisposableSpec.swift b/ReactiveCocoaTests/Swift/DisposableSpec.swift deleted file mode 100644 index e6928c89ce..0000000000 --- a/ReactiveCocoaTests/Swift/DisposableSpec.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// DisposableSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-07-13. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Nimble -import Quick -import ReactiveCocoa - -class DisposableSpec: QuickSpec { - override func spec() { - describe("SimpleDisposable") { - it("should set disposed to true") { - let disposable = SimpleDisposable() - expect(disposable.isDisposed) == false - - disposable.dispose() - expect(disposable.isDisposed) == true - } - } - - describe("ActionDisposable") { - it("should run the given action upon disposal") { - var didDispose = false - let disposable = ActionDisposable { - didDispose = true - } - - expect(didDispose) == false - expect(disposable.isDisposed) == false - - disposable.dispose() - expect(didDispose) == true - expect(disposable.isDisposed) == true - } - } - - describe("CompositeDisposable") { - var disposable = CompositeDisposable() - - beforeEach { - disposable = CompositeDisposable() - } - - it("should ignore the addition of nil") { - disposable.add(nil) - return - } - - it("should dispose of added disposables") { - let simpleDisposable = SimpleDisposable() - disposable += simpleDisposable - - var didDispose = false - disposable += { - didDispose = true - } - - expect(simpleDisposable.isDisposed) == false - expect(didDispose) == false - expect(disposable.isDisposed) == false - - disposable.dispose() - expect(simpleDisposable.isDisposed) == true - expect(didDispose) == true - expect(disposable.isDisposed) == true - } - - it("should not dispose of removed disposables") { - let simpleDisposable = SimpleDisposable() - let handle = disposable += simpleDisposable - - // We should be allowed to call this any number of times. - handle.remove() - handle.remove() - expect(simpleDisposable.isDisposed) == false - - disposable.dispose() - expect(simpleDisposable.isDisposed) == false - } - } - - describe("ScopedDisposable") { - it("should dispose of the inner disposable upon deinitialization") { - let simpleDisposable = SimpleDisposable() - - func runScoped() { - let scopedDisposable = ScopedDisposable(simpleDisposable) - expect(simpleDisposable.isDisposed) == false - expect(scopedDisposable.isDisposed) == false - } - - expect(simpleDisposable.isDisposed) == false - - runScoped() - expect(simpleDisposable.isDisposed) == true - } - } - - describe("SerialDisposable") { - var disposable: SerialDisposable! - - beforeEach { - disposable = SerialDisposable() - } - - it("should dispose of the inner disposable") { - let simpleDisposable = SimpleDisposable() - disposable.innerDisposable = simpleDisposable - - expect(disposable.innerDisposable).notTo(beNil()) - expect(simpleDisposable.isDisposed) == false - expect(disposable.isDisposed) == false - - disposable.dispose() - expect(disposable.innerDisposable).to(beNil()) - expect(simpleDisposable.isDisposed) == true - expect(disposable.isDisposed) == true - } - - it("should dispose of the previous disposable when swapping innerDisposable") { - let oldDisposable = SimpleDisposable() - let newDisposable = SimpleDisposable() - - disposable.innerDisposable = oldDisposable - expect(oldDisposable.isDisposed) == false - expect(newDisposable.isDisposed) == false - - disposable.innerDisposable = newDisposable - expect(oldDisposable.isDisposed) == true - expect(newDisposable.isDisposed) == false - expect(disposable.isDisposed) == false - - disposable.innerDisposable = nil - expect(newDisposable.isDisposed) == true - expect(disposable.isDisposed) == false - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/FlattenSpec.swift b/ReactiveCocoaTests/Swift/FlattenSpec.swift deleted file mode 100644 index e87c7efc34..0000000000 --- a/ReactiveCocoaTests/Swift/FlattenSpec.swift +++ /dev/null @@ -1,963 +0,0 @@ -// -// FlattenSpec.swift -// ReactiveCocoa -// -// Created by Oleg Shnitko on 1/22/16. -// Copyright © 2016 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -import ReactiveCocoa - -private extension SignalProtocol { - typealias Pipe = (signal: Signal, observer: Observer) -} - -private typealias Pipe = Signal, TestError>.Pipe - -class FlattenSpec: QuickSpec { - override func spec() { - func describeSignalFlattenDisposal(_ flattenStrategy: FlattenStrategy, name: String) { - describe(name) { - var pipe: Pipe! - var disposable: Disposable? - - beforeEach { - pipe = Signal.pipe() - disposable = pipe.signal - .flatten(flattenStrategy) - .observe { _ in } - } - - afterEach { - disposable?.dispose() - } - - context("disposal") { - var disposed = false - - beforeEach { - disposed = false - pipe.observer.sendNext(SignalProducer { _, disposable in - disposable += ActionDisposable { - disposed = true - } - }) - } - - it("should dispose inner signals when outer signal interrupted") { - pipe.observer.sendInterrupted() - expect(disposed) == true - } - - it("should dispose inner signals when outer signal failed") { - pipe.observer.sendFailed(.default) - expect(disposed) == true - } - - it("should not dispose inner signals when outer signal completed") { - pipe.observer.sendCompleted() - expect(disposed) == false - } - } - } - } - - context("Signal") { - describeSignalFlattenDisposal(.latest, name: "switchToLatest") - describeSignalFlattenDisposal(.merge, name: "merge") - describeSignalFlattenDisposal(.concat, name: "concat") - } - - func describeSignalProducerFlattenDisposal(_ flattenStrategy: FlattenStrategy, name: String) { - describe(name) { - it("disposes original signal when result signal interrupted") { - var disposed = false - - let disposable = SignalProducer, NoError> { _, disposable in - disposable += ActionDisposable { - disposed = true - } - } - .flatten(flattenStrategy) - .start() - - disposable.dispose() - expect(disposed) == true - } - } - } - - context("SignalProducer") { - describeSignalProducerFlattenDisposal(.latest, name: "switchToLatest") - describeSignalProducerFlattenDisposal(.merge, name: "merge") - describeSignalProducerFlattenDisposal(.concat, name: "concat") - } - - describe("Signal.flatten()") { - it("works with TestError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with SequenceType as a value") { - let (signal, innerObserver) = Signal<[Int], NoError>.pipe() - let sequence = [1, 2, 3] - var observedValues = [Int]() - - signal - .flatten(.concat) - .observeNext { value in - observedValues.append(value) - } - - innerObserver.sendNext(sequence) - expect(observedValues) == sequence - } - } - - describe("SignalProducer.flatten()") { - it("works with TestError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatten(.latest) - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(inner) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with SequenceType as a value") { - let sequence = [1, 2, 3] - var observedValues = [Int]() - - let producer = SignalProducer<[Int], NoError>(value: sequence) - producer - .flatten(.latest) - .startWithNext { value in - observedValues.append(value) - } - - expect(observedValues) == sequence - } - } - - describe("Signal.flatMap()") { - it("works with TestError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = Signal - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .observeNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - } - - describe("SignalProducer.flatMap()") { - it("works with TestError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError Signal") { - typealias Inner = Signal - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a TestError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with NoError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - - it("works with TestError and a NoError SignalProducer") { - typealias Inner = SignalProducer - typealias Outer = SignalProducer - - let (inner, innerObserver) = Inner.pipe() - let (outer, outerObserver) = Outer.pipe() - - var observed: Int? = nil - outer - .flatMap(.latest) { _ in inner } - .assumeNoErrors() - .startWithNext { value in - observed = value - } - - outerObserver.sendNext(4) - innerObserver.sendNext(4) - expect(observed) == 4 - } - } - - describe("Signal.merge()") { - it("should emit values from all signals") { - let (signal1, observer1) = Signal.pipe() - let (signal2, observer2) = Signal.pipe() - - let mergedSignals = Signal.merge([signal1, signal2]) - - var lastValue: Int? - mergedSignals.observeNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer1.sendNext(1) - expect(lastValue) == 1 - - observer2.sendNext(2) - expect(lastValue) == 2 - - observer1.sendNext(3) - expect(lastValue) == 3 - } - - it("should not stop when one signal completes") { - let (signal1, observer1) = Signal.pipe() - let (signal2, observer2) = Signal.pipe() - - let mergedSignals = Signal.merge([signal1, signal2]) - - var lastValue: Int? - mergedSignals.observeNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer1.sendNext(1) - expect(lastValue) == 1 - - observer1.sendCompleted() - expect(lastValue) == 1 - - observer2.sendNext(2) - expect(lastValue) == 2 - } - - it("should complete when all signals complete") { - let (signal1, observer1) = Signal.pipe() - let (signal2, observer2) = Signal.pipe() - - let mergedSignals = Signal.merge([signal1, signal2]) - - var completed = false - mergedSignals.observeCompleted { completed = true } - - expect(completed) == false - - observer1.sendNext(1) - expect(completed) == false - - observer1.sendCompleted() - expect(completed) == false - - observer2.sendCompleted() - expect(completed) == true - } - } - - describe("SignalProducer.merge()") { - it("should emit values from all producers") { - let (signal1, observer1) = SignalProducer.pipe() - let (signal2, observer2) = SignalProducer.pipe() - - let mergedSignals = SignalProducer.merge([signal1, signal2]) - - var lastValue: Int? - mergedSignals.startWithNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer1.sendNext(1) - expect(lastValue) == 1 - - observer2.sendNext(2) - expect(lastValue) == 2 - - observer1.sendNext(3) - expect(lastValue) == 3 - } - - it("should not stop when one producer completes") { - let (signal1, observer1) = SignalProducer.pipe() - let (signal2, observer2) = SignalProducer.pipe() - - let mergedSignals = SignalProducer.merge([signal1, signal2]) - - var lastValue: Int? - mergedSignals.startWithNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer1.sendNext(1) - expect(lastValue) == 1 - - observer1.sendCompleted() - expect(lastValue) == 1 - - observer2.sendNext(2) - expect(lastValue) == 2 - } - - it("should complete when all producers complete") { - let (signal1, observer1) = SignalProducer.pipe() - let (signal2, observer2) = SignalProducer.pipe() - - let mergedSignals = SignalProducer.merge([signal1, signal2]) - - var completed = false - mergedSignals.startWithCompleted { completed = true } - - expect(completed) == false - - observer1.sendNext(1) - expect(completed) == false - - observer1.sendCompleted() - expect(completed) == false - - observer2.sendCompleted() - expect(completed) == true - } - } - - describe("SignalProducer.prefix()") { - it("should emit initial value") { - let (signal, observer) = SignalProducer.pipe() - - let mergedSignals = signal.prefix(value: 0) - - var lastValue: Int? - mergedSignals.startWithNext { lastValue = $0 } - - expect(lastValue) == 0 - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - observer.sendNext(3) - expect(lastValue) == 3 - } - - it("should emit initial value") { - let (signal, observer) = SignalProducer.pipe() - - let mergedSignals = signal.prefix(SignalProducer(value: 0)) - - var lastValue: Int? - mergedSignals.startWithNext { lastValue = $0 } - - expect(lastValue) == 0 - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - observer.sendNext(3) - expect(lastValue) == 3 - } - } - - describe("SignalProducer.concat(value:)") { - it("should emit final value") { - let (signal, observer) = SignalProducer.pipe() - - let mergedSignals = signal.concat(value: 4) - - var lastValue: Int? - mergedSignals.startWithNext { lastValue = $0 } - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - observer.sendNext(3) - expect(lastValue) == 3 - - observer.sendCompleted() - expect(lastValue) == 4 - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/FoundationExtensionsSpec.swift b/ReactiveCocoaTests/Swift/FoundationExtensionsSpec.swift deleted file mode 100644 index 7a3ddf943c..0000000000 --- a/ReactiveCocoaTests/Swift/FoundationExtensionsSpec.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// FoundationExtensionsSpec.swift -// ReactiveCocoa -// -// Created by Neil Pankey on 5/22/15. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -import ReactiveCocoa - -extension Notification.Name { - static let racFirst = Notification.Name(rawValue: "rac_notifications_test") - static let racAnother = Notification.Name(rawValue: "rac_notifications_another") -} - -class FoundationExtensionsSpec: QuickSpec { - override func spec() { - describe("NSNotificationCenter.rac_notifications") { - let center = NotificationCenter.default - - it("should send notifications on the producer") { - let producer = center.rac_notifications(forName: .racFirst) - - var notif: Notification? = nil - let disposable = producer.startWithNext { notif = $0 } - - center.post(name: .racAnother, object: nil) - expect(notif).to(beNil()) - - center.post(name: .racFirst, object: nil) - expect(notif?.name) == .racFirst - - notif = nil - disposable.dispose() - - center.post(name: .racFirst, object: nil) - expect(notif).to(beNil()) - } - - it("should send Interrupted when the observed object is freed") { - var observedObject: AnyObject? = NSObject() - let producer = center.rac_notifications(forName: nil, object: observedObject) - observedObject = nil - - var interrupted = false - let disposable = producer.startWithInterrupted { - interrupted = true - } - expect(interrupted) == true - - disposable.dispose() - } - - } - } -} diff --git a/ReactiveCocoaTests/Swift/KeyValueObservingSpec.swift b/ReactiveCocoaTests/Swift/KeyValueObservingSpec.swift deleted file mode 100644 index a8ca47d08f..0000000000 --- a/ReactiveCocoaTests/Swift/KeyValueObservingSpec.swift +++ /dev/null @@ -1,377 +0,0 @@ -import Foundation -@testable import ReactiveCocoa -import enum Result.NoError -import Quick -import Nimble - -class KeyValueObservingSpec: QuickSpec { - override func spec() { - describe("NSObject.valuesForKeyPath") { - it("should sends the current value and then the changes for the key path") { - let object = ObservableObject() - var values: [Int] = [] - object.values(forKeyPath: "rac_value").startWithNext { value in - expect(value).notTo(beNil()) - values.append(value as! Int) - } - - expect(values) == [ 0 ] - - object.rac_value = 1 - expect(values) == [ 0, 1 ] - - object.rac_value = 2 - expect(values) == [ 0, 1, 2 ] - } - - it("should sends the current value and then the changes for the key path, even if the value actually remains unchanged") { - let object = ObservableObject() - var values: [Int] = [] - object.values(forKeyPath: "rac_value").startWithNext { value in - expect(value).notTo(beNil()) - values.append(value as! Int) - } - - expect(values) == [ 0 ] - - object.rac_value = 0 - expect(values) == [ 0, 0 ] - - object.rac_value = 0 - expect(values) == [ 0, 0, 0 ] - } - - it("should complete when the object deallocates") { - var completed = false - - _ = { - // Use a closure so this object has a shorter lifetime. - let object = ObservableObject() - - object.values(forKeyPath: "rac_value").startWithCompleted { - completed = true - } - - expect(completed) == false - }() - - expect(completed).toEventually(beTruthy()) - } - - it("should interrupt") { - var interrupted = false - - let object = ObservableObject() - let disposable = object.values(forKeyPath: "rac_value") - .startWithInterrupted { interrupted = true } - - expect(interrupted) == false - - disposable.dispose() - expect(interrupted) == true - } - - it("should observe changes in a nested key path") { - let parentObject = NestedObservableObject() - var values: [Int] = [] - - parentObject - .values(forKeyPath: "rac_object.rac_value") - .map { $0 as! NSNumber } - .map { $0.intValue } - .startWithNext { - values.append($0) - } - - expect(values) == [0] - - parentObject.rac_object.rac_value = 1 - expect(values) == [0, 1] - - let oldInnerObject = parentObject.rac_object - - let newInnerObject = ObservableObject() - parentObject.rac_object = newInnerObject - expect(values) == [0, 1, 0] - - parentObject.rac_object.rac_value = 10 - oldInnerObject.rac_value = 2 - expect(values) == [0, 1, 0, 10] - } - - it("should observe changes in a nested weak key path") { - let parentObject = NestedObservableObject() - var innerObject = Optional(ObservableObject()) - parentObject.rac_weakObject = innerObject - - var values: [Int] = [] - parentObject - .values(forKeyPath: "rac_weakObject.rac_value") - .map { $0 as! NSNumber } - .map { $0.intValue } - .startWithNext { - values.append($0) - } - - expect(values) == [0] - - innerObject?.rac_value = 1 - expect(values) == [0, 1] - - autoreleasepool { - innerObject = nil - } - - expect(values) == [0, 1] - - innerObject = ObservableObject() - parentObject.rac_weakObject = innerObject - expect(values) == [0, 1, 0] - - innerObject?.rac_value = 10 - expect(values) == [0, 1, 0, 10] - } - - it("should not retain replaced value in a nested key path") { - let parentObject = NestedObservableObject() - - weak var weakOriginalInner: ObservableObject? = parentObject.rac_object - expect(weakOriginalInner).toNot(beNil()) - - parentObject.rac_object = ObservableObject() - expect(weakOriginalInner).to(beNil()) - } - - describe("thread safety") { - var testObject: ObservableObject! - var concurrentQueue: DispatchQueue! - - beforeEach { - testObject = ObservableObject() - concurrentQueue = DispatchQueue(label: "org.reactivecocoa.ReactiveCocoa.DynamicPropertySpec.concurrentQueue", - attributes: .concurrent) - } - - it("should handle changes being made on another queue") { - var observedValue = 0 - - testObject.values(forKeyPath: "rac_value") - .skip(first: 1) - .take(first: 1) - .map { $0 as! NSNumber } - .map { $0.intValue } - .startWithNext { - observedValue = $0 - } - - concurrentQueue.async { - testObject.rac_value = 2 - } - - concurrentQueue.sync(flags: .barrier) {} - expect(observedValue).toEventually(equal(2)) - } - - it("should handle changes being made on another queue using deliverOn") { - var observedValue = 0 - - testObject.values(forKeyPath: "rac_value") - .skip(first: 1) - .take(first: 1) - .observe(on: UIScheduler()) - .map { $0 as! NSNumber } - .map { $0.intValue } - .startWithNext { - observedValue = $0 - } - - concurrentQueue.async { - testObject.rac_value = 2 - } - - concurrentQueue.sync(flags: .barrier) {} - expect(observedValue).toEventually(equal(2)) - } - - it("async disposal of target") { - var observedValue = 0 - - testObject.values(forKeyPath: "rac_value") - .observe(on: UIScheduler()) - .map { $0 as! NSNumber } - .map { $0.intValue } - .startWithNext { - observedValue = $0 - } - - concurrentQueue.async { - testObject.rac_value = 2 - testObject = nil - } - - concurrentQueue.sync(flags: .barrier) {} - expect(observedValue).toEventually(equal(2)) - } - } - - describe("stress tests") { - let numIterations = 5000 - - var testObject: ObservableObject! - var iterationQueue: DispatchQueue! - var concurrentQueue: DispatchQueue! - - beforeEach { - testObject = ObservableObject() - iterationQueue = DispatchQueue(label: "org.reactivecocoa.ReactiveCocoa.RACKVOProxySpec.iterationQueue", - attributes: .concurrent) - concurrentQueue = DispatchQueue(label: "org.reactivecocoa.ReactiveCocoa.DynamicPropertySpec.concurrentQueue", - attributes: .concurrent) - } - - it("attach observers") { - let deliveringObserver: QueueScheduler - if #available(*, OSX 10.10) { - deliveringObserver = QueueScheduler(name: "\(#file):\(#line)") - } else { - deliveringObserver = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - var atomicCounter = Int64(0) - - DispatchQueue.concurrentPerform(iterations: numIterations) { index in - testObject.values(forKeyPath: "rac_value") - .skip(first: 1) - .observe(on: deliveringObserver) - .map { $0 as! NSNumber } - .map { $0.int64Value } - .startWithNext { value in - OSAtomicAdd64(value, &atomicCounter) - } - } - - testObject.rac_value = 2 - - expect(atomicCounter).toEventually(equal(10000), timeout: 30.0) - } - - // ReactiveCocoa/ReactiveCocoa#1122 - it("async disposal of observer") { - let serialDisposable = SerialDisposable() - - iterationQueue.async { - DispatchQueue.concurrentPerform(iterations: numIterations) { index in - let disposable = testObject.values(forKeyPath: "rac_value").startWithCompleted {} - serialDisposable.innerDisposable = disposable - - concurrentQueue.async { - testObject.rac_value = index - } - } - } - - iterationQueue.sync(flags: .barrier) { - serialDisposable.dispose() - } - } - - it("async disposal of signal with in-flight changes") { - let (teardown, teardownObserver) = Signal<(), NoError>.pipe() - let otherScheduler: QueueScheduler - if #available(*, OSX 10.10) { - otherScheduler = QueueScheduler(name: "\(#file):\(#line)") - } else { - otherScheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - let replayProducer = testObject.values(forKeyPath: "rac_value") - .map { $0 as! NSNumber } - .map { $0.intValue } - .map { $0 % 2 == 0 } - .observe(on: otherScheduler) - .take(until: teardown) - .replayLazily(upTo: 1) - - replayProducer.start { _ in } - - iterationQueue.async { - DispatchQueue.concurrentPerform(iterations: numIterations) { index in - testObject.rac_value = index - } - } - - iterationQueue.async(flags: .barrier) { - teardownObserver.sendNext() - } - - let event = replayProducer.last() - expect(event).toNot(beNil()) - } - } - } - - describe("property type and attribute query") { - let object = TestAttributeQueryObject() - - it("should be able to classify weak references") { - "weakProperty".withCString { cString in - let propertyPointer = class_getProperty(type(of: object), cString) - expect(propertyPointer) != nil - - if let pointer = propertyPointer { - let attributes = PropertyAttributes(property: pointer) - expect(attributes.isWeak) == true - expect(attributes.isObject) == true - expect(attributes.isBlock) == false - expect(attributes.objectClass).to(beIdenticalTo(NSObject.self)) - } - } - } - - it("should be able to classify blocks") { - "block".withCString { cString in - let propertyPointer = class_getProperty(type(of: object), cString) - expect(propertyPointer) != nil - - if let pointer = propertyPointer { - let attributes = PropertyAttributes(property: pointer) - expect(attributes.isWeak) == false - expect(attributes.isObject) == true - expect(attributes.isBlock) == true - expect(attributes.objectClass).to(beNil()) - } - } - } - - it("should be able to classify non object properties") { - "integer".withCString { cString in - let propertyPointer = class_getProperty(type(of: object), cString) - expect(propertyPointer) != nil - - if let pointer = propertyPointer { - let attributes = PropertyAttributes(property: pointer) - expect(attributes.isWeak) == false - expect(attributes.isObject) == false - expect(attributes.isBlock) == false - expect(attributes.objectClass).to(beNil()) - } - } - } - } - } -} - -private class ObservableObject: NSObject { - dynamic var rac_value: Int = 0 -} - -private class NestedObservableObject: NSObject { - dynamic var rac_object: ObservableObject = ObservableObject() - dynamic weak var rac_weakObject: ObservableObject? -} - -private class TestAttributeQueryObject: NSObject { - @objc weak var weakProperty: NSObject? = nil - @objc var block: @convention(block) (NSObject) -> NSObject? = { _ in nil } - @objc let integer = 0 -} diff --git a/ReactiveCocoaTests/Swift/LifetimeSpec.swift b/ReactiveCocoaTests/Swift/LifetimeSpec.swift deleted file mode 100644 index a13514667a..0000000000 --- a/ReactiveCocoaTests/Swift/LifetimeSpec.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Quick -import Nimble -import ReactiveCocoa -import Result - -final class LifetimeSpec: QuickSpec { - override func spec() { - describe("NSObject.rac_lifetime") { - it("should complete its lifetime ended signal when the it deinitializes") { - let object = MutableReference(TestObject()) - - var isCompleted = false - - object.value!.lifetime.ended.observeCompleted { isCompleted = true } - expect(isCompleted) == false - - object.value = nil - expect(isCompleted) == true - } - - it("should complete its lifetime ended signal even if the lifetime object is being retained") { - let object = MutableReference(TestObject()) - let lifetime = object.value!.lifetime - - var isCompleted = false - - lifetime.ended.observeCompleted { isCompleted = true } - expect(isCompleted) == false - - object.value = nil - expect(isCompleted) == true - } - } - } -} - -internal final class MutableReference { - var value: Value? - init(_ value: Value?) { - self.value = value - } -} - -internal final class TestObject { - private let token = Lifetime.Token() - var lifetime: Lifetime { return Lifetime(token) } -} diff --git a/ReactiveCocoaTests/Swift/ObjectiveCBridgingSpec.swift b/ReactiveCocoaTests/Swift/ObjectiveCBridgingSpec.swift deleted file mode 100644 index fde257d82f..0000000000 --- a/ReactiveCocoaTests/Swift/ObjectiveCBridgingSpec.swift +++ /dev/null @@ -1,300 +0,0 @@ -// -// ObjectiveCBridgingSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2015-01-23. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -import ReactiveCocoa -import XCTest - -class ObjectiveCBridgingSpec: QuickSpec { - override func spec() { - describe("RACScheduler") { - var originalScheduler: RACTestScheduler! - var scheduler: DateSchedulerProtocol! - - beforeEach { - originalScheduler = RACTestScheduler() - scheduler = originalScheduler as DateSchedulerProtocol - } - - it("gives current date") { - expect(scheduler.currentDate).to(beCloseTo(Date(), within: 0.0002)) - } - - it("schedules actions") { - var actionRan: Bool = false - - scheduler.schedule { - actionRan = true - } - - expect(actionRan) == false - originalScheduler.step() - expect(actionRan) == true - } - - it("does not invoke action if disposed") { - var actionRan: Bool = false - - let disposable: Disposable? = scheduler.schedule { - actionRan = true - } - - expect(actionRan) == false - disposable!.dispose() - originalScheduler.step() - expect(actionRan) == false - } - } - - describe("RACSignal.toSignalProducer") { - it("should subscribe once per start()") { - var subscriptions = 0 - - let racSignal = RACSignal.createSignal { subscriber in - subscriber.sendNext(subscriptions) - subscriber.sendCompleted() - - subscriptions += 1 - - return nil - } - - let producer = racSignal.toSignalProducer().map { $0 as! Int } - - expect((producer.single())?.value) == 0 - expect((producer.single())?.value) == 1 - expect((producer.single())?.value) == 2 - } - - it("should forward errors") { - let error = TestError.default as NSError - - let racSignal = RACSignal.error(error) - let producer = racSignal.toSignalProducer() - let result = producer.last() - - expect(result?.error) == error - } - } - - describe("toRACSignal") { - let key = NSLocalizedDescriptionKey - let userInfo: [String: String] = [key: "TestValue"] - let testNSError = NSError(domain: "TestDomain", code: 1, userInfo: userInfo) - describe("on a Signal") { - it("should forward events") { - let (signal, observer) = Signal.pipe() - let racSignal = signal.toRACSignal() - - var lastValue: NSNumber? - var didComplete = false - - racSignal.subscribeNext({ number in - lastValue = number as? NSNumber - }, completed: { - didComplete = true - }) - - expect(lastValue).to(beNil()) - - for number in [1, 2, 3] { - observer.sendNext(number as NSNumber) - expect(lastValue) == number as NSNumber - } - - expect(didComplete) == false - observer.sendCompleted() - expect(didComplete) == true - } - - it("should convert errors to NSError") { - let (signal, observer) = Signal.pipe() - let racSignal = signal.toRACSignal() - - let expectedError = TestError.error2 - var error: TestError? - - racSignal.subscribeError { - error = $0 as? TestError - return - } - - observer.sendFailed(expectedError) - expect(error) == expectedError - } - - it("should maintain userInfo on NSError") { - let (signal, observer) = Signal.pipe() - let racSignal = signal.toRACSignal() - - var error: String? - - racSignal.subscribeError { - error = $0?.localizedDescription - return - } - - observer.sendFailed(testNSError) - - expect(error) == userInfo[key] - } - } - - describe("on a SignalProducer") { - it("should start once per subscription") { - var subscriptions = 0 - - let producer = SignalProducer.attempt { - defer { - subscriptions += 1 - } - - return .success(subscriptions as NSNumber) - } - let racSignal = producer.toRACSignal() - - expect(racSignal.first() as? NSNumber) == 0 - expect(racSignal.first() as? NSNumber) == 1 - expect(racSignal.first() as? NSNumber) == 2 - } - - it("should convert errors to NSError") { - let producer = SignalProducer(error: .error1) - let racSignal = producer.toRACSignal().materialize() - - let event = racSignal.first() as? RACEvent - expect(event?.error as? NSError) == TestError.error1 as NSError - } - - it("should maintain userInfo on NSError") { - let producer = SignalProducer(error: testNSError) - let racSignal = producer.toRACSignal().materialize() - - let event = racSignal.first() as? RACEvent - let userInfoValue = event?.error?.localizedDescription - expect(userInfoValue) == userInfo[key] - } - } - } - - describe("toAction") { - var command: RACCommand! - var results: [Int] = [] - - var enabledSubject: RACSubject! - var enabled = false - - var action: Action! - - beforeEach { - enabledSubject = RACSubject() - results = [] - - command = RACCommand(enabled: enabledSubject) { (input: AnyObject?) -> RACSignal in - let inputNumber = input as! Int - return RACSignal.`return`(inputNumber + 1) - } - - expect(command).notTo(beNil()) - - command.enabled.subscribeNext { enabled = $0 as! Bool } - expect(enabled) == true - - command.executionSignals.flatten().subscribeNext { results.append($0 as! Int) } - expect(results) == [] - - action = bridgedAction(from: command) - } - - it("should reflect the enabledness of the command") { - expect(action.isEnabled.value) == true - - enabledSubject.sendNext(false) - expect(enabled).toEventually(beFalsy()) - expect(action.isEnabled.value) == false - } - - it("should execute the command once per start()") { - let producer = action.apply(0 as NSNumber) - expect(results) == [] - - producer.start() - expect(results).toEventually(equal([ 1 ])) - - producer.start() - expect(results).toEventually(equal([ 1, 1 ])) - - let otherProducer = action.apply(2 as NSNumber) - expect(results) == [ 1, 1 ] - - otherProducer.start() - expect(results).toEventually(equal([ 1, 1, 3 ])) - - producer.start() - expect(results).toEventually(equal([ 1, 1, 3, 1 ])) - } - } - - describe("toRACCommand") { - var action: Action! - var results: [NSString] = [] - - var enabledProperty: MutableProperty! - - var command: RACCommand! - var enabled = false - - beforeEach { - results = [] - enabledProperty = MutableProperty(true) - - action = Action(enabledIf: enabledProperty) { input in - let inputNumber = input as! Int - return SignalProducer(value: "\(inputNumber + 1)" as NSString) - } - - expect(action.isEnabled.value) == true - - action.values.observeNext { results.append($0) } - - command = action.toRACCommand() - expect(command).notTo(beNil()) - - command.enabled.subscribeNext { enabled = $0 as! Bool } - expect(enabled) == true - } - - it("should reflect the enabledness of the action") { - enabledProperty.value = false - expect(enabled).toEventually(beFalsy()) - - enabledProperty.value = true - expect(enabled).toEventually(beTruthy()) - } - - it("should apply and start a signal once per execution") { - let signal = command.execute(0 as NSNumber) - - do { - try signal.asynchronouslyWaitUntilCompleted() - expect(results) == [ "1" ] - - try signal.asynchronouslyWaitUntilCompleted() - expect(results) == [ "1" ] - - try command.execute(2 as NSNumber).asynchronouslyWaitUntilCompleted() - expect(results) == [ "1", "3" ] - } catch { - XCTFail("Failed to wait for completion") - } - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/PropertySpec.swift b/ReactiveCocoaTests/Swift/PropertySpec.swift deleted file mode 100644 index 6fb9b42756..0000000000 --- a/ReactiveCocoaTests/Swift/PropertySpec.swift +++ /dev/null @@ -1,1778 +0,0 @@ -// -// PropertySpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2015-01-23. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -@testable import ReactiveCocoa - -private let initialPropertyValue = "InitialValue" -private let subsequentPropertyValue = "SubsequentValue" -private let finalPropertyValue = "FinalValue" - -private let initialOtherPropertyValue = "InitialOtherValue" -private let subsequentOtherPropertyValue = "SubsequentOtherValue" -private let finalOtherPropertyValue = "FinalOtherValue" - -class PropertySpec: QuickSpec { - override func spec() { - describe("MutableProperty") { - it("should have the value given at initialization") { - let mutableProperty = MutableProperty(initialPropertyValue) - - expect(mutableProperty.value) == initialPropertyValue - } - - it("should yield a producer that sends the current value then all changes") { - let mutableProperty = MutableProperty(initialPropertyValue) - var sentValue: String? - - mutableProperty.producer.startWithNext { sentValue = $0 } - - expect(sentValue) == initialPropertyValue - - mutableProperty.value = subsequentPropertyValue - expect(sentValue) == subsequentPropertyValue - - mutableProperty.value = finalPropertyValue - expect(sentValue) == finalPropertyValue - } - - it("should yield a producer that sends the current value then all changes, even if the value actually remains unchanged") { - let mutableProperty = MutableProperty(initialPropertyValue) - var count = 0 - - mutableProperty.producer.startWithNext { _ in count = count + 1 } - - expect(count) == 1 - - mutableProperty.value = initialPropertyValue - expect(count) == 2 - - mutableProperty.value = initialPropertyValue - expect(count) == 3 - } - - it("should yield a signal that emits subsequent changes to the value") { - let mutableProperty = MutableProperty(initialPropertyValue) - var sentValue: String? - - mutableProperty.signal.observeNext { sentValue = $0 } - - expect(sentValue).to(beNil()) - - mutableProperty.value = subsequentPropertyValue - expect(sentValue) == subsequentPropertyValue - - mutableProperty.value = finalPropertyValue - expect(sentValue) == finalPropertyValue - } - - it("should yield a signal that emits subsequent changes to the value, even if the value actually remains unchanged") { - let mutableProperty = MutableProperty(initialPropertyValue) - var count = 0 - - mutableProperty.signal.observeNext { _ in count = count + 1 } - - expect(count) == 0 - - mutableProperty.value = initialPropertyValue - expect(count) == 1 - - mutableProperty.value = initialPropertyValue - expect(count) == 2 - } - - it("should complete its producer when deallocated") { - var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) - var producerCompleted = false - - mutableProperty!.producer.startWithCompleted { producerCompleted = true } - - mutableProperty = nil - expect(producerCompleted) == true - } - - it("should complete its signal when deallocated") { - var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) - var signalCompleted = false - - mutableProperty!.signal.observeCompleted { signalCompleted = true } - - mutableProperty = nil - expect(signalCompleted) == true - } - - it("should yield a producer which emits the latest value and complete even if the property is deallocated") { - var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) - let producer = mutableProperty!.producer - - var producerCompleted = false - var hasUnanticipatedEvent = false - var latestValue = mutableProperty?.value - - mutableProperty!.value = subsequentPropertyValue - mutableProperty = nil - - producer.start { event in - switch event { - case let .next(value): - latestValue = value - case .completed: - producerCompleted = true - case .interrupted, .failed: - hasUnanticipatedEvent = true - } - } - - expect(hasUnanticipatedEvent) == false - expect(producerCompleted) == true - expect(latestValue) == subsequentPropertyValue - } - - it("should modify the value atomically") { - let property = MutableProperty(initialPropertyValue) - - property.modify { $0 = subsequentPropertyValue } - expect(property.value) == subsequentPropertyValue - } - - it("should modify the value atomically and subsquently send out a Next event with the new value") { - let property = MutableProperty(initialPropertyValue) - var value: String? - - property.producer.startWithNext { - value = $0 - } - - expect(value) == initialPropertyValue - - property.modify { $0 = subsequentPropertyValue } - expect(property.value) == subsequentPropertyValue - expect(value) == subsequentPropertyValue - } - - it("should swap the value atomically") { - let property = MutableProperty(initialPropertyValue) - - expect(property.swap(subsequentPropertyValue)) == initialPropertyValue - expect(property.value) == subsequentPropertyValue - } - - it("should swap the value atomically and subsquently send out a Next event with the new value") { - let property = MutableProperty(initialPropertyValue) - var value: String? - - property.producer.startWithNext { - value = $0 - } - - expect(value) == initialPropertyValue - expect(property.swap(subsequentPropertyValue)) == initialPropertyValue - - expect(property.value) == subsequentPropertyValue - expect(value) == subsequentPropertyValue - } - - it("should perform an action with the value") { - let property = MutableProperty(initialPropertyValue) - - let result: Bool = property.withValue { $0.isEmpty } - - expect(result) == false - expect(property.value) == initialPropertyValue - } - - it("should not deadlock on recursive value access") { - let (producer, observer) = SignalProducer.pipe() - let property = MutableProperty(0) - var value: Int? - - property <~ producer - property.producer.startWithNext { _ in - value = property.value - } - - observer.sendNext(10) - expect(value) == 10 - } - - it("should not deadlock on recursive value access with a closure") { - let (producer, observer) = SignalProducer.pipe() - let property = MutableProperty(0) - var value: Int? - - property <~ producer - property.producer.startWithNext { _ in - value = property.withValue { $0 + 1 } - } - - observer.sendNext(10) - expect(value) == 11 - } - - it("should not deadlock on recursive observation") { - let property = MutableProperty(0) - - var value: Int? - property.producer.startWithNext { _ in - property.producer.startWithNext { x in value = x } - } - - expect(value) == 0 - - property.value = 1 - expect(value) == 1 - } - - it("should not deadlock on recursive ABA observation") { - let propertyA = MutableProperty(0) - let propertyB = MutableProperty(0) - - var value: Int? - propertyA.producer.startWithNext { _ in - propertyB.producer.startWithNext { _ in - propertyA.producer.startWithNext { x in value = x } - } - } - - expect(value) == 0 - - propertyA.value = 1 - expect(value) == 1 - } - - it("should expose a lifetime that ends upon its deinitialization") { - var property = Optional(MutableProperty(1)) - - var isEnded = false - property!.lifetime.ended.observeCompleted { - isEnded = true - } - - expect(isEnded) == false - - property!.value = 2 - expect(isEnded) == false - - property = nil - expect(isEnded) == true - } - } - - describe("Property") { - describe("constant property") { - it("should have the value given at initialization") { - let constantProperty = Property(value: initialPropertyValue) - - expect(constantProperty.value) == initialPropertyValue - } - - it("should yield a signal that interrupts observers without emitting any value.") { - let constantProperty = Property(value: initialPropertyValue) - - var signalInterrupted = false - var hasUnexpectedEventsEmitted = false - - constantProperty.signal.observe { event in - switch event { - case .interrupted: - signalInterrupted = true - case .next, .failed, .completed: - hasUnexpectedEventsEmitted = true - } - } - - expect(signalInterrupted) == true - expect(hasUnexpectedEventsEmitted) == false - } - - it("should yield a producer that sends the current value then completes") { - let constantProperty = Property(value: initialPropertyValue) - - var sentValue: String? - var signalCompleted = false - - constantProperty.producer.start { event in - switch event { - case let .next(value): - sentValue = value - case .completed: - signalCompleted = true - case .failed, .interrupted: - break - } - } - - expect(sentValue) == initialPropertyValue - expect(signalCompleted) == true - } - } - - describe("existential property") { - it("should pass through behaviors of the wrapped property") { - let constantProperty = Property(value: initialPropertyValue) - let property = Property(constantProperty) - - var sentValue: String? - var signalSentValue: String? - var producerCompleted = false - var signalInterrupted = false - - property.producer.start { event in - switch event { - case let .next(value): - sentValue = value - case .completed: - producerCompleted = true - case .failed, .interrupted: - break - } - } - - property.signal.observe { event in - switch event { - case let .next(value): - signalSentValue = value - case .interrupted: - signalInterrupted = true - case .failed, .completed: - break - } - } - - expect(sentValue) == initialPropertyValue - expect(signalSentValue).to(beNil()) - expect(producerCompleted) == true - expect(signalInterrupted) == true - } - } - - describe("composed properties") { - describe("from properties") { - it("should have the latest value available before sending any value") { - var latestValue: Int! - - let property = MutableProperty(1) - let mappedProperty = property.map { $0 + 1 } - mappedProperty.producer.startWithNext { _ in latestValue = mappedProperty.value } - - expect(latestValue) == 2 - - property.value = 2 - expect(latestValue) == 3 - - property.value = 3 - expect(latestValue) == 4 - } - - it("should retain its source property") { - var property = Optional(MutableProperty(1)) - weak var weakProperty = property - - var firstMappedProperty = Optional(property!.map { $0 + 1 }) - var secondMappedProperty = Optional(firstMappedProperty!.map { $0 + 2 }) - - // Suppress the "written to but never read" warning on `secondMappedProperty`. - _ = secondMappedProperty - - property = nil - expect(weakProperty).toNot(beNil()) - - firstMappedProperty = nil - expect(weakProperty).toNot(beNil()) - - secondMappedProperty = nil - expect(weakProperty).to(beNil()) - } - - it("should transform property from a property that has a terminated producer") { - let property = Property(value: 1) - let transformedProperty = property.map { $0 + 1 } - - expect(transformedProperty.value) == 2 - } - - it("should return a producer and a signal which respect the lifetime of the source property instead of the read-only view itself") { - var signalCompleted = 0 - var producerCompleted = 0 - - var property = Optional(MutableProperty(1)) - var firstMappedProperty = Optional(property!.map { $0 + 1 }) - var secondMappedProperty = Optional(firstMappedProperty!.map { $0 + 2 }) - var thirdMappedProperty = Optional(secondMappedProperty!.map { $0 + 2 }) - - firstMappedProperty!.signal.observeCompleted { signalCompleted += 1 } - secondMappedProperty!.signal.observeCompleted { signalCompleted += 1 } - thirdMappedProperty!.signal.observeCompleted { signalCompleted += 1 } - - firstMappedProperty!.producer.startWithCompleted { producerCompleted += 1 } - secondMappedProperty!.producer.startWithCompleted { producerCompleted += 1 } - thirdMappedProperty!.producer.startWithCompleted { producerCompleted += 1 } - - firstMappedProperty = nil - expect(signalCompleted) == 0 - expect(producerCompleted) == 0 - - secondMappedProperty = nil - expect(signalCompleted) == 0 - expect(producerCompleted) == 0 - - property = nil - expect(signalCompleted) == 0 - expect(producerCompleted) == 0 - - thirdMappedProperty = nil - expect(signalCompleted) == 3 - expect(producerCompleted) == 3 - } - - it("should not capture intermediate properties but only the ultimate sources") { - func increment(input: Int) -> Int { - return input + 1 - } - - weak var weakSourceProperty: MutableProperty? - weak var weakPropertyA: Property? - weak var weakPropertyB: Property? - weak var weakPropertyC: Property? - - var finalProperty: Property! - - func scope() { - let property = MutableProperty(1) - weakSourceProperty = property - - let propertyA = property.map(increment) - weakPropertyA = propertyA - - let propertyB = propertyA.map(increment) - weakPropertyB = propertyB - - let propertyC = propertyB.map(increment) - weakPropertyC = propertyC - - finalProperty = propertyC.map(increment) - } - - scope() - - expect(finalProperty.value) == 5 - expect(weakSourceProperty).toNot(beNil()) - expect(weakPropertyA).to(beNil()) - expect(weakPropertyB).to(beNil()) - expect(weakPropertyC).to(beNil()) - } - } - - describe("from a value and SignalProducer") { - it("should initially take on the supplied value") { - let property = Property(initial: initialPropertyValue, - then: SignalProducer.never) - - expect(property.value) == initialPropertyValue - } - - it("should take on each value sent on the producer") { - let property = Property(initial: initialPropertyValue, - then: SignalProducer(value: subsequentPropertyValue)) - - expect(property.value) == subsequentPropertyValue - } - - it("should return a producer and a signal that respect the lifetime of its ultimate source") { - var signalCompleted = false - var producerCompleted = false - var signalInterrupted = false - - let (signal, observer) = Signal.pipe() - var property: Property? = Property(initial: 1, - then: SignalProducer(signal: signal)) - let propertySignal = property!.signal - - propertySignal.observeCompleted { signalCompleted = true } - property!.producer.startWithCompleted { producerCompleted = true } - - expect(property!.value) == 1 - - observer.sendNext(2) - expect(property!.value) == 2 - expect(producerCompleted) == false - expect(signalCompleted) == false - - property = nil - expect(producerCompleted) == false - expect(signalCompleted) == false - - observer.sendCompleted() - expect(producerCompleted) == true - expect(signalCompleted) == true - - propertySignal.observeInterrupted { signalInterrupted = true } - expect(signalInterrupted) == true - } - } - - describe("from a value and Signal") { - it("should initially take on the supplied value, then values sent on the signal") { - let (signal, observer) = Signal.pipe() - - let property = Property(initial: initialPropertyValue, - then: signal) - - expect(property.value) == initialPropertyValue - - observer.sendNext(subsequentPropertyValue) - - expect(property.value) == subsequentPropertyValue - } - - - it("should return a producer and a signal that respect the lifetime of its ultimate source") { - var signalCompleted = false - var producerCompleted = false - var signalInterrupted = false - - let (signal, observer) = Signal.pipe() - var property: Property? = Property(initial: 1, - then: signal) - let propertySignal = property!.signal - - propertySignal.observeCompleted { signalCompleted = true } - property!.producer.startWithCompleted { producerCompleted = true } - - expect(property!.value) == 1 - - observer.sendNext(2) - expect(property!.value) == 2 - expect(producerCompleted) == false - expect(signalCompleted) == false - - property = nil - expect(producerCompleted) == false - expect(signalCompleted) == false - - observer.sendCompleted() - expect(producerCompleted) == true - expect(signalCompleted) == true - - propertySignal.observeInterrupted { signalInterrupted = true } - expect(signalInterrupted) == true - } - } - } - } - - describe("PropertyProtocol") { - describe("map") { - it("should transform the current value and all subsequent values") { - let property = MutableProperty(1) - let mappedProperty = property - .map { $0 + 1 } - expect(mappedProperty.value) == 2 - - property.value = 2 - expect(mappedProperty.value) == 3 - } - } - - describe("combineLatest") { - var property: MutableProperty! - var otherProperty: MutableProperty! - - beforeEach { - property = MutableProperty(initialPropertyValue) - otherProperty = MutableProperty(initialOtherPropertyValue) - } - - it("should forward the latest values from both inputs") { - let combinedProperty = property.combineLatest(with: otherProperty) - var latest: (String, String)? - combinedProperty.signal.observeNext { latest = $0 } - - property.value = subsequentPropertyValue - expect(latest?.0) == subsequentPropertyValue - expect(latest?.1) == initialOtherPropertyValue - - // is there a better way to test tuples? - otherProperty.value = subsequentOtherPropertyValue - expect(latest?.0) == subsequentPropertyValue - expect(latest?.1) == subsequentOtherPropertyValue - - property.value = finalPropertyValue - expect(latest?.0) == finalPropertyValue - expect(latest?.1) == subsequentOtherPropertyValue - } - - it("should complete when the source properties are deinitialized") { - var completed = false - - var combinedProperty = Optional(property.combineLatest(with: otherProperty)) - combinedProperty!.signal.observeCompleted { completed = true } - - combinedProperty = nil - expect(completed) == false - - property = nil - expect(completed) == false - - otherProperty = nil - expect(completed) == true - } - - it("should be consistent between its cached value and its values producer") { - var firstResult: String! - var secondResult: String! - - let combined = property.combineLatest(with: otherProperty) - combined.producer.startWithNext { (left, right) in firstResult = left + right } - - func getValue() -> String { - return combined.value.0 + combined.value.1 - } - - expect(getValue()) == initialPropertyValue + initialOtherPropertyValue - expect(firstResult) == initialPropertyValue + initialOtherPropertyValue - - property.value = subsequentPropertyValue - expect(getValue()) == subsequentPropertyValue + initialOtherPropertyValue - expect(firstResult) == subsequentPropertyValue + initialOtherPropertyValue - - combined.producer.startWithNext { (left, right) in secondResult = left + right } - expect(secondResult) == subsequentPropertyValue + initialOtherPropertyValue - - otherProperty.value = subsequentOtherPropertyValue - expect(getValue()) == subsequentPropertyValue + subsequentOtherPropertyValue - expect(firstResult) == subsequentPropertyValue + subsequentOtherPropertyValue - expect(secondResult) == subsequentPropertyValue + subsequentOtherPropertyValue - } - - it("should be consistent between nested combined properties") { - let A = MutableProperty(1) - let B = MutableProperty(100) - let C = MutableProperty(10000) - - var firstResult: Int! - - let combined = A.combineLatest(with: B) - combined.producer.startWithNext { (left, right) in firstResult = left + right } - - func getValue() -> Int { - return combined.value.0 + combined.value.1 - } - - /// Initial states - expect(getValue()) == 101 - expect(firstResult) == 101 - - A.value = 2 - expect(getValue()) == 102 - expect(firstResult) == 102 - - B.value = 200 - expect(getValue()) == 202 - expect(firstResult) == 202 - - /// Setup - A.value = 3 - expect(getValue()) == 203 - expect(firstResult) == 203 - - /// Zip another property now. - var secondResult: Int! - let anotherCombined = combined.combineLatest(with: C) - anotherCombined.producer.startWithNext { (left, right) in secondResult = (left.0 + left.1) + right } - - func getAnotherValue() -> Int { - return (anotherCombined.value.0.0 + anotherCombined.value.0.1) + anotherCombined.value.1 - } - - expect(getAnotherValue()) == 10203 - - A.value = 4 - expect(getValue()) == 204 - expect(getAnotherValue()) == 10204 - } - } - - describe("zip") { - var property: MutableProperty! - var otherProperty: MutableProperty! - - beforeEach { - property = MutableProperty(initialPropertyValue) - otherProperty = MutableProperty(initialOtherPropertyValue) - } - - it("should combine pairs") { - var result: [String] = [] - - let zippedProperty = property.zip(with: otherProperty) - zippedProperty.producer.startWithNext { (left, right) in result.append("\(left)\(right)") } - - let firstResult = [ "\(initialPropertyValue)\(initialOtherPropertyValue)" ] - let secondResult = firstResult + [ "\(subsequentPropertyValue)\(subsequentOtherPropertyValue)" ] - let thirdResult = secondResult + [ "\(finalPropertyValue)\(finalOtherPropertyValue)" ] - let finalResult = thirdResult + [ "\(initialPropertyValue)\(initialOtherPropertyValue)" ] - - expect(result) == firstResult - - property.value = subsequentPropertyValue - expect(result) == firstResult - - otherProperty.value = subsequentOtherPropertyValue - expect(result) == secondResult - - property.value = finalPropertyValue - otherProperty.value = finalOtherPropertyValue - expect(result) == thirdResult - - property.value = initialPropertyValue - expect(result) == thirdResult - - property.value = subsequentPropertyValue - expect(result) == thirdResult - - otherProperty.value = initialOtherPropertyValue - expect(result) == finalResult - } - - it("should be consistent between its cached value and its values producer") { - var firstResult: String! - var secondResult: String! - - let zippedProperty = property.zip(with: otherProperty) - zippedProperty.producer.startWithNext { (left, right) in firstResult = left + right } - - func getValue() -> String { - return zippedProperty.value.0 + zippedProperty.value.1 - } - - expect(getValue()) == initialPropertyValue + initialOtherPropertyValue - expect(firstResult) == initialPropertyValue + initialOtherPropertyValue - - property.value = subsequentPropertyValue - expect(getValue()) == initialPropertyValue + initialOtherPropertyValue - expect(firstResult) == initialPropertyValue + initialOtherPropertyValue - - // It should still be the tuple with initial property values, - // since `otherProperty` isn't changed yet. - zippedProperty.producer.startWithNext { (left, right) in secondResult = left + right } - expect(secondResult) == initialPropertyValue + initialOtherPropertyValue - - otherProperty.value = subsequentOtherPropertyValue - expect(getValue()) == subsequentPropertyValue + subsequentOtherPropertyValue - expect(firstResult) == subsequentPropertyValue + subsequentOtherPropertyValue - expect(secondResult) == subsequentPropertyValue + subsequentOtherPropertyValue - } - - it("should be consistent between nested zipped properties") { - let A = MutableProperty(1) - let B = MutableProperty(100) - let C = MutableProperty(10000) - - var firstResult: Int! - - let zipped = A.zip(with: B) - zipped.producer.startWithNext { (left, right) in firstResult = left + right } - - func getValue() -> Int { - return zipped.value.0 + zipped.value.1 - } - - /// Initial states - expect(getValue()) == 101 - expect(firstResult) == 101 - - A.value = 2 - expect(getValue()) == 101 - expect(firstResult) == 101 - - B.value = 200 - expect(getValue()) == 202 - expect(firstResult) == 202 - - /// Setup - A.value = 3 - expect(getValue()) == 202 - expect(firstResult) == 202 - - /// Zip another property now. - var secondResult: Int! - let anotherZipped = zipped.zip(with: C) - anotherZipped.producer.startWithNext { (left, right) in secondResult = (left.0 + left.1) + right } - - func getAnotherValue() -> Int { - return (anotherZipped.value.0.0 + anotherZipped.value.0.1) + anotherZipped.value.1 - } - - /// Since `zipped` is 202 now, and `C` is 10000, - /// shouldn't this be 10202? - - /// Verify `zipped` again. - expect(getValue()) == 202 - expect(firstResult) == 202 - - /// Then... well, no. Surprise! (Only before #3042) - /// We get 10203 here. - /// - /// https://github.com/ReactiveCocoa/ReactiveCocoa/pull/3042 - expect(getAnotherValue()) == 10202 - } - - it("should be consistent between combined and nested zipped properties") { - let A = MutableProperty(1) - let B = MutableProperty(100) - let C = MutableProperty(10000) - let D = MutableProperty(1000000) - - var firstResult: Int! - - let zipped = A.zip(with: B) - zipped.producer.startWithNext { (left, right) in firstResult = left + right } - - func getValue() -> Int { - return zipped.value.0 + zipped.value.1 - } - - /// Initial states - expect(getValue()) == 101 - expect(firstResult) == 101 - - A.value = 2 - expect(getValue()) == 101 - expect(firstResult) == 101 - - B.value = 200 - expect(getValue()) == 202 - expect(firstResult) == 202 - - /// Setup - A.value = 3 - expect(getValue()) == 202 - expect(firstResult) == 202 - - /// Zip another property now. - var secondResult: Int! - let anotherZipped = zipped.zip(with: C) - anotherZipped.producer.startWithNext { (left, right) in secondResult = (left.0 + left.1) + right } - - func getAnotherValue() -> Int { - return (anotherZipped.value.0.0 + anotherZipped.value.0.1) + anotherZipped.value.1 - } - - /// Verify `zipped` again. - expect(getValue()) == 202 - expect(firstResult) == 202 - - expect(getAnotherValue()) == 10202 - - /// Zip `D` with `anotherZipped`. - let yetAnotherZipped = anotherZipped.zip(with: D) - - /// Combine with another property. - /// (((Int, Int), Int), (((Int, Int), Int), Int)) - let combined = anotherZipped.combineLatest(with: yetAnotherZipped) - - var thirdResult: Int! - combined.producer.startWithNext { (left, right) in - let leftResult = left.0.0 + left.0.1 + left.1 - let rightResult = right.0.0.0 + right.0.0.1 + right.0.1 + right.1 - thirdResult = leftResult + rightResult - } - - expect(thirdResult) == 1020404 - } - - it("should complete its producer only when the source properties are deinitialized") { - var result: [String] = [] - var completed = false - - var zippedProperty = Optional(property.zip(with: otherProperty)) - zippedProperty!.producer.start { event in - switch event { - case let .next(left, right): - result.append("\(left)\(right)") - case .completed: - completed = true - default: - break - } - } - - expect(completed) == false - expect(result) == [ "\(initialPropertyValue)\(initialOtherPropertyValue)" ] - - property.value = subsequentPropertyValue - expect(result) == [ "\(initialPropertyValue)\(initialOtherPropertyValue)" ] - - zippedProperty = nil - expect(completed) == false - - property = nil - otherProperty = nil - expect(completed) == true - } - } - - describe("unary operators") { - var property: MutableProperty! - - beforeEach { - property = MutableProperty(initialPropertyValue) - } - - describe("combinePrevious") { - it("should pack the current value and the previous value a tuple") { - let transformedProperty = property.combinePrevious(initialPropertyValue) - - expect(transformedProperty.value.0) == initialPropertyValue - expect(transformedProperty.value.1) == initialPropertyValue - - property.value = subsequentPropertyValue - - expect(transformedProperty.value.0) == initialPropertyValue - expect(transformedProperty.value.1) == subsequentPropertyValue - - property.value = finalPropertyValue - - expect(transformedProperty.value.0) == subsequentPropertyValue - expect(transformedProperty.value.1) == finalPropertyValue - } - - it("should complete its producer only when the source property is deinitialized") { - var result: (String, String)? - var completed = false - - var transformedProperty = Optional(property.combinePrevious(initialPropertyValue)) - transformedProperty!.producer.start { event in - switch event { - case let .next(tuple): - result = tuple - case .completed: - completed = true - default: - break - } - } - - expect(result?.0) == initialPropertyValue - expect(result?.1) == initialPropertyValue - - property.value = subsequentPropertyValue - - expect(result?.0) == initialPropertyValue - expect(result?.1) == subsequentPropertyValue - - transformedProperty = nil - expect(completed) == false - - property = nil - expect(completed) == true - } - } - - describe("skipRepeats") { - it("should not emit events for subsequent equatable values that are the same as the current value") { - let transformedProperty = property.skipRepeats() - - var counter = 0 - transformedProperty.signal.observeNext { _ in - counter += 1 - } - - property.value = initialPropertyValue - property.value = initialPropertyValue - property.value = initialPropertyValue - - expect(counter) == 0 - - property.value = subsequentPropertyValue - property.value = subsequentPropertyValue - property.value = subsequentPropertyValue - - expect(counter) == 1 - - property.value = finalPropertyValue - property.value = initialPropertyValue - property.value = subsequentPropertyValue - - expect(counter) == 4 - } - - it("should not emit events for subsequent values that are regarded as the same as the current value by the supplied closure") { - var counter = 0 - let transformedProperty = property.skipRepeats { _, newValue in newValue == initialPropertyValue } - - transformedProperty.signal.observeNext { _ in - counter += 1 - } - - property.value = initialPropertyValue - expect(counter) == 0 - - property.value = subsequentPropertyValue - expect(counter) == 1 - - property.value = finalPropertyValue - expect(counter) == 2 - - property.value = initialPropertyValue - expect(counter) == 2 - } - - it("should complete its producer only when the source property is deinitialized") { - var counter = 0 - var completed = false - - var transformedProperty = Optional(property.skipRepeats()) - transformedProperty!.producer.start { event in - switch event { - case .next: - counter += 1 - case .completed: - completed = true - default: - break - } - } - - expect(counter) == 1 - - property.value = initialPropertyValue - expect(counter) == 1 - - transformedProperty = nil - expect(completed) == false - - property = nil - expect(completed) == true - } - } - - describe("uniqueValues") { - it("should emit hashable values that have not been emited before") { - let transformedProperty = property.uniqueValues() - - var counter = 0 - transformedProperty.signal.observeNext { _ in - counter += 1 - } - - property.value = initialPropertyValue - expect(counter) == 0 - - property.value = subsequentPropertyValue - property.value = subsequentPropertyValue - - expect(counter) == 1 - - property.value = finalPropertyValue - property.value = initialPropertyValue - property.value = subsequentPropertyValue - - expect(counter) == 2 - } - - it("should emit only the values of which the computed identity have not been captured before") { - let transformedProperty = property.uniqueValues { _ in 0 } - - var counter = 0 - transformedProperty.signal.observeNext { _ in - counter += 1 - } - - property.value = initialPropertyValue - property.value = subsequentPropertyValue - property.value = finalPropertyValue - expect(counter) == 0 - } - - it("should complete its producer only when the source property is deinitialized") { - var counter = 0 - var completed = false - - var transformedProperty = Optional(property.uniqueValues()) - transformedProperty!.producer.start { event in - switch event { - case .next: - counter += 1 - case .completed: - completed = true - default: - break - } - } - - expect(counter) == 1 - - property.value = initialPropertyValue - expect(counter) == 1 - - transformedProperty = nil - expect(completed) == false - - property = nil - expect(completed) == true - } - } - } - - describe("flattening") { - describe("flatten") { - describe("FlattenStrategy.concat") { - it("should concatenate the values as the inner property is replaced and deinitialized") { - var firstProperty = Optional(MutableProperty(0)) - var secondProperty = Optional(MutableProperty(10)) - var thirdProperty = Optional(MutableProperty(20)) - - var outerProperty = Optional(MutableProperty(firstProperty!)) - - var receivedValues: [Int] = [] - var errored = false - var completed = false - - var flattenedProperty = Optional(outerProperty!.flatten(.concat)) - - flattenedProperty!.producer.start { event in - switch event { - case let .next(value): - receivedValues.append(value) - case .completed: - completed = true - case .failed: - errored = true - case .interrupted: - break - } - } - - expect(receivedValues) == [ 0 ] - - outerProperty!.value = secondProperty! - secondProperty!.value = 11 - outerProperty!.value = thirdProperty! - thirdProperty!.value = 21 - - expect(receivedValues) == [ 0 ] - expect(completed) == false - - secondProperty!.value = 12 - thirdProperty!.value = 22 - - expect(receivedValues) == [ 0 ] - expect(completed) == false - - firstProperty = nil - - expect(receivedValues) == [ 0, 12 ] - expect(completed) == false - - secondProperty = nil - - expect(receivedValues) == [ 0, 12, 22 ] - expect(completed) == false - - outerProperty = nil - expect(completed) == false - - thirdProperty = nil - expect(completed) == false - - flattenedProperty = nil - expect(completed) == true - expect(errored) == false - } - } - - describe("FlattenStrategy.merge") { - it("should merge the values of all inner properties") { - var firstProperty = Optional(MutableProperty(0)) - var secondProperty = Optional(MutableProperty(10)) - var thirdProperty = Optional(MutableProperty(20)) - - var outerProperty = Optional(MutableProperty(firstProperty!)) - - var receivedValues: [Int] = [] - var errored = false - var completed = false - - var flattenedProperty = Optional(outerProperty!.flatten(.merge)) - - flattenedProperty!.producer.start { event in - switch event { - case let .next(value): - receivedValues.append(value) - case .completed: - completed = true - case .failed: - errored = true - case .interrupted: - break - } - } - - expect(receivedValues) == [ 0 ] - - outerProperty!.value = secondProperty! - secondProperty!.value = 11 - outerProperty!.value = thirdProperty! - thirdProperty!.value = 21 - - expect(receivedValues) == [ 0, 10, 11, 20, 21 ] - expect(completed) == false - - secondProperty!.value = 12 - thirdProperty!.value = 22 - - expect(receivedValues) == [ 0, 10, 11, 20, 21, 12, 22 ] - expect(completed) == false - - firstProperty = nil - - expect(receivedValues) == [ 0, 10, 11, 20, 21, 12, 22 ] - expect(completed) == false - - secondProperty = nil - - expect(receivedValues) == [ 0, 10, 11, 20, 21, 12, 22 ] - expect(completed) == false - - outerProperty = nil - expect(completed) == false - - thirdProperty = nil - expect(completed) == false - - flattenedProperty = nil - expect(completed) == true - expect(errored) == false - } - } - - describe("FlattenStrategy.latest") { - it("should forward values from the latest inner property") { - let firstProperty = Optional(MutableProperty(0)) - var secondProperty = Optional(MutableProperty(10)) - var thirdProperty = Optional(MutableProperty(20)) - - var outerProperty = Optional(MutableProperty(firstProperty!)) - - var receivedValues: [Int] = [] - var errored = false - var completed = false - - outerProperty!.flatten(.latest).producer.start { event in - switch event { - case let .next(value): - receivedValues.append(value) - case .completed: - completed = true - case .failed: - errored = true - case .interrupted: - break - } - } - - expect(receivedValues) == [ 0 ] - - outerProperty!.value = secondProperty! - secondProperty!.value = 11 - outerProperty!.value = thirdProperty! - thirdProperty!.value = 21 - - expect(receivedValues) == [ 0, 10, 11, 20, 21 ] - expect(errored) == false - expect(completed) == false - - secondProperty!.value = 12 - secondProperty = nil - thirdProperty!.value = 22 - thirdProperty = nil - - expect(receivedValues) == [ 0, 10, 11, 20, 21, 22 ] - expect(errored) == false - expect(completed) == false - - outerProperty = nil - expect(errored) == false - expect(completed) == true - } - - it("should release the old properties when switched or deallocated") { - var firstProperty = Optional(MutableProperty(0)) - var secondProperty = Optional(MutableProperty(10)) - var thirdProperty = Optional(MutableProperty(20)) - - weak var weakFirstProperty = firstProperty - weak var weakSecondProperty = secondProperty - weak var weakThirdProperty = thirdProperty - - var outerProperty = Optional(MutableProperty(firstProperty!)) - var flattened = Optional(outerProperty!.flatten(.latest)) - - var errored = false - var completed = false - - flattened!.producer.start { event in - switch event { - case .completed: - completed = true - case .failed: - errored = true - case .interrupted, .next: - break - } - } - - firstProperty = nil - outerProperty!.value = secondProperty! - expect(weakFirstProperty).to(beNil()) - - secondProperty = nil - outerProperty!.value = thirdProperty! - expect(weakSecondProperty).to(beNil()) - - thirdProperty = nil - outerProperty = nil - flattened = nil - expect(weakThirdProperty).to(beNil()) - expect(errored) == false - expect(completed) == true - } - } - } - - describe("flatMap") { - describe("PropertyFlattenStrategy.latest") { - it("should forward values from the latest inner transformed property") { - let firstProperty = Optional(MutableProperty(0)) - var secondProperty = Optional(MutableProperty(10)) - var thirdProperty = Optional(MutableProperty(20)) - - var outerProperty = Optional(MutableProperty(firstProperty!)) - - var receivedValues: [String] = [] - var errored = false - var completed = false - - outerProperty!.flatMap(.latest) { $0.map { "\($0)" } }.producer.start { event in - switch event { - case let .next(value): - receivedValues.append(value) - case .completed: - completed = true - case .failed: - errored = true - case .interrupted: - break - } - } - - expect(receivedValues) == [ "0" ] - - outerProperty!.value = secondProperty! - secondProperty!.value = 11 - outerProperty!.value = thirdProperty! - thirdProperty!.value = 21 - - expect(receivedValues) == [ "0", "10", "11", "20", "21" ] - expect(errored) == false - expect(completed) == false - - secondProperty!.value = 12 - secondProperty = nil - thirdProperty!.value = 22 - thirdProperty = nil - - expect(receivedValues) == [ "0", "10", "11", "20", "21", "22" ] - expect(errored) == false - expect(completed) == false - - outerProperty = nil - expect(errored) == false - expect(completed) == true - } - } - } - } - } - - describe("DynamicProperty") { - var object: ObservableObject! - var property: DynamicProperty! - - let propertyValue: () -> Int? = { - if let value: Any = property?.value { - return value as? Int - } else { - return nil - } - } - - beforeEach { - object = ObservableObject() - expect(object.rac_value) == 0 - - property = DynamicProperty(object: object, keyPath: "rac_value") - } - - afterEach { - object = nil - } - - it("should read the underlying object") { - expect(propertyValue()) == 0 - - object.rac_value = 1 - expect(propertyValue()) == 1 - } - - it("should write the underlying object") { - property.value = 1 - expect(object.rac_value) == 1 - expect(propertyValue()) == 1 - } - - it("should yield a producer that sends the current value and then the changes for the key path of the underlying object") { - var values: [Int] = [] - property.producer.startWithNext { value in - expect(value).notTo(beNil()) - values.append(value!) - } - - expect(values) == [ 0 ] - - property.value = 1 - expect(values) == [ 0, 1 ] - - object.rac_value = 2 - expect(values) == [ 0, 1, 2 ] - } - - it("should yield a producer that sends the current value and then the changes for the key path of the underlying object, even if the value actually remains unchanged") { - var values: [Int] = [] - property.producer.startWithNext { value in - expect(value).notTo(beNil()) - values.append(value!) - } - - expect(values) == [ 0 ] - - property.value = 0 - expect(values) == [ 0, 0 ] - - object.rac_value = 0 - expect(values) == [ 0, 0, 0 ] - } - - it("should yield a signal that emits subsequent values for the key path of the underlying object") { - var values: [Int] = [] - property.signal.observeNext { value in - expect(value).notTo(beNil()) - values.append(value!) - } - - expect(values) == [] - - property.value = 1 - expect(values) == [ 1 ] - - object.rac_value = 2 - expect(values) == [ 1, 2 ] - } - - it("should yield a signal that emits subsequent values for the key path of the underlying object, even if the value actually remains unchanged") { - var values: [Int] = [] - property.signal.observeNext { value in - expect(value).notTo(beNil()) - values.append(value!) - } - - expect(values) == [] - - property.value = 0 - expect(values) == [ 0 ] - - object.rac_value = 0 - expect(values) == [ 0, 0 ] - } - - it("should have a completed producer when the underlying object deallocates") { - var completed = false - - property = { - // Use a closure so this object has a shorter lifetime. - let object = ObservableObject() - let property = DynamicProperty(object: object, keyPath: "rac_value") - - property.producer.startWithCompleted { - completed = true - } - - expect(completed) == false - expect(property.value).notTo(beNil()) - return property - }() - - expect(completed).toEventually(beTruthy()) - expect(property.value).to(beNil()) - } - - it("should have a completed signal when the underlying object deallocates") { - var completed = false - - property = { - // Use a closure so this object has a shorter lifetime. - let object = ObservableObject() - let property = DynamicProperty(object: object, keyPath: "rac_value") - - property.signal.observeCompleted { - completed = true - } - - expect(completed) == false - expect(property.value).notTo(beNil()) - return property - }() - - expect(completed).toEventually(beTruthy()) - expect(property.value).to(beNil()) - } - - it("should retain property while DynamicProperty's underlying object is retained"){ - weak var dynamicProperty: DynamicProperty? = property - - property = nil - expect(dynamicProperty).toNot(beNil()) - - object = nil - expect(dynamicProperty).to(beNil()) - } - - it("should support un-bridged reference types") { - let dynamicProperty = DynamicProperty(object: object, keyPath: "rac_reference") - dynamicProperty.value = UnbridgedObject("foo") - expect(object.rac_reference.value) == "foo" - } - - it("should expose a lifetime that ends upon the deinitialization of its underlying object") { - var isEnded = false - property!.lifetime.ended.observeCompleted { - isEnded = true - } - - expect(isEnded) == false - - property = nil - expect(isEnded) == false - - object = nil - expect(isEnded) == true - } - } - - describe("binding") { - describe("from a Signal") { - it("should update the property with values sent from the signal") { - let (signal, observer) = Signal.pipe() - - let mutableProperty = MutableProperty(initialPropertyValue) - - mutableProperty <~ signal - - // Verify that the binding hasn't changed the property value: - expect(mutableProperty.value) == initialPropertyValue - - observer.sendNext(subsequentPropertyValue) - expect(mutableProperty.value) == subsequentPropertyValue - } - - it("should tear down the binding when disposed") { - let (signal, observer) = Signal.pipe() - - let mutableProperty = MutableProperty(initialPropertyValue) - - let bindingDisposable = mutableProperty <~ signal - bindingDisposable!.dispose() - - observer.sendNext(subsequentPropertyValue) - expect(mutableProperty.value) == initialPropertyValue - } - - it("should tear down the binding when the property deallocates") { - var signal: Signal? = { - let (signal, _) = Signal.pipe() - return signal - }() - weak var weakSignal = signal - - var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) - - mutableProperty! <~ signal! - signal = nil - - // The binding attached an observer to the signal, so it cannot - // deinitialize. - expect(weakSignal).toNot(beNil()) - - // The deinitialization should tear down the binding, which would - // remove the last observer from the signal, causing it to - // dispose of itself. - mutableProperty = nil - expect(weakSignal).to(beNil()) - } - } - - describe("from a SignalProducer") { - it("should start a signal and update the property with its values") { - let signalValues = [initialPropertyValue, subsequentPropertyValue] - let signalProducer = SignalProducer(values: signalValues) - - let mutableProperty = MutableProperty(initialPropertyValue) - - mutableProperty <~ signalProducer - - expect(mutableProperty.value) == signalValues.last! - } - - it("should tear down the binding when disposed") { - let (signalProducer, observer) = SignalProducer.pipe() - - let mutableProperty = MutableProperty(initialPropertyValue) - let disposable = mutableProperty <~ signalProducer - - disposable.dispose() - - observer.sendNext(subsequentPropertyValue) - expect(mutableProperty.value) == initialPropertyValue - } - - it("should tear down the binding when the property deallocates") { - let (signal, _) = Signal.pipe() - let signalProducer = SignalProducer(signal: signal) - - var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) - - var isDisposed = false - mutableProperty! <~ signalProducer.on(disposed: { isDisposed = true }) - expect(isDisposed) == false - - mutableProperty = nil - expect(isDisposed) == true - } - } - - describe("from another property") { - it("should take the source property's current value") { - let sourceProperty = Property(value: initialPropertyValue) - - let destinationProperty = MutableProperty("") - - destinationProperty <~ sourceProperty.producer - - expect(destinationProperty.value) == initialPropertyValue - } - - it("should update with changes to the source property's value") { - let sourceProperty = MutableProperty(initialPropertyValue) - - let destinationProperty = MutableProperty("") - - destinationProperty <~ sourceProperty.producer - - sourceProperty.value = subsequentPropertyValue - expect(destinationProperty.value) == subsequentPropertyValue - } - - it("should tear down the binding when disposed") { - let sourceProperty = MutableProperty(initialPropertyValue) - - let destinationProperty = MutableProperty("") - - let bindingDisposable = destinationProperty <~ sourceProperty.producer - bindingDisposable.dispose() - - sourceProperty.value = subsequentPropertyValue - - expect(destinationProperty.value) == initialPropertyValue - } - - it("should tear down the binding when the source property deallocates") { - var sourceProperty: MutableProperty? = MutableProperty(initialPropertyValue) - - let destinationProperty = MutableProperty("") - destinationProperty <~ sourceProperty!.producer - - sourceProperty = nil - // TODO: Assert binding was torn down? - } - - it("should tear down the binding when the destination property deallocates") { - let sourceProperty = MutableProperty(initialPropertyValue) - var destinationProperty: MutableProperty? = MutableProperty("") - - var isDisposed = false - destinationProperty! <~ sourceProperty.producer.on(disposed: { isDisposed = true }) - expect(isDisposed) == false - - destinationProperty = nil - expect(isDisposed) == true - } - } - - describe("to a dynamic property") { - var object: ObservableObject! - var property: DynamicProperty! - - beforeEach { - object = ObservableObject() - expect(object.rac_value) == 0 - - property = DynamicProperty(object: object, keyPath: "rac_value") - } - - afterEach { - object = nil - } - - it("should bridge values sent on a signal to Objective-C") { - let (signal, observer) = Signal.pipe() - property <~ signal - observer.sendNext(1) - expect(object.rac_value) == 1 - } - - it("should bridge values sent on a signal producer to Objective-C") { - let producer = SignalProducer(value: 1) - property <~ producer - expect(object.rac_value) == 1 - } - - it("should bridge values from a source property to Objective-C") { - let source = MutableProperty(1) - property <~ source - expect(object.rac_value) == 1 - } - } - } - } -} - -private class ObservableObject: NSObject { - dynamic var rac_value: Int = 0 - dynamic var rac_reference: UnbridgedObject = UnbridgedObject("") -} - -private class UnbridgedObject: NSObject { - let value: String - init(_ value: String) { - self.value = value - } -} diff --git a/ReactiveCocoaTests/Swift/SchedulerSpec.swift b/ReactiveCocoaTests/Swift/SchedulerSpec.swift deleted file mode 100644 index 5c5850ef7c..0000000000 --- a/ReactiveCocoaTests/Swift/SchedulerSpec.swift +++ /dev/null @@ -1,298 +0,0 @@ -// -// SchedulerSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2014-07-13. -// Copyright (c) 2014 GitHub. All rights reserved. -// - -import Foundation -import Nimble -import Quick -@testable -import ReactiveCocoa - -class SchedulerSpec: QuickSpec { - override func spec() { - describe("ImmediateScheduler") { - it("should run enqueued actions immediately") { - var didRun = false - ImmediateScheduler().schedule { - didRun = true - } - - expect(didRun) == true - } - } - - describe("UIScheduler") { - func dispatchSyncInBackground(_ action: @escaping () -> Void) { - let group = DispatchGroup() - - let globalQueue: DispatchQueue - if #available(*, OSX 10.10) { - globalQueue = DispatchQueue.global() - } else { - globalQueue = DispatchQueue.global(priority: .default) - } - - globalQueue.async(group: group, execute: action) - group.wait() - } - - it("should run actions immediately when on the main thread") { - let scheduler = UIScheduler() - var values: [Int] = [] - expect(Thread.isMainThread) == true - - scheduler.schedule { - values.append(0) - } - - expect(values) == [ 0 ] - - scheduler.schedule { - values.append(1) - } - - scheduler.schedule { - values.append(2) - } - - expect(values) == [ 0, 1, 2 ] - } - - it("should enqueue actions scheduled from the background") { - let scheduler = UIScheduler() - var values: [Int] = [] - - dispatchSyncInBackground { - scheduler.schedule { - expect(Thread.isMainThread) == true - values.append(0) - } - - return - } - - expect(values) == [] - expect(values).toEventually(equal([ 0 ])) - - dispatchSyncInBackground { - scheduler.schedule { - expect(Thread.isMainThread) == true - values.append(1) - } - - scheduler.schedule { - expect(Thread.isMainThread) == true - values.append(2) - } - - return - } - - expect(values) == [ 0 ] - expect(values).toEventually(equal([ 0, 1, 2 ])) - } - - it("should run actions enqueued from the main thread after those from the background") { - let scheduler = UIScheduler() - var values: [Int] = [] - - dispatchSyncInBackground { - scheduler.schedule { - expect(Thread.isMainThread) == true - values.append(0) - } - - return - } - - scheduler.schedule { - expect(Thread.isMainThread) == true - values.append(1) - } - - scheduler.schedule { - expect(Thread.isMainThread) == true - values.append(2) - } - - expect(values) == [] - expect(values).toEventually(equal([ 0, 1, 2 ])) - } - } - - describe("QueueScheduler") { - it("should run enqueued actions on a global queue") { - var didRun = false - - let scheduler: QueueScheduler - if #available(OSX 10.10, *) { - scheduler = QueueScheduler(qos: .default, name: "\(#file):\(#line)") - } else { - scheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - scheduler.schedule { - didRun = true - expect(Thread.isMainThread) == false - } - - expect{didRun}.toEventually(beTruthy()) - } - - describe("on a given queue") { - var scheduler: QueueScheduler! - - beforeEach { - if #available(OSX 10.10, *) { - scheduler = QueueScheduler(qos: .default, name: "\(#file):\(#line)") - } else { - scheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - scheduler.queue.suspend() - } - - it("should run enqueued actions serially on the given queue") { - var value = 0 - - for _ in 0..<5 { - scheduler.schedule { - expect(Thread.isMainThread) == false - value += 1 - } - } - - expect(value) == 0 - - scheduler.queue.resume() - expect{value}.toEventually(equal(5)) - } - - it("should run enqueued actions after a given date") { - var didRun = false - scheduler.schedule(after: Date()) { - didRun = true - expect(Thread.isMainThread) == false - } - - expect(didRun) == false - - scheduler.queue.resume() - expect{didRun}.toEventually(beTruthy()) - } - - it("should repeatedly run actions after a given date") { - let disposable = SerialDisposable() - - var count = 0 - let timesToRun = 3 - - disposable.innerDisposable = scheduler.schedule(after: Date(), interval: 0.01, leeway: 0) { - expect(Thread.isMainThread) == false - - count += 1 - - if count == timesToRun { - disposable.dispose() - } - } - - expect(count) == 0 - - scheduler.queue.resume() - expect{count}.toEventually(equal(timesToRun)) - } - } - } - - describe("TestScheduler") { - var scheduler: TestScheduler! - var startDate: Date! - - // How much dates are allowed to differ when they should be "equal." - let dateComparisonDelta = 0.00001 - - beforeEach { - startDate = Date() - - scheduler = TestScheduler(startDate: startDate) - expect(scheduler.currentDate) == startDate - } - - it("should run immediately enqueued actions upon advancement") { - var string = "" - - scheduler.schedule { - string += "foo" - expect(Thread.isMainThread) == true - } - - scheduler.schedule { - string += "bar" - expect(Thread.isMainThread) == true - } - - expect(string) == "" - - scheduler.advance() - expect(scheduler.currentDate).to(beCloseTo(startDate)) - - expect(string) == "foobar" - } - - it("should run actions when advanced past the target date") { - var string = "" - - scheduler.schedule(after: 15) { [weak scheduler] in - string += "bar" - expect(Thread.isMainThread) == true - expect(scheduler?.currentDate).to(beCloseTo(startDate.addingTimeInterval(15), within: dateComparisonDelta)) - } - - scheduler.schedule(after: 5) { [weak scheduler] in - string += "foo" - expect(Thread.isMainThread) == true - expect(scheduler?.currentDate).to(beCloseTo(startDate.addingTimeInterval(5), within: dateComparisonDelta)) - } - - expect(string) == "" - - scheduler.advance(by: 10) - expect(scheduler.currentDate).to(beCloseTo(startDate.addingTimeInterval(10), within: TimeInterval(dateComparisonDelta))) - expect(string) == "foo" - - scheduler.advance(by: 10) - expect(scheduler.currentDate).to(beCloseTo(startDate.addingTimeInterval(20), within: dateComparisonDelta)) - expect(string) == "foobar" - } - - it("should run all remaining actions in order") { - var string = "" - - scheduler.schedule(after: 15) { - string += "bar" - expect(Thread.isMainThread) == true - } - - scheduler.schedule(after: 5) { - string += "foo" - expect(Thread.isMainThread) == true - } - - scheduler.schedule { - string += "fuzzbuzz" - expect(Thread.isMainThread) == true - } - - expect(string) == "" - - scheduler.run() - expect(scheduler.currentDate) == NSDate.distantFuture - expect(string) == "fuzzbuzzfoobar" - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/SignalLifetimeSpec.swift b/ReactiveCocoaTests/Swift/SignalLifetimeSpec.swift deleted file mode 100644 index 30ad5f86b5..0000000000 --- a/ReactiveCocoaTests/Swift/SignalLifetimeSpec.swift +++ /dev/null @@ -1,414 +0,0 @@ -// -// SignalLifetimeSpec.swift -// ReactiveCocoa -// -// Created by Vadim Yelagin on 2015-12-13. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -import ReactiveCocoa - -class SignalLifetimeSpec: QuickSpec { - override func spec() { - describe("init") { - var testScheduler: TestScheduler! - - beforeEach { - testScheduler = TestScheduler() - } - - it("should deallocate") { - weak var signal: Signal? = Signal { _ in nil } - - expect(signal).to(beNil()) - } - - it("should deallocate if it does not have any observers") { - weak var signal: Signal? = { - let signal: Signal = Signal { _ in nil } - return signal - }() - expect(signal).to(beNil()) - } - - it("should deallocate if no one retains it") { - var signal: Signal? = Signal { _ in nil } - weak var weakSignal = signal - - expect(weakSignal).toNot(beNil()) - - var reference = signal - signal = nil - expect(weakSignal).toNot(beNil()) - - reference = nil - expect(weakSignal).to(beNil()) - } - - it("should deallocate even if the generator observer is retained") { - var observer: Signal.Observer? - - weak var signal: Signal? = { - let signal: Signal = Signal { innerObserver in - observer = innerObserver - return nil - } - return signal - }() - expect(observer).toNot(beNil()) - expect(signal).to(beNil()) - } - - it("should not deallocate if it has at least one observer") { - var disposable: Disposable? = nil - weak var signal: Signal? = { - let signal: Signal = Signal { _ in nil } - disposable = signal.observe(Observer()) - return signal - }() - expect(signal).toNot(beNil()) - disposable?.dispose() - expect(signal).to(beNil()) - } - - it("should be alive until erroring if it has at least one observer, despite not being explicitly retained") { - var errored = false - - weak var signal: Signal? = { - let signal = Signal { observer in - testScheduler.schedule { - observer.sendFailed(TestError.default) - } - return nil - } - signal.observeFailed { _ in errored = true } - return signal - }() - - expect(errored) == false - expect(signal).toNot(beNil()) - - testScheduler.run() - - expect(errored) == true - expect(signal).to(beNil()) - } - - it("should be alive until completion if it has at least one observer, despite not being explicitly retained") { - var completed = false - - weak var signal: Signal? = { - let signal = Signal { observer in - testScheduler.schedule { - observer.sendCompleted() - } - return nil - } - signal.observeCompleted { completed = true } - return signal - }() - - expect(completed) == false - expect(signal).toNot(beNil()) - - testScheduler.run() - - expect(completed) == true - expect(signal).to(beNil()) - } - - it("should be alive until interruption if it has at least one observer, despite not being explicitly retained") { - var interrupted = false - - weak var signal: Signal? = { - let signal = Signal { observer in - testScheduler.schedule { - observer.sendInterrupted() - } - - return nil - } - signal.observeInterrupted { interrupted = true } - return signal - }() - - expect(interrupted) == false - expect(signal).toNot(beNil()) - - testScheduler.run() - - expect(interrupted) == true - expect(signal).to(beNil()) - } - } - - describe("Signal.pipe") { - it("should deallocate") { - weak var signal = Signal<(), NoError>.pipe().0 - - expect(signal).to(beNil()) - } - - it("should be alive until erroring if it has at least one observer, despite not being explicitly retained") { - let testScheduler = TestScheduler() - var errored = false - weak var weakSignal: Signal<(), TestError>? - - // Use an inner closure to help ARC deallocate things as we - // expect. - let test = { - let (signal, observer) = Signal<(), TestError>.pipe() - weakSignal = signal - testScheduler.schedule { - // Note that the input observer has a weak reference to the signal. - observer.sendFailed(TestError.default) - } - signal.observeFailed { _ in errored = true } - } - test() - - expect(weakSignal).toNot(beNil()) - expect(errored) == false - - testScheduler.run() - expect(weakSignal).to(beNil()) - expect(errored) == true - } - - it("should be alive until completion if it has at least one observer, despite not being explicitly retained") { - let testScheduler = TestScheduler() - var completed = false - weak var weakSignal: Signal<(), TestError>? - - // Use an inner closure to help ARC deallocate things as we - // expect. - let test = { - let (signal, observer) = Signal<(), TestError>.pipe() - weakSignal = signal - testScheduler.schedule { - // Note that the input observer has a weak reference to the signal. - observer.sendCompleted() - } - signal.observeCompleted { completed = true } - } - test() - - expect(weakSignal).toNot(beNil()) - expect(completed) == false - - testScheduler.run() - expect(weakSignal).to(beNil()) - expect(completed) == true - } - - it("should be alive until interruption if it has at least one observer, despite not being explicitly retained") { - let testScheduler = TestScheduler() - var interrupted = false - weak var weakSignal: Signal<(), NoError>? - - let test = { - let (signal, observer) = Signal<(), NoError>.pipe() - weakSignal = signal - - testScheduler.schedule { - // Note that the input observer has a weak reference to the signal. - observer.sendInterrupted() - } - - signal.observeInterrupted { interrupted = true } - } - - test() - expect(weakSignal).toNot(beNil()) - expect(interrupted) == false - - testScheduler.run() - expect(weakSignal).to(beNil()) - expect(interrupted) == true - } - } - - describe("testTransform") { - it("should deallocate") { - weak var signal: Signal? = Signal { _ in nil }.testTransform() - - expect(signal).to(beNil()) - } - - it("should not deallocate if it has at least one observer, despite not being explicitly retained") { - weak var signal: Signal? = { - let signal: Signal = Signal { _ in nil }.testTransform() - signal.observe(Observer()) - return signal - }() - expect(signal).toNot(beNil()) - } - - it("should not deallocate if it has at least one observer, despite not being explicitly retained") { - var disposable: Disposable? = nil - weak var signal: Signal? = { - let signal: Signal = Signal { _ in nil }.testTransform() - disposable = signal.observe(Observer()) - return signal - }() - expect(signal).toNot(beNil()) - disposable?.dispose() - expect(signal).to(beNil()) - } - - it("should deallocate if it is unreachable and has no observer") { - let (sourceSignal, sourceObserver) = Signal.pipe() - - var firstCounter = 0 - var secondCounter = 0 - var thirdCounter = 0 - - func run() { - _ = sourceSignal - .map { value -> Int in - firstCounter += 1 - return value - } - .map { value -> Int in - secondCounter += 1 - return value - } - .map { value -> Int in - thirdCounter += 1 - return value - } - } - - run() - - sourceObserver.sendNext(1) - expect(firstCounter) == 0 - expect(secondCounter) == 0 - expect(thirdCounter) == 0 - - sourceObserver.sendNext(2) - expect(firstCounter) == 0 - expect(secondCounter) == 0 - expect(thirdCounter) == 0 - } - - it("should not deallocate if it is unreachable but still has at least one observer") { - let (sourceSignal, sourceObserver) = Signal.pipe() - - var firstCounter = 0 - var secondCounter = 0 - var thirdCounter = 0 - - var disposable: Disposable? - - func run() { - disposable = sourceSignal - .map { value -> Int in - firstCounter += 1 - return value - } - .map { value -> Int in - secondCounter += 1 - return value - } - .map { value -> Int in - thirdCounter += 1 - return value - } - .observe { _ in } - } - - run() - - sourceObserver.sendNext(1) - expect(firstCounter) == 1 - expect(secondCounter) == 1 - expect(thirdCounter) == 1 - - sourceObserver.sendNext(2) - expect(firstCounter) == 2 - expect(secondCounter) == 2 - expect(thirdCounter) == 2 - - disposable?.dispose() - - sourceObserver.sendNext(3) - expect(firstCounter) == 2 - expect(secondCounter) == 2 - expect(thirdCounter) == 2 - } - } - - describe("observe") { - var signal: Signal! - var observer: Signal.Observer! - - var token: NSObject? = nil - weak var weakToken: NSObject? - - func expectTokenNotDeallocated() { - expect(weakToken).toNot(beNil()) - } - - func expectTokenDeallocated() { - expect(weakToken).to(beNil()) - } - - beforeEach { - let (signalTemp, observerTemp) = Signal.pipe() - signal = signalTemp - observer = observerTemp - - token = NSObject() - weakToken = token - - signal.observe { [token = token] _ in - _ = token!.description - } - } - - it("should deallocate observe handler when signal completes") { - expectTokenNotDeallocated() - - observer.sendNext(1) - expectTokenNotDeallocated() - - token = nil - expectTokenNotDeallocated() - - observer.sendNext(2) - expectTokenNotDeallocated() - - observer.sendCompleted() - expectTokenDeallocated() - } - - it("should deallocate observe handler when signal fails") { - expectTokenNotDeallocated() - - observer.sendNext(1) - expectTokenNotDeallocated() - - token = nil - expectTokenNotDeallocated() - - observer.sendNext(2) - expectTokenNotDeallocated() - - observer.sendFailed(.default) - expectTokenDeallocated() - } - } - } -} - -private extension SignalProtocol { - func testTransform() -> Signal { - return Signal { observer in - return self.observe(observer.action) - } - } -} diff --git a/ReactiveCocoaTests/Swift/SignalProducerLiftingSpec.swift b/ReactiveCocoaTests/Swift/SignalProducerLiftingSpec.swift deleted file mode 100644 index deafd94ee9..0000000000 --- a/ReactiveCocoaTests/Swift/SignalProducerLiftingSpec.swift +++ /dev/null @@ -1,1536 +0,0 @@ -// -// SignalProducerLiftingSpec.swift -// ReactiveCocoa -// -// Created by Neil Pankey on 6/14/15. -// Copyright © 2015 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -import ReactiveCocoa - -class SignalProducerLiftingSpec: QuickSpec { - override func spec() { - describe("map") { - it("should transform the values of the signal") { - let (producer, observer) = SignalProducer.pipe() - let mappedProducer = producer.map { String($0 + 1) } - - var lastValue: String? - - mappedProducer.startWithNext { - lastValue = $0 - return - } - - expect(lastValue).to(beNil()) - - observer.sendNext(0) - expect(lastValue) == "1" - - observer.sendNext(1) - expect(lastValue) == "2" - } - } - - describe("mapError") { - it("should transform the errors of the signal") { - let (producer, observer) = SignalProducer.pipe() - let producerError = NSError(domain: "com.reactivecocoa.errordomain", code: 100, userInfo: nil) - var error: NSError? - - producer - .mapError { _ in producerError } - .startWithFailed { error = $0 } - - expect(error).to(beNil()) - - observer.sendFailed(TestError.default) - expect(error) == producerError - } - } - - describe("filter") { - it("should omit values from the producer") { - let (producer, observer) = SignalProducer.pipe() - let mappedProducer = producer.filter { $0 % 2 == 0 } - - var lastValue: Int? - - mappedProducer.startWithNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext(0) - expect(lastValue) == 0 - - observer.sendNext(1) - expect(lastValue) == 0 - - observer.sendNext(2) - expect(lastValue) == 2 - } - } - - describe("skipNil") { - it("should forward only non-nil values") { - let (producer, observer) = SignalProducer.pipe() - let mappedProducer = producer.skipNil() - - var lastValue: Int? - - mappedProducer.startWithNext { lastValue = $0 } - expect(lastValue).to(beNil()) - - observer.sendNext(nil) - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(nil) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - } - } - - describe("scan") { - it("should incrementally accumulate a value") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.scan("", +) - - var lastValue: String? - - producer.startWithNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext("a") - expect(lastValue) == "a" - - observer.sendNext("bb") - expect(lastValue) == "abb" - } - } - - describe("reduce") { - it("should accumulate one value") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.reduce(1, +) - - var lastValue: Int? - var completed = false - - producer.start { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue).to(beNil()) - - expect(completed) == false - observer.sendCompleted() - expect(completed) == true - - expect(lastValue) == 4 - } - - it("should send the initial value if none are received") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.reduce(1, +) - - var lastValue: Int? - var completed = false - - producer.start { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - expect(lastValue).to(beNil()) - expect(completed) == false - - observer.sendCompleted() - - expect(lastValue) == 1 - expect(completed) == true - } - } - - describe("skip") { - it("should skip initial values") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.skip(first: 1) - - var lastValue: Int? - producer.startWithNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue) == 2 - } - - it("should not skip any values when 0") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.skip(first: 0) - - var lastValue: Int? - producer.startWithNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - } - } - - describe("skipRepeats") { - it("should skip duplicate Equatable values") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.skipRepeats() - - var values: [Bool] = [] - producer.startWithNext { values.append($0) } - - expect(values) == [] - - observer.sendNext(true) - expect(values) == [ true ] - - observer.sendNext(true) - expect(values) == [ true ] - - observer.sendNext(false) - expect(values) == [ true, false ] - - observer.sendNext(true) - expect(values) == [ true, false, true ] - } - - it("should skip values according to a predicate") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.skipRepeats { $0.characters.count == $1.characters.count } - - var values: [String] = [] - producer.startWithNext { values.append($0) } - - expect(values) == [] - - observer.sendNext("a") - expect(values) == [ "a" ] - - observer.sendNext("b") - expect(values) == [ "a" ] - - observer.sendNext("cc") - expect(values) == [ "a", "cc" ] - - observer.sendNext("d") - expect(values) == [ "a", "cc", "d" ] - } - } - - describe("skipWhile") { - var producer: SignalProducer! - var observer: Signal.Observer! - - var lastValue: Int? - - beforeEach { - let (baseProducer, incomingObserver) = SignalProducer.pipe() - - producer = baseProducer.skip { $0 < 2 } - observer = incomingObserver - lastValue = nil - - producer.startWithNext { lastValue = $0 } - } - - it("should skip while the predicate is true") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue) == 2 - - observer.sendNext(0) - expect(lastValue) == 0 - } - - it("should not skip any values when the predicate starts false") { - expect(lastValue).to(beNil()) - - observer.sendNext(3) - expect(lastValue) == 3 - - observer.sendNext(1) - expect(lastValue) == 1 - } - } - - describe("skipUntil") { - var producer: SignalProducer! - var observer: Signal.Observer! - var triggerObserver: Signal<(), NoError>.Observer! - - var lastValue: Int? = nil - - beforeEach { - let (baseProducer, baseIncomingObserver) = SignalProducer.pipe() - let (triggerProducer, incomingTriggerObserver) = SignalProducer<(), NoError>.pipe() - - producer = baseProducer.skip(until: triggerProducer) - observer = baseIncomingObserver - triggerObserver = incomingTriggerObserver - - lastValue = nil - - producer.start { event in - switch event { - case let .next(value): - lastValue = value - case .failed, .completed, .interrupted: - break - } - } - } - - it("should skip values until the trigger fires") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue).to(beNil()) - - triggerObserver.sendNext(()) - observer.sendNext(0) - expect(lastValue) == 0 - } - - it("should skip values until the trigger completes") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue).to(beNil()) - - triggerObserver.sendCompleted() - observer.sendNext(0) - expect(lastValue) == 0 - } - } - - describe("take") { - it("should take initial values") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.take(first: 2) - - var lastValue: Int? - var completed = false - producer.start { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - expect(lastValue).to(beNil()) - expect(completed) == false - - observer.sendNext(1) - expect(lastValue) == 1 - expect(completed) == false - - observer.sendNext(2) - expect(lastValue) == 2 - expect(completed) == true - } - - it("should complete immediately after taking given number of values") { - let numbers = [ 1, 2, 4, 4, 5 ] - let testScheduler = TestScheduler() - - let producer: SignalProducer = SignalProducer { observer, _ in - // workaround `Class declaration cannot close over value 'observer' defined in outer scope` - let observer = observer - - testScheduler.schedule { - for number in numbers { - observer.sendNext(number) - } - } - } - - var completed = false - - producer - .take(first: numbers.count) - .startWithCompleted { completed = true } - - expect(completed) == false - testScheduler.run() - expect(completed) == true - } - - it("should interrupt when 0") { - let numbers = [ 1, 2, 4, 4, 5 ] - let testScheduler = TestScheduler() - - let producer: SignalProducer = SignalProducer { observer, _ in - // workaround `Class declaration cannot close over value 'observer' defined in outer scope` - let observer = observer - - testScheduler.schedule { - for number in numbers { - observer.sendNext(number) - } - } - } - - var result: [Int] = [] - var interrupted = false - - producer - .take(first: 0) - .start { event in - switch event { - case let .next(number): - result.append(number) - case .interrupted: - interrupted = true - case .failed, .completed: - break - } - } - - expect(interrupted) == true - - testScheduler.run() - expect(result).to(beEmpty()) - } - } - - describe("collect") { - it("should collect all values") { - let (original, observer) = SignalProducer.pipe() - let producer = original.collect() - let expectedResult = [ 1, 2, 3 ] - - var result: [Int]? - - producer.startWithNext { value in - expect(result).to(beNil()) - result = value - } - - for number in expectedResult { - observer.sendNext(number) - } - - expect(result).to(beNil()) - observer.sendCompleted() - expect(result) == expectedResult - } - - it("should complete with an empty array if there are no values") { - let (original, observer) = SignalProducer.pipe() - let producer = original.collect() - - var result: [Int]? - - producer.startWithNext { result = $0 } - - expect(result).to(beNil()) - observer.sendCompleted() - expect(result) == [] - } - - it("should forward errors") { - let (original, observer) = SignalProducer.pipe() - let producer = original.collect() - - var error: TestError? - - producer.startWithFailed { error = $0 } - - expect(error).to(beNil()) - observer.sendFailed(.default) - expect(error) == TestError.default - } - - it("should collect an exact count of values") { - let (original, observer) = SignalProducer.pipe() - - let producer = original.collect(count: 3) - - var observedValues: [[Int]] = [] - - producer.startWithNext { value in - observedValues.append(value) - } - - var expectation: [[Int]] = [] - - for i in 1...7 { - - observer.sendNext(i) - - if i % 3 == 0 { - expectation.append([Int]((i - 2)...i)) - expect(observedValues as NSArray) == expectation as NSArray - } else { - expect(observedValues as NSArray) == expectation as NSArray - } - } - - observer.sendCompleted() - - expectation.append([7]) - expect(observedValues as NSArray) == expectation as NSArray - } - - it("should collect values until it matches a certain value") { - let (original, observer) = SignalProducer.pipe() - - let producer = original.collect { _, next in next != 5 } - - var expectedValues = [ - [5, 5], - [42, 5] - ] - - producer.startWithNext { value in - expect(value) == expectedValues.removeFirst() - } - - producer.startWithCompleted { - expect(expectedValues as NSArray) == [] as NSArray - } - - expectedValues - .flatMap { $0 } - .forEach(observer.sendNext) - - observer.sendCompleted() - } - - it("should collect values until it matches a certain condition on values") { - let (original, observer) = SignalProducer.pipe() - - let producer = original.collect { values in values.reduce(0, +) == 10 } - - var expectedValues = [ - [1, 2, 3, 4], - [5, 6, 7, 8, 9] - ] - - producer.startWithNext { value in - expect(value) == expectedValues.removeFirst() - } - - producer.startWithCompleted { - expect(expectedValues as NSArray) == [] as NSArray - } - - expectedValues - .flatMap { $0 } - .forEach(observer.sendNext) - - observer.sendCompleted() - } - - } - - describe("takeUntil") { - var producer: SignalProducer! - var observer: Signal.Observer! - var triggerObserver: Signal<(), NoError>.Observer! - - var lastValue: Int? = nil - var completed: Bool = false - - beforeEach { - let (baseProducer, baseIncomingObserver) = SignalProducer.pipe() - let (triggerProducer, incomingTriggerObserver) = SignalProducer<(), NoError>.pipe() - - producer = baseProducer.take(until: triggerProducer) - observer = baseIncomingObserver - triggerObserver = incomingTriggerObserver - - lastValue = nil - completed = false - - producer.start { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - } - - it("should take values until the trigger fires") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - expect(completed) == false - triggerObserver.sendNext(()) - expect(completed) == true - } - - it("should take values until the trigger completes") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - expect(completed) == false - triggerObserver.sendCompleted() - expect(completed) == true - } - - it("should complete if the trigger fires immediately") { - expect(lastValue).to(beNil()) - expect(completed) == false - - triggerObserver.sendNext(()) - - expect(completed) == true - expect(lastValue).to(beNil()) - } - } - - describe("takeUntilReplacement") { - var producer: SignalProducer! - var observer: Signal.Observer! - var replacementObserver: Signal.Observer! - - var lastValue: Int? = nil - var completed: Bool = false - - beforeEach { - let (baseProducer, incomingObserver) = SignalProducer.pipe() - let (replacementProducer, incomingReplacementObserver) = SignalProducer.pipe() - - producer = baseProducer.take(untilReplacement: replacementProducer) - observer = incomingObserver - replacementObserver = incomingReplacementObserver - - lastValue = nil - completed = false - - producer.start { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - } - - it("should take values from the original then the replacement") { - expect(lastValue).to(beNil()) - expect(completed) == false - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - replacementObserver.sendNext(3) - - expect(lastValue) == 3 - expect(completed) == false - - observer.sendNext(4) - - expect(lastValue) == 3 - expect(completed) == false - - replacementObserver.sendNext(5) - expect(lastValue) == 5 - - expect(completed) == false - replacementObserver.sendCompleted() - expect(completed) == true - } - } - - describe("takeWhile") { - var producer: SignalProducer! - var observer: Signal.Observer! - - beforeEach { - let (baseProducer, incomingObserver) = SignalProducer.pipe() - producer = baseProducer.take { $0 <= 4 } - observer = incomingObserver - } - - it("should take while the predicate is true") { - var latestValue: Int! - var completed = false - - producer.start { event in - switch event { - case let .next(value): - latestValue = value - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - for value in -1...4 { - observer.sendNext(value) - expect(latestValue) == value - expect(completed) == false - } - - observer.sendNext(5) - expect(latestValue) == 4 - expect(completed) == true - } - - it("should complete if the predicate starts false") { - var latestValue: Int? - var completed = false - - producer.start { event in - switch event { - case let .next(value): - latestValue = value - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - observer.sendNext(5) - expect(latestValue).to(beNil()) - expect(completed) == true - } - } - - describe("observeOn") { - it("should send events on the given scheduler") { - let testScheduler = TestScheduler() - let (producer, observer) = SignalProducer.pipe() - - var result: [Int] = [] - - producer - .observe(on: testScheduler) - .startWithNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - expect(result).to(beEmpty()) - - testScheduler.run() - expect(result) == [ 1, 2 ] - } - } - - describe("delay") { - it("should send events on the given scheduler after the interval") { - let testScheduler = TestScheduler() - let producer: SignalProducer = SignalProducer { observer, _ in - testScheduler.schedule { - observer.sendNext(1) - } - testScheduler.schedule(after: 5) { - observer.sendNext(2) - observer.sendCompleted() - } - } - - var result: [Int] = [] - var completed = false - - producer - .delay(10, on: testScheduler) - .start { event in - switch event { - case let .next(number): - result.append(number) - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - testScheduler.advance(by: 4) // send initial value - expect(result).to(beEmpty()) - - testScheduler.advance(by: 10) // send second value and receive first - expect(result) == [ 1 ] - expect(completed) == false - - testScheduler.advance(by: 10) // send second value and receive first - expect(result) == [ 1, 2 ] - expect(completed) == true - } - - it("should schedule errors immediately") { - let testScheduler = TestScheduler() - let producer: SignalProducer = SignalProducer { observer, _ in - // workaround `Class declaration cannot close over value 'observer' defined in outer scope` - let observer = observer - - testScheduler.schedule { - observer.sendFailed(TestError.default) - } - } - - var errored = false - - producer - .delay(10, on: testScheduler) - .startWithFailed { _ in errored = true } - - testScheduler.advance() - expect(errored) == true - } - } - - describe("throttle") { - var scheduler: TestScheduler! - var observer: Signal.Observer! - var producer: SignalProducer! - - beforeEach { - scheduler = TestScheduler() - - let (baseProducer, baseObserver) = SignalProducer.pipe() - observer = baseObserver - - producer = baseProducer.throttle(1, on: scheduler) - } - - it("should send values on the given scheduler at no less than the interval") { - var values: [Int] = [] - producer.startWithNext { value in - values.append(value) - } - - expect(values) == [] - - observer.sendNext(0) - expect(values) == [] - - scheduler.advance() - expect(values) == [ 0 ] - - observer.sendNext(1) - observer.sendNext(2) - expect(values) == [ 0 ] - - scheduler.advance(by: 1.5) - expect(values) == [ 0, 2 ] - - scheduler.advance(by: 3) - expect(values) == [ 0, 2 ] - - observer.sendNext(3) - expect(values) == [ 0, 2 ] - - scheduler.advance() - expect(values) == [ 0, 2, 3 ] - - observer.sendNext(4) - observer.sendNext(5) - scheduler.advance() - expect(values) == [ 0, 2, 3 ] - - scheduler.rewind(by: 2) - expect(values) == [ 0, 2, 3 ] - - observer.sendNext(6) - scheduler.advance() - expect(values) == [ 0, 2, 3, 6 ] - - observer.sendNext(7) - observer.sendNext(8) - scheduler.advance() - expect(values) == [ 0, 2, 3, 6 ] - - scheduler.run() - expect(values) == [ 0, 2, 3, 6, 8 ] - } - - it("should schedule completion immediately") { - var values: [Int] = [] - var completed = false - - producer.start { event in - switch event { - case let .next(value): - values.append(value) - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - observer.sendNext(0) - scheduler.advance() - expect(values) == [ 0 ] - - observer.sendNext(1) - observer.sendCompleted() - expect(completed) == false - - scheduler.run() - expect(values) == [ 0 ] - expect(completed) == true - } - } - - describe("sampleWith") { - var sampledProducer: SignalProducer<(Int, String), NoError>! - var observer: Signal.Observer! - var samplerObserver: Signal.Observer! - - beforeEach { - let (producer, incomingObserver) = SignalProducer.pipe() - let (sampler, incomingSamplerObserver) = SignalProducer.pipe() - sampledProducer = producer.sample(with: sampler) - observer = incomingObserver - samplerObserver = incomingSamplerObserver - } - - it("should forward the latest value when the sampler fires") { - var result: [String] = [] - sampledProducer.startWithNext { (left, right) in result.append("\(left)\(right)") } - - observer.sendNext(1) - observer.sendNext(2) - samplerObserver.sendNext("a") - expect(result) == [ "2a" ] - } - - it("should do nothing if sampler fires before signal receives value") { - var result: [String] = [] - sampledProducer.startWithNext { (left, right) in result.append("\(left)\(right)") } - - samplerObserver.sendNext("a") - expect(result).to(beEmpty()) - } - - it("should send lates value multiple times when sampler fires multiple times") { - var result: [String] = [] - sampledProducer.startWithNext { (left, right) in result.append("\(left)\(right)") } - - observer.sendNext(1) - samplerObserver.sendNext("a") - samplerObserver.sendNext("b") - expect(result) == [ "1a", "1b" ] - } - - it("should complete when both inputs have completed") { - var completed = false - sampledProducer.startWithCompleted { completed = true } - - observer.sendCompleted() - expect(completed) == false - - samplerObserver.sendCompleted() - expect(completed) == true - } - - it("should emit an initial value if the sampler is a synchronous SignalProducer") { - let producer = SignalProducer(values: [1]) - let sampler = SignalProducer(value: "a") - - let result = producer.sample(with: sampler) - - var valueReceived: String? - result.startWithNext { (left, right) in valueReceived = "\(left)\(right)" } - - expect(valueReceived) == "1a" - } - } - - describe("sampleOn") { - var sampledProducer: SignalProducer! - var observer: Signal.Observer! - var samplerObserver: Signal<(), NoError>.Observer! - - beforeEach { - let (producer, incomingObserver) = SignalProducer.pipe() - let (sampler, incomingSamplerObserver) = SignalProducer<(), NoError>.pipe() - sampledProducer = producer.sample(on: sampler) - observer = incomingObserver - samplerObserver = incomingSamplerObserver - } - - it("should forward the latest value when the sampler fires") { - var result: [Int] = [] - sampledProducer.startWithNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - samplerObserver.sendNext(()) - expect(result) == [ 2 ] - } - - it("should do nothing if sampler fires before signal receives value") { - var result: [Int] = [] - sampledProducer.startWithNext { result.append($0) } - - samplerObserver.sendNext(()) - expect(result).to(beEmpty()) - } - - it("should send lates value multiple times when sampler fires multiple times") { - var result: [Int] = [] - sampledProducer.startWithNext { result.append($0) } - - observer.sendNext(1) - samplerObserver.sendNext(()) - samplerObserver.sendNext(()) - expect(result) == [ 1, 1 ] - } - - it("should complete when both inputs have completed") { - var completed = false - sampledProducer.startWithCompleted { completed = true } - - observer.sendCompleted() - expect(completed) == false - - samplerObserver.sendCompleted() - expect(completed) == true - } - - it("should emit an initial value if the sampler is a synchronous SignalProducer") { - let producer = SignalProducer(values: [1]) - let sampler = SignalProducer<(), NoError>(value: ()) - - let result = producer.sample(on: sampler) - - var valueReceived: Int? - result.startWithNext { valueReceived = $0 } - - expect(valueReceived) == 1 - } - - describe("memory") { - class Payload { - let action: () -> Void - - init(onDeinit action: @escaping () -> Void) { - self.action = action - } - - deinit { - action() - } - } - - var sampledProducer: SignalProducer! - var observer: Signal.Observer! - - beforeEach { - let (producer, incomingObserver) = SignalProducer.pipe() - let (sampler, _) = Signal<(), NoError>.pipe() - sampledProducer = producer.sample(on: sampler) - observer = incomingObserver - } - - it("should free payload when interrupted after complete of incoming producer") { - var payloadFreed = false - - let disposable = sampledProducer.start() - - observer.sendNext(Payload { payloadFreed = true }) - observer.sendCompleted() - - expect(payloadFreed) == false - - disposable.dispose() - expect(payloadFreed) == true - } - } - } - - describe("combineLatestWith") { - var combinedProducer: SignalProducer<(Int, Double), NoError>! - var observer: Signal.Observer! - var otherObserver: Signal.Observer! - - beforeEach { - let (producer, incomingObserver) = SignalProducer.pipe() - let (otherSignal, incomingOtherObserver) = SignalProducer.pipe() - combinedProducer = producer.combineLatest(with: otherSignal) - observer = incomingObserver - otherObserver = incomingOtherObserver - } - - it("should forward the latest values from both inputs") { - var latest: (Int, Double)? - combinedProducer.startWithNext { latest = $0 } - - observer.sendNext(1) - expect(latest).to(beNil()) - - // is there a better way to test tuples? - otherObserver.sendNext(1.5) - expect(latest?.0) == 1 - expect(latest?.1) == 1.5 - - observer.sendNext(2) - expect(latest?.0) == 2 - expect(latest?.1) == 1.5 - } - - it("should complete when both inputs have completed") { - var completed = false - combinedProducer.startWithCompleted { completed = true } - - observer.sendCompleted() - expect(completed) == false - - otherObserver.sendCompleted() - expect(completed) == true - } - } - - describe("zipWith") { - var leftObserver: Signal.Observer! - var rightObserver: Signal.Observer! - var zipped: SignalProducer<(Int, String), NoError>! - - beforeEach { - let (leftProducer, incomingLeftObserver) = SignalProducer.pipe() - let (rightProducer, incomingRightObserver) = SignalProducer.pipe() - - leftObserver = incomingLeftObserver - rightObserver = incomingRightObserver - zipped = leftProducer.zip(with: rightProducer) - } - - it("should combine pairs") { - var result: [String] = [] - zipped.startWithNext { (left, right) in result.append("\(left)\(right)") } - - leftObserver.sendNext(1) - leftObserver.sendNext(2) - expect(result) == [] - - rightObserver.sendNext("foo") - expect(result) == [ "1foo" ] - - leftObserver.sendNext(3) - rightObserver.sendNext("bar") - expect(result) == [ "1foo", "2bar" ] - - rightObserver.sendNext("buzz") - expect(result) == [ "1foo", "2bar", "3buzz" ] - - rightObserver.sendNext("fuzz") - expect(result) == [ "1foo", "2bar", "3buzz" ] - - leftObserver.sendNext(4) - expect(result) == [ "1foo", "2bar", "3buzz", "4fuzz" ] - } - - it("should complete when the shorter signal has completed") { - var result: [String] = [] - var completed = false - - zipped.start { event in - switch event { - case let .next(left, right): - result.append("\(left)\(right)") - case .completed: - completed = true - case .failed, .interrupted: - break - } - } - - expect(completed) == false - - leftObserver.sendNext(0) - leftObserver.sendCompleted() - expect(completed) == false - expect(result) == [] - - rightObserver.sendNext("foo") - expect(completed) == true - expect(result) == [ "0foo" ] - } - } - - describe("materialize") { - it("should reify events from the signal") { - let (producer, observer) = SignalProducer.pipe() - var latestEvent: Event? - producer - .materialize() - .startWithNext { latestEvent = $0 } - - observer.sendNext(2) - - expect(latestEvent).toNot(beNil()) - if let latestEvent = latestEvent { - switch latestEvent { - case let .next(value): - expect(value) == 2 - case .failed, .completed, .interrupted: - fail() - } - } - - observer.sendFailed(TestError.default) - if let latestEvent = latestEvent { - switch latestEvent { - case .failed: - break - case .next, .completed, .interrupted: - fail() - } - } - } - } - - describe("dematerialize") { - typealias IntEvent = Event - var observer: Signal.Observer! - var dematerialized: SignalProducer! - - beforeEach { - let (producer, incomingObserver) = SignalProducer.pipe() - observer = incomingObserver - dematerialized = producer.dematerialize() - } - - it("should send values for Next events") { - var result: [Int] = [] - dematerialized - .assumeNoErrors() - .startWithNext { result.append($0) } - - expect(result).to(beEmpty()) - - observer.sendNext(.next(2)) - expect(result) == [ 2 ] - - observer.sendNext(.next(4)) - expect(result) == [ 2, 4 ] - } - - it("should error out for Error events") { - var errored = false - dematerialized.startWithFailed { _ in errored = true } - - expect(errored) == false - - observer.sendNext(.failed(TestError.default)) - expect(errored) == true - } - - it("should complete early for Completed events") { - var completed = false - dematerialized.startWithCompleted { completed = true } - - expect(completed) == false - observer.sendNext(IntEvent.completed) - expect(completed) == true - } - } - - describe("takeLast") { - var observer: Signal.Observer! - var lastThree: SignalProducer! - - beforeEach { - let (producer, incomingObserver) = SignalProducer.pipe() - observer = incomingObserver - lastThree = producer.take(last: 3) - } - - it("should send the last N values upon completion") { - var result: [Int] = [] - lastThree - .assumeNoErrors() - .startWithNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - expect(result).to(beEmpty()) - - observer.sendCompleted() - expect(result) == [ 2, 3, 4 ] - } - - it("should send less than N values if not enough were received") { - var result: [Int] = [] - lastThree - .assumeNoErrors() - .startWithNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendCompleted() - expect(result) == [ 1, 2 ] - } - - it("should send nothing when errors") { - var result: [Int] = [] - var errored = false - lastThree.start { event in - switch event { - case let .next(value): - result.append(value) - case .failed: - errored = true - case .completed, .interrupted: - break - } - } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - expect(errored) == false - - observer.sendFailed(TestError.default) - expect(errored) == true - expect(result).to(beEmpty()) - } - } - - describe("timeoutWithError") { - var testScheduler: TestScheduler! - var producer: SignalProducer! - var observer: Signal.Observer! - - beforeEach { - testScheduler = TestScheduler() - let (baseProducer, incomingObserver) = SignalProducer.pipe() - producer = baseProducer.timeout(after: 2, raising: TestError.default, on: testScheduler) - observer = incomingObserver - } - - it("should complete if within the interval") { - var completed = false - var errored = false - producer.start { event in - switch event { - case .completed: - completed = true - case .failed: - errored = true - case .next, .interrupted: - break - } - } - - testScheduler.schedule(after: 1) { - observer.sendCompleted() - } - - expect(completed) == false - expect(errored) == false - - testScheduler.run() - expect(completed) == true - expect(errored) == false - } - - it("should error if not completed before the interval has elapsed") { - var completed = false - var errored = false - producer.start { event in - switch event { - case .completed: - completed = true - case .failed: - errored = true - case .next, .interrupted: - break - } - } - - testScheduler.schedule(after: 3) { - observer.sendCompleted() - } - - expect(completed) == false - expect(errored) == false - - testScheduler.run() - expect(completed) == false - expect(errored) == true - } - - it("should be available for NoError") { - let producer: SignalProducer = SignalProducer.never - .timeout(after: 2, raising: TestError.default, on: testScheduler) - - _ = producer - } - } - - describe("attempt") { - it("should forward original values upon success") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.attempt { _ in - return .success() - } - - var current: Int? - producer - .assumeNoErrors() - .startWithNext { value in - current = value - } - - for value in 1...5 { - observer.sendNext(value) - expect(current) == value - } - } - - it("should error if an attempt fails") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.attempt { _ in - return .failure(.default) - } - - var error: TestError? - producer.startWithFailed { err in - error = err - } - - observer.sendNext(42) - expect(error) == TestError.default - } - } - - describe("attemptMap") { - it("should forward mapped values upon success") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.attemptMap { num -> Result in - return .success(num % 2 == 0) - } - - var even: Bool? - producer - .assumeNoErrors() - .startWithNext { value in - even = value - } - - observer.sendNext(1) - expect(even) == false - - observer.sendNext(2) - expect(even) == true - } - - it("should error if a mapping fails") { - let (baseProducer, observer) = SignalProducer.pipe() - let producer = baseProducer.attemptMap { _ -> Result in - return .failure(.default) - } - - var error: TestError? - producer.startWithFailed { err in - error = err - } - - observer.sendNext(42) - expect(error) == TestError.default - } - } - - describe("combinePrevious") { - var observer: Signal.Observer! - let initialValue: Int = 0 - var latestValues: (Int, Int)? - - beforeEach { - latestValues = nil - - let (signal, baseObserver) = SignalProducer.pipe() - observer = baseObserver - signal.combinePrevious(initialValue).startWithNext { latestValues = $0 } - } - - it("should forward the latest value with previous value") { - expect(latestValues).to(beNil()) - - observer.sendNext(1) - expect(latestValues?.0) == initialValue - expect(latestValues?.1) == 1 - - observer.sendNext(2) - expect(latestValues?.0) == 1 - expect(latestValues?.1) == 2 - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/SignalProducerNimbleMatchers.swift b/ReactiveCocoaTests/Swift/SignalProducerNimbleMatchers.swift deleted file mode 100644 index ad2d89e569..0000000000 --- a/ReactiveCocoaTests/Swift/SignalProducerNimbleMatchers.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// SignalProducerNimbleMatchers.swift -// ReactiveCocoa -// -// Created by Javier Soto on 1/25/15. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -import Foundation - -import ReactiveCocoa -import Nimble - -public func sendValue(_ value: T?, sendError: E?, complete: Bool) -> NonNilMatcherFunc> { - return sendValues(value.map { [$0] } ?? [], sendError: sendError, complete: complete) -} - -public func sendValues(_ values: [T], sendError maybeSendError: E?, complete: Bool) -> NonNilMatcherFunc> { - return NonNilMatcherFunc { actualExpression, failureMessage in - precondition(maybeSendError == nil || !complete, "Signals can't both send an error and complete") - - failureMessage.postfixMessage = "Send values \(values). Send error \(maybeSendError). Complete: \(complete)" - let maybeProducer = try actualExpression.evaluate() - - if let signalProducer = maybeProducer { - var sentValues: [T] = [] - var sentError: E? - var signalCompleted = false - - signalProducer.start { event in - switch event { - case let .next(value): - sentValues.append(value) - case .completed: - signalCompleted = true - case let .failed(error): - sentError = error - default: - break - } - } - - if sentValues != values { - return false - } - - if sentError != maybeSendError { - return false - } - - return signalCompleted == complete - } - else { - return false - } - } -} diff --git a/ReactiveCocoaTests/Swift/SignalProducerSpec.swift b/ReactiveCocoaTests/Swift/SignalProducerSpec.swift deleted file mode 100644 index 7a0b768267..0000000000 --- a/ReactiveCocoaTests/Swift/SignalProducerSpec.swift +++ /dev/null @@ -1,2314 +0,0 @@ -// -// SignalProducerSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2015-01-23. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -import Foundation - -import Result -import Nimble -import Quick -import ReactiveCocoa - -class SignalProducerSpec: QuickSpec { - override func spec() { - describe("init") { - it("should run the handler once per start()") { - var handlerCalledTimes = 0 - let signalProducer = SignalProducer() { observer, disposable in - handlerCalledTimes += 1 - - return - } - - signalProducer.start() - signalProducer.start() - - expect(handlerCalledTimes) == 2 - } - - it("should not release signal observers when given disposable is disposed") { - var disposable: Disposable! - - let producer = SignalProducer { observer, innerDisposable in - disposable = innerDisposable - - innerDisposable += { - // This is necessary to keep the observer long enough to - // even test the memory management. - observer.sendNext(0) - } - } - - weak var objectRetainedByObserver: NSObject? - producer.startWithSignal { signal, _ in - let object = NSObject() - objectRetainedByObserver = object - signal.observeNext { _ in _ = object } - } - - expect(objectRetainedByObserver).toNot(beNil()) - - disposable.dispose() - - // https://github.com/ReactiveCocoa/ReactiveCocoa/pull/2959 - // - // Before #2959, this would be `nil` as the input observer is not - // retained, and observers would not retain the signal. - // - // After #2959, the object is still retained, since the observation - // keeps the signal alive. - expect(objectRetainedByObserver).toNot(beNil()) - } - - it("should dispose of added disposables upon completion") { - let addedDisposable = SimpleDisposable() - var observer: Signal<(), NoError>.Observer! - - let producer = SignalProducer<(), NoError>() { incomingObserver, disposable in - disposable += addedDisposable - observer = incomingObserver - } - - producer.start() - expect(addedDisposable.isDisposed) == false - - observer.sendCompleted() - expect(addedDisposable.isDisposed) == true - } - - it("should dispose of added disposables upon error") { - let addedDisposable = SimpleDisposable() - var observer: Signal<(), TestError>.Observer! - - let producer = SignalProducer<(), TestError>() { incomingObserver, disposable in - disposable += addedDisposable - observer = incomingObserver - } - - producer.start() - expect(addedDisposable.isDisposed) == false - - observer.sendFailed(.default) - expect(addedDisposable.isDisposed) == true - } - - it("should dispose of added disposables upon interruption") { - let addedDisposable = SimpleDisposable() - var observer: Signal<(), NoError>.Observer! - - let producer = SignalProducer<(), NoError>() { incomingObserver, disposable in - disposable += addedDisposable - observer = incomingObserver - } - - producer.start() - expect(addedDisposable.isDisposed) == false - - observer.sendInterrupted() - expect(addedDisposable.isDisposed) == true - } - - it("should dispose of added disposables upon start() disposal") { - let addedDisposable = SimpleDisposable() - - let producer = SignalProducer<(), TestError>() { _, disposable in - disposable += addedDisposable - return - } - - let startDisposable = producer.start() - expect(addedDisposable.isDisposed) == false - - startDisposable.dispose() - expect(addedDisposable.isDisposed) == true - } - } - - describe("init(signal:)") { - var signal: Signal! - var observer: Signal.Observer! - - beforeEach { - // Cannot directly assign due to compiler crash on Xcode 7.0.1 - let (signalTemp, observerTemp) = Signal.pipe() - signal = signalTemp - observer = observerTemp - } - - it("should emit values then complete") { - let producer = SignalProducer(signal: signal) - - var values: [Int] = [] - var error: TestError? - var completed = false - producer.start { event in - switch event { - case let .next(value): - values.append(value) - case let .failed(err): - error = err - case .completed: - completed = true - default: - break - } - } - - expect(values) == [] - expect(error).to(beNil()) - expect(completed) == false - - observer.sendNext(1) - expect(values) == [ 1 ] - observer.sendNext(2) - observer.sendNext(3) - expect(values) == [ 1, 2, 3 ] - - observer.sendCompleted() - expect(completed) == true - } - - it("should emit error") { - let producer = SignalProducer(signal: signal) - - var error: TestError? - let sentError = TestError.default - - producer.start { event in - switch event { - case let .failed(err): - error = err - default: - break - } - } - - expect(error).to(beNil()) - - observer.sendFailed(sentError) - expect(error) == sentError - } - } - - describe("init(value:)") { - it("should immediately send the value then complete") { - let producerValue = "StringValue" - let signalProducer = SignalProducer(value: producerValue) - - expect(signalProducer).to(sendValue(producerValue, sendError: nil, complete: true)) - } - } - - describe("init(error:)") { - it("should immediately send the error") { - let producerError = NSError(domain: "com.reactivecocoa.errordomain", code: 4815, userInfo: nil) - let signalProducer = SignalProducer(error: producerError) - - expect(signalProducer).to(sendValue(nil, sendError: producerError, complete: false)) - } - } - - describe("init(result:)") { - it("should immediately send the value then complete") { - let producerValue = "StringValue" - let producerResult = .success(producerValue) as Result - let signalProducer = SignalProducer(result: producerResult) - - expect(signalProducer).to(sendValue(producerValue, sendError: nil, complete: true)) - } - - it("should immediately send the error") { - let producerError = NSError(domain: "com.reactivecocoa.errordomain", code: 4815, userInfo: nil) - let producerResult = .failure(producerError) as Result - let signalProducer = SignalProducer(result: producerResult) - - expect(signalProducer).to(sendValue(nil, sendError: producerError, complete: false)) - } - } - - describe("init(values:)") { - it("should immediately send the sequence of values") { - let sequenceValues = [1, 2, 3] - let signalProducer = SignalProducer(values: sequenceValues) - - expect(signalProducer).to(sendValues(sequenceValues, sendError: nil, complete: true)) - } - } - - describe("SignalProducer.empty") { - it("should immediately complete") { - let signalProducer = SignalProducer.empty - - expect(signalProducer).to(sendValue(nil, sendError: nil, complete: true)) - } - } - - describe("SignalProducer.never") { - it("should not send any events") { - let signalProducer = SignalProducer.never - - expect(signalProducer).to(sendValue(nil, sendError: nil, complete: false)) - } - } - - describe("trailing closure") { - it("receives next values") { - let (producer, observer) = SignalProducer.pipe() - - var values = [Int]() - producer.startWithNext { next in - values.append(next) - } - - observer.sendNext(1) - expect(values) == [1] - } - } - - describe("SignalProducer.attempt") { - it("should run the operation once per start()") { - var operationRunTimes = 0 - let operation: () -> Result = { - operationRunTimes += 1 - - return .success("OperationValue") - } - - SignalProducer.attempt(operation).start() - SignalProducer.attempt(operation).start() - - expect(operationRunTimes) == 2 - } - - it("should send the value then complete") { - let operationReturnValue = "OperationValue" - let operation: () -> Result = { - return .success(operationReturnValue) - } - - let signalProducer = SignalProducer.attempt(operation) - - expect(signalProducer).to(sendValue(operationReturnValue, sendError: nil, complete: true)) - } - - it("should send the error") { - let operationError = NSError(domain: "com.reactivecocoa.errordomain", code: 4815, userInfo: nil) - let operation: () -> Result = { - return .failure(operationError) - } - - let signalProducer = SignalProducer.attempt(operation) - - expect(signalProducer).to(sendValue(nil, sendError: operationError, complete: false)) - } - } - - describe("startWithSignal") { - it("should invoke the closure before any effects or events") { - var started = false - var value: Int? - - SignalProducer(value: 42) - .on(started: { - started = true - }, next: { - value = $0 - }) - .startWithSignal { _ in - expect(started) == false - expect(value).to(beNil()) - } - - expect(started) == true - expect(value) == 42 - } - - it("should dispose of added disposables if disposed") { - let addedDisposable = SimpleDisposable() - var disposable: Disposable! - - let producer = SignalProducer() { _, disposable in - disposable += addedDisposable - return - } - - producer.startWithSignal { _, innerDisposable in - disposable = innerDisposable - } - - expect(addedDisposable.isDisposed) == false - - disposable.dispose() - expect(addedDisposable.isDisposed) == true - } - - it("should send interrupted if disposed") { - var interrupted = false - var disposable: Disposable! - - SignalProducer(value: 42) - .start(on: TestScheduler()) - .startWithSignal { signal, innerDisposable in - signal.observeInterrupted { - interrupted = true - } - - disposable = innerDisposable - } - - expect(interrupted) == false - - disposable.dispose() - expect(interrupted) == true - } - - it("should release signal observers if disposed") { - weak var objectRetainedByObserver: NSObject? - var disposable: Disposable! - - let producer = SignalProducer.never - producer.startWithSignal { signal, innerDisposable in - let object = NSObject() - objectRetainedByObserver = object - signal.observeNext { _ in _ = object.description } - disposable = innerDisposable - } - - expect(objectRetainedByObserver).toNot(beNil()) - - disposable.dispose() - expect(objectRetainedByObserver).to(beNil()) - } - - it("should not trigger effects if disposed before closure return") { - var started = false - var value: Int? - - SignalProducer(value: 42) - .on(started: { - started = true - }, next: { - value = $0 - }) - .startWithSignal { _, disposable in - expect(started) == false - expect(value).to(beNil()) - - disposable.dispose() - } - - expect(started) == false - expect(value).to(beNil()) - } - - it("should send interrupted if disposed before closure return") { - var interrupted = false - - SignalProducer(value: 42) - .startWithSignal { signal, disposable in - expect(interrupted) == false - - signal.observeInterrupted { - interrupted = true - } - - disposable.dispose() - } - - expect(interrupted) == true - } - - it("should dispose of added disposables upon completion") { - let addedDisposable = SimpleDisposable() - var observer: Signal.Observer! - - let producer = SignalProducer() { incomingObserver, disposable in - disposable += addedDisposable - observer = incomingObserver - } - - producer.startWithSignal { _ in } - expect(addedDisposable.isDisposed) == false - - observer.sendCompleted() - expect(addedDisposable.isDisposed) == true - } - - it("should dispose of added disposables upon error") { - let addedDisposable = SimpleDisposable() - var observer: Signal.Observer! - - let producer = SignalProducer() { incomingObserver, disposable in - disposable += addedDisposable - observer = incomingObserver - } - - producer.startWithSignal { _ in } - expect(addedDisposable.isDisposed) == false - - observer.sendFailed(.default) - expect(addedDisposable.isDisposed) == true - } - } - - describe("start") { - it("should immediately begin sending events") { - let producer = SignalProducer(values: [1, 2]) - - var values: [Int] = [] - var completed = false - producer.start { event in - switch event { - case let .next(value): - values.append(value) - case .completed: - completed = true - default: - break - } - } - - expect(values) == [1, 2] - expect(completed) == true - } - - it("should send interrupted if disposed") { - let producer = SignalProducer<(), NoError>.never - - var interrupted = false - let disposable = producer.startWithInterrupted { - interrupted = true - } - - expect(interrupted) == false - - disposable.dispose() - expect(interrupted) == true - } - - it("should release observer when disposed") { - weak var objectRetainedByObserver: NSObject? - var disposable: Disposable! - let test = { - let producer = SignalProducer.never - let object = NSObject() - objectRetainedByObserver = object - disposable = producer.startWithNext { _ in _ = object } - } - - test() - expect(objectRetainedByObserver).toNot(beNil()) - - disposable.dispose() - expect(objectRetainedByObserver).to(beNil()) - } - - describe("trailing closure") { - it("receives next values") { - let (producer, observer) = SignalProducer.pipe() - - var values = [Int]() - producer.startWithNext { next in - values.append(next) - } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - - observer.sendCompleted() - - expect(values) == [1, 2, 3] - } - - it("receives results") { - let (producer, observer) = SignalProducer.pipe() - - var results: [Result] = [] - producer.startWithResult { results.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendFailed(.default) - - observer.sendCompleted() - - expect(results).to(haveCount(4)) - expect(results[0].value) == 1 - expect(results[1].value) == 2 - expect(results[2].value) == 3 - expect(results[3].error) == .default - } - } - } - - describe("lift") { - describe("over unary operators") { - it("should invoke transformation once per started signal") { - let baseProducer = SignalProducer(values: [1, 2]) - - var counter = 0 - let transform = { (signal: Signal) -> Signal in - counter += 1 - return signal - } - - let producer = baseProducer.lift(transform) - expect(counter) == 0 - - producer.start() - expect(counter) == 1 - - producer.start() - expect(counter) == 2 - } - - it("should not miss any events") { - let baseProducer = SignalProducer(values: [1, 2, 3, 4]) - - let producer = baseProducer.lift { signal in - return signal.map { $0 * $0 } - } - let result = producer.collect().single() - - expect(result?.value) == [1, 4, 9, 16] - } - } - - describe("over binary operators") { - it("should invoke transformation once per started signal") { - let baseProducer = SignalProducer(values: [1, 2]) - let otherProducer = SignalProducer(values: [3, 4]) - - var counter = 0 - let transform = { (signal: Signal) -> (Signal) -> Signal<(Int, Int), NoError> in - return { otherSignal in - counter += 1 - return Signal.zip(signal, otherSignal) - } - } - - let producer = baseProducer.lift(transform)(otherProducer) - expect(counter) == 0 - - producer.start() - expect(counter) == 1 - - producer.start() - expect(counter) == 2 - } - - it("should not miss any events") { - let baseProducer = SignalProducer(values: [1, 2, 3]) - let otherProducer = SignalProducer(values: [4, 5, 6]) - - let transform = { (signal: Signal) -> (Signal) -> Signal in - return { otherSignal in - return Signal.zip(signal, otherSignal).map { first, second in first + second } - } - } - - let producer = baseProducer.lift(transform)(otherProducer) - let result = producer.collect().single() - - expect(result?.value) == [5, 7, 9] - } - } - - describe("over binary operators with signal") { - it("should invoke transformation once per started signal") { - let baseProducer = SignalProducer(values: [1, 2]) - let (otherSignal, otherSignalObserver) = Signal.pipe() - - var counter = 0 - let transform = { (signal: Signal) -> (Signal) -> Signal<(Int, Int), NoError> in - return { otherSignal in - counter += 1 - return Signal.zip(signal, otherSignal) - } - } - - let producer = baseProducer.lift(transform)(otherSignal) - expect(counter) == 0 - - producer.start() - otherSignalObserver.sendNext(1) - expect(counter) == 1 - - producer.start() - otherSignalObserver.sendNext(2) - expect(counter) == 2 - } - - it("should not miss any events") { - let baseProducer = SignalProducer(values: [ 1, 2, 3 ]) - let (otherSignal, otherSignalObserver) = Signal.pipe() - - let transform = { (signal: Signal) -> (Signal) -> Signal in - return { otherSignal in - return Signal.zip(signal, otherSignal).map(+) - } - } - - let producer = baseProducer.lift(transform)(otherSignal) - var result: [Int] = [] - var completed: Bool = false - - producer.start { event in - switch event { - case .next(let value): result.append(value) - case .completed: completed = true - default: break - } - } - - otherSignalObserver.sendNext(4) - expect(result) == [ 5 ] - - otherSignalObserver.sendNext(5) - expect(result) == [ 5, 7 ] - - otherSignalObserver.sendNext(6) - expect(result) == [ 5, 7, 9 ] - expect(completed) == true - } - } - } - - describe("combineLatest") { - it("should combine the events to one array") { - let (producerA, observerA) = SignalProducer.pipe() - let (producerB, observerB) = SignalProducer.pipe() - - let producer = SignalProducer.combineLatest([producerA, producerB]) - - var values = [[Int]]() - producer.startWithNext { next in - values.append(next) - } - - observerA.sendNext(1) - observerB.sendNext(2) - observerA.sendNext(3) - observerA.sendCompleted() - observerB.sendCompleted() - - expect(values as NSArray) == [[1, 2], [3, 2]] - } - - it("should start signal producers in order as defined") { - var ids = [Int]() - let createProducer = { (id: Int) -> SignalProducer in - return SignalProducer { observer, disposable in - ids.append(id) - - observer.sendNext(id) - observer.sendCompleted() - } - } - - let producerA = createProducer(1) - let producerB = createProducer(2) - - let producer = SignalProducer.combineLatest([producerA, producerB]) - - var values = [[Int]]() - producer.startWithNext { next in - values.append(next) - } - - expect(ids) == [1, 2] - expect(values as NSArray) == [[1, 2]] as NSArray - } - } - - describe("zip") { - it("should zip the events to one array") { - let producerA = SignalProducer(values: [ 1, 2 ]) - let producerB = SignalProducer(values: [ 3, 4 ]) - - let producer = SignalProducer.zip([producerA, producerB]) - let result = producer.collect().single() - - expect(result?.value.map { $0 as NSArray }) == [[1, 3], [2, 4]] as NSArray - } - - it("should start signal producers in order as defined") { - var ids = [Int]() - let createProducer = { (id: Int) -> SignalProducer in - return SignalProducer { observer, disposable in - ids.append(id) - - observer.sendNext(id) - observer.sendCompleted() - } - } - - let producerA = createProducer(1) - let producerB = createProducer(2) - - let producer = SignalProducer.zip([producerA, producerB]) - - var values = [[Int]]() - producer.startWithNext { next in - values.append(next) - } - - expect(ids) == [1, 2] - expect(values as NSArray) == [[1, 2]] as NSArray - } - } - - describe("timer") { - it("should send the current date at the given interval") { - let scheduler = TestScheduler() - let producer = timer(interval: 1, on: scheduler, leeway: 0) - - let startDate = scheduler.currentDate - let tick1 = startDate.addingTimeInterval(1) - let tick2 = startDate.addingTimeInterval(2) - let tick3 = startDate.addingTimeInterval(3) - - var dates: [Date] = [] - producer.startWithNext { dates.append($0) } - - scheduler.advance(by: 0.9) - expect(dates) == [] - - scheduler.advance(by: 1) - expect(dates) == [tick1] - - scheduler.advance() - expect(dates) == [tick1] - - scheduler.advance(by: 0.2) - expect(dates) == [tick1, tick2] - - scheduler.advance(by: 1) - expect(dates) == [tick1, tick2, tick3] - } - - it("should release the signal when disposed") { - let scheduler = TestScheduler() - let producer = timer(interval: 1, on: scheduler, leeway: 0) - var interrupted = false - - weak var weakSignal: Signal? - producer.startWithSignal { signal, disposable in - weakSignal = signal - scheduler.schedule { - disposable.dispose() - } - signal.observeInterrupted { interrupted = true } - } - - expect(weakSignal).toNot(beNil()) - expect(interrupted) == false - - scheduler.run() - expect(weakSignal).to(beNil()) - expect(interrupted) == true - } - } - - describe("on") { - it("should attach event handlers to each started signal") { - let (baseProducer, observer) = SignalProducer.pipe() - - var starting = 0 - var started = 0 - var event = 0 - var next = 0 - var completed = 0 - var terminated = 0 - - let producer = baseProducer - .on(starting: { - starting += 1 - }, started: { - started += 1 - }, event: { e in - event += 1 - }, next: { n in - next += 1 - }, completed: { - completed += 1 - }, terminated: { - terminated += 1 - }) - - producer.start() - expect(starting) == 1 - expect(started) == 1 - - producer.start() - expect(starting) == 2 - expect(started) == 2 - - observer.sendNext(1) - expect(event) == 2 - expect(next) == 2 - - observer.sendCompleted() - expect(event) == 4 - expect(completed) == 2 - expect(terminated) == 2 - } - - it("should attach event handlers for disposal") { - let (baseProducer, _) = SignalProducer.pipe() - - var disposed: Bool = false - - let producer = baseProducer - .on(disposed: { disposed = true }) - - let disposable = producer.start() - - expect(disposed) == false - disposable.dispose() - expect(disposed) == true - } - - it("should invoke the `started` action of the inner producer first") { - let (baseProducer, _) = SignalProducer.pipe() - - var numbers = [Int]() - - _ = baseProducer - .on(started: { numbers.append(1) }) - .on(started: { numbers.append(2) }) - .on(started: { numbers.append(3) }) - .start() - - expect(numbers) == [1, 2, 3] - } - - it("should invoke the `starting` action of the outer producer first") { - let (baseProducer, _) = SignalProducer.pipe() - - var numbers = [Int]() - - _ = baseProducer - .on(starting: { numbers.append(1) }) - .on(starting: { numbers.append(2) }) - .on(starting: { numbers.append(3) }) - .start() - - expect(numbers) == [3, 2, 1] - } - } - - describe("startOn") { - it("should invoke effects on the given scheduler") { - let scheduler = TestScheduler() - var invoked = false - - let producer = SignalProducer() { _ in - invoked = true - } - - producer.start(on: scheduler).start() - expect(invoked) == false - - scheduler.advance() - expect(invoked) == true - } - - it("should forward events on their original scheduler") { - let startScheduler = TestScheduler() - let testScheduler = TestScheduler() - - let producer = timer(interval: 2, on: testScheduler, leeway: 0) - - var next: Date? - producer.start(on: startScheduler).startWithNext { next = $0 } - - startScheduler.advance(by: 2) - expect(next).to(beNil()) - - testScheduler.advance(by: 1) - expect(next).to(beNil()) - - testScheduler.advance(by: 1) - expect(next) == testScheduler.currentDate - } - } - - describe("flatMapError") { - it("should invoke the handler and start new producer for an error") { - let (baseProducer, baseObserver) = SignalProducer.pipe() - - var values: [Int] = [] - var completed = false - - baseProducer - .flatMapError { (error: TestError) -> SignalProducer in - expect(error) == TestError.default - expect(values) == [1] - - return .init(value: 2) - } - .start { event in - switch event { - case let .next(value): - values.append(value) - case .completed: - completed = true - default: - break - } - } - - baseObserver.sendNext(1) - baseObserver.sendFailed(.default) - - expect(values) == [1, 2] - expect(completed) == true - } - - it("should interrupt the replaced producer on disposal") { - let (baseProducer, baseObserver) = SignalProducer.pipe() - - var (disposed, interrupted) = (false, false) - let disposable = baseProducer - .flatMapError { (error: TestError) -> SignalProducer in - return SignalProducer { _, disposable in - disposable += ActionDisposable { disposed = true } - } - } - .startWithInterrupted { interrupted = true } - - baseObserver.sendFailed(.default) - disposable.dispose() - - expect(interrupted) == true - expect(disposed) == true - } - } - - describe("flatten") { - describe("FlattenStrategy.concat") { - describe("sequencing") { - var completePrevious: (() -> Void)! - var sendSubsequent: (() -> Void)! - var completeOuter: (() -> Void)! - - var subsequentStarted = false - - beforeEach { - let (outerProducer, outerObserver) = SignalProducer, NoError>.pipe() - let (previousProducer, previousObserver) = SignalProducer.pipe() - - subsequentStarted = false - let subsequentProducer = SignalProducer { _ in - subsequentStarted = true - } - - completePrevious = { previousObserver.sendCompleted() } - sendSubsequent = { outerObserver.sendNext(subsequentProducer) } - completeOuter = { outerObserver.sendCompleted() } - - outerProducer.flatten(.concat).start() - outerObserver.sendNext(previousProducer) - } - - it("should immediately start subsequent inner producer if previous inner producer has already completed") { - completePrevious() - sendSubsequent() - expect(subsequentStarted) == true - } - - context("with queued producers") { - beforeEach { - // Place the subsequent producer into `concat`'s queue. - sendSubsequent() - expect(subsequentStarted) == false - } - - it("should start subsequent inner producer upon completion of previous inner producer") { - completePrevious() - expect(subsequentStarted) == true - } - - it("should start subsequent inner producer upon completion of previous inner producer and completion of outer producer") { - completeOuter() - completePrevious() - expect(subsequentStarted) == true - } - } - } - - it("should forward an error from an inner producer") { - let errorProducer = SignalProducer(error: TestError.default) - let outerProducer = SignalProducer, TestError>(value: errorProducer) - - var error: TestError? - (outerProducer.flatten(.concat)).startWithFailed { e in - error = e - } - - expect(error) == TestError.default - } - - it("should forward an error from the outer producer") { - let (outerProducer, outerObserver) = SignalProducer, TestError>.pipe() - - var error: TestError? - outerProducer.flatten(.concat).startWithFailed { e in - error = e - } - - outerObserver.sendFailed(TestError.default) - expect(error) == TestError.default - } - - describe("completion") { - var completeOuter: (() -> Void)! - var completeInner: (() -> Void)! - - var completed = false - - beforeEach { - let (outerProducer, outerObserver) = SignalProducer, NoError>.pipe() - let (innerProducer, innerObserver) = SignalProducer.pipe() - - completeOuter = { outerObserver.sendCompleted() } - completeInner = { innerObserver.sendCompleted() } - - completed = false - outerProducer.flatten(.concat).startWithCompleted { - completed = true - } - - outerObserver.sendNext(innerProducer) - } - - it("should complete when inner producers complete, then outer producer completes") { - completeInner() - expect(completed) == false - - completeOuter() - expect(completed) == true - } - - it("should complete when outer producers completes, then inner producers complete") { - completeOuter() - expect(completed) == false - - completeInner() - expect(completed) == true - } - } - } - - describe("FlattenStrategy.merge") { - describe("behavior") { - var completeA: (() -> Void)! - var sendA: (() -> Void)! - var completeB: (() -> Void)! - var sendB: (() -> Void)! - - var outerCompleted = false - - var recv = [Int]() - - beforeEach { - let (outerProducer, outerObserver) = SignalProducer, NoError>.pipe() - let (producerA, observerA) = SignalProducer.pipe() - let (producerB, observerB) = SignalProducer.pipe() - - completeA = { observerA.sendCompleted() } - completeB = { observerB.sendCompleted() } - - var a = 0 - sendA = { observerA.sendNext(a); a += 1 } - - var b = 100 - sendB = { observerB.sendNext(b); b += 1 } - - outerProducer.flatten(.merge).start { event in - switch event { - case let .next(i): - recv.append(i) - case .completed: - outerCompleted = true - default: - break - } - } - - outerObserver.sendNext(producerA) - outerObserver.sendNext(producerB) - - outerObserver.sendCompleted() - } - - it("should forward values from any inner signals") { - sendA() - sendA() - sendB() - sendA() - sendB() - expect(recv) == [0, 1, 100, 2, 101] - } - - it("should complete when all signals have completed") { - completeA() - expect(outerCompleted) == false - completeB() - expect(outerCompleted) == true - } - } - - describe("error handling") { - it("should forward an error from an inner signal") { - let errorProducer = SignalProducer(error: TestError.default) - let outerProducer = SignalProducer, TestError>(value: errorProducer) - - var error: TestError? - outerProducer.flatten(.merge).startWithFailed { e in - error = e - } - expect(error) == TestError.default - } - - it("should forward an error from the outer signal") { - let (outerProducer, outerObserver) = SignalProducer, TestError>.pipe() - - var error: TestError? - outerProducer.flatten(.merge).startWithFailed { e in - error = e - } - - outerObserver.sendFailed(TestError.default) - expect(error) == TestError.default - } - } - } - - describe("FlattenStrategy.latest") { - it("should forward values from the latest inner signal") { - let (outer, outerObserver) = SignalProducer, TestError>.pipe() - let (firstInner, firstInnerObserver) = SignalProducer.pipe() - let (secondInner, secondInnerObserver) = SignalProducer.pipe() - - var receivedValues: [Int] = [] - var errored = false - var completed = false - - outer.flatten(.latest).start { event in - switch event { - case let .next(value): - receivedValues.append(value) - case .completed: - completed = true - case .failed: - errored = true - case .interrupted: - break - } - } - - outerObserver.sendNext(SignalProducer(value: 0)) - outerObserver.sendNext(firstInner) - firstInnerObserver.sendNext(1) - outerObserver.sendNext(secondInner) - secondInnerObserver.sendNext(2) - outerObserver.sendCompleted() - - expect(receivedValues) == [ 0, 1, 2 ] - expect(errored) == false - expect(completed) == false - - firstInnerObserver.sendNext(3) - firstInnerObserver.sendCompleted() - secondInnerObserver.sendNext(4) - secondInnerObserver.sendCompleted() - - expect(receivedValues) == [ 0, 1, 2, 4 ] - expect(errored) == false - expect(completed) == true - } - - it("should forward an error from an inner signal") { - let inner = SignalProducer(error: .default) - let outer = SignalProducer, TestError>(value: inner) - - let result = outer.flatten(.latest).first() - expect(result?.error) == TestError.default - } - - it("should forward an error from the outer signal") { - let outer = SignalProducer, TestError>(error: .default) - - let result = outer.flatten(.latest).first() - expect(result?.error) == TestError.default - } - - it("should complete when the original and latest signals have completed") { - let inner = SignalProducer.empty - let outer = SignalProducer, TestError>(value: inner) - - var completed = false - outer.flatten(.latest).startWithCompleted { - completed = true - } - - expect(completed) == true - } - - it("should complete when the outer signal completes before sending any signals") { - let outer = SignalProducer, TestError>.empty - - var completed = false - outer.flatten(.latest).startWithCompleted { - completed = true - } - - expect(completed) == true - } - - it("should not deadlock") { - let producer = SignalProducer(value: 1) - .flatMap(.latest) { _ in SignalProducer(value: 10) } - - let result = producer.take(first: 1).last() - expect(result?.value) == 10 - } - } - - describe("interruption") { - var innerObserver: Signal<(), NoError>.Observer! - var outerObserver: Signal, NoError>.Observer! - var execute: ((FlattenStrategy) -> Void)! - - var interrupted = false - var completed = false - - beforeEach { - let (innerProducer, incomingInnerObserver) = SignalProducer<(), NoError>.pipe() - let (outerProducer, incomingOuterObserver) = SignalProducer, NoError>.pipe() - - innerObserver = incomingInnerObserver - outerObserver = incomingOuterObserver - - execute = { strategy in - interrupted = false - completed = false - - outerProducer - .flatten(strategy) - .start { event in - switch event { - case .interrupted: - interrupted = true - case .completed: - completed = true - default: - break - } - } - } - - incomingOuterObserver.sendNext(innerProducer) - } - - describe("Concat") { - it("should drop interrupted from an inner producer") { - execute(.concat) - - innerObserver.sendInterrupted() - expect(interrupted) == false - expect(completed) == false - - outerObserver.sendCompleted() - expect(completed) == true - } - - it("should forward interrupted from the outer producer") { - execute(.concat) - outerObserver.sendInterrupted() - expect(interrupted) == true - } - } - - describe("Latest") { - it("should drop interrupted from an inner producer") { - execute(.latest) - - innerObserver.sendInterrupted() - expect(interrupted) == false - expect(completed) == false - - outerObserver.sendCompleted() - expect(completed) == true - } - - it("should forward interrupted from the outer producer") { - execute(.latest) - outerObserver.sendInterrupted() - expect(interrupted) == true - } - } - - describe("Merge") { - it("should drop interrupted from an inner producer") { - execute(.merge) - - innerObserver.sendInterrupted() - expect(interrupted) == false - expect(completed) == false - - outerObserver.sendCompleted() - expect(completed) == true - } - - it("should forward interrupted from the outer producer") { - execute(.merge) - outerObserver.sendInterrupted() - expect(interrupted) == true - } - } - } - - describe("disposal") { - var completeOuter: (() -> Void)! - var disposeOuter: (() -> Void)! - var execute: ((FlattenStrategy) -> Void)! - - var innerDisposable = SimpleDisposable() - var interrupted = false - - beforeEach { - execute = { strategy in - let (outerProducer, outerObserver) = SignalProducer, NoError>.pipe() - - innerDisposable = SimpleDisposable() - let innerProducer = SignalProducer { $1.add(innerDisposable) } - - interrupted = false - let outerDisposable = outerProducer.flatten(strategy).startWithInterrupted { - interrupted = true - } - - completeOuter = outerObserver.sendCompleted - disposeOuter = outerDisposable.dispose - - outerObserver.sendNext(innerProducer) - } - } - - describe("Concat") { - it("should cancel inner work when disposed before the outer producer completes") { - execute(.concat) - - expect(innerDisposable.isDisposed) == false - expect(interrupted) == false - disposeOuter() - - expect(innerDisposable.isDisposed) == true - expect(interrupted) == true - } - - it("should cancel inner work when disposed after the outer producer completes") { - execute(.concat) - - completeOuter() - - expect(innerDisposable.isDisposed) == false - expect(interrupted) == false - disposeOuter() - - expect(innerDisposable.isDisposed) == true - expect(interrupted) == true - } - } - - describe("Latest") { - it("should cancel inner work when disposed before the outer producer completes") { - execute(.latest) - - expect(innerDisposable.isDisposed) == false - expect(interrupted) == false - disposeOuter() - - expect(innerDisposable.isDisposed) == true - expect(interrupted) == true - } - - it("should cancel inner work when disposed after the outer producer completes") { - execute(.latest) - - completeOuter() - - expect(innerDisposable.isDisposed) == false - expect(interrupted) == false - disposeOuter() - - expect(innerDisposable.isDisposed) == true - expect(interrupted) == true - } - } - - describe("Merge") { - it("should cancel inner work when disposed before the outer producer completes") { - execute(.merge) - - expect(innerDisposable.isDisposed) == false - expect(interrupted) == false - disposeOuter() - - expect(innerDisposable.isDisposed) == true - expect(interrupted) == true - } - - it("should cancel inner work when disposed after the outer producer completes") { - execute(.merge) - - completeOuter() - - expect(innerDisposable.isDisposed) == false - expect(interrupted) == false - disposeOuter() - - expect(innerDisposable.isDisposed) == true - expect(interrupted) == true - } - } - } - } - - describe("times") { - it("should start a signal N times upon completion") { - let original = SignalProducer(values: [ 1, 2, 3 ]) - let producer = original.times(3) - - let result = producer.collect().single() - expect(result?.value) == [ 1, 2, 3, 1, 2, 3, 1, 2, 3 ] - } - - it("should produce an equivalent signal producer if count is 1") { - let original = SignalProducer(value: 1) - let producer = original.times(1) - - let result = producer.collect().single() - expect(result?.value) == [ 1 ] - } - - it("should produce an empty signal if count is 0") { - let original = SignalProducer(value: 1) - let producer = original.times(0) - - let result = producer.first() - expect(result).to(beNil()) - } - - it("should not repeat upon error") { - let results: [Result] = [ - .success(1), - .success(2), - .failure(.default) - ] - - let original = SignalProducer.attemptWithResults(results) - let producer = original.times(3) - - let events = producer - .materialize() - .collect() - .single() - let result = events?.value - - let expectedEvents: [Event] = [ - .next(1), - .next(2), - .failed(.default) - ] - - // TODO: if let result = result where result.count == expectedEvents.count - if result?.count != expectedEvents.count { - fail("Invalid result: \(result)") - } else { - // Can't test for equality because Array is not Equatable, - // and neither is Event. - expect(result![0] == expectedEvents[0]) == true - expect(result![1] == expectedEvents[1]) == true - expect(result![2] == expectedEvents[2]) == true - } - } - - it("should evaluate lazily") { - let original = SignalProducer(value: 1) - let producer = original.times(Int.max) - - let result = producer.take(first: 1).single() - expect(result?.value) == 1 - } - } - - describe("retry") { - it("should start a signal N times upon error") { - let results: [Result] = [ - .failure(.error1), - .failure(.error2), - .success(1) - ] - - let original = SignalProducer.attemptWithResults(results) - let producer = original.retry(upTo: 2) - - let result = producer.single() - - expect(result?.value) == 1 - } - - it("should forward errors that occur after all retries") { - let results: [Result] = [ - .failure(.default), - .failure(.error1), - .failure(.error2), - ] - - let original = SignalProducer.attemptWithResults(results) - let producer = original.retry(upTo: 2) - - let result = producer.single() - - expect(result?.error) == TestError.error2 - } - - it("should not retry upon completion") { - let results: [Result] = [ - .success(1), - .success(2), - .success(3) - ] - - let original = SignalProducer.attemptWithResults(results) - let producer = original.retry(upTo: 2) - - let result = producer.single() - expect(result?.value) == 1 - } - } - - describe("then") { - it("should start the subsequent producer after the completion of the original") { - let (original, observer) = SignalProducer.pipe() - - var subsequentStarted = false - let subsequent = SignalProducer { observer, _ in - subsequentStarted = true - } - - let producer = original.then(subsequent) - producer.start() - expect(subsequentStarted) == false - - observer.sendCompleted() - expect(subsequentStarted) == true - } - - it("should forward errors from the original producer") { - let original = SignalProducer(error: .default) - let subsequent = SignalProducer.empty - - let result = original.then(subsequent).first() - expect(result?.error) == TestError.default - } - - it("should forward errors from the subsequent producer") { - let original = SignalProducer.empty - let subsequent = SignalProducer(error: .default) - - let result = original.then(subsequent).first() - expect(result?.error) == TestError.default - } - - it("should forward interruptions from the original producer") { - let (original, observer) = SignalProducer.pipe() - - var subsequentStarted = false - let subsequent = SignalProducer { observer, _ in - subsequentStarted = true - } - - var interrupted = false - let producer = original.then(subsequent) - producer.startWithInterrupted { - interrupted = true - } - expect(subsequentStarted) == false - - observer.sendInterrupted() - expect(interrupted) == true - } - - it("should complete when both inputs have completed") { - let (original, originalObserver) = SignalProducer.pipe() - let (subsequent, subsequentObserver) = SignalProducer.pipe() - - let producer = original.then(subsequent) - - var completed = false - producer.startWithCompleted { - completed = true - } - - originalObserver.sendCompleted() - expect(completed) == false - - subsequentObserver.sendCompleted() - expect(completed) == true - } - - it("works with NoError and TestError") { - let producer: SignalProducer = SignalProducer.empty - .then(SignalProducer.empty) - - _ = producer - } - - it("works with TestError and NoError") { - let producer: SignalProducer = SignalProducer.empty - .then(SignalProducer.empty) - - _ = producer - } - - it("works with NoError and NoError") { - let producer: SignalProducer = SignalProducer.empty - .then(SignalProducer.empty) - - _ = producer - } - } - - describe("first") { - it("should start a signal then block on the first value") { - let (_signal, observer) = Signal.pipe() - - let forwardingScheduler: QueueScheduler - - if #available(OSX 10.10, *) { - forwardingScheduler = QueueScheduler(qos: .default, name: "\(#file):\(#line)") - } else { - forwardingScheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - let producer = SignalProducer(signal: _signal.delay(0.1, on: forwardingScheduler)) - - let observingScheduler: QueueScheduler - - if #available(OSX 10.10, *) { - observingScheduler = QueueScheduler(qos: .default, name: "\(#file):\(#line)") - } else { - observingScheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - var result: Int? - - observingScheduler.schedule { - result = producer.first()?.value - } - - expect(result).to(beNil()) - - observer.sendNext(1) - expect(result).toEventually(equal(1), timeout: 5.0) - } - - it("should return a nil result if no values are sent before completion") { - let result = SignalProducer.empty.first() - expect(result).to(beNil()) - } - - it("should return the first value if more than one value is sent") { - let result = SignalProducer(values: [ 1, 2 ]).first() - expect(result?.value) == 1 - } - - it("should return an error if one occurs before the first value") { - let result = SignalProducer(error: .default).first() - expect(result?.error) == TestError.default - } - } - - describe("single") { - it("should start a signal then block until completion") { - let (_signal, observer) = Signal.pipe() - let forwardingScheduler: QueueScheduler - - if #available(OSX 10.10, *) { - forwardingScheduler = QueueScheduler(qos: .default, name: "\(#file):\(#line)") - } else { - forwardingScheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - let producer = SignalProducer(signal: _signal.delay(0.1, on: forwardingScheduler)) - - let observingScheduler: QueueScheduler - - if #available(OSX 10.10, *) { - observingScheduler = QueueScheduler(qos: .default, name: "\(#file):\(#line)") - } else { - observingScheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - var result: Int? - - observingScheduler.schedule { - result = producer.single()?.value - } - expect(result).to(beNil()) - - observer.sendNext(1) - - Thread.sleep(forTimeInterval: 3.0) - expect(result).to(beNil()) - - observer.sendCompleted() - expect(result).toEventually(equal(1)) - } - - it("should return a nil result if no values are sent before completion") { - let result = SignalProducer.empty.single() - expect(result).to(beNil()) - } - - it("should return a nil result if more than one value is sent before completion") { - let result = SignalProducer(values: [ 1, 2 ]).single() - expect(result).to(beNil()) - } - - it("should return an error if one occurs") { - let result = SignalProducer(error: .default).single() - expect(result?.error) == TestError.default - } - } - - describe("last") { - it("should start a signal then block until completion") { - let (_signal, observer) = Signal.pipe() - let scheduler: QueueScheduler - - if #available(*, OSX 10.10) { - scheduler = QueueScheduler(name: "\(#file):\(#line)") - } else { - scheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - let producer = SignalProducer(signal: _signal.delay(0.1, on: scheduler)) - - var result: Result? - - let group = DispatchGroup() - - let globalQueue: DispatchQueue - if #available(*, OSX 10.10) { - globalQueue = DispatchQueue.global() - } else { - globalQueue = DispatchQueue.global(priority: .default) - } - - globalQueue.async(group: group, flags: []) { - result = producer.last() - } - expect(result).to(beNil()) - - observer.sendNext(1) - observer.sendNext(2) - expect(result).to(beNil()) - - observer.sendCompleted() - group.wait() - - expect(result?.value) == 2 - } - - it("should return a nil result if no values are sent before completion") { - let result = SignalProducer.empty.last() - expect(result).to(beNil()) - } - - it("should return the last value if more than one value is sent") { - let result = SignalProducer(values: [ 1, 2 ]).last() - expect(result?.value) == 2 - } - - it("should return an error if one occurs") { - let result = SignalProducer(error: .default).last() - expect(result?.error) == TestError.default - } - } - - describe("wait") { - it("should start a signal then block until completion") { - let (_signal, observer) = Signal.pipe() - let scheduler: QueueScheduler - if #available(*, OSX 10.10) { - scheduler = QueueScheduler(name: "\(#file):\(#line)") - } else { - scheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - let producer = SignalProducer(signal: _signal.delay(0.1, on: scheduler)) - - var result: Result<(), NoError>? - - let group = DispatchGroup() - - let globalQueue: DispatchQueue - if #available(*, OSX 10.10) { - globalQueue = DispatchQueue.global() - } else { - globalQueue = DispatchQueue.global(priority: .default) - } - - globalQueue.async(group: group, flags: []) { - result = producer.wait() - } - - expect(result).to(beNil()) - - observer.sendCompleted() - group.wait() - - expect(result?.value).toNot(beNil()) - } - - it("should return an error if one occurs") { - let result = SignalProducer(error: .default).wait() - expect(result.error) == TestError.default - } - } - - describe("observeOn") { - it("should immediately cancel upstream producer's work when disposed") { - var upstreamDisposable: Disposable! - let producer = SignalProducer<(), NoError>{ _, innerDisposable in - upstreamDisposable = innerDisposable - } - - var downstreamDisposable: Disposable! - producer - .observe(on: TestScheduler()) - .startWithSignal { signal, innerDisposable in - downstreamDisposable = innerDisposable - } - - expect(upstreamDisposable.isDisposed) == false - - downstreamDisposable.dispose() - expect(upstreamDisposable.isDisposed) == true - } - } - - describe("take") { - it("Should not start concat'ed producer if the first one sends a value when using take(1)") { - let scheduler: QueueScheduler - if #available(OSX 10.10, *) { - scheduler = QueueScheduler(name: "\(#file):\(#line)") - } else { - scheduler = QueueScheduler(queue: DispatchQueue(label: "\(#file):\(#line)")) - } - - // Delaying producer1 from sending a value to test whether producer2 is started in the mean-time. - let producer1 = SignalProducer() { handler, _ in - handler.sendNext(1) - handler.sendCompleted() - }.start(on: scheduler) - - var started = false - let producer2 = SignalProducer() { handler, _ in - started = true - handler.sendNext(2) - handler.sendCompleted() - } - - let result = producer1.concat(producer2).take(first: 1).collect().first() - - expect(result?.value) == [1] - expect(started) == false - } - } - - describe("replayLazily") { - var producer: SignalProducer! - var observer: SignalProducer.ProducedSignal.Observer! - - var replayedProducer: SignalProducer! - - beforeEach { - let (producerTemp, observerTemp) = SignalProducer.pipe() - producer = producerTemp - observer = observerTemp - - replayedProducer = producer.replayLazily(upTo: 2) - } - - context("subscribing to underlying producer") { - it("emits new values") { - var last: Int? - - replayedProducer - .assumeNoErrors() - .startWithNext { last = $0 } - - expect(last).to(beNil()) - - observer.sendNext(1) - expect(last) == 1 - - observer.sendNext(2) - expect(last) == 2 - } - - it("emits errors") { - var error: TestError? - - replayedProducer.startWithFailed { error = $0 } - expect(error).to(beNil()) - - observer.sendFailed(.default) - expect(error) == TestError.default - } - } - - context("buffers past values") { - it("emits last value upon subscription") { - let disposable = replayedProducer - .start() - - observer.sendNext(1) - disposable.dispose() - - var last: Int? - - replayedProducer - .assumeNoErrors() - .startWithNext { last = $0 } - expect(last) == 1 - } - - it("emits previous failure upon subscription") { - let disposable = replayedProducer - .start() - - observer.sendFailed(.default) - disposable.dispose() - - var error: TestError? - - replayedProducer - .startWithFailed { error = $0 } - expect(error) == TestError.default - } - - it("emits last n values upon subscription") { - var disposable = replayedProducer - .start() - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - disposable.dispose() - - var values: [Int] = [] - - disposable = replayedProducer - .assumeNoErrors() - .startWithNext { values.append($0) } - expect(values) == [ 3, 4 ] - - observer.sendNext(5) - expect(values) == [ 3, 4, 5 ] - - disposable.dispose() - values = [] - - replayedProducer - .assumeNoErrors() - .startWithNext { values.append($0) } - expect(values) == [ 4, 5 ] - } - } - - context("starting underying producer") { - it("starts lazily") { - var started = false - - let producer = SignalProducer(value: 0) - .on(started: { started = true }) - expect(started) == false - - let replayedProducer = producer - .replayLazily(upTo: 1) - expect(started) == false - - replayedProducer.start() - expect(started) == true - } - - it("shares a single subscription") { - var startedTimes = 0 - - let producer = SignalProducer.never - .on(started: { startedTimes += 1 }) - expect(startedTimes) == 0 - - let replayedProducer = producer - .replayLazily(upTo: 1) - expect(startedTimes) == 0 - - replayedProducer.start() - expect(startedTimes) == 1 - - replayedProducer.start() - expect(startedTimes) == 1 - } - - it("does not start multiple times when subscribing multiple times") { - var startedTimes = 0 - - let producer = SignalProducer(value: 0) - .on(started: { startedTimes += 1 }) - - let replayedProducer = producer - .replayLazily(upTo: 1) - - expect(startedTimes) == 0 - replayedProducer.start().dispose() - expect(startedTimes) == 1 - replayedProducer.start().dispose() - expect(startedTimes) == 1 - } - - it("does not start again if it finished") { - var startedTimes = 0 - - let producer = SignalProducer.empty - .on(started: { startedTimes += 1 }) - expect(startedTimes) == 0 - - let replayedProducer = producer - .replayLazily(upTo: 1) - expect(startedTimes) == 0 - - replayedProducer.start() - expect(startedTimes) == 1 - - replayedProducer.start() - expect(startedTimes) == 1 - } - } - - context("lifetime") { - it("does not dispose underlying subscription if the replayed producer is still in memory") { - var disposed = false - - let producer = SignalProducer.never - .on(disposed: { disposed = true }) - - let replayedProducer = producer - .replayLazily(upTo: 1) - - expect(disposed) == false - let disposable = replayedProducer.start() - expect(disposed) == false - - disposable.dispose() - expect(disposed) == false - } - - it("does not dispose if it has active subscriptions") { - var disposed = false - - let producer = SignalProducer.never - .on(disposed: { disposed = true }) - - var replayedProducer = ImplicitlyUnwrappedOptional(producer.replayLazily(upTo: 1)) - - expect(disposed) == false - let disposable1 = replayedProducer?.start() - let disposable2 = replayedProducer?.start() - expect(disposed) == false - - replayedProducer = nil - expect(disposed) == false - - disposable1?.dispose() - expect(disposed) == false - - disposable2?.dispose() - expect(disposed) == true - } - - it("disposes underlying producer when the producer is deallocated") { - var disposed = false - - let producer = SignalProducer.never - .on(disposed: { disposed = true }) - - var replayedProducer = ImplicitlyUnwrappedOptional(producer.replayLazily(upTo: 1)) - - expect(disposed) == false - let disposable = replayedProducer?.start() - expect(disposed) == false - - disposable?.dispose() - expect(disposed) == false - - replayedProducer = nil - expect(disposed) == true - } - - it("does not leak buffered values") { - final class Value { - private let deinitBlock: () -> Void - - init(deinitBlock: @escaping () -> Void) { - self.deinitBlock = deinitBlock - } - - deinit { - self.deinitBlock() - } - } - - var deinitValues = 0 - - var producer: SignalProducer! = SignalProducer(value: Value { - deinitValues += 1 - }) - expect(deinitValues) == 0 - - var replayedProducer: SignalProducer! = producer - .replayLazily(upTo: 1) - - let disposable = replayedProducer - .start() - - disposable.dispose() - expect(deinitValues) == 0 - - producer = nil - expect(deinitValues) == 0 - - replayedProducer = nil - expect(deinitValues) == 1 - } - } - - describe("log events") { - it("should output the correct event") { - let expectations: [(String) -> Void] = [ - { event in expect(event) == "[] started" }, - { event in expect(event) == "[] next 1" }, - { event in expect(event) == "[] completed" }, - { event in expect(event) == "[] terminated" }, - { event in expect(event) == "[] disposed" } - ] - - let logger = TestLogger(expectations: expectations) - - let (producer, observer) = SignalProducer.pipe() - producer - .logEvents(logger: logger.logEvent) - .start() - - observer.sendNext(1) - observer.sendCompleted() - } - } - - describe("init(values) ambiguity") { - it("should not be a SignalProducer, NoError>") { - - let producer1: SignalProducer = SignalProducer.empty - let producer2: SignalProducer = SignalProducer.empty - - // This expression verifies at compile time that the type is as expected. - let _: SignalProducer = SignalProducer(values: [producer1, producer2]) - .flatten(.merge) - } - } - } - - describe("take(during:)") { - it("completes a signal when the lifetime ends") { - let (signal, observer) = Signal.pipe() - let object = MutableReference(TestObject()) - - let output = signal.take(during: object.value!.lifetime) - - var results: [Int] = [] - output.observeNext { results.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - object.value = nil - observer.sendNext(3) - - expect(results) == [1, 2] - } - - it("completes a signal producer when the lifetime ends") { - let (producer, observer) = Signal.pipe() - let object = MutableReference(TestObject()) - - let output = producer.take(during: object.value!.lifetime) - - var results: [Int] = [] - output.observeNext { results.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - object.value = nil - observer.sendNext(3) - - expect(results) == [1, 2] - } - } - } -} - -// MARK: - Helpers - -extension SignalProducer { - internal static func pipe() -> (SignalProducer, ProducedSignal.Observer) { - let (signal, observer) = ProducedSignal.pipe() - let producer = SignalProducer(signal: signal) - return (producer, observer) - } - - /// Creates a producer that can be started as many times as elements in `results`. - /// Each signal will immediately send either a value or an error. - fileprivate static func attemptWithResults(_ results: C) -> SignalProducer where C.Iterator.Element == Result, C.IndexDistance == C.Index, C.Index == Int { - let resultCount = results.count - var operationIndex = 0 - - precondition(resultCount > 0) - - let operation: () -> Result = { - if operationIndex < resultCount { - defer { - operationIndex += 1 - } - - return results[results.index(results.startIndex, offsetBy: operationIndex)] - } else { - fail("Operation started too many times") - - return results[results.startIndex] - } - } - - return SignalProducer.attempt(operation) - } -} diff --git a/ReactiveCocoaTests/Swift/SignalSpec.swift b/ReactiveCocoaTests/Swift/SignalSpec.swift deleted file mode 100755 index 5114b43d5b..0000000000 --- a/ReactiveCocoaTests/Swift/SignalSpec.swift +++ /dev/null @@ -1,2268 +0,0 @@ -// -// SignalSpec.swift -// ReactiveCocoa -// -// Created by Justin Spahr-Summers on 2015-01-23. -// Copyright (c) 2015 GitHub. All rights reserved. -// - -import Result -import Nimble -import Quick -import ReactiveCocoa - -class SignalSpec: QuickSpec { - override func spec() { - describe("init") { - var testScheduler: TestScheduler! - - beforeEach { - testScheduler = TestScheduler() - } - - it("should run the generator immediately") { - var didRunGenerator = false - _ = Signal { observer in - didRunGenerator = true - return nil - } - - expect(didRunGenerator) == true - } - - it("should forward events to observers") { - let numbers = [ 1, 2, 5 ] - - let signal: Signal = Signal { observer in - testScheduler.schedule { - for number in numbers { - observer.sendNext(number) - } - observer.sendCompleted() - } - return nil - } - - var fromSignal: [Int] = [] - var completed = false - - signal.observe { event in - switch event { - case let .next(number): - fromSignal.append(number) - case .completed: - completed = true - default: - break - } - } - - expect(completed) == false - expect(fromSignal).to(beEmpty()) - - testScheduler.run() - - expect(completed) == true - expect(fromSignal) == numbers - } - - it("should dispose of returned disposable upon error") { - let disposable = SimpleDisposable() - - let signal: Signal = Signal { observer in - testScheduler.schedule { - observer.sendFailed(TestError.default) - } - return disposable - } - - var errored = false - - signal.observeFailed { _ in errored = true } - - expect(errored) == false - expect(disposable.isDisposed) == false - - testScheduler.run() - - expect(errored) == true - expect(disposable.isDisposed) == true - } - - it("should dispose of returned disposable upon completion") { - let disposable = SimpleDisposable() - - let signal: Signal = Signal { observer in - testScheduler.schedule { - observer.sendCompleted() - } - return disposable - } - - var completed = false - - signal.observeCompleted { completed = true } - - expect(completed) == false - expect(disposable.isDisposed) == false - - testScheduler.run() - - expect(completed) == true - expect(disposable.isDisposed) == true - } - - it("should dispose of returned disposable upon interrupted") { - let disposable = SimpleDisposable() - - let signal: Signal = Signal { observer in - testScheduler.schedule { - observer.sendInterrupted() - } - return disposable - } - - var interrupted = false - signal.observeInterrupted { - interrupted = true - } - - expect(interrupted) == false - expect(disposable.isDisposed) == false - - testScheduler.run() - - expect(interrupted) == true - expect(disposable.isDisposed) == true - } - } - - describe("Signal.empty") { - it("should interrupt its observers without emitting any value") { - let signal = Signal<(), NoError>.empty - - var hasUnexpectedEventsEmitted = false - var signalInterrupted = false - - signal.observe { event in - switch event { - case .next, .failed, .completed: - hasUnexpectedEventsEmitted = true - case .interrupted: - signalInterrupted = true - } - } - - expect(hasUnexpectedEventsEmitted) == false - expect(signalInterrupted) == true - } - } - - describe("Signal.pipe") { - it("should forward events to observers") { - let (signal, observer) = Signal.pipe() - - var fromSignal: [Int] = [] - var completed = false - - signal.observe { event in - switch event { - case let .next(number): - fromSignal.append(number) - case .completed: - completed = true - default: - break - } - } - - expect(fromSignal).to(beEmpty()) - expect(completed) == false - - observer.sendNext(1) - expect(fromSignal) == [ 1 ] - - observer.sendNext(2) - expect(fromSignal) == [ 1, 2 ] - - expect(completed) == false - observer.sendCompleted() - expect(completed) == true - } - - context("memory") { - it("should not crash allocating memory with a few observers") { - let (signal, _) = Signal.pipe() - - for _ in 0..<50 { - autoreleasepool { - let disposable = signal.observe { _ in } - - disposable!.dispose() - } - } - } - } - } - - describe("observe") { - var testScheduler: TestScheduler! - - beforeEach { - testScheduler = TestScheduler() - } - - it("should stop forwarding events when disposed") { - let disposable = SimpleDisposable() - - let signal: Signal = Signal { observer in - testScheduler.schedule { - for number in [ 1, 2 ] { - observer.sendNext(number) - } - observer.sendCompleted() - observer.sendNext(4) - } - return disposable - } - - var fromSignal: [Int] = [] - signal.observeNext { number in - fromSignal.append(number) - } - - expect(disposable.isDisposed) == false - expect(fromSignal).to(beEmpty()) - - testScheduler.run() - - expect(disposable.isDisposed) == true - expect(fromSignal) == [ 1, 2 ] - } - - it("should not trigger side effects") { - var runCount = 0 - let signal: Signal<(), NoError> = Signal { observer in - runCount += 1 - return nil - } - - expect(runCount) == 1 - - signal.observe(Observer<(), NoError>()) - expect(runCount) == 1 - } - - it("should release observer after termination") { - weak var testStr: NSMutableString? - let (signal, observer) = Signal.pipe() - - let test = { - let innerStr: NSMutableString = NSMutableString() - signal.observeNext { value in - innerStr.append("\(value)") - } - testStr = innerStr - } - test() - - observer.sendNext(1) - expect(testStr) == "1" - observer.sendNext(2) - expect(testStr) == "12" - - observer.sendCompleted() - expect(testStr).to(beNil()) - } - - it("should release observer after interruption") { - weak var testStr: NSMutableString? - let (signal, observer) = Signal.pipe() - - let test = { - let innerStr: NSMutableString = NSMutableString() - signal.observeNext { value in - innerStr.append("\(value)") - } - - testStr = innerStr - } - - test() - - observer.sendNext(1) - expect(testStr) == "1" - - observer.sendNext(2) - expect(testStr) == "12" - - observer.sendInterrupted() - expect(testStr).to(beNil()) - } - } - - describe("trailing closure") { - it("receives next values") { - var values = [Int]() - let (signal, observer) = Signal.pipe() - - signal.observeNext { next in - values.append(next) - } - - observer.sendNext(1) - expect(values) == [1] - } - - it("receives results") { - let (signal, observer) = Signal.pipe() - - var results: [Result] = [] - signal.observeResult { results.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendFailed(.default) - - observer.sendCompleted() - - expect(results).to(haveCount(4)) - expect(results[0].value) == 1 - expect(results[1].value) == 2 - expect(results[2].value) == 3 - expect(results[3].error) == .default - } - } - - describe("map") { - it("should transform the values of the signal") { - let (signal, observer) = Signal.pipe() - let mappedSignal = signal.map { String($0 + 1) } - - var lastValue: String? - - mappedSignal.observeNext { - lastValue = $0 - return - } - - expect(lastValue).to(beNil()) - - observer.sendNext(0) - expect(lastValue) == "1" - - observer.sendNext(1) - expect(lastValue) == "2" - } - } - - - describe("mapError") { - it("should transform the errors of the signal") { - let (signal, observer) = Signal.pipe() - let producerError = NSError(domain: "com.reactivecocoa.errordomain", code: 100, userInfo: nil) - var error: NSError? - - signal - .mapError { _ in producerError } - .observeFailed { err in error = err } - - expect(error).to(beNil()) - - observer.sendFailed(TestError.default) - expect(error) == producerError - } - } - - describe("filter") { - it("should omit values from the signal") { - let (signal, observer) = Signal.pipe() - let mappedSignal = signal.filter { $0 % 2 == 0 } - - var lastValue: Int? - - mappedSignal.observeNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext(0) - expect(lastValue) == 0 - - observer.sendNext(1) - expect(lastValue) == 0 - - observer.sendNext(2) - expect(lastValue) == 2 - } - } - - describe("skipNil") { - it("should forward only non-nil values") { - let (signal, observer) = Signal.pipe() - let mappedSignal = signal.skipNil() - - var lastValue: Int? - - mappedSignal.observeNext { lastValue = $0 } - expect(lastValue).to(beNil()) - - observer.sendNext(nil) - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(nil) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - } - } - - describe("scan") { - it("should incrementally accumulate a value") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.scan("", +) - - var lastValue: String? - - signal.observeNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext("a") - expect(lastValue) == "a" - - observer.sendNext("bb") - expect(lastValue) == "abb" - } - } - - describe("reduce") { - it("should accumulate one value") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.reduce(1, +) - - var lastValue: Int? - var completed = false - - signal.observe { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - default: - break - } - } - - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue).to(beNil()) - - expect(completed) == false - observer.sendCompleted() - expect(completed) == true - - expect(lastValue) == 4 - } - - it("should send the initial value if none are received") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.reduce(1, +) - - var lastValue: Int? - var completed = false - - signal.observe { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - default: - break - } - } - - expect(lastValue).to(beNil()) - expect(completed) == false - - observer.sendCompleted() - - expect(lastValue) == 1 - expect(completed) == true - } - } - - describe("skip") { - it("should skip initial values") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.skip(first: 1) - - var lastValue: Int? - signal.observeNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue) == 2 - } - - it("should not skip any values when 0") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.skip(first: 0) - - var lastValue: Int? - signal.observeNext { lastValue = $0 } - - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - } - } - - describe("skipRepeats") { - it("should skip duplicate Equatable values") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.skipRepeats() - - var values: [Bool] = [] - signal.observeNext { values.append($0) } - - expect(values) == [] - - observer.sendNext(true) - expect(values) == [ true ] - - observer.sendNext(true) - expect(values) == [ true ] - - observer.sendNext(false) - expect(values) == [ true, false ] - - observer.sendNext(true) - expect(values) == [ true, false, true ] - } - - it("should skip values according to a predicate") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.skipRepeats { $0.characters.count == $1.characters.count } - - var values: [String] = [] - signal.observeNext { values.append($0) } - - expect(values) == [] - - observer.sendNext("a") - expect(values) == [ "a" ] - - observer.sendNext("b") - expect(values) == [ "a" ] - - observer.sendNext("cc") - expect(values) == [ "a", "cc" ] - - observer.sendNext("d") - expect(values) == [ "a", "cc", "d" ] - } - - it("should not store strong reference to previously passed items") { - var disposedItems: [Bool] = [] - - struct Item { - let payload: Bool - let disposable: ScopedDisposable - } - - func item(_ payload: Bool) -> Item { - return Item( - payload: payload, - disposable: ScopedDisposable(ActionDisposable { disposedItems.append(payload) }) - ) - } - - let (baseSignal, observer) = Signal.pipe() - baseSignal.skipRepeats { $0.payload == $1.payload }.observeNext { _ in } - - observer.sendNext(item(true)) - expect(disposedItems) == [] - - observer.sendNext(item(false)) - expect(disposedItems) == [ true ] - - observer.sendNext(item(false)) - expect(disposedItems) == [ true, false ] - - observer.sendNext(item(true)) - expect(disposedItems) == [ true, false, false ] - - observer.sendCompleted() - expect(disposedItems) == [ true, false, false, true ] - } - } - - describe("uniqueValues") { - it("should skip values that have been already seen") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.uniqueValues() - - var values: [String] = [] - signal.observeNext { values.append($0) } - - expect(values) == [] - - observer.sendNext("a") - expect(values) == [ "a" ] - - observer.sendNext("b") - expect(values) == [ "a", "b" ] - - observer.sendNext("a") - expect(values) == [ "a", "b" ] - - observer.sendNext("b") - expect(values) == [ "a", "b" ] - - observer.sendNext("c") - expect(values) == [ "a", "b", "c" ] - - observer.sendCompleted() - expect(values) == [ "a", "b", "c" ] - } - } - - describe("skipWhile") { - var signal: Signal! - var observer: Signal.Observer! - - var lastValue: Int? - - beforeEach { - let (baseSignal, incomingObserver) = Signal.pipe() - - signal = baseSignal.skip { $0 < 2 } - observer = incomingObserver - lastValue = nil - - signal.observeNext { lastValue = $0 } - } - - it("should skip while the predicate is true") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue) == 2 - - observer.sendNext(0) - expect(lastValue) == 0 - } - - it("should not skip any values when the predicate starts false") { - expect(lastValue).to(beNil()) - - observer.sendNext(3) - expect(lastValue) == 3 - - observer.sendNext(1) - expect(lastValue) == 1 - } - } - - describe("skipUntil") { - var signal: Signal! - var observer: Signal.Observer! - var triggerObserver: Signal<(), NoError>.Observer! - - var lastValue: Int? = nil - - beforeEach { - let (baseSignal, incomingObserver) = Signal.pipe() - let (triggerSignal, incomingTriggerObserver) = Signal<(), NoError>.pipe() - - signal = baseSignal.skip(until: triggerSignal) - observer = incomingObserver - triggerObserver = incomingTriggerObserver - - lastValue = nil - - signal.observe { event in - switch event { - case let .next(value): - lastValue = value - default: - break - } - } - } - - it("should skip values until the trigger fires") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue).to(beNil()) - - triggerObserver.sendNext(()) - observer.sendNext(0) - expect(lastValue) == 0 - } - - it("should skip values until the trigger completes") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue).to(beNil()) - - observer.sendNext(2) - expect(lastValue).to(beNil()) - - triggerObserver.sendCompleted() - observer.sendNext(0) - expect(lastValue) == 0 - } - } - - describe("take") { - it("should take initial values") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.take(first: 2) - - var lastValue: Int? - var completed = false - signal.observe { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - default: - break - } - } - - expect(lastValue).to(beNil()) - expect(completed) == false - - observer.sendNext(1) - expect(lastValue) == 1 - expect(completed) == false - - observer.sendNext(2) - expect(lastValue) == 2 - expect(completed) == true - } - - it("should complete immediately after taking given number of values") { - let numbers = [ 1, 2, 4, 4, 5 ] - let testScheduler = TestScheduler() - - var signal: Signal = Signal { observer in - testScheduler.schedule { - for number in numbers { - observer.sendNext(number) - } - } - return nil - } - - var completed = false - - signal = signal.take(first: numbers.count) - signal.observeCompleted { completed = true } - - expect(completed) == false - testScheduler.run() - expect(completed) == true - } - - it("should interrupt when 0") { - let numbers = [ 1, 2, 4, 4, 5 ] - let testScheduler = TestScheduler() - - let signal: Signal = Signal { observer in - testScheduler.schedule { - for number in numbers { - observer.sendNext(number) - } - } - return nil - } - - var result: [Int] = [] - var interrupted = false - - signal - .take(first: 0) - .observe { event in - switch event { - case let .next(number): - result.append(number) - case .interrupted: - interrupted = true - default: - break - } - } - - expect(interrupted) == true - - testScheduler.run() - expect(result).to(beEmpty()) - } - } - - describe("collect") { - it("should collect all values") { - let (original, observer) = Signal.pipe() - let signal = original.collect() - let expectedResult = [ 1, 2, 3 ] - - var result: [Int]? - - signal.observeNext { value in - expect(result).to(beNil()) - result = value - } - - for number in expectedResult { - observer.sendNext(number) - } - - expect(result).to(beNil()) - observer.sendCompleted() - expect(result) == expectedResult - } - - it("should complete with an empty array if there are no values") { - let (original, observer) = Signal.pipe() - let signal = original.collect() - - var result: [Int]? - - signal.observeNext { result = $0 } - - expect(result).to(beNil()) - observer.sendCompleted() - expect(result) == [] - } - - it("should forward errors") { - let (original, observer) = Signal.pipe() - let signal = original.collect() - - var error: TestError? - - signal.observeFailed { error = $0 } - - expect(error).to(beNil()) - observer.sendFailed(.default) - expect(error) == TestError.default - } - - it("should collect an exact count of values") { - let (original, observer) = Signal.pipe() - - let signal = original.collect(count: 3) - - var observedValues: [[Int]] = [] - - signal.observeNext { value in - observedValues.append(value) - } - - var expectation: [[Int]] = [] - - for i in 1...7 { - observer.sendNext(i) - - if i % 3 == 0 { - expectation.append([Int]((i - 2)...i)) - expect(observedValues as NSArray) == expectation as NSArray - } else { - expect(observedValues as NSArray) == expectation as NSArray - } - } - - observer.sendCompleted() - - expectation.append([7]) - expect(observedValues as NSArray) == expectation as NSArray - } - - it("should collect values until it matches a certain value") { - let (original, observer) = Signal.pipe() - - let signal = original.collect { _, next in next != 5 } - - var expectedValues = [ - [5, 5], - [42, 5] - ] - - signal.observeNext { value in - expect(value) == expectedValues.removeFirst() - } - - signal.observeCompleted { - expect(expectedValues as NSArray) == [] - } - - expectedValues - .flatMap { $0 } - .forEach(observer.sendNext) - - observer.sendCompleted() - } - - it("should collect values until it matches a certain condition on values") { - let (original, observer) = Signal.pipe() - - let signal = original.collect { values in values.reduce(0, +) == 10 } - - var expectedValues = [ - [1, 2, 3, 4], - [5, 6, 7, 8, 9] - ] - - signal.observeNext { value in - expect(value) == expectedValues.removeFirst() - } - - signal.observeCompleted { - expect(expectedValues as NSArray) == [] - } - - expectedValues - .flatMap { $0 } - .forEach(observer.sendNext) - - observer.sendCompleted() - } - } - - describe("takeUntil") { - var signal: Signal! - var observer: Signal.Observer! - var triggerObserver: Signal<(), NoError>.Observer! - - var lastValue: Int? = nil - var completed: Bool = false - - beforeEach { - let (baseSignal, incomingObserver) = Signal.pipe() - let (triggerSignal, incomingTriggerObserver) = Signal<(), NoError>.pipe() - - signal = baseSignal.take(until: triggerSignal) - observer = incomingObserver - triggerObserver = incomingTriggerObserver - - lastValue = nil - completed = false - - signal.observe { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - default: - break - } - } - } - - it("should take values until the trigger fires") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - expect(completed) == false - triggerObserver.sendNext(()) - expect(completed) == true - } - - it("should take values until the trigger completes") { - expect(lastValue).to(beNil()) - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - expect(completed) == false - triggerObserver.sendCompleted() - expect(completed) == true - } - - it("should complete if the trigger fires immediately") { - expect(lastValue).to(beNil()) - expect(completed) == false - - triggerObserver.sendNext(()) - - expect(completed) == true - expect(lastValue).to(beNil()) - } - } - - describe("takeUntilReplacement") { - var signal: Signal! - var observer: Signal.Observer! - var replacementObserver: Signal.Observer! - - var lastValue: Int? = nil - var completed: Bool = false - - beforeEach { - let (baseSignal, incomingObserver) = Signal.pipe() - let (replacementSignal, incomingReplacementObserver) = Signal.pipe() - - signal = baseSignal.take(untilReplacement: replacementSignal) - observer = incomingObserver - replacementObserver = incomingReplacementObserver - - lastValue = nil - completed = false - - signal.observe { event in - switch event { - case let .next(value): - lastValue = value - case .completed: - completed = true - default: - break - } - } - } - - it("should take values from the original then the replacement") { - expect(lastValue).to(beNil()) - expect(completed) == false - - observer.sendNext(1) - expect(lastValue) == 1 - - observer.sendNext(2) - expect(lastValue) == 2 - - replacementObserver.sendNext(3) - - expect(lastValue) == 3 - expect(completed) == false - - observer.sendNext(4) - - expect(lastValue) == 3 - expect(completed) == false - - replacementObserver.sendNext(5) - expect(lastValue) == 5 - - expect(completed) == false - replacementObserver.sendCompleted() - expect(completed) == true - } - } - - describe("takeWhile") { - var signal: Signal! - var observer: Signal.Observer! - - beforeEach { - let (baseSignal, incomingObserver) = Signal.pipe() - signal = baseSignal.take { $0 <= 4 } - observer = incomingObserver - } - - it("should take while the predicate is true") { - var latestValue: Int! - var completed = false - - signal.observe { event in - switch event { - case let .next(value): - latestValue = value - case .completed: - completed = true - default: - break - } - } - - for value in -1...4 { - observer.sendNext(value) - expect(latestValue) == value - expect(completed) == false - } - - observer.sendNext(5) - expect(latestValue) == 4 - expect(completed) == true - } - - it("should complete if the predicate starts false") { - var latestValue: Int? - var completed = false - - signal.observe { event in - switch event { - case let .next(value): - latestValue = value - case .completed: - completed = true - default: - break - } - } - - observer.sendNext(5) - expect(latestValue).to(beNil()) - expect(completed) == true - } - } - - describe("observeOn") { - it("should send events on the given scheduler") { - let testScheduler = TestScheduler() - let (signal, observer) = Signal.pipe() - - var result: [Int] = [] - - signal - .observe(on: testScheduler) - .observeNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - expect(result).to(beEmpty()) - - testScheduler.run() - expect(result) == [ 1, 2 ] - } - } - - describe("delay") { - it("should send events on the given scheduler after the interval") { - let testScheduler = TestScheduler() - let signal: Signal = Signal { observer in - testScheduler.schedule { - observer.sendNext(1) - } - testScheduler.schedule(after: 5) { - observer.sendNext(2) - observer.sendCompleted() - } - return nil - } - - var result: [Int] = [] - var completed = false - - signal - .delay(10, on: testScheduler) - .observe { event in - switch event { - case let .next(number): - result.append(number) - case .completed: - completed = true - default: - break - } - } - - testScheduler.advance(by: 4) // send initial value - expect(result).to(beEmpty()) - - testScheduler.advance(by: 10) // send second value and receive first - expect(result) == [ 1 ] - expect(completed) == false - - testScheduler.advance(by: 10) // send second value and receive first - expect(result) == [ 1, 2 ] - expect(completed) == true - } - - it("should schedule errors immediately") { - let testScheduler = TestScheduler() - let signal: Signal = Signal { observer in - testScheduler.schedule { - observer.sendFailed(TestError.default) - } - return nil - } - - var errored = false - - signal - .delay(10, on: testScheduler) - .observeFailed { _ in errored = true } - - testScheduler.advance() - expect(errored) == true - } - } - - describe("throttle") { - var scheduler: TestScheduler! - var observer: Signal.Observer! - var signal: Signal! - - beforeEach { - scheduler = TestScheduler() - - let (baseSignal, baseObserver) = Signal.pipe() - observer = baseObserver - - signal = baseSignal.throttle(1, on: scheduler) - expect(signal).notTo(beNil()) - } - - it("should send values on the given scheduler at no less than the interval") { - var values: [Int] = [] - signal.observeNext { value in - values.append(value) - } - - expect(values) == [] - - observer.sendNext(0) - expect(values) == [] - - scheduler.advance() - expect(values) == [ 0 ] - - observer.sendNext(1) - observer.sendNext(2) - expect(values) == [ 0 ] - - scheduler.advance(by: 1.5) - expect(values) == [ 0, 2 ] - - scheduler.advance(by: 3) - expect(values) == [ 0, 2 ] - - observer.sendNext(3) - expect(values) == [ 0, 2 ] - - scheduler.advance() - expect(values) == [ 0, 2, 3 ] - - observer.sendNext(4) - observer.sendNext(5) - scheduler.advance() - expect(values) == [ 0, 2, 3 ] - - scheduler.rewind(by: 2) - expect(values) == [ 0, 2, 3 ] - - observer.sendNext(6) - scheduler.advance() - expect(values) == [ 0, 2, 3, 6 ] - - observer.sendNext(7) - observer.sendNext(8) - scheduler.advance() - expect(values) == [ 0, 2, 3, 6 ] - - scheduler.run() - expect(values) == [ 0, 2, 3, 6, 8 ] - } - - it("should schedule completion immediately") { - var values: [Int] = [] - var completed = false - - signal.observe { event in - switch event { - case let .next(value): - values.append(value) - case .completed: - completed = true - default: - break - } - } - - observer.sendNext(0) - scheduler.advance() - expect(values) == [ 0 ] - - observer.sendNext(1) - observer.sendCompleted() - expect(completed) == false - - scheduler.advance() - expect(values) == [ 0 ] - expect(completed) == true - - scheduler.run() - expect(values) == [ 0 ] - expect(completed) == true - } - } - - describe("debounce") { - var scheduler: TestScheduler! - var observer: Signal.Observer! - var signal: Signal! - - beforeEach { - scheduler = TestScheduler() - - let (baseSignal, baseObserver) = Signal.pipe() - observer = baseObserver - - signal = baseSignal.debounce(1, on: scheduler) - expect(signal).notTo(beNil()) - } - - it("should send values on the given scheduler once the interval has passed since the last value was sent") { - var values: [Int] = [] - signal.observeNext { value in - values.append(value) - } - - expect(values) == [] - - observer.sendNext(0) - expect(values) == [] - - scheduler.advance() - expect(values) == [] - - observer.sendNext(1) - observer.sendNext(2) - expect(values) == [] - - scheduler.advance(by: 1.5) - expect(values) == [ 2 ] - - scheduler.advance(by: 3) - expect(values) == [ 2 ] - - observer.sendNext(3) - expect(values) == [ 2 ] - - scheduler.advance() - expect(values) == [ 2 ] - - observer.sendNext(4) - observer.sendNext(5) - scheduler.advance() - expect(values) == [ 2 ] - - scheduler.run() - expect(values) == [ 2, 5 ] - } - - it("should schedule completion immediately") { - var values: [Int] = [] - var completed = false - - signal.observe { event in - switch event { - case let .next(value): - values.append(value) - case .completed: - completed = true - default: - break - } - } - - observer.sendNext(0) - scheduler.advance() - expect(values) == [] - - observer.sendNext(1) - observer.sendCompleted() - expect(completed) == false - - scheduler.advance() - expect(values) == [] - expect(completed) == true - - scheduler.run() - expect(values) == [] - expect(completed) == true - } - } - - describe("sampleWith") { - var sampledSignal: Signal<(Int, String), NoError>! - var observer: Signal.Observer! - var samplerObserver: Signal.Observer! - - beforeEach { - let (signal, incomingObserver) = Signal.pipe() - let (sampler, incomingSamplerObserver) = Signal.pipe() - sampledSignal = signal.sample(with: sampler) - observer = incomingObserver - samplerObserver = incomingSamplerObserver - } - - it("should forward the latest value when the sampler fires") { - var result: [String] = [] - sampledSignal.observeNext { (left, right) in result.append("\(left)\(right)") } - - observer.sendNext(1) - observer.sendNext(2) - samplerObserver.sendNext("a") - expect(result) == [ "2a" ] - } - - it("should do nothing if sampler fires before signal receives value") { - var result: [String] = [] - sampledSignal.observeNext { (left, right) in result.append("\(left)\(right)") } - - samplerObserver.sendNext("a") - expect(result).to(beEmpty()) - } - - it("should send lates value with sampler value multiple times when sampler fires multiple times") { - var result: [String] = [] - sampledSignal.observeNext { (left, right) in result.append("\(left)\(right)") } - - observer.sendNext(1) - samplerObserver.sendNext("a") - samplerObserver.sendNext("b") - expect(result) == [ "1a", "1b" ] - } - - it("should complete when both inputs have completed") { - var completed = false - sampledSignal.observeCompleted { completed = true } - - observer.sendCompleted() - expect(completed) == false - - samplerObserver.sendCompleted() - expect(completed) == true - } - } - - describe("sampleOn") { - var sampledSignal: Signal! - var observer: Signal.Observer! - var samplerObserver: Signal<(), NoError>.Observer! - - beforeEach { - let (signal, incomingObserver) = Signal.pipe() - let (sampler, incomingSamplerObserver) = Signal<(), NoError>.pipe() - sampledSignal = signal.sample(on: sampler) - observer = incomingObserver - samplerObserver = incomingSamplerObserver - } - - it("should forward the latest value when the sampler fires") { - var result: [Int] = [] - sampledSignal.observeNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - samplerObserver.sendNext(()) - expect(result) == [ 2 ] - } - - it("should do nothing if sampler fires before signal receives value") { - var result: [Int] = [] - sampledSignal.observeNext { result.append($0) } - - samplerObserver.sendNext(()) - expect(result).to(beEmpty()) - } - - it("should send lates value multiple times when sampler fires multiple times") { - var result: [Int] = [] - sampledSignal.observeNext { result.append($0) } - - observer.sendNext(1) - samplerObserver.sendNext(()) - samplerObserver.sendNext(()) - expect(result) == [ 1, 1 ] - } - - it("should complete when both inputs have completed") { - var completed = false - sampledSignal.observeCompleted { completed = true } - - observer.sendCompleted() - expect(completed) == false - - samplerObserver.sendCompleted() - expect(completed) == true - } - } - - describe("combineLatestWith") { - var combinedSignal: Signal<(Int, Double), NoError>! - var observer: Signal.Observer! - var otherObserver: Signal.Observer! - - beforeEach { - let (signal, incomingObserver) = Signal.pipe() - let (otherSignal, incomingOtherObserver) = Signal.pipe() - combinedSignal = signal.combineLatest(with: otherSignal) - observer = incomingObserver - otherObserver = incomingOtherObserver - } - - it("should forward the latest values from both inputs") { - var latest: (Int, Double)? - combinedSignal.observeNext { latest = $0 } - - observer.sendNext(1) - expect(latest).to(beNil()) - - // is there a better way to test tuples? - otherObserver.sendNext(1.5) - expect(latest?.0) == 1 - expect(latest?.1) == 1.5 - - observer.sendNext(2) - expect(latest?.0) == 2 - expect(latest?.1) == 1.5 - } - - it("should complete when both inputs have completed") { - var completed = false - combinedSignal.observeCompleted { completed = true } - - observer.sendCompleted() - expect(completed) == false - - otherObserver.sendCompleted() - expect(completed) == true - } - } - - describe("zipWith") { - var leftObserver: Signal.Observer! - var rightObserver: Signal.Observer! - var zipped: Signal<(Int, String), NoError>! - - beforeEach { - let (leftSignal, incomingLeftObserver) = Signal.pipe() - let (rightSignal, incomingRightObserver) = Signal.pipe() - - leftObserver = incomingLeftObserver - rightObserver = incomingRightObserver - zipped = leftSignal.zip(with: rightSignal) - } - - it("should combine pairs") { - var result: [String] = [] - zipped.observeNext { (left, right) in result.append("\(left)\(right)") } - - leftObserver.sendNext(1) - leftObserver.sendNext(2) - expect(result) == [] - - rightObserver.sendNext("foo") - expect(result) == [ "1foo" ] - - leftObserver.sendNext(3) - rightObserver.sendNext("bar") - expect(result) == [ "1foo", "2bar" ] - - rightObserver.sendNext("buzz") - expect(result) == [ "1foo", "2bar", "3buzz" ] - - rightObserver.sendNext("fuzz") - expect(result) == [ "1foo", "2bar", "3buzz" ] - - leftObserver.sendNext(4) - expect(result) == [ "1foo", "2bar", "3buzz", "4fuzz" ] - } - - it("should complete when the shorter signal has completed") { - var result: [String] = [] - var completed = false - - zipped.observe { event in - switch event { - case let .next(left, right): - result.append("\(left)\(right)") - case .completed: - completed = true - default: - break - } - } - - expect(completed) == false - - leftObserver.sendNext(0) - leftObserver.sendCompleted() - expect(completed) == false - expect(result) == [] - - rightObserver.sendNext("foo") - expect(completed) == true - expect(result) == [ "0foo" ] - } - - it("should complete when both signal have completed") { - var result: [String] = [] - var completed = false - - zipped.observe { event in - switch event { - case let .next(left, right): - result.append("\(left)\(right)") - case .completed: - completed = true - default: - break - } - } - - expect(completed) == false - - leftObserver.sendNext(0) - leftObserver.sendCompleted() - expect(completed) == false - expect(result) == [] - - rightObserver.sendCompleted() - expect(result) == [ ] - } - - it("should complete and drop unpaired pending values when both signal have completed") { - var result: [String] = [] - var completed = false - - zipped.observe { event in - switch event { - case let .next(left, right): - result.append("\(left)\(right)") - case .completed: - completed = true - default: - break - } - } - - expect(completed) == false - - leftObserver.sendNext(0) - leftObserver.sendNext(1) - leftObserver.sendNext(2) - leftObserver.sendNext(3) - leftObserver.sendCompleted() - expect(completed) == false - expect(result) == [] - - rightObserver.sendNext("foo") - rightObserver.sendNext("bar") - rightObserver.sendCompleted() - expect(result) == ["0foo", "1bar"] - } - } - - describe("materialize") { - it("should reify events from the signal") { - let (signal, observer) = Signal.pipe() - var latestEvent: Event? - signal - .materialize() - .observeNext { latestEvent = $0 } - - observer.sendNext(2) - - expect(latestEvent).toNot(beNil()) - if let latestEvent = latestEvent { - switch latestEvent { - case let .next(value): - expect(value) == 2 - default: - fail() - } - } - - observer.sendFailed(TestError.default) - if let latestEvent = latestEvent { - switch latestEvent { - case .failed: - () - default: - fail() - } - } - } - } - - describe("dematerialize") { - typealias IntEvent = Event - var observer: Signal.Observer! - var dematerialized: Signal! - - beforeEach { - let (signal, incomingObserver) = Signal.pipe() - observer = incomingObserver - dematerialized = signal.dematerialize() - } - - it("should send values for Next events") { - var result: [Int] = [] - dematerialized - .assumeNoErrors() - .observeNext { result.append($0) } - - expect(result).to(beEmpty()) - - observer.sendNext(.next(2)) - expect(result) == [ 2 ] - - observer.sendNext(.next(4)) - expect(result) == [ 2, 4 ] - } - - it("should error out for Error events") { - var errored = false - dematerialized.observeFailed { _ in errored = true } - - expect(errored) == false - - observer.sendNext(.failed(TestError.default)) - expect(errored) == true - } - - it("should complete early for Completed events") { - var completed = false - dematerialized.observeCompleted { completed = true } - - expect(completed) == false - observer.sendNext(IntEvent.completed) - expect(completed) == true - } - } - - describe("takeLast") { - var observer: Signal.Observer! - var lastThree: Signal! - - beforeEach { - let (signal, incomingObserver) = Signal.pipe() - observer = incomingObserver - lastThree = signal.take(last: 3) - } - - it("should send the last N values upon completion") { - var result: [Int] = [] - lastThree - .assumeNoErrors() - .observeNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - observer.sendNext(4) - expect(result).to(beEmpty()) - - observer.sendCompleted() - expect(result) == [ 2, 3, 4 ] - } - - it("should send less than N values if not enough were received") { - var result: [Int] = [] - lastThree - .assumeNoErrors() - .observeNext { result.append($0) } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendCompleted() - expect(result) == [ 1, 2 ] - } - - it("should send nothing when errors") { - var result: [Int] = [] - var errored = false - lastThree.observe { event in - switch event { - case let .next(value): - result.append(value) - case .failed: - errored = true - default: - break - } - } - - observer.sendNext(1) - observer.sendNext(2) - observer.sendNext(3) - expect(errored) == false - - observer.sendFailed(TestError.default) - expect(errored) == true - expect(result).to(beEmpty()) - } - } - - describe("timeoutWithError") { - var testScheduler: TestScheduler! - var signal: Signal! - var observer: Signal.Observer! - - beforeEach { - testScheduler = TestScheduler() - let (baseSignal, incomingObserver) = Signal.pipe() - signal = baseSignal.timeout(after: 2, raising: TestError.default, on: testScheduler) - observer = incomingObserver - } - - it("should complete if within the interval") { - var completed = false - var errored = false - signal.observe { event in - switch event { - case .completed: - completed = true - case .failed: - errored = true - default: - break - } - } - - testScheduler.schedule(after: 1) { - observer.sendCompleted() - } - - expect(completed) == false - expect(errored) == false - - testScheduler.run() - expect(completed) == true - expect(errored) == false - } - - it("should error if not completed before the interval has elapsed") { - var completed = false - var errored = false - signal.observe { event in - switch event { - case .completed: - completed = true - case .failed: - errored = true - default: - break - } - } - - testScheduler.schedule(after: 3) { - observer.sendCompleted() - } - - expect(completed) == false - expect(errored) == false - - testScheduler.run() - expect(completed) == false - expect(errored) == true - } - - it("should be available for NoError") { - let signal: Signal = Signal.never - .timeout(after: 2, raising: TestError.default, on: testScheduler) - - _ = signal - } - } - - describe("attempt") { - it("should forward original values upon success") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.attempt { _ in - return .success() - } - - var current: Int? - signal - .assumeNoErrors() - .observeNext { value in - current = value - } - - for value in 1...5 { - observer.sendNext(value) - expect(current) == value - } - } - - it("should error if an attempt fails") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.attempt { _ in - return .failure(.default) - } - - var error: TestError? - signal.observeFailed { err in - error = err - } - - observer.sendNext(42) - expect(error) == TestError.default - } - } - - describe("attemptMap") { - it("should forward mapped values upon success") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.attemptMap { num -> Result in - return .success(num % 2 == 0) - } - - var even: Bool? - signal - .assumeNoErrors() - .observeNext { value in - even = value - } - - observer.sendNext(1) - expect(even) == false - - observer.sendNext(2) - expect(even) == true - } - - it("should error if a mapping fails") { - let (baseSignal, observer) = Signal.pipe() - let signal = baseSignal.attemptMap { _ -> Result in - return .failure(.default) - } - - var error: TestError? - signal.observeFailed { err in - error = err - } - - observer.sendNext(42) - expect(error) == TestError.default - } - } - - describe("combinePrevious") { - var observer: Signal.Observer! - let initialValue: Int = 0 - var latestValues: (Int, Int)? - - beforeEach { - latestValues = nil - - let (signal, baseObserver) = Signal.pipe() - observer = baseObserver - signal.combinePrevious(initialValue).observeNext { latestValues = $0 } - } - - it("should forward the latest value with previous value") { - expect(latestValues).to(beNil()) - - observer.sendNext(1) - expect(latestValues?.0) == initialValue - expect(latestValues?.1) == 1 - - observer.sendNext(2) - expect(latestValues?.0) == 1 - expect(latestValues?.1) == 2 - } - } - - describe("combineLatest") { - var signalA: Signal! - var signalB: Signal! - var signalC: Signal! - var observerA: Signal.Observer! - var observerB: Signal.Observer! - var observerC: Signal.Observer! - - var combinedValues: [Int]? - var completed: Bool! - - beforeEach { - combinedValues = nil - completed = false - - let (baseSignalA, baseObserverA) = Signal.pipe() - let (baseSignalB, baseObserverB) = Signal.pipe() - let (baseSignalC, baseObserverC) = Signal.pipe() - - signalA = baseSignalA - signalB = baseSignalB - signalC = baseSignalC - - observerA = baseObserverA - observerB = baseObserverB - observerC = baseObserverC - } - - let combineLatestExampleName = "combineLatest examples" - sharedExamples(combineLatestExampleName) { - it("should forward the latest values from all inputs"){ - expect(combinedValues).to(beNil()) - - observerA.sendNext(0) - observerB.sendNext(1) - observerC.sendNext(2) - expect(combinedValues) == [0, 1, 2] - - observerA.sendNext(10) - expect(combinedValues) == [10, 1, 2] - } - - it("should not forward the latest values before all inputs"){ - expect(combinedValues).to(beNil()) - - observerA.sendNext(0) - expect(combinedValues).to(beNil()) - - observerB.sendNext(1) - expect(combinedValues).to(beNil()) - - observerC.sendNext(2) - expect(combinedValues) == [0, 1, 2] - } - - it("should complete when all inputs have completed"){ - expect(completed) == false - - observerA.sendCompleted() - observerB.sendCompleted() - expect(completed) == false - - observerC.sendCompleted() - expect(completed) == true - } - } - - describe("tuple") { - beforeEach { - Signal.combineLatest(signalA, signalB, signalC) - .observe { event in - switch event { - case let .next(value): - combinedValues = [value.0, value.1, value.2] - case .completed: - completed = true - default: - break - } - } - } - - itBehavesLike(combineLatestExampleName) - } - - describe("sequence") { - beforeEach { - Signal.combineLatest([signalA, signalB, signalC]) - .observe { event in - switch event { - case let .next(values): - combinedValues = values - case .completed: - completed = true - default: - break - } - } - } - - itBehavesLike(combineLatestExampleName) - } - } - - describe("zip") { - var signalA: Signal! - var signalB: Signal! - var signalC: Signal! - var observerA: Signal.Observer! - var observerB: Signal.Observer! - var observerC: Signal.Observer! - - var zippedValues: [Int]? - var completed: Bool! - - beforeEach { - zippedValues = nil - completed = false - - let (baseSignalA, baseObserverA) = Signal.pipe() - let (baseSignalB, baseObserverB) = Signal.pipe() - let (baseSignalC, baseObserverC) = Signal.pipe() - - signalA = baseSignalA - signalB = baseSignalB - signalC = baseSignalC - - observerA = baseObserverA - observerB = baseObserverB - observerC = baseObserverC - } - - let zipExampleName = "zip examples" - sharedExamples(zipExampleName) { - it("should combine all set"){ - expect(zippedValues).to(beNil()) - - observerA.sendNext(0) - expect(zippedValues).to(beNil()) - - observerB.sendNext(1) - expect(zippedValues).to(beNil()) - - observerC.sendNext(2) - expect(zippedValues) == [0, 1, 2] - - observerA.sendNext(10) - expect(zippedValues) == [0, 1, 2] - - observerA.sendNext(20) - expect(zippedValues) == [0, 1, 2] - - observerB.sendNext(11) - expect(zippedValues) == [0, 1, 2] - - observerC.sendNext(12) - expect(zippedValues) == [10, 11, 12] - } - - it("should complete when the shorter signal has completed"){ - expect(completed) == false - - observerB.sendNext(1) - observerC.sendNext(2) - observerB.sendCompleted() - observerC.sendCompleted() - expect(completed) == false - - observerA.sendNext(0) - expect(completed) == true - } - } - - describe("tuple") { - beforeEach { - Signal.zip(signalA, signalB, signalC) - .observe { event in - switch event { - case let .next(value): - zippedValues = [value.0, value.1, value.2] - case .completed: - completed = true - default: - break - } - } - } - - itBehavesLike(zipExampleName) - } - - describe("sequence") { - beforeEach { - Signal.zip([signalA, signalB, signalC]) - .observe { event in - switch event { - case let .next(values): - zippedValues = values - case .completed: - completed = true - default: - break - } - } - } - - itBehavesLike(zipExampleName) - } - - describe("log events") { - it("should output the correct event without identifier") { - let expectations: [(String) -> Void] = [ - { event in expect(event) == "[] next 1" }, - { event in expect(event) == "[] completed" }, - { event in expect(event) == "[] terminated" }, - { event in expect(event) == "[] disposed" }, - ] - - let logger = TestLogger(expectations: expectations) - - let (signal, observer) = Signal.pipe() - signal - .logEvents(logger: logger.logEvent) - .observe { _ in } - - observer.sendNext(1) - observer.sendCompleted() - } - - it("should output the correct event with identifier") { - let expectations: [(String) -> Void] = [ - { event in expect(event) == "[test.rac] next 1" }, - { event in expect(event) == "[test.rac] failed error1" }, - { event in expect(event) == "[test.rac] terminated" }, - { event in expect(event) == "[test.rac] disposed" }, - ] - - let logger = TestLogger(expectations: expectations) - - let (signal, observer) = Signal.pipe() - signal - .logEvents(identifier: "test.rac", logger: logger.logEvent) - .observe { _ in } - - observer.sendNext(1) - observer.sendFailed(.error1) - } - - it("should only output the events specified in the `events` parameter") { - let expectations: [(String) -> Void] = [ - { event in expect(event) == "[test.rac] failed error1" }, - ] - - let logger = TestLogger(expectations: expectations) - - let (signal, observer) = Signal.pipe() - signal - .logEvents(identifier: "test.rac", events: [.failed], logger: logger.logEvent) - .observe { _ in } - - observer.sendNext(1) - observer.sendFailed(.error1) - } - } - } - } -} diff --git a/ReactiveCocoaTests/Swift/TestLogger.swift b/ReactiveCocoaTests/Swift/TestLogger.swift deleted file mode 100644 index 9f406573b6..0000000000 --- a/ReactiveCocoaTests/Swift/TestLogger.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// TestLogger.swift -// ReactiveCocoa -// -// Created by Rui Peres on 29/04/2016. -// Copyright © 2016 GitHub. All rights reserved. -// - -import Foundation -@testable import ReactiveCocoa - -final class TestLogger { - fileprivate var expectations: [(String) -> Void] - - init(expectations: [(String) -> Void]) { - self.expectations = expectations - } -} - -extension TestLogger { - func logEvent(_ identifier: String, event: String, fileName: String, functionName: String, lineNumber: Int) { - expectations.removeFirst()("[\(identifier)] \(event)") - } -} diff --git a/ReactiveCocoaTests/SwizzlingSpec.swift b/ReactiveCocoaTests/SwizzlingSpec.swift new file mode 100644 index 0000000000..7de97f7464 --- /dev/null +++ b/ReactiveCocoaTests/SwizzlingSpec.swift @@ -0,0 +1,46 @@ +@testable import ReactiveCocoa +import Foundation +import Quick +import Nimble + +class SwizzledObject: NSObject {} + +class SwizzlingSpec: QuickSpec { + override func spec() { + describe("runtime subclassing") { + it("should swizzle the instance while still reporting the perceived class in `-class` and `+class`") { + let object = SwizzledObject() + expect(type(of: object)).to(beIdenticalTo(SwizzledObject.self)) + + let subclass: AnyClass = swizzleClass(object) + expect(type(of: object)).to(beIdenticalTo(SwizzledObject.self)) + expect(object.objcClass).to(beIdenticalTo(SwizzledObject.self)) + expect(object_getClass(object)).to(beIdenticalTo(subclass)) + + let objcClass: AnyClass = (object as AnyObject).objcClass + expect(objcClass).to(beIdenticalTo(SwizzledObject.self)) + expect((objcClass as AnyObject).objcClass as Any?).to(beIdenticalTo(SwizzledObject.self as AnyClass)) + + expect(String(cString: class_getName(subclass))).to(contain("_RACSwift")) + } + + it("should reuse the runtime subclass across instances") { + let object = SwizzledObject() + let subclass: AnyClass = swizzleClass(object) + + let object2 = SwizzledObject() + let subclass2: AnyClass = swizzleClass(object2) + + expect(subclass).to(beIdenticalTo(subclass2)) + } + + it("should return the known runtime subclass") { + let object = SwizzledObject() + let subclass: AnyClass = swizzleClass(object) + let subclass2: AnyClass = swizzleClass(object) + + expect(subclass).to(beIdenticalTo(subclass2)) + } + } + } +} diff --git a/ReactiveCocoaTests/Swift/TestError.swift b/ReactiveCocoaTests/TestError.swift similarity index 55% rename from ReactiveCocoaTests/Swift/TestError.swift rename to ReactiveCocoaTests/TestError.swift index 8ed0054d8d..e0a4660893 100644 --- a/ReactiveCocoaTests/Swift/TestError.swift +++ b/ReactiveCocoaTests/TestError.swift @@ -1,15 +1,7 @@ -// -// TestError.swift -// ReactiveCocoa -// -// Created by Almas Sapargali on 1/26/15. -// Copyright (c) 2015 GitHub. All rights reserved. -// - import ReactiveCocoa -import Result +import ReactiveSwift -enum TestError: Int { +internal enum TestError: Int { case `default` = 0 case error1 = 1 case error2 = 2 @@ -19,21 +11,21 @@ extension TestError: Error { } -internal extension SignalProducerProtocol { +internal extension SignalProducer { /// Halts if an error is emitted in the receiver signal. /// This is useful in tests to be able to just use `startWithNext` /// in cases where we know that an error won't be emitted. - func assumeNoErrors() -> SignalProducer { - return self.lift { $0.assumeNoErrors() } + func assumeNoErrors() -> SignalProducer { + return lift { $0.assumeNoErrors() } } } -internal extension SignalProtocol { +internal extension Signal { /// Halts if an error is emitted in the receiver signal. /// This is useful in tests to be able to just use `startWithNext` /// in cases where we know that an error won't be emitted. - func assumeNoErrors() -> Signal { - return self.mapError { error in + func assumeNoErrors() -> Signal { + return mapError { error in fatalError("Unexpected error: \(error)") () diff --git a/ReactiveCocoaTests/UIKit/UIActivityIndicatorViewSpec.swift b/ReactiveCocoaTests/UIKit/UIActivityIndicatorViewSpec.swift new file mode 100644 index 0000000000..33f1106c1d --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIActivityIndicatorViewSpec.swift @@ -0,0 +1,35 @@ +#if canImport(UIKit) +import UIKit +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa + +class UIActivityIndicatorSpec: QuickSpec { + override func spec() { + var activityIndicatorView: UIActivityIndicatorView! + weak var _activityIndicatorView: UIActivityIndicatorView? + + beforeEach { + activityIndicatorView = UIActivityIndicatorView(frame: .zero) + _activityIndicatorView = activityIndicatorView + } + + afterEach { + activityIndicatorView = nil + expect(_activityIndicatorView).to(beNil()) + } + + it("should accept changes from bindings to its animating state") { + let (pipeSignal, observer) = Signal.pipe() + activityIndicatorView.reactive.isAnimating <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(activityIndicatorView.isAnimating) == true + + observer.send(value: false) + expect(activityIndicatorView.isAnimating) == false + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIBarButtonItemSpec.swift b/ReactiveCocoaTests/UIKit/UIBarButtonItemSpec.swift new file mode 100644 index 0000000000..ae47f85f22 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIBarButtonItemSpec.swift @@ -0,0 +1,124 @@ +#if canImport(UIKit) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import UIKit + +class UIBarButtonItemSpec: QuickSpec { + override func spec() { + var barButtonItem: UIBarButtonItem! + weak var _barButtonItem: UIBarButtonItem? + + beforeEach { + barButtonItem = UIBarButtonItem() + _barButtonItem = barButtonItem + } + + afterEach { + barButtonItem = nil + expect(_barButtonItem).to(beNil()) + } + + it("should not be retained with the presence of a `pressed` action") { + let action = Action<(),(),Never> { SignalProducer(value: ()) } + barButtonItem.reactive.pressed = CocoaAction(action) + } + + it("should accept changes from bindings to its enabling state") { + let (pipeSignal, observer) = Signal.pipe() + barButtonItem.reactive.isEnabled <~ SignalProducer(pipeSignal) + + observer.send(value: false) + expect(barButtonItem.isEnabled) == false + + observer.send(value: true) + expect(barButtonItem.isEnabled) == true + } + + it("should accept changes from bindings to its title") { + let (pipeSignal, observer) = Signal.pipe() + barButtonItem.reactive.title <~ SignalProducer(pipeSignal) + + observer.send(value: "title") + expect(barButtonItem.title) == "title" + + observer.send(value: nil) + expect(barButtonItem.title).to(beNil()) + } + + it("should accept changes from bindings to its image") { + let (pipeSignal, observer) = Signal.pipe() + barButtonItem.reactive.image <~ SignalProducer(pipeSignal) + + let image = UIImage() + expect(image).notTo(beNil()) + + observer.send(value: image) + expect(barButtonItem.image) == image + + observer.send(value: nil) + expect(barButtonItem.image).to(beNil()) + } + + it("should accept changes from bindings to its style") { + let (pipeSignal, observer) = Signal.pipe() + barButtonItem.reactive.style <~ SignalProducer(pipeSignal) + + observer.send(value: .done) + expect(barButtonItem.style) == .done + + observer.send(value: .plain) + expect(barButtonItem.style) == .plain + } + + it("should accept changes from bindings to its width") { + let (pipeSignal, observer) = Signal.pipe() + barButtonItem.reactive.width <~ SignalProducer(pipeSignal) + + observer.send(value: 42.0) + expect(barButtonItem.width) == 42.0 + + observer.send(value: 320.0) + expect(barButtonItem.width) == 320.0 + + observer.send(value: 0.0) + expect(barButtonItem.width) == 0.0 + } + + it("should accept changes from bindings to its possible titles") { + let (pipeSignal, observer) = Signal?, Never>.pipe() + barButtonItem.reactive.possibleTitles <~ SignalProducer(pipeSignal) + + let possibleTitles = Set(["Unread (123,456,789)", "Unread"]) + observer.send(value: possibleTitles) + expect(barButtonItem.possibleTitles) == possibleTitles + + observer.send(value: nil) + expect(barButtonItem.possibleTitles).to(beNil()) + } + + it("should accept changes from bindings to its custom view") { + let firstChange = UIView() + firstChange.accessibilityIdentifier = "first" + + let secondChange = UIView() + secondChange.accessibilityIdentifier = "second" + + barButtonItem.customView = nil + + let (pipeSignal, observer) = Signal.pipe() + barButtonItem.reactive.customView <~ pipeSignal + + observer.send(value: firstChange) + expect(barButtonItem.customView) == firstChange + + observer.send(value: secondChange) + expect(barButtonItem.customView) == secondChange + + observer.send(value: nil) + expect(barButtonItem.customView).to(beNil()) + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIButtonSpec.swift b/ReactiveCocoaTests/UIKit/UIButtonSpec.swift new file mode 100644 index 0000000000..b17093e030 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIButtonSpec.swift @@ -0,0 +1,73 @@ +#if canImport(UIKit) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import UIKit + +class UIButtonSpec: QuickSpec { + override func spec() { + var button: UIButton! + weak var _button: UIButton? + + beforeEach { + button = UIButton(frame: .zero) + _button = button + } + + afterEach { + button = nil + expect(_button).to(beNil()) + } + + it("should accept changes from bindings to its titles under different states") { + let firstTitle = "First title" + let secondTitle = "Second title" + + let (pipeSignal, observer) = Signal.pipe() + button.reactive.title <~ SignalProducer(pipeSignal) + button.setTitle("", for: .selected) + button.setTitle("", for: .highlighted) + + observer.send(value: firstTitle) + expect(button.title(for: UIControl.State())) == firstTitle + expect(button.title(for: .highlighted)) == "" + expect(button.title(for: .selected)) == "" + + observer.send(value: secondTitle) + expect(button.title(for: UIControl.State())) == secondTitle + expect(button.title(for: .highlighted)) == "" + expect(button.title(for: .selected)) == "" + } + + let pressedTest: (UIButton, UIControl.Event) -> Void = { button, event in + button.isEnabled = true + button.isUserInteractionEnabled = true + + let pressed = MutableProperty(false) + let action = Action<(), Bool, Never> { _ in + SignalProducer(value: true) + } + + pressed <~ SignalProducer(action.values) + + button.reactive.pressed = CocoaAction(action) + expect(pressed.value) == false + + button.sendActions(for: event) + + expect(pressed.value) == true + } + + if #available(iOS 9.0, tvOS 9.0, *) { + it("should execute the `pressed` action upon receiving a `primaryActionTriggered` action message.") { + pressedTest(button, .primaryActionTriggered) + } + } else { + it("should execute the `pressed` action upon receiving a `touchUpInside` action message.") { + pressedTest(button, .touchUpInside) + } + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UICollectionViewSpec.swift b/ReactiveCocoaTests/UIKit/UICollectionViewSpec.swift new file mode 100644 index 0000000000..74270c5b54 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UICollectionViewSpec.swift @@ -0,0 +1,64 @@ +#if canImport(UIKit) +import Quick +import Nimble +import ReactiveCocoa +import ReactiveSwift +import UIKit + +final class UICollectionViewSpec: QuickSpec { + override func spec() { + var collectionView: TestCollectionView! + + beforeEach { + collectionView = TestCollectionView() + } + + describe("reloadData") { + var bindingSignal: Signal<(), Never>! + var bindingObserver: Signal<(), Never>.Observer! + + var reloadDataCount = 0 + + beforeEach { + let (signal, observer) = Signal<(), Never>.pipe() + (bindingSignal, bindingObserver) = (signal, observer) + + reloadDataCount = 0 + + collectionView.reloadDataSignal.observeValues { + reloadDataCount += 1 + } + } + + it("invokes reloadData whenever the bound signal sends a value") { + collectionView.reactive.reloadData <~ bindingSignal + + bindingObserver.send(value: ()) + bindingObserver.send(value: ()) + + expect(reloadDataCount) == 2 + } + } + } +} + +private final class TestCollectionView: UICollectionView { + let reloadDataSignal: Signal<(), Never> + private let reloadDataObserver: Signal<(), Never>.Observer + + init() { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + } + + required init?(coder aDecoder: NSCoder) { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(coder: aDecoder) + } + + override func reloadData() { + super.reloadData() + reloadDataObserver.send(value: ()) + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIControl+EnableSendActionsForControlEvents.swift b/ReactiveCocoaTests/UIKit/UIControl+EnableSendActionsForControlEvents.swift new file mode 100644 index 0000000000..c2d5ad92fe --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIControl+EnableSendActionsForControlEvents.swift @@ -0,0 +1,42 @@ +#if canImport(UIKit) +import UIKit + +private func rac_swizzle() { + let originalSelector = #selector(UIControl.sendAction(_:to:for:)) + let swizzledSelector = #selector(UIControl.rac_sendAction(_:to:forEvent:)) + + let originalMethod = class_getInstanceMethod(UIControl.self, originalSelector)! + let swizzledMethod = class_getInstanceMethod(UIControl.self, swizzledSelector)! + + let didAddMethod = class_addMethod(UIControl.self, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)!) + + if didAddMethod { + class_replaceMethod(UIControl.self, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)!) + } else { + method_exchangeImplementations(originalMethod, swizzledMethod) + } +} + +/// Unfortunately, there's an apparent limitation in using `sendActionsForControlEvents` +/// on unit-tests for any control besides `UIButton` which is very unfortunate since we +/// want test our bindings for `UIDatePicker`, `UISwitch`, `UITextField` and others +/// in the future. To be able to test them, we're now using swizzling to manually invoke +/// the pair target+action. +extension UIControl { + static func _initialize() { + rac_swizzle() + } + + // MARK: - Method Swizzling + + @objc func rac_sendAction(_ action: Selector, to target: AnyObject?, forEvent event: UIEvent?) { + _ = target?.perform(action, with: self) + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIControlSpec.swift b/ReactiveCocoaTests/UIKit/UIControlSpec.swift new file mode 100644 index 0000000000..3277b219a3 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIControlSpec.swift @@ -0,0 +1,85 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIControlSpec: QuickSpec { + override func spec() { + var control: UIControl! + weak var _control: UIControl? + + beforeEach { + control = UIControl(frame: .zero) + _control = control + } + afterEach { + control = nil + expect(_control).to(beNil()) + } + + it("should accept changes from bindings to its enabling state") { + control.isEnabled = false + + let (pipeSignal, observer) = Signal.pipe() + control.reactive.isEnabled <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(control.isEnabled) == true + + observer.send(value: false) + expect(control.isEnabled) == false + } + + it("should accept changes from bindings to its selecting state") { + control.isSelected = false + + let (pipeSignal, observer) = Signal.pipe() + control.reactive.isSelected <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(control.isSelected) == true + + observer.send(value: false) + expect(control.isSelected) == false + } + + it("should accept changes from bindings to its highlighting state") { + control.isHighlighted = false + + let (pipeSignal, observer) = Signal.pipe() + control.reactive.isHighlighted <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(control.isHighlighted) == true + + observer.send(value: false) + expect(control.isHighlighted) == false + } + + it("should accept changes from mutliple bindings to its states") { + control.isSelected = false + control.isEnabled = false + + let (pipeSignalSelected, observerSelected) = Signal.pipe() + let (pipeSignalEnabled, observerEnabled) = Signal.pipe() + control.reactive.isSelected <~ SignalProducer(pipeSignalSelected) + control.reactive.isEnabled <~ SignalProducer(pipeSignalEnabled) + + observerSelected.send(value: true) + observerEnabled.send(value: true) + expect(control.isEnabled) == true + expect(control.isSelected) == true + + observerSelected.send(value: false) + expect(control.isEnabled) == true + expect(control.isSelected) == false + + observerEnabled.send(value: false) + expect(control.isEnabled) == false + expect(control.isSelected) == false + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIDatePickerSpec.swift b/ReactiveCocoaTests/UIKit/UIDatePickerSpec.swift new file mode 100644 index 0000000000..53d693f868 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIDatePickerSpec.swift @@ -0,0 +1,49 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIDatePickerSpec: QuickSpec { + override func spec() { + var date: Date! + var picker: UIDatePicker! + weak var _picker: UIDatePicker? + + beforeEach { + let formatter = DateFormatter() + formatter.dateFormat = "MM/dd/YYYY" + date = formatter.date(from: "11/29/1988")! + + picker = UIDatePicker(frame: .zero) + _picker = picker + } + + afterEach { + picker = nil + expect(_picker).to(beNil()) + } + + it("should accept changes from bindings to its date value") { + picker.reactive.date.action(date) + expect(picker.date) == date + } + + it("should emit user initiated changes to its date value") { + let expectation = self.expectation(description: "Expected rac_date to send an event when picker's date value is changed by a UI event") + defer { self.waitForExpectations(timeout: 2, handler: nil) } + + picker.reactive.dates.observeValues { changedDate in + expect(changedDate) == date + expectation.fulfill() + } + + picker.date = date + picker.isEnabled = true + picker.isUserInteractionEnabled = true + picker.sendActions(for: .valueChanged) + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIGestureRecognizerSpec.swift b/ReactiveCocoaTests/UIKit/UIGestureRecognizerSpec.swift new file mode 100644 index 0000000000..1675c14117 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIGestureRecognizerSpec.swift @@ -0,0 +1,102 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIGestureRecognizerSpec: QuickSpec { + override func spec() { + var gestureRecognizer: TestTapGestureRecognizer! + weak var _gestureRecognizer: TestTapGestureRecognizer? + + beforeEach { + gestureRecognizer = TestTapGestureRecognizer() + _gestureRecognizer = gestureRecognizer + } + + afterEach { + gestureRecognizer = nil + expect(_gestureRecognizer).to(beNil()) + } + + it("should send a value when the gesture state changes") { + let signal = gestureRecognizer.reactive.stateChanged + + var counter = 0 + signal.observeValues { _ in counter += 1 } + + expect(counter) == 0 + gestureRecognizer.fireGestureEvent(.possible) + + expect(counter) == 1 + + gestureRecognizer.fireGestureEvent( .began) + expect(counter) == 2 + + gestureRecognizer.fireGestureEvent(.changed) + expect(counter) == 3 + + gestureRecognizer.fireGestureEvent(.ended) + expect(counter) == 4 + } + + it("should send it's gesture recognizer in signal") { + let signal = gestureRecognizer.reactive.stateChanged + var counter = 0 + signal.observeValues { signalGestureRecognizer in + if signalGestureRecognizer === gestureRecognizer{ + counter += 1 + } + } + gestureRecognizer.fireGestureEvent( .began) + expect(counter) == 1 + } + + it("should send it's gesture recognizer with the fired state") { + let signal = gestureRecognizer.reactive.stateChanged + weak var signalGestureRecognizer: TestTapGestureRecognizer? + signal.observeValues { recognizer in + signalGestureRecognizer = recognizer + } + + gestureRecognizer.fireGestureEvent(.possible) + expect(signalGestureRecognizer?.state) == .possible + + gestureRecognizer.fireGestureEvent( .began) + expect(signalGestureRecognizer?.state) == .began + + gestureRecognizer.fireGestureEvent(.changed) + expect(signalGestureRecognizer?.state) == .changed + + gestureRecognizer.fireGestureEvent(.ended) + expect(signalGestureRecognizer?.state) == .ended + } + } +} + +private final class TestTapGestureRecognizer: UITapGestureRecognizer { + private struct TargetActionPair { + let target: AnyObject + let action: Selector + } + + private var targetActionPair: TargetActionPair? + private var forceState: UIGestureRecognizer.State = .ended + + fileprivate override var state: UIGestureRecognizer.State { + get { return forceState } + set { } + } + + fileprivate override func addTarget(_ target: Any, action: Selector) { + targetActionPair = TargetActionPair(target: target as AnyObject, action: action) + } + + fileprivate func fireGestureEvent(_ state: UIGestureRecognizer.State) { + guard let targetAction = self.targetActionPair else { return } + forceState = state + _ = targetAction.target.perform(targetAction.action, with: self) + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIImageViewSpec.swift b/ReactiveCocoaTests/UIKit/UIImageViewSpec.swift new file mode 100644 index 0000000000..847bfbff3a --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIImageViewSpec.swift @@ -0,0 +1,52 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIImageViewSpec: QuickSpec { + override func spec() { + var imageView: UIImageView! + weak var _imageView: UIImageView? + + beforeEach { + imageView = UIImageView(frame: .zero) + _imageView = imageView + } + + afterEach { + imageView = nil + expect(_imageView).to(beNil()) + } + + it("should accept changes from bindings to its displaying image") { + let firstChange = UIImage() + let secondChange = UIImage() + + let (pipeSignal, observer) = Signal.pipe() + imageView.reactive.image <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(imageView.image) == firstChange + + observer.send(value: secondChange) + expect(imageView.image) == secondChange + } + + it("should accept changes from bindings to its displaying image when highlighted") { + let firstChange = UIImage() + let secondChange = UIImage() + + let (pipeSignal, observer) = Signal.pipe() + imageView.reactive.highlightedImage <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(imageView.highlightedImage) == firstChange + + observer.send(value: secondChange) + expect(imageView.highlightedImage) == secondChange + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIKeyboardSpec.swift b/ReactiveCocoaTests/UIKit/UIKeyboardSpec.swift new file mode 100644 index 0000000000..de8fd55bd9 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIKeyboardSpec.swift @@ -0,0 +1,197 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIKeyboardSpec: QuickSpec { + override func spec() { + describe("NotificationCenter.reactive.keyboard(_;)") { + it("should emit a `value` event when the notification is posted") { + var context: KeyboardChangeContext? + + let testCenter = NotificationCenter() + + testCenter.reactive.keyboard(.willShow) + .observeValues { context = $0 } + + var dummyInfo: [AnyHashable: Any] = [ + UIResponder.keyboardFrameBeginUserInfoKey: CGRect(x: 10, y: 10, width: 10, height: 10), + UIResponder.keyboardFrameEndUserInfoKey: CGRect(x: 20, y: 20, width: 20, height: 20), + UIResponder.keyboardAnimationDurationUserInfoKey: 1.0, + UIResponder.keyboardAnimationCurveUserInfoKey: NSNumber(value: UIView.AnimationCurve.easeInOut.rawValue) + ] + + if #available(*, iOS 9.0) { + dummyInfo[UIResponder.keyboardIsLocalUserInfoKey] = NSNumber(value: true) + } + + testCenter.post(name: UIResponder.keyboardWillShowNotification, + object: nil, + userInfo: dummyInfo) + + expect(context).toNot(beNil()) + + expect(context?.event) == .willShow + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 10) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + } + } + + describe("NotificationCenter.reactive.keyboard(_:_:_;)") { + it("should emit a `value` event when the notification is posted") { + var context: KeyboardChangeContext? + + let testCenter = NotificationCenter() + + testCenter.reactive.keyboard(.willShow, .didShow, .willHide, .didHide, .willChangeFrame, .didChangeFrame) + .observeValues { context = $0 } + + func makeDummyInfo(beginFrameHeight: CGFloat) -> [AnyHashable: Any] { + var dummyInfo: [AnyHashable: Any] = [ + UIResponder.keyboardFrameBeginUserInfoKey: CGRect(x: 10, y: 10, width: 10, height: beginFrameHeight), + UIResponder.keyboardFrameEndUserInfoKey: CGRect(x: 20, y: 20, width: 20, height: 20), + UIResponder.keyboardAnimationDurationUserInfoKey: 1.0, + UIResponder.keyboardAnimationCurveUserInfoKey: NSNumber(value: UIView.AnimationCurve.easeInOut.rawValue) + ] + if #available(*, iOS 9.0) { + dummyInfo[UIResponder.keyboardIsLocalUserInfoKey] = NSNumber(value: true) + } + return dummyInfo + } + + testCenter.post(name: UIResponder.keyboardWillShowNotification, + object: nil, + userInfo: makeDummyInfo(beginFrameHeight: 10)) + + expect(context).toNot(beNil()) + + expect(context?.event) == .willShow + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 10) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + + testCenter.post(name: UIResponder.keyboardDidShowNotification, + object: nil, + userInfo: makeDummyInfo(beginFrameHeight: 20)) + + expect(context?.event) == .didShow + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 20) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + + testCenter.post(name: UIResponder.keyboardWillHideNotification, + object: nil, + userInfo: makeDummyInfo(beginFrameHeight: 30)) + + expect(context?.event) == .willHide + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 30) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + + testCenter.post(name: UIResponder.keyboardDidHideNotification, + object: nil, + userInfo: makeDummyInfo(beginFrameHeight: 40)) + + expect(context?.event) == .didHide + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 40) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + + testCenter.post(name: UIResponder.keyboardWillChangeFrameNotification, + object: nil, + userInfo: makeDummyInfo(beginFrameHeight: 50)) + + expect(context?.event) == .willChangeFrame + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 50) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + + testCenter.post(name: UIResponder.keyboardDidChangeFrameNotification, + object: nil, + userInfo: makeDummyInfo(beginFrameHeight: 60)) + + expect(context?.event) == .didChangeFrame + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 60) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + } + } + + describe("NotificationCenter.reactive.keyboardChange") { + it("should emit a `value` event when the notification is posted") { + var context: KeyboardChangeContext? + + let testCenter = NotificationCenter() + + testCenter.reactive.keyboardChange + .observeValues { context = $0 } + + var dummyInfo: [AnyHashable: Any] = [ + UIResponder.keyboardFrameBeginUserInfoKey: CGRect(x: 10, y: 10, width: 10, height: 10), + UIResponder.keyboardFrameEndUserInfoKey: CGRect(x: 20, y: 20, width: 20, height: 20), + UIResponder.keyboardAnimationDurationUserInfoKey: 1.0, + UIResponder.keyboardAnimationCurveUserInfoKey: NSNumber(value: UIView.AnimationCurve.easeInOut.rawValue) + ] + + if #available(*, iOS 9.0) { + dummyInfo[UIResponder.keyboardIsLocalUserInfoKey] = NSNumber(value: true) + } + + testCenter.post(name: UIResponder.keyboardWillChangeFrameNotification, + object: nil, + userInfo: dummyInfo) + + expect(context).toNot(beNil()) + + expect(context?.event) == .willChangeFrame + expect(context?.beginFrame) == CGRect(x: 10, y: 10, width: 10, height: 10) + expect(context?.endFrame) == CGRect(x: 20, y: 20, width: 20, height: 20) + expect(context?.animationCurve) == .easeInOut + expect(context?.animationDuration) == 1.0 + + if #available(*, iOS 9.0) { + expect(context?.isLocal) == true + } + } + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIKitReusableComponentsSpec.swift b/ReactiveCocoaTests/UIKit/UIKitReusableComponentsSpec.swift new file mode 100644 index 0000000000..a72ff4a630 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIKitReusableComponentsSpec.swift @@ -0,0 +1,59 @@ +#if canImport(UIKit) +import Quick +import Nimble +import ReactiveSwift +import ReactiveCocoa +import UIKit + +class ReusableComponentsSpec: QuickSpec { + override func spec() { + describe("UITableViewCell") { + it("should send a `value` event when `prepareForReuse` is triggered") { + let cell = UITableViewCell() + + var isTriggered = false + cell.reactive.prepareForReuse.observeValues { + isTriggered = true + } + + expect(isTriggered) == false + + cell.prepareForReuse() + expect(isTriggered) == true + } + } + + describe("UITableViewHeaderFooterView") { + it("should send a `value` event when `prepareForReuse` is triggered") { + let cell = UITableViewHeaderFooterView() + + var isTriggered = false + cell.reactive.prepareForReuse.observeValues { + isTriggered = true + } + + expect(isTriggered) == false + + cell.prepareForReuse() + expect(isTriggered) == true + } + } + + describe("UICollectionReusableView") { + it("should send a `value` event when `prepareForReuse` is triggered") { + let cell = UICollectionReusableView() + + var isTriggered = false + cell.reactive.prepareForReuse.observeValues { + isTriggered = true + } + + expect(isTriggered) == false + + cell.prepareForReuse() + expect(isTriggered) == true + } + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UILabelSpec.swift b/ReactiveCocoaTests/UIKit/UILabelSpec.swift new file mode 100644 index 0000000000..9c30f30bbb --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UILabelSpec.swift @@ -0,0 +1,76 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UILabelSpec: QuickSpec { + override func spec() { + var label: UILabel! + weak var _label: UILabel? + + beforeEach { + label = UILabel(frame: .zero) + _label = label + } + + afterEach { + label = nil + expect(_label).to(beNil()) + } + + it("should accept changes from bindings to its text value") { + let firstChange = "first" + let secondChange = "second" + + label.text = "" + + let (pipeSignal, observer) = Signal.pipe() + label.reactive.text <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(label.text) == firstChange + + observer.send(value: secondChange) + expect(label.text) == secondChange + + observer.send(value: nil) + expect(label.text).to(beNil()) + } + + it("should accept changes from bindings to its attributed text value") { + let firstChange = NSAttributedString(string: "first") + let secondChange = NSAttributedString(string: "second") + + label.attributedText = NSAttributedString(string: "") + + let (pipeSignal, observer) = Signal.pipe() + label.reactive.attributedText <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(label.attributedText) == firstChange + + observer.send(value: secondChange) + expect(label.attributedText) == secondChange + } + + it("should accept changes from bindings to its text color value") { + let firstChange = UIColor.red + let secondChange = UIColor.black + + let label = UILabel(frame: .zero) + + let (pipeSignal, observer) = Signal.pipe() + label.textColor = UIColor.black + label.reactive.textColor <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(label.textColor) == firstChange + + observer.send(value: secondChange) + expect(label.textColor) == secondChange + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UINavigationItemSpec.swift b/ReactiveCocoaTests/UIKit/UINavigationItemSpec.swift new file mode 100644 index 0000000000..1f4cf45dad --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UINavigationItemSpec.swift @@ -0,0 +1,285 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UINavigationItemSpec: QuickSpec { + override func spec() { + var navigationItem: UINavigationItem! + weak var _navigationItem: UINavigationItem? + + beforeEach { + navigationItem = UINavigationItem(title: "initial") + _navigationItem = navigationItem + } + + afterEach { + navigationItem = nil + expect(_navigationItem).to(beNil()) + } + + it("should accept changes from bindings to its title value") { + let firstChange = "first" + let secondChange = "second" + + navigationItem.title = "" + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.title <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.title) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.title) == secondChange + + observer.send(value: nil) + expect(navigationItem.title).to(beNil()) + } + + it("should accept changes from bindings to its titleView value") { + let firstChange = UIView() + firstChange.accessibilityIdentifier = "first" + + let secondChange = UIView() + secondChange.accessibilityIdentifier = "second" + + navigationItem.titleView = nil + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.titleView <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.titleView) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.titleView) == secondChange + + observer.send(value: nil) + expect(navigationItem.titleView).to(beNil()) + } + +#if os(iOS) + it("should accept changes from bindings to its prompt value") { + let firstChange = "first" + let secondChange = "second" + + navigationItem.prompt = "" + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.prompt <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.prompt) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.prompt) == secondChange + + observer.send(value: nil) + expect(navigationItem.prompt).to(beNil()) + } + + it("should accept changes from bindings to its backBarButtonItem value") { + let firstChange = UIBarButtonItem(title: "first", style: .plain, target: nil, action: nil) + let secondChange = UIBarButtonItem(title: "second", style: .plain, target: nil, action: nil) + + navigationItem.backBarButtonItem = nil + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.backBarButtonItem <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.backBarButtonItem) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.backBarButtonItem) == secondChange + + observer.send(value: nil) + expect(navigationItem.backBarButtonItem).to(beNil()) + } + + it("should accept changes from bindings to its hidesBackButton value") { + let firstChange = true + let secondChange = false + + navigationItem.hidesBackButton = false + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.hidesBackButton <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.hidesBackButton) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.hidesBackButton) == secondChange + } +#endif + + it("should accept changes from bindings to its leftBarButtonItems value") { + let firstChange = [ + UIBarButtonItem(barButtonSystemItem: .action, target: nil, action: nil), + UIBarButtonItem(title: "first", style: .plain, target: nil, action: nil) + ] + + let secondChange = [ + UIBarButtonItem(barButtonSystemItem: .action, target: nil, action: nil), + UIBarButtonItem(title: "second", style: .plain, target: nil, action: nil) + ] + + navigationItem.leftBarButtonItems = nil + + let (pipeSignal, observer) = Signal<[UIBarButtonItem]?, Never>.pipe() + navigationItem.reactive.leftBarButtonItems <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.leftBarButtonItems) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.leftBarButtonItems) == secondChange + + observer.send(value: nil) + expect(navigationItem.leftBarButtonItems).to(beNil()) + } + + it("should accept changes from bindings to its rightBarButtonItems value") { + let firstChange = [ + UIBarButtonItem(barButtonSystemItem: .action, target: nil, action: nil), + UIBarButtonItem(title: "first", style: .plain, target: nil, action: nil) + ] + + let secondChange = [ + UIBarButtonItem(barButtonSystemItem: .action, target: nil, action: nil), + UIBarButtonItem(title: "second", style: .plain, target: nil, action: nil) + ] + + navigationItem.rightBarButtonItems = nil + + let (pipeSignal, observer) = Signal<[UIBarButtonItem]?, Never>.pipe() + navigationItem.reactive.rightBarButtonItems <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.rightBarButtonItems) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.rightBarButtonItems) == secondChange + + observer.send(value: nil) + expect(navigationItem.rightBarButtonItems).to(beNil()) + } + + it("should accept changes from bindings to its leftBarButtonItem value") { + let firstChange = UIBarButtonItem(title: "first", style: .plain, target: nil, action: nil) + let secondChange = UIBarButtonItem(barButtonSystemItem: .action, target: nil, action: nil) + + navigationItem.leftBarButtonItem = nil + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.leftBarButtonItem <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.leftBarButtonItem) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.leftBarButtonItem) == secondChange + + observer.send(value: nil) + expect(navigationItem.leftBarButtonItem).to(beNil()) + } + + it("should accept changes from bindings to its rightBarButtonItem value") { + let firstChange = UIBarButtonItem(title: "first", style: .plain, target: nil, action: nil) + let secondChange = UIBarButtonItem(title: "second", style: .plain, target: nil, action: nil) + + navigationItem.rightBarButtonItem = nil + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.rightBarButtonItem <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.rightBarButtonItem) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.rightBarButtonItem) == secondChange + + observer.send(value: nil) + expect(navigationItem.rightBarButtonItem).to(beNil()) + } + +#if os(iOS) + it("should accept changes from bindings to its leftItemsSupplementBackButton value") { + let firstChange = true + let secondChange = false + + navigationItem.leftItemsSupplementBackButton = false + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.leftItemsSupplementBackButton <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.leftItemsSupplementBackButton) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.leftItemsSupplementBackButton) == secondChange + } + + if #available(iOS 11.0, *) { + it("should accept changes from bindings to its largeTitleDisplayMode value") { + let firstChange = UINavigationItem.LargeTitleDisplayMode.always + let secondChange = UINavigationItem.LargeTitleDisplayMode.automatic + + navigationItem.largeTitleDisplayMode = .automatic + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.largeTitleDisplayMode <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.largeTitleDisplayMode) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.largeTitleDisplayMode) == secondChange + } + + it("should accept changes from bindings to its searchController value") { + let firstChange = UISearchController() + firstChange.view.accessibilityIdentifier = "firstChange" + + let secondChange = UISearchController() + secondChange.view.accessibilityIdentifier = "firstChange" + + navigationItem.searchController = nil + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.searchController <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.searchController) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.searchController) == secondChange + + observer.send(value: nil) + expect(navigationItem.searchController).to(beNil()) + } + + it("should accept changes from bindings to its hidesSearchBarWhenScrolling value") { + let firstChange = true + let secondChange = false + + navigationItem.hidesSearchBarWhenScrolling = false + + let (pipeSignal, observer) = Signal.pipe() + navigationItem.reactive.hidesSearchBarWhenScrolling <~ pipeSignal + + observer.send(value: firstChange) + expect(navigationItem.hidesSearchBarWhenScrolling) == firstChange + + observer.send(value: secondChange) + expect(navigationItem.hidesSearchBarWhenScrolling) == secondChange + } + } +#endif + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIPickerViewSpec.swift b/ReactiveCocoaTests/UIKit/UIPickerViewSpec.swift new file mode 100644 index 0000000000..e56d4d33eb --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIPickerViewSpec.swift @@ -0,0 +1,154 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +private final class PickerDataSource: NSObject, UIPickerViewDataSource { + @objc func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 2 + } + + @objc func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return 4 + } +} + +final class UIPickerViewSpec: QuickSpec { + override func spec() { + var dataSource: UIPickerViewDataSource! + var pickerView: TestPickerView! + weak var _pickerView: UIPickerView? + + beforeEach { + autoreleasepool { + dataSource = PickerDataSource() + + pickerView = TestPickerView() + pickerView.dataSource = dataSource + pickerView.reloadAllComponents() + _pickerView = pickerView + } + } + + afterEach { + autoreleasepool { + dataSource = nil + pickerView = nil + } + expect(_pickerView).toEventually(beNil()) + } + + it("should accept changes from bindings to selected rows") { + + let (pipeSignal, observer) = Signal.pipe() + pickerView.reactive.selectedRow(inComponent: 0) <~ SignalProducer(pipeSignal) + + let (anotherPipeSignal, anotherObserver) = Signal.pipe() + pickerView.reactive.selectedRow(inComponent: 1) <~ SignalProducer(anotherPipeSignal) + + observer.send(value: 1) + expect(pickerView.selectedRow(inComponent: 0)) == 1 + + anotherObserver.send(value: 3) + expect(pickerView.selectedRow(inComponent: 1)) == 3 + + observer.send(value: 2) + expect(pickerView.selectedRow(inComponent: 0)) == 2 + } + + it("should emit user initiated changes for row selection") { + var latestValue: (row: Int, component: Int)! + pickerView.reactive.selections.observeValues { + latestValue = $0 + } + + pickerView.selectRow(1, inComponent: 0, animated: false) + pickerView.delegate!.pickerView!(pickerView, didSelectRow: 1, inComponent: 0) + expect(latestValue.component) == 0 + expect(latestValue.row) == 1 + + pickerView.selectRow(2, inComponent: 1, animated: false) + pickerView.delegate!.pickerView!(pickerView, didSelectRow: 2, inComponent: 1) + expect(latestValue.component) == 1 + expect(latestValue.row) == 2 + } + + it("invokes reloadAllComponents whenever the bound signal sends a value") { + let (signal, observer) = Signal<(), Never>.pipe() + + var reloadAllComponentsCount = 0 + + pickerView.reloadAllComponentsSignal.observeValues { + reloadAllComponentsCount += 1 + } + + pickerView.reactive.reloadAllComponents <~ signal + + observer.send(value: ()) + observer.send(value: ()) + + expect(reloadAllComponentsCount) == 2 + } + + it("invokes reloadComponent whenever the bound signal sends a value") { + let (signal, observer) = Signal.pipe() + + var reloadFirstComponentCount = 0 + var reloadSecondComponentCount = 0 + + pickerView.reloadComponentSignal.observeValues { component in + if (component == 0) { + reloadFirstComponentCount += 1 + } else if (component == 1) { + reloadSecondComponentCount += 1 + } + } + + pickerView.reactive.reloadComponent <~ signal + + observer.send(value: 3) + expect(reloadFirstComponentCount) == 0 + expect(reloadSecondComponentCount) == 0 + + observer.send(value: 0) + observer.send(value: 0) + expect(reloadFirstComponentCount) == 2 + + observer.send(value: 1) + expect(reloadSecondComponentCount) == 1 + } + } +} + +private final class TestPickerView: UIPickerView { + let reloadAllComponentsSignal: Signal<(), Never> + private let reloadAllComponentsObserver: Signal<(), Never>.Observer + + let reloadComponentSignal: Signal + private let reloadComponentObserver: Signal.Observer + + init() { + (reloadAllComponentsSignal, reloadAllComponentsObserver) = Signal.pipe() + (reloadComponentSignal, reloadComponentObserver) = Signal.pipe() + super.init(frame: .zero) + } + + required init?(coder aDecoder: NSCoder) { + (reloadAllComponentsSignal, reloadAllComponentsObserver) = Signal.pipe() + (reloadComponentSignal, reloadComponentObserver) = Signal.pipe() + super.init(coder: aDecoder) + } + + override func reloadAllComponents() { + super.reloadAllComponents() + reloadAllComponentsObserver.send(value: ()) + } + + override func reloadComponent(_ component: Int) { + super.reloadComponent(component) + reloadComponentObserver.send(value: component) + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIProgressViewSpec.swift b/ReactiveCocoaTests/UIKit/UIProgressViewSpec.swift new file mode 100644 index 0000000000..0e3e7ddb54 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIProgressViewSpec.swift @@ -0,0 +1,40 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIProgressViewSpec: QuickSpec { + override func spec() { + var progressView: UIProgressView! + weak var _progressView: UIProgressView? + + beforeEach { + progressView = UIProgressView(frame: .zero) + _progressView = progressView + } + + afterEach { + progressView = nil + expect(_progressView).to(beNil()) + } + + it("should accept changes from bindings to its progress value") { + let firstChange: Float = 0.5 + let secondChange: Float = 0.0 + + progressView.progress = 1.0 + + let (pipeSignal, observer) = Signal.pipe() + progressView.reactive.progress <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(progressView.progress) ≈ firstChange + + observer.send(value: secondChange) + expect(progressView.progress) ≈ secondChange + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIRefreshControlSpec.swift b/ReactiveCocoaTests/UIKit/UIRefreshControlSpec.swift new file mode 100644 index 0000000000..85510fb34f --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIRefreshControlSpec.swift @@ -0,0 +1,86 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIRefreshControlSpec: QuickSpec { + override func spec() { + var refreshControl: UIRefreshControl! + weak var _refreshControl: UIRefreshControl! + + beforeEach { + refreshControl = UIRefreshControl() + _refreshControl = refreshControl + } + + afterEach { + refreshControl = nil + expect(_refreshControl).to(beNil()) + } + + it("should accept changes from bindings to its refreshing state") { + let (pipeSignal, observer) = Signal.pipe() + refreshControl.reactive.isRefreshing <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(refreshControl.isRefreshing) == true + + observer.send(value: false) + expect(refreshControl.isRefreshing) == false + } + + it("should accept changes from bindings to its attributed title state") { + let (pipeSignal, observer) = Signal.pipe() + refreshControl.reactive.attributedTitle <~ SignalProducer(pipeSignal) + + let string = NSAttributedString(string: "test") + + observer.send(value: nil) + expect(refreshControl.attributedTitle).to(beNil()) + + observer.send(value: string) + expect(refreshControl.attributedTitle) == string + + observer.send(value: nil) + expect(refreshControl.attributedTitle).to(beNil()) + } + + it("should execute the `refresh` action upon receiving a `valueChanged` action message.") { + refreshControl.isEnabled = true + refreshControl.isUserInteractionEnabled = true + + let refreshed = MutableProperty(false) + let action = Action<(), Bool, Never> { _ in + SignalProducer(value: true) + } + + refreshed <~ SignalProducer(action.values) + + refreshControl.reactive.refresh = CocoaAction(action) + expect(refreshed.value) == false + + refreshControl.sendActions(for: .valueChanged) + expect(refreshed.value) == true + } + + it("should set `isRefreshing` while `refresh` is executing.") { + refreshControl.isEnabled = true + refreshControl.isUserInteractionEnabled = true + + let action = Action<(), Bool, Never> { _ in + SignalProducer(value: true).delay(1, on: QueueScheduler.main) + } + + refreshControl.reactive.refresh = CocoaAction(action) + expect(refreshControl.isRefreshing) == false + + refreshControl.sendActions(for: .valueChanged) + expect(refreshControl.isRefreshing) == true + + expect(refreshControl.isRefreshing).toEventually(equal(false), timeout: .seconds(2)) + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIResponderSpec.swift b/ReactiveCocoaTests/UIKit/UIResponderSpec.swift new file mode 100644 index 0000000000..20a3d1336e --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIResponderSpec.swift @@ -0,0 +1,25 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIResponderSpec: QuickSpec { + override func spec() { + #if !targetEnvironment(macCatalyst) + it("should become and resign first responder") { + let window = UIWindow(frame: .zero) + let textField = UITextField(frame: .zero) + window.addSubview(textField) + + expect(textField.isFirstResponder).to(beFalse()) + textField.reactive.becomeFirstResponder <~ SignalProducer(value: ()) + expect(textField.isFirstResponder).to(beTrue()) + textField.reactive.resignFirstResponder <~ SignalProducer(value: ()) + expect(textField.isFirstResponder).to(beFalse()) + } + #endif + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIScrollViewSpec.swift b/ReactiveCocoaTests/UIKit/UIScrollViewSpec.swift new file mode 100644 index 0000000000..9f4f2203fd --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIScrollViewSpec.swift @@ -0,0 +1,125 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +private final class UIScrollViewDelegateForZooming: NSObject, UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return scrollView.subviews.first! + } +} + +class UIScrollViewSpec: QuickSpec { + override func spec() { + var scrollView: UIScrollView! + weak var _scrollView: UIScrollView? + + beforeEach { + scrollView = UIScrollView(frame: .zero) + _scrollView = scrollView + } + + afterEach { + scrollView = nil + expect(_scrollView).to(beNil()) + } + + it("should accept changes from bindings to its content inset value") { + scrollView.contentInset = .zero + + let (pipeSignal, observer) = Signal.pipe() + scrollView.reactive.contentInset <~ SignalProducer(pipeSignal) + + observer.send(value: UIEdgeInsets(top: 1, left: 2, bottom: 3, right: 4)) + expect(scrollView.contentInset) == UIEdgeInsets(top: 1, left: 2, bottom: 3, right: 4) + + observer.send(value: .zero) + expect(scrollView.contentInset) == UIEdgeInsets.zero + } + + it("should accept changes from bindings to its scroll indicator insets value") { + scrollView.scrollIndicatorInsets = .zero + + let (pipeSignal, observer) = Signal.pipe() + scrollView.reactive.scrollIndicatorInsets <~ SignalProducer(pipeSignal) + + observer.send(value: UIEdgeInsets(top: 1, left: 2, bottom: 3, right: 4)) + expect(scrollView.scrollIndicatorInsets) == UIEdgeInsets(top: 1, left: 2, bottom: 3, right: 4) + + observer.send(value: .zero) + expect(scrollView.scrollIndicatorInsets) == UIEdgeInsets.zero + } + + it("should accept changes from bindings to its scroll enabled state") { + scrollView.isScrollEnabled = true + + let (pipeSignal, observer) = Signal.pipe() + scrollView.reactive.isScrollEnabled <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(scrollView.isScrollEnabled) == true + + observer.send(value: false) + expect(scrollView.isScrollEnabled) == false + } + + it("should accept changes from bindings to its zoom scale value") { + let contentView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + scrollView.addSubview(contentView) + let delegate = UIScrollViewDelegateForZooming() + scrollView.delegate = delegate + + scrollView.minimumZoomScale = 1 + scrollView.maximumZoomScale = 5 + scrollView.zoomScale = 1 + + let (pipeSignal, observer) = Signal.pipe() + scrollView.reactive.zoomScale <~ SignalProducer(pipeSignal) + + observer.send(value: 3) + expect(scrollView.zoomScale) == 3 + observer.send(value: 1) + expect(scrollView.zoomScale) == 1 + } + + it("should accept changes from bindings to its minimum zoom scale value") { + scrollView.minimumZoomScale = 0 + + let (pipeSignal, observer) = Signal.pipe() + scrollView.reactive.minimumZoomScale <~ SignalProducer(pipeSignal) + + observer.send(value: 42) + expect(scrollView.minimumZoomScale) == 42 + observer.send(value: 0) + expect(scrollView.minimumZoomScale) == 0 + } + + it("should accept changes from bindings to its maximum zoom scale value") { + scrollView.maximumZoomScale = 0 + + let (pipeSignal, observer) = Signal.pipe() + scrollView.reactive.maximumZoomScale <~ SignalProducer(pipeSignal) + + observer.send(value: 42) + expect(scrollView.maximumZoomScale) == 42 + observer.send(value: 0) + expect(scrollView.maximumZoomScale) == 0 + } + + it("should accept changes from bindings to its scrolls to top state") { + scrollView.scrollsToTop = true + + let (pipeSignal, observer) = Signal.pipe() + scrollView.reactive.scrollsToTop <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(scrollView.scrollsToTop) == true + + observer.send(value: false) + expect(scrollView.scrollsToTop) == false + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UISearchBarSpec.swift b/ReactiveCocoaTests/UIKit/UISearchBarSpec.swift new file mode 100644 index 0000000000..2aace1adc5 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UISearchBarSpec.swift @@ -0,0 +1,261 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UISearchBarSpec: QuickSpec { + override func spec() { + var searchBar: UISearchBar! + weak var _searchBar: UISearchBar? + var receiver: SearchBarDelegateReceiver! + + beforeEach { + autoreleasepool { + receiver = SearchBarDelegateReceiver() + searchBar = UISearchBar(frame: .zero) + _searchBar = searchBar + + _ = searchBar.reactive.textValues + searchBar.delegate = receiver + + expect(searchBar.delegate).toNot(beIdenticalTo(receiver)) + } + } + + afterEach { + autoreleasepool { + searchBar = nil + } + expect(_searchBar).toEventually(beNil()) + } + + it("should accept changes from bindings to its text value") { + let firstChange = "first" + let secondChange = "second" + + searchBar.text = "" + + let (pipeSignal, observer) = Signal.pipe() + searchBar.reactive.text <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(searchBar.text) == firstChange + + observer.send(value: secondChange) + expect(searchBar.text) == secondChange + } + + it("should emit user initiated changes to its text value when the editing ends") { + searchBar.text = "Test" + + var latestValue: String? + searchBar.reactive.textValues.observeValues { text in + latestValue = text + } + + expect(latestValue).to(beNil()) + expect(receiver.endEditingTexts.isEmpty) == true + + searchBar.delegate!.searchBarTextDidEndEditing!(searchBar) + + expect(latestValue) == searchBar.text + expect(receiver.endEditingTexts.last) == searchBar.text + } + + it("should emit user initiated changes to its text value continuously") { + searchBar.text = "newValue" + + var latestValue: String? + searchBar.reactive.continuousTextValues.observeValues { text in + latestValue = text + } + + expect(latestValue).to(beNil()) + expect(receiver.texts.isEmpty) == true + + searchBar.delegate!.searchBar!(searchBar, textDidChange: "newValue") + expect(latestValue) == "newValue" + expect(receiver.texts.last) == "newValue" + } + + it("should accept changes from bindings to its scope button index") { + let firstChange = 1 + let secondChange = 2 + + searchBar.scopeButtonTitles = ["First", "Second", "Third"] + + let (pipeSignal, observer) = Signal.pipe() + searchBar.reactive.selectedScopeButtonIndex <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(searchBar.selectedScopeButtonIndex) == firstChange + + observer.send(value: secondChange) + expect(searchBar.selectedScopeButtonIndex) == secondChange + } + + it("should emit user initiated changes to its text value when the editing ends") { + searchBar.scopeButtonTitles = ["First", "Second", "Third"] + + var latestValue: Int? + searchBar.reactive.selectedScopeButtonIndices.observeValues { text in + latestValue = text + } + + expect(latestValue).to(beNil()) + expect(receiver.selectedScopeButtonIndices.isEmpty) == true + + searchBar.delegate!.searchBar!(searchBar, selectedScopeButtonIndexDidChange: 1) + + expect(latestValue) == 1 + expect(receiver.selectedScopeButtonIndices.last) == 1 + + searchBar.delegate!.searchBar!(searchBar, selectedScopeButtonIndexDidChange: 2) + + expect(latestValue) == 2 + expect(receiver.selectedScopeButtonIndices.last) == 2 + } + + it("should notify when the cancel button is clicked") { + var isClicked: Bool? + searchBar.reactive.cancelButtonClicked + .observeValues { isClicked = true } + + expect(isClicked).to(beNil()) + expect(receiver.cancelButtonClickedCounter) == 0 + + searchBar.delegate!.searchBarCancelButtonClicked!(searchBar) + expect(isClicked) == true + expect(receiver.cancelButtonClickedCounter) == 1 + } + + it("should notify when the search button is clicked") { + var isClicked: Bool? + searchBar.reactive.searchButtonClicked + .observeValues { isClicked = true } + + expect(isClicked).to(beNil()) + expect(receiver.searchButtonClickedCounter) == 0 + + searchBar.delegate!.searchBarSearchButtonClicked!(searchBar) + expect(isClicked) == true + expect(receiver.searchButtonClickedCounter) == 1 + } + + + it("should notify when the bookmark button is clicked") { + var isClicked: Bool? + searchBar.reactive.bookmarkButtonClicked + .observeValues { isClicked = true } + + expect(isClicked).to(beNil()) + expect(receiver.bookmarkButtonClickedCounter) == 0 + + searchBar.delegate!.searchBarBookmarkButtonClicked!(searchBar) + expect(isClicked) == true + expect(receiver.bookmarkButtonClickedCounter) == 1 + } + + + it("should notify when the results list button is clicked") { + var isClicked: Bool? + searchBar.reactive.resultsListButtonClicked + .observeValues { isClicked = true } + + expect(isClicked).to(beNil()) + expect(receiver.resultsListButtonClickedCounter) == 0 + + searchBar.delegate!.searchBarResultsListButtonClicked!(searchBar) + expect(isClicked) == true + expect(receiver.resultsListButtonClickedCounter) == 1 + } + + + it("should notify when started editing") { + var didBegin: Bool? + searchBar.reactive.textDidBeginEditing + .observeValues { didBegin = true } + + expect(didBegin).to(beNil()) + expect(receiver.beginEditingCounter) == 0 + + searchBar.delegate!.searchBarTextDidBeginEditing!(searchBar) + expect(didBegin) == true + expect(receiver.beginEditingCounter) == 1 + } + + + it("should notify when ended editing") { + var didEnd: Bool? + searchBar.reactive.textDidEndEditing + .observeValues { didEnd = true } + + expect(didEnd).to(beNil()) + expect(receiver.endEditingTexts.isEmpty) == true + + searchBar.delegate!.searchBarTextDidEndEditing!(searchBar) + expect(didEnd) == true + expect(receiver.endEditingTexts.count) == 1 + } + + + it("should accept changes from bindings to its hidden state of the cancel button") { + searchBar.showsCancelButton = false + + let (pipeSignal, observer) = Signal.pipe() + searchBar.reactive.showsCancelButton <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(searchBar.showsCancelButton) == true + + observer.send(value: false) + expect(searchBar.showsCancelButton) == false + } + } +} + +class SearchBarDelegateReceiver: NSObject, UISearchBarDelegate { + var texts: [String] = [] + var beginEditingCounter = 0 + var endEditingTexts: [String] = [] + var searchButtonClickedCounter = 0 + var cancelButtonClickedCounter = 0 + var bookmarkButtonClickedCounter = 0 + var resultsListButtonClickedCounter = 0 + var selectedScopeButtonIndices: [Int] = [] + + func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + searchButtonClickedCounter += 1 + } + + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + texts.append(searchText) + } + + func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + beginEditingCounter += 1 + } + + func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { + endEditingTexts.append(searchBar.text ?? "") + } + + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + cancelButtonClickedCounter += 1 + } + + func searchBarResultsListButtonClicked(_ searchBar: UISearchBar) { + resultsListButtonClickedCounter += 1 + } + + func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) { + bookmarkButtonClickedCounter += 1 + } + + func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { + selectedScopeButtonIndices.append(selectedScope) + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UISegmentedControlSpec.swift b/ReactiveCocoaTests/UIKit/UISegmentedControlSpec.swift new file mode 100644 index 0000000000..662eb6072b --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UISegmentedControlSpec.swift @@ -0,0 +1,28 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UISegmentedControlSpec: QuickSpec { + override func spec() { + it("should accept changes from bindings to its selected segment index") { + let s = UISegmentedControl(items: ["0", "1", "2"]) + s.selectedSegmentIndex = UISegmentedControl.noSegment + expect(s.numberOfSegments) == 3 + + let (pipeSignal, observer) = Signal.pipe() + s.reactive.selectedSegmentIndex <~ SignalProducer(pipeSignal) + + expect(s.selectedSegmentIndex) == UISegmentedControl.noSegment + + observer.send(value: 1) + expect(s.selectedSegmentIndex) == 1 + + observer.send(value: 2) + expect(s.selectedSegmentIndex) == 2 + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UISliderSpec.swift b/ReactiveCocoaTests/UIKit/UISliderSpec.swift new file mode 100644 index 0000000000..143b6ce059 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UISliderSpec.swift @@ -0,0 +1,70 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UISliderSpec: QuickSpec { + override func spec() { + var slider: UISlider! + weak var _slider: UISlider? + + beforeEach { + slider = UISlider() + _slider = slider + } + + afterEach { + slider = nil + expect(_slider).to(beNil()) + } + + it("should accept changes from bindings to its value") { + expect(slider.value) == 0.0 + + let (pipeSignal, observer) = Signal.pipe() + + slider.reactive.value <~ pipeSignal + + observer.send(value: 0.5) + expect(slider.value) ≈ 0.5 + } + + it("should accept changes from bindings to its minimum value") { + expect(slider.minimumValue) == 0.0 + + let (pipeSignal, observer) = Signal.pipe() + + slider.reactive.minimumValue <~ pipeSignal + + observer.send(value: 0.3) + expect(slider.minimumValue) ≈ 0.3 + } + + it("should accept changes from bindings to its maximum value") { + expect(slider.maximumValue) == 1.0 + + let (pipeSignal, observer) = Signal.pipe() + + slider.reactive.maximumValue <~ pipeSignal + + observer.send(value: 0.7) + expect(slider.maximumValue) ≈ 0.7 + } + + it("should emit user's changes for its value") { + slider.value = 0.25 + + var updatedValue: Float? + slider.reactive.values.observeValues { value in + updatedValue = value + } + + expect(updatedValue).to(beNil()) + slider.sendActions(for: .valueChanged) + expect(updatedValue) ≈ 0.25 + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIStepperSpec.swift b/ReactiveCocoaTests/UIKit/UIStepperSpec.swift new file mode 100644 index 0000000000..5f1227503e --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIStepperSpec.swift @@ -0,0 +1,70 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIStepperSpec: QuickSpec { + override func spec() { + var stepper: UIStepper! + weak var _stepper: UIStepper? + + beforeEach { + stepper = UIStepper() + _stepper = stepper + } + + afterEach { + stepper = nil + expect(_stepper).to(beNil()) + } + + it("should accept changes from bindings to its value") { + expect(stepper.value) == 0.0 + + let (pipeSignal, observer) = Signal.pipe() + + stepper.reactive.value <~ pipeSignal + + observer.send(value: 0.5) + expect(stepper.value) ≈ 0.5 + } + + it("should accept changes from bindings to its minimum value") { + expect(stepper.minimumValue) == 0.0 + + let (pipeSignal, observer) = Signal.pipe() + + stepper.reactive.minimumValue <~ pipeSignal + + observer.send(value: 0.3) + expect(stepper.minimumValue) ≈ 0.3 + } + + it("should accept changes from bindings to its maximum value") { + expect(stepper.maximumValue) == 100.0 + + let (pipeSignal, observer) = Signal.pipe() + + stepper.reactive.maximumValue <~ pipeSignal + + observer.send(value: 33.0) + expect(stepper.maximumValue) ≈ 33.0 + } + + it("should emit user's changes for its value") { + stepper.value = 0.25 + + var updatedValue: Double? + stepper.reactive.values.observeValues { value in + updatedValue = value + } + + expect(updatedValue).to(beNil()) + stepper.sendActions(for: .valueChanged) + expect(updatedValue) ≈ 0.25 + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UISwitchSpec.swift b/ReactiveCocoaTests/UIKit/UISwitchSpec.swift new file mode 100644 index 0000000000..5ad3baceae --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UISwitchSpec.swift @@ -0,0 +1,73 @@ +#if canImport(UIKit) && !os(tvOS) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UISwitchSpec: QuickSpec { + override func spec() { + var toggle: UISwitch! + weak var _toggle: UISwitch? + + beforeEach { + toggle = UISwitch(frame: .zero) + _toggle = toggle + } + + afterEach { + toggle = nil + if #available(*, iOS 10.2) { + expect(_toggle).to(beNil()) + } + } + + it("should accept changes from bindings to its `isOn` state") { + toggle.isOn = false + + let (pipeSignal, observer) = Signal.pipe() + toggle.reactive.isOn <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(toggle.isOn) == true + + observer.send(value: false) + expect(toggle.isOn) == false + } + + it("should emit user initiated changes to its `isOn` state") { + var latestValue: Bool? + toggle.reactive.isOnValues.observeValues { latestValue = $0 } + + toggle.isOn = true + toggle.sendActions(for: .valueChanged) + expect(latestValue!) == true + } + + it("should execute the `toggled` action upon receiving a `valueChanged` action message.") { + toggle.isOn = false + toggle.isEnabled = true + toggle.isUserInteractionEnabled = true + + let isOn = MutableProperty(false) + let action = Action { isOn in + return SignalProducer(value: isOn) + } + isOn <~ SignalProducer(action.values) + + toggle.reactive.toggled = CocoaAction(action) { return $0.isOn } + + expect(isOn.value) == false + + toggle.isOn = true + toggle.sendActions(for: .valueChanged) + expect(isOn.value) == true + + toggle.isOn = false + toggle.sendActions(for: .valueChanged) + expect(isOn.value) == false + + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UITabBarItemSpec.swift b/ReactiveCocoaTests/UIKit/UITabBarItemSpec.swift new file mode 100644 index 0000000000..16af36c5fb --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UITabBarItemSpec.swift @@ -0,0 +1,64 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UITabBarSpec: QuickSpec { + override func spec() { + var tabBarItem: UITabBarItem! + weak var _tabBarItem: UITabBarItem? + + beforeEach { + tabBarItem = UITabBarItem(tabBarSystemItem: .downloads, tag: 1) + _tabBarItem = tabBarItem + } + + afterEach { + tabBarItem = nil + expect(_tabBarItem).to(beNil()) + } + + it("should accept changes from bindings to its badge value") { + let firstChange = "first" + let secondChange = "second" + + tabBarItem.badgeValue = "" + + let (pipeSignal, observer) = Signal.pipe() + tabBarItem.reactive.badgeValue <~ pipeSignal + + observer.send(value: firstChange) + expect(tabBarItem.badgeValue) == firstChange + + observer.send(value: secondChange) + expect(tabBarItem.badgeValue) == secondChange + + observer.send(value: nil) + expect(tabBarItem.badgeValue).to(beNil()) + } + + if #available(iOS 10, *), #available(tvOS 10, *) { + it("should accept changes from bindings to its badge color value") { + let firstChange: UIColor = .red + let secondChange: UIColor = .green + + tabBarItem.badgeColor = .blue + + let (pipeSignal, observer) = Signal.pipe() + tabBarItem.reactive.badgeColor <~ pipeSignal + + observer.send(value: firstChange) + expect(tabBarItem.badgeColor) == firstChange + + observer.send(value: secondChange) + expect(tabBarItem.badgeColor) == secondChange + + observer.send(value: nil) + expect(tabBarItem.badgeColor).to(beNil()) + } + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UITableViewSpec.swift b/ReactiveCocoaTests/UIKit/UITableViewSpec.swift new file mode 100644 index 0000000000..12ee8da778 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UITableViewSpec.swift @@ -0,0 +1,64 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +final class UITableViewSpec: QuickSpec { + override func spec() { + var tableView: TestTableView! + + beforeEach { + tableView = TestTableView() + } + + describe("reloadData") { + var bindingSignal: Signal<(), Never>! + var bindingObserver: Signal<(), Never>.Observer! + + var reloadDataCount = 0 + + beforeEach { + let (signal, observer) = Signal<(), Never>.pipe() + (bindingSignal, bindingObserver) = (signal, observer) + + reloadDataCount = 0 + + tableView.reloadDataSignal.observeValues { + reloadDataCount += 1 + } + } + + it("invokes reloadData whenever the bound signal sends a value") { + tableView.reactive.reloadData <~ bindingSignal + + bindingObserver.send(value: ()) + bindingObserver.send(value: ()) + + expect(reloadDataCount) == 2 + } + } + } +} + +private final class TestTableView: UITableView { + let reloadDataSignal: Signal<(), Never> + private let reloadDataObserver: Signal<(), Never>.Observer + + override init(frame: CGRect, style: UITableView.Style) { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(frame: frame, style: style) + } + + required init?(coder aDecoder: NSCoder) { + (reloadDataSignal, reloadDataObserver) = Signal.pipe() + super.init(coder: aDecoder) + } + + override func reloadData() { + super.reloadData() + reloadDataObserver.send(value: ()) + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UITextFieldSpec.swift b/ReactiveCocoaTests/UIKit/UITextFieldSpec.swift new file mode 100644 index 0000000000..bad595b323 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UITextFieldSpec.swift @@ -0,0 +1,205 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UITextFieldSpec: QuickSpec { + override func spec() { + var textField: UITextField! + weak var _textField: UITextField? + + beforeEach { + autoreleasepool { + textField = UITextField(frame: .zero) + _textField = textField + } + } + + afterEach { + autoreleasepool { + textField = nil + } + + // FIXME: iOS 11.0 SDK beta 1 + // expect(_textField).toEventually(beNil()) + } + + it("should emit user initiated changes to its text value when the editing ends") { + textField.text = "Test" + + var latestValue: String? + textField.reactive.textValues.observeValues { text in + latestValue = text + } + + textField.sendActions(for: .editingDidEnd) + expect(latestValue) == textField.text + } + + it("should emit user initiated changes to its text value when the editing ends as a reuslt of the return key being pressed") { + textField.text = "Test" + + var latestValue: String? + textField.reactive.textValues.observeValues { text in + latestValue = text + } + + textField.sendActions(for: .editingDidEndOnExit) + expect(latestValue) == textField.text + } + + it("should emit user initiated changes to its text value continuously") { + var latestValue: String? + textField.reactive.continuousTextValues.observeValues { text in + latestValue = text + } + + for event in UIControl.Event.editingEvents { + textField.text = "Test \(event)" + + textField.sendActions(for: event) + expect(latestValue) == textField.text + } + } + + it("should accept changes from bindings to its attributed text value") { + let firstChange = NSAttributedString(string: "first") + let secondChange = NSAttributedString(string: "second") + + textField.attributedText = NSAttributedString(string: "") + + let (pipeSignal, observer) = Signal.pipe() + textField.reactive.attributedText <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(textField.attributedText?.string) == firstChange.string + + observer.send(value: secondChange) + expect(textField.attributedText?.string) == secondChange.string + } + + it("should emit user initiated changes to its attributed text value when the editing ends") { + textField.attributedText = NSAttributedString(string: "Test") + + var latestValue: NSAttributedString? + textField.reactive.attributedTextValues.observeValues { attributedText in + latestValue = attributedText + } + + textField.sendActions(for: .editingDidEnd) + expect(latestValue) == textField.attributedText + } + + it("should emit user initiated changes to its attributed text value when the editing ends as a result of the return key being pressed") { + textField.attributedText = NSAttributedString(string: "Test") + + var latestValue: NSAttributedString? + textField.reactive.attributedTextValues.observeValues { attributedText in + latestValue = attributedText + } + + textField.sendActions(for: .editingDidEndOnExit) + expect(latestValue) == textField.attributedText + } + + it("should emit user initiated changes to its attributed text value continuously") { + var latestValue: NSAttributedString? + textField.reactive.continuousAttributedTextValues.observeValues { attributedText in + latestValue = attributedText + } + + for event in UIControl.Event.editingEvents { + textField.attributedText = NSAttributedString(string: "Test \(event)") + + textField.sendActions(for: event) + expect(latestValue?.string) == textField.attributedText?.string + } + } + + it("should accept changes from bindings to its placeholder attribute") { + let (pipeSignal, observer) = Signal.pipe() + textField.reactive.placeholder <~ pipeSignal + + observer.send(value: "A placeholder") + expect(textField.placeholder).to(equal("A placeholder")) + + observer.send(value: nil) + expect(textField.placeholder).to(beNil()) + + observer.send(value: "Another placeholder") + expect(textField.placeholder).to(equal("Another placeholder")) + } + + it("should accept changes from bindings to its secureTextEntry attribute") { + let (pipeSignal, observer) = Signal.pipe() + textField.reactive.isSecureTextEntry <~ pipeSignal + + observer.send(value: true) + expect(textField.isSecureTextEntry) == true + + observer.send(value: false) + expect(textField.isSecureTextEntry) == false + } + + it("should accept changes from bindings to its textColor attribute") { + let (pipeSignal, observer) = Signal.pipe() + textField.reactive.textColor <~ pipeSignal + + observer.send(value: UIColor.red) + expect(textField.textColor == UIColor.red) == true + + observer.send(value: UIColor.blue) + expect(textField.textColor == UIColor.red) == false + } + + #if !targetEnvironment(macCatalyst) + it("should not deadlock when the text field is asked to resign first responder by any of its observers") { + UIView.setAnimationsEnabled(false) + defer { UIView.setAnimationsEnabled(true) } + + autoreleasepool { + let window = UIWindow(frame: .zero) + window.addSubview(textField) + + defer { + textField.removeFromSuperview() + expect(textField.superview).to(beNil()) + } + + expect(textField.becomeFirstResponder()) == true + expect(textField.isFirstResponder) == true + + var values: [String] = [] + + textField.reactive.continuousTextValues.observeValues { text in + values.append(text) + + if text == "2" { + textField.resignFirstResponder() + textField.text = "3" + } + } + expect(values) == [] + + textField.text = "1" + textField.sendActions(for: .editingChanged) + expect(values) == ["1"] + + textField.text = "2" + textField.sendActions(for: .editingChanged) + expect(textField.isFirstResponder) == false + expect(values) == ["1", "2", "2"] + } + } + #endif + } +} + +extension UIControl.Event { + fileprivate static var editingEvents: [UIControl.Event] { + return [.allEditingEvents, .editingDidBegin, .editingChanged, .editingDidEndOnExit, .editingDidEnd] + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UITextViewSpec.swift b/ReactiveCocoaTests/UIKit/UITextViewSpec.swift new file mode 100644 index 0000000000..faa5795773 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UITextViewSpec.swift @@ -0,0 +1,118 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UITextViewSpec: QuickSpec { + override func spec() { + var textView: UITextView! + weak var _textView: UITextView? + + #if swift(>=4.0) + let attributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.systemFont(ofSize: 18), + .foregroundColor: UIColor.red + ] + #else + let attributes = [ + NSFontAttributeName: UIFont.systemFont(ofSize: 18), + NSForegroundColorAttributeName: UIColor.red + ] + #endif + + beforeEach { + autoreleasepool { + textView = UITextView(frame: .zero) + _textView = textView + } + } + + afterEach { + autoreleasepool { + textView = nil + } + expect(_textView).toEventually(beNil()) + } + + it("should emit user initiated changes to its text value when the editing ends") { + textView.text = "Test" + + var latestValue: String? + textView.reactive.textValues.observeValues { text in + latestValue = text + } + + NotificationCenter.default.post(name: UITextView.textDidEndEditingNotification, object: textView) + expect(latestValue) == textView.text + } + + it("should emit user initiated changes to its text value continuously") { + textView.text = "Test" + + var latestValue: String? + textView.reactive.continuousTextValues.observeValues { text in + latestValue = text + } + + NotificationCenter.default.post(name: UITextView.textDidChangeNotification, object: textView) + expect(latestValue) == textView.text + } + + it("should accept changes from bindings to its attributed text value") { + let firstChange = NSAttributedString(string: "first", attributes: attributes) + let secondChange = NSAttributedString(string: "second", attributes: attributes) + + textView.attributedText = NSAttributedString(string: "") + + let (pipeSignal, observer) = Signal.pipe() + textView.reactive.attributedText <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(textView.attributedText) == firstChange + + observer.send(value: secondChange) + expect(textView.attributedText) == secondChange + } + + it("should emit user initiated changes to its attributed text value when the editing ends") { + textView.attributedText = NSAttributedString(string: "Test", attributes: attributes) + + var latestValue: NSAttributedString? + textView.reactive.attributedTextValues.observeValues { attributedText in + latestValue = attributedText + } + + NotificationCenter.default.post(name: UITextView.textDidEndEditingNotification, object: textView) + expect(latestValue) == textView.attributedText + } + + it("should emit user initiated changes to its attributed text value continuously") { + textView.attributedText = NSAttributedString(string: "Test", attributes: attributes) + + var latestValue: NSAttributedString? + textView.reactive.continuousAttributedTextValues.observeValues { attributedText in + latestValue = attributedText + } + + NotificationCenter.default.post(name: UITextView.textDidChangeNotification, object: textView) + expect(latestValue) == textView.attributedText + } + + it("should emit user initiated changes for selection") { + var latestValue: NSRange! + textView.reactive.selectedRangeValues.observeValues { + latestValue = $0 + } + + textView.text = "Test" + textView.selectedRange = NSRange(location: 1, length: 2) + + textView.delegate!.textViewDidChangeSelection!(textView) + expect(latestValue.location) == 1 + expect(latestValue.length) == 2 + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIViewControllerSpec.swift b/ReactiveCocoaTests/UIKit/UIViewControllerSpec.swift new file mode 100644 index 0000000000..48513621ed --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIViewControllerSpec.swift @@ -0,0 +1,115 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIViewControllerSpec: QuickSpec { + override func spec() { + var viewController: UIViewController! + weak var _viewController: UIViewController? + + beforeEach { + viewController = UIViewController() + _viewController = viewController + } + + afterEach { + viewController = nil + expect(_viewController).to(beNil()) + } + + it("should accept changes from bindings to its title value") { + let firstChange = "first" + let secondChange = "second" + + viewController.title = "" + + let (pipeSignal, observer) = Signal.pipe() + viewController.reactive.title <~ pipeSignal + + observer.send(value: firstChange) + expect(viewController.title) == firstChange + + observer.send(value: secondChange) + expect(viewController.title) == secondChange + + observer.send(value: nil) + expect(viewController.title).to(beNil()) + } + + it("should send a `value` event when `viewWillAppear` is invoked") { + var isInvoked = false + viewController.reactive.viewWillAppear.observeValues { + isInvoked = true + } + + expect(isInvoked) == false + + viewController.viewWillAppear(false) + expect(isInvoked) == true + } + + it("should send a `value` event when `viewDidAppear` is invoked") { + var isInvoked = false + viewController.reactive.viewDidAppear.observeValues { + isInvoked = true + } + + expect(isInvoked) == false + + viewController.viewDidAppear(false) + expect(isInvoked) == true + } + + it("should send a `value` event when `viewWillDisappear` is invoked") { + var isInvoked = false + viewController.reactive.viewWillDisappear.observeValues { + isInvoked = true + } + + expect(isInvoked) == false + + viewController.viewWillDisappear(false) + expect(isInvoked) == true + } + + it("should send a `value` event when `viewDidDisappear` is invoked") { + var isInvoked = false + viewController.reactive.viewDidDisappear.observeValues { + isInvoked = true + } + + expect(isInvoked) == false + + viewController.viewDidDisappear(false) + expect(isInvoked) == true + } + + it("should send a `value` event when `viewWillLayoutSubviews` is invoked") { + var isInvoked = false + viewController.reactive.viewWillLayoutSubviews.observeValues { + isInvoked = true + } + + expect(isInvoked) == false + + viewController.viewWillLayoutSubviews() + expect(isInvoked) == true + } + + it("should send a `value` event when `viewDidLayoutSubviews` is invoked") { + var isInvoked = false + viewController.reactive.viewDidLayoutSubviews.observeValues { + isInvoked = true + } + + expect(isInvoked) == false + + viewController.viewDidLayoutSubviews() + expect(isInvoked) == true + } + } +} +#endif diff --git a/ReactiveCocoaTests/UIKit/UIViewSpec.swift b/ReactiveCocoaTests/UIKit/UIViewSpec.swift new file mode 100644 index 0000000000..669aa51876 --- /dev/null +++ b/ReactiveCocoaTests/UIKit/UIViewSpec.swift @@ -0,0 +1,98 @@ +#if canImport(UIKit) +import ReactiveSwift +import ReactiveCocoa +import UIKit +import Quick +import Nimble + +class UIViewSpec: QuickSpec { + override func spec() { + var view: UIView! + weak var _view: UIView? + + beforeEach { + view = UIView(frame: .zero) + _view = view + } + + afterEach { + view = nil + expect(_view).to(beNil()) + } + + it("should accept changes from bindings to its hiding state") { + view.isHidden = true + + let (pipeSignal, observer) = Signal.pipe() + view.reactive.isHidden <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(view.isHidden) == true + + observer.send(value: false) + expect(view.isHidden) == false + } + + it("should accept changes from bindings to its alpha value") { + view.alpha = 0.0 + + let firstChange = CGFloat(0.5) + let secondChange = CGFloat(0.7) + + let (pipeSignal, observer) = Signal.pipe() + view.reactive.alpha <~ SignalProducer(pipeSignal) + + observer.send(value: firstChange) + expect(view.alpha) ≈ firstChange + + observer.send(value: secondChange) + expect(view.alpha) ≈ secondChange + } + + it("should accept changes from bindings to its user interaction enabling state") { + view.isUserInteractionEnabled = true + + let (pipeSignal, observer) = Signal.pipe() + view.reactive.isUserInteractionEnabled <~ SignalProducer(pipeSignal) + + observer.send(value: true) + expect(view.isUserInteractionEnabled) == true + + observer.send(value: false) + expect(view.isUserInteractionEnabled) == false + } + + it("should accept changes from bindings to its background color") { + view.backgroundColor = .white + + let (pipeSignal, observer) = Signal.pipe() + view.reactive.backgroundColor <~ SignalProducer(pipeSignal) + + observer.send(value: .yellow) + expect(view.backgroundColor) == .yellow + + observer.send(value: .green) + expect(view.backgroundColor) == .green + + observer.send(value: .red) + expect(view.backgroundColor) == .red + } + + it("should accept changes from bindings to its tint color") { + view.tintColor = .white + + let (pipeSignal, observer) = Signal.pipe() + view.reactive.tintColor <~ SignalProducer(pipeSignal) + + observer.send(value: .yellow) + expect(view.tintColor) == .yellow + + observer.send(value: .green) + expect(view.tintColor) == .green + + observer.send(value: .red) + expect(view.tintColor) == .red + } + } +} +#endif diff --git a/ReactiveMapKit.podspec b/ReactiveMapKit.podspec new file mode 100644 index 0000000000..6109d4e670 --- /dev/null +++ b/ReactiveMapKit.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = "ReactiveMapKit" + s.version = "12.0.0" + s.summary = "MapKit bindings for ReactiveCocoa." + s.description = <<-DESC + Provide MapKit bindings for ReactiveCocoa. ReactiveCocoa (RAC) is a Cocoa framework built on top of ReactiveSwift. It provides APIs for using ReactiveSwift with Apple's Cocoa frameworks. + DESC + s.homepage = "https://github.com/ReactiveCocoa/ReactiveCocoa" + s.license = { :type => "MIT", :file => "LICENSE.md" } + s.author = "ReactiveCocoa" + + s.osx.deployment_target = "10.13" + s.ios.deployment_target = "11.0" + s.tvos.deployment_target = "11.0" + + s.source = { :git => "https://github.com/ReactiveCocoa/ReactiveCocoa.git", :tag => "#{s.version}" } + s.source_files = "ReactiveMapKit/*.{swift,h,m}" + + s.dependency 'ReactiveCocoa', "#{s.version}" + + s.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS[config=Release]" => "$(inherited) -suppress-warnings" } + s.swift_versions = ['5.0', '5.1', '5.2'] +end diff --git a/ReactiveMapKit/Info.plist b/ReactiveMapKit/Info.plist new file mode 100644 index 0000000000..db11367805 --- /dev/null +++ b/ReactiveMapKit/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 12.0.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/ReactiveMapKit/MKLocalSearchRequest.swift b/ReactiveMapKit/MKLocalSearchRequest.swift new file mode 100644 index 0000000000..b5b20201aa --- /dev/null +++ b/ReactiveMapKit/MKLocalSearchRequest.swift @@ -0,0 +1,25 @@ +import ReactiveSwift +import ReactiveCocoa +import MapKit + +private let defaultLocalSearchError = NSError(domain: "org.reactivecocoa.ReactiveCocoa.Reactivity.MKLocalSearchRequest", + code: 1, + userInfo: nil) +@available(tvOS 9.2, *) +extension Reactive where Base: MKLocalSearch.Request { + /// A SignalProducer which performs an `MKLocalSearch`. + public var search: SignalProducer { + return SignalProducer {[base = self.base] observer, lifetime in + let search = MKLocalSearch(request: base) + search.start { response, error in + if let response = response { + observer.send(value: response) + observer.sendCompleted() + } else { + observer.send(error: error ?? defaultLocalSearchError) + } + } + lifetime.observeEnded(search.cancel) + } + } +} diff --git a/ReactiveMapKit/MKMapView.swift b/ReactiveMapKit/MKMapView.swift new file mode 100644 index 0000000000..152cf9871a --- /dev/null +++ b/ReactiveMapKit/MKMapView.swift @@ -0,0 +1,34 @@ +import ReactiveSwift +import ReactiveCocoa +import MapKit + +@available(tvOS 9.2, *) +extension Reactive where Base: MKMapView { + + /// Sets the map type. + public var mapType: BindingTarget { + return makeBindingTarget { $0.mapType = $1 } + } + + /// Sets if zoom is enabled for map. + public var isZoomEnabled: BindingTarget { + return makeBindingTarget { $0.isZoomEnabled = $1 } + } + + /// Sets if scroll is enabled for map. + public var isScrollEnabled: BindingTarget { + return makeBindingTarget { $0.isScrollEnabled = $1 } + } + + #if !os(tvOS) + /// Sets if pitch is enabled for map. + public var isPitchEnabled: BindingTarget { + return makeBindingTarget { $0.isPitchEnabled = $1 } + } + + /// Sets if rotation is enabled for map. + public var isRotateEnabled: BindingTarget { + return makeBindingTarget { $0.isRotateEnabled = $1 } + } + #endif +} diff --git a/ReactiveMapKitTests/Info.plist b/ReactiveMapKitTests/Info.plist new file mode 100644 index 0000000000..6c40a6cd0c --- /dev/null +++ b/ReactiveMapKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/ReactiveMapKitTests/MKMapViewSpec.swift b/ReactiveMapKitTests/MKMapViewSpec.swift new file mode 100644 index 0000000000..092767af29 --- /dev/null +++ b/ReactiveMapKitTests/MKMapViewSpec.swift @@ -0,0 +1,94 @@ +import ReactiveSwift +import ReactiveCocoa +import ReactiveMapKit +import Quick +import Nimble +import MapKit + +@available(tvOS 9.2, *) +class MKMapViewSpec: QuickSpec { + override func spec() { + var mapView: MKMapView! + weak var _mapView: MKMapView? + + beforeEach { + mapView = MKMapView(frame: .zero) + _mapView = mapView + } + + afterEach { + autoreleasepool { + mapView = nil + } + // FIXME: SDK_ISSUE + // + // Temporarily disabled since the expectation keeps failing with + // Xcode 8.3 and macOS Sierra 10.12.4. + #if !os(macOS) && !targetEnvironment(macCatalyst) + // using toEventually(beNil()) here + // since it takes time to release MKMapView + expect(_mapView).toEventually(beNil()) + #endif + } + + it("should accept changes from bindings to its map type") { + expect(mapView.mapType) == MKMapType.standard + + let (pipeSignal, observer) = Signal.pipe() + + mapView.reactive.mapType <~ pipeSignal + + observer.send(value: MKMapType.satellite) + expect(mapView.mapType) == MKMapType.satellite + + observer.send(value: MKMapType.hybrid) + expect(mapView.mapType) == MKMapType.hybrid + } + + it("should accept changes from bindings to its zoom enabled state") { + expect(mapView.isZoomEnabled) == true + + let (pipeSignal, observer) = Signal.pipe() + + mapView.reactive.isZoomEnabled <~ pipeSignal + + observer.send(value: false) + expect(mapView.isZoomEnabled) == false + } + + it("should accept changes from bindings to its scroll enabled state") { + expect(mapView.isScrollEnabled) == true + + let (pipeSignal, observer) = Signal.pipe() + + mapView.reactive.isScrollEnabled <~ pipeSignal + + observer.send(value: false) + expect(mapView.isScrollEnabled) == false + } + + #if !os(tvOS) + it("should accept changes from bindings to its pitch enabled state") { + expect(mapView.isPitchEnabled) == true + + let (pipeSignal, observer) = Signal.pipe() + + mapView.reactive.isPitchEnabled <~ pipeSignal + + observer.send(value: false) + expect(mapView.isPitchEnabled) == false + } + + it("should accept changes from bindings to its rotate enabled state") { + expect(mapView.isRotateEnabled) == true + + let (pipeSignal, observer) = Signal.pipe() + + mapView.reactive.isRotateEnabled <~ pipeSignal + + observer.send(value: false) + expect(mapView.isRotateEnabled) == false + } + #endif + } +} diff --git a/Sources b/Sources deleted file mode 120000 index c5c6e8cb28..0000000000 --- a/Sources +++ /dev/null @@ -1 +0,0 @@ -ReactiveCocoa/Swift/ \ No newline at end of file diff --git a/script/build b/script/build index e36726ee19..fffabcd56b 100755 --- a/script/build +++ b/script/build @@ -2,14 +2,10 @@ BUILD_DIRECTORY="build" CONFIGURATION=Release +XCODE_WORKSPACE=ReactiveCocoa.xcworkspace -if [[ -z $TRAVIS_XCODE_WORKSPACE ]]; then - echo "Error: \$TRAVIS_XCODE_WORKSPACE is not set." - exit 1 -fi - -if [[ -z $TRAVIS_XCODE_SCHEME ]]; then - echo "Error: \$TRAVIS_XCODE_SCHEME is not set!" +if [[ -z $XCODE_SCHEME ]]; then + echo "Error: \$XCODE_SCHEME is not set!" exit 1 fi @@ -30,8 +26,8 @@ fi set -o pipefail xcodebuild $XCODE_ACTION \ - -workspace "$TRAVIS_XCODE_WORKSPACE" \ - -scheme "$TRAVIS_XCODE_SCHEME" \ + -workspace "$XCODE_WORKSPACE" \ + -scheme "$XCODE_SCHEME" \ -sdk "$XCODE_SDK" \ -destination "$XCODE_DESTINATION" \ -derivedDataPath "${BUILD_DIRECTORY}" \ @@ -45,8 +41,8 @@ if [ "$result" -ne 0 ]; then exit $result fi -# Compile code in playgrounds -if [[ $XCODE_SDK = "macosx" ]]; then - echo "SDK is $XCODE_SDK, validating playground..." +# Compile code in playgrounds +if [[ "$XCODE_PLAYGROUND_TARGET" ]]; then + echo "Validating playground..." . script/validate-playground.sh fi diff --git a/script/update-version b/script/update-version new file mode 100755 index 0000000000..71d5b41a25 --- /dev/null +++ b/script/update-version @@ -0,0 +1,24 @@ +#!/bin/bash + +if [[ -z "$1" ]]; then + echo "Please specify a version tag." + exit +fi + +PRERELEASE_STRIPPED=$(echo "$1" | perl -0777 -ne '/([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?/ and print "$1.$2.$3"') + +if [[ -z "$PRERELEASE_STRIPPED" ]]; then + echo "The version tag is not semver compliant." + exit +fi + +CURRENT_TAG=$(perl -0777 -ne '/s.version([\s]+)=([\s]+)"(.+)"/ and print $3 and last' *.podspec) +echo "Current tag: $CURRENT_TAG" + +perl -0777 -i -pe 's/s.version([\s]+)=([\s]+)"'${CURRENT_TAG}'"/s.version$1=$2"'${1}'"/' *.podspec +perl -0777 -i -pe 's/g>'${CURRENT_TAG}'<\/str/g>'${PRERELEASE_STRIPPED}'<\/str/' */Info.plist +perl -0777 -i -pe 's/g>'${CURRENT_TAG}'<\/str/g>'${PRERELEASE_STRIPPED}'<\/str/' */*/Info.plist +sed -i '' '3i\ +\ +# '${1} CHANGELOG.md + diff --git a/script/validate-playground.sh b/script/validate-playground.sh index 17b6d4f2ea..017d9e68b7 100755 --- a/script/validate-playground.sh +++ b/script/validate-playground.sh @@ -10,16 +10,30 @@ if [ -z "$BUILD_DIRECTORY" ]; then exit 1 fi +if [ -z "$PLAYGROUND" ]; then + echo "\$PLAYGROUND is not set." + exit 1 +fi + if [ -z "$XCODE_PLAYGROUND_TARGET" ]; then echo "\$XCODE_PLAYGROUND_TARGET is not set." exit 1 fi -PAGES_PATH=${BUILD_DIRECTORY}/Build/Products/${CONFIGURATION}/all-playground-pages.swift +BUILD_DIR_PATH= + +if [ "$XCODE_SDK" == "macosx" ]; then + BUILD_DIR_PATH=Products/${CONFIGURATION} +else + BUILD_DIR_PATH=Intermediates/CodeCoverage/Products/${CONFIGURATION}-${XCODE_SDK} +fi + +SDK_ROOT=$(xcrun --sdk ${XCODE_SDK} --show-sdk-path) +PAGES_PATH=${BUILD_DIRECTORY}/Build/${BUILD_DIR_PATH}/all-playground-pages.swift -cat ReactiveCocoa.playground/Sources/*.swift ReactiveCocoa.playground/Pages/**/*.swift > ${PAGES_PATH} +cat ${PLAYGROUND}/Sources/*.swift ${PLAYGROUND}/Pages/**/*.swift > ${PAGES_PATH} -swift -v -target ${XCODE_PLAYGROUND_TARGET} -D NOT_IN_PLAYGROUND -F ${BUILD_DIRECTORY}/Build/Products/${CONFIGURATION} ${PAGES_PATH} > /dev/null +swift -v -target ${XCODE_PLAYGROUND_TARGET} -sdk ${SDK_ROOT} -D NOT_IN_PLAYGROUND -F ${BUILD_DIRECTORY}/Build/${BUILD_DIR_PATH} ${PAGES_PATH} > /dev/null result=$? # Cleanup