├── .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 | [![codecov](https://codecov.io/gh/RuiAAPEres/Receiver/branch/master/graph/badge.svg)](https://codecov.io/gh/RuiAAPeres/Receiver) 6 | [![Build Status](https://travis-ci.org/RuiAAPeres/Receiver.svg?branch=master)](https://travis-ci.org/RuiAAPeres/Receiver) 7 | [![Swift 4.1](https://img.shields.io/badge/Swift-4.1-orange.svg?style=flat)](https://developer.apple.com/swift/) 8 | [![License MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg?style=flat)](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 | ![](https://viralviralvideos.com/wp-content/uploads/GIF/2015/06/OMG-this-is-so-awesome-GIF.gif) 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 | --------------------------------------------------------------------------------