├── .gitignore
├── .swift-version
├── .travis.yml
├── LICENSE
├── README.md
├── Receiver.podspec
├── Receiver.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── xcshareddata
│ └── xcschemes
│ │ └── Receiver.xcscheme
└── xcuserdata
│ └── rui.peres.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Receiver
├── Sources
│ ├── Atomic.swift
│ ├── Disposable.swift
│ ├── Optiona+Extensions.swift
│ ├── Receiver+Operators.swift
│ └── Receiver.swift
└── Supporting Files
│ └── Info.plist
└── ReceiverTests
├── Info.plist
├── ReceiverTests+Operators.swift
├── ReceiverTests+Performance.swift
└── ReceiverTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata
19 |
20 | ## Other
21 | *.xccheckout
22 | *.moved-aside
23 | *.xcuserstate
24 | *.xcscmblueprint
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 |
30 | # Swift Package Manager
31 | #
32 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
33 | # Packages/
34 | .build/
35 |
36 | # CocoaPods
37 | #
38 | # We recommend against adding the Pods directory to your .gitignore. However
39 | # you should judge for yourself, the pros and cons are mentioned at:
40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
41 | #
42 | # Pods/
43 |
44 | # Carthage
45 | #
46 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
47 | # Carthage/Checkouts
48 |
49 | Carthage/Build
50 |
51 | # fastlane
52 | #
53 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
54 | # screenshots whenever they are needed.
55 | # For more information about the recommended setup visit:
56 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
57 |
58 | fastlane/report.xml
59 | fastlane/screenshots
60 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode9.3
3 | script:
4 | - set -o pipefail
5 | - xcodebuild test -scheme Receiver -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 6s" | xcpretty -c
6 | after_success:
7 | - bash <(curl -s https://codecov.io/bash)
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Rui Peres
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Receiver
2 |
3 |
4 |
5 | [](https://codecov.io/gh/RuiAAPeres/Receiver)
6 | [](https://travis-ci.org/RuiAAPeres/Receiver)
7 | [](https://developer.apple.com/swift/)
8 | [](https://opensource.org/licenses/MIT)
9 |
10 |
11 | 1. [Intro](https://github.com/RuiAAPeres/Receiver#intro)
12 | 2. [🌈 Enter `Receiver`! 🌈](https://github.com/RuiAAPeres/Receiver#-enter-receiver-)
13 | 3. [Adding as a Dependency 🚀](https://github.com/RuiAAPeres/Receiver#adding-as-a-dependency-)
14 | 4. [Basic usage 😎](https://github.com/RuiAAPeres/Receiver#basic-usage-)
15 | 5. [Operators 🤖](https://github.com/RuiAAPeres/Receiver#operators-)
16 | 6. [Strategies](https://github.com/RuiAAPeres/Receiver#strategies)
17 | 7. [Opinionated, in what way? 🤓](https://github.com/RuiAAPeres/Receiver#opinionated-in-what-way-)
18 | 8. [Ok, so why would I use this? 🤷♀️](https://github.com/RuiAAPeres/Receiver#ok-so-why-would-i-use-this-️)
19 |
20 |
21 | ## Intro
22 |
23 | As a [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift) user myself, most of time, it's difficult to convince someone to just simply start using it. The reality, for better or worse, is that most projects/teams are not ready to adopt it:
24 |
25 | 1. The intrinsic problems of adding a big dependency.
26 | 2. The learning curve.
27 | 3. Adapting the current codebase to a FRP mindset/approach.
28 |
29 | Nevertheless, a precious pattern can still be used, even without such an awesome lib like [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift). 😖
30 |
31 | ## 🌈 Enter `Receiver`! 🌈
32 |
33 | 
34 |
35 | `Receiver` is nothing more than an opinionated micro framework implementation of the [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) (**~120 LOC**). Or, if you prefer, [`FRP`](https://en.wikipedia.org/wiki/Functional_reactive_programming) without the `F` and a really small `R` ([rP](https://en.wikipedia.org/wiki/Reactive_programming) 🤔).
36 |
37 | ## Adding as a Dependency 🚀
38 |
39 | #### Carthage
40 |
41 | If you use Carthage to manage your dependencies, simply add Receiver to your Cartfile:
42 |
43 | ```
44 | github "RuiAAPeres/Receiver" ~> 0.0.1
45 | ```
46 |
47 | If you use Carthage to build your dependencies, make sure you have added `Receiver.framework` to the "Linked Frameworks and Libraries" section of your target, and have included them in your Carthage framework copying build phase.
48 |
49 | #### CocoaPods
50 |
51 | If you use CocoaPods to manage your dependencies, simply add `Receiver` to your Podfile:
52 |
53 | ```
54 | pod 'Receiver', '~> 0.0.1'
55 | ```
56 |
57 | ## Basic usage 😎
58 |
59 | Let's begin with the basics. **There are three methods in total**. Yup, that's right.
60 |
61 | #### 1. Creating the Receiver
62 |
63 | ```swift
64 | let (transmitter, receiver) = Receiver.make()
65 | ```
66 |
67 | A `receiver` can never be created without an associated `transmitter` (what good would that be?)
68 |
69 | #### 2. Listening to an event 📡
70 |
71 | This is how you observe events:
72 |
73 | ```swift
74 | receiver.listen { cheezburgers in print("Can I haz \(cheezburgers) cheezburger. 🐈") }
75 | ```
76 |
77 | As expected, you can do so as many times as you want:
78 |
79 | ```swift
80 | receiver.listen { cheezburgers in print("Can I haz \(cheezburgers) cheezburger. 🐈") }
81 |
82 |
83 | receiver.listen { cheezburgers in print("I have \(cheezburgers) cheezburgers and you have none!")}
84 | ```
85 |
86 | And both handlers will be called, when an event is broadcasted. ⚡️
87 |
88 | #### 3. Broadcasting an event 📻
89 |
90 | This is how you send events:
91 |
92 | ```swift
93 | transmitter.broadcast(1)
94 | ```
95 |
96 | ## Operators 🤖
97 |
98 | Receiver provides a set of operators akin to ReactiveSwift:
99 |
100 | * [`map`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L2#L26)
101 | * [`filter`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L28#L56)
102 | * [`withPrevious`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L58#L88)
103 | * [`take`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L129#L164)
104 | * [`skip`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L90#L127)
105 | * [`skipRepeats`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L167#L214)
106 | * [`skipNil`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L257#L290)
107 | * [`uniqueValues`](https://github.com/RuiAAPeres/Receiver/blob/master/Receiver/Sources/Receiver%2BOperators.swift#L217#L254)
108 |
109 | ## Strategies
110 |
111 | If you are familiar with FRP, you must have heard about [cold and hot semantics](http://codeplease.io/2017/10/15/ras-s1e3-3/) (if not don't worry! ☺️). `Receiver` provides all three flavours explicitly, when you initialize it, via `make(strategy:)`. By default, the `Receiver` is `.hot`.
112 |
113 | ### `.cold` ❄️:
114 |
115 | ```swift
116 | let (transmitter, receiver) = Receiver.make(with: .cold)
117 | transmitter.broadcast(1)
118 | transmitter.broadcast(2)
119 | transmitter.broadcast(3)
120 |
121 | receiver.listen { wave in
122 | // This will be called with `wave == 1`
123 | // This will be called with `wave == 2`
124 | // This will be called with `wave == 3`
125 | // This will be called with `wave == 4`
126 | }
127 |
128 | transmitter.broadcast(4)
129 | ```
130 |
131 | Internally, the `Receiver` will keep a buffer of the previous sent values. Once there is a new listener, all the previous values are sent. When the `4` is sent, it will be "listened to" as expected.
132 |
133 | ### `.warm(upTo: Int)` 🌈:
134 |
135 | This strategy allows you to specify how big the buffer should be:
136 |
137 | ```swift
138 | let (transmitter, receiver) = Receiver.make(with: .warm(upTo: 1))
139 | transmitter.broadcast(1)
140 | transmitter.broadcast(2)
141 |
142 | receiver.listen { wave in
143 | // This will be called with `wave == 2`
144 | // This will be called with `wave == 3`
145 | }
146 |
147 | transmitter.broadcast(3)
148 | ```
149 |
150 | In this case `1` will never be called, because the limit specified (`upTo: 1`) is too low, so only `2` is kept in the buffer.
151 |
152 | ### `.hot` 🔥:
153 |
154 | ```swift
155 | let (transmitter, receiver) = Receiver.make(with: .hot) // this is the default strategy
156 | transmitter.broadcast(1)
157 | transmitter.broadcast(2)
158 |
159 | receiver.listen { wave in
160 | // This will be called with `wave == 3`
161 | }
162 |
163 | transmitter.broadcast(3)
164 | ```
165 |
166 | Anything broadcasted before listening is discarded.
167 |
168 | ## Opinionated, in what way? 🤓
169 |
170 | #### Initializer. 🌳
171 |
172 | The `make` method, follows the same approach used in ReactiveSwift, with `pipe`. Since a `receiver` only makes sense with a `transmitter`, it's only logical for them to be created together.
173 |
174 | #### Separation between the reader and the writer. ⬆️ ⬇️
175 |
176 | A lot of libs have the reader and the writer bundled within the same entity. For the purposes and use cases of this lib, it makes sense to have these concerns separated. It's a bit like a `UITableView` and a `UITableViewDataSource`: one fuels the other, so it might be better for them to be split into two different entities.
177 |
178 | ## Ok, so why would I use this? 🤷♀️
179 |
180 | ~Well, to make your codebase awesome of course.~ There are a lot of places where the observer pattern can be useful. In the most simplistic scenario, when delegation is not good enough and you have an `1-to-N` relationship.
181 |
182 | A good use case for this would in tracking an `UIApplication`'s lifecycle:
183 |
184 | ```swift
185 | enum ApplicationLifecycle {
186 | case didFinishLaunching
187 | case didBecomeActive
188 | case didEnterBackground
189 | }
190 |
191 | @UIApplicationMain
192 | class AppDelegate: UIResponder, UIApplicationDelegate {
193 |
194 | var window: UIWindow?
195 | private var transmitter: Receiver.Transmitter!
196 |
197 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
198 |
199 | let (transmitter, receiver) = Receiver.make()
200 | self.transmitter = transmitter
201 | // Pass down the `receiver` to where it's needed (e.g. ViewModel, Controllers)
202 |
203 | transmitter.broadcast(.didFinishLaunching)
204 | return true
205 | }
206 |
207 | func applicationDidEnterBackground(_ application: UIApplication) {
208 | transmitter.broadcast(.didEnterBackground)
209 | }
210 |
211 | func applicationDidBecomeActive(_ application: UIApplication) {
212 | transmitter.broadcast(.didBecomeActive)
213 | }
214 | }
215 | ```
216 |
217 | Similar to the `ApplicationLifecycle`, the same approach could be used for MVVM:
218 |
219 |
220 | ```swift
221 | class MyViewController: UIViewController {
222 | private let viewModel: MyViewModel
223 | private let transmitter: Receiver.Transmitter
224 |
225 | init(viewModel: MyViewModel, transmitter: Receiver.Transmitter) {
226 | self.viewModel = viewModel
227 | self.transmitter = transmitter
228 | super.init(nibName: nil, bundle: nil)
229 | }
230 |
231 | override func viewDidLoad() {
232 | super.viewdDidLoad()
233 | transmitter.broadcast(.viewDidLoad)
234 | }
235 |
236 | override func viewDidAppear(_ animated: Bool) {
237 | super.viewDidAppear(animated)
238 | transmitter.broadcast(.viewDidAppear)
239 | }
240 |
241 | override func viewDidDisappear(_ animated: Bool) {
242 | super.viewDidDisappear(animated)
243 | transmitter.broadcast(.viewDidDisappear)
244 | }
245 | }
246 | ```
247 |
248 | The nice part is that the `UIViewController` is never aware of the `receiver`, as it should be. ✨
249 |
250 | At initialization time:
251 |
252 | ```swift
253 | let (transmitter, receiver) = Receiver.make()
254 | let viewModel = MyViewModel(with: receiver)
255 | let viewController = MyViewController(viewModel: viewModel, transmitter: transmitter)
256 | ```
257 |
--------------------------------------------------------------------------------
/Receiver.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "Receiver"
3 | s.version = "0.0.4"
4 | s.summary = "Swift µframework implementing the Observer pattern"
5 | s.description = <<-DESC
6 | Receiver is nothing more than an opinionated micro framework implementation of the Observer pattern.
7 | DESC
8 | s.homepage = "https://github.com/RuiAAPeres/Receiver"
9 | s.license = { :type => "MIT", :file => "LICENSE" }
10 | s.author = "Rui Peres"
11 | s.social_media_url = "http://twitter.com/peres"
12 | s.ios.deployment_target = '10.0'
13 | s.source = { :git => "https://github.com/RuiAAPeres/Receiver.git" }
14 | s.source_files = "Receiver/Sources/*.swift"
15 | end
16 |
--------------------------------------------------------------------------------
/Receiver.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3AB58B431FD9A15E00DDF2BC /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB58B421FD9A15E00DDF2BC /* Disposable.swift */; };
11 | B286F8311FBCD22C00FE73BB /* Receiver.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B286F8271FBCD22B00FE73BB /* Receiver.framework */; };
12 | B286F8361FBCD22C00FE73BB /* ReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B286F8351FBCD22C00FE73BB /* ReceiverTests.swift */; };
13 | B286F8471FBCD25A00FE73BB /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = B286F8441FBCD25A00FE73BB /* Atomic.swift */; };
14 | B286F8481FBCD25A00FE73BB /* Receiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B286F8451FBCD25A00FE73BB /* Receiver.swift */; };
15 | B28B1E7C1FD0448D009ACCCB /* Optiona+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B28B1E7B1FD0448D009ACCCB /* Optiona+Extensions.swift */; };
16 | B2CBF31C1FCA2F0200D6F497 /* ReceiverTests+Performance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CBF31B1FCA2F0200D6F497 /* ReceiverTests+Performance.swift */; };
17 | E8F62DF91FC39178006899AC /* Receiver+Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F62DF81FC39178006899AC /* Receiver+Operators.swift */; };
18 | E8F62DFB1FC3A129006899AC /* ReceiverTests+Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F62DFA1FC3A129006899AC /* ReceiverTests+Operators.swift */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXContainerItemProxy section */
22 | B286F8321FBCD22C00FE73BB /* PBXContainerItemProxy */ = {
23 | isa = PBXContainerItemProxy;
24 | containerPortal = B286F81E1FBCD22B00FE73BB /* Project object */;
25 | proxyType = 1;
26 | remoteGlobalIDString = B286F8261FBCD22B00FE73BB;
27 | remoteInfo = Receiver;
28 | };
29 | /* End PBXContainerItemProxy section */
30 |
31 | /* Begin PBXFileReference section */
32 | 3AB58B421FD9A15E00DDF2BC /* Disposable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disposable.swift; sourceTree = ""; };
33 | B286F8271FBCD22B00FE73BB /* Receiver.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Receiver.framework; sourceTree = BUILT_PRODUCTS_DIR; };
34 | B286F8301FBCD22C00FE73BB /* ReceiverTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReceiverTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
35 | B286F8351FBCD22C00FE73BB /* ReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiverTests.swift; sourceTree = ""; };
36 | B286F8371FBCD22C00FE73BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
37 | B286F8421FBCD25A00FE73BB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
38 | B286F8441FBCD25A00FE73BB /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; };
39 | B286F8451FBCD25A00FE73BB /* Receiver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Receiver.swift; sourceTree = ""; };
40 | B28B1E7B1FD0448D009ACCCB /* Optiona+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optiona+Extensions.swift"; sourceTree = ""; };
41 | B2CBF31B1FCA2F0200D6F497 /* ReceiverTests+Performance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReceiverTests+Performance.swift"; sourceTree = ""; };
42 | E8F62DF81FC39178006899AC /* Receiver+Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Receiver+Operators.swift"; sourceTree = ""; };
43 | E8F62DFA1FC3A129006899AC /* ReceiverTests+Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReceiverTests+Operators.swift"; sourceTree = ""; };
44 | /* End PBXFileReference section */
45 |
46 | /* Begin PBXFrameworksBuildPhase section */
47 | B286F8231FBCD22B00FE73BB /* Frameworks */ = {
48 | isa = PBXFrameworksBuildPhase;
49 | buildActionMask = 2147483647;
50 | files = (
51 | );
52 | runOnlyForDeploymentPostprocessing = 0;
53 | };
54 | B286F82D1FBCD22C00FE73BB /* Frameworks */ = {
55 | isa = PBXFrameworksBuildPhase;
56 | buildActionMask = 2147483647;
57 | files = (
58 | B286F8311FBCD22C00FE73BB /* Receiver.framework in Frameworks */,
59 | );
60 | runOnlyForDeploymentPostprocessing = 0;
61 | };
62 | /* End PBXFrameworksBuildPhase section */
63 |
64 | /* Begin PBXGroup section */
65 | B286F81D1FBCD22B00FE73BB = {
66 | isa = PBXGroup;
67 | children = (
68 | B286F8291FBCD22B00FE73BB /* Receiver */,
69 | B286F8341FBCD22C00FE73BB /* ReceiverTests */,
70 | B286F8281FBCD22B00FE73BB /* Products */,
71 | );
72 | sourceTree = "";
73 | };
74 | B286F8281FBCD22B00FE73BB /* Products */ = {
75 | isa = PBXGroup;
76 | children = (
77 | B286F8271FBCD22B00FE73BB /* Receiver.framework */,
78 | B286F8301FBCD22C00FE73BB /* ReceiverTests.xctest */,
79 | );
80 | name = Products;
81 | sourceTree = "";
82 | };
83 | B286F8291FBCD22B00FE73BB /* Receiver */ = {
84 | isa = PBXGroup;
85 | children = (
86 | B286F8431FBCD25A00FE73BB /* Sources */,
87 | B286F8411FBCD25A00FE73BB /* Supporting Files */,
88 | );
89 | path = Receiver;
90 | sourceTree = "";
91 | };
92 | B286F8341FBCD22C00FE73BB /* ReceiverTests */ = {
93 | isa = PBXGroup;
94 | children = (
95 | B286F8351FBCD22C00FE73BB /* ReceiverTests.swift */,
96 | E8F62DFA1FC3A129006899AC /* ReceiverTests+Operators.swift */,
97 | B2CBF31B1FCA2F0200D6F497 /* ReceiverTests+Performance.swift */,
98 | B286F8371FBCD22C00FE73BB /* Info.plist */,
99 | );
100 | path = ReceiverTests;
101 | sourceTree = "";
102 | };
103 | B286F8411FBCD25A00FE73BB /* Supporting Files */ = {
104 | isa = PBXGroup;
105 | children = (
106 | B286F8421FBCD25A00FE73BB /* Info.plist */,
107 | );
108 | path = "Supporting Files";
109 | sourceTree = "";
110 | };
111 | B286F8431FBCD25A00FE73BB /* Sources */ = {
112 | isa = PBXGroup;
113 | children = (
114 | B286F8441FBCD25A00FE73BB /* Atomic.swift */,
115 | B286F8451FBCD25A00FE73BB /* Receiver.swift */,
116 | E8F62DF81FC39178006899AC /* Receiver+Operators.swift */,
117 | B28B1E7B1FD0448D009ACCCB /* Optiona+Extensions.swift */,
118 | 3AB58B421FD9A15E00DDF2BC /* Disposable.swift */,
119 | );
120 | path = Sources;
121 | sourceTree = "";
122 | };
123 | /* End PBXGroup section */
124 |
125 | /* Begin PBXHeadersBuildPhase section */
126 | B286F8241FBCD22B00FE73BB /* Headers */ = {
127 | isa = PBXHeadersBuildPhase;
128 | buildActionMask = 2147483647;
129 | files = (
130 | );
131 | runOnlyForDeploymentPostprocessing = 0;
132 | };
133 | /* End PBXHeadersBuildPhase section */
134 |
135 | /* Begin PBXNativeTarget section */
136 | B286F8261FBCD22B00FE73BB /* Receiver */ = {
137 | isa = PBXNativeTarget;
138 | buildConfigurationList = B286F83B1FBCD22C00FE73BB /* Build configuration list for PBXNativeTarget "Receiver" */;
139 | buildPhases = (
140 | B286F8221FBCD22B00FE73BB /* Sources */,
141 | B286F8231FBCD22B00FE73BB /* Frameworks */,
142 | B286F8241FBCD22B00FE73BB /* Headers */,
143 | B286F8251FBCD22B00FE73BB /* Resources */,
144 | );
145 | buildRules = (
146 | );
147 | dependencies = (
148 | );
149 | name = Receiver;
150 | productName = Receiver;
151 | productReference = B286F8271FBCD22B00FE73BB /* Receiver.framework */;
152 | productType = "com.apple.product-type.framework";
153 | };
154 | B286F82F1FBCD22C00FE73BB /* ReceiverTests */ = {
155 | isa = PBXNativeTarget;
156 | buildConfigurationList = B286F83E1FBCD22C00FE73BB /* Build configuration list for PBXNativeTarget "ReceiverTests" */;
157 | buildPhases = (
158 | B286F82C1FBCD22C00FE73BB /* Sources */,
159 | B286F82D1FBCD22C00FE73BB /* Frameworks */,
160 | B286F82E1FBCD22C00FE73BB /* Resources */,
161 | );
162 | buildRules = (
163 | );
164 | dependencies = (
165 | B286F8331FBCD22C00FE73BB /* PBXTargetDependency */,
166 | );
167 | name = ReceiverTests;
168 | productName = ReceiverTests;
169 | productReference = B286F8301FBCD22C00FE73BB /* ReceiverTests.xctest */;
170 | productType = "com.apple.product-type.bundle.unit-test";
171 | };
172 | /* End PBXNativeTarget section */
173 |
174 | /* Begin PBXProject section */
175 | B286F81E1FBCD22B00FE73BB /* Project object */ = {
176 | isa = PBXProject;
177 | attributes = {
178 | LastSwiftUpdateCheck = 0910;
179 | LastUpgradeCheck = 0930;
180 | ORGANIZATIONNAME = "Emergency Avocado";
181 | TargetAttributes = {
182 | B286F8261FBCD22B00FE73BB = {
183 | CreatedOnToolsVersion = 9.1;
184 | ProvisioningStyle = Automatic;
185 | };
186 | B286F82F1FBCD22C00FE73BB = {
187 | CreatedOnToolsVersion = 9.1;
188 | ProvisioningStyle = Automatic;
189 | };
190 | };
191 | };
192 | buildConfigurationList = B286F8211FBCD22B00FE73BB /* Build configuration list for PBXProject "Receiver" */;
193 | compatibilityVersion = "Xcode 8.0";
194 | developmentRegion = en;
195 | hasScannedForEncodings = 0;
196 | knownRegions = (
197 | en,
198 | );
199 | mainGroup = B286F81D1FBCD22B00FE73BB;
200 | productRefGroup = B286F8281FBCD22B00FE73BB /* Products */;
201 | projectDirPath = "";
202 | projectRoot = "";
203 | targets = (
204 | B286F8261FBCD22B00FE73BB /* Receiver */,
205 | B286F82F1FBCD22C00FE73BB /* ReceiverTests */,
206 | );
207 | };
208 | /* End PBXProject section */
209 |
210 | /* Begin PBXResourcesBuildPhase section */
211 | B286F8251FBCD22B00FE73BB /* Resources */ = {
212 | isa = PBXResourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | B286F82E1FBCD22C00FE73BB /* Resources */ = {
219 | isa = PBXResourcesBuildPhase;
220 | buildActionMask = 2147483647;
221 | files = (
222 | );
223 | runOnlyForDeploymentPostprocessing = 0;
224 | };
225 | /* End PBXResourcesBuildPhase section */
226 |
227 | /* Begin PBXSourcesBuildPhase section */
228 | B286F8221FBCD22B00FE73BB /* Sources */ = {
229 | isa = PBXSourcesBuildPhase;
230 | buildActionMask = 2147483647;
231 | files = (
232 | B28B1E7C1FD0448D009ACCCB /* Optiona+Extensions.swift in Sources */,
233 | E8F62DF91FC39178006899AC /* Receiver+Operators.swift in Sources */,
234 | 3AB58B431FD9A15E00DDF2BC /* Disposable.swift in Sources */,
235 | B286F8481FBCD25A00FE73BB /* Receiver.swift in Sources */,
236 | B286F8471FBCD25A00FE73BB /* Atomic.swift in Sources */,
237 | );
238 | runOnlyForDeploymentPostprocessing = 0;
239 | };
240 | B286F82C1FBCD22C00FE73BB /* Sources */ = {
241 | isa = PBXSourcesBuildPhase;
242 | buildActionMask = 2147483647;
243 | files = (
244 | B2CBF31C1FCA2F0200D6F497 /* ReceiverTests+Performance.swift in Sources */,
245 | E8F62DFB1FC3A129006899AC /* ReceiverTests+Operators.swift in Sources */,
246 | B286F8361FBCD22C00FE73BB /* ReceiverTests.swift in Sources */,
247 | );
248 | runOnlyForDeploymentPostprocessing = 0;
249 | };
250 | /* End PBXSourcesBuildPhase section */
251 |
252 | /* Begin PBXTargetDependency section */
253 | B286F8331FBCD22C00FE73BB /* PBXTargetDependency */ = {
254 | isa = PBXTargetDependency;
255 | target = B286F8261FBCD22B00FE73BB /* Receiver */;
256 | targetProxy = B286F8321FBCD22C00FE73BB /* PBXContainerItemProxy */;
257 | };
258 | /* End PBXTargetDependency section */
259 |
260 | /* Begin XCBuildConfiguration section */
261 | B286F8391FBCD22C00FE73BB /* Debug */ = {
262 | isa = XCBuildConfiguration;
263 | buildSettings = {
264 | ALWAYS_SEARCH_USER_PATHS = NO;
265 | CLANG_ANALYZER_NONNULL = YES;
266 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
268 | CLANG_CXX_LIBRARY = "libc++";
269 | CLANG_ENABLE_MODULES = YES;
270 | CLANG_ENABLE_OBJC_ARC = YES;
271 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
272 | CLANG_WARN_BOOL_CONVERSION = YES;
273 | CLANG_WARN_COMMA = YES;
274 | CLANG_WARN_CONSTANT_CONVERSION = YES;
275 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
277 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
278 | CLANG_WARN_EMPTY_BODY = YES;
279 | CLANG_WARN_ENUM_CONVERSION = YES;
280 | CLANG_WARN_INFINITE_RECURSION = YES;
281 | CLANG_WARN_INT_CONVERSION = YES;
282 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
283 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
284 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
286 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
287 | CLANG_WARN_STRICT_PROTOTYPES = YES;
288 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
289 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
290 | CLANG_WARN_UNREACHABLE_CODE = YES;
291 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
292 | CODE_SIGN_IDENTITY = "iPhone Developer";
293 | COPY_PHASE_STRIP = NO;
294 | CURRENT_PROJECT_VERSION = 1;
295 | DEBUG_INFORMATION_FORMAT = dwarf;
296 | ENABLE_STRICT_OBJC_MSGSEND = YES;
297 | ENABLE_TESTABILITY = YES;
298 | GCC_C_LANGUAGE_STANDARD = gnu11;
299 | GCC_DYNAMIC_NO_PIC = NO;
300 | GCC_NO_COMMON_BLOCKS = YES;
301 | GCC_OPTIMIZATION_LEVEL = 0;
302 | GCC_PREPROCESSOR_DEFINITIONS = (
303 | "DEBUG=1",
304 | "$(inherited)",
305 | );
306 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
307 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
308 | GCC_WARN_UNDECLARED_SELECTOR = YES;
309 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
310 | GCC_WARN_UNUSED_FUNCTION = YES;
311 | GCC_WARN_UNUSED_VARIABLE = YES;
312 | IPHONEOS_DEPLOYMENT_TARGET = 11.1;
313 | MTL_ENABLE_DEBUG_INFO = YES;
314 | ONLY_ACTIVE_ARCH = YES;
315 | SDKROOT = iphoneos;
316 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
317 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
318 | VERSIONING_SYSTEM = "apple-generic";
319 | VERSION_INFO_PREFIX = "";
320 | };
321 | name = Debug;
322 | };
323 | B286F83A1FBCD22C00FE73BB /* Release */ = {
324 | isa = XCBuildConfiguration;
325 | buildSettings = {
326 | ALWAYS_SEARCH_USER_PATHS = NO;
327 | CLANG_ANALYZER_NONNULL = YES;
328 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
330 | CLANG_CXX_LIBRARY = "libc++";
331 | CLANG_ENABLE_MODULES = YES;
332 | CLANG_ENABLE_OBJC_ARC = YES;
333 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
334 | CLANG_WARN_BOOL_CONVERSION = YES;
335 | CLANG_WARN_COMMA = YES;
336 | CLANG_WARN_CONSTANT_CONVERSION = YES;
337 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
338 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
339 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
340 | CLANG_WARN_EMPTY_BODY = YES;
341 | CLANG_WARN_ENUM_CONVERSION = YES;
342 | CLANG_WARN_INFINITE_RECURSION = YES;
343 | CLANG_WARN_INT_CONVERSION = YES;
344 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
345 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
346 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
347 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
348 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
349 | CLANG_WARN_STRICT_PROTOTYPES = YES;
350 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
351 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
352 | CLANG_WARN_UNREACHABLE_CODE = YES;
353 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
354 | CODE_SIGN_IDENTITY = "iPhone Developer";
355 | COPY_PHASE_STRIP = NO;
356 | CURRENT_PROJECT_VERSION = 1;
357 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
358 | ENABLE_NS_ASSERTIONS = NO;
359 | ENABLE_STRICT_OBJC_MSGSEND = YES;
360 | GCC_C_LANGUAGE_STANDARD = gnu11;
361 | GCC_NO_COMMON_BLOCKS = YES;
362 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
363 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
364 | GCC_WARN_UNDECLARED_SELECTOR = YES;
365 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
366 | GCC_WARN_UNUSED_FUNCTION = YES;
367 | GCC_WARN_UNUSED_VARIABLE = YES;
368 | IPHONEOS_DEPLOYMENT_TARGET = 11.1;
369 | MTL_ENABLE_DEBUG_INFO = NO;
370 | SDKROOT = iphoneos;
371 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
372 | VALIDATE_PRODUCT = YES;
373 | VERSIONING_SYSTEM = "apple-generic";
374 | VERSION_INFO_PREFIX = "";
375 | };
376 | name = Release;
377 | };
378 | B286F83C1FBCD22C00FE73BB /* Debug */ = {
379 | isa = XCBuildConfiguration;
380 | buildSettings = {
381 | CODE_SIGN_IDENTITY = "";
382 | CODE_SIGN_STYLE = Automatic;
383 | DEFINES_MODULE = YES;
384 | DYLIB_COMPATIBILITY_VERSION = 1;
385 | DYLIB_CURRENT_VERSION = 1;
386 | DYLIB_INSTALL_NAME_BASE = "@rpath";
387 | INFOPLIST_FILE = "Receiver/Supporting Files/Info.plist";
388 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
389 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
391 | PRODUCT_BUNDLE_IDENTIFIER = com.emergencyavocado.Receiver;
392 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
393 | SKIP_INSTALL = YES;
394 | SWIFT_VERSION = 4.0;
395 | TARGETED_DEVICE_FAMILY = "1,2";
396 | };
397 | name = Debug;
398 | };
399 | B286F83D1FBCD22C00FE73BB /* Release */ = {
400 | isa = XCBuildConfiguration;
401 | buildSettings = {
402 | CODE_SIGN_IDENTITY = "";
403 | CODE_SIGN_STYLE = Automatic;
404 | DEFINES_MODULE = YES;
405 | DYLIB_COMPATIBILITY_VERSION = 1;
406 | DYLIB_CURRENT_VERSION = 1;
407 | DYLIB_INSTALL_NAME_BASE = "@rpath";
408 | INFOPLIST_FILE = "Receiver/Supporting Files/Info.plist";
409 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
410 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
411 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
412 | PRODUCT_BUNDLE_IDENTIFIER = com.emergencyavocado.Receiver;
413 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
414 | SKIP_INSTALL = YES;
415 | SWIFT_VERSION = 4.0;
416 | TARGETED_DEVICE_FAMILY = "1,2";
417 | };
418 | name = Release;
419 | };
420 | B286F83F1FBCD22C00FE73BB /* Debug */ = {
421 | isa = XCBuildConfiguration;
422 | buildSettings = {
423 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
424 | CODE_SIGN_STYLE = Automatic;
425 | INFOPLIST_FILE = ReceiverTests/Info.plist;
426 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
427 | PRODUCT_BUNDLE_IDENTIFIER = com.emergencyavocado.ReceiverTests;
428 | PRODUCT_NAME = "$(TARGET_NAME)";
429 | SWIFT_VERSION = 4.0;
430 | TARGETED_DEVICE_FAMILY = "1,2";
431 | };
432 | name = Debug;
433 | };
434 | B286F8401FBCD22C00FE73BB /* Release */ = {
435 | isa = XCBuildConfiguration;
436 | buildSettings = {
437 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
438 | CODE_SIGN_STYLE = Automatic;
439 | INFOPLIST_FILE = ReceiverTests/Info.plist;
440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
441 | PRODUCT_BUNDLE_IDENTIFIER = com.emergencyavocado.ReceiverTests;
442 | PRODUCT_NAME = "$(TARGET_NAME)";
443 | SWIFT_VERSION = 4.0;
444 | TARGETED_DEVICE_FAMILY = "1,2";
445 | };
446 | name = Release;
447 | };
448 | /* End XCBuildConfiguration section */
449 |
450 | /* Begin XCConfigurationList section */
451 | B286F8211FBCD22B00FE73BB /* Build configuration list for PBXProject "Receiver" */ = {
452 | isa = XCConfigurationList;
453 | buildConfigurations = (
454 | B286F8391FBCD22C00FE73BB /* Debug */,
455 | B286F83A1FBCD22C00FE73BB /* Release */,
456 | );
457 | defaultConfigurationIsVisible = 0;
458 | defaultConfigurationName = Release;
459 | };
460 | B286F83B1FBCD22C00FE73BB /* Build configuration list for PBXNativeTarget "Receiver" */ = {
461 | isa = XCConfigurationList;
462 | buildConfigurations = (
463 | B286F83C1FBCD22C00FE73BB /* Debug */,
464 | B286F83D1FBCD22C00FE73BB /* Release */,
465 | );
466 | defaultConfigurationIsVisible = 0;
467 | defaultConfigurationName = Release;
468 | };
469 | B286F83E1FBCD22C00FE73BB /* Build configuration list for PBXNativeTarget "ReceiverTests" */ = {
470 | isa = XCConfigurationList;
471 | buildConfigurations = (
472 | B286F83F1FBCD22C00FE73BB /* Debug */,
473 | B286F8401FBCD22C00FE73BB /* Release */,
474 | );
475 | defaultConfigurationIsVisible = 0;
476 | defaultConfigurationName = Release;
477 | };
478 | /* End XCConfigurationList section */
479 | };
480 | rootObject = B286F81E1FBCD22B00FE73BB /* Project object */;
481 | }
482 |
--------------------------------------------------------------------------------
/Receiver.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Receiver.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Receiver.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Latest
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Receiver.xcodeproj/xcshareddata/xcschemes/Receiver.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Receiver.xcodeproj/xcuserdata/rui.peres.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Receiver.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | B286F8261FBCD22B00FE73BB
16 |
17 | primary
18 |
19 |
20 | B286F82F1FBCD22C00FE73BB
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Receiver/Sources/Atomic.swift:
--------------------------------------------------------------------------------
1 | /// Taken and edited from https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Atomic.swift
2 | import Foundation
3 |
4 | private class UnfairLock {
5 | private let _lock: os_unfair_lock_t
6 |
7 | fileprivate init() {
8 | _lock = .allocate(capacity: 1)
9 | _lock.initialize(to: os_unfair_lock())
10 | }
11 |
12 | fileprivate func lock() {
13 | os_unfair_lock_lock(_lock)
14 | }
15 |
16 | fileprivate func unlock() {
17 | os_unfair_lock_unlock(_lock)
18 | }
19 |
20 | deinit {
21 | _lock.deallocate()
22 | }
23 | }
24 |
25 | internal class Atomic {
26 | private let lock = UnfairLock()
27 | private var _value: Value
28 |
29 | internal init(_ value: Value) {
30 | _value = value
31 | }
32 |
33 | internal func apply(_ action: (inout Value) -> Void) {
34 | lock.lock()
35 | defer { lock.unlock() }
36 | action(&_value)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Receiver/Sources/Disposable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Used to remove the handler from being called again (aka dispose of),
4 | /// when you invoke `receiver.listen(handler)`.
5 | /// Example:
6 | ///
7 | /// Your Receiver is shared across multiple screens (or entities) and one
8 | /// of those, stops existing. In this scenario,
9 | /// you would call `diposable.dispose()` at `deinit` time.
10 | public class Disposable {
11 | private let cleanUp: () -> Void
12 |
13 | internal init(_ cleanUp: @escaping () -> Void) {
14 | self.cleanUp = cleanUp
15 | }
16 |
17 | /// Used to dispose of the handler passed to the receiver.
18 | public func dispose() {
19 | cleanUp()
20 | }
21 |
22 | /// Used to add disposable to the bag to be disposed of later
23 | public func disposed(by bag: DisposeBag) {
24 | bag.insert(self)
25 | }
26 | }
27 |
28 | /// Used to keep a list of Disposable to dispose of them later
29 | /// Example:
30 | ///
31 | /// In a UIViewController, you have several Receivers. You create a DisposeBag.
32 | /// When the UIViewController is deinit, the DisposeBag is deinit too. When
33 | /// the DisposeBag is deinit, all disposables will be disposed of.
34 | /// ```
35 | /// private let disposeBag = DisposeBag()
36 | ///
37 | /// override func viewDidLoad() {
38 | /// super.viewDidLoad()
39 | ///
40 | /// receiver.listen { value in
41 | /// //// Do whatever
42 | /// }.disposed(by: disposeBag)
43 | /// }
44 | /// ```
45 | /// No need to keep a reference to the Disposable. No need to call dispose() on
46 | /// the disposable. It is disposed by the DisposeBag.
47 | /// You can add multiple disposable to the DisposeBag.
48 | public class DisposeBag {
49 | /// Keep a reference to all Disposable instances -> this is the actual bag
50 | private let disposables = Atomic<[Disposable]>([])
51 |
52 | /// To be able to create a bag
53 | public init() {}
54 |
55 | /// Called by a Disposable instance
56 | fileprivate func insert(_ disposable: Disposable) {
57 | disposables.apply { _disposables in
58 | _disposables.append(disposable)
59 | }
60 | }
61 |
62 | /// Clean everything when the bag is deinited by calling dispose()
63 | /// on all Disposable instances
64 | deinit {
65 | disposables.apply { _disposables in
66 | _disposables.forEach { $0.dispose() }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Receiver/Sources/Optiona+Extensions.swift:
--------------------------------------------------------------------------------
1 | /// Same approach here: https://stackoverflow.com/questions/33436199/add-constraints-to-generic-parameters-in-extension
2 | /// Taken from here: https://github.com/ReactiveCocoa/ReactiveSwift/blob/59b55e9b9de06e7377f3348414ea96f810f487a7/Sources/Optional.swift
3 | public protocol OptionalProtocol {
4 | /// The type contained in the otpional.
5 | associatedtype Wrapped
6 |
7 | /// Extracts an optional from the receiver.
8 | var optional: Wrapped? { get }
9 | }
10 |
11 | extension Optional: OptionalProtocol {
12 | public var optional: Wrapped? {
13 | return self
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Receiver/Sources/Receiver+Operators.swift:
--------------------------------------------------------------------------------
1 | extension Receiver {
2 | /// Map each value (`Wave`) sent to the `receiver` to a new value (`U`).
3 | /// ```
4 | /// let (transmitter, receiver) = Receiver.make()
5 | /// let integersToStrings = receiver.map(String.init)
6 | ///
7 | /// integersToStrings.listen { value in
8 | /// /// value is `"1"`
9 | /// }
10 | ///
11 | /// transmitter.broadcast(1)
12 | /// ```
13 | /// - parameters:
14 | /// - transform: An anonymous function that accepts the current value type
15 | /// (`Wave`) and transforms it to a `U`.
16 | ///
17 | /// - returns: A `receiver` with the applied transformation.
18 | public func map(_ transform: @escaping (Wave) -> U) -> Receiver {
19 | let (transmitter, receiver) = Receiver.make()
20 |
21 | self.listen {
22 | transmitter.broadcast(transform($0))
23 | }
24 |
25 | return receiver
26 | }
27 |
28 | /// Filters each value (`Wave`) sent to the `receiver` based on the anonymous
29 | /// function provided (the predicate).
30 | /// ```
31 | /// let (transmitter, receiver) = Receiver.make()
32 | /// let onlyEvenNumbers = receiver.filter { $0 % 2 == 0}
33 | ///
34 | /// onlyEvenNumbers.listen { value in
35 | /// }
36 | ///
37 | /// /// Value is not sent to the listener, because `1` is an odd number
38 | /// transmitter.broadcast(1)
39 | /// /// Value is sent to the listener, because `2` is an even number
40 | /// transmitter.broadcast(2)
41 | /// ```
42 | /// - parameters:
43 | /// - isIncluded: An anonymous function that acts as a predicate.
44 | /// Only when `true`, the value is forwarded.
45 | ///
46 | /// - returns: A `receiver` that discards values based on a predicate.
47 | public func filter(_ isIncluded: @escaping (Wave) -> Bool) -> Receiver {
48 | let (transmitter, receiver) = Receiver.make()
49 |
50 | self.listen {
51 | guard isIncluded($0) else { return }
52 | transmitter.broadcast($0)
53 | }
54 |
55 | return receiver
56 | }
57 |
58 | /// Bundles each value (`Wave`) sent to the `receiver` with the previous value sent.
59 | /// ```
60 | /// let (transmitter, receiver) = Receiver.make()
61 | /// let newReceiver = receiver.withPrevious()
62 | ///
63 | /// newReceiver.listen { value in
64 | /// /// `value` == (nil, 1)
65 | /// /// `value` == (1, 2)
66 | /// }
67 | ///
68 | /// transmitter.broadcast(1)
69 | /// transmitter.broadcast(2)
70 | /// ```
71 | ///
72 | /// - returns: A `receiver` that pairs the previous value with the current.
73 | public func withPrevious() -> Receiver<(Wave?, Wave)> {
74 | let (transmitter, receiver) = Receiver<(Wave?, Wave)>.make()
75 | let values = Atomic<[Wave]>([])
76 |
77 | self.listen { newValue in
78 | values.apply { _values in
79 |
80 | let previous = _values.last
81 | _values.append(newValue)
82 |
83 | transmitter.broadcast((previous, newValue))
84 | }
85 | }
86 |
87 | return receiver
88 | }
89 |
90 | /// Skips values up to count, forwarding values normally afterwards.
91 | /// ```
92 | /// let (transmitter, receiver) = Receiver.make()
93 | /// let skippedValues = receiver.skip(count: 3)
94 | ///
95 | /// newReceiver.listen { value in
96 | /// /// `value` == 4
97 | /// }
98 | ///
99 | /// transmitter.broadcast(1)
100 | /// transmitter.broadcast(2)
101 | /// transmitter.broadcast(3)
102 | /// transmitter.broadcast(4)
103 | /// ```
104 | /// - parameters:
105 | /// - count: The number of values it will skip.
106 | ///
107 | /// - returns: A `receiver` that skips values up to `count`.
108 | public func skip(count: Int) -> Receiver {
109 | guard count > 0 else { return self }
110 |
111 | let (transmitter, receiver) = Receiver.make()
112 | let counter = Atomic(0)
113 |
114 | self.listen { newValue in
115 | counter.apply { _counterValue in
116 |
117 | guard _counterValue >= count else {
118 | _counterValue = _counterValue + 1
119 | return
120 | }
121 |
122 | transmitter.broadcast(newValue)
123 | }
124 | }
125 |
126 | return receiver
127 | }
128 |
129 | /// Only forwards values up to `count`, skipping all the values afterwards.
130 | /// ```
131 | /// let (transmitter, receiver) = Receiver.make()
132 | /// let takeValues = receiver.take(count: 3)
133 | ///
134 | /// takeValues.listen { value in
135 | /// /// `value` == 1
136 | /// /// `value` == 2
137 | /// /// `value` == 3
138 | /// }
139 | ///
140 | /// transmitter.broadcast(1)
141 | /// transmitter.broadcast(2)
142 | /// transmitter.broadcast(3)
143 | /// transmitter.broadcast(4)
144 | /// ```
145 | /// - parameters:
146 | /// - count: The number of values it will forward.
147 | ///
148 | /// - returns: A `receiver` that forwards values up to count.
149 | public func take(count: Int) -> Receiver {
150 | let (transmitter, receiver) = Receiver.make()
151 | let counter = Atomic(count)
152 |
153 | self.listen { newValue in
154 | counter.apply { _counterValue in
155 | guard _counterValue > 0 else { return }
156 | _counterValue = _counterValue - 1
157 |
158 | transmitter.broadcast(newValue)
159 | }
160 | }
161 |
162 | return receiver
163 | }
164 | }
165 |
166 | extension Receiver where Wave: Equatable {
167 | /// Skips consecutive repeated values.
168 | /// ```
169 | /// let (transmitter, receiver) = Receiver.make()
170 | /// let skipRepeatedValues = receiver.skipRepeats()
171 | ///
172 | /// skipRepeatedValues.listen { value in
173 | /// /// `value` == 1
174 | /// /// `value` == 2
175 | /// /// `value` == 3
176 | /// /// `value` == 1
177 | /// }
178 | ///
179 | /// transmitter.broadcast(1)
180 | /// transmitter.broadcast(1)
181 | /// transmitter.broadcast(1)
182 | /// transmitter.broadcast(2)
183 | /// transmitter.broadcast(2)
184 | /// transmitter.broadcast(3)
185 | /// transmitter.broadcast(1)
186 | /// ```
187 | ///
188 | /// - returns: A `receiver` that skips repeated consecutive values.
189 | public func skipRepeats() -> Receiver {
190 | let (transmitter, receiver) = Receiver.make()
191 | let values = Atomic<[Wave]>([])
192 |
193 | self.listen { newValue in
194 | values.apply { _values in
195 |
196 | func f(_ newValue: Wave) {
197 | _values.append(newValue)
198 | transmitter.broadcast(newValue)
199 | }
200 |
201 | switch _values.last {
202 | case let .some(lastValue) where lastValue != newValue:
203 | f(newValue)
204 | case .none:
205 | f(newValue)
206 | default:
207 | return
208 | }
209 | }
210 | }
211 |
212 | return receiver
213 | }
214 | }
215 |
216 | extension Receiver where Wave: Hashable {
217 | /// Only forwards unique values
218 | /// ```
219 | /// let (transmitter, receiver) = Receiver.make()
220 | /// let uniqueValues = receiver.uniqueValues()
221 | ///
222 | /// uniqueValues.listen { value in
223 | /// /// `value` == 1
224 | /// /// `value` == 2
225 | /// /// `value` == 3
226 | /// }
227 | ///
228 | /// transmitter.broadcast(1)
229 | /// transmitter.broadcast(2)
230 | /// transmitter.broadcast(1)
231 | /// transmitter.broadcast(1)
232 | /// transmitter.broadcast(2)
233 | /// transmitter.broadcast(3)
234 | /// transmitter.broadcast(1)
235 | /// transmitter.broadcast(2)
236 | /// ```
237 | ///
238 | /// - returns: A `receiver` that only forwards unique events.
239 | public func uniqueValues() -> Receiver {
240 | let (transmitter, receiver) = Receiver.make()
241 | let values = Atomic>([])
242 |
243 | self.listen { newValue in
244 | values.apply { _values in
245 | guard _values.contains(newValue) == false else { return }
246 |
247 | _values.insert(newValue)
248 | transmitter.broadcast(newValue)
249 | }
250 | }
251 |
252 | return receiver
253 | }
254 | }
255 |
256 | extension Receiver where Wave: OptionalProtocol {
257 | /// Skips nil values, forwarding only non-nil values
258 | /// ```
259 | /// let (transmitter, receiver) = Receiver.make()
260 | /// let skipNilValues = receiver.skipNil()
261 | ///
262 | /// skipNilValues.listen { value in
263 | /// /// `value` == 1
264 | /// /// `value` == 2
265 | /// /// `value` == 3
266 | /// }
267 | ///
268 | /// transmitter.broadcast(1)
269 | /// transmitter.broadcast(1?)
270 | /// transmitter.broadcast(2?)
271 | /// transmitter.broadcast(2)
272 | /// transmitter.broadcast(3)
273 | /// ```
274 | ///
275 | /// - returns: A `receiver` that skips nil values.
276 | public func skipNil() -> Receiver {
277 | let (transmitter, receiver) = Receiver.make()
278 |
279 | self.listen { newValue in
280 | switch newValue.optional {
281 | case let .some(_newValue):
282 | transmitter.broadcast(_newValue)
283 | case .none:
284 | return
285 | }
286 | }
287 |
288 | return receiver
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/Receiver/Sources/Receiver.swift:
--------------------------------------------------------------------------------
1 | /// The read only implementation of the Observer pattern.
2 | /// Consumers use the `listen` method and provide a suitable handler that will
3 | /// be called every time a new value (`Wave`) is forward.
4 | /// A Receiver is always created as a pair with its
5 | /// write only counter part: Transmitter.
6 | ///
7 | /// By default it has `hot` semantics (as a strategy):
8 | ///
9 | /// hot: Before receiving any new values, a handler needs to be provided:
10 | ///
11 | /// ```
12 | /// let (transmitter, receiver) = Receiver.make()
13 | ///
14 | /// /// `1` is discarded, since there is no listener
15 | /// transmitter.broadcast(1)
16 | ///
17 | /// receiver.listen { value in
18 | /// /// `2` is forwarded, since there is a listener
19 | /// }
20 | ///
21 | /// transmitter.broadcast(2)
22 | /// ```
23 | ///
24 | /// warm: Will forward previous values (up to the provided value) to the
25 | /// handler once it is provided:
26 | ///
27 | /// ```
28 | /// let (transmitter, receiver) = Receiver.make(with: .warm(upTo: 1))
29 | ///
30 | /// /// `1` is discarded, since the limit is `1` (`.warm(upTo: 1)`)
31 | /// transmitter.broadcast(1)
32 | /// /// `2` is stored, until there is an observer
33 | /// transmitter.broadcast(2)
34 | ///
35 | /// receiver.listen { value in
36 | /// /// `2` is forwarded, since a listener was added
37 | /// /// `3` is forwarded, since a listener exists
38 | /// }
39 | ///
40 | /// transmitter.broadcast(3)
41 | /// ```
42 | ///
43 | /// cold: Will forward all the previous values, once the handler is provided:
44 | ///
45 | /// ```
46 | /// let (transmitter, receiver) = Receiver.make(with: .cold)
47 | ///
48 | /// /// `1` is stored, until there is an observer
49 | /// transmitter.broadcast(1)
50 | /// /// `2` is stored, until there is an observer
51 | /// transmitter.broadcast(2)
52 | ///
53 | /// receiver.listen { value in
54 | /// /// `1` is forwarded, since a listener was added
55 | /// /// `2` is forwarded, since a listener was added
56 | /// /// `3` is forwarded, since a listener exists
57 | /// }
58 | ///
59 | /// transmitter.broadcast(3)
60 | /// ```
61 | ///
62 | /// For more examples, please check the `ReceiverTests/ReceiverTests.swift` file.
63 | ///
64 | /// Note: Providing `.warm(upTo: Int.max)` will have the same meaning as `.cold`.
65 | public class Receiver {
66 |
67 | public typealias Handler = (Wave) -> Void
68 |
69 | private let values = Atomic<[Wave]>([])
70 | private let strategy: Strategy
71 | private let handlers = Atomic<[Int:Handler]>([:])
72 |
73 | private init(strategy: Strategy) {
74 | self.strategy = strategy
75 | }
76 |
77 | private func broadcast(elements: Int) {
78 | values.apply { _values in
79 |
80 | let lowerLimit = max(_values.count - elements, 0)
81 | let indexs = (lowerLimit ..< _values.count)
82 |
83 | for index in indexs {
84 | let value = _values[index]
85 | handlers.apply { _handlers in
86 | for _handler in _handlers.values {
87 | _handler(value)
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | fileprivate func append(value: Wave) {
95 | values.apply { currentValues in
96 | currentValues.append(value)
97 | }
98 | broadcast(elements: 1)
99 | }
100 |
101 | /// Adds a listener to the receiver.
102 | ///
103 | /// - parameters:
104 | /// - handle: An anonymous function that gets called every time a
105 | /// a new value is sent.
106 | /// - returns: A reference to a disposable
107 | @discardableResult public func listen(to handle: @escaping (Wave) -> Void) -> Disposable {
108 | var _key: Int!
109 | handlers.apply { _handlers in
110 | _key = (_handlers.keys.map { $0.hashValue }.max() ?? -1) + 1
111 | _handlers[_key] = handle
112 | }
113 |
114 | switch strategy {
115 | case .cold:
116 | broadcast(elements: Int.max)
117 | case let .warm(upTo: limit):
118 | broadcast(elements: limit)
119 | case .hot:
120 | broadcast(elements: 0)
121 | }
122 |
123 | return Disposable {[weak self] in
124 | self?.handlers.apply { _handlers in
125 | _handlers[_key] = nil
126 | }
127 | }
128 | }
129 |
130 | /// Factory method to create the pair `transmitter` and `receiver`.
131 | ///
132 | /// - parameters:
133 | /// - strategy: The strategy that modifies the Receiver's behaviour
134 | /// By default it's `hot`.
135 | public static func make(with strategy: Strategy = .hot)
136 | -> (Receiver.Transmitter, Receiver) {
137 | let receiver = Receiver(strategy: strategy)
138 | let transmitter = Receiver.Transmitter(receiver)
139 |
140 | return (transmitter, receiver)
141 | }
142 | }
143 |
144 | extension Receiver {
145 | /// Enum that represents the Receiver's strategies
146 | public enum Strategy {
147 | case cold
148 | case warm(upTo: Int)
149 | case hot
150 | }
151 | }
152 |
153 | extension Receiver {
154 | /// The write only implementation of the Observer pattern.
155 | /// Used to broadcast values (`Wave`) that will be observed by the `receiver`
156 | /// and forward to all its listeners.
157 | ///
158 | /// Note: Keep in mind that the `transmitter` will hold strongly
159 | /// to its `receiver`.
160 | public struct Transmitter {
161 | private let receiver: Receiver
162 |
163 | internal init(_ receiver: Receiver) {
164 | self.receiver = receiver
165 | }
166 |
167 | /// Used to forward values to the associated `receiver`.
168 | ///
169 | /// - parameters:
170 | /// - wave: The value to be forward
171 | public func broadcast(_ wave: Wave) {
172 | receiver.append(value: wave)
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/Receiver/Supporting Files/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ReceiverTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ReceiverTests/ReceiverTests+Operators.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Receiver
3 |
4 | class ReceiverTests_Operators: XCTestCase {
5 |
6 | func test_map() {
7 | let (transmitter, receiver) = Receiver.make()
8 | let newReceiver = receiver.map(String.init)
9 | var called = 0
10 |
11 | newReceiver.listen { wave in
12 | XCTAssertTrue(wave == "1")
13 | called = called + 1
14 | }
15 |
16 | transmitter.broadcast(1)
17 | XCTAssertTrue(called == 1)
18 | }
19 |
20 | func test_filter() {
21 | let (transmitter, receiver) = Receiver.make()
22 | let newReceiver = receiver.filter { $0 % 2 == 0 }
23 | var called = 0
24 |
25 | newReceiver.listen { wave in
26 | XCTAssertTrue(wave == 2)
27 | called = called + 1
28 | }
29 |
30 | transmitter.broadcast(1)
31 | transmitter.broadcast(2)
32 | transmitter.broadcast(3)
33 |
34 | XCTAssertTrue(called == 1)
35 | }
36 |
37 | func test_skipRepeats() {
38 | let (transmitter, receiver) = Receiver.make()
39 | let newReceiver = receiver.skipRepeats()
40 | var called = 0
41 |
42 | newReceiver.listen { wave in
43 | called = called + 1
44 | }
45 |
46 | transmitter.broadcast(1)
47 | transmitter.broadcast(1)
48 | transmitter.broadcast(2)
49 | transmitter.broadcast(1)
50 | transmitter.broadcast(2)
51 | transmitter.broadcast(2)
52 | transmitter.broadcast(3)
53 |
54 | XCTAssertTrue(called == 5)
55 | }
56 |
57 | func test_withPrevious_nil() {
58 | let (transmitter, receiver) = Receiver.make()
59 | let newReceiver = receiver.withPrevious()
60 | var called = 0
61 | var expected: (Int?, Int) = (0, 0)
62 |
63 | newReceiver.listen { wave in
64 | expected = wave
65 | called = called + 1
66 | }
67 |
68 | transmitter.broadcast(1)
69 | XCTAssertTrue(expected.0 == nil)
70 | XCTAssertTrue(expected.1 == 1)
71 |
72 | transmitter.broadcast(2)
73 | XCTAssertTrue(expected.0 == 1)
74 | XCTAssertTrue(expected.1 == 2)
75 |
76 | XCTAssertTrue(called == 2)
77 | }
78 |
79 | func test_skip() {
80 | let (transmitter, receiver) = Receiver.make()
81 | let newReceiver = receiver.skip(count: 3)
82 | var called = 0
83 |
84 | newReceiver.listen { wave in
85 | called = called + 1
86 | }
87 |
88 | transmitter.broadcast(1)
89 | transmitter.broadcast(1)
90 | transmitter.broadcast(1)
91 | XCTAssertTrue(called == 0)
92 |
93 | transmitter.broadcast(1)
94 | XCTAssertTrue(called == 1)
95 |
96 | transmitter.broadcast(1)
97 | XCTAssertTrue(called == 2)
98 | }
99 |
100 | func test_skip_zero() {
101 | let (transmitter, receiver) = Receiver.make()
102 | let newReceiver = receiver.skip(count: 0)
103 | var called = 0
104 |
105 | newReceiver.listen { wave in
106 | called = called + 1
107 | }
108 |
109 | transmitter.broadcast(1)
110 | XCTAssertTrue(called == 1)
111 | }
112 |
113 | func test_take() {
114 | let (transmitter, receiver) = Receiver.make()
115 | let newReceiver = receiver.take(count: 2)
116 | var called = 0
117 |
118 | newReceiver.listen { wave in
119 | called = called + 1
120 | }
121 |
122 | transmitter.broadcast(1)
123 | transmitter.broadcast(1)
124 | transmitter.broadcast(1)
125 | transmitter.broadcast(1)
126 |
127 | XCTAssertTrue(called == 2)
128 | }
129 |
130 | func test_take_zero() {
131 | let (transmitter, receiver) = Receiver.make()
132 | let newReceiver = receiver.take(count: 0)
133 | var called = 0
134 |
135 | newReceiver.listen { wave in
136 | called = called + 1
137 | }
138 |
139 | transmitter.broadcast(1)
140 | transmitter.broadcast(1)
141 | transmitter.broadcast(1)
142 | transmitter.broadcast(1)
143 |
144 | XCTAssertTrue(called == 0)
145 | }
146 |
147 | func test_skipNil() {
148 | let (transmitter, receiver) = Receiver.make()
149 | let newReceiver = receiver.skipNil()
150 | var called = 0
151 |
152 | newReceiver.listen { wave in
153 | called = called + 1
154 | }
155 |
156 | transmitter.broadcast(1)
157 | transmitter.broadcast(nil)
158 | transmitter.broadcast(1)
159 |
160 | XCTAssertTrue(called == 2)
161 | }
162 |
163 | func test_uniqueValues() {
164 | let (transmitter, receiver) = Receiver.make()
165 | let newReceiver = receiver.uniqueValues()
166 | var called = 0
167 |
168 | newReceiver.listen { wave in
169 | called = called + 1
170 | }
171 |
172 | transmitter.broadcast(1)
173 | transmitter.broadcast(2)
174 | transmitter.broadcast(1)
175 | transmitter.broadcast(3)
176 | transmitter.broadcast(1)
177 | transmitter.broadcast(3)
178 | transmitter.broadcast(2)
179 |
180 | XCTAssertTrue(called == 3)
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/ReceiverTests/ReceiverTests+Performance.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Receiver
3 |
4 | class ReceiverTests_Performance: XCTestCase {
5 |
6 | func test_map_filter_performance() {
7 | // at ~`0.192`
8 | self.measure {
9 | let (transmitter, receiver) = Receiver.make()
10 | let newReceiver = receiver
11 | .map { $0 * 3 }
12 | .filter { $0 / 2 > 0}
13 | .map { $0 * 3 }
14 |
15 | for i in 1...1000 {
16 | newReceiver.listen { _ in }
17 | transmitter.broadcast(i)
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ReceiverTests/ReceiverTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Receiver
3 |
4 | class ReceiverTests: XCTestCase {
5 |
6 | func test_OneListener() {
7 | let (transmitter, receiver) = Receiver.make()
8 | var called = 0
9 |
10 | receiver.listen { wave in
11 | XCTAssertTrue(wave == 1)
12 | called = called + 1
13 | }
14 |
15 | transmitter.broadcast(1)
16 | XCTAssertTrue(called == 1)
17 | }
18 |
19 | func test_MultipleListeners() {
20 | let (transmitter, receiver) = Receiver.make()
21 | var called = 0
22 |
23 | for _ in 1...5 {
24 | receiver.listen { wave in
25 | XCTAssertTrue(wave == 1)
26 | called = called + 1
27 | }
28 | }
29 |
30 | transmitter.broadcast(1)
31 | XCTAssertTrue(called == 5)
32 |
33 | transmitter.broadcast(1)
34 | XCTAssertTrue(called == 10)
35 | }
36 |
37 | func test_Multithread() {
38 | let expect = expectation(description: "fun")
39 | let (transmitter, receiver) = Receiver.make()
40 | var called = 0
41 |
42 | let oneQueue = DispatchQueue(label: "oneQueue")
43 | let twoQueues = DispatchQueue(label: "twoQueues")
44 | let threeQueues = DispatchQueue(label: "threeQueues")
45 | let fourQueues = DispatchQueue(label: "fourQueues")
46 |
47 | receiver.listen { wave in
48 | called = called + 1
49 | }
50 |
51 | receiver.listen { wave in
52 | called = called + 1
53 | }
54 |
55 | for _ in 1...5 {
56 | oneQueue.async {
57 | transmitter.broadcast(1)
58 | }
59 | twoQueues.async {
60 | transmitter.broadcast(2)
61 | }
62 | threeQueues.async {
63 | transmitter.broadcast(3)
64 | }
65 | fourQueues.async {
66 | transmitter.broadcast(4)
67 | }
68 | }
69 |
70 | DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
71 | XCTAssert(called == 40)
72 | expect.fulfill()
73 | }
74 |
75 | waitForExpectations(timeout: 1, handler: nil)
76 | }
77 |
78 | func test_Warm_0() {
79 | runWarmBattery(expectedValues: [1, 2], upTo: 0)
80 | }
81 |
82 | func test_Warm_2_Queue1() {
83 | runWarmBattery(expectedValues: [1, 2], upTo: 1)
84 | }
85 |
86 | func test_Warm_2_Queue2() {
87 | runWarmBattery(expectedValues: [1, 2], upTo: 2)
88 | }
89 |
90 | func test_Warm_2_Queue3() {
91 | runWarmBattery(expectedValues: [1, 2], upTo: 3)
92 | }
93 |
94 | func test_Warm_5_Queue3() {
95 | runWarmBattery(expectedValues: [1, 2, 3, 4, 5], upTo: 3)
96 | }
97 |
98 | func runWarmBattery(expectedValues: [Int], upTo limit: Int) {
99 | let (transmitter, receiver) = Receiver.make(with: .warm(upTo: limit))
100 | var called = 0
101 |
102 | expectedValues.forEach(transmitter.broadcast)
103 |
104 | receiver.listen { wave in
105 | let index = max((expectedValues.count - limit), 0) + called
106 | XCTAssertTrue(expectedValues[index] == wave)
107 | called = called + 1
108 | }
109 |
110 | XCTAssertTrue(called == min(expectedValues.count, limit))
111 | }
112 |
113 | func test_Cold() {
114 | let (transmitter, receiver) = Receiver.make(with: .cold)
115 | var called = 0
116 |
117 | let expectedValues = [1, 2, 3, 4, 5]
118 | expectedValues.forEach(transmitter.broadcast)
119 |
120 | receiver.listen { wave in
121 | XCTAssertTrue(expectedValues[called] == wave)
122 | called = called + 1
123 | }
124 |
125 | XCTAssertTrue(called == 5)
126 | }
127 |
128 | func test_NoValueIsSent_IfBroadCastBeforeListenning_forHot() {
129 | let expect = expectation(description: "fun")
130 | let (transmitter, receiver) = Receiver.make()
131 |
132 | transmitter.broadcast(1)
133 |
134 | receiver.listen { wave in
135 | fatalError()
136 | }
137 |
138 | DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
139 | expect.fulfill()
140 | }
141 |
142 | waitForExpectations(timeout: 1, handler: nil)
143 | }
144 |
145 | func test_weakness() {
146 |
147 | var outterTransmitter: Receiver.Transmitter?
148 | weak var outterReceiver: Receiver?
149 |
150 | autoreleasepool {
151 | let (transmitter, receiver) = Receiver.make()
152 | outterTransmitter = transmitter
153 | outterReceiver = receiver
154 | }
155 |
156 | XCTAssertNotNil(outterTransmitter)
157 | XCTAssertNotNil(outterReceiver)
158 | }
159 |
160 | func test_disposable() {
161 | let (transmitter, receiver) = Receiver.make()
162 | var called = 0
163 |
164 | let disposable = receiver.listen { wave in
165 | called = called + 1
166 | }
167 |
168 | disposable.dispose()
169 | transmitter.broadcast(1)
170 | XCTAssertTrue(called == 0)
171 | }
172 |
173 | func test_disposable_MultipleListeners() {
174 | let (transmitter, receiver) = Receiver.make()
175 | var value = 0
176 |
177 | let disposable1 = receiver.listen { wave in
178 | value = 1
179 | }
180 |
181 | disposable1.dispose()
182 | transmitter.broadcast(1)
183 | XCTAssertTrue(value == 0)
184 |
185 | receiver.listen { wave in
186 | value = 2
187 | }
188 |
189 | transmitter.broadcast(1)
190 | XCTAssertTrue(value == 2)
191 | }
192 |
193 | func test_disposeBag() {
194 | let (transmitter, receiver) = Receiver.make()
195 | var called = 0
196 |
197 | var disposeBag: DisposeBag? = DisposeBag()
198 |
199 | receiver.listen { wave in
200 | called = called + 1
201 | }.disposed(by: disposeBag!)
202 |
203 | disposeBag = nil
204 | transmitter.broadcast(1)
205 | XCTAssertTrue(called == 0)
206 | }
207 |
208 | func test_disposeBag_multipleListeners() {
209 | let (transmitter, receiver) = Receiver.make()
210 | var called = 0
211 |
212 | var disposeBag: DisposeBag? = DisposeBag()
213 |
214 | receiver.listen { wave in
215 | called = called + 1
216 | }.disposed(by: disposeBag!)
217 |
218 | receiver.listen { wave in
219 | called = called + 1
220 | }.disposed(by: disposeBag!)
221 |
222 | receiver.listen { wave in
223 | called = called + 1
224 | }.disposed(by: disposeBag!)
225 |
226 | receiver.listen { wave in
227 | called = called + 1
228 | }.disposed(by: disposeBag!)
229 |
230 | disposeBag = nil // this forces the DisposeBag to be deinit()
231 | transmitter.broadcast(1)
232 | XCTAssertTrue(called == 0)
233 | }
234 | }
235 |
--------------------------------------------------------------------------------