├── .gitmodules
├── Assets
└── logo.png
├── Playground.playground
├── Pages
│ ├── Working with UI.xcplaygroundpage
│ │ ├── timeline.xctimeline
│ │ └── Contents.swift
│ ├── Exploration.xcplaygroundpage
│ │ └── Contents.swift
│ ├── Creating Signals.xcplaygroundpage
│ │ └── Contents.swift
│ ├── Observing Signals.xcplaygroundpage
│ │ └── Contents.swift
│ └── Transforming Signals.xcplaygroundpage
│ │ └── Contents.swift
└── contents.xcplayground
├── Tests
├── LinuxMain.swift
└── ReactiveKitTests
│ ├── Scheduler.swift
│ ├── PublishedTests.swift
│ ├── Stress.swift
│ ├── CombineTests.swift
│ └── PropertyTests.swift
├── ReactiveKit.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── ReactiveKit.xcscmblueprint
└── xcshareddata
│ ├── xcbaselines
│ └── ECBCCDD91BEB6B9B00723476.xcbaseline
│ │ ├── FB4F14DE-1778-47BF-9AF1-A000D430FBBA.plist
│ │ ├── 15721443-CEB9-478C-919D-711F1A7CCA56.plist
│ │ └── Info.plist
│ └── xcschemes
│ ├── ReactiveKit-tvOS.xcscheme
│ ├── ReactiveKit-macOS.xcscheme
│ ├── ReactiveKit-watchOS.xcscheme
│ └── ReactiveKit-iOS.xcscheme
├── ReactiveKit.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── ReactiveKit.xcscmblueprint
├── Package.swift
├── .gitignore
├── Supporting Files
├── TestsInfo.plist
├── Info.plist
└── ReactiveKit.h
├── ReactiveKit.podspec
├── .github
└── workflows
│ └── swift.yml
├── LICENSE
├── .travis.yml
└── Sources
├── Atomic.swift
├── Cancellable.swift
├── Subscription.swift
├── Subscribers
├── Completion.swift
├── Demand.swift
├── Sink.swift
└── Accumulator.swift
├── Lock.swift
├── Subscriber.swift
├── Published.swift
├── Publishers
├── Deferred.swift
└── Empty.swift
├── Scheduler.swift
├── Deallocatable.swift
├── ObservableObject.swift
├── SignalProtocol+Sequence.swift
├── SignalProtocol+Threading.swift
├── SignalProtocol+Optional.swift
├── SignalProtocol+Event.swift
├── Signal.Event.swift
├── Reactive.swift
├── Combine.swift
├── SignalProtocol+Async.swift
├── Property.swift
├── ExecutionContext.swift
├── SignalProtocol+Result.swift
├── Observer.swift
├── Connectable.swift
├── SignalProtocol.swift
├── LoadingProperty.swift
├── SignalProtocol+Transforming.swift
├── Subjects.swift
├── Bindable.swift
├── SignalProtocol+ErrorHandling.swift
└── SignalProtocol+Utilities.swift
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DeclarativeHub/ReactiveKit/HEAD/Assets/logo.png
--------------------------------------------------------------------------------
/Playground.playground/Pages/Working with UI.xcplaygroundpage/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import ReactiveKitTests
3 |
4 | XCTMain([
5 | testCase(SignalTests.allTests),
6 | testCase(PropertyTests.allTests),
7 | testCase(SubjectTests.allTests),
8 | ])
9 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ReactiveKit.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ReactiveKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Playground.playground/Pages/Exploration.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play
2 |
3 | import ReactiveKit
4 | import PlaygroundSupport
5 |
6 | //: Explore ReactiveKit here
7 |
8 | enum MyError: Error {
9 | case unknown
10 | }
11 |
12 | let a = Signal(sequence: 0...4, interval: 0.5)
13 | let b = SafeSignal(sequence: 0...2, interval: 2)
14 |
15 | b.concat(with: b)
16 |
17 |
--------------------------------------------------------------------------------
/Playground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "ReactiveKit",
6 | platforms: [
7 | .macOS(.v10_10), .iOS(.v9), .tvOS(.v9), .watchOS(.v2)
8 | ],
9 | products: [
10 | .library(name: "ReactiveKit", targets: ["ReactiveKit"])
11 | ],
12 | targets: [
13 | .target(name: "ReactiveKit", path: "Sources"),
14 | .testTarget(name: "ReactiveKitTests", dependencies: ["ReactiveKit"])
15 | ]
16 | )
17 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ECBCCDD91BEB6B9B00723476.xcbaseline/FB4F14DE-1778-47BF-9AF1-A000D430FBBA.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | SignalTests
8 |
9 | testPerformance()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 0.078778
15 | baselineIntegrationDisplayName
16 | Local Baseline
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ECBCCDD91BEB6B9B00723476.xcbaseline/15721443-CEB9-478C-919D-711F1A7CCA56.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | SignalTests
8 |
9 | testPerformance()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 0.077
15 | baselineIntegrationDisplayName
16 | Local Baseline
17 | maxPercentRelativeStandardDeviation
18 | 20
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 | .DS_Store
20 |
21 | # CocoaPods
22 | #
23 | # We recommend against adding the Pods directory to your .gitignore. However
24 | # you should judge for yourself, the pros and cons are mentioned at:
25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
26 | #
27 | # Pods/
28 |
29 | # Carthage
30 | #
31 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
32 | # Carthage/Checkouts
33 |
34 | Carthage/Build
35 |
36 | # Swift Package Manager
37 |
38 | .build
39 | .swiftpm
--------------------------------------------------------------------------------
/Supporting Files/TestsInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ReactiveKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "ReactiveKit"
3 | s.version = "3.19.1"
4 | s.summary = "A Swift Reactive Programming Framework"
5 | s.description = "ReactiveKit is a Swift framework for reactive and functional reactive programming."
6 | s.homepage = "https://github.com/DeclarativeHub/ReactiveKit"
7 | s.license = 'MIT'
8 | s.author = { "Srdan Rasic" => "srdan.rasic@gmail.com" }
9 | s.source = { :git => "https://github.com/DeclarativeHub/ReactiveKit.git", :tag => "v3.19.1" }
10 |
11 | s.ios.deployment_target = '8.0'
12 | s.osx.deployment_target = '10.11'
13 | s.watchos.deployment_target = '2.0'
14 | s.tvos.deployment_target = '9.0'
15 |
16 | s.source_files = 'Sources/**/*.swift', 'ReactiveKit/*.{h,m}'
17 | s.requires_arc = true
18 | s.swift_version = '5.0'
19 | end
20 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test
2 |
3 | on:
4 | - push
5 | - pull_request
6 |
7 | jobs:
8 | linux:
9 | name: Test on Linux
10 | runs-on: ubuntu-latest
11 | container:
12 | image: swift:latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: Show verion
16 | run: swift --version
17 | - name: Build
18 | run: swift build -v
19 | - name: Run tests
20 | run: swift test -v
21 |
22 | macOS:
23 | name: Test on macOS
24 | runs-on: macOS-latest
25 | steps:
26 | - uses: actions/checkout@v1
27 | - name: Build
28 | run: swift build -v
29 | - name: Run tests
30 | run: swift test -v
31 |
32 | android:
33 | name: Test on Android
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v4
37 | - name: Test Swift Package on Android
38 | uses: skiptools/swift-android-action@v2
39 |
--------------------------------------------------------------------------------
/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 ReactiveKit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Tests/ReactiveKitTests/Scheduler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Scheduler.swift
3 | // ReactiveKit-Tests
4 | //
5 | // Created by Srdan Rasic on 01/01/2020.
6 | // Copyright © 2020 DeclarativeHub. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveKit
11 |
12 | /// A scheduler that buffers actions and enables their manual execution through `run` methods.
13 | class Scheduler: ReactiveKit.Scheduler {
14 |
15 | private var availableRuns = 0
16 | private var scheduledBlocks: [() -> Void] = []
17 | private(set) var numberOfRuns = 0
18 |
19 | func schedule(_ action: @escaping () -> Void) {
20 | scheduledBlocks.append(action)
21 | tryRun()
22 | }
23 |
24 | func runOne() {
25 | guard availableRuns < Int.max else { return }
26 | availableRuns += 1
27 | tryRun()
28 | }
29 |
30 | func runRemaining() {
31 | availableRuns = Int.max
32 | tryRun()
33 | }
34 |
35 | private func tryRun() {
36 | while availableRuns > 0 && scheduledBlocks.count > 0 {
37 | let block = scheduledBlocks.removeFirst()
38 | block()
39 | numberOfRuns += 1
40 | availableRuns -= 1
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | osx_image: xcode10.2
3 |
4 | jobs:
5 | include:
6 | - stage: "Xcode"
7 | name: "Run tests on iOS"
8 | script: xcrun xcodebuild test -destination "platform=iOS Simulator,OS=12.2,name=iPhone X" -workspace "ReactiveKit.xcworkspace" -scheme "ReactiveKit-iOS"
9 | after_success: 'bash <(curl -s https://codecov.io/bash)'
10 | -
11 | name: "Build for macOS"
12 | script: xcrun xcodebuild build -destination "platform=macOS" -workspace "ReactiveKit.xcworkspace" -scheme "ReactiveKit-macOS"
13 | -
14 | name: "Build for tvOS"
15 | script: xcrun xcodebuild build -destination "platform=tvOS Simulator,OS=12.2,name=Apple TV 4K" -workspace "ReactiveKit.xcworkspace" -scheme "ReactiveKit-tvOS"
16 | -
17 | name: "Build for watchOS"
18 | script: xcrun xcodebuild build -destination "platform=watchOS Simulator,OS=5.2,name=Apple Watch Series 4 - 44mm" -workspace "ReactiveKit.xcworkspace" -scheme "ReactiveKit-watchOS"
19 |
20 | - stage: "Swift Package Manager"
21 | name: "Run Tests"
22 | script: swift test
23 |
24 | - stage: "CocoaPods"
25 | name: "Lint Podspec"
26 | script: pod lib lint
27 |
--------------------------------------------------------------------------------
/Tests/ReactiveKitTests/PublishedTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PublishedTests.swift
3 | // ReactiveKit-iOS
4 | //
5 | // Created by Ibrahim Koteish on 15/12/2019.
6 | // Copyright © 2019 DeclarativeHub. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import ReactiveKit
11 |
12 | #if compiler(>=5.1)
13 |
14 | class PublishedTests: XCTestCase {
15 |
16 | class User: ReactiveKit.ObservableObject {
17 | @ReactiveKit.Published var id: Int
18 | init(id: Int) { self.id = id }
19 | }
20 |
21 | func testPublished() {
22 |
23 | let user = User(id: 0)
24 |
25 | let objectSubscriber = Subscribers.Accumulator()
26 | let propertySubscriber = Subscribers.Accumulator()
27 |
28 | user.objectWillChange.subscribe(objectSubscriber)
29 | user.$id.subscribe(propertySubscriber)
30 |
31 | XCTAssertEqual(user.id, 0)
32 |
33 | user.id = 1
34 | user.id = 2
35 | user.id = 3
36 |
37 | XCTAssertEqual(propertySubscriber.values, [0, 1, 2, 3])
38 | XCTAssertFalse(propertySubscriber.isFinished)
39 |
40 | XCTAssertEqual(objectSubscriber.values.count, 3)
41 | XCTAssertFalse(objectSubscriber.isFinished)
42 | }
43 | }
44 |
45 | #endif
46 |
--------------------------------------------------------------------------------
/Sources/Atomic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Atomic.swift
3 | // ReactiveKit
4 | //
5 | // Created by Srdan Rasic on 06/11/2019.
6 | // Copyright © 2019 DeclarativeHub. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class Atomic {
12 |
13 | private var _value: T
14 | private let lock: NSLocking
15 |
16 | init(_ value: T, lock: NSLocking = NSRecursiveLock()) {
17 | self._value = value
18 | self.lock = lock
19 | }
20 |
21 | var value: T {
22 | get {
23 | lock.lock()
24 | let value = _value
25 | lock.unlock()
26 | return value
27 | }
28 | set {
29 | lock.lock()
30 | _value = newValue
31 | lock.unlock()
32 | }
33 | }
34 |
35 | func mutate(_ block: (inout T) -> Void) {
36 | lock.lock()
37 | block(&_value)
38 | lock.unlock()
39 | }
40 |
41 | func mutateAndRead(_ block: (T) -> T) -> T {
42 | lock.lock()
43 | let newValue = block(_value)
44 | _value = newValue
45 | lock.unlock()
46 | return newValue
47 | }
48 |
49 | func readAndMutate(_ block: (T) -> T) -> T {
50 | lock.lock()
51 | let oldValue = _value
52 | _value = block(_value)
53 | lock.unlock()
54 | return oldValue
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Cancellable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2020 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public protocol Cancellable {
28 | func cancel()
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/Subscription.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2020 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public protocol Subscription: Cancellable {
28 | func request(_ demand: Subscribers.Demand)
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/Subscribers/Completion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2019 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public enum Subscribers {
28 |
29 | public enum Completion where Failure: Error {
30 | case finished
31 | case failure(Failure)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Supporting Files/ReactiveKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2015 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | #import
26 |
27 | //! Project version number for ReactiveKit.
28 | FOUNDATION_EXPORT double ReactiveKitVersionNumber;
29 |
30 | //! Project version string for ReactiveKit.
31 | FOUNDATION_EXPORT const unsigned char ReactiveKitVersionString[];
32 |
--------------------------------------------------------------------------------
/Sources/Lock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2016 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension NSLock {
28 |
29 | public convenience init(name: String) {
30 | self.init()
31 | self.name = name
32 | }
33 | }
34 |
35 | extension NSRecursiveLock {
36 |
37 | public convenience init(name: String) {
38 | self.init()
39 | self.name = name
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/Subscriber.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2020 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | public protocol Subscriber {
28 |
29 | associatedtype Input
30 | associatedtype Failure: Error
31 |
32 | func receive(subscription: Subscription)
33 |
34 | func receive(_ input: Self.Input) -> Subscribers.Demand
35 |
36 | func receive(completion: Subscribers.Completion)
37 | }
38 |
39 | extension Subscriber where Self.Input == Void {
40 |
41 | public func receive() -> Subscribers.Demand {
42 | return receive(())
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Playground.playground/Pages/Creating Signals.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import ReactiveKit
5 | import PlaygroundSupport
6 |
7 | PlaygroundPage.current.needsIndefiniteExecution = true
8 |
9 | //: # Creating Signals
10 | //: Uncomment the `observe { ... }` line to explore the behaviour!
11 |
12 | SafeSignal(just: "Jim")
13 | //.observe { print($0) }
14 |
15 | SafeSignal(just: "Jim after 1 second", after: 1)
16 | //.observe { print($0) }
17 |
18 | SafeSignal(sequence: [1, 2, 3])
19 | //.observe { print($0) }
20 |
21 | SafeSignal(sequence: [1, 2, 3], interval: 1)
22 | //.observe { print($0) }
23 |
24 | SafeSignal(sequence: 1..., interval: 1)
25 | //.observe { print($0) }
26 |
27 | SafeSignal(performing: {
28 | (0...1000).reduce(0, +)
29 | })
30 | //.observe { print($0) }
31 |
32 | Signal(evaluating: {
33 | if let file = try? String(contentsOf: URL(fileURLWithPath: "list.txt")) {
34 | return .success(file)
35 | } else {
36 | return .failure(NSError(domain: "No such file", code: 0, userInfo: nil))
37 | }
38 | })
39 | //.observe { print($0) }
40 |
41 | Signal(catching: {
42 | try String(contentsOf: URL(string: "https://pokeapi.co/api/v2/pokemon/ditto/")!, encoding: .utf8)
43 | })
44 | //.observe { print($0) }
45 |
46 | Signal { observer in
47 | observer.receive(1)
48 | observer.receive(2)
49 | observer.receive(completion: .finished)
50 | return BlockDisposable {
51 | print("disposed")
52 | }
53 | }
54 | //.observe { print($0) }
55 |
56 | var didTapReload: () -> Void = {}
57 | let reloadTaps = Signal(takingOver: &didTapReload)
58 |
59 | reloadTaps
60 | //.observeNext { print("reload") }
61 |
62 | didTapReload()
63 | didTapReload()
64 |
65 | //: [Next](@next)
66 |
--------------------------------------------------------------------------------
/ReactiveKit.xcworkspace/xcshareddata/ReactiveKit.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "C1F6A63EF115019142AF6E3E8A173E32E3445DB3",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "D78924967B5ECB0A1D20E197FAB93ACBFA47F0D1" : 9223372036854775807,
8 | "C1F6A63EF115019142AF6E3E8A173E32E3445DB3" : 9223372036854775807
9 | },
10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "380029EF-6AB5-47E0-9552-F3F289E07C83",
11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
12 | "D78924967B5ECB0A1D20E197FAB93ACBFA47F0D1" : "..\/..",
13 | "C1F6A63EF115019142AF6E3E8A173E32E3445DB3" : "ReactiveKit\/"
14 | },
15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "ReactiveKit",
16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204,
17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "ReactiveKit.xcworkspace",
18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
19 | {
20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:ReactiveKit\/ReactiveKit.git",
21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C1F6A63EF115019142AF6E3E8A173E32E3445DB3"
23 | },
24 | {
25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/SwiftBond\/Bond.git",
26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D78924967B5ECB0A1D20E197FAB93ACBFA47F0D1"
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/Sources/Published.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Published.swift
3 | // ReactiveKit
4 | //
5 | // Created by Srdan Rasic on 07/12/2019.
6 | // Copyright © 2019 DeclarativeHub. All rights reserved.
7 | //
8 |
9 | #if compiler(>=5.1)
10 |
11 | @propertyWrapper
12 | public struct Published {
13 |
14 | private let publisher: Publisher
15 | private let willChangeSubject = PassthroughSubject()
16 |
17 | public init(wrappedValue: Value) {
18 | publisher = Publisher(wrappedValue)
19 | }
20 |
21 | /// A publisher for properties used with the `@Published` attribute.
22 | public struct Publisher: SignalProtocol {
23 | public typealias Element = Value
24 | public typealias Error = Never
25 |
26 | fileprivate let property: Property
27 |
28 | public func observe(with observer: @escaping (Signal.Event) -> Void) -> Disposable {
29 | self.property.observe(with: observer)
30 | }
31 |
32 | fileprivate init(_ output: Element) {
33 | self.property = Property(output)
34 | }
35 | }
36 |
37 | public var wrappedValue: Value {
38 | get { self.publisher.property.value }
39 | nonmutating set {
40 | self.willChangeSubject.send()
41 | self.publisher.property.value = newValue
42 | }
43 | }
44 |
45 | public var projectedValue: Publisher {
46 | get { publisher }
47 | }
48 | }
49 |
50 | protocol _MutablePropertyWrapper {
51 | var willChange: Signal { mutating get }
52 | }
53 |
54 | extension Published: _MutablePropertyWrapper {
55 |
56 | var willChange: Signal {
57 | mutating get {
58 | return willChangeSubject.toSignal()
59 | }
60 | }
61 | }
62 |
63 | #endif
64 |
--------------------------------------------------------------------------------
/Sources/Publishers/Deferred.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Deferred.swift
3 | // GlovoCourier
4 | //
5 | // Created by Ibrahim Koteish on 01/12/2019.
6 | // Copyright © 2019 Glovo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// A signal that awaits subscription before running the supplied closure
12 | /// to create a signal for the new subscriber.
13 | public struct Deferred: SignalProtocol {
14 |
15 | /// The kind of values published by this signal.
16 | public typealias Element = DeferredSignal.Element
17 |
18 | /// The kind of errors this signal might publish.
19 | ///
20 | /// Use `Never` if this `signal` does not publish errors.
21 | public typealias Error = DeferredSignal.Error
22 |
23 | /// The closure to execute when it receives a subscription.
24 | ///
25 | /// The signal returned by this closure immediately
26 | /// receives the incoming subscription.
27 | public let signalFactory: () -> DeferredSignal
28 |
29 | /// Creates a deferred signal.
30 | ///
31 | /// - Parameter signalFactory: The closure to execute
32 | /// when calling `observe(with:)`.
33 | public init(signalFactory: @escaping () -> DeferredSignal) {
34 | self.signalFactory = signalFactory
35 | }
36 |
37 | /// This function is called to attach the specified `Observer`
38 | /// to this `Signal` by `observe(with:)`
39 | ///
40 | /// - Parameters:
41 | /// - observer: The observer to attach to this `Signal`.
42 | /// once attached it can begin to receive values.
43 | public func observe(
44 | with observer: @escaping (Signal.Event) -> Void)
45 | -> Disposable
46 | {
47 | let deferredSignal = signalFactory()
48 | return deferredSignal.observe(with: observer)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Subscribers/Demand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2020 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension Subscribers {
28 |
29 | public struct Demand: Equatable, Hashable {
30 |
31 | public let value: Int
32 |
33 | private init(value: Int) {
34 | self.value = value
35 | }
36 |
37 | public static let unlimited: Subscribers.Demand = .init(value: Int.max)
38 |
39 | @available(*, unavailable, message: "Not supported yet.")
40 | public static func max(_ value: Int) -> Subscribers.Demand {
41 | return .init(value: value)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Tests/ReactiveKitTests/Stress.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Common.swift
3 | // ReactiveKit
4 | //
5 | // Created by Srdan Rasic on 14/04/16.
6 | // Copyright © 2016 Srdan Rasic. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import ReactiveKit
11 |
12 | extension SignalProtocol {
13 |
14 | func stress(
15 | with sendElements: [(Int) -> Void],
16 | queuesCount: Int = 3,
17 | eventsCount: Int = 3000,
18 | timeout: Double = 2,
19 | expectation: XCTestExpectation
20 | ) -> Disposable {
21 |
22 | let dispatchQueues = Array((0..(
45 | with subjects: [S],
46 | queuesCount: Int = 3,
47 | eventsCount: Int = 3000,
48 | timeout: Double = 2,
49 | expectation: XCTestExpectation
50 | ) -> Disposable where S.Element == Int {
51 | return stress(
52 | with: subjects.map { subject in { event in subject.send(event) } },
53 | queuesCount: queuesCount,
54 | eventsCount: eventsCount,
55 | timeout: timeout,
56 | expectation: expectation
57 | )
58 | }
59 | }
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Sources/Scheduler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2019 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 | import Dispatch
27 |
28 | /// A protocol that defines when and how to execute a closure.
29 | public protocol Scheduler {
30 |
31 | /// Performs the action at the next possible opportunity.
32 | func schedule(_ action: @escaping () -> Void)
33 | }
34 |
35 | extension ExecutionContext: Scheduler {
36 |
37 | @inlinable
38 | public func schedule(_ action: @escaping () -> Void) {
39 | context(action)
40 | }
41 | }
42 |
43 | extension DispatchQueue: Scheduler {
44 |
45 | @inlinable
46 | public func schedule(_ action: @escaping () -> Void) {
47 | async(execute: action)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Tests/ReactiveKitTests/CombineTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CombineTests.swift
3 | // ReactiveKit-Tests
4 | //
5 | // Created by Srdan Rasic on 18/01/2020.
6 | // Copyright © 2020 DeclarativeHub. All rights reserved.
7 | //
8 |
9 | #if canImport(Combine)
10 |
11 | import XCTest
12 | import ReactiveKit
13 | import Combine
14 |
15 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
16 | class CombineTests: XCTestCase {
17 |
18 | func testPublisherToSignal() {
19 | let publisher = Combine.PassthroughSubject()
20 | let subscriber = ReactiveKit.Subscribers.Accumulator()
21 | publisher.toSignal().subscribe(subscriber)
22 | publisher.send(0)
23 | publisher.send(1)
24 | publisher.send(2)
25 | publisher.send(completion: .failure(.error))
26 | publisher.send(3)
27 | XCTAssertEqual(subscriber.values, [0, 1, 2])
28 | XCTAssertFalse(subscriber.isFinished)
29 | XCTAssertTrue(subscriber.isFailure)
30 | }
31 |
32 | func testSignalToPublisher() {
33 | let publisher = ReactiveKit.PassthroughSubject()
34 | var receivedValues: [Int] = []
35 | var receivedCompletion: Combine.Subscribers.Completion? = nil
36 |
37 | let cancellable = publisher.toPublisher().sink(
38 | receiveCompletion: { (completion) in
39 | receivedCompletion = completion
40 | }, receiveValue: { value in
41 | receivedValues.append(value)
42 | }
43 | )
44 |
45 | publisher.send(0)
46 | publisher.send(1)
47 | publisher.send(2)
48 | publisher.send(completion: .failure(.error))
49 | publisher.send(3)
50 |
51 | XCTAssertEqual(receivedValues, [0, 1, 2])
52 | XCTAssertEqual(receivedCompletion, .failure(.error))
53 |
54 | cancellable.cancel()
55 | }
56 | }
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/Sources/Deallocatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Deallocatable.swift
3 | // ReactiveKit
4 | //
5 | // Created by Srdan Rasic on 17/03/2017.
6 | // Copyright © 2017 Srdan Rasic. All rights reserved.
7 | //
8 |
9 | /// A type that notifies about its own deallocation.
10 | ///
11 | /// `Deallocatable` can be used as a binding target. For example,
12 | /// instead of observing a signal, one can bind it to a `Deallocatable`.
13 | ///
14 | /// class View: Deallocatable { ... }
15 | ///
16 | /// let view: View = ...
17 | /// let signal: SafeSignal = ...
18 | ///
19 | /// signal.bind(to: view) { view, number in
20 | /// view.display(number)
21 | /// }
22 | public protocol Deallocatable: class {
23 |
24 | /// A signal that fires `completed` event when the receiver is deallocated.
25 | var deallocated: SafeSignal { get }
26 | }
27 |
28 | /// A type that provides a dispose bag.
29 | /// `DisposeBagProvider` conforms to `Deallocatable` out of the box.
30 | public protocol DisposeBagProvider: Deallocatable {
31 |
32 | /// A `DisposeBag` that can be used to dispose observations and bindings.
33 | var bag: DisposeBag { get }
34 | }
35 |
36 | extension DisposeBagProvider {
37 |
38 | /// A signal that fires `completed` event when the receiver is deallocated.
39 | public var deallocated: SafeSignal {
40 | return bag.deallocated
41 | }
42 | }
43 |
44 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
45 |
46 | import ObjectiveC.runtime
47 |
48 | extension NSObject: DisposeBagProvider {
49 |
50 | private struct AssociatedKeys {
51 | static var DisposeBagKey = "DisposeBagKey"
52 | }
53 |
54 | /// A `DisposeBag` that can be used to dispose observations and bindings.
55 | public var bag: DisposeBag {
56 | if let disposeBag = objc_getAssociatedObject(self, &NSObject.AssociatedKeys.DisposeBagKey) {
57 | return disposeBag as! DisposeBag
58 | } else {
59 | let disposeBag = DisposeBag()
60 | objc_setAssociatedObject(self, &NSObject.AssociatedKeys.DisposeBagKey, disposeBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
61 | return disposeBag
62 | }
63 | }
64 | }
65 |
66 | #endif
67 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ECBCCDD91BEB6B9B00723476.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | 15721443-CEB9-478C-919D-711F1A7CCA56
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i5
17 | cpuSpeedInMHz
18 | 2700
19 | logicalCPUCoresPerPackage
20 | 4
21 | modelCode
22 | MacBookPro12,1
23 | physicalCPUCoresPerPackage
24 | 2
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 | targetDevice
31 |
32 | modelCode
33 | iPhone9,2
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 | FB4F14DE-1778-47BF-9AF1-A000D430FBBA
39 |
40 | localComputer
41 |
42 | busSpeedInMHz
43 | 100
44 | cpuCount
45 | 1
46 | cpuKind
47 | Intel Core i5
48 | cpuSpeedInMHz
49 | 2700
50 | logicalCPUCoresPerPackage
51 | 4
52 | modelCode
53 | MacBookPro12,1
54 | physicalCPUCoresPerPackage
55 | 2
56 | platformIdentifier
57 | com.apple.platform.macosx
58 |
59 | targetArchitecture
60 | x86_64
61 | targetDevice
62 |
63 | modelCode
64 | iPhone9,1
65 | platformIdentifier
66 | com.apple.platform.iphonesimulator
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Playground.playground/Pages/Working with UI.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import ReactiveKit
5 | import PlaygroundSupport
6 | import UIKit
7 |
8 | PlaygroundPage.current.needsIndefiniteExecution = true
9 |
10 | //: # Working with UI
11 |
12 | // Let's play with Pokemons again!
13 |
14 | struct Pokemon: Codable {
15 | let name: String
16 | let height: Int
17 | let weight: Int
18 | }
19 |
20 | // We will make a Pokemon profile view and fill it with a Pokemon details
21 |
22 | class PokeProfile: UIView {
23 | let nameLabel = UILabel()
24 | let heightLabel = UILabel()
25 | let weightLabel = UILabel()
26 |
27 | override init(frame: CGRect) {
28 | super.init(frame: frame)
29 | let stackView = UIStackView(arrangedSubviews: [nameLabel, heightLabel, weightLabel])
30 | stackView.distribution = .fillEqually
31 | stackView.axis = .vertical
32 | stackView.frame = frame
33 | addSubview(stackView)
34 | backgroundColor = .white
35 | }
36 | required init?(coder aDecoder: NSCoder) { fatalError() }
37 | }
38 |
39 | // Open Assistent Editor to see the view!
40 | let profileView = PokeProfile(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
41 | PlaygroundPage.current.liveView = profileView
42 |
43 | // Load the Pokemon from PokéAPI and bind it to the view.
44 | // First we fetch the JSON response as Data
45 | Signal { try Data(contentsOf: URL(string: "https://pokeapi.co/api/v2/pokemon/chandelure")!) }
46 | // Then decode the Pokemon type from the data
47 | .map { try JSONDecoder().decode(Pokemon.self, from: $0) }
48 | // Make sure we do the fetching and parsing on a non-main thread (queue)
49 | .executeOn(.global(qos: .utility))
50 | // We can only bind non-failable signals, so handle the potential error somehow
51 | .suppressError(logging: true)
52 | // Finally, bind the data to the view, ensuring the main thread
53 | .bind(to: profileView, context: .main) { view, pokemon in
54 | view.nameLabel.text = "Name: \(pokemon.name)"
55 | view.heightLabel.text = "Height: \(pokemon.height)"
56 | view.weightLabel.text = "Weight: \(pokemon.weight)"
57 | }
58 |
59 | // Make sure to check out [Bond](https://github.com/DeclarativeHub/Bond) framework that
60 | // provides many extensions that simplify usage of ReactiveKit in UI based apps.
61 |
62 | //: [Next](@next)
63 |
--------------------------------------------------------------------------------
/Sources/ObservableObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2019 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | #if compiler(>=5.1)
26 |
27 | import Foundation
28 |
29 | /// A type of object with a publisher that emits before the object has changed.
30 | ///
31 | /// By default an `ObservableObject` will synthesize an `objectWillChange`
32 | /// publisher that emits before any of its `@Published` properties changes:
33 | public protocol ObservableObject: AnyObject {
34 |
35 | /// The type of signal that emits before the object has changed.
36 | associatedtype ObjectWillChangeSignal: SignalProtocol = Signal where Self.ObjectWillChangeSignal.Error == Never
37 |
38 | /// A signal that emits before the object has changed.
39 | var objectWillChange: Self.ObjectWillChangeSignal { get }
40 | }
41 |
42 | extension ObservableObject where Self.ObjectWillChangeSignal == Signal {
43 |
44 | /// A publisher that emits before the object has changed.
45 | public var objectWillChange: Signal {
46 | var signals: [Signal] = []
47 | let mirror = Mirror(reflecting: self)
48 | for child in mirror.children {
49 | if var publishedProperty = child.value as? _MutablePropertyWrapper {
50 | signals.append(publishedProperty.willChange)
51 | }
52 | }
53 | return Signal(flattening: signals, strategy: .merge)
54 | }
55 | }
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/Sources/Publishers/Empty.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Empty.swift
3 | // ReactiveKit-iOS
4 | //
5 | // Created by Ibrahim Koteish on 06/03/2020.
6 | // Copyright © 2020 DeclarativeHub. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// A signal that never publishes any values, and optionally finishes immediately.
12 | ///
13 | /// You can create a ”Never” signal — one which never sends values and never
14 | /// finishes or fails — with the initializer `Empty(completeImmediately: false)`.
15 | public struct Empty: SignalProtocol, Equatable {
16 |
17 | /// The kind of values published by this signal.
18 | public typealias Element = Element
19 |
20 | /// The kind of errors this signal might publish.
21 | ///
22 | /// Use `Never` if this `signal` does not publish errors.
23 | public typealias Error = Error
24 |
25 | /// Creates an empty signal.
26 | ///
27 | /// - Parameter completeImmediately: A Boolean value that indicates whether
28 | /// the signal should immediately finish.
29 | public init(completeImmediately: Bool = true) {
30 | self.completeImmediately = completeImmediately
31 | }
32 |
33 | /// Creates an empty signal with the given completion behavior and output and
34 | /// failure types.
35 | ///
36 | /// Use this initializer to connect the empty signal to observers or other
37 | /// signals that have specific output and failure types.
38 | /// - Parameters:
39 | /// - completeImmediately: A Boolean value that indicates whether the signal
40 | /// should immediately finish.
41 | /// - outputType: The output type exposed by this signal.
42 | /// - failureType: The failure type exposed by this signal.
43 | public init(completeImmediately: Bool = true,
44 | outputType: Element.Type,
45 | failureType: Error.Type) {
46 | self.init(completeImmediately: completeImmediately)
47 | }
48 |
49 | /// A Boolean value that indicates whether the signal immediately sends
50 | /// a completion.
51 | ///
52 | /// If `true`, the signal finishes immediately after sending an event
53 | /// to the observer. If `false`, it never completes.
54 | public let completeImmediately: Bool
55 |
56 | public func observe(with observer: @escaping (Signal.Event) -> Void) -> Disposable {
57 |
58 | if completeImmediately {
59 | return Signal.completed().observe(with: observer)
60 | }
61 | return Signal.never().observe(with: observer)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/SignalProtocol+Sequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2016-2019 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension SignalProtocol {
28 |
29 | /// Map element into a collection, flattening the collection into next elements.
30 | /// Shorthand for `map(transform).flattenElements()`.
31 | public func flatMap(_ transform: @escaping (Element) -> [NewElement]) -> Signal {
32 | return map(transform).flattenElements()
33 | }
34 | }
35 |
36 | extension SignalProtocol where Element: Sequence {
37 |
38 | /// Map inner sequence.
39 | public func mapElement(_ transform: @escaping (Element.Iterator.Element) -> NewElement) -> Signal<[NewElement], Error> {
40 | return map { $0.map(transform) }
41 | }
42 |
43 | /// Unwrap elements from each emitted sequence into the elements of the signal.
44 | public func flattenElements() -> Signal {
45 | return Signal { observer in
46 | return self.observe { event in
47 | switch event {
48 | case .next(let sequence):
49 | sequence.forEach(observer.receive(_:))
50 | case .completed:
51 | observer.receive(completion: .finished)
52 | case .failed(let error):
53 | observer.receive(completion: .failure(error))
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/SignalProtocol+Threading.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2016-2019 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension SignalProtocol {
28 |
29 | /// Set the scheduler on which to execute the signal (i.e. to run the signal's producer).
30 | ///
31 | /// In contrast with `receive(on:)`, which affects downstream actions, `subscribe(on:)` changes the execution context of upstream actions.
32 | public func subscribe(on scheduler: S) -> Signal {
33 | return Signal { observer in
34 | let serialDisposable = SerialDisposable(otherDisposable: nil)
35 | scheduler.schedule {
36 | if !serialDisposable.isDisposed {
37 | serialDisposable.otherDisposable = self.observe(with: observer.on)
38 | }
39 | }
40 | return serialDisposable
41 | }
42 | }
43 |
44 | /// Set the scheduler used to receive events (i.e. to run the observer / sink).
45 | ///
46 | /// In contrast with `subscribe(on:)`, which affects upstream actions, `receive(on:)` changes the execution context of downstream actions.
47 | public func receive(on scheduler: S) -> Signal {
48 | return Signal { observer in
49 | return self.observe { event in
50 | scheduler.schedule {
51 | observer.on(event)
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Subscribers/Sink.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2020 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension Subscribers {
28 |
29 | final public class Sink: Subscriber, Cancellable where Failure: Error {
30 |
31 | private enum State {
32 | case initialized
33 | case subscribed(Cancellable)
34 | case terminated
35 | }
36 |
37 | private var state = State.initialized
38 |
39 | final public let receiveValue: (Input) -> Void
40 | final public let receiveCompletion: (Subscribers.Completion) -> Void
41 |
42 | public init(receiveCompletion: @escaping ((Subscribers.Completion) -> Void), receiveValue: @escaping ((Input) -> Void)) {
43 | self.receiveValue = receiveValue
44 | self.receiveCompletion = receiveCompletion
45 | }
46 |
47 | final public func receive(subscription: Subscription) {
48 | switch state {
49 | case .initialized:
50 | state = .subscribed(subscription)
51 | subscription.request(.unlimited)
52 | default:
53 | subscription.cancel()
54 | }
55 | }
56 |
57 | final public func receive(_ value: Input) -> Subscribers.Demand {
58 | receiveValue(value)
59 | return .unlimited
60 | }
61 |
62 | final public func receive(completion: Subscribers.Completion) {
63 | receiveCompletion(completion)
64 | state = .terminated
65 | }
66 |
67 | final public func cancel() {
68 | switch state {
69 | case .subscribed(let subscription):
70 | subscription.cancel()
71 | state = .terminated
72 | default:
73 | break
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/xcshareddata/xcschemes/ReactiveKit-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/xcshareddata/xcschemes/ReactiveKit-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/xcshareddata/xcschemes/ReactiveKit-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/Sources/SignalProtocol+Optional.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2016-2019 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension SignalProtocol {
28 |
29 | /// Map element into a result, propagating `.some` value as a next event or skipping an element in case of a `nil`.
30 | /// Shorthand for `map(transform).ignoreNils()`.
31 | public func compactMap(_ transform: @escaping (Element) -> NewWrapped?) -> Signal {
32 | return map(transform).ignoreNils()
33 | }
34 | }
35 |
36 | extension SignalProtocol where Element: OptionalProtocol {
37 |
38 | /// Map inner optional.
39 | /// Shorthand for `map { $0.map(transform) }`.
40 | public func mapWrapped(_ transform: @escaping (Element.Wrapped) -> NewWrapped) -> Signal {
41 | return map { $0._unbox.map(transform) }
42 | }
43 |
44 | /// Replace all `nil`-elements with the provided replacement.
45 | public func replaceNils(with replacement: Element.Wrapped) -> Signal {
46 | return compactMap { $0._unbox ?? replacement }
47 | }
48 |
49 | /// Suppress all `nil`-elements.
50 | public func ignoreNils() -> Signal {
51 | return Signal { observer in
52 | return self.observe { event in
53 | switch event {
54 | case .next(let element):
55 | if let element = element._unbox {
56 | observer.receive(element)
57 | }
58 | case .failed(let error):
59 | observer.receive(completion: .failure(error))
60 | case .completed:
61 | observer.receive(completion: .finished)
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 | public protocol OptionalProtocol {
69 | associatedtype Wrapped
70 | var _unbox: Optional { get }
71 | init(nilLiteral: ())
72 | init(_ some: Wrapped)
73 | }
74 |
75 | extension Optional: OptionalProtocol {
76 |
77 | public var _unbox: Optional {
78 | return self
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Sources/SignalProtocol+Event.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2016-2019 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension SignalProtocol {
28 |
29 | /// Unwrap events into elements.
30 | public func materialize() -> Signal.Event, Never> {
31 | return Signal { observer in
32 | return self.observe { event in
33 | switch event {
34 | case .next(let element):
35 | observer.receive(.next(element))
36 | case .failed(let error):
37 | observer.receive(.failed(error))
38 | observer.receive(completion: .finished)
39 | case .completed:
40 | observer.receive(.completed)
41 | observer.receive(completion: .finished)
42 | }
43 | }
44 | }
45 | }
46 |
47 | /// Inverse of `materialize`.
48 | public func dematerialize() -> Signal where Element == Signal.Event, E == Error {
49 | return Signal { observer in
50 | return self.observe { event in
51 | switch event {
52 | case .next(let innerEvent):
53 | switch innerEvent {
54 | case .next(let element):
55 | observer.receive(element)
56 | case .failed(let error):
57 | observer.receive(completion: .failure(error))
58 | case .completed:
59 | observer.receive(completion: .finished)
60 | }
61 | case .failed(let error):
62 | observer.receive(completion: .failure(error))
63 | case .completed:
64 | observer.receive(completion: .finished)
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
71 | extension SignalProtocol where Error == Never {
72 |
73 | /// Inverse of `materialize`.
74 | public func dematerialize() -> Signal where Element == Signal.Event {
75 | return (castError() as Signal).dematerialize()
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Sources/Signal.Event.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2016 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | extension Signal {
26 |
27 | /// An event of a sequence.
28 | public enum Event {
29 |
30 | /// An event that carries next element.
31 | case next(Element)
32 |
33 | /// An event that represents failure. Carries an error.
34 | case failed(Error)
35 |
36 | /// An event that marks the completion of a sequence.
37 | case completed
38 | }
39 | }
40 |
41 | extension Signal.Event {
42 |
43 | /// Return `true` in case of `.next` event.
44 | public var isNext: Bool {
45 | switch self {
46 | case .next:
47 | return true
48 | default:
49 | return false
50 | }
51 | }
52 |
53 | /// Return `true` in case of `.failed` event.
54 | public var isFailed: Bool {
55 | switch self {
56 | case .failed:
57 | return true
58 | default:
59 | return false
60 | }
61 | }
62 |
63 | /// Return `true` in case of `.completed` event.
64 | public var isCompleted: Bool {
65 | switch self {
66 | case .completed:
67 | return true
68 | default:
69 | return false
70 | }
71 | }
72 |
73 | /// Return `true` in case of `.failure` or `.completed` event.
74 | public var isTerminal: Bool {
75 | switch self {
76 | case .next:
77 | return false
78 | default:
79 | return true
80 | }
81 | }
82 |
83 | /// Returns the next element, or nil if the event is not `.next`
84 | public var element: Element? {
85 | switch self {
86 | case .next(let element):
87 | return element
88 | default:
89 | return nil
90 | }
91 | }
92 |
93 | /// Return the failed error, or nil if the event is not `.failed`
94 | public var error: Error? {
95 | switch self {
96 | case .failed(let error):
97 | return error
98 | default:
99 | return nil
100 | }
101 | }
102 | }
103 |
104 | extension Signal.Event: Equatable where Element: Equatable, Error: Equatable {}
105 |
--------------------------------------------------------------------------------
/Playground.playground/Pages/Observing Signals.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import Foundation
4 | import ReactiveKit
5 | import PlaygroundSupport
6 |
7 | PlaygroundPage.current.needsIndefiniteExecution = true
8 |
9 | //: # Observing Signals
10 |
11 | // Let's make a signal that simulates doing some work like loading of a large file.
12 |
13 | let loadedFile = SafeSignal { observer in
14 | print("Now loading file...")
15 | sleep(1)
16 | observer.completed(with: "first line\nsecond line")
17 | print("File loaded.")
18 | return NonDisposable.instance
19 | }
20 |
21 | // If you run the playground up to this line, nothing will happen.
22 | // You console log will be empty. Even if we access the signal
23 |
24 | _ = loadedFile
25 |
26 | // still nothing happens. We could transform it using any of the operators
27 |
28 | let numberOfLines = loadedFile
29 | .map { $0.split(separator: "\n").count }
30 | .map { "The file has \($0) lines." }
31 |
32 | // and if you run the playground up to this line, nothing again.
33 |
34 | // While this might be a bit confusing, it's the most powerful feature of signals and
35 | // functional-reactive programming. Signals allow us to express the logic without doing side effects.
36 |
37 | // In our example, we've defined how to load a file and how to count number of lines
38 | // in the file, but we have not actually loaded the file nor counted the lines.
39 |
40 | // To make side effects, to do the work, one has to observe the signal. It's the act
41 | // of observing that starts everything! Let's try observing the signal.
42 |
43 | numberOfLines.observe { event in
44 | print(event)
45 | }
46 |
47 | // Run the playground up to this line and watch the console log.
48 | // It's only now that the file gets loaded, signal transformed and events printed.
49 |
50 | // This is very useful in real world development, but be aware of a caveat: observing the
51 | // signal again will repeat the side efects. In our example, the file will be loaded again.
52 |
53 | numberOfLines.observe { event in
54 | print(event)
55 | }
56 |
57 | // Bummer? No. Once you get more into functional-reactive parading you will see that
58 | // being able to express the logic without doing side effects outweights this inconvenience.
59 | // In order to share the sequence, all we need to do is apply `shareReplay` operator.
60 |
61 | let sharedLoadedFile = loadedFile.share()
62 |
63 | // The first time we observe the shared signal, it will load the file:
64 |
65 | sharedLoadedFile.observe { print($0) }
66 |
67 | // However, any subsequent observation will just use the shared sequence cached in the memory:
68 |
69 | sharedLoadedFile.observe { print($0) }
70 | sharedLoadedFile.observe { print($0) }
71 |
72 | // There are few convience methods for observing signals. When you are only interested into elements
73 | // of the signal, as opposed to events that contain element, you could just observe next events:
74 |
75 | sharedLoadedFile.observeNext { fileContent in
76 | print(fileContent)
77 | }
78 |
79 | // If you are interested only when the signal completes, then observe just the completed event:
80 |
81 | sharedLoadedFile.observeCompleted {
82 | print("Done")
83 | }
84 |
85 | // Some signals can fail with an error. When you are interested only in the failure, observe the failed event:
86 |
87 | sharedLoadedFile.observeFailed { error in
88 | print("Failed with error", error) // Will not happen because our signal doesn't fail.
89 | }
90 |
91 | //: [Next](@next)
92 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/project.xcworkspace/xcshareddata/ReactiveKit.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "C1F6A63EF115019142AF6E3E8A173E32E3445DB3",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "C1F6A63EF115019142AF6E3E8A173E32E3445DB3" : 0,
8 | "D78924967B5ECB0A1D20E197FAB93ACBFA47F0D1" : 9223372036854775807,
9 | "46F7DF751715C6D9E4FA7D31B7BBF03DFA4C06D4" : 9223372036854775807,
10 | "95438028B10BBB846574013D29F154A00556A9D1" : 0,
11 | "422BABDE4288A2028DC1FD12E5A5767EA1FD5926" : 0,
12 | "D0725CAC6FF2D66F2C83C2C48DC12106D42DAA64" : 0
13 | },
14 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "DF86A0A1-D0CF-4030-B5AC-6FE303947238",
15 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
16 | "C1F6A63EF115019142AF6E3E8A173E32E3445DB3" : "ReactiveKit\/",
17 | "D78924967B5ECB0A1D20E197FAB93ACBFA47F0D1" : "..\/..",
18 | "46F7DF751715C6D9E4FA7D31B7BBF03DFA4C06D4" : "..\/..\/..\/..",
19 | "95438028B10BBB846574013D29F154A00556A9D1" : "ReactiveKit\/Carthage\/Checkouts\/Nimble\/",
20 | "422BABDE4288A2028DC1FD12E5A5767EA1FD5926" : "",
21 | "D0725CAC6FF2D66F2C83C2C48DC12106D42DAA64" : "ReactiveKit\/Carthage\/Checkouts\/Quick\/"
22 | },
23 | "DVTSourceControlWorkspaceBlueprintNameKey" : "ReactiveKit",
24 | "DVTSourceControlWorkspaceBlueprintVersion" : 204,
25 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "ReactiveKit.xcodeproj",
26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
27 | {
28 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:ReactiveKit\/ReactiveFoundation.git",
29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
30 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "422BABDE4288A2028DC1FD12E5A5767EA1FD5926"
31 | },
32 | {
33 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "bitbucket.org:shapedk\/goboat-ios.git",
34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
35 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "46F7DF751715C6D9E4FA7D31B7BBF03DFA4C06D4"
36 | },
37 | {
38 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Quick\/Nimble.git",
39 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
40 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "95438028B10BBB846574013D29F154A00556A9D1"
41 | },
42 | {
43 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:ReactiveKit\/ReactiveKit.git",
44 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
45 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C1F6A63EF115019142AF6E3E8A173E32E3445DB3"
46 | },
47 | {
48 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Quick\/Quick.git",
49 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
50 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D0725CAC6FF2D66F2C83C2C48DC12106D42DAA64"
51 | },
52 | {
53 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:ReactiveKit\/Bond.git",
54 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
55 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D78924967B5ECB0A1D20E197FAB93ACBFA47F0D1"
56 | }
57 | ]
58 | }
--------------------------------------------------------------------------------
/Sources/Reactive.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2016 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | /// A proxy protocol for reactive extensions.
28 | ///
29 | /// To provide reactive extensions on type `X`, do
30 | ///
31 | /// extension ReactiveExtensions where Base == X {
32 | /// var y: SafeSignal { ... }
33 | /// }
34 | ///
35 | /// where `X` conforms to `ReactiveExtensionsProvider`.
36 | public protocol ReactiveExtensions {
37 | associatedtype Base
38 | var base: Base { get }
39 | }
40 |
41 | public struct Reactive: ReactiveExtensions {
42 | public let base: Base
43 |
44 | public init(_ base: Base) {
45 | self.base = base
46 | }
47 | }
48 |
49 | public protocol ReactiveExtensionsProvider: class {}
50 |
51 | extension ReactiveExtensionsProvider {
52 |
53 | /// Reactive extensions of `self`.
54 | public var reactive: Reactive {
55 | return Reactive(self)
56 | }
57 |
58 | /// Reactive extensions of `Self`.
59 | public static var reactive: Reactive.Type {
60 | return Reactive.self
61 | }
62 | }
63 |
64 | extension NSObject: ReactiveExtensionsProvider {}
65 |
66 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
67 |
68 | extension ReactiveExtensions where Base: NSObject {
69 |
70 | /// A signal that fires completion event when the object is deallocated.
71 | public var deallocated: SafeSignal {
72 | return base.bag.deallocated
73 | }
74 |
75 | /// A `DisposeBag` that can be used to dispose observations and bindings.
76 | public var bag: DisposeBag {
77 | return base.bag
78 | }
79 |
80 | /// Create a Signal that establishes a key-value observation of the given key path when observed.
81 | ///
82 | /// For example:
83 | ///
84 | /// let player = AVPlayer()
85 | /// player.reactive.publisher(for: \.status).sink { print("Playback status: \($0)") }
86 | ///
87 | public func publisher(for keyPath: KeyPath, options: NSKeyValueObservingOptions = [.initial, .new]) -> Signal {
88 | return Signal { [weak base] observer in
89 | guard let base = base else {
90 | observer.receive(completion: .finished)
91 | return SimpleDisposable(isDisposed: true)
92 | }
93 | let observation = base.observe(keyPath, options: options) { (base, change) in
94 | observer.receive(base[keyPath: keyPath])
95 | }
96 | return BlockDisposable {
97 | observation.invalidate()
98 | }
99 | }
100 | }
101 | }
102 |
103 |
104 | #endif
105 |
--------------------------------------------------------------------------------
/ReactiveKit.xcodeproj/xcshareddata/xcschemes/ReactiveKit-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
65 |
66 |
72 |
73 |
74 |
75 |
81 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/Sources/Subscribers/Accumulator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2020 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension Subscribers {
28 |
29 | /// A subscriber that accumulates received values into an array. This subscriber can be useful in unit testing by
30 | /// asserting the state of various properties of the subscriber.
31 | final public class Accumulator: Subscriber, Cancellable {
32 |
33 | private enum State {
34 | case initialized
35 | case subscribed(Cancellable)
36 | case terminated(Completion?)
37 | }
38 |
39 | private var state = State.initialized
40 |
41 | /// An array of values received by the subscriber.
42 | final public private(set) var values: [Input] = []
43 |
44 | /// True if the subscriber has received a finished event.
45 | final public var isFinished: Bool {
46 | switch state {
47 | case .terminated(.some(.finished)):
48 | return true
49 | default:
50 | return false
51 | }
52 | }
53 |
54 | /// True if the subscriber has received a failure event.
55 | final public var isFailure: Bool {
56 | switch state {
57 | case .terminated(.some(.failure)):
58 | return true
59 | default:
60 | return false
61 | }
62 | }
63 |
64 | /// Non-nil if the subscriber has received a failure event.
65 | final public var error: Failure? {
66 | switch state {
67 | case .terminated(.some(.failure(let error))):
68 | return error
69 | default:
70 | return nil
71 | }
72 | }
73 |
74 |
75 | public init() {
76 | }
77 |
78 | final public func receive(subscription: Subscription) {
79 | switch state {
80 | case .initialized:
81 | state = .subscribed(subscription)
82 | subscription.request(.unlimited)
83 | default:
84 | subscription.cancel()
85 | }
86 | }
87 |
88 | final public func receive(_ value: Input) -> Subscribers.Demand {
89 | values.append(value)
90 | return .unlimited
91 | }
92 |
93 | final public func receive(completion: Subscribers.Completion) {
94 | switch state {
95 | case .terminated:
96 | break
97 | default:
98 | state = .terminated(completion)
99 | }
100 | }
101 |
102 | final public func cancel() {
103 | switch state {
104 | case .subscribed(let subscription):
105 | subscription.cancel()
106 | state = .terminated(nil)
107 | default:
108 | break
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Sources/Combine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2020 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | #if canImport(Combine)
26 | import Combine
27 |
28 | @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
29 | extension Combine.Publisher {
30 |
31 | /// Convert `Combine.Publisher` into `ReactiveKit.Signal`
32 | public func toSignal() -> Signal