From a8c816987156c41c447ad028cd12ebefae7fcd56 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 16 Aug 2016 09:34:37 -0400 Subject: [PATCH 01/20] Create ReactiveSwift --- .travis.yml | 21 + .../contents.xcworkspacedata | 3 - .../Sandbox.xcplaygroundpage/Contents.swift | 12 +- .../Signal.xcplaygroundpage/Contents.swift | 16 +- .../Contents.swift | 16 +- .../Sources/PlaygroundUtility.swift | 0 .../contents.xcplayground | 2 +- ReactiveSwift.xcodeproj/project.pbxproj | 1595 ++++++++++++ .../xcschemes/ReactiveSwift-iOS.xcscheme | 156 ++ .../xcschemes/ReactiveSwift-macOS.xcscheme | 156 ++ .../xcschemes/ReactiveSwift-tvOS.xcscheme | 156 ++ .../xcschemes/ReactiveSwift-watchOS.xcscheme | 71 + ReactiveSwift/Action.swift | 199 ++ ReactiveSwift/Atomic.swift | 169 ++ ReactiveSwift/Bag.swift | 110 + ReactiveSwift/Deprecations+Removals.swift | 255 ++ ReactiveSwift/Disposable.swift | 353 +++ ReactiveSwift/Event.swift | 165 ++ ReactiveSwift/EventLogger.swift | 132 + ReactiveSwift/Flatten.swift | 918 +++++++ ReactiveSwift/FoundationExtensions.swift | 80 + ReactiveSwift/Info.plist | 28 + ReactiveSwift/Lifetime.swift | 50 + ReactiveSwift/Observer.swift | 104 + ReactiveSwift/Optional.swift | 42 + ReactiveSwift/Property.swift | 878 +++++++ ReactiveSwift/ReactiveSwift.h | 15 + ReactiveSwift/Scheduler.swift | 493 ++++ ReactiveSwift/Signal.swift | 1838 +++++++++++++ ReactiveSwift/SignalProducer.swift | 1907 ++++++++++++++ ReactiveSwift/TupleExtensions.swift | 42 + ReactiveSwiftTests/ActionSpec.swift | 142 ++ ReactiveSwiftTests/AtomicSpec.swift | 44 + ReactiveSwiftTests/BagSpec.swift | 54 + ReactiveSwiftTests/DisposableSpec.swift | 143 ++ ReactiveSwiftTests/FlattenSpec.swift | 963 +++++++ .../FoundationExtensionsSpec.swift | 59 + ReactiveSwiftTests/Info.plist | 24 + ReactiveSwiftTests/LifetimeSpec.swift | 83 + ReactiveSwiftTests/PropertySpec.swift | 1560 ++++++++++++ ReactiveSwiftTests/SchedulerSpec.swift | 298 +++ ReactiveSwiftTests/SignalLifetimeSpec.swift | 414 +++ .../SignalProducerLiftingSpec.swift | 1536 +++++++++++ .../SignalProducerNimbleMatchers.swift | 57 + ReactiveSwiftTests/SignalProducerSpec.swift | 2257 ++++++++++++++++ ReactiveSwiftTests/SignalSpec.swift | 2269 +++++++++++++++++ ReactiveSwiftTests/TestError.swift | 43 + ReactiveSwiftTests/TestLogger.swift | 25 + 48 files changed, 19927 insertions(+), 26 deletions(-) rename {ReactiveCocoa.playground => ReactiveSwift.playground}/Pages/Sandbox.xcplaygroundpage/Contents.swift (58%) rename {ReactiveCocoa.playground => ReactiveSwift.playground}/Pages/Signal.xcplaygroundpage/Contents.swift (93%) rename {ReactiveCocoa.playground => ReactiveSwift.playground}/Pages/SignalProducer.xcplaygroundpage/Contents.swift (97%) rename {ReactiveCocoa.playground => ReactiveSwift.playground}/Sources/PlaygroundUtility.swift (100%) rename {ReactiveCocoa.playground => ReactiveSwift.playground}/contents.xcplayground (67%) create mode 100644 ReactiveSwift.xcodeproj/project.pbxproj create mode 100644 ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-iOS.xcscheme create mode 100644 ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-macOS.xcscheme create mode 100644 ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-tvOS.xcscheme create mode 100644 ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-watchOS.xcscheme create mode 100644 ReactiveSwift/Action.swift create mode 100644 ReactiveSwift/Atomic.swift create mode 100644 ReactiveSwift/Bag.swift create mode 100644 ReactiveSwift/Deprecations+Removals.swift create mode 100644 ReactiveSwift/Disposable.swift create mode 100644 ReactiveSwift/Event.swift create mode 100644 ReactiveSwift/EventLogger.swift create mode 100644 ReactiveSwift/Flatten.swift create mode 100644 ReactiveSwift/FoundationExtensions.swift create mode 100644 ReactiveSwift/Info.plist create mode 100644 ReactiveSwift/Lifetime.swift create mode 100644 ReactiveSwift/Observer.swift create mode 100644 ReactiveSwift/Optional.swift create mode 100644 ReactiveSwift/Property.swift create mode 100644 ReactiveSwift/ReactiveSwift.h create mode 100644 ReactiveSwift/Scheduler.swift create mode 100644 ReactiveSwift/Signal.swift create mode 100644 ReactiveSwift/SignalProducer.swift create mode 100644 ReactiveSwift/TupleExtensions.swift create mode 100755 ReactiveSwiftTests/ActionSpec.swift create mode 100644 ReactiveSwiftTests/AtomicSpec.swift create mode 100644 ReactiveSwiftTests/BagSpec.swift create mode 100644 ReactiveSwiftTests/DisposableSpec.swift create mode 100644 ReactiveSwiftTests/FlattenSpec.swift create mode 100644 ReactiveSwiftTests/FoundationExtensionsSpec.swift create mode 100644 ReactiveSwiftTests/Info.plist create mode 100644 ReactiveSwiftTests/LifetimeSpec.swift create mode 100644 ReactiveSwiftTests/PropertySpec.swift create mode 100644 ReactiveSwiftTests/SchedulerSpec.swift create mode 100644 ReactiveSwiftTests/SignalLifetimeSpec.swift create mode 100644 ReactiveSwiftTests/SignalProducerLiftingSpec.swift create mode 100644 ReactiveSwiftTests/SignalProducerNimbleMatchers.swift create mode 100644 ReactiveSwiftTests/SignalProducerSpec.swift create mode 100755 ReactiveSwiftTests/SignalSpec.swift create mode 100644 ReactiveSwiftTests/TestError.swift create mode 100644 ReactiveSwiftTests/TestLogger.swift diff --git a/.travis.yml b/.travis.yml index 85e315aaed..51ed833049 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,27 @@ matrix: - XCODE_SDK=watchsimulator - XCODE_ACTION=build - XCODE_DESTINATION="platform=watchOS Simulator,name=Apple Watch - 38mm" + - xcode_scheme: ReactiveSwift-macOS + env: + - XCODE_SDK=macosx + - XCODE_ACTION="build test" + - XCODE_DESTINATION="arch=x86_64" + - XCODE_PLAYGROUND_TARGET="x86_64-apple-macosx10.10" + - xcode_scheme: ReactiveSwift-macOS + env: + - XCODE_SDK=iphonesimulator + - XCODE_ACTION="build-for-testing test-without-building" + - XCODE_DESTINATION="platform=iOS Simulator,name=iPhone 6s" + - xcode_scheme: ReactiveSwift-macOS + env: + - XCODE_SDK=appletvsimulator + - XCODE_ACTION="build-for-testing test-without-building" + - XCODE_DESTINATION="platform=tvOS Simulator,name=Apple TV 1080p" + - xcode_scheme: ReactiveSwift-macOS + 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 diff --git a/ReactiveCocoa.xcworkspace/contents.xcworkspacedata b/ReactiveCocoa.xcworkspace/contents.xcworkspacedata index 9dc8faab27..4761de5116 100644 --- a/ReactiveCocoa.xcworkspace/contents.xcworkspacedata +++ b/ReactiveCocoa.xcworkspace/contents.xcworkspacedata @@ -1,9 +1,6 @@ - - diff --git a/ReactiveCocoa.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift b/ReactiveSwift.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift similarity index 58% rename from ReactiveCocoa.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift rename to ReactiveSwift.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift index afe8ad4d77..c2de48c890 100644 --- a/ReactiveCocoa.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift +++ b/ReactiveSwift.playground/Pages/Sandbox.xcplaygroundpage/Contents.swift @@ -1,19 +1,19 @@ /*: - > # IMPORTANT: To use `ReactiveCocoa.playground`, please: + > # IMPORTANT: To use `ReactiveSwift.playground`, please: - 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: + 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveSwift project root directory: - `script/bootstrap` **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed - `carthage checkout` - 1. Open `ReactiveCocoa.xcworkspace` + 1. Open `ReactiveSwift.xcworkspace` 1. Build `Result-Mac` scheme - 1. Build `ReactiveCocoa-Mac` scheme - 1. Finally open the `ReactiveCocoa.playground` + 1. Build `ReactiveSwift-macOS` scheme + 1. Finally open the `ReactiveSwift.playground` 1. Choose `View > Show Debug Area` */ import Result -import ReactiveCocoa +import ReactiveSwift import Foundation /*: diff --git a/ReactiveCocoa.playground/Pages/Signal.xcplaygroundpage/Contents.swift b/ReactiveSwift.playground/Pages/Signal.xcplaygroundpage/Contents.swift similarity index 93% rename from ReactiveCocoa.playground/Pages/Signal.xcplaygroundpage/Contents.swift rename to ReactiveSwift.playground/Pages/Signal.xcplaygroundpage/Contents.swift index 6a310cd20f..385659b078 100644 --- a/ReactiveCocoa.playground/Pages/Signal.xcplaygroundpage/Contents.swift +++ b/ReactiveSwift.playground/Pages/Signal.xcplaygroundpage/Contents.swift @@ -1,25 +1,25 @@ /*: -> # IMPORTANT: To use `ReactiveCocoa.playground`, please: +> # IMPORTANT: To use `ReactiveSwift.playground`, please: -1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: +1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveSwift project root directory: - `script/bootstrap` **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed - `carthage checkout` -1. Open `ReactiveCocoa.xcworkspace` +1. Open `ReactiveSwift.xcworkspace` 1. Build `Result-Mac` scheme -1. Build `ReactiveCocoa-Mac` scheme -1. Finally open the `ReactiveCocoa.playground` +1. Build `ReactiveSwift-macOS` scheme +1. Finally open the `ReactiveSwift.playground` 1. Choose `View > Show Debug Area` */ import Result -import ReactiveCocoa +import ReactiveSwift 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 +A **signal**, represented by the [`Signal`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/ReactiveSwift/Signal.swift) type, is any series of [`Event`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/ReactiveSwift/Event.swift) values over time that can be observed. Signals are generally used to represent event streams that are already “in progress”, @@ -34,7 +34,7 @@ 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. +Signals can be manipulated by applying [primitives](https://github.com/ReactiveCocoa/ReactiveSwift/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. diff --git a/ReactiveCocoa.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift b/ReactiveSwift.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift similarity index 97% rename from ReactiveCocoa.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift rename to ReactiveSwift.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift index 9cd61b63c5..0bc7a9dfbf 100644 --- a/ReactiveCocoa.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift +++ b/ReactiveSwift.playground/Pages/SignalProducer.xcplaygroundpage/Contents.swift @@ -1,26 +1,26 @@ /*: -> # IMPORTANT: To use `ReactiveCocoa.playground`, please: +> # IMPORTANT: To use `ReactiveSwift.playground`, please: -1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveCocoa project root directory: +1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveSwift project root directory: - `script/bootstrap` **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed - `carthage checkout` -1. Open `ReactiveCocoa.xcworkspace` +1. Open `ReactiveSwift.xcworkspace` 1. Build `Result-Mac` scheme -1. Build `ReactiveCocoa-Mac` scheme -1. Finally open the `ReactiveCocoa.playground` +1. Build `ReactiveSwift-macOS` scheme +1. Finally open the `ReactiveSwift.playground` 1. Choose `View > Show Debug Area` */ import Result -import ReactiveCocoa +import ReactiveSwift 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. +A **signal producer**, represented by the [`SignalProducer`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/ReactiveSwift/SignalProducer.swift) type, creates +[signals](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/ReactiveSwift/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 diff --git a/ReactiveCocoa.playground/Sources/PlaygroundUtility.swift b/ReactiveSwift.playground/Sources/PlaygroundUtility.swift similarity index 100% rename from ReactiveCocoa.playground/Sources/PlaygroundUtility.swift rename to ReactiveSwift.playground/Sources/PlaygroundUtility.swift diff --git a/ReactiveCocoa.playground/contents.xcplayground b/ReactiveSwift.playground/contents.xcplayground similarity index 67% rename from ReactiveCocoa.playground/contents.xcplayground rename to ReactiveSwift.playground/contents.xcplayground index fa86d24d80..43399d296e 100644 --- a/ReactiveCocoa.playground/contents.xcplayground +++ b/ReactiveSwift.playground/contents.xcplayground @@ -1,5 +1,5 @@ - + diff --git a/ReactiveSwift.xcodeproj/project.pbxproj b/ReactiveSwift.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..3baddc24d0 --- /dev/null +++ b/ReactiveSwift.xcodeproj/project.pbxproj @@ -0,0 +1,1595 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + 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 */; }; + 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 */; }; + 57A4D1B41BA13D7A00F7D4B1 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BE19EF2A5800984962 /* Disposable.swift */; }; + 57A4D1B61BA13D7A00F7D4B1 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B51A69A3DB00AD8286 /* Event.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 */; }; + 57A4D2081BA13D7A00F7D4B1 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC42E2E1AE7AB8B00965373 /* Result.framework */; }; + 57A4D20A1BA13D7A00F7D4B1 /* ReactiveSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveSwift.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7DFBED081CDB8C9500EE435B /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57A4D2411BA13D7A00F7D4B1 /* ReactiveSwift.framework */; }; + 7DFBED1E1CDB8D7000EE435B /* ReactiveSwift.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 57A4D2411BA13D7A00F7D4B1 /* ReactiveSwift.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 */; }; + 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 */; }; + 7DFBED6D1CDB8F7D00EE435B /* SignalProducerNimbleMatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.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 */; }; + A9B315BC1B3940810001CB9C /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312BE19EF2A5800984962 /* Disposable.swift */; }; + A9B315BE1B3940810001CB9C /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C54B51A69A3DB00AD8286 /* Event.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 */; }; + A9B315CA1B3940AB0001CB9C /* ReactiveSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveSwift.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A9F793341B60D0140026BCBA /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = D871D69E1B3B29A40070F16C /* Optional.swift */; }; + B696FB811A7640C00075236D /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; + B696FB821A7640C00075236D /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; + BFA6B94D1A7604D400C846D1 /* SignalProducerNimbleMatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */; }; + BFA6B94E1A7604D500C846D1 /* SignalProducerNimbleMatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.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 */; }; + 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 /* ReactiveSwift.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D047260C19E49F82006002AA /* ReactiveSwift.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 */; }; + D037666419EDA43C00A782A9 /* ReactiveSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveSwift.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 /* ReactiveSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = D04725EF19E49ED7006002AA /* ReactiveSwift.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D04725F619E49ED7006002AA /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D04725EA19E49ED7006002AA /* ReactiveSwift.framework */; }; + D047261719E49F82006002AA /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D047260C19E49F82006002AA /* ReactiveSwift.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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 7DFBED091CDB8C9500EE435B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D04725E119E49ED7006002AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57A4D1AF1BA13D7A00F7D4B1; + remoteInfo = "ReactiveCocoa-tvOS"; + }; + D04725F719E49ED7006002AA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D04725E119E49ED7006002AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = D04725E919E49ED7006002AA; + remoteInfo = ReactiveCocoa; + }; + D047261819E49F82006002AA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D04725E119E49ED7006002AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = D047260B19E49F82006002AA; + remoteInfo = ReactiveCocoa; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 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 /* ReactiveSwift.framework in Copy Frameworks */, + ); + name = "Copy Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + D01B7B6119EDD8F600D26E01 /* Copy Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + CDC42E331AE7AC6D00965373 /* Result.framework in Copy Frameworks */, + D01B7B6219EDD8FE00D26E01 /* Nimble.framework in Copy Frameworks */, + D01B7B6319EDD8FE00D26E01 /* Quick.framework in Copy Frameworks */, + D01B7B6419EDD94B00D26E01 /* ReactiveSwift.framework in Copy Frameworks */, + ); + name = "Copy Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 02D260291C1D6DAF003ACC61 /* SignalLifetimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalLifetimeSpec.swift; sourceTree = ""; }; + 4A0E10FE1D2A92720065D310 /* Lifetime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Lifetime.swift; path = Lifetime.swift; sourceTree = ""; }; + 4A0E11031D2A95200065D310 /* LifetimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LifetimeSpec.swift; sourceTree = ""; }; + 57A4D2411BA13D7A00F7D4B1 /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveSwift.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 = ""; }; + 7DFBED031CDB8C9500EE435B /* ReactiveSwiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveSwiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deprecations+Removals.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 /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B696FB801A7640C00075236D /* TestError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = ""; }; + BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalProducerNimbleMatchers.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 = ""; }; + 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; name = TupleExtensions.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; }; + 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; name = FoundationExtensions.swift; path = FoundationExtensions.swift; sourceTree = ""; }; + D04725EA19E49ED7006002AA /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D04725EE19E49ED7006002AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D04725EF19E49ED7006002AA /* ReactiveSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactiveSwift.h; sourceTree = ""; }; + D04725F519E49ED7006002AA /* ReactiveSwiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveSwiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D04725FB19E49ED7006002AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D047260C19E49F82006002AA /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D047261619E49F82006002AA /* ReactiveSwiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveSwiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D047262719E49FE8006002AA /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; + D047262919E49FE8006002AA /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + D047262A19E49FE8006002AA /* Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Profile.xcconfig; sourceTree = ""; }; + D047262B19E49FE8006002AA /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + D047262C19E49FE8006002AA /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = ""; }; + D047262E19E49FE8006002AA /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = ""; }; + D047262F19E49FE8006002AA /* Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Framework.xcconfig; sourceTree = ""; }; + D047263019E49FE8006002AA /* StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticLibrary.xcconfig; sourceTree = ""; }; + D047263219E49FE8006002AA /* iOS-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Application.xcconfig"; sourceTree = ""; }; + 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 = ""; }; + 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; name = Action.swift; path = Action.swift; sourceTree = ""; }; + D08C54B01A69A2AC00AD8286 /* Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Property.swift; path = Property.swift; sourceTree = ""; }; + D08C54B11A69A2AC00AD8286 /* Signal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Signal.swift; path = Signal.swift; sourceTree = ""; }; + D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SignalProducer.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; }; + 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 = ""; }; + 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 = ""; }; + 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; name = Flatten.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; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 57A4D2071BA13D7A00F7D4B1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 57A4D2081BA13D7A00F7D4B1 /* Result.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7DFBED001CDB8C9500EE435B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CDF066CA1CDC1CA200199626 /* Nimble.framework in Frameworks */, + CDF066CB1CDC1CA200199626 /* Quick.framework in Frameworks */, + 7DFBED081CDB8C9500EE435B /* ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A9B315501B3940610001CB9C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A9B315C91B3940980001CB9C /* Result.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D04725E619E49ED7006002AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC42E2F1AE7AB8B00965373 /* Result.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D04725F219E49ED7006002AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC42E301AE7AB8B00965373 /* Result.framework in Frameworks */, + D05E662519EDD82000904ACA /* Nimble.framework in Frameworks */, + D037672D19EDA75D00A782A9 /* Quick.framework in Frameworks */, + D04725F619E49ED7006002AA /* ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047260819E49F82006002AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC42E311AE7AB8B00965373 /* Result.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047261319E49F82006002AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D05E662619EDD83000904ACA /* Nimble.framework in Frameworks */, + D037672F19EDA78B00A782A9 /* Quick.framework in Frameworks */, + D047261719E49F82006002AA /* ReactiveSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 57A4D2431BA13F9700F7D4B1 /* tvOS */ = { + isa = PBXGroup; + children = ( + 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */, + 57A4D2451BA13F9700F7D4B1 /* tvOS-Base.xcconfig */, + 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */, + 57A4D2471BA13F9700F7D4B1 /* tvOS-StaticLibrary.xcconfig */, + ); + path = tvOS; + 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 = ""; + }; + D03B4A3919F4C25F009E02AC /* Signals */ = { + isa = PBXGroup; + children = ( + D08C54AF1A69A2AC00AD8286 /* Action.swift */, + D85C65291C0D84C7005A77AD /* Flatten.swift */, + 4A0E10FE1D2A92720065D310 /* Lifetime.swift */, + D08C54B01A69A2AC00AD8286 /* Property.swift */, + D08C54B11A69A2AC00AD8286 /* Signal.swift */, + D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */, + ); + name = Signals; + path = ""; + sourceTree = ""; + }; + D03B4A3A19F4C26D009E02AC /* Internal Utilities */ = { + isa = PBXGroup; + children = ( + D00004081A46864E000E7D41 /* TupleExtensions.swift */, + ); + name = "Internal Utilities"; + path = ""; + sourceTree = ""; + }; + D03B4A3B19F4C281009E02AC /* Extensions */ = { + isa = PBXGroup; + children = ( + D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */, + ); + name = Extensions; + path = ""; + sourceTree = ""; + }; + D04725E019E49ED7006002AA = { + isa = PBXGroup; + children = ( + D04725EC19E49ED7006002AA /* ReactiveSwift */, + D04725F919E49ED7006002AA /* ReactiveCocoaTests */, + D047262519E49FE8006002AA /* Configuration */, + D04725EB19E49ED7006002AA /* Products */, + ); + sourceTree = ""; + usesTabs = 1; + }; + D04725EB19E49ED7006002AA /* Products */ = { + isa = PBXGroup; + children = ( + D04725EA19E49ED7006002AA /* ReactiveSwift.framework */, + D04725F519E49ED7006002AA /* ReactiveSwiftTests.xctest */, + D047260C19E49F82006002AA /* ReactiveSwift.framework */, + D047261619E49F82006002AA /* ReactiveSwiftTests.xctest */, + A9B315541B3940610001CB9C /* ReactiveSwift.framework */, + 57A4D2411BA13D7A00F7D4B1 /* ReactiveSwift.framework */, + 7DFBED031CDB8C9500EE435B /* ReactiveSwiftTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + D04725EC19E49ED7006002AA /* ReactiveSwift */ = { + isa = PBXGroup; + children = ( + D04725EF19E49ED7006002AA /* ReactiveSwift.h */, + D0C312BB19EF2A5800984962 /* Atomic.swift */, + D0C312BC19EF2A5800984962 /* Bag.swift */, + D0C312BE19EF2A5800984962 /* Disposable.swift */, + D08C54B51A69A3DB00AD8286 /* Event.swift */, + EBCC7DBB1BBF010C00A2AE92 /* Observer.swift */, + D871D69E1B3B29A40070F16C /* Optional.swift */, + D0C312C819EF2A5800984962 /* Scheduler.swift */, + C79B647B1CD52E23003F2376 /* EventLogger.swift */, + D03B4A3919F4C25F009E02AC /* Signals */, + D03B4A3A19F4C26D009E02AC /* Internal Utilities */, + D03B4A3B19F4C281009E02AC /* Extensions */, + 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */, + D04725ED19E49ED7006002AA /* Supporting Files */, + ); + path = ReactiveSwift; + sourceTree = ""; + }; + D04725ED19E49ED7006002AA /* Supporting Files */ = { + isa = PBXGroup; + children = ( + CDC42E2E1AE7AB8B00965373 /* Result.framework */, + D04725EE19E49ED7006002AA /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D04725F919E49ED7006002AA /* ReactiveCocoaTests */ = { + isa = PBXGroup; + children = ( + D021671C1A6CD50500987861 /* ActionSpec.swift */, + D0C312EE19EF2A7700984962 /* AtomicSpec.swift */, + D0C312EF19EF2A7700984962 /* BagSpec.swift */, + D0C312F019EF2A7700984962 /* DisposableSpec.swift */, + CA6F284F1C52626B001879D2 /* FlattenSpec.swift */, + D8170FC01B100EBC004192AD /* FoundationExtensionsSpec.swift */, + 4A0E11031D2A95200065D310 /* LifetimeSpec.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 */, + D04725FA19E49ED7006002AA /* Supporting Files */, + ); + name = ReactiveCocoaTests; + path = ReactiveSwiftTests; + sourceTree = ""; + }; + D04725FA19E49ED7006002AA /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D05E662419EDD82000904ACA /* Nimble.framework */, + D037672B19EDA75D00A782A9 /* Quick.framework */, + BFA6B94A1A76044800C846D1 /* SignalProducerNimbleMatchers.swift */, + D04725FB19E49ED7006002AA /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D047262519E49FE8006002AA /* Configuration */ = { + isa = PBXGroup; + children = ( + D047262619E49FE8006002AA /* Base */, + D047263119E49FE8006002AA /* iOS */, + D047263619E49FE8006002AA /* Mac OS X */, + A97451321B3A935E00F48E55 /* watchOS */, + 57A4D2431BA13F9700F7D4B1 /* tvOS */, + D047263C19E49FE8006002AA /* README.md */, + ); + name = Configuration; + path = Carthage/Checkouts/xcconfigs; + sourceTree = ""; + }; + D047262619E49FE8006002AA /* Base */ = { + isa = PBXGroup; + children = ( + D047262719E49FE8006002AA /* Common.xcconfig */, + D047262819E49FE8006002AA /* Configurations */, + D047262D19E49FE8006002AA /* Targets */, + ); + path = Base; + sourceTree = ""; + }; + D047262819E49FE8006002AA /* Configurations */ = { + isa = PBXGroup; + children = ( + D047262919E49FE8006002AA /* Debug.xcconfig */, + D047262A19E49FE8006002AA /* Profile.xcconfig */, + D047262B19E49FE8006002AA /* Release.xcconfig */, + D047262C19E49FE8006002AA /* Test.xcconfig */, + ); + path = Configurations; + sourceTree = ""; + }; + D047262D19E49FE8006002AA /* Targets */ = { + isa = PBXGroup; + children = ( + D047262E19E49FE8006002AA /* Application.xcconfig */, + D047262F19E49FE8006002AA /* Framework.xcconfig */, + D047263019E49FE8006002AA /* StaticLibrary.xcconfig */, + ); + path = Targets; + sourceTree = ""; + }; + D047263119E49FE8006002AA /* iOS */ = { + isa = PBXGroup; + children = ( + D047263219E49FE8006002AA /* iOS-Application.xcconfig */, + D047263319E49FE8006002AA /* iOS-Base.xcconfig */, + D047263419E49FE8006002AA /* iOS-Framework.xcconfig */, + D047263519E49FE8006002AA /* iOS-StaticLibrary.xcconfig */, + ); + path = iOS; + sourceTree = ""; + }; + D047263619E49FE8006002AA /* Mac OS X */ = { + isa = PBXGroup; + children = ( + D047263719E49FE8006002AA /* Mac-Application.xcconfig */, + D047263819E49FE8006002AA /* Mac-Base.xcconfig */, + D047263919E49FE8006002AA /* Mac-DynamicLibrary.xcconfig */, + D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */, + D047263B19E49FE8006002AA /* Mac-StaticLibrary.xcconfig */, + ); + path = "Mac OS X"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 57A4D2091BA13D7A00F7D4B1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 57A4D20A1BA13D7A00F7D4B1 /* ReactiveSwift.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A9B315511B3940610001CB9C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A9B315CA1B3940AB0001CB9C /* ReactiveSwift.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D04725E719E49ED7006002AA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D04725F019E49ED7006002AA /* ReactiveSwift.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047260919E49F82006002AA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D037666419EDA43C00A782A9 /* ReactiveSwift.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 57A4D1AF1BA13D7A00F7D4B1 /* ReactiveSwift-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57A4D23C1BA13D7A00F7D4B1 /* Build configuration list for PBXNativeTarget "ReactiveSwift-tvOS" */; + buildPhases = ( + 57A4D1B01BA13D7A00F7D4B1 /* Sources */, + 57A4D2071BA13D7A00F7D4B1 /* Frameworks */, + 57A4D2091BA13D7A00F7D4B1 /* Headers */, + 57A4D23B1BA13D7A00F7D4B1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveSwift-tvOS"; + productName = ReactiveCocoa; + productReference = 57A4D2411BA13D7A00F7D4B1 /* ReactiveSwift.framework */; + productType = "com.apple.product-type.framework"; + }; + 7DFBED021CDB8C9500EE435B /* ReactiveSwift-tvOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7DFBED0F1CDB8C9500EE435B /* Build configuration list for PBXNativeTarget "ReactiveSwift-tvOSTests" */; + buildPhases = ( + 7DFBECFF1CDB8C9500EE435B /* Sources */, + 7DFBED001CDB8C9500EE435B /* Frameworks */, + 7DFBED011CDB8C9500EE435B /* Resources */, + 7DFBED151CDB8CEC00EE435B /* Copy Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 7DFBED0A1CDB8C9500EE435B /* PBXTargetDependency */, + ); + name = "ReactiveSwift-tvOSTests"; + productName = "ReactiveCocoa-tvOSTests"; + productReference = 7DFBED031CDB8C9500EE435B /* ReactiveSwiftTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + A9B315531B3940610001CB9C /* ReactiveSwift-watchOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = A9B3155D1B3940610001CB9C /* Build configuration list for PBXNativeTarget "ReactiveSwift-watchOS" */; + buildPhases = ( + A9B3154F1B3940610001CB9C /* Sources */, + A9B315501B3940610001CB9C /* Frameworks */, + A9B315511B3940610001CB9C /* Headers */, + A9B315521B3940610001CB9C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveSwift-watchOS"; + productName = ReactiveCocoa; + productReference = A9B315541B3940610001CB9C /* ReactiveSwift.framework */; + productType = "com.apple.product-type.framework"; + }; + D04725E919E49ED7006002AA /* ReactiveSwift-macOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = D047260019E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-macOS" */; + buildPhases = ( + D04725E519E49ED7006002AA /* Sources */, + D04725E619E49ED7006002AA /* Frameworks */, + D04725E719E49ED7006002AA /* Headers */, + D04725E819E49ED7006002AA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveSwift-macOS"; + productName = ReactiveCocoa; + productReference = D04725EA19E49ED7006002AA /* ReactiveSwift.framework */; + productType = "com.apple.product-type.framework"; + }; + D04725F419E49ED7006002AA /* ReactiveSwift-macOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D047260319E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-macOSTests" */; + buildPhases = ( + D04725F119E49ED7006002AA /* Sources */, + D04725F219E49ED7006002AA /* Frameworks */, + D04725F319E49ED7006002AA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D04725F819E49ED7006002AA /* PBXTargetDependency */, + ); + name = "ReactiveSwift-macOSTests"; + productName = ReactiveCocoaTests; + productReference = D04725F519E49ED7006002AA /* ReactiveSwiftTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + D047260B19E49F82006002AA /* ReactiveSwift-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = D047261F19E49F82006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-iOS" */; + buildPhases = ( + D047260719E49F82006002AA /* Sources */, + D047260819E49F82006002AA /* Frameworks */, + D047260919E49F82006002AA /* Headers */, + D047260A19E49F82006002AA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ReactiveSwift-iOS"; + productName = ReactiveCocoa; + productReference = D047260C19E49F82006002AA /* ReactiveSwift.framework */; + productType = "com.apple.product-type.framework"; + }; + D047261519E49F82006002AA /* ReactiveSwift-iOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D047262219E49F82006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-iOSTests" */; + buildPhases = ( + D047261219E49F82006002AA /* Sources */, + D047261319E49F82006002AA /* Frameworks */, + D047261419E49F82006002AA /* Resources */, + D01B7B6119EDD8F600D26E01 /* Copy Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D047261919E49F82006002AA /* PBXTargetDependency */, + ); + name = "ReactiveSwift-iOSTests"; + productName = ReactiveCocoaTests; + productReference = D047261619E49F82006002AA /* ReactiveSwiftTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D04725E119E49ED7006002AA /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = GitHub; + TargetAttributes = { + 57A4D1AF1BA13D7A00F7D4B1 = { + LastSwiftMigration = 0800; + }; + 7DFBED021CDB8C9500EE435B = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; + }; + A9B315531B3940610001CB9C = { + CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 0800; + }; + D04725E919E49ED7006002AA = { + CreatedOnToolsVersion = 6.1; + LastSwiftMigration = 0800; + }; + D04725F419E49ED7006002AA = { + CreatedOnToolsVersion = 6.1; + LastSwiftMigration = 0800; + }; + D047260B19E49F82006002AA = { + CreatedOnToolsVersion = 6.1; + LastSwiftMigration = 0800; + }; + D047261519E49F82006002AA = { + CreatedOnToolsVersion = 6.1; + LastSwiftMigration = 0800; + }; + }; + }; + buildConfigurationList = D04725E419E49ED7006002AA /* Build configuration list for PBXProject "ReactiveSwift" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D04725E019E49ED7006002AA; + productRefGroup = D04725EB19E49ED7006002AA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D04725E919E49ED7006002AA /* ReactiveSwift-macOS */, + D04725F419E49ED7006002AA /* ReactiveSwift-macOSTests */, + D047260B19E49F82006002AA /* ReactiveSwift-iOS */, + D047261519E49F82006002AA /* ReactiveSwift-iOSTests */, + A9B315531B3940610001CB9C /* ReactiveSwift-watchOS */, + 57A4D1AF1BA13D7A00F7D4B1 /* ReactiveSwift-tvOS */, + 7DFBED021CDB8C9500EE435B /* ReactiveSwift-tvOSTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 57A4D23B1BA13D7A00F7D4B1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7DFBED011CDB8C9500EE435B /* 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 = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047260A19E49F82006002AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047261419E49F82006002AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 57A4D1B01BA13D7A00F7D4B1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 57A4D1B11BA13D7A00F7D4B1 /* Optional.swift in Sources */, + 57A4D1B41BA13D7A00F7D4B1 /* Disposable.swift in Sources */, + 57A4D1B61BA13D7A00F7D4B1 /* Event.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 */, + D85C652D1C0E70E5005A77AD /* Flatten.swift in Sources */, + 9ABCB1881D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, + EBCC7DBF1BBF01E200A2AE92 /* Observer.swift in Sources */, + C79B64801CD52E4E003F2376 /* EventLogger.swift in Sources */, + 4A0E11021D2A92720065D310 /* Lifetime.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7DFBECFF1CDB8C9500EE435B /* Sources */ = { + 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 */, + 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 */, + 4A0E11061D2A95200065D310 /* LifetimeSpec.swift in Sources */, + 7DFBED6D1CDB8F7D00EE435B /* SignalProducerNimbleMatchers.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A9B3154F1B3940610001CB9C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A9F793341B60D0140026BCBA /* Optional.swift in Sources */, + A9B315BC1B3940810001CB9C /* Disposable.swift in Sources */, + A9B315BE1B3940810001CB9C /* Event.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 */, + D85C652C1C0E70E4005A77AD /* Flatten.swift in Sources */, + 9ABCB1871D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, + EBCC7DBE1BBF01E200A2AE92 /* Observer.swift in Sources */, + C79B647F1CD52E4D003F2376 /* EventLogger.swift in Sources */, + 4A0E11011D2A92720065D310 /* Lifetime.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D04725E519E49ED7006002AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D00004091A46864E000E7D41 /* TupleExtensions.swift in Sources */, + D871D69F1B3B29A40070F16C /* Optional.swift in Sources */, + D08C54B61A69A3DB00AD8286 /* Event.swift in Sources */, + D0C312D319EF2A5800984962 /* Disposable.swift in Sources */, + EBCC7DBC1BBF010C00A2AE92 /* Observer.swift in Sources */, + D03B4A3D19F4C39A009E02AC /* FoundationExtensions.swift in Sources */, + D08C54B31A69A2AE00AD8286 /* Signal.swift in Sources */, + D85C652A1C0D84C7005A77AD /* Flatten.swift in Sources */, + D0C312CF19EF2A5800984962 /* Bag.swift in Sources */, + 4A0E10FF1D2A92720065D310 /* Lifetime.swift in Sources */, + D0C312E719EF2A5800984962 /* Scheduler.swift in Sources */, + D0C312CD19EF2A5800984962 /* Atomic.swift in Sources */, + D08C54BA1A69C54300AD8286 /* Property.swift in Sources */, + D0D11AB91A6AE87700C1F8B1 /* Action.swift in Sources */, + C79B647C1CD52E23003F2376 /* EventLogger.swift in Sources */, + 9ABCB1851D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, + D08C54B81A69A9D000AD8286 /* SignalProducer.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D04725F119E49ED7006002AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0A2260E1A72F16D00D33B74 /* PropertySpec.swift in Sources */, + B696FB811A7640C00075236D /* TestError.swift in Sources */, + D021671D1A6CD50500987861 /* ActionSpec.swift in Sources */, + D0C3130E19EF2B1F00984962 /* SchedulerSpec.swift in Sources */, + BFA6B94D1A7604D400C846D1 /* SignalProducerNimbleMatchers.swift in Sources */, + D8170FC11B100EBC004192AD /* FoundationExtensionsSpec.swift in Sources */, + C79B64741CD38B2B003F2376 /* TestLogger.swift in Sources */, + CA6F28501C52626B001879D2 /* FlattenSpec.swift in Sources */, + 4A0E11041D2A95200065D310 /* LifetimeSpec.swift in Sources */, + CDCD247A1C277EEC00710AEE /* AtomicSpec.swift in Sources */, + 579504331BB8A34200A5E482 /* BagSpec.swift in Sources */, + D0A226081A72E0E900D33B74 /* SignalSpec.swift in Sources */, + 02D2602B1C1D6DB8003ACC61 /* SignalLifetimeSpec.swift in Sources */, + D0C3130C19EF2B1F00984962 /* DisposableSpec.swift in Sources */, + D0A2260B1A72E6C500D33B74 /* SignalProducerSpec.swift in Sources */, + D8024DB21B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047260719E49F82006002AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D08C54B41A69A2AF00AD8286 /* Signal.swift in Sources */, + D8E84A671B3B32FB00C3E831 /* Optional.swift in Sources */, + D0C312D419EF2A5800984962 /* Disposable.swift in Sources */, + D08C54B91A69A9D100AD8286 /* SignalProducer.swift in Sources */, + 9ABCB1861D2A5B5A00BCA243 /* Deprecations+Removals.swift in Sources */, + EBCC7DBD1BBF01E100A2AE92 /* Observer.swift in Sources */, + D85C652B1C0E70E3005A77AD /* Flatten.swift in Sources */, + 4A0E11001D2A92720065D310 /* Lifetime.swift in Sources */, + D08C54BB1A69C54400AD8286 /* Property.swift in Sources */, + D03B4A3E19F4C39A009E02AC /* FoundationExtensions.swift in Sources */, + D000040A1A46864E000E7D41 /* TupleExtensions.swift in Sources */, + D08C54B71A69A3DB00AD8286 /* Event.swift in Sources */, + C79B647D1CD52E4A003F2376 /* EventLogger.swift in Sources */, + D0C312CE19EF2A5800984962 /* Atomic.swift in Sources */, + D0C312E819EF2A5800984962 /* Scheduler.swift in Sources */, + D0C312D019EF2A5800984962 /* Bag.swift in Sources */, + D0D11ABA1A6AE87700C1F8B1 /* Action.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D047261219E49F82006002AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0A2260C1A72E6C500D33B74 /* SignalProducerSpec.swift in Sources */, + D0A2260F1A72F16D00D33B74 /* PropertySpec.swift in Sources */, + D0A226091A72E0E900D33B74 /* SignalSpec.swift in Sources */, + CDCD247B1C277EED00710AEE /* AtomicSpec.swift in Sources */, + D021671E1A6CD50500987861 /* ActionSpec.swift in Sources */, + D8024DB31B2E1BB0005E6B9A /* SignalProducerLiftingSpec.swift in Sources */, + BFA6B94E1A7604D500C846D1 /* SignalProducerNimbleMatchers.swift in Sources */, + B696FB821A7640C00075236D /* TestError.swift in Sources */, + D8170FC21B100EBC004192AD /* FoundationExtensionsSpec.swift in Sources */, + D0C3131419EF2B2000984962 /* SchedulerSpec.swift in Sources */, + C79B64751CD38B2B003F2376 /* TestLogger.swift in Sources */, + D0C3131219EF2B2000984962 /* DisposableSpec.swift in Sources */, + CA6F28511C52626B001879D2 /* FlattenSpec.swift in Sources */, + 579504341BB8A34300A5E482 /* BagSpec.swift in Sources */, + 4A0E11051D2A95200065D310 /* LifetimeSpec.swift in Sources */, + 02D2602A1C1D6DAF003ACC61 /* SignalLifetimeSpec.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 7DFBED0A1CDB8C9500EE435B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57A4D1AF1BA13D7A00F7D4B1 /* ReactiveSwift-tvOS */; + targetProxy = 7DFBED091CDB8C9500EE435B /* PBXContainerItemProxy */; + }; + D04725F819E49ED7006002AA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D04725E919E49ED7006002AA /* ReactiveSwift-macOS */; + targetProxy = D04725F719E49ED7006002AA /* PBXContainerItemProxy */; + }; + D047261919E49F82006002AA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D047260B19E49F82006002AA /* ReactiveSwift-iOS */; + targetProxy = D047261819E49F82006002AA /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 57A4D23D1BA13D7A00F7D4B1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Debug; + }; + 57A4D23E1BA13D7A00F7D4B1 /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Test; + }; + 57A4D23F1BA13D7A00F7D4B1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Release; + }; + 57A4D2401BA13D7A00F7D4B1 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2461BA13F9700F7D4B1 /* tvOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Profile; + }; + 7DFBED0B1CDB8C9500EE435B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Debug; + }; + 7DFBED0C1CDB8C9500EE435B /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Test; + }; + 7DFBED0D1CDB8C9500EE435B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Release; + }; + 7DFBED0E1CDB8C9500EE435B /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Profile; + }; + A9B315591B3940610001CB9C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Debug; + }; + A9B3155A1B3940610001CB9C /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Test; + }; + A9B3155B1B3940610001CB9C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Release; + }; + A9B3155C1B3940610001CB9C /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DTRACE_PROBES_DISABLED=1", + ); + INFOPLIST_FILE = ReactiveSwift/Info.plist; + }; + name = Profile; + }; + D04725FE19E49ED7006002AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047262919E49FE8006002AA /* Debug.xcconfig */; + buildSettings = { + BITCODE_GENERATION_MODE = bitcode; + 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; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(PROJECT_NAME)"; + SWIFT_VERSION = 3.0; + TVOS_DEPLOYMENT_TARGET = 9.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + D04725FF19E49ED7006002AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047262B19E49FE8006002AA /* Release.xcconfig */; + buildSettings = { + BITCODE_GENERATION_MODE = bitcode; + 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; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(PROJECT_NAME)"; + SWIFT_VERSION = 3.0; + TVOS_DEPLOYMENT_TARGET = 9.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; + D047260119E49ED7006002AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + }; + name = Debug; + }; + D047260219E49ED7006002AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + }; + name = Release; + }; + D047260419E49ED7006002AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Debug; + }; + D047260519E49ED7006002AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Release; + }; + D047262019E49F82006002AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + }; + name = Debug; + }; + D047262119E49F82006002AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + }; + name = Release; + }; + D047262319E49F82006002AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Debug; + }; + D047262419E49F82006002AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Release; + }; + D047263D19E4A008006002AA /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047262A19E49FE8006002AA /* Profile.xcconfig */; + buildSettings = { + BITCODE_GENERATION_MODE = bitcode; + CODE_SIGNING_REQUIRED = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(PROJECT_NAME)"; + SWIFT_VERSION = 3.0; + TVOS_DEPLOYMENT_TARGET = 9.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Profile; + }; + D047263E19E4A008006002AA /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + }; + name = Profile; + }; + D047263F19E4A008006002AA /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Profile; + }; + D047264019E4A008006002AA /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + }; + name = Profile; + }; + D047264119E4A008006002AA /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Profile; + }; + D047264219E4A00B006002AA /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047262C19E49FE8006002AA /* Test.xcconfig */; + buildSettings = { + BITCODE_GENERATION_MODE = bitcode; + CODE_SIGNING_REQUIRED = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)-Tests"; + PRODUCT_NAME = "$(PROJECT_NAME)"; + SWIFT_VERSION = 3.0; + TVOS_DEPLOYMENT_TARGET = 9.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Test; + }; + D047264319E4A00B006002AA /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263A19E49FE8006002AA /* Mac-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + }; + name = Test; + }; + D047264419E4A00B006002AA /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263719E49FE8006002AA /* Mac-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Test; + }; + D047264519E4A00B006002AA /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263419E49FE8006002AA /* iOS-Framework.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = ReactiveSwift/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + }; + name = Test; + }; + D047264619E4A00B006002AA /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D047263219E49FE8006002AA /* iOS-Application.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ReactiveSwiftTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(PROJECT_NAME)Tests"; + }; + name = Test; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 57A4D23C1BA13D7A00F7D4B1 /* Build configuration list for PBXNativeTarget "ReactiveSwift-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57A4D23D1BA13D7A00F7D4B1 /* Debug */, + 57A4D23E1BA13D7A00F7D4B1 /* Test */, + 57A4D23F1BA13D7A00F7D4B1 /* Release */, + 57A4D2401BA13D7A00F7D4B1 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7DFBED0F1CDB8C9500EE435B /* Build configuration list for PBXNativeTarget "ReactiveSwift-tvOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7DFBED0B1CDB8C9500EE435B /* Debug */, + 7DFBED0C1CDB8C9500EE435B /* Test */, + 7DFBED0D1CDB8C9500EE435B /* Release */, + 7DFBED0E1CDB8C9500EE435B /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A9B3155D1B3940610001CB9C /* Build configuration list for PBXNativeTarget "ReactiveSwift-watchOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A9B315591B3940610001CB9C /* Debug */, + A9B3155A1B3940610001CB9C /* Test */, + A9B3155B1B3940610001CB9C /* Release */, + A9B3155C1B3940610001CB9C /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D04725E419E49ED7006002AA /* Build configuration list for PBXProject "ReactiveSwift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D04725FE19E49ED7006002AA /* Debug */, + D047264219E4A00B006002AA /* Test */, + D04725FF19E49ED7006002AA /* Release */, + D047263D19E4A008006002AA /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D047260019E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-macOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D047260119E49ED7006002AA /* Debug */, + D047264319E4A00B006002AA /* Test */, + D047260219E49ED7006002AA /* Release */, + D047263E19E4A008006002AA /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D047260319E49ED7006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-macOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D047260419E49ED7006002AA /* Debug */, + D047264419E4A00B006002AA /* Test */, + D047260519E49ED7006002AA /* Release */, + D047263F19E4A008006002AA /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D047261F19E49F82006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D047262019E49F82006002AA /* Debug */, + D047264519E4A00B006002AA /* Test */, + D047262119E49F82006002AA /* Release */, + D047264019E4A008006002AA /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D047262219E49F82006002AA /* Build configuration list for PBXNativeTarget "ReactiveSwift-iOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D047262319E49F82006002AA /* Debug */, + D047264619E4A00B006002AA /* Test */, + D047262419E49F82006002AA /* Release */, + D047264119E4A008006002AA /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D04725E119E49ED7006002AA /* Project object */; +} diff --git a/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-iOS.xcscheme b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-iOS.xcscheme new file mode 100644 index 0000000000..749a8751b4 --- /dev/null +++ b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-iOS.xcscheme @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-macOS.xcscheme b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-macOS.xcscheme new file mode 100644 index 0000000000..c989ea24f3 --- /dev/null +++ b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-macOS.xcscheme @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-tvOS.xcscheme b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-tvOS.xcscheme new file mode 100644 index 0000000000..863741d9ed --- /dev/null +++ b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-tvOS.xcscheme @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-watchOS.xcscheme b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-watchOS.xcscheme new file mode 100644 index 0000000000..2bdc6d5ca2 --- /dev/null +++ b/ReactiveSwift.xcodeproj/xcshareddata/xcschemes/ReactiveSwift-watchOS.xcscheme @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ReactiveSwift/Action.swift b/ReactiveSwift/Action.swift new file mode 100644 index 0000000000..0ad813a89e --- /dev/null +++ b/ReactiveSwift/Action.swift @@ -0,0 +1,199 @@ +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.ReactiveSwift.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: (Input) -> SignalProducer) { + 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: (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/ReactiveSwift/Atomic.swift b/ReactiveSwift/Atomic.swift new file mode 100644 index 0000000000..db24e684e5 --- /dev/null +++ b/ReactiveSwift/Atomic.swift @@ -0,0 +1,169 @@ +// +// Atomic.swift +// ReactiveSwift +// +// 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: @noescape (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: @noescape (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(_:)) + 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: @noescape (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: @noescape (Value) throws -> Result) rethrows -> Result { + lock.lock() + defer { lock.unlock() } + + return try action(_value) + } +} + +public protocol AtomicProtocol: class { + associatedtype Value + + @discardableResult + func withValue(_ action: @noescape (Value) throws -> Result) rethrows -> Result + + @discardableResult + func modify(_ action: @noescape (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/ReactiveSwift/Bag.swift b/ReactiveSwift/Bag.swift new file mode 100644 index 0000000000..c7099938b7 --- /dev/null +++ b/ReactiveSwift/Bag.swift @@ -0,0 +1,110 @@ +// +// Bag.swift +// ReactiveSwift +// +// 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 { + private var identifier: UInt? + + private init(identifier: UInt) { + self.identifier = identifier + } +} + +/// An unordered, non-unique collection of values of type `Element`. +public struct Bag { + private 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/ReactiveSwift/Deprecations+Removals.swift b/ReactiveSwift/Deprecations+Removals.swift new file mode 100644 index 0000000000..53899b6245 --- /dev/null +++ b/ReactiveSwift/Deprecations+Removals.swift @@ -0,0 +1,255 @@ +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() } +} + +// 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/ReactiveSwift/Disposable.swift b/ReactiveSwift/Disposable.swift new file mode 100644 index 0000000000..9a7614b815 --- /dev/null +++ b/ReactiveSwift/Disposable.swift @@ -0,0 +1,353 @@ +// +// Disposable.swift +// ReactiveSwift +// +// 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: () -> 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? + + private static let empty = DisposableHandle() + + private init() { + self.bagToken = Atomic(nil) + } + + private 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) { + 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) { + 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: () -> 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: () -> ()) -> 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: () -> ()) -> CompositeDisposable.DisposableHandle { + return lhs.innerDisposable.add(rhs) +} diff --git a/ReactiveSwift/Event.swift b/ReactiveSwift/Event.swift new file mode 100644 index 0000000000..4547483161 --- /dev/null +++ b/ReactiveSwift/Event.swift @@ -0,0 +1,165 @@ +// +// Event.swift +// ReactiveSwift +// +// 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/ReactiveSwift/EventLogger.swift b/ReactiveSwift/EventLogger.swift new file mode 100644 index 0000000000..c6181b3479 --- /dev/null +++ b/ReactiveSwift/EventLogger.swift @@ -0,0 +1,132 @@ +// +// EventLogger.swift +// ReactiveSwift +// +// 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: identifier, event: event, fileName: fileName, functionName: functionName, lineNumber: 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: identifier, + event: event, + fileName: fileName, + functionName: functionName, + lineNumber: 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: (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/ReactiveSwift/Flatten.swift b/ReactiveSwift/Flatten.swift new file mode 100644 index 0000000000..804621338f --- /dev/null +++ b/ReactiveSwift/Flatten.swift @@ -0,0 +1,918 @@ +// +// Flatten.swift +// ReactiveSwift +// +// 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. + private func concat() -> Signal { + return Signal { relayObserver in + let disposable = CompositeDisposable() + let relayDisposable = CompositeDisposable() + + disposable += relayDisposable + disposable += self.observeConcat(relayObserver, relayDisposable) + + return disposable + } + } + + private 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. + private 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 { + 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. + private func merge() -> Signal { + return Signal { relayObserver in + let disposable = CompositeDisposable() + let relayDisposable = CompositeDisposable() + + disposable += relayDisposable + disposable += self.observeMerge(relayObserver, relayDisposable) + + return disposable + } + } + + private 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. + private 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 { + 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 { + 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 { + 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 { + 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. + private func switchToLatest() -> Signal { + return Signal { observer in + let composite = CompositeDisposable() + let serial = SerialDisposable() + + composite += serial + composite += self.observeSwitchToLatest(observer, serial) + + return composite + } + } + + private 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. + private 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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (Error) -> SignalProducer) -> Signal { + return Signal { observer in + self.observeFlatMapError(handler, observer, SerialDisposable()) + } + } + + private func observeFlatMapError(_ handler: (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: (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/ReactiveSwift/FoundationExtensions.swift b/ReactiveSwift/FoundationExtensions.swift new file mode 100644 index 0000000000..d5f0ef19da --- /dev/null +++ b/ReactiveSwift/FoundationExtensions.swift @@ -0,0 +1,80 @@ +// +// FoundationExtensions.swift +// ReactiveSwift +// +// 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.ReactiveSwift.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 ?? defaultSessionError) + } + } + + disposable += { + task.cancel() + } + task.resume() + } + } +} diff --git a/ReactiveSwift/Info.plist b/ReactiveSwift/Info.plist new file mode 100644 index 0000000000..9a11afdc43 --- /dev/null +++ b/ReactiveSwift/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2014 GitHub. All rights reserved. + NSPrincipalClass + + + diff --git a/ReactiveSwift/Lifetime.swift b/ReactiveSwift/Lifetime.swift new file mode 100644 index 0000000000..5ab129e6db --- /dev/null +++ b/ReactiveSwift/Lifetime.swift @@ -0,0 +1,50 @@ +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 { + /// A signal that sends a Completed event when the lifetime ends. + public let ended: Signal<(), NoError> + + /// 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 init(_ token: Token) { + 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. + private let ended: Signal<(), NoError> + + private let endedObserver: Signal<(), NoError>.Observer + + public init() { + (ended, endedObserver) = Signal.pipe() + } + + deinit { + endedObserver.sendCompleted() + } + } +} diff --git a/ReactiveSwift/Observer.swift b/ReactiveSwift/Observer.swift new file mode 100644 index 0000000000..938df641cb --- /dev/null +++ b/ReactiveSwift/Observer.swift @@ -0,0 +1,104 @@ +// +// Observer.swift +// ReactiveSwift +// +// 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 = (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/ReactiveSwift/Optional.swift b/ReactiveSwift/Optional.swift new file mode 100644 index 0000000000..c5446bd733 --- /dev/null +++ b/ReactiveSwift/Optional.swift @@ -0,0 +1,42 @@ +// +// Optional.swift +// ReactiveSwift +// +// 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/ReactiveSwift/Property.swift b/ReactiveSwift/Property.swift new file mode 100644 index 0000000000..0bf9a114f6 --- /dev/null +++ b/ReactiveSwift/Property.swift @@ -0,0 +1,878 @@ +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 { + /// The current value of the property. + var value: Value { get set } +} + +/// 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. + private func lift(_ transform: @noescape (SignalProducer) -> SignalProducer) -> Property { + return Property(self, transform: transform) + } + + /// Lifts a binary SignalProducer operator to operate upon PropertyProtocol instead. + private func lift(_ transform: @noescape (SignalProducer) -> (SignalProducer) -> SignalProducer) -> @noescape (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: (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: (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: (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: (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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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]>? { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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)> { + 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]>? { + 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 _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 = [] + _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) { + sources = Property.capture(property) + _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`. + private convenience init(_ property: P, transform: @noescape (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`. + private convenience init(_ firstProperty: P1, _ secondProperty: P2, transform: @noescape (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) + + // Verify that an initial is sent. This is friendlier than deadlocking + // in the event that one isn't. + var value: Value? = nil + let disposable = producer.start { event in + switch event { + case let .next(newValue): + value = newValue + + case .completed, .interrupted: + break + + case let .failed(error): + fatalError("Receive unexpected error from a producer of `NoError` type: \(error)") + } + } + guard value != nil else { + fatalError("A producer promised to send at least one value. Received none.") + } + disposable.dispose() + + self.sources = sources + _value = { producer.take(first: 1).single()!.value! } + _producer = { producer } + _signal = { + var extractedSignal: Signal! + producer.startWithSignal { signal, _ in extractedSignal = signal } + return extractedSignal + } + } + + /// 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 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) + } + } + + /// 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() + + /// 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.ReactiveSwift.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: @noescape (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: @noescape (Value) throws -> Result) rethrows -> Result { + return try atomic.withValue(action) + } + + deinit { + observer.sendCompleted() + } +} + +private class Box { + var value: Value + + init(_ value: Value) { + self.value = value + } +} + +infix operator <~ { + associativity right + + // Binds tighter than assignment but looser than everything else + precedence 93 +} + +/// Binds a signal to a property, updating the property's value to the latest +/// value sent by the signal. +/// +/// - note: The binding will automatically terminate when the property 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: +/// - property: A property to bind to. +/// - signal: A signal to bind. +/// +/// - returns: A disposable that can be used to terminate binding before the +/// deinitialization of property or signal's `completed` event. +@discardableResult +public func <~ (property: P, signal: Signal) -> Disposable { + let disposable = CompositeDisposable() + disposable += property.producer.startWithCompleted { + disposable.dispose() + } + + disposable += signal.observe { [weak property] event in + switch event { + case let .next(value): + property?.value = value + case .completed: + disposable.dispose() + case .failed, .interrupted: + break + } + } + + return disposable +} + +/// Creates a signal from the given producer, which will be immediately bound to +/// the given property, updating the property's value to the latest value sent +/// by the signal. +/// +/// ```` +/// 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() +/// ```` +/// +/// - note: The binding will automatically terminate when the property is +/// deinitialized, or when the created producer sends a `completed` +/// event. +/// +/// - parameters: +/// - property: A property to bind to. +/// - producer: A producer to bind. +/// +/// - returns: A disposable that can be used to terminate binding before the +/// deinitialization of property or producer's `completed` event. +@discardableResult +public func <~ (property: P, producer: SignalProducer) -> Disposable { + let disposable = CompositeDisposable() + + producer + .on(completed: { disposable.dispose() }) + .startWithSignal { signal, signalDisposable in + disposable += property <~ signal + disposable += signalDisposable + + disposable += property.producer.startWithCompleted { + disposable.dispose() + } + } + + return disposable +} + +/// Binds a signal to a property, updating the property's value to the latest +/// value sent by the signal. +/// +/// - note: The binding will automatically terminate when the property 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: +/// - property: A property to bind to. +/// - signal: A signal to bind. +/// +/// - returns: A disposable that can be used to terminate binding before the +/// deinitialization of property or signal's `completed` event. +@discardableResult +public func <~ (property: P, signal: S) -> Disposable { + return property <~ signal.optionalize() +} + +/// Creates a signal from the given producer, which will be immediately bound to +/// the given property, updating the property's value to the latest value sent +/// by the signal. +/// +/// ```` +/// 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() +/// ```` +/// +/// - note: The binding will automatically terminate when the property is +/// deinitialized, or when the created producer sends a `completed` +/// event. +/// +/// - parameters: +/// - property: A property to bind to. +/// - producer: A producer to bind. +/// +/// - returns: A disposable that can be used to terminate binding before the +/// deinitialization of property or producer's `completed` event. +@discardableResult +public func <~ (property: P, producer: S) -> Disposable { + return property <~ producer.optionalize() +} + +/// Binds `destinationProperty` to the latest values of `sourceProperty`. +/// +/// ```` +/// 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: +/// - destinationProperty: A property to bind to. +/// - sourceProperty: A property to bind. +/// +/// - returns: A disposable that can be used to terminate binding before the +/// deinitialization of destination property or source property +/// producer's `completed` event. +@discardableResult +public func <~ (destinationProperty: Destination, sourceProperty: Source) -> Disposable { + return destinationProperty <~ sourceProperty.producer +} + +/// Binds `destinationProperty` to the latest values of `sourceProperty`. +/// +/// ```` +/// 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: +/// - destinationProperty: A property to bind to. +/// - sourceProperty: A property to bind. +/// +/// - returns: A disposable that can be used to terminate binding before the +/// deinitialization of destination property or source property +/// producer's `completed` event. +@discardableResult +public func <~ (destinationProperty: Destination, sourceProperty: Source) -> Disposable { + return destinationProperty <~ sourceProperty.producer +} diff --git a/ReactiveSwift/ReactiveSwift.h b/ReactiveSwift/ReactiveSwift.h new file mode 100644 index 0000000000..dde845ccc0 --- /dev/null +++ b/ReactiveSwift/ReactiveSwift.h @@ -0,0 +1,15 @@ +// +// ReactiveSwift.h +// ReactiveSwift +// +// Created by Matt Diephouse on 8/15/16. +// Copyright (c) 2016 the ReactiveSwift contributors. All rights reserved. +// + +#import + +//! Project version number for ReactiveSwift. +FOUNDATION_EXPORT double ReactiveSwiftVersionNumber; + +//! Project version string for ReactiveSwift. +FOUNDATION_EXPORT const unsigned char ReactiveSwiftVersionString[]; diff --git a/ReactiveSwift/Scheduler.swift b/ReactiveSwift/Scheduler.swift new file mode 100644 index 0000000000..daa3cb429c --- /dev/null +++ b/ReactiveSwift/Scheduler.swift @@ -0,0 +1,493 @@ +// +// Scheduler.swift +// ReactiveSwift +// +// 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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.ReactiveSwift.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.ReactiveSwift.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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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.ReactiveSwift.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: () -> 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: () -> Void) -> Disposable? { + return schedule(after: currentDate.addingTimeInterval(delay), action: action) + } + + @discardableResult + public func schedule(after date: Date, action: () -> 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: () -> 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: () -> 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: () -> 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/ReactiveSwift/Signal.swift b/ReactiveSwift/Signal.swift new file mode 100644 index 0000000000..6fef3d0525 --- /dev/null +++ b/ReactiveSwift/Signal.swift @@ -0,0 +1,1838 @@ +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 = ReactiveSwift.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: @noescape (Observer) -> Disposable?) { + state = Atomic(SignalState()) + + /// Used to ensure that events are serialized during delivery to observers. + let sendLock = NSLock() + sendLock.name = "org.reactivecocoa.ReactiveSwift.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: (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: () -> 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: (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: () -> 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: (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: (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: (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: (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: (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(values: 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: (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(values: state.values, next: 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.ReactiveSwift.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: (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. + 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: (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: (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: (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: (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: (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: (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: (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> { + 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> { + 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/ReactiveSwift/SignalProducer.swift b/ReactiveSwift/SignalProducer.swift new file mode 100644 index 0000000000..a3030992f6 --- /dev/null +++ b/ReactiveSwift/SignalProducer.swift @@ -0,0 +1,1907 @@ +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) { + 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: (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) { + 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: () -> 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: @noescape (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: signal, interrupter: 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: (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: @noescape (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 = Signal.Observer()) -> 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: (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: () -> 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: (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: () -> 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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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) } + } +} + +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: (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> { + 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> { + 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 + } + } + } + } + } + + /// 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/ReactiveSwift/TupleExtensions.swift b/ReactiveSwift/TupleExtensions.swift new file mode 100644 index 0000000000..f976369328 --- /dev/null +++ b/ReactiveSwift/TupleExtensions.swift @@ -0,0 +1,42 @@ +// +// TupleExtensions.swift +// ReactiveSwift +// +// 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/ReactiveSwiftTests/ActionSpec.swift b/ReactiveSwiftTests/ActionSpec.swift new file mode 100755 index 0000000000..7a56cd5501 --- /dev/null +++ b/ReactiveSwiftTests/ActionSpec.swift @@ -0,0 +1,142 @@ +// +// ActionSpec.swift +// ReactiveSwift +// +// Created by Justin Spahr-Summers on 2014-12-11. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +import Result +import Nimble +import Quick +import ReactiveSwift + +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/ReactiveSwiftTests/AtomicSpec.swift b/ReactiveSwiftTests/AtomicSpec.swift new file mode 100644 index 0000000000..8234f5b9f4 --- /dev/null +++ b/ReactiveSwiftTests/AtomicSpec.swift @@ -0,0 +1,44 @@ +// +// AtomicSpec.swift +// ReactiveSwift +// +// Created by Justin Spahr-Summers on 2014-07-13. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +import Nimble +import Quick +import ReactiveSwift + +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/ReactiveSwiftTests/BagSpec.swift b/ReactiveSwiftTests/BagSpec.swift new file mode 100644 index 0000000000..706e781a07 --- /dev/null +++ b/ReactiveSwiftTests/BagSpec.swift @@ -0,0 +1,54 @@ +// +// BagSpec.swift +// ReactiveSwift +// +// Created by Justin Spahr-Summers on 2014-07-13. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +import Nimble +import Quick +import ReactiveSwift + +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/ReactiveSwiftTests/DisposableSpec.swift b/ReactiveSwiftTests/DisposableSpec.swift new file mode 100644 index 0000000000..b8ac11cf9c --- /dev/null +++ b/ReactiveSwiftTests/DisposableSpec.swift @@ -0,0 +1,143 @@ +// +// DisposableSpec.swift +// ReactiveSwift +// +// Created by Justin Spahr-Summers on 2014-07-13. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +import Nimble +import Quick +import ReactiveSwift + +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/ReactiveSwiftTests/FlattenSpec.swift b/ReactiveSwiftTests/FlattenSpec.swift new file mode 100644 index 0000000000..bf19fdfa2f --- /dev/null +++ b/ReactiveSwiftTests/FlattenSpec.swift @@ -0,0 +1,963 @@ +// +// FlattenSpec.swift +// ReactiveSwift +// +// Created by Oleg Shnitko on 1/22/16. +// Copyright © 2016 GitHub. All rights reserved. +// + +import Result +import Nimble +import Quick +import ReactiveSwift + +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/ReactiveSwiftTests/FoundationExtensionsSpec.swift b/ReactiveSwiftTests/FoundationExtensionsSpec.swift new file mode 100644 index 0000000000..249eb44a8f --- /dev/null +++ b/ReactiveSwiftTests/FoundationExtensionsSpec.swift @@ -0,0 +1,59 @@ +// +// FoundationExtensionsSpec.swift +// ReactiveSwift +// +// Created by Neil Pankey on 5/22/15. +// Copyright (c) 2015 GitHub. All rights reserved. +// + +import Result +import Nimble +import Quick +import ReactiveSwift + +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/ReactiveSwiftTests/Info.plist b/ReactiveSwiftTests/Info.plist new file mode 100644 index 0000000000..ba72822e87 --- /dev/null +++ b/ReactiveSwiftTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/ReactiveSwiftTests/LifetimeSpec.swift b/ReactiveSwiftTests/LifetimeSpec.swift new file mode 100644 index 0000000000..4a23a9c259 --- /dev/null +++ b/ReactiveSwiftTests/LifetimeSpec.swift @@ -0,0 +1,83 @@ +import Quick +import Nimble +import ReactiveSwift +import Result + +final class LifetimeSpec: QuickSpec { + override func spec() { + describe("SignalProducerProtocol.takeDuring") { + 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] + } + } + + describe("NSObject") { + 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 + } + } + } +} + +private final class MutableReference { + var value: Value? + init(_ value: Value?) { + self.value = value + } +} + +private final class TestObject { + private let token = Lifetime.Token() + var lifetime: Lifetime { return Lifetime(token) } +} diff --git a/ReactiveSwiftTests/PropertySpec.swift b/ReactiveSwiftTests/PropertySpec.swift new file mode 100644 index 0000000000..e32c9126a6 --- /dev/null +++ b/ReactiveSwiftTests/PropertySpec.swift @@ -0,0 +1,1560 @@ +// +// PropertySpec.swift +// ReactiveSwift +// +// Created by Justin Spahr-Summers on 2015-01-23. +// Copyright (c) 2015 GitHub. All rights reserved. +// + +import Result +import Nimble +import Quick +import ReactiveSwift + +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 + } + } + + describe("Property") { + describe("from a value") { + 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("from a PropertyProtocol") { + it("should pass through behaviors of the input 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") { + 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 + } + + describe("signal lifetime and producer lifetime") { + 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 + } + } + } + + describe("Property.capture") { + 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("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 bound signal is completed") { + let (signal, observer) = Signal.pipe() + + let mutableProperty = MutableProperty(initialPropertyValue) + + let bindingDisposable = mutableProperty <~ signal + + expect(bindingDisposable.isDisposed) == false + observer.sendCompleted() + expect(bindingDisposable.isDisposed) == true + } + + it("should tear down the binding when the property deallocates") { + let (signal, _) = Signal.pipe() + + var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) + + let bindingDisposable = mutableProperty! <~ signal + + mutableProperty = nil + expect(bindingDisposable.isDisposed) == true + } + } + + 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 bound signal is completed") { + let (signalProducer, observer) = SignalProducer.pipe() + + let mutableProperty = MutableProperty(initialPropertyValue) + let disposable = mutableProperty <~ signalProducer + + observer.sendCompleted() + + expect(disposable.isDisposed) == true + } + + it("should tear down the binding when the property deallocates") { + let signalValues = [initialPropertyValue, subsequentPropertyValue] + let signalProducer = SignalProducer(values: signalValues) + + var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) + let disposable = mutableProperty! <~ signalProducer + + mutableProperty = nil + expect(disposable.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("") + + let bindingDisposable = destinationProperty! <~ sourceProperty.producer + destinationProperty = nil + + expect(bindingDisposable.isDisposed) == true + } + } + } + } +} + +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/ReactiveSwiftTests/SchedulerSpec.swift b/ReactiveSwiftTests/SchedulerSpec.swift new file mode 100644 index 0000000000..9daba6d9f0 --- /dev/null +++ b/ReactiveSwiftTests/SchedulerSpec.swift @@ -0,0 +1,298 @@ +// +// SchedulerSpec.swift +// ReactiveSwift +// +// Created by Justin Spahr-Summers on 2014-07-13. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +import Foundation +import Nimble +import Quick +@testable +import ReactiveSwift + +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: () -> 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/ReactiveSwiftTests/SignalLifetimeSpec.swift b/ReactiveSwiftTests/SignalLifetimeSpec.swift new file mode 100644 index 0000000000..a1ea79ea5d --- /dev/null +++ b/ReactiveSwiftTests/SignalLifetimeSpec.swift @@ -0,0 +1,414 @@ +// +// SignalLifetimeSpec.swift +// ReactiveSwift +// +// Created by Vadim Yelagin on 2015-12-13. +// Copyright (c) 2015 GitHub. All rights reserved. +// + +import Result +import Nimble +import Quick +import ReactiveSwift + +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/ReactiveSwiftTests/SignalProducerLiftingSpec.swift b/ReactiveSwiftTests/SignalProducerLiftingSpec.swift new file mode 100644 index 0000000000..17637ea83e --- /dev/null +++ b/ReactiveSwiftTests/SignalProducerLiftingSpec.swift @@ -0,0 +1,1536 @@ +// +// SignalProducerLiftingSpec.swift +// ReactiveSwift +// +// Created by Neil Pankey on 6/14/15. +// Copyright © 2015 GitHub. All rights reserved. +// + +import Result +import Nimble +import Quick +import ReactiveSwift + +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) == expectation + } else { + expect(observedValues) == expectation + } + } + + observer.sendCompleted() + + expectation.append([7]) + expect(observedValues) == expectation + } + + 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) == [] + } + + 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) == [] + } + + 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: () -> 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/ReactiveSwiftTests/SignalProducerNimbleMatchers.swift b/ReactiveSwiftTests/SignalProducerNimbleMatchers.swift new file mode 100644 index 0000000000..bc9654a3e4 --- /dev/null +++ b/ReactiveSwiftTests/SignalProducerNimbleMatchers.swift @@ -0,0 +1,57 @@ +// +// 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) -> 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/ReactiveSwiftTests/SignalProducerSpec.swift b/ReactiveSwiftTests/SignalProducerSpec.swift new file mode 100644 index 0000000000..1435d12f23 --- /dev/null +++ b/ReactiveSwiftTests/SignalProducerSpec.swift @@ -0,0 +1,2257 @@ +// +// SignalProducerSpec.swift +// ReactiveSwift +// +// 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 ReactiveSwift + +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) == [[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) == [[1, 2]] + } + } + + 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) == [[1, 3], [2, 4]] + } + + 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) == [[1, 2]] + } + } + + 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: [NSDate] = [] + 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: NSDate? + 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 + } + } + + 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(be(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(be(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: () -> 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) + } + } + } + } +} + +// 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. + private static func attemptWithResults, C.IndexDistance == C.Index, C.Index == Int>(_ results: C) -> SignalProducer { + 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/ReactiveSwiftTests/SignalSpec.swift b/ReactiveSwiftTests/SignalSpec.swift new file mode 100755 index 0000000000..39f70a2c7d --- /dev/null +++ b/ReactiveSwiftTests/SignalSpec.swift @@ -0,0 +1,2269 @@ +// +// SignalSpec.swift +// ReactiveSwift +// +// Created by Justin Spahr-Summers on 2015-01-23. +// Copyright (c) 2015 GitHub. All rights reserved. +// + +import Result +import Nimble +import Quick +import ReactiveSwift + +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) == expectation + } else { + expect(observedValues) == expectation + } + } + + observer.sendCompleted() + + expectation.append([7]) + expect(observedValues) == expectation + } + + 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) == [] + } + + 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) == [] + } + + 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/ReactiveSwiftTests/TestError.swift b/ReactiveSwiftTests/TestError.swift new file mode 100644 index 0000000000..39125cdfed --- /dev/null +++ b/ReactiveSwiftTests/TestError.swift @@ -0,0 +1,43 @@ +// +// TestError.swift +// ReactiveSwift +// +// Created by Almas Sapargali on 1/26/15. +// Copyright (c) 2015 GitHub. All rights reserved. +// + +import ReactiveSwift +import Result + +enum TestError: Int { + case `default` = 0 + case error1 = 1 + case error2 = 2 +} + +extension TestError: Error { +} + + +internal extension SignalProducerProtocol { + /// 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() } + } +} + +internal extension SignalProtocol { + /// 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 + fatalError("Unexpected error: \(error)") + + () + } + } +} + diff --git a/ReactiveSwiftTests/TestLogger.swift b/ReactiveSwiftTests/TestLogger.swift new file mode 100644 index 0000000000..85a769070a --- /dev/null +++ b/ReactiveSwiftTests/TestLogger.swift @@ -0,0 +1,25 @@ +// +// TestLogger.swift +// ReactiveSwift +// +// Created by Rui Peres on 29/04/2016. +// Copyright © 2016 GitHub. All rights reserved. +// + +import Foundation +@testable import ReactiveSwift + +final class TestLogger { + private 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)") + } +} From 073031ed0c2f5260a4139250060dfe7580acb9db Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 16 Aug 2016 17:27:47 -0400 Subject: [PATCH 02/20] Use ReactiveSwift in ReactiveCocoa --- ReactiveCocoa.xcodeproj/project.pbxproj | 329 +-- .../xcschemes/ReactiveCocoa-Mac.xcscheme | 14 + .../xcschemes/ReactiveCocoa-iOS.xcscheme | 14 + .../xcschemes/ReactiveCocoa-tvOS.xcscheme | 14 + .../contents.xcworkspacedata | 3 + ReactiveCocoa/Swift/Action.swift | 199 -- ReactiveCocoa/Swift/Atomic.swift | 169 -- ReactiveCocoa/Swift/Bag.swift | 110 - ReactiveCocoa/Swift/CocoaAction.swift | 1 + .../Swift/Deprecations+Removals.swift | 263 -- ReactiveCocoa/Swift/Disposable.swift | 353 --- ReactiveCocoa/Swift/DynamicProperty.swift | 1 + ReactiveCocoa/Swift/Event.swift | 165 -- ReactiveCocoa/Swift/EventLogger.swift | 132 - ReactiveCocoa/Swift/Flatten.swift | 918 ------- .../Swift/FoundationExtensions.swift | 80 - ReactiveCocoa/Swift/Lifetime.swift | 49 +- .../Swift/NSObject+KeyValueObserving.swift | 1 + ReactiveCocoa/Swift/ObjectiveCBridging.swift | 16 + ReactiveCocoa/Swift/Observer.swift | 104 - ReactiveCocoa/Swift/Optional.swift | 42 - ReactiveCocoa/Swift/Property.swift | 878 ------- ReactiveCocoa/Swift/Scheduler.swift | 493 ---- ReactiveCocoa/Swift/Signal.swift | 1838 ------------- ReactiveCocoa/Swift/SignalProducer.swift | 1907 -------------- ReactiveCocoa/Swift/TupleExtensions.swift | 42 - ReactiveCocoaTests/Swift/ActionSpec.swift | 142 -- ReactiveCocoaTests/Swift/AtomicSpec.swift | 44 - ReactiveCocoaTests/Swift/BagSpec.swift | 54 - .../Swift/CocoaActionSpec.swift | 1 + ReactiveCocoaTests/Swift/DisposableSpec.swift | 143 -- .../Swift/DynamicPropertySpec.swift | 233 ++ ReactiveCocoaTests/Swift/FlattenSpec.swift | 963 ------- .../Swift/FoundationExtensionsSpec.swift | 59 - .../Swift/KeyValueObservingSpec.swift | 1 + ReactiveCocoaTests/Swift/LifetimeSpec.swift | 83 - .../Swift/ObjectiveCBridgingSpec.swift | 1 + ReactiveCocoaTests/Swift/PropertySpec.swift | 1754 ------------- ReactiveCocoaTests/Swift/SchedulerSpec.swift | 298 --- .../Swift/SignalLifetimeSpec.swift | 414 --- .../Swift/SignalProducerLiftingSpec.swift | 1536 ----------- .../Swift/SignalProducerNimbleMatchers.swift | 1 + .../Swift/SignalProducerSpec.swift | 2257 ---------------- ReactiveCocoaTests/Swift/SignalSpec.swift | 2269 ----------------- ReactiveCocoaTests/Swift/TestError.swift | 1 + ReactiveCocoaTests/Swift/TestLogger.swift | 25 - ReactiveSwift/Scheduler.swift | 2 +- 47 files changed, 350 insertions(+), 18066 deletions(-) delete mode 100644 ReactiveCocoa/Swift/Action.swift delete mode 100644 ReactiveCocoa/Swift/Atomic.swift delete mode 100644 ReactiveCocoa/Swift/Bag.swift delete mode 100644 ReactiveCocoa/Swift/Deprecations+Removals.swift delete mode 100644 ReactiveCocoa/Swift/Disposable.swift delete mode 100644 ReactiveCocoa/Swift/Event.swift delete mode 100644 ReactiveCocoa/Swift/EventLogger.swift delete mode 100644 ReactiveCocoa/Swift/Flatten.swift delete mode 100644 ReactiveCocoa/Swift/FoundationExtensions.swift delete mode 100644 ReactiveCocoa/Swift/Observer.swift delete mode 100644 ReactiveCocoa/Swift/Optional.swift delete mode 100644 ReactiveCocoa/Swift/Property.swift delete mode 100644 ReactiveCocoa/Swift/Scheduler.swift delete mode 100644 ReactiveCocoa/Swift/Signal.swift delete mode 100644 ReactiveCocoa/Swift/SignalProducer.swift delete mode 100644 ReactiveCocoa/Swift/TupleExtensions.swift delete mode 100755 ReactiveCocoaTests/Swift/ActionSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/AtomicSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/BagSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/DisposableSpec.swift create mode 100644 ReactiveCocoaTests/Swift/DynamicPropertySpec.swift delete mode 100644 ReactiveCocoaTests/Swift/FlattenSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/FoundationExtensionsSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/LifetimeSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/PropertySpec.swift delete mode 100644 ReactiveCocoaTests/Swift/SchedulerSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/SignalLifetimeSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/SignalProducerLiftingSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/SignalProducerSpec.swift delete mode 100755 ReactiveCocoaTests/Swift/SignalSpec.swift delete mode 100644 ReactiveCocoaTests/Swift/TestLogger.swift diff --git a/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoa.xcodeproj/project.pbxproj index a4cf19ff5f..132c1005d8 100644 --- a/ReactiveCocoa.xcodeproj/project.pbxproj +++ b/ReactiveCocoa.xcodeproj/project.pbxproj @@ -7,34 +7,15 @@ 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 */; }; @@ -179,21 +160,8 @@ 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 */; }; + 7DFBED281CDB8DE300EE435B /* DynamicPropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.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 */; }; @@ -240,10 +208,6 @@ 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, ); }; }; - 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 */; }; 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 */; }; @@ -322,18 +286,7 @@ 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 */; }; 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, ); }; }; @@ -384,27 +337,26 @@ 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 */; }; B696FB811A7640C00075236D /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; B696FB821A7640C00075236D /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696FB801A7640C00075236D /* TestError.swift */; }; + BE330A0F1D634F1E00806963 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE330A0E1D634F1E00806963 /* ReactiveSwift.framework */; }; + BE330A111D634F2900806963 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE330A101D634F2900806963 /* ReactiveSwift.framework */; }; + BE330A131D634F2E00806963 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE330A121D634F2E00806963 /* ReactiveSwift.framework */; }; + BE330A151D634F4000806963 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE330A141D634F4000806963 /* ReactiveSwift.framework */; }; + BE330A171D634F4E00806963 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE330A161D634F4E00806963 /* ReactiveSwift.framework */; }; + BE330A191D634F5900806963 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE330A181D634F5900806963 /* ReactiveSwift.framework */; }; + BE330A1B1D634F5F00806963 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE330A1A1D634F5F00806963 /* ReactiveSwift.framework */; }; 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 */; }; 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 */; }; 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 */; }; @@ -416,17 +368,11 @@ 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 */; }; @@ -790,43 +736,17 @@ 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 */; }; + D0A2260E1A72F16D00D33B74 /* DynamicPropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.swift */; }; + D0A2260F1A72F16D00D33B74 /* DynamicPropertySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.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 */; }; @@ -834,22 +754,6 @@ 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 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -908,11 +812,9 @@ /* 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 = ""; }; 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 = ""; }; @@ -922,7 +824,6 @@ 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 = ""; }; 7DFBED031CDB8C9500EE435B /* ReactiveCocoaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveCocoaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 9ABCB1841D2A5B5A00BCA243 /* Deprecations+Removals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deprecations+Removals.swift"; sourceTree = ""; }; 9A1E72B91D4DE96500CC20C3 /* KeyValueObservingSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyValueObservingSpec.swift; sourceTree = ""; }; 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+KeyValueObserving.swift"; sourceTree = ""; }; A97451331B3A935E00F48E55 /* watchOS-Application.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Application.xcconfig"; sourceTree = ""; }; @@ -931,16 +832,18 @@ 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; }; B696FB801A7640C00075236D /* TestError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = ""; }; + BE330A0E1D634F1E00806963 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = build/Debug/ReactiveSwift.framework; sourceTree = ""; }; + BE330A101D634F2900806963 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = build/Debug/ReactiveSwift.framework; sourceTree = ""; }; + BE330A121D634F2E00806963 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = "build/Debug-iphoneos/ReactiveSwift.framework"; sourceTree = ""; }; + BE330A141D634F4000806963 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = "build/Debug-iphoneos/ReactiveSwift.framework"; sourceTree = ""; }; + BE330A161D634F4E00806963 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = "build/Debug-watchos/ReactiveSwift.framework"; sourceTree = ""; }; + BE330A181D634F5900806963 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = "build/Debug-appletvos/ReactiveSwift.framework"; sourceTree = ""; }; + BE330A1A1D634F5F00806963 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = "build/Debug-appletvos/ReactiveSwift.framework"; 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 = ""; }; CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicProperty.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 = ""; }; @@ -1186,7 +1089,6 @@ 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 = ""; }; @@ -1213,24 +1115,9 @@ D047263B19E49FE8006002AA /* Mac-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-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; }; + D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DynamicPropertySpec.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 = ""; }; @@ -1238,11 +1125,6 @@ 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; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1250,6 +1132,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BE330A191D634F5900806963 /* ReactiveSwift.framework in Frameworks */, 57A4D2081BA13D7A00F7D4B1 /* Result.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1258,6 +1141,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BE330A1B1D634F5F00806963 /* ReactiveSwift.framework in Frameworks */, CDF066CA1CDC1CA200199626 /* Nimble.framework in Frameworks */, CDF066CB1CDC1CA200199626 /* Quick.framework in Frameworks */, 7DFBED081CDB8C9500EE435B /* ReactiveCocoa.framework in Frameworks */, @@ -1268,6 +1152,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BE330A171D634F4E00806963 /* ReactiveSwift.framework in Frameworks */, A9B315C91B3940980001CB9C /* Result.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1276,6 +1161,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BE330A0F1D634F1E00806963 /* ReactiveSwift.framework in Frameworks */, CDC42E2F1AE7AB8B00965373 /* Result.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1284,6 +1170,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BE330A111D634F2900806963 /* ReactiveSwift.framework in Frameworks */, CDC42E301AE7AB8B00965373 /* Result.framework in Frameworks */, D05E662519EDD82000904ACA /* Nimble.framework in Frameworks */, D037672D19EDA75D00A782A9 /* Quick.framework in Frameworks */, @@ -1295,6 +1182,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BE330A131D634F2E00806963 /* ReactiveSwift.framework in Frameworks */, CDC42E311AE7AB8B00965373 /* Result.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1303,6 +1191,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BE330A151D634F4000806963 /* ReactiveSwift.framework in Frameworks */, D05E662619EDD83000904ACA /* Nimble.framework in Frameworks */, D037672F19EDA78B00A782A9 /* Quick.framework in Frameworks */, D047261719E49F82006002AA /* ReactiveCocoa.framework in Frameworks */, @@ -1334,6 +1223,20 @@ path = watchOS; sourceTree = ""; }; + BE330A0D1D634F1E00806963 /* Frameworks */ = { + isa = PBXGroup; + children = ( + BE330A1A1D634F5F00806963 /* ReactiveSwift.framework */, + BE330A181D634F5900806963 /* ReactiveSwift.framework */, + BE330A161D634F4E00806963 /* ReactiveSwift.framework */, + BE330A141D634F4000806963 /* ReactiveSwift.framework */, + BE330A121D634F2E00806963 /* ReactiveSwift.framework */, + BE330A101D634F2900806963 /* ReactiveSwift.framework */, + BE330A0E1D634F1E00806963 /* ReactiveSwift.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; D037642919EDA3B600A782A9 /* Objective-C */ = { isa = PBXGroup; children = ( @@ -1614,30 +1517,16 @@ D03B4A3919F4C25F009E02AC /* Signals */ = { 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 */, ); name = Signals; sourceTree = ""; }; - D03B4A3A19F4C26D009E02AC /* Internal Utilities */ = { - isa = PBXGroup; - children = ( - D00004081A46864E000E7D41 /* TupleExtensions.swift */, - ); - name = "Internal Utilities"; - sourceTree = ""; - }; D03B4A3B19F4C281009E02AC /* Extensions */ = { isa = PBXGroup; children = ( - D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */, 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */, ); name = Extensions; @@ -1650,6 +1539,7 @@ D04725F919E49ED7006002AA /* ReactiveCocoaTests */, D047262519E49FE8006002AA /* Configuration */, D04725EB19E49ED7006002AA /* Products */, + BE330A0D1D634F1E00806963 /* Frameworks */, ); sourceTree = ""; usesTabs = 1; @@ -1781,19 +1671,9 @@ D0C312B919EF2A3000984962 /* Swift */ = { 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; sourceTree = ""; @@ -1801,23 +1681,10 @@ D0C312ED19EF2A6F00984962 /* Swift */ = { 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 */, + D0A2260D1A72F16D00D33B74 /* DynamicPropertySpec.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 */, ); path = Swift; @@ -2338,29 +2205,16 @@ 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 */, @@ -2374,7 +2228,6 @@ 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 */, @@ -2393,12 +2246,10 @@ 57A4D1DD1BA13D7A00F7D4B1 /* RACDelegateProxy.m in Sources */, 57A4D1DE1BA13D7A00F7D4B1 /* RACDisposable.m 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 */, @@ -2446,21 +2297,8 @@ 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 */, + 7DFBED281CDB8DE300EE435B /* DynamicPropertySpec.swift in Sources */, 7DFBED321CDB8DE300EE435B /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */, 7DFBED331CDB8DE300EE435B /* NSNotificationCenterRACSupportSpec.m in Sources */, 7DFBED351CDB8DE300EE435B /* NSObjectRACDeallocatingSpec.m in Sources */, @@ -2469,7 +2307,6 @@ 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 */, @@ -2507,6 +2344,7 @@ 7DFBED671CDB8DE300EE435B /* RACTestExampleScheduler.m in Sources */, 7DFBED691CDB8DE300EE435B /* RACTestObject.m in Sources */, 7DFBED6A1CDB8DE300EE435B /* RACTestSchedulerSpec.m in Sources */, + BEE020661D637B0000DF261F /* TestError.swift in Sources */, 7DFBED6C1CDB8DE300EE435B /* RACTestUIButton.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2515,32 +2353,18 @@ 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 */, @@ -2561,8 +2385,6 @@ 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 */, @@ -2605,7 +2427,6 @@ A9B315A61B3940750001CB9C /* RACUnit.m in Sources */, A9B315A71B3940750001CB9C /* RACValueTransformer.m in Sources */, 9AD0F06C1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */, - A9B315BB1B3940750001CB9C /* RACDynamicPropertySuperclass.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2618,12 +2439,9 @@ 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 */, D03764F219EDA41200A782A9 /* NSControl+RACTextSignalSupport.m in Sources */, D037650219EDA41200A782A9 /* NSFileHandle+RACSupport.m in Sources */, D03765E219EDA41200A782A9 /* RACStream.m in Sources */, @@ -2632,19 +2450,15 @@ 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 */, 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 */, 9AD0F06A1D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift in Sources */, D037660A19EDA41200A782A9 /* RACTupleSequence.m in Sources */, D03765D419EDA41200A782A9 /* RACSignal.m in Sources */, @@ -2655,8 +2469,6 @@ 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 */, @@ -2680,8 +2492,6 @@ 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 */, @@ -2689,21 +2499,16 @@ 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 */, @@ -2715,51 +2520,39 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D0A2260E1A72F16D00D33B74 /* PropertySpec.swift in Sources */, + D0A2260E1A72F16D00D33B74 /* DynamicPropertySpec.swift in Sources */, D03766C719EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m in Sources */, D03766E319EDA60000A782A9 /* RACDelegateProxySpec.m 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 */, 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 */, CD8401831CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */, D03766EF19EDA60000A782A9 /* RACPropertySignalExamples.m in Sources */, D037670519EDA60000A782A9 /* RACSubscriberExamples.m 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 */, @@ -2775,7 +2568,6 @@ 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 */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2785,23 +2577,18 @@ 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 */, @@ -2810,7 +2597,6 @@ 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 */, @@ -2826,7 +2612,6 @@ 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 */, @@ -2839,14 +2624,11 @@ 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 */, @@ -2861,13 +2643,9 @@ 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 */, @@ -2875,8 +2653,6 @@ D037662319EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m in Sources */, D03765A119EDA41200A782A9 /* RACKVOTrampoline.m in Sources */, D037665B19EDA41200A782A9 /* UITableViewHeaderFooterView+RACSignalSupport.m 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 */, @@ -2902,24 +2678,19 @@ 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 */, + D0A2260F1A72F16D00D33B74 /* DynamicPropertySpec.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 */, BFA6B94E1A7604D500C846D1 /* SignalProducerNimbleMatchers.swift in Sources */, D03766CA19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m in Sources */, D0C3132319EF2D9700984962 /* RACTestSchedulerSpec.m in Sources */, @@ -2934,15 +2705,10 @@ 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 */, @@ -2961,13 +2727,10 @@ 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 */, CD8401841CEE8ED7009F0ABF /* CocoaActionSpec.swift in Sources */, - 4A0E11051D2A95200065D310 /* LifetimeSpec.swift in Sources */, - 02D2602A1C1D6DAF003ACC61 /* SignalLifetimeSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme index 25ac5b0747..e26a5aa441 100644 --- a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:Carthage/Checkouts/Result/Result.xcodeproj"> + + + + + + + + + + + + + + diff --git a/ReactiveCocoa/Swift/Action.swift b/ReactiveCocoa/Swift/Action.swift deleted file mode 100644 index a8611ace3a..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: (Input) -> SignalProducer) { - 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: (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 c1a9bb0a73..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: @noescape (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: @noescape (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(_:)) - 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: @noescape (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: @noescape (Value) throws -> Result) rethrows -> Result { - lock.lock() - defer { lock.unlock() } - - return try action(_value) - } -} - -public protocol AtomicProtocol: class { - associatedtype Value - - @discardableResult - func withValue(_ action: @noescape (Value) throws -> Result) rethrows -> Result - - @discardableResult - func modify(_ action: @noescape (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 a4b10c97d0..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 { - private var identifier: UInt? - - private init(identifier: UInt) { - self.identifier = identifier - } -} - -/// An unordered, non-unique collection of values of type `Element`. -public struct Bag { - private 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 index 44d80d6e0d..8d54a422e8 100644 --- a/ReactiveCocoa/Swift/CocoaAction.swift +++ b/ReactiveCocoa/Swift/CocoaAction.swift @@ -1,4 +1,5 @@ import Foundation +import ReactiveSwift /// Wraps an Action for use by a GUI control (such as `NSControl` or /// `UIControl`), with KVO, or with Cocoa Bindings. 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 757b80a737..0000000000 --- a/ReactiveCocoa/Swift/Disposable.swift +++ /dev/null @@ -1,353 +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: () -> 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? - - private static let empty = DisposableHandle() - - private init() { - self.bagToken = Atomic(nil) - } - - private 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) { - 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) { - 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: () -> 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: () -> ()) -> 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: () -> ()) -> CompositeDisposable.DisposableHandle { - return lhs.innerDisposable.add(rhs) -} diff --git a/ReactiveCocoa/Swift/DynamicProperty.swift b/ReactiveCocoa/Swift/DynamicProperty.swift index 857078f69a..309c81c879 100644 --- a/ReactiveCocoa/Swift/DynamicProperty.swift +++ b/ReactiveCocoa/Swift/DynamicProperty.swift @@ -1,4 +1,5 @@ import Foundation +import ReactiveSwift import enum Result.NoError /// Models types that can be represented in Objective-C (i.e., reference 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 4b68c7fe1c..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: identifier, event: event, fileName: fileName, functionName: functionName, lineNumber: 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: identifier, - event: event, - fileName: fileName, - functionName: functionName, - lineNumber: 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: (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 d01296644f..0000000000 --- a/ReactiveCocoa/Swift/Flatten.swift +++ /dev/null @@ -1,918 +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. - private func concat() -> Signal { - return Signal { relayObserver in - let disposable = CompositeDisposable() - let relayDisposable = CompositeDisposable() - - disposable += relayDisposable - disposable += self.observeConcat(relayObserver, relayDisposable) - - return disposable - } - } - - private 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. - private 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 { - 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. - private func merge() -> Signal { - return Signal { relayObserver in - let disposable = CompositeDisposable() - let relayDisposable = CompositeDisposable() - - disposable += relayDisposable - disposable += self.observeMerge(relayObserver, relayDisposable) - - return disposable - } - } - - private 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. - private 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 { - 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 { - 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 { - 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 { - 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. - private func switchToLatest() -> Signal { - return Signal { observer in - let composite = CompositeDisposable() - let serial = SerialDisposable() - - composite += serial - composite += self.observeSwitchToLatest(observer, serial) - - return composite - } - } - - private 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. - private 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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (Error) -> SignalProducer) -> Signal { - return Signal { observer in - self.observeFlatMapError(handler, observer, SerialDisposable()) - } - } - - private func observeFlatMapError(_ handler: (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: (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 98b6543e85..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 ?? defaultSessionError) - } - } - - disposable += { - task.cancel() - } - task.resume() - } - } -} diff --git a/ReactiveCocoa/Swift/Lifetime.swift b/ReactiveCocoa/Swift/Lifetime.swift index 54f385b9c1..388800548a 100644 --- a/ReactiveCocoa/Swift/Lifetime.swift +++ b/ReactiveCocoa/Swift/Lifetime.swift @@ -1,54 +1,7 @@ import Foundation +import ReactiveSwift import enum Result.NoError -/// Represents the lifetime of an object, and provides a hook to observe when -/// the object deinitializes. -public final class Lifetime { - /// A signal that sends a Completed event when the lifetime ends. - public let ended: Signal<(), NoError> - - /// 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 init(_ token: Token) { - 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. - private 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 diff --git a/ReactiveCocoa/Swift/NSObject+KeyValueObserving.swift b/ReactiveCocoa/Swift/NSObject+KeyValueObserving.swift index 32dfe1b541..f00dda85a1 100644 --- a/ReactiveCocoa/Swift/NSObject+KeyValueObserving.swift +++ b/ReactiveCocoa/Swift/NSObject+KeyValueObserving.swift @@ -1,4 +1,5 @@ import Foundation +import ReactiveSwift import enum Result.NoError extension NSObject { diff --git a/ReactiveCocoa/Swift/ObjectiveCBridging.swift b/ReactiveCocoa/Swift/ObjectiveCBridging.swift index f08e04e752..6fd75be418 100644 --- a/ReactiveCocoa/Swift/ObjectiveCBridging.swift +++ b/ReactiveCocoa/Swift/ObjectiveCBridging.swift @@ -6,8 +6,24 @@ // Copyright (c) 2014 GitHub, Inc. All rights reserved. // +import Foundation +import ReactiveSwift import Result +extension SignalProtocol { + /// Turns each value into an Optional. + private func optionalize() -> Signal { + return map(Optional.init) + } +} + +extension SignalProducerProtocol { + /// Turns each value into an Optional. + private func optionalize() -> SignalProducer { + return lift { $0.optionalize() } + } +} + extension RACDisposable: Disposable {} extension RACScheduler: DateSchedulerProtocol { /// The current date, as determined by this scheduler. diff --git a/ReactiveCocoa/Swift/Observer.swift b/ReactiveCocoa/Swift/Observer.swift deleted file mode 100644 index 667a97b64d..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 = (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 3fec743901..0000000000 --- a/ReactiveCocoa/Swift/Property.swift +++ /dev/null @@ -1,878 +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 { - /// The current value of the property. - var value: Value { get set } -} - -/// 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. - private func lift(_ transform: @noescape (SignalProducer) -> SignalProducer) -> Property { - return Property(self, transform: transform) - } - - /// Lifts a binary SignalProducer operator to operate upon PropertyProtocol instead. - private func lift(_ transform: @noescape (SignalProducer) -> (SignalProducer) -> SignalProducer) -> @noescape (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: (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: (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: (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: (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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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]>? { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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)> { - 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]>? { - 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 _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 = [] - _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) { - sources = Property.capture(property) - _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`. - private convenience init(_ property: P, transform: @noescape (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`. - private convenience init(_ firstProperty: P1, _ secondProperty: P2, transform: @noescape (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) - - // Verify that an initial is sent. This is friendlier than deadlocking - // in the event that one isn't. - var value: Value? = nil - let disposable = producer.start { event in - switch event { - case let .next(newValue): - value = newValue - - case .completed, .interrupted: - break - - case let .failed(error): - fatalError("Receive unexpected error from a producer of `NoError` type: \(error)") - } - } - guard value != nil else { - fatalError("A producer promised to send at least one value. Received none.") - } - disposable.dispose() - - self.sources = sources - _value = { producer.take(first: 1).single()!.value! } - _producer = { producer } - _signal = { - var extractedSignal: Signal! - producer.startWithSignal { signal, _ in extractedSignal = signal } - return extractedSignal - } - } - - /// 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 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) - } - } - - /// 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() - - /// 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: @noescape (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: @noescape (Value) throws -> Result) rethrows -> Result { - return try atomic.withValue(action) - } - - deinit { - observer.sendCompleted() - } -} - -private class Box { - var value: Value - - init(_ value: Value) { - self.value = value - } -} - -infix operator <~ { - associativity right - - // Binds tighter than assignment but looser than everything else - precedence 93 -} - -/// Binds a signal to a property, updating the property's value to the latest -/// value sent by the signal. -/// -/// - note: The binding will automatically terminate when the property 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: -/// - property: A property to bind to. -/// - signal: A signal to bind. -/// -/// - returns: A disposable that can be used to terminate binding before the -/// deinitialization of property or signal's `completed` event. -@discardableResult -public func <~ (property: P, signal: Signal) -> Disposable { - let disposable = CompositeDisposable() - disposable += property.producer.startWithCompleted { - disposable.dispose() - } - - disposable += signal.observe { [weak property] event in - switch event { - case let .next(value): - property?.value = value - case .completed: - disposable.dispose() - case .failed, .interrupted: - break - } - } - - return disposable -} - -/// Creates a signal from the given producer, which will be immediately bound to -/// the given property, updating the property's value to the latest value sent -/// by the signal. -/// -/// ```` -/// 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() -/// ```` -/// -/// - note: The binding will automatically terminate when the property is -/// deinitialized, or when the created producer sends a `completed` -/// event. -/// -/// - parameters: -/// - property: A property to bind to. -/// - producer: A producer to bind. -/// -/// - returns: A disposable that can be used to terminate binding before the -/// deinitialization of property or producer's `completed` event. -@discardableResult -public func <~ (property: P, producer: SignalProducer) -> Disposable { - let disposable = CompositeDisposable() - - producer - .on(completed: { disposable.dispose() }) - .startWithSignal { signal, signalDisposable in - disposable += property <~ signal - disposable += signalDisposable - - disposable += property.producer.startWithCompleted { - disposable.dispose() - } - } - - return disposable -} - -/// Binds a signal to a property, updating the property's value to the latest -/// value sent by the signal. -/// -/// - note: The binding will automatically terminate when the property 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: -/// - property: A property to bind to. -/// - signal: A signal to bind. -/// -/// - returns: A disposable that can be used to terminate binding before the -/// deinitialization of property or signal's `completed` event. -@discardableResult -public func <~ (property: P, signal: S) -> Disposable { - return property <~ signal.optionalize() -} - -/// Creates a signal from the given producer, which will be immediately bound to -/// the given property, updating the property's value to the latest value sent -/// by the signal. -/// -/// ```` -/// 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() -/// ```` -/// -/// - note: The binding will automatically terminate when the property is -/// deinitialized, or when the created producer sends a `completed` -/// event. -/// -/// - parameters: -/// - property: A property to bind to. -/// - producer: A producer to bind. -/// -/// - returns: A disposable that can be used to terminate binding before the -/// deinitialization of property or producer's `completed` event. -@discardableResult -public func <~ (property: P, producer: S) -> Disposable { - return property <~ producer.optionalize() -} - -/// Binds `destinationProperty` to the latest values of `sourceProperty`. -/// -/// ```` -/// 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: -/// - destinationProperty: A property to bind to. -/// - sourceProperty: A property to bind. -/// -/// - returns: A disposable that can be used to terminate binding before the -/// deinitialization of destination property or source property -/// producer's `completed` event. -@discardableResult -public func <~ (destinationProperty: Destination, sourceProperty: Source) -> Disposable { - return destinationProperty <~ sourceProperty.producer -} - -/// Binds `destinationProperty` to the latest values of `sourceProperty`. -/// -/// ```` -/// 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: -/// - destinationProperty: A property to bind to. -/// - sourceProperty: A property to bind. -/// -/// - returns: A disposable that can be used to terminate binding before the -/// deinitialization of destination property or source property -/// producer's `completed` event. -@discardableResult -public func <~ (destinationProperty: Destination, sourceProperty: Source) -> Disposable { - return destinationProperty <~ sourceProperty.producer -} diff --git a/ReactiveCocoa/Swift/Scheduler.swift b/ReactiveCocoa/Swift/Scheduler.swift deleted file mode 100644 index c4bd14d0c5..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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> 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: () -> Void) -> Disposable? { - return schedule(after: currentDate.addingTimeInterval(delay), action: action) - } - - @discardableResult - public func schedule(after date: Date, action: () -> 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: () -> 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: () -> 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: () -> 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 d2368b6885..0000000000 --- a/ReactiveCocoa/Swift/Signal.swift +++ /dev/null @@ -1,1838 +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: @noescape (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: (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: () -> 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: (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: () -> 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: (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: (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: (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: (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: (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(values: 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: (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(values: state.values, next: 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: (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. - 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: (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: (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: (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: (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: (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: (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: (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> { - 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> { - 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 a3030992f6..0000000000 --- a/ReactiveCocoa/Swift/SignalProducer.swift +++ /dev/null @@ -1,1907 +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) { - 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: (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) { - 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: () -> 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: @noescape (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: signal, interrupter: 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: (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: @noescape (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 = Signal.Observer()) -> 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: (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: () -> 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: (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: () -> 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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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: (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) } - } -} - -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: (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> { - 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> { - 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 - } - } - } - } - } - - /// 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/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 index a11db898e5..d99d777e96 100644 --- a/ReactiveCocoaTests/Swift/CocoaActionSpec.swift +++ b/ReactiveCocoaTests/Swift/CocoaActionSpec.swift @@ -1,3 +1,4 @@ +import ReactiveSwift import Result import Nimble import Quick 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/DynamicPropertySpec.swift b/ReactiveCocoaTests/Swift/DynamicPropertySpec.swift new file mode 100644 index 0000000000..6eb5cf78fc --- /dev/null +++ b/ReactiveCocoaTests/Swift/DynamicPropertySpec.swift @@ -0,0 +1,233 @@ +// +// DynamicPropertySpec.swift +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2015-01-23. +// Copyright (c) 2015 GitHub. All rights reserved. +// + +import ReactiveSwift +import Result +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") { + var object: ObservableObject! + var property: DynamicProperty! + + let propertyValue: () -> Int? = { + if let value: AnyObject = 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" + } + } + + 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.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/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 index 7989829354..cd407d92fc 100644 --- a/ReactiveCocoaTests/Swift/KeyValueObservingSpec.swift +++ b/ReactiveCocoaTests/Swift/KeyValueObservingSpec.swift @@ -1,5 +1,6 @@ import Foundation @testable import ReactiveCocoa +import ReactiveSwift import enum Result.NoError import Quick import Nimble diff --git a/ReactiveCocoaTests/Swift/LifetimeSpec.swift b/ReactiveCocoaTests/Swift/LifetimeSpec.swift deleted file mode 100644 index 3569657892..0000000000 --- a/ReactiveCocoaTests/Swift/LifetimeSpec.swift +++ /dev/null @@ -1,83 +0,0 @@ -import Quick -import Nimble -import ReactiveCocoa -import Result - -final class LifetimeSpec: QuickSpec { - override func spec() { - describe("SignalProducerProtocol.takeDuring") { - 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] - } - } - - describe("NSObject") { - 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 - } - } - } -} - -private final class MutableReference { - var value: Value? - init(_ value: Value?) { - self.value = value - } -} - -private 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 index dd3369a0e2..ea2480c597 100644 --- a/ReactiveCocoaTests/Swift/ObjectiveCBridgingSpec.swift +++ b/ReactiveCocoaTests/Swift/ObjectiveCBridgingSpec.swift @@ -6,6 +6,7 @@ // Copyright (c) 2015 GitHub. All rights reserved. // +import ReactiveSwift import Result import Nimble import Quick diff --git a/ReactiveCocoaTests/Swift/PropertySpec.swift b/ReactiveCocoaTests/Swift/PropertySpec.swift deleted file mode 100644 index afb23c2037..0000000000 --- a/ReactiveCocoaTests/Swift/PropertySpec.swift +++ /dev/null @@ -1,1754 +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 -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 - } - } - - describe("Property") { - describe("from a value") { - 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("from a PropertyProtocol") { - it("should pass through behaviors of the input 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") { - 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 - } - - describe("signal lifetime and producer lifetime") { - 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 - } - } - } - - describe("Property.capture") { - 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: AnyObject = 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" - } - } - - 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 bound signal is completed") { - let (signal, observer) = Signal.pipe() - - let mutableProperty = MutableProperty(initialPropertyValue) - - let bindingDisposable = mutableProperty <~ signal - - expect(bindingDisposable.isDisposed) == false - observer.sendCompleted() - expect(bindingDisposable.isDisposed) == true - } - - it("should tear down the binding when the property deallocates") { - let (signal, _) = Signal.pipe() - - var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) - - let bindingDisposable = mutableProperty! <~ signal - - mutableProperty = nil - expect(bindingDisposable.isDisposed) == true - } - } - - 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 bound signal is completed") { - let (signalProducer, observer) = SignalProducer.pipe() - - let mutableProperty = MutableProperty(initialPropertyValue) - let disposable = mutableProperty <~ signalProducer - - observer.sendCompleted() - - expect(disposable.isDisposed) == true - } - - it("should tear down the binding when the property deallocates") { - let signalValues = [initialPropertyValue, subsequentPropertyValue] - let signalProducer = SignalProducer(values: signalValues) - - var mutableProperty: MutableProperty? = MutableProperty(initialPropertyValue) - let disposable = mutableProperty! <~ signalProducer - - mutableProperty = nil - expect(disposable.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("") - - let bindingDisposable = destinationProperty! <~ sourceProperty.producer - destinationProperty = nil - - expect(bindingDisposable.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 8060935706..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: () -> 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 eae2d84a3d..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) == expectation - } else { - expect(observedValues) == expectation - } - } - - observer.sendCompleted() - - expectation.append([7]) - expect(observedValues) == expectation - } - - 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) == [] - } - - 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) == [] - } - - 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: () -> 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 index ad2d89e569..5790549622 100644 --- a/ReactiveCocoaTests/Swift/SignalProducerNimbleMatchers.swift +++ b/ReactiveCocoaTests/Swift/SignalProducerNimbleMatchers.swift @@ -9,6 +9,7 @@ import Foundation import ReactiveCocoa +import ReactiveSwift import Nimble public func sendValue(_ value: T?, sendError: E?, complete: Bool) -> NonNilMatcherFunc> { diff --git a/ReactiveCocoaTests/Swift/SignalProducerSpec.swift b/ReactiveCocoaTests/Swift/SignalProducerSpec.swift deleted file mode 100644 index ce7ca47861..0000000000 --- a/ReactiveCocoaTests/Swift/SignalProducerSpec.swift +++ /dev/null @@ -1,2257 +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) == [[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) == [[1, 2]] - } - } - - 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) == [[1, 3], [2, 4]] - } - - 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) == [[1, 2]] - } - } - - 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: [NSDate] = [] - 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: NSDate? - 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 - } - } - - 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(be(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(be(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: () -> 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) - } - } - } - } -} - -// 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. - private static func attemptWithResults, C.IndexDistance == C.Index, C.Index == Int>(_ results: C) -> SignalProducer { - 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 38f710eda9..0000000000 --- a/ReactiveCocoaTests/Swift/SignalSpec.swift +++ /dev/null @@ -1,2269 +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) == expectation - } else { - expect(observedValues) == expectation - } - } - - observer.sendCompleted() - - expectation.append([7]) - expect(observedValues) == expectation - } - - 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) == [] - } - - 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) == [] - } - - 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/TestError.swift b/ReactiveCocoaTests/Swift/TestError.swift index 8ed0054d8d..275aef893a 100644 --- a/ReactiveCocoaTests/Swift/TestError.swift +++ b/ReactiveCocoaTests/Swift/TestError.swift @@ -7,6 +7,7 @@ // import ReactiveCocoa +import ReactiveSwift import Result enum TestError: Int { diff --git a/ReactiveCocoaTests/Swift/TestLogger.swift b/ReactiveCocoaTests/Swift/TestLogger.swift deleted file mode 100644 index 4d6cea60a5..0000000000 --- a/ReactiveCocoaTests/Swift/TestLogger.swift +++ /dev/null @@ -1,25 +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 { - private 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/ReactiveSwift/Scheduler.swift b/ReactiveSwift/Scheduler.swift index daa3cb429c..0e3ea04ba0 100644 --- a/ReactiveSwift/Scheduler.swift +++ b/ReactiveSwift/Scheduler.swift @@ -144,7 +144,7 @@ public final class QueueScheduler: DateSchedulerProtocol { return Date() } - internal let queue: DispatchQueue + public let queue: DispatchQueue internal init(internalQueue: DispatchQueue) { queue = internalQueue From 954c82b8412ca06c057b7b0c4c3c49e93ebc09ee Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 16 Aug 2016 17:30:01 -0400 Subject: [PATCH 03/20] Rename ReactiveCocoa-Mac to ReactiveCocoa-macOS --- .travis.yml | 2 +- README.md | 6 ++--- ReactiveCocoa.xcodeproj/project.pbxproj | 22 +++++++++---------- ....xcscheme => ReactiveCocoa-macOS.xcscheme} | 12 +++++----- 4 files changed, 21 insertions(+), 21 deletions(-) rename ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/{ReactiveCocoa-Mac.xcscheme => ReactiveCocoa-macOS.xcscheme} (95%) diff --git a/.travis.yml b/.travis.yml index 51ed833049..f4a4ebeeeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ script: xcode_workspace: ReactiveCocoa.xcworkspace matrix: include: - - xcode_scheme: ReactiveCocoa-Mac + - xcode_scheme: ReactiveCocoa-macOS env: - XCODE_SDK=macosx - XCODE_ACTION="build test" diff --git a/README.md b/README.md index 578cc7b88f..d83ac67bdc 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ our event stream. #### Debugging event streams -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. +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: ```swift @@ -354,10 +354,10 @@ We also provide a great Playground, so you can get used to ReactiveCocoa's opera - `carthage checkout` 1. Open `ReactiveCocoa.xcworkspace` 1. Build `Result-Mac` scheme - 1. Build `ReactiveCocoa-Mac` scheme + 1. Build `ReactiveCocoa-macOS` scheme 1. Finally open the `ReactiveCocoa.playground` 1. Choose `View > Show Debug Area` - + [Actions]: Documentation/FrameworkOverview.md#actions [Basic Operators]: Documentation/BasicOperators.md [CHANGELOG]: CHANGELOG.md diff --git a/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoa.xcodeproj/project.pbxproj index 132c1005d8..8ed66bdb47 100644 --- a/ReactiveCocoa.xcodeproj/project.pbxproj +++ b/ReactiveCocoa.xcodeproj/project.pbxproj @@ -2010,9 +2010,9 @@ 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 = ( D04725E519E49ED7006002AA /* Sources */, D04725E619E49ED7006002AA /* Frameworks */, @@ -2023,14 +2023,14 @@ ); dependencies = ( ); - name = "ReactiveCocoa-Mac"; + name = "ReactiveCocoa-macOS"; productName = ReactiveCocoa; 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 */, @@ -2041,7 +2041,7 @@ dependencies = ( D04725F819E49ED7006002AA /* PBXTargetDependency */, ); - name = "ReactiveCocoa-MacTests"; + name = "ReactiveCocoa-macOSTests"; productName = ReactiveCocoaTests; productReference = D04725F519E49ED7006002AA /* ReactiveCocoaTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -2134,8 +2134,8 @@ projectDirPath = ""; projectRoot = ""; targets = ( - D04725E919E49ED7006002AA /* ReactiveCocoa-Mac */, - D04725F419E49ED7006002AA /* ReactiveCocoa-MacTests */, + D04725E919E49ED7006002AA /* ReactiveCocoa-macOS */, + D04725F419E49ED7006002AA /* ReactiveCocoa-macOSTests */, D047260B19E49F82006002AA /* ReactiveCocoa-iOS */, D047261519E49F82006002AA /* ReactiveCocoa-iOSTests */, A9B315531B3940610001CB9C /* ReactiveCocoa-watchOS */, @@ -2744,7 +2744,7 @@ }; D04725F819E49ED7006002AA /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = D04725E919E49ED7006002AA /* ReactiveCocoa-Mac */; + target = D04725E919E49ED7006002AA /* ReactiveCocoa-macOS */; targetProxy = D04725F719E49ED7006002AA /* PBXContainerItemProxy */; }; D047261919E49F82006002AA /* PBXTargetDependency */ = { @@ -3281,7 +3281,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 */, @@ -3292,7 +3292,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-Mac.xcscheme b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-macOS.xcscheme similarity index 95% rename from ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme rename to ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-macOS.xcscheme index e26a5aa441..f00a9e8579 100644 --- a/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-Mac.xcscheme +++ b/ReactiveCocoa.xcodeproj/xcshareddata/xcschemes/ReactiveCocoa-macOS.xcscheme @@ -44,7 +44,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "D04725E919E49ED7006002AA" BuildableName = "ReactiveCocoa.framework" - BlueprintName = "ReactiveCocoa-Mac" + BlueprintName = "ReactiveCocoa-macOS" ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> @@ -86,7 +86,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "D04725F419E49ED7006002AA" BuildableName = "ReactiveCocoaTests.xctest" - BlueprintName = "ReactiveCocoa-MacTests" + BlueprintName = "ReactiveCocoa-macOSTests" ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> @@ -105,7 +105,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "D04725F419E49ED7006002AA" BuildableName = "ReactiveCocoaTests.xctest" - BlueprintName = "ReactiveCocoa-MacTests" + BlueprintName = "ReactiveCocoa-macOSTests" ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> @@ -115,7 +115,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "D04725E919E49ED7006002AA" BuildableName = "ReactiveCocoa.framework" - BlueprintName = "ReactiveCocoa-Mac" + BlueprintName = "ReactiveCocoa-macOS" ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> @@ -137,7 +137,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "D04725E919E49ED7006002AA" BuildableName = "ReactiveCocoa.framework" - BlueprintName = "ReactiveCocoa-Mac" + BlueprintName = "ReactiveCocoa-macOS" ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> @@ -155,7 +155,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "D04725E919E49ED7006002AA" BuildableName = "ReactiveCocoa.framework" - BlueprintName = "ReactiveCocoa-Mac" + BlueprintName = "ReactiveCocoa-macOS" ReferencedContainer = "container:ReactiveCocoa.xcodeproj"> From 20ad15e4fe219193a7190def1e9f51052dc58b7d Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 16 Aug 2016 17:44:24 -0400 Subject: [PATCH 04/20] Split up README for ReactiveSwift --- README-ReactiveSwift.md | 355 ++++++++++++++++++++++++++++++++++++++++ README.md | 280 ++----------------------------- 2 files changed, 366 insertions(+), 269 deletions(-) create mode 100644 README-ReactiveSwift.md diff --git a/README-ReactiveSwift.md b/README-ReactiveSwift.md new file mode 100644 index 0000000000..c4111f8dd6 --- /dev/null +++ b/README-ReactiveSwift.md @@ -0,0 +1,355 @@ +# ReactiveSwift + +[![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) + +ReactiveSwift is a Swift 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**. + + 1. [Introduction](#introduction) + 1. [Example: online search](#example-online-search) + 1. [How does ReactiveSwift relate to Rx?](#how-does-reactivecocoa-relate-to-rx) + 1. [Getting started](#getting-started) + 1. [Playground](#playground) + +If you’re already familiar with functional reactive programming or what +ReactiveSwift 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. + +If you'd like to use ReactiveSwift with Apple's Cocoa frameworks, +[ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) provides +extensions that work with ReactiveSwift. + +If you have a question, please see if any discussions in our [GitHub +issues](https://github.com/ReactiveCocoa/ReactiveSwift/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/ReactiveSwift/issues/new)! + +#### Compatibility + +This documents ReactiveSwift 3.x which targets `Swift 3.0.x`. For `Swift 2.x` support see [ReactiveCocoa +4](https://github.com/ReactiveCocoa/ReactiveCocoa/tree/v4.0.0). + +## Introduction + +ReactiveSwift 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. + +Event streams unify common patterns for asynchrony and event +handling, including: + + * Delegate methods + * Callback blocks + * Notifications + * 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) + +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. + +For more information about the concepts in ReactiveSwift, see the [Framework +Overview][]. + +## Example: online search + +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. + +#### 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 +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()) +``` + +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]). + +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! + +#### Receiving the results + +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 +searchResults.startWithNext { results in + print("Search results: \(results)") +} +``` + +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. + +#### Handling failures + +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. + +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: + +```swift + .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in + let URLRequest = self.searchRequestWithEscapedQuery(query) + + 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()) +``` + +#### Throttling requests + +Now, let’s say you only want to actually perform the search periodically, +to minimize traffic. + +ReactiveCocoa has a declarative `throttle` operator that we can apply to our +search strings: + +```swift +let searchStrings = textField.rac_textSignal() + .toSignalProducer() + .map { text in text as! String } + .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) +``` + +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. + +#### Debugging event streams + +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: + +```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() +``` + +For more information and advance usage, check the [Debugging Techniques](Documentation/DebuggingTechniques.md) document. + +## How does ReactiveSwift 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 ReactiveSwift 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 + +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`. + +**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) +``` + +… 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][https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/CHANGELOG.md] 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, **ReactiveSwift’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 ReactiveSwift, +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. + +ReactiveSwift 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, **ReactiveSwift has been improved many times +specifically for this purpose**—even when it means diverging further from Rx. + +## Getting started + +ReactiveSwift supports `OS X 10.9+`, `iOS 8.0+`, `watchOS 2.0`, and `tvOS 9.0`. + +To add RAC to your application: + + 1. Add the ReactiveSwift repository as a + [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) of your + application’s repository. + 1. Run `git submodule update --init --recursive` from within the ReactiveSwift folder. + 1. Drag and drop `ReactiveSwift.xcodeproj` and `Carthage/Checkouts/Result/Result.xcodeproj` + into your application’s Xcode project or workspace. + 1. On the “General” tab of your application target’s settings, add + `ReactiveSwift.framework` and `Result.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 +ReactiveSwift to your `Cartfile`: + +``` +github "ReactiveCocoa/ReactiveSwift" +``` +Make sure to add both `ReactiveSwift.framework` and `Result.framework` to "Linked Frameworks and Libraries" and "copy-frameworks" Build Phases. + +Once you’ve set up your project, check out the [Framework Overview][] for +a tour of ReactiveSwift’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 ReactiveSwift repository. + 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveSwift project root directory: + - `git submodule update --init --recursive` **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed + - `carthage checkout` + 1. Open `ReactiveSwift.xcworkspace` + 1. Build `Result-Mac` scheme + 1. Build `ReactiveSwift-macOS` scheme + 1. Finally open the `ReactiveSwift.playground` + 1. Choose `View > Show Debug Area` + +[Actions]: Documentation/FrameworkOverview.md#actions +[Basic Operators]: Documentation/BasicOperators.md +[CHANGELOG]: CHANGELOG.md +[Code]: ReactiveCocoa +[Documentation]: Documentation +[Events]: Documentation/FrameworkOverview.md#events +[Framework Overview]: Documentation/FrameworkOverview.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 diff --git a/README.md b/README.md index d83ac67bdc..b8dc9c0490 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ [![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) -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**. +ReactiveCocoa (RAC) is a Cocoa framework built on top of [ReactiveSwift][]. It +provides APIs for using ReactiveSwift with Apple's Cocoa frameworks. 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) If you’re already familiar with functional reactive programming or what ReactiveCocoa is about, check out the [Documentation][] folder for more in-depth @@ -49,170 +49,7 @@ 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. -For more information about the concepts in ReactiveCocoa, see the [Framework -Overview][]. - -## Example: online search - -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. - -#### 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 -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()) -``` - -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]). - -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! - -#### Receiving the results - -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 -searchResults.startWithNext { results in - print("Search results: \(results)") -} -``` - -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. - -#### Handling failures - -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. - -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: - -```swift - .flatMap(.Latest) { (query: String) -> SignalProducer<(NSData, NSURLResponse), NSError> in - let URLRequest = self.searchRequestWithEscapedQuery(query) - - 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()) -``` - -#### Throttling requests - -Now, let’s say you only want to actually perform the search periodically, -to minimize traffic. - -ReactiveCocoa has a declarative `throttle` operator that we can apply to our -search strings: - -```swift -let searchStrings = textField.rac_textSignal() - .toSignalProducer() - .map { text in text as! String } - .throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler) -``` - -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. - -#### Debugging event streams - -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: - -```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() -``` - -For more information and advance usage, check the [Debugging Techniques](Documentation/DebuggingTechniques.md) document. - +For more information about the concepts in ReactiveCocoa, see [ReactiveSwift][]. ## Objective-C and Swift @@ -230,87 +67,6 @@ 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 - -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`. - -**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) -``` - -… 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`. @@ -321,10 +77,13 @@ To add RAC to your application: [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) 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`, + `Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj`, and + `Carthage/Checkouts/Result/Result.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`, `ReactiveSwift.framework`, and `Result.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”. @@ -334,7 +93,7 @@ ReactiveCocoa to your `Cartfile`: ``` github "ReactiveCocoa/ReactiveCocoa" ``` -Make sure to add both `ReactiveCocoa.framework` and `Result.framework` to "Linked Frameworks and Libraries" and "copy-frameworks" Build Phases. +Make sure to add `ReactiveCocoa.framework`, `ReactiveSwift`, 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) @@ -344,33 +103,16 @@ 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-macOS` scheme - 1. Finally open the `ReactiveCocoa.playground` - 1. Choose `View > Show Debug Area` - +[ReactiveSwift]: https://github.com/ReactiveCocoa/ReactiveSwift [Actions]: Documentation/FrameworkOverview.md#actions [Basic Operators]: Documentation/BasicOperators.md [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 From 55b8823e0f5746c1a58cdf5f3ab3a35481c767fb Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Wed, 17 Aug 2016 08:23:36 -0400 Subject: [PATCH 05/20] Make TestError explicitly internal --- ReactiveCocoaTests/Swift/TestError.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactiveCocoaTests/Swift/TestError.swift b/ReactiveCocoaTests/Swift/TestError.swift index 275aef893a..43e6905648 100644 --- a/ReactiveCocoaTests/Swift/TestError.swift +++ b/ReactiveCocoaTests/Swift/TestError.swift @@ -10,13 +10,13 @@ import ReactiveCocoa import ReactiveSwift import Result -enum TestError: Int { +internal enum TestError: Int { case `default` = 0 case error1 = 1 case error2 = 2 } -extension TestError: Error { +internal extension TestError: Error { } From 2436352c1a39020c755982208e9ab6db72625665 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Wed, 17 Aug 2016 19:21:56 -0400 Subject: [PATCH 06/20] This is ReactiveSwift --- ReactiveSwift.xcodeproj/project.pbxproj | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ReactiveSwift.xcodeproj/project.pbxproj b/ReactiveSwift.xcodeproj/project.pbxproj index 3baddc24d0..7629a15004 100644 --- a/ReactiveSwift.xcodeproj/project.pbxproj +++ b/ReactiveSwift.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ /* Begin PBXFileReference section */ 02D260291C1D6DAF003ACC61 /* SignalLifetimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalLifetimeSpec.swift; sourceTree = ""; }; - 4A0E10FE1D2A92720065D310 /* Lifetime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Lifetime.swift; path = Lifetime.swift; 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 = ""; }; 57A4D2411BA13D7A00F7D4B1 /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 57A4D2441BA13F9700F7D4B1 /* tvOS-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "tvOS-Application.xcconfig"; sourceTree = ""; }; @@ -228,10 +228,10 @@ 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 = ""; }; 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; name = TupleExtensions.swift; path = TupleExtensions.swift; sourceTree = ""; }; + 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; }; 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; name = FoundationExtensions.swift; path = FoundationExtensions.swift; sourceTree = ""; }; + D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = ""; }; D04725EA19E49ED7006002AA /* ReactiveSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D04725EE19E49ED7006002AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D04725EF19E49ED7006002AA /* ReactiveSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactiveSwift.h; sourceTree = ""; }; @@ -258,10 +258,10 @@ D047263B19E49FE8006002AA /* Mac-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-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; name = Action.swift; path = Action.swift; sourceTree = ""; }; - D08C54B01A69A2AC00AD8286 /* Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Property.swift; path = Property.swift; sourceTree = ""; }; - D08C54B11A69A2AC00AD8286 /* Signal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Signal.swift; path = Signal.swift; sourceTree = ""; }; - D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SignalProducer.swift; path = SignalProducer.swift; sourceTree = ""; }; + 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; }; @@ -276,7 +276,7 @@ D0C312F219EF2A7700984962 /* SchedulerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchedulerSpec.swift; 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; name = Flatten.swift; path = Flatten.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; }; /* End PBXFileReference section */ @@ -381,7 +381,6 @@ D08C54B21A69A2AC00AD8286 /* SignalProducer.swift */, ); name = Signals; - path = ""; sourceTree = ""; }; D03B4A3A19F4C26D009E02AC /* Internal Utilities */ = { @@ -390,7 +389,6 @@ D00004081A46864E000E7D41 /* TupleExtensions.swift */, ); name = "Internal Utilities"; - path = ""; sourceTree = ""; }; D03B4A3B19F4C281009E02AC /* Extensions */ = { @@ -399,14 +397,13 @@ D03B4A3C19F4C39A009E02AC /* FoundationExtensions.swift */, ); name = Extensions; - path = ""; sourceTree = ""; }; D04725E019E49ED7006002AA = { isa = PBXGroup; children = ( D04725EC19E49ED7006002AA /* ReactiveSwift */, - D04725F919E49ED7006002AA /* ReactiveCocoaTests */, + D04725F919E49ED7006002AA /* ReactiveSwiftTests */, D047262519E49FE8006002AA /* Configuration */, D04725EB19E49ED7006002AA /* Products */, ); @@ -457,7 +454,7 @@ name = "Supporting Files"; sourceTree = ""; }; - D04725F919E49ED7006002AA /* ReactiveCocoaTests */ = { + D04725F919E49ED7006002AA /* ReactiveSwiftTests */ = { isa = PBXGroup; children = ( D021671C1A6CD50500987861 /* ActionSpec.swift */, @@ -477,7 +474,6 @@ C79B64731CD38B2B003F2376 /* TestLogger.swift */, D04725FA19E49ED7006002AA /* Supporting Files */, ); - name = ReactiveCocoaTests; path = ReactiveSwiftTests; sourceTree = ""; }; From 33a8addbc5e0b0fe80dcfac969c617b74baff6c8 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Wed, 17 Aug 2016 19:22:46 -0400 Subject: [PATCH 07/20] Make this TestError internal as well --- ReactiveSwiftTests/TestError.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReactiveSwiftTests/TestError.swift b/ReactiveSwiftTests/TestError.swift index 39125cdfed..87824a691e 100644 --- a/ReactiveSwiftTests/TestError.swift +++ b/ReactiveSwiftTests/TestError.swift @@ -9,13 +9,13 @@ import ReactiveSwift import Result -enum TestError: Int { +internal enum TestError: Int { case `default` = 0 case error1 = 1 case error2 = 2 } -extension TestError: Error { +internal extension TestError: Error { } From 30a17347d0fe01d0d10194555b620ea31efa857e Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 23 Aug 2016 09:14:24 -0400 Subject: [PATCH 08/20] Fix .travis.yml scheme names --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f4a4ebeeeb..c2bee6f281 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,17 +38,17 @@ matrix: - XCODE_ACTION="build test" - XCODE_DESTINATION="arch=x86_64" - XCODE_PLAYGROUND_TARGET="x86_64-apple-macosx10.10" - - xcode_scheme: ReactiveSwift-macOS + - xcode_scheme: ReactiveSwift-iOS env: - XCODE_SDK=iphonesimulator - XCODE_ACTION="build-for-testing test-without-building" - XCODE_DESTINATION="platform=iOS Simulator,name=iPhone 6s" - - xcode_scheme: ReactiveSwift-macOS + - xcode_scheme: ReactiveSwift-tvOS env: - XCODE_SDK=appletvsimulator - XCODE_ACTION="build-for-testing test-without-building" - XCODE_DESTINATION="platform=tvOS Simulator,name=Apple TV 1080p" - - xcode_scheme: ReactiveSwift-macOS + - xcode_scheme: ReactiveSwift-watchOS env: - XCODE_SDK=watchsimulator - XCODE_ACTION=build From aebb6b2473008d925c2216c0872a861d95cde071 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 23 Aug 2016 09:25:02 -0400 Subject: [PATCH 09/20] Update Package.swift --- Package.swift | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/Package.swift b/Package.swift index 814e59be46..a21a71fd70 100644 --- a/Package.swift +++ b/Package.swift @@ -1,24 +1,11 @@ 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", + name: "ReactiveSwift", dependencies: [ .Package(url: "https://github.com/antitypical/Result.git", "3.0.0-alpha.2") ], - exclude: excludes + exclude: [ + "Sources/Deprecations+Removals.swift", + ] ) From d5acfb76f4141b1427973f9e1adb0c488be6c509 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 23 Aug 2016 20:07:59 -0400 Subject: [PATCH 10/20] Use newer version of Result --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index a21a71fd70..4b15638cbe 100644 --- a/Package.swift +++ b/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "ReactiveSwift", dependencies: [ - .Package(url: "https://github.com/antitypical/Result.git", "3.0.0-alpha.2") + .Package(url: "https://github.com/antitypical/Result.git", "3.0.0-alpha.3") ], exclude: [ "Sources/Deprecations+Removals.swift", From 6b308875b9ff476863f9726632b776acdab9f50a Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 23 Aug 2016 20:09:19 -0400 Subject: [PATCH 11/20] Update SwiftPM symlink --- Sources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources b/Sources index c5c6e8cb28..2ebf1f0a4c 120000 --- a/Sources +++ b/Sources @@ -1 +1 @@ -ReactiveCocoa/Swift/ \ No newline at end of file +ReactiveSwift/ \ No newline at end of file From 98cb8bca52e735292d200147ad29cdd3afcea96f Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Thu, 25 Aug 2016 20:20:38 -0400 Subject: [PATCH 12/20] Split up Carthage job by platform --- .travis.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c2bee6f281..0acd878ec0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,9 +56,27 @@ matrix: - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current + - carthage build --no-skip-current --platform mac env: - - JOB=CARTHAGE + - JOB=CARTHAGE-macOS + - script: + - brew update + - brew outdated carthage || brew upgrade carthage + - carthage build --no-skip-current --platform iOS + env: + - JOB=CARTHAGE-iOS + - script: + - brew update + - brew outdated carthage || brew upgrade carthage + - carthage build --no-skip-current --platform tvOS + env: + - JOB=CARTHAGE-tvOS + - script: + - brew update + - brew outdated carthage || brew upgrade carthage + - carthage build --no-skip-current --platform watchOS + env: + - JOB=CARTHAGE-watchOS - os: osx language: generic script: swift build From 6a9bc2f6c9638d6533956b3700b21f402b089b87 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Thu, 25 Aug 2016 20:24:22 -0400 Subject: [PATCH 13/20] Make Carthage builds verbose as well This should help with timeout issues on Travis. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0acd878ec0..daec383285 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,25 +56,25 @@ matrix: - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform mac + - carthage build --no-skip-current --platform mac --verbose env: - JOB=CARTHAGE-macOS - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform iOS + - carthage build --no-skip-current --platform iOS --verbose env: - JOB=CARTHAGE-iOS - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform tvOS + - carthage build --no-skip-current --platform tvOS --verbose env: - JOB=CARTHAGE-tvOS - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform watchOS + - carthage build --no-skip-current --platform watchOS --verbose env: - JOB=CARTHAGE-watchOS - os: osx From 0d23a153d35694abeb8c9413da7a02d801e00f73 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Fri, 26 Aug 2016 08:31:24 -0400 Subject: [PATCH 14/20] Evidently verbose is a bad idea --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index daec383285..0acd878ec0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,25 +56,25 @@ matrix: - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform mac --verbose + - carthage build --no-skip-current --platform mac env: - JOB=CARTHAGE-macOS - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform iOS --verbose + - carthage build --no-skip-current --platform iOS env: - JOB=CARTHAGE-iOS - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform tvOS --verbose + - carthage build --no-skip-current --platform tvOS env: - JOB=CARTHAGE-tvOS - script: - brew update - brew outdated carthage || brew upgrade carthage - - carthage build --no-skip-current --platform watchOS --verbose + - carthage build --no-skip-current --platform watchOS env: - JOB=CARTHAGE-watchOS - os: osx From 4f6dc44788fb6c732a234295ffdb3a8c28db077d Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 6 Sep 2016 20:11:36 -0400 Subject: [PATCH 15/20] Rename file to reflect that it's an extension # Conflicts: # ReactiveCocoa.xcodeproj/project.pbxproj --- ReactiveCocoa.xcodeproj/project.pbxproj | 24 ++++++++----------- ...Lifetime.swift => NSObject+Lifetime.swift} | 0 2 files changed, 10 insertions(+), 14 deletions(-) rename ReactiveCocoa/Swift/{Lifetime.swift => NSObject+Lifetime.swift} (100%) diff --git a/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoa.xcodeproj/project.pbxproj index 1a1f901486..6bbbe45df1 100644 --- a/ReactiveCocoa.xcodeproj/project.pbxproj +++ b/ReactiveCocoa.xcodeproj/project.pbxproj @@ -9,10 +9,10 @@ /* Begin PBXBuildFile section */ 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 */; }; + 4A0E10FF1D2A92720065D310 /* NSObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */; }; + 4A0E11001D2A92720065D310 /* NSObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */; }; + 4A0E11011D2A92720065D310 /* NSObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */; }; + 4A0E11021D2A92720065D310 /* NSObject+Lifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */; }; 57A4D1B21BA13D7A00F7D4B1 /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D037646A19EDA41200A782A9 /* RACCompoundDisposableProvider.d */; }; 57A4D1B31BA13D7A00F7D4B1 /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = D03764A319EDA41200A782A9 /* RACSignalProvider.d */; }; 57A4D1B71BA13D7A00F7D4B1 /* ObjectiveCBridging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */; }; @@ -814,7 +814,7 @@ /* Begin PBXFileReference section */ 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 = ""; }; + 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Lifetime.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 = ""; }; @@ -825,8 +825,6 @@ 7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOProxySpec.m; sourceTree = ""; }; 7DFBED031CDB8C9500EE435B /* ReactiveCocoaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveCocoaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+KeyValueObserving.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 = ""; }; @@ -1521,7 +1519,7 @@ children = ( C7142DBB1CDEA167009F402D /* CocoaAction.swift */, CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */, - 4A0E10FE1D2A92720065D310 /* Lifetime.swift */, + 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */, ); name = Signals; sourceTree = ""; @@ -2253,7 +2251,7 @@ 57A4D1E01BA13D7A00F7D4B1 /* RACDynamicSignal.m in Sources */, 57A4D1E11BA13D7A00F7D4B1 /* RACEagerSequence.m in Sources */, 57D4768D1C42063C00EFE697 /* UIControl+RACSignalSupport.m in Sources */, - 4A0E11021D2A92720065D310 /* Lifetime.swift in Sources */, + 4A0E11021D2A92720065D310 /* NSObject+Lifetime.swift in Sources */, 57A4D1E21BA13D7A00F7D4B1 /* RACEmptySequence.m in Sources */, 57A4D1E31BA13D7A00F7D4B1 /* RACEmptySignal.m in Sources */, 57A4D1E41BA13D7A00F7D4B1 /* RACErrorSignal.m in Sources */, @@ -2280,7 +2278,6 @@ 57A4D1F81BA13D7A00F7D4B1 /* RACSignalSequence.m in Sources */, 57A4D1F91BA13D7A00F7D4B1 /* RACStream.m in Sources */, 57A4D1FA1BA13D7A00F7D4B1 /* RACStringSequence.m in Sources */, - 9A694EF61D5CE02E009B05BD /* UnidirectionalBinding.swift in Sources */, CD0C45E11CC9A288009F5BF0 /* DynamicProperty.swift in Sources */, 57A4D1FB1BA13D7A00F7D4B1 /* RACSubject.m in Sources */, 57A4D1FC1BA13D7A00F7D4B1 /* RACSubscriber.m in Sources */, @@ -2403,7 +2400,6 @@ 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 */, @@ -2419,7 +2415,7 @@ A9B3159C1B3940750001CB9C /* RACStringSequence.m in Sources */, CD0C45E01CC9A288009F5BF0 /* DynamicProperty.swift in Sources */, A9B3159D1B3940750001CB9C /* RACSubject.m in Sources */, - 4A0E11011D2A92720065D310 /* Lifetime.swift in Sources */, + 4A0E11011D2A92720065D310 /* NSObject+Lifetime.swift in Sources */, A9B3159E1B3940750001CB9C /* RACSubscriber.m in Sources */, A9B3159F1B3940750001CB9C /* RACSubscriptingAssignmentTrampoline.m in Sources */, A9B315A01B3940750001CB9C /* RACSubscriptionScheduler.m in Sources */, @@ -2495,7 +2491,7 @@ D037659419EDA41200A782A9 /* RACImmediateScheduler.m in Sources */, 7A7065811A3F88B8001E8354 /* RACKVOProxy.m in Sources */, D037651619EDA41200A782A9 /* NSObject+RACDeallocating.m in Sources */, - 4A0E10FF1D2A92720065D310 /* Lifetime.swift in Sources */, + 4A0E10FF1D2A92720065D310 /* NSObject+Lifetime.swift in Sources */, D037658419EDA41200A782A9 /* RACEmptySignal.m in Sources */, D037654619EDA41200A782A9 /* NSURLConnection+RACSupport.m in Sources */, D03765F019EDA41200A782A9 /* RACSubscriber.m in Sources */, @@ -2623,7 +2619,7 @@ D037665F19EDA41200A782A9 /* UITextField+RACSignalSupport.m in Sources */, D037650F19EDA41200A782A9 /* NSNotificationCenter+RACSupport.m in Sources */, D03765FB19EDA41200A782A9 /* RACSubscriptionScheduler.m in Sources */, - 4A0E11001D2A92720065D310 /* Lifetime.swift in Sources */, + 4A0E11001D2A92720065D310 /* NSObject+Lifetime.swift in Sources */, D037658119EDA41200A782A9 /* RACEmptySequence.m in Sources */, D0C312E019EF2A5800984962 /* ObjectiveCBridging.swift in Sources */, D037654B19EDA41200A782A9 /* NSUserDefaults+RACSupport.m in Sources */, diff --git a/ReactiveCocoa/Swift/Lifetime.swift b/ReactiveCocoa/Swift/NSObject+Lifetime.swift similarity index 100% rename from ReactiveCocoa/Swift/Lifetime.swift rename to ReactiveCocoa/Swift/NSObject+Lifetime.swift From 5c5e746dd57f9c346e3164256e3ff8d14a910e12 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 6 Sep 2016 20:11:44 -0400 Subject: [PATCH 16/20] Remove unnecessary import --- ReactiveCocoa/Swift/NSObject+Lifetime.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ReactiveCocoa/Swift/NSObject+Lifetime.swift b/ReactiveCocoa/Swift/NSObject+Lifetime.swift index 388800548a..1c74e43063 100644 --- a/ReactiveCocoa/Swift/NSObject+Lifetime.swift +++ b/ReactiveCocoa/Swift/NSObject+Lifetime.swift @@ -1,6 +1,5 @@ import Foundation import ReactiveSwift -import enum Result.NoError #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) From 08dbc8e56913d61e677854ed6fafc474f01788e1 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 6 Sep 2016 20:11:54 -0400 Subject: [PATCH 17/20] Remove unnecessary platform conditional --- ReactiveCocoa/Swift/NSObject+Lifetime.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ReactiveCocoa/Swift/NSObject+Lifetime.swift b/ReactiveCocoa/Swift/NSObject+Lifetime.swift index 1c74e43063..1f220d12d4 100644 --- a/ReactiveCocoa/Swift/NSObject+Lifetime.swift +++ b/ReactiveCocoa/Swift/NSObject+Lifetime.swift @@ -1,8 +1,6 @@ import Foundation import ReactiveSwift -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - private var lifetimeKey: UInt8 = 0 private var lifetimeTokenKey: UInt8 = 0 @@ -25,5 +23,3 @@ extension NSObject { return lifetime } } - -#endif From 7e73dbc5b04836eba7b88c4d754d0a16d2beb975 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 6 Sep 2016 20:12:14 -0400 Subject: [PATCH 18/20] Flatten Xcode hierarchy for Swift code --- ReactiveCocoa.xcodeproj/project.pbxproj | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoa.xcodeproj/project.pbxproj index 6bbbe45df1..401145b1fb 100644 --- a/ReactiveCocoa.xcodeproj/project.pbxproj +++ b/ReactiveCocoa.xcodeproj/project.pbxproj @@ -1514,24 +1514,6 @@ path = "Objective-C"; sourceTree = ""; }; - D03B4A3919F4C25F009E02AC /* Signals */ = { - isa = PBXGroup; - children = ( - C7142DBB1CDEA167009F402D /* CocoaAction.swift */, - CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */, - 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */, - ); - name = Signals; - sourceTree = ""; - }; - D03B4A3B19F4C281009E02AC /* Extensions */ = { - isa = PBXGroup; - children = ( - 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */, - ); - name = Extensions; - sourceTree = ""; - }; D04725E019E49ED7006002AA = { isa = PBXGroup; children = ( @@ -1671,9 +1653,11 @@ D0C312B919EF2A3000984962 /* Swift */ = { isa = PBXGroup; children = ( + C7142DBB1CDEA167009F402D /* CocoaAction.swift */, + CD0C45DD1CC9A288009F5BF0 /* DynamicProperty.swift */, + 9AD0F0691D48BA4800ADFAB7 /* NSObject+KeyValueObserving.swift */, + 4A0E10FE1D2A92720065D310 /* NSObject+Lifetime.swift */, D0C312C419EF2A5800984962 /* ObjectiveCBridging.swift */, - D03B4A3919F4C25F009E02AC /* Signals */, - D03B4A3B19F4C281009E02AC /* Extensions */, ); path = Swift; sourceTree = ""; From 2838f36fb49c0ef923ae1905844d58eb391726d0 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Tue, 6 Sep 2016 20:13:10 -0400 Subject: [PATCH 19/20] Fix description --- ReactiveSwiftTests/LifetimeSpec.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactiveSwiftTests/LifetimeSpec.swift b/ReactiveSwiftTests/LifetimeSpec.swift index 4c06dca363..215a9d1a48 100644 --- a/ReactiveSwiftTests/LifetimeSpec.swift +++ b/ReactiveSwiftTests/LifetimeSpec.swift @@ -5,7 +5,7 @@ import Result final class LifetimeSpec: QuickSpec { override func spec() { - describe("NSObject.rac_lifetime") { + describe("Lifetime") { it("should complete its lifetime ended signal when the it deinitializes") { let object = MutableReference(TestObject()) From 8d5f963f0705c849b9574fcdd5033426be16b3d8 Mon Sep 17 00:00:00 2001 From: Matt Diephouse Date: Fri, 9 Sep 2016 09:50:06 -0400 Subject: [PATCH 20/20] Remove unneeded build settings --- ReactiveCocoa.xcodeproj/project.pbxproj | 5 ----- ReactiveSwift.xcodeproj/project.pbxproj | 5 ----- 2 files changed, 10 deletions(-) diff --git a/ReactiveCocoa.xcodeproj/project.pbxproj b/ReactiveCocoa.xcodeproj/project.pbxproj index 401145b1fb..2521e58730 100644 --- a/ReactiveCocoa.xcodeproj/project.pbxproj +++ b/ReactiveCocoa.xcodeproj/project.pbxproj @@ -2926,11 +2926,9 @@ BITCODE_GENERATION_MODE = bitcode; 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; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SWIFT_VERSION = 3.0; @@ -2948,7 +2946,6 @@ BITCODE_GENERATION_MODE = bitcode; 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; @@ -3077,7 +3074,6 @@ BITCODE_GENERATION_MODE = bitcode; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; @@ -3151,7 +3147,6 @@ BITCODE_GENERATION_MODE = bitcode; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)-Tests"; diff --git a/ReactiveSwift.xcodeproj/project.pbxproj b/ReactiveSwift.xcodeproj/project.pbxproj index b68999f786..aed684e9b1 100644 --- a/ReactiveSwift.xcodeproj/project.pbxproj +++ b/ReactiveSwift.xcodeproj/project.pbxproj @@ -1220,11 +1220,9 @@ BITCODE_GENERATION_MODE = bitcode; 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; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SWIFT_VERSION = 3.0; @@ -1242,7 +1240,6 @@ BITCODE_GENERATION_MODE = bitcode; 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; @@ -1367,7 +1364,6 @@ BITCODE_GENERATION_MODE = bitcode; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)"; @@ -1439,7 +1435,6 @@ BITCODE_GENERATION_MODE = bitcode; CODE_SIGNING_REQUIRED = NO; CURRENT_PROJECT_VERSION = 1; - ENABLE_BITCODE = NO; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = "org.reactivecocoa.$(PRODUCT_NAME:rfc1034identifier)-Tests";