!
37 | observable.addEventHandler { event in
38 | lastEvent = event
39 | }
40 |
41 | // Verify that we haven't received any events yet.
42 | XCTAssertNil(lastEvent)
43 |
44 | // Verify that changing the value publishes an event with the old and new value.
45 | testObject.value = "Second Value"
46 | XCTAssertEqual(lastEvent.oldValue, "Initial Value")
47 | XCTAssertEqual(lastEvent.newValue, "Second Value")
48 |
49 | // Verify that changing the value via the observable publishes an event.
50 | observable.value = "Third Value"
51 | XCTAssertEqual(lastEvent.oldValue, "Second Value")
52 | XCTAssertEqual(lastEvent.newValue, "Third Value")
53 | }
54 |
55 | func testObservingWhenEventHandlersAreAdded() {
56 | let testObject = TestObject(value: "Initial Value")
57 | let observable = testObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
58 |
59 | // Verify that the observer is not observing when no event handlers are added.
60 | XCTAssertFalse(observable.isObserving)
61 |
62 | // After adding an event handler, the observer should be observing.
63 | let firstEventHandlerToken = observable.addEventHandler { _ in }
64 | XCTAssertTrue(observable.isObserving)
65 |
66 | // After adding another event handler, the observer should still be observing.
67 | let secondEventHandlerToken = observable.addEventHandler { _ in }
68 | XCTAssertTrue(observable.isObserving)
69 |
70 | // After removing the first event handler, the observer should still be observing.
71 | observable.removeEventHandler(with: firstEventHandlerToken)
72 | XCTAssertTrue(observable.isObserving)
73 |
74 | // After removing the second and last event handler, the observer should stop observing.
75 | observable.removeEventHandler(with: secondEventHandlerToken)
76 | XCTAssertFalse(observable.isObserving)
77 | }
78 |
79 | func testObservingWhenEventHandlersAreAddedOnMultipleQueues() {
80 | let testObject = TestObject(value: "Initial Value")
81 | let observable = testObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
82 |
83 | // Verify that the observer is not observing when no event handlers are added.
84 | XCTAssertFalse(observable.isObserving)
85 |
86 | // Add and remove 100 event handlers on different queues.
87 | for i in 0..<100 {
88 | let eventHandlerAddedExpectation = expectation(description: "Added event handler")
89 | let eventHandlerRemovedExpectation = expectation(description: "Removed event handler")
90 |
91 | let addEventHandlerQueue = DispatchQueue(label: "com.blendle.hanson.tests.dynamic-observable.add-event-handler-queue\(i)")
92 | addEventHandlerQueue.async {
93 | let eventHandlerToken = observable.addEventHandler { _ in }
94 |
95 | XCTAssertTrue(observable.isObserving)
96 |
97 | eventHandlerAddedExpectation.fulfill()
98 |
99 | let removeEventHandlerQueue = DispatchQueue(label: "com.blendle.hanson.tests.dynamic-roperty.remove-event-handler-queue\(i)")
100 | removeEventHandlerQueue.async {
101 | observable.removeEventHandler(with: eventHandlerToken)
102 |
103 | eventHandlerRemovedExpectation.fulfill()
104 | }
105 | }
106 | }
107 |
108 | waitForExpectations(timeout: 10, handler: nil)
109 |
110 | XCTAssertFalse(observable.isObserving)
111 | }
112 |
113 | func testRetainedTargetDuringObservation() {
114 | let testObject = TestObject(value: "Initial value")
115 | let observable = testObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
116 |
117 | // Verify that the dynamic observable doesn't initially retain the target.
118 | XCTAssertNil(observable.retainedTarget)
119 |
120 | // After adding event handlers, the observable should retain its target.
121 | let firstEventHandlerToken = observable.addEventHandler { _ in }
122 | let secondEventHandlerToken = observable.addEventHandler { _ in }
123 | XCTAssertNotNil(observable.retainedTarget)
124 |
125 | // When removing the first event handler, the observable should still retain its target.
126 | observable.removeEventHandler(with: firstEventHandlerToken)
127 | XCTAssertNotNil(observable.retainedTarget)
128 |
129 | // When removing the second and last event handler, the observable should release its target.
130 | observable.removeEventHandler(with: secondEventHandlerToken)
131 | XCTAssertNil(observable.retainedTarget)
132 | }
133 |
134 | func testUnretainedTargetDuringObservation() {
135 | let testObject = TestObject(value: "Initial value")
136 | let observable = testObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self, shouldRetainTarget: false) // Prevent retaining target.
137 |
138 | // Verify that the dynamic observable doesn't initially retain the target.
139 | XCTAssertNil(observable.retainedTarget)
140 |
141 | // After adding an event handler, the observable still should not retain its target.
142 | let eventHandlerToken = observable.addEventHandler { _ in }
143 | XCTAssertNil(observable.retainedTarget)
144 |
145 | // After removing the event handler, the observable still should not retain its target.
146 | observable.removeEventHandler(with: eventHandlerToken)
147 | XCTAssertNil(observable.retainedTarget)
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
11 |
13 |
15 |
16 |
17 | ## What is Hanson?
18 |
19 | Hanson is a simple, lightweight library to observe and bind values in Swift. It's been developed to support the MVVM architecture in our [Blendle iOS app](https://itunes.apple.com/nl/app/blendle/id947936149). Hanson provides several advantages to using KVO in Swift, such as a Swiftier syntax, no boilerplate code, and the ability to use it in pure Swift types.
20 |
21 | ## Example Usage
22 |
23 | The most basic use case is to simply observe an `Observable` for changes:
24 | ```swift
25 | let observable = Observable("Hello World")
26 | observe(observable) { event in
27 | // Invoked whenever observable.value is set.
28 | print("Value changed from \(event.oldValue) to \(event.newValue)")
29 | }
30 | ```
31 |
32 | Hanson also provides a wrapper around KVO, so you can do the following to observe a `UITextField`'s `text` property for changes:
33 | ```swift
34 | let textField = UITextField()
35 | let textFieldObservable = textField.dynamicObservable(keyPath: #keyPath(UITextField.text), type: String.self)
36 | observe(textFieldObservable) { event in
37 | print("Text field value changed from \(event.oldValue) to \(event.newValue)")
38 | }
39 | ```
40 |
41 | Furthermore, you can also use Hanson to bind an observable to another observable. Let's say we have a view model that's responsible for loading data, and we want the view to show an activity indicator while the view model is loading data:
42 | ```swift
43 | class ViewModel {
44 |
45 | let isLoadingData = Observable(false)
46 |
47 | }
48 |
49 | class View {
50 |
51 | let showsActivityIndicator = Observable(false)
52 |
53 | }
54 |
55 | let viewModel = ViewModel()
56 | let view = View()
57 | bind(viewModel.isLoadingData, to: view.showsActivityIndicator)
58 | ```
59 |
60 | Now, whenever the view model's `isLoadingData` property is set to a different value, it will automatically be set to the view's `showsActivityIndicator` property.
61 |
62 | Binding is also supported from and to KVO-backed observables. To bind a text field's content to a label:
63 | ```swift
64 | let textField = UITextField()
65 | let textFieldObservable = textField.dynamicObservable(keyPath: #keyPath(UITextField.text), type: String.self)
66 |
67 | let label = UILabel()
68 | let labelObservable = label.dynamicObservable(keyPath: #keyPath(UILabel.text), type: String.self)
69 |
70 | bind(textFieldObservable, to: labelObservable)
71 | ```
72 |
73 | If you want to handle the binding yourself, you can also provide a closure that will be invoked when a new value should be set. In the following example, we'll bind an `isLoadingData` observable to a `UIActivityIndicatorView`:
74 | ```swift
75 | let isLoadingData = Observable(false)
76 | let activityIndicatorView = UIActivityIndicatorView()
77 | bind(isLoadingData, to: activityIndicatorView) { activityIndicatorView, isLoadingData in
78 | if isLoadingData {
79 | activityIndicatorView.startAnimating()
80 | } else {
81 | activityIndicatorView.stopAnimating()
82 | }
83 | }
84 | ```
85 |
86 | Hanson also supports observering notifications sent through a `NotificationCenter`. For example, to observe when an application is entering the background:
87 | ```swift
88 | let observable = NotificationCenter.default.observable(for: Notification.Name.UIApplicationDidEnterBackground)
89 | observe(observable) { notification in
90 | print("Application did enter background")
91 | }
92 | ```
93 |
94 | ### Schedulers
95 |
96 | Schedulers can be used to schedule the events of your observation. By default, Hanson uses the `CurrentThreadScheduler`, which immediatly sends events on whatever thread it currently is. Hanson also offers the `MainThreadScheduler`, which ensures events are sent on the main thread. This is useful when observing a value that can change from a background thread and you want to do UI changes based on that value. For example:
97 |
98 | ```swift
99 | let observable = Observable("Hello World")
100 | observe(observable, with: MainThreadScheduler()) { event in
101 | // It's safe to do UI work here without calling DispatchQueue.main.async here
102 | }
103 |
104 | performOnBackground {
105 | observable.value = "Hello from a background"
106 | }
107 | ```
108 |
109 | Schedulers are also supported when binding observables:
110 |
111 | ```swift
112 | let isLoadingData = Observable(true)
113 | let activityIndicatorView = UIActivityIndicatorView()
114 | bind(isLoadingData, with: MainThreadScheduler(), to: activityIndicatorView) { activityIndicatorView, isLoadingData in
115 | // It's safe to do UI work here without calling DispatchQueue.main.async here
116 | }
117 |
118 | performOnBackground {
119 | isLoadingData.value = false
120 | }
121 | ```
122 |
123 | You can create your own scheduler by conforming to the `EventScheduler` protocol.
124 |
125 | ## Requirements
126 |
127 | * iOS 8.0+ / macOS 10.9+ / tvOS 9.0+
128 | * Xcode 8
129 |
130 | ## Installation
131 |
132 | Hanson is available through either [CocoaPods](http://cocoapods.org) or [Carthage](https://github.com/Carthage/Carthage).
133 |
134 | ### Cocoapods
135 |
136 | 1. Add `pod 'Hanson'` to your `Podfile`.
137 | 2. Run `pod install`.
138 |
139 | ### Carthage
140 |
141 | 1. Add `github 'blendle/Hanson'` to your `Cartfile`.
142 | 2. Run `carthage update`.
143 | 3. Link the framework with your target as described in [Carthage Readme](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application).
144 |
145 | ### Swift Package Manager
146 |
147 | 1. In Xcode, select your project and scroll to `Frameworks, Libraries, and Embedded Content`.
148 | 2. Click the `+`.
149 | 3. At the bottom of the frameworks and libraries window that opens, select `Add other...` and then `Add package dependency...`.
150 | 4. Paste `https://github.com/blendle/Hanson.git` in the search textfield and follow through with the assistant.
151 |
152 | ## Building
153 |
154 | The project obviously builds fine through Xcode, just load up `Hanson.xcodeproj` and run it.
155 |
156 | For convenience, we've included a few scripts and a `Makefile` that allow you to build Hanson from the command line and through continuous integration. They are inspired by GitHub's [Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) boilerplate:
157 |
158 | ```
159 | |-- script/
160 | |-- etc/
161 | |-- config.sh # Contains basic configuration parameters
162 | |-- bootstrap # Prepares the project
163 | |-- setup # Sets up the local building process
164 | |-- test # Runs tests locally
165 | |-- cisetup # Sets up the CI building process
166 | |-- citest # Runs tests in a CI environment
167 | ```
168 |
169 | To get started:
170 |
171 | `$ make`
172 |
173 | To skip setup and immediately start testing:
174 |
175 | `$ make test`
176 |
177 | Make sure all tests pass before opening a Pull Request.
178 |
179 | ## Release Notes
180 |
181 | See [CHANGELOG.md](https://github.com/blendle/Hanson/blob/master/CHANGELOG.md) for a list of changes.
182 |
183 | ## License
184 |
185 | Hanson is released under the ISC license. See [LICENSE](https://github.com/blendle/Hanson/blob/master/LICENSE) for details.
186 |
--------------------------------------------------------------------------------
/HansonTests/EventPublisherTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventPublisherTests.swift
3 | // Hanson
4 | //
5 | // Created by Joost van Dijk on 26/01/2017.
6 | // Copyright © 2017 Blendle. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Hanson
11 |
12 | class EventPublisherTests: XCTestCase {
13 |
14 | func testPublishingEventWithoutEventHandlers() {
15 | let eventPublisher = TestEventPublisher()
16 | eventPublisher.publish("Sample event")
17 | }
18 |
19 | func testPublishingMultipleEvents() {
20 | let eventPublisher = TestEventPublisher()
21 |
22 | var latestEvent: String!
23 | eventPublisher.addEventHandler { event in
24 | latestEvent = event
25 | }
26 |
27 | // No event should have been published yet.
28 | XCTAssertNil(latestEvent)
29 |
30 | // When publishing an event, the event handler should be invoked with the published event.
31 | eventPublisher.publish("First event")
32 | XCTAssertEqual(latestEvent, "First event")
33 |
34 | eventPublisher.publish("Second event")
35 | XCTAssertEqual(latestEvent, "Second event")
36 | }
37 |
38 | func testPublishingMultipleEventsWithMultipleEventHandlers() {
39 | let eventPublisher = TestEventPublisher()
40 |
41 | var numberOfFirstEventHandlerInvocations = 0
42 | let firstEventHandlerToken = eventPublisher.addEventHandler { _ in
43 | numberOfFirstEventHandlerInvocations += 1
44 | }
45 |
46 | var numberOfSecondEventHandlerInvocations = 0
47 | let secondEventHandlerToken = eventPublisher.addEventHandler { _ in
48 | numberOfSecondEventHandlerInvocations += 1
49 | }
50 |
51 | // When an event is published, both counter should increment.
52 | eventPublisher.publish("Sample event")
53 | XCTAssertEqual(numberOfFirstEventHandlerInvocations, 1)
54 | XCTAssertEqual(numberOfSecondEventHandlerInvocations, 1)
55 |
56 | // After removing an event handler, only one counter should increment when publishing an event.
57 | eventPublisher.removeEventHandler(with: firstEventHandlerToken)
58 | eventPublisher.publish("Sample event")
59 | XCTAssertEqual(numberOfFirstEventHandlerInvocations, 1)
60 | XCTAssertEqual(numberOfSecondEventHandlerInvocations, 2)
61 |
62 | // After removing the other event handler, neither of the counters should increment when publishing an event.
63 | eventPublisher.removeEventHandler(with: secondEventHandlerToken)
64 | XCTAssertEqual(numberOfFirstEventHandlerInvocations, 1)
65 | XCTAssertEqual(numberOfSecondEventHandlerInvocations, 2)
66 | }
67 |
68 | func testPublishingMultipleEventsOnMultipleQueuesWhileAddingEventHandlers() {
69 | let eventPublisher = TestEventPublisher()
70 |
71 | var numberOfEvents = 0
72 | eventPublisher.addEventHandler { _ in
73 | numberOfEvents += 1
74 | }
75 |
76 | // Publish 100 events and add 100 event handlers on different queues.
77 | for i in 0..<100 {
78 | let publishExpectation = expectation(description: "Event \(i) published")
79 |
80 | let queue = DispatchQueue(label: "com.blendle.hanson.tests.event-publisher.queue\(i)")
81 | queue.async {
82 | eventPublisher.publish("Event \(i)")
83 | eventPublisher.addEventHandler { _ in }
84 |
85 | publishExpectation.fulfill()
86 | }
87 | }
88 |
89 | // Wait until all events have been published and event handlers have been added.
90 | waitForExpectations(timeout: 10, handler: nil)
91 |
92 | // Verify that 100 events have been published.
93 | XCTAssertEqual(numberOfEvents, 100)
94 |
95 | // Verify that 100 event handlers have been added, on top of the original one.
96 | XCTAssertEqual(eventPublisher.eventHandlers.count, 101)
97 | }
98 |
99 | func testAddingAndRemovingEventHandlers() {
100 | let eventPublisher = TestEventPublisher()
101 |
102 | // Initially, the event publisher shouldn't have any event handlers.
103 | XCTAssertTrue(eventPublisher.eventHandlers.isEmpty)
104 |
105 | // After adding the first event handler, the event publisher should have one event handler.
106 | let firstEventHandlerToken = eventPublisher.addEventHandler { _ in }
107 | XCTAssertEqual(eventPublisher.eventHandlers.count, 1)
108 |
109 | // After adding the second event handler, the event publisher should have two event handlers.
110 | let secondEventHandlerToken = eventPublisher.addEventHandler { _ in }
111 | XCTAssertEqual(eventPublisher.eventHandlers.count, 2)
112 |
113 | // After removing the first event handler, the event publisher should have one event handler.
114 | eventPublisher.removeEventHandler(with: firstEventHandlerToken)
115 | XCTAssertEqual(eventPublisher.eventHandlers.count, 1)
116 |
117 | // After removing the second and last event handler, the event publisher shouldn't have any event handlers.
118 | eventPublisher.removeEventHandler(with: secondEventHandlerToken)
119 | XCTAssertTrue(eventPublisher.eventHandlers.isEmpty)
120 | }
121 |
122 | func testAddingAndRemovingEventHandlersOnMultipleQueues() {
123 | let eventPublisher = TestEventPublisher()
124 |
125 | // Add 100 event handlers on different queues.
126 | for i in 0..<100 {
127 | let eventHandlerExpectation = expectation(description: "Event handler \(i) registered")
128 |
129 | let queue = DispatchQueue(label: "com.blendle.hanson.tests.event-publisher.queue\(i)")
130 | queue.async {
131 | eventPublisher.addEventHandler { _ in }
132 |
133 | eventHandlerExpectation.fulfill()
134 | }
135 | }
136 |
137 | // Wait until all event handlers have been added.
138 | waitForExpectations(timeout: 10, handler: nil)
139 |
140 | // Verify that all event handlers have been added.
141 | XCTAssertEqual(eventPublisher.eventHandlers.count, 100)
142 |
143 | // Remove the event handlers on different queues.
144 | for (i, element) in eventPublisher.eventHandlers.enumerated() {
145 | let eventHandlerToken = element.key
146 | let eventHandlerExpectation = expectation(description: "Event handler \(i) deregistered")
147 |
148 | let queue = DispatchQueue(label: "com.blendle.hanson.tests.event-publisher.queue\(i)")
149 | queue.async {
150 | eventPublisher.removeEventHandler(with: eventHandlerToken)
151 | eventHandlerExpectation.fulfill()
152 | }
153 | }
154 |
155 | // Wait until all event handlers have been removed.
156 | waitForExpectations(timeout: 10, handler: nil)
157 |
158 | // Verify that all event handlers have been removed.
159 | XCTAssertTrue(eventPublisher.eventHandlers.isEmpty)
160 | }
161 |
162 | func testPublishingEventWithCurrentThreadScheduler() {
163 | let eventPublisher = TestEventPublisher()
164 |
165 | // Add event handler with main thread scheduler
166 | let schedulerExpectation = expectation(description: "Event is sent on current thread")
167 | eventPublisher.addEventHandler({ _ in
168 | XCTAssertFalse(Thread.isMainThread)
169 |
170 | schedulerExpectation.fulfill()
171 | }, eventScheduler: CurrentThreadScheduler())
172 |
173 | // Publish event from background thread
174 | DispatchQueue.global(qos: .background).async {
175 | eventPublisher.publish("Sample event")
176 | }
177 |
178 | waitForExpectations(timeout: 10, handler: nil)
179 | }
180 |
181 | func testPublishingEventWithMainThreadScheduler() {
182 | let eventPublisher = TestEventPublisher()
183 |
184 | // Add event handler with main thread scheduler
185 | let schedulerExpectation = expectation(description: "Event is sent on main thread")
186 | eventPublisher.addEventHandler({ _ in
187 | XCTAssertTrue(Thread.isMainThread)
188 |
189 | schedulerExpectation.fulfill()
190 | }, eventScheduler: MainThreadScheduler())
191 |
192 | // Publish event from background thread
193 | DispatchQueue.global(qos: .background).async {
194 | eventPublisher.publish("Sample event")
195 | }
196 |
197 | waitForExpectations(timeout: 10, handler: nil)
198 | }
199 |
200 | }
201 |
202 |
203 |
--------------------------------------------------------------------------------
/HansonTests/ObservationManagerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObservationManagerTests.swift
3 | // Hanson
4 | //
5 | // Created by Joost van Dijk on 23/01/2017.
6 | // Copyright © 2017 Blendle. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Hanson
11 |
12 | class ObservationManagerTests: XCTestCase {
13 |
14 | func testObservingAndUnobserving() {
15 | let eventPublisher = TestEventPublisher()
16 | let observationManager = ObservationManager()
17 | let observation = observationManager.observe(eventPublisher) { _ in }
18 |
19 | // Verify that one event handler has been added.
20 | XCTAssertEqual(eventPublisher.eventHandlers.count, 1)
21 |
22 | observationManager.unobserve(observation)
23 |
24 | // Verify that the event handler has been removed.
25 | XCTAssertTrue(eventPublisher.eventHandlers.isEmpty)
26 | }
27 |
28 | func testMultipleObservationsOnSameEventPublisher() {
29 | let eventPublisher = TestEventPublisher()
30 | let observationManager = ObservationManager()
31 |
32 | for _ in 0..<10 {
33 | observationManager.observe(eventPublisher) { _ in }
34 | }
35 |
36 | XCTAssertEqual(eventPublisher.eventHandlers.count, 10)
37 |
38 | observationManager.observations.forEach { observation in
39 | observationManager.unobserve(observation)
40 | }
41 |
42 | XCTAssertTrue(eventPublisher.eventHandlers.isEmpty)
43 | }
44 |
45 | func testObservingAndUnobservingOnMultipleQueues() {
46 | let eventPublisher = TestEventPublisher()
47 | let observationManager = ObservationManager()
48 |
49 | // Add 100 observations on different queues.
50 | for i in 0..<100 {
51 | let observationExpectation = expectation(description: "Observation \(i) added")
52 |
53 | let queue = DispatchQueue(label: "com.blendle.hanson.tests.observation-manager.queue\(i)")
54 | queue.async {
55 | observationManager.observe(eventPublisher) { _ in }
56 |
57 | observationExpectation.fulfill()
58 | }
59 | }
60 |
61 | waitForExpectations(timeout: 10, handler: nil)
62 |
63 | // Verify that all observations have been added.
64 | XCTAssertEqual(observationManager.observations.count, 100)
65 |
66 | // Remove the observations on different queues.
67 | for (i, observation) in observationManager.observations.enumerated() {
68 | let observationExpectation = expectation(description: "Observation \(i) added")
69 |
70 | let queue = DispatchQueue(label: "com.blendle.hanson.tests.observation-manager.queue\(i)")
71 | queue.async {
72 | observationManager.unobserve(observation)
73 |
74 | observationExpectation.fulfill()
75 | }
76 | }
77 |
78 | waitForExpectations(timeout: 10, handler: nil)
79 |
80 | // Verify that all observations have been removed.
81 | XCTAssertTrue(observationManager.observations.isEmpty)
82 | }
83 |
84 | func testUnobserveAll() {
85 | let eventPublisher = TestEventPublisher()
86 | let observationManager = ObservationManager()
87 |
88 | // Add some sample observations.
89 | for _ in 0..<10 {
90 | observationManager.observe(eventPublisher) { _ in }
91 | }
92 |
93 | // Ensure all the observations have been added.
94 | XCTAssertEqual(observationManager.observations.count, 10)
95 | XCTAssertEqual(eventPublisher.eventHandlers.count, 10)
96 |
97 | // Verify that unobserving all removes all observations.
98 | observationManager.unobserveAll()
99 | XCTAssertTrue(observationManager.observations.isEmpty)
100 | XCTAssertTrue(eventPublisher.eventHandlers.isEmpty)
101 | }
102 |
103 | func testRetainingEventPublisherDuringObservation() {
104 | var eventPublisher: TestEventPublisher! = TestEventPublisher()
105 | weak var weakEventPublisher = eventPublisher
106 |
107 | let observationManager = ObservationManager()
108 | var observation: Observation! = observationManager.observe(eventPublisher) { _ in }
109 |
110 | // When removing our reference to the event publisher, the event publisher should still be retained by the observation manager.
111 | eventPublisher = nil
112 | XCTAssertNotNil(weakEventPublisher)
113 |
114 | // When removing the observation and our reference to the observation, the event publisher should be released.
115 | observationManager.unobserve(observation)
116 | observation = nil
117 | XCTAssertNil(weakEventPublisher)
118 | }
119 |
120 | // MARK: Bindings
121 |
122 | func testBindingWithProperties() {
123 | let fromObservable = Observable("Initial Value")
124 | let toObservable = Observable("")
125 | testBinding(from: fromObservable, to: toObservable)
126 | }
127 |
128 | func testBindingWithDynamicObservables() {
129 | let fromObject = TestObject(value: "Initial Value")
130 | let fromObservable = fromObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
131 |
132 | let toObject = TestObject(value: "")
133 | let toObservable = toObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
134 |
135 | testBinding(from: fromObservable, to: toObservable)
136 | }
137 |
138 | func testBindingFromObservableToDynamicObservable() {
139 | let fromObservable = Observable("Initial Value")
140 |
141 | let toObject = TestObject(value: "")
142 | let toObservable = toObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
143 |
144 | testBinding(from: fromObservable, to: toObservable)
145 | }
146 |
147 | func testBindingFromDynamicObservableToObservable() {
148 | let fromObject = TestObject(value: "Initial Value")
149 | let fromObservable = fromObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
150 |
151 | let toObject = TestObject(value: "")
152 | let toObservable = toObject.dynamicObservable(keyPath: #keyPath(TestObject.value), type: String.self)
153 |
154 | testBinding(from: fromObservable, to: toObservable)
155 | }
156 |
157 | func testBinding(from eventPublisher: E, to bindable: B) where E.Value == String, E.Value == B.Value {
158 | let observationManager = ObservationManager()
159 |
160 | // After binding, the receiving bindable's value should be set to the event publisher's value.
161 | let observation = observationManager.bind(eventPublisher, to: bindable)
162 | XCTAssertEqual(eventPublisher.value, bindable.value)
163 |
164 | // After updating the event publisher's value, the receiving bindable's value should also be updated.
165 | eventPublisher.value = "Second Value"
166 | XCTAssertEqual(bindable.value, eventPublisher.value)
167 |
168 | // After updating the receiving bindable's value, the event publisher's value should be left the same.
169 | bindable.value = "Third Value"
170 | XCTAssertEqual(eventPublisher.value, "Second Value")
171 | XCTAssertEqual(bindable.value, "Third Value")
172 |
173 | observationManager.unobserve(observation)
174 | }
175 |
176 | // MARK: Binding with Custom Bindable
177 |
178 | func testBindingFromObservableToCustomBindable() {
179 | let fromObservable = Observable("Initial Value")
180 |
181 | var toValue = ""
182 | let toBindable = CustomBindable(target: self) { (_, value) in
183 | toValue = value
184 | }
185 |
186 | let observationManager = ObservationManager()
187 |
188 | // After binding, the custom bindable's setter should be invoked with the event publisher's initial value.
189 | observationManager.bind(fromObservable, to: toBindable)
190 | XCTAssertEqual(fromObservable.value, "Initial Value")
191 | XCTAssertEqual(fromObservable.value, toValue)
192 |
193 | // After updating the event publisher's value, the custom bindable's setter should be invoked.
194 | fromObservable.value = "Second Value"
195 | XCTAssertEqual(fromObservable.value, toValue)
196 |
197 | // After updating the custom bindable's value, the event publisher's value should be left the same.
198 | toBindable.value = "Third Value"
199 | XCTAssertEqual(fromObservable.value, "Second Value")
200 | XCTAssertEqual(toValue, "Third Value")
201 | }
202 |
203 | // MARK: Schedulers
204 |
205 | func testObservingWithCurrentThreadScheduler() {
206 | let observationManager = ObservationManager()
207 | let observable = Observable("Initial Value")
208 |
209 | // Observe the observable so we can see on what thread the event is sent
210 | let schedulerExpectation = expectation(description: "Event is sent immediately, on the current thread")
211 | observationManager.observe(observable, with: CurrentThreadScheduler()) { _ in
212 | XCTAssertFalse(Thread.isMainThread)
213 |
214 | schedulerExpectation.fulfill()
215 | }
216 |
217 | // Change the value on a background thread
218 | DispatchQueue.global(qos: .background).async {
219 | observable.value = "Second Value"
220 | }
221 |
222 | waitForExpectations(timeout: 10, handler: nil)
223 | }
224 |
225 | func testObservingWithMainThreadScheduler() {
226 | let observationManager = ObservationManager()
227 | let observable = Observable("Initial Value")
228 |
229 | // Observe the observable so we can see on what thread the event is sent
230 | let schedulerExpectation = expectation(description: "Event is sent on the main thread")
231 | observationManager.observe(observable, with: MainThreadScheduler()) { _ in
232 | XCTAssertTrue(Thread.isMainThread)
233 |
234 | schedulerExpectation.fulfill()
235 | }
236 |
237 | // Change the value on a background thread
238 | DispatchQueue.global(qos: .background).async {
239 | observable.value = "Second Value"
240 | }
241 |
242 | waitForExpectations(timeout: 10, handler: nil)
243 | }
244 |
245 | func testInitialBindValueWithCurrentThreadScheduler() {
246 | let observationManager = ObservationManager()
247 | let fromObservable = Observable("Initial Value")
248 |
249 | let schedulerExpectation = expectation(description: "Event is sent on the main thread")
250 | let toBindable = CustomBindable(target: self) { _,_ in
251 | XCTAssertFalse(Thread.isMainThread)
252 |
253 | schedulerExpectation.fulfill()
254 | }
255 |
256 | // Create binding in background thread
257 | DispatchQueue.global(qos: .background).async {
258 | observationManager.bind(fromObservable, with: CurrentThreadScheduler(), to: toBindable)
259 | }
260 |
261 | waitForExpectations(timeout: 10, handler: nil)
262 | }
263 |
264 | func testInitialBindValueWithMainThreadScheduler() {
265 | let observationManager = ObservationManager()
266 | let fromObservable = Observable("Initial Value")
267 |
268 | let schedulerExpectation = expectation(description: "Event is sent on the main thread")
269 | let toBindable = CustomBindable(target: self) { _,_ in
270 | XCTAssertTrue(Thread.isMainThread)
271 |
272 | schedulerExpectation.fulfill()
273 | }
274 |
275 | // Create binding in background thread
276 | DispatchQueue.global(qos: .background).async {
277 | observationManager.bind(fromObservable, with: MainThreadScheduler(), to: toBindable)
278 | }
279 |
280 | waitForExpectations(timeout: 10, handler: nil)
281 | }
282 |
283 | func testBindingWithCurrentThreadScheduler() {
284 | let observationManager = ObservationManager()
285 | let fromObservable = Observable("Initial Value")
286 | let toObservable = Observable("")
287 |
288 | // Bind the observables with an ImmediateScheduler
289 | observationManager.bind(fromObservable, with: CurrentThreadScheduler(), to: toObservable)
290 |
291 | // Observe the toObservable so we can see on what thread the event is sent
292 | let schedulerExpectation = expectation(description: "Event is sent immediately, on the current thread")
293 | observationManager.observe(toObservable) { _ in
294 | XCTAssertFalse(Thread.isMainThread)
295 |
296 | schedulerExpectation.fulfill()
297 | }
298 |
299 | // Change the value on a background thread
300 | DispatchQueue.global(qos: .background).async {
301 | fromObservable.value = "Second Value"
302 | }
303 |
304 | waitForExpectations(timeout: 10, handler: nil)
305 | }
306 |
307 | func testBindingWithMainThreadScheduler() {
308 | let observationManager = ObservationManager()
309 | let fromObservable = Observable("Initial Value")
310 | let toObservable = Observable("")
311 |
312 | // Bind the observables with an MainThreadScheduler
313 | observationManager.bind(fromObservable, with: MainThreadScheduler(), to: toObservable)
314 |
315 | // Observe the toObservable so we can see on what thread the event is sent
316 | let schedulerExpectation = expectation(description: "Event is sent on main thread")
317 | observationManager.observe(toObservable) { _ in
318 | XCTAssertTrue(Thread.isMainThread)
319 |
320 | schedulerExpectation.fulfill()
321 | }
322 |
323 | // Change the value on a background thread
324 | DispatchQueue.global(qos: .background).async {
325 | fromObservable.value = "Second Value"
326 | }
327 |
328 | waitForExpectations(timeout: 10, handler: nil)
329 | }
330 |
331 | }
332 |
--------------------------------------------------------------------------------
/Hanson.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C8237A5C1ED82978003279DB /* NotificationObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8237A5B1ED82978003279DB /* NotificationObservable.swift */; };
11 | C8237A5E1ED82BBA003279DB /* NotificationObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8237A5D1ED82BBA003279DB /* NotificationObservableTests.swift */; };
12 | C8888E841E9CCA7C00803644 /* Bindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E741E9CCA7C00803644 /* Bindable.swift */; };
13 | C8888E851E9CCA7C00803644 /* CustomBindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E751E9CCA7C00803644 /* CustomBindable.swift */; };
14 | C8888E861E9CCA7C00803644 /* EventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E771E9CCA7C00803644 /* EventHandler.swift */; };
15 | C8888E871E9CCA7C00803644 /* EventPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E781E9CCA7C00803644 /* EventPublisher.swift */; };
16 | C8888E881E9CCA7C00803644 /* ValueChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E791E9CCA7C00803644 /* ValueChange.swift */; };
17 | C8888E891E9CCA7C00803644 /* NSObject+Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E7B1E9CCA7C00803644 /* NSObject+Observer.swift */; };
18 | C8888E8A1E9CCA7C00803644 /* NSRecursiveLock+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E7C1E9CCA7C00803644 /* NSRecursiveLock+Helpers.swift */; };
19 | C8888E8B1E9CCA7C00803644 /* DynamicObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E7E1E9CCA7C00803644 /* DynamicObservable.swift */; };
20 | C8888E8C1E9CCA7C00803644 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E7F1E9CCA7C00803644 /* Observable.swift */; };
21 | C8888E8D1E9CCA7C00803644 /* Observation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E811E9CCA7C00803644 /* Observation.swift */; };
22 | C8888E8E1E9CCA7C00803644 /* ObservationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E821E9CCA7C00803644 /* ObservationManager.swift */; };
23 | C8888E8F1E9CCA7C00803644 /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E831E9CCA7C00803644 /* Observer.swift */; };
24 | C8DBC7251E3A47370028E936 /* Hanson.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8DBC71B1E3A47370028E936 /* Hanson.framework */; };
25 | C8DBC72C1E3A47370028E936 /* Hanson.h in Headers */ = {isa = PBXBuildFile; fileRef = C8DBC71E1E3A47370028E936 /* Hanson.h */; settings = {ATTRIBUTES = (Public, ); }; };
26 | C8DBC7591E3A4E170028E936 /* DynamicObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DBC7521E3A4E170028E936 /* DynamicObservableTests.swift */; };
27 | C8DBC75A1E3A4E170028E936 /* EventPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DBC7531E3A4E170028E936 /* EventPublisherTests.swift */; };
28 | C8DBC75B1E3A4E170028E936 /* ObservationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DBC7541E3A4E170028E936 /* ObservationManagerTests.swift */; };
29 | C8DBC75C1E3A4E170028E936 /* ObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DBC7551E3A4E170028E936 /* ObservableTests.swift */; };
30 | C8DBC7631E3A4E2E0028E936 /* TestObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DBC7611E3A4E2E0028E936 /* TestObject.swift */; };
31 | C8DBC7641E3A4E2E0028E936 /* TestEventPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DBC7621E3A4E2E0028E936 /* TestEventPublisher.swift */; };
32 | C8EB01DC1E435A0F0036E3C9 /* CustomBindableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB01DB1E435A0F0036E3C9 /* CustomBindableTests.swift */; };
33 | CE68BC6925545B8600257431 /* EventScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE68BC6825545B8600257431 /* EventScheduler.swift */; };
34 | CE68BC6D25545DE700257431 /* CurrentThreadScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE68BC6C25545DE700257431 /* CurrentThreadScheduler.swift */; };
35 | CE68BC7125545E0400257431 /* MainThreadScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE68BC7025545E0400257431 /* MainThreadScheduler.swift */; };
36 | /* End PBXBuildFile section */
37 |
38 | /* Begin PBXContainerItemProxy section */
39 | C8DBC7261E3A47370028E936 /* PBXContainerItemProxy */ = {
40 | isa = PBXContainerItemProxy;
41 | containerPortal = C8DBC7121E3A47370028E936 /* Project object */;
42 | proxyType = 1;
43 | remoteGlobalIDString = C8DBC71A1E3A47370028E936;
44 | remoteInfo = Hanson;
45 | };
46 | /* End PBXContainerItemProxy section */
47 |
48 | /* Begin PBXFileReference section */
49 | C8237A5B1ED82978003279DB /* NotificationObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObservable.swift; sourceTree = ""; };
50 | C8237A5D1ED82BBA003279DB /* NotificationObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObservableTests.swift; sourceTree = ""; };
51 | C8888E741E9CCA7C00803644 /* Bindable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bindable.swift; sourceTree = ""; };
52 | C8888E751E9CCA7C00803644 /* CustomBindable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomBindable.swift; sourceTree = ""; };
53 | C8888E771E9CCA7C00803644 /* EventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHandler.swift; sourceTree = ""; };
54 | C8888E781E9CCA7C00803644 /* EventPublisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventPublisher.swift; sourceTree = ""; };
55 | C8888E791E9CCA7C00803644 /* ValueChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueChange.swift; sourceTree = ""; };
56 | C8888E7B1E9CCA7C00803644 /* NSObject+Observer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+Observer.swift"; sourceTree = ""; };
57 | C8888E7C1E9CCA7C00803644 /* NSRecursiveLock+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSRecursiveLock+Helpers.swift"; sourceTree = ""; };
58 | C8888E7E1E9CCA7C00803644 /* DynamicObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicObservable.swift; sourceTree = ""; };
59 | C8888E7F1E9CCA7C00803644 /* Observable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; };
60 | C8888E811E9CCA7C00803644 /* Observation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observation.swift; sourceTree = ""; };
61 | C8888E821E9CCA7C00803644 /* ObservationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservationManager.swift; sourceTree = ""; };
62 | C8888E831E9CCA7C00803644 /* Observer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observer.swift; sourceTree = ""; };
63 | C8DBC71B1E3A47370028E936 /* Hanson.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Hanson.framework; sourceTree = BUILT_PRODUCTS_DIR; };
64 | C8DBC71E1E3A47370028E936 /* Hanson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Hanson.h; sourceTree = ""; };
65 | C8DBC71F1E3A47370028E936 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
66 | C8DBC7241E3A47370028E936 /* HansonTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HansonTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
67 | C8DBC72B1E3A47370028E936 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
68 | C8DBC7521E3A4E170028E936 /* DynamicObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicObservableTests.swift; sourceTree = ""; };
69 | C8DBC7531E3A4E170028E936 /* EventPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventPublisherTests.swift; sourceTree = ""; };
70 | C8DBC7541E3A4E170028E936 /* ObservationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservationManagerTests.swift; sourceTree = ""; };
71 | C8DBC7551E3A4E170028E936 /* ObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableTests.swift; sourceTree = ""; };
72 | C8DBC7611E3A4E2E0028E936 /* TestObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestObject.swift; sourceTree = ""; };
73 | C8DBC7621E3A4E2E0028E936 /* TestEventPublisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEventPublisher.swift; sourceTree = ""; };
74 | C8EB01DB1E435A0F0036E3C9 /* CustomBindableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomBindableTests.swift; sourceTree = ""; };
75 | CE68BC6825545B8600257431 /* EventScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventScheduler.swift; sourceTree = ""; };
76 | CE68BC6C25545DE700257431 /* CurrentThreadScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentThreadScheduler.swift; sourceTree = ""; };
77 | CE68BC7025545E0400257431 /* MainThreadScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainThreadScheduler.swift; sourceTree = ""; };
78 | /* End PBXFileReference section */
79 |
80 | /* Begin PBXFrameworksBuildPhase section */
81 | C8DBC7171E3A47370028E936 /* Frameworks */ = {
82 | isa = PBXFrameworksBuildPhase;
83 | buildActionMask = 2147483647;
84 | files = (
85 | );
86 | runOnlyForDeploymentPostprocessing = 0;
87 | };
88 | C8DBC7211E3A47370028E936 /* Frameworks */ = {
89 | isa = PBXFrameworksBuildPhase;
90 | buildActionMask = 2147483647;
91 | files = (
92 | C8DBC7251E3A47370028E936 /* Hanson.framework in Frameworks */,
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | /* End PBXFrameworksBuildPhase section */
97 |
98 | /* Begin PBXGroup section */
99 | C8888E731E9CCA7C00803644 /* Bindable */ = {
100 | isa = PBXGroup;
101 | children = (
102 | C8888E741E9CCA7C00803644 /* Bindable.swift */,
103 | C8888E751E9CCA7C00803644 /* CustomBindable.swift */,
104 | );
105 | path = Bindable;
106 | sourceTree = "";
107 | };
108 | C8888E761E9CCA7C00803644 /* Event Publisher */ = {
109 | isa = PBXGroup;
110 | children = (
111 | C8888E781E9CCA7C00803644 /* EventPublisher.swift */,
112 | C8888E771E9CCA7C00803644 /* EventHandler.swift */,
113 | C8888E791E9CCA7C00803644 /* ValueChange.swift */,
114 | );
115 | path = "Event Publisher";
116 | sourceTree = "";
117 | };
118 | C8888E7A1E9CCA7C00803644 /* Helpers */ = {
119 | isa = PBXGroup;
120 | children = (
121 | C8888E7B1E9CCA7C00803644 /* NSObject+Observer.swift */,
122 | C8888E7C1E9CCA7C00803644 /* NSRecursiveLock+Helpers.swift */,
123 | );
124 | path = Helpers;
125 | sourceTree = "";
126 | };
127 | C8888E7D1E9CCA7C00803644 /* Observable */ = {
128 | isa = PBXGroup;
129 | children = (
130 | C8888E7F1E9CCA7C00803644 /* Observable.swift */,
131 | C8888E7E1E9CCA7C00803644 /* DynamicObservable.swift */,
132 | C8237A5B1ED82978003279DB /* NotificationObservable.swift */,
133 | );
134 | path = Observable;
135 | sourceTree = "";
136 | };
137 | C8888E801E9CCA7C00803644 /* Observation Manager */ = {
138 | isa = PBXGroup;
139 | children = (
140 | C8888E821E9CCA7C00803644 /* ObservationManager.swift */,
141 | C8888E811E9CCA7C00803644 /* Observation.swift */,
142 | C8888E831E9CCA7C00803644 /* Observer.swift */,
143 | );
144 | path = "Observation Manager";
145 | sourceTree = "";
146 | };
147 | C8DBC7111E3A47370028E936 = {
148 | isa = PBXGroup;
149 | children = (
150 | C8DBC71D1E3A47370028E936 /* Hanson */,
151 | C8DBC7281E3A47370028E936 /* HansonTests */,
152 | C8DBC71C1E3A47370028E936 /* Products */,
153 | );
154 | sourceTree = "";
155 | };
156 | C8DBC71C1E3A47370028E936 /* Products */ = {
157 | isa = PBXGroup;
158 | children = (
159 | C8DBC71B1E3A47370028E936 /* Hanson.framework */,
160 | C8DBC7241E3A47370028E936 /* HansonTests.xctest */,
161 | );
162 | name = Products;
163 | sourceTree = "";
164 | };
165 | C8DBC71D1E3A47370028E936 /* Hanson */ = {
166 | isa = PBXGroup;
167 | children = (
168 | C8888E801E9CCA7C00803644 /* Observation Manager */,
169 | C8888E761E9CCA7C00803644 /* Event Publisher */,
170 | CE68BC6725545B4600257431 /* Event Scheduler */,
171 | C8888E7D1E9CCA7C00803644 /* Observable */,
172 | C8888E731E9CCA7C00803644 /* Bindable */,
173 | C8888E7A1E9CCA7C00803644 /* Helpers */,
174 | C8DBC71E1E3A47370028E936 /* Hanson.h */,
175 | C8DBC71F1E3A47370028E936 /* Info.plist */,
176 | );
177 | path = Hanson;
178 | sourceTree = "";
179 | };
180 | C8DBC7281E3A47370028E936 /* HansonTests */ = {
181 | isa = PBXGroup;
182 | children = (
183 | C8DBC7601E3A4E2E0028E936 /* Helpers */,
184 | C8DBC7541E3A4E170028E936 /* ObservationManagerTests.swift */,
185 | C8DBC7531E3A4E170028E936 /* EventPublisherTests.swift */,
186 | C8DBC7551E3A4E170028E936 /* ObservableTests.swift */,
187 | C8DBC7521E3A4E170028E936 /* DynamicObservableTests.swift */,
188 | C8237A5D1ED82BBA003279DB /* NotificationObservableTests.swift */,
189 | C8EB01DB1E435A0F0036E3C9 /* CustomBindableTests.swift */,
190 | C8DBC72B1E3A47370028E936 /* Info.plist */,
191 | );
192 | path = HansonTests;
193 | sourceTree = "";
194 | };
195 | C8DBC7601E3A4E2E0028E936 /* Helpers */ = {
196 | isa = PBXGroup;
197 | children = (
198 | C8DBC7611E3A4E2E0028E936 /* TestObject.swift */,
199 | C8DBC7621E3A4E2E0028E936 /* TestEventPublisher.swift */,
200 | );
201 | path = Helpers;
202 | sourceTree = "";
203 | };
204 | CE68BC6725545B4600257431 /* Event Scheduler */ = {
205 | isa = PBXGroup;
206 | children = (
207 | CE68BC6825545B8600257431 /* EventScheduler.swift */,
208 | CE68BC6C25545DE700257431 /* CurrentThreadScheduler.swift */,
209 | CE68BC7025545E0400257431 /* MainThreadScheduler.swift */,
210 | );
211 | path = "Event Scheduler";
212 | sourceTree = "";
213 | };
214 | /* End PBXGroup section */
215 |
216 | /* Begin PBXHeadersBuildPhase section */
217 | C8DBC7181E3A47370028E936 /* Headers */ = {
218 | isa = PBXHeadersBuildPhase;
219 | buildActionMask = 2147483647;
220 | files = (
221 | C8DBC72C1E3A47370028E936 /* Hanson.h in Headers */,
222 | );
223 | runOnlyForDeploymentPostprocessing = 0;
224 | };
225 | /* End PBXHeadersBuildPhase section */
226 |
227 | /* Begin PBXNativeTarget section */
228 | C8DBC71A1E3A47370028E936 /* Hanson */ = {
229 | isa = PBXNativeTarget;
230 | buildConfigurationList = C8DBC72F1E3A47370028E936 /* Build configuration list for PBXNativeTarget "Hanson" */;
231 | buildPhases = (
232 | C8DBC7161E3A47370028E936 /* Sources */,
233 | C8DBC7171E3A47370028E936 /* Frameworks */,
234 | C8DBC7181E3A47370028E936 /* Headers */,
235 | C8DBC7191E3A47370028E936 /* Resources */,
236 | );
237 | buildRules = (
238 | );
239 | dependencies = (
240 | );
241 | name = Hanson;
242 | productName = Hanson;
243 | productReference = C8DBC71B1E3A47370028E936 /* Hanson.framework */;
244 | productType = "com.apple.product-type.framework";
245 | };
246 | C8DBC7231E3A47370028E936 /* HansonTests */ = {
247 | isa = PBXNativeTarget;
248 | buildConfigurationList = C8DBC7321E3A47370028E936 /* Build configuration list for PBXNativeTarget "HansonTests" */;
249 | buildPhases = (
250 | C8DBC7201E3A47370028E936 /* Sources */,
251 | C8DBC7211E3A47370028E936 /* Frameworks */,
252 | C8DBC7221E3A47370028E936 /* Resources */,
253 | );
254 | buildRules = (
255 | );
256 | dependencies = (
257 | C8DBC7271E3A47370028E936 /* PBXTargetDependency */,
258 | );
259 | name = HansonTests;
260 | productName = HansonTests;
261 | productReference = C8DBC7241E3A47370028E936 /* HansonTests.xctest */;
262 | productType = "com.apple.product-type.bundle.unit-test";
263 | };
264 | /* End PBXNativeTarget section */
265 |
266 | /* Begin PBXProject section */
267 | C8DBC7121E3A47370028E936 /* Project object */ = {
268 | isa = PBXProject;
269 | attributes = {
270 | LastSwiftUpdateCheck = 0820;
271 | LastUpgradeCheck = 1100;
272 | ORGANIZATIONNAME = Blendle;
273 | TargetAttributes = {
274 | C8DBC71A1E3A47370028E936 = {
275 | CreatedOnToolsVersion = 8.2.1;
276 | LastSwiftMigration = 1020;
277 | ProvisioningStyle = Automatic;
278 | };
279 | C8DBC7231E3A47370028E936 = {
280 | CreatedOnToolsVersion = 8.2.1;
281 | LastSwiftMigration = 1020;
282 | ProvisioningStyle = Automatic;
283 | };
284 | };
285 | };
286 | buildConfigurationList = C8DBC7151E3A47370028E936 /* Build configuration list for PBXProject "Hanson" */;
287 | compatibilityVersion = "Xcode 3.2";
288 | developmentRegion = en;
289 | hasScannedForEncodings = 0;
290 | knownRegions = (
291 | en,
292 | Base,
293 | );
294 | mainGroup = C8DBC7111E3A47370028E936;
295 | productRefGroup = C8DBC71C1E3A47370028E936 /* Products */;
296 | projectDirPath = "";
297 | projectRoot = "";
298 | targets = (
299 | C8DBC71A1E3A47370028E936 /* Hanson */,
300 | C8DBC7231E3A47370028E936 /* HansonTests */,
301 | );
302 | };
303 | /* End PBXProject section */
304 |
305 | /* Begin PBXResourcesBuildPhase section */
306 | C8DBC7191E3A47370028E936 /* Resources */ = {
307 | isa = PBXResourcesBuildPhase;
308 | buildActionMask = 2147483647;
309 | files = (
310 | );
311 | runOnlyForDeploymentPostprocessing = 0;
312 | };
313 | C8DBC7221E3A47370028E936 /* Resources */ = {
314 | isa = PBXResourcesBuildPhase;
315 | buildActionMask = 2147483647;
316 | files = (
317 | );
318 | runOnlyForDeploymentPostprocessing = 0;
319 | };
320 | /* End PBXResourcesBuildPhase section */
321 |
322 | /* Begin PBXSourcesBuildPhase section */
323 | C8DBC7161E3A47370028E936 /* Sources */ = {
324 | isa = PBXSourcesBuildPhase;
325 | buildActionMask = 2147483647;
326 | files = (
327 | CE68BC7125545E0400257431 /* MainThreadScheduler.swift in Sources */,
328 | C8237A5C1ED82978003279DB /* NotificationObservable.swift in Sources */,
329 | CE68BC6925545B8600257431 /* EventScheduler.swift in Sources */,
330 | C8888E841E9CCA7C00803644 /* Bindable.swift in Sources */,
331 | C8888E8F1E9CCA7C00803644 /* Observer.swift in Sources */,
332 | C8888E861E9CCA7C00803644 /* EventHandler.swift in Sources */,
333 | C8888E851E9CCA7C00803644 /* CustomBindable.swift in Sources */,
334 | C8888E8A1E9CCA7C00803644 /* NSRecursiveLock+Helpers.swift in Sources */,
335 | C8888E8D1E9CCA7C00803644 /* Observation.swift in Sources */,
336 | C8888E8C1E9CCA7C00803644 /* Observable.swift in Sources */,
337 | C8888E8B1E9CCA7C00803644 /* DynamicObservable.swift in Sources */,
338 | C8888E871E9CCA7C00803644 /* EventPublisher.swift in Sources */,
339 | C8888E881E9CCA7C00803644 /* ValueChange.swift in Sources */,
340 | C8888E891E9CCA7C00803644 /* NSObject+Observer.swift in Sources */,
341 | CE68BC6D25545DE700257431 /* CurrentThreadScheduler.swift in Sources */,
342 | C8888E8E1E9CCA7C00803644 /* ObservationManager.swift in Sources */,
343 | );
344 | runOnlyForDeploymentPostprocessing = 0;
345 | };
346 | C8DBC7201E3A47370028E936 /* Sources */ = {
347 | isa = PBXSourcesBuildPhase;
348 | buildActionMask = 2147483647;
349 | files = (
350 | C8DBC75C1E3A4E170028E936 /* ObservableTests.swift in Sources */,
351 | C8237A5E1ED82BBA003279DB /* NotificationObservableTests.swift in Sources */,
352 | C8EB01DC1E435A0F0036E3C9 /* CustomBindableTests.swift in Sources */,
353 | C8DBC7591E3A4E170028E936 /* DynamicObservableTests.swift in Sources */,
354 | C8DBC75A1E3A4E170028E936 /* EventPublisherTests.swift in Sources */,
355 | C8DBC7641E3A4E2E0028E936 /* TestEventPublisher.swift in Sources */,
356 | C8DBC75B1E3A4E170028E936 /* ObservationManagerTests.swift in Sources */,
357 | C8DBC7631E3A4E2E0028E936 /* TestObject.swift in Sources */,
358 | );
359 | runOnlyForDeploymentPostprocessing = 0;
360 | };
361 | /* End PBXSourcesBuildPhase section */
362 |
363 | /* Begin PBXTargetDependency section */
364 | C8DBC7271E3A47370028E936 /* PBXTargetDependency */ = {
365 | isa = PBXTargetDependency;
366 | target = C8DBC71A1E3A47370028E936 /* Hanson */;
367 | targetProxy = C8DBC7261E3A47370028E936 /* PBXContainerItemProxy */;
368 | };
369 | /* End PBXTargetDependency section */
370 |
371 | /* Begin XCBuildConfiguration section */
372 | C8DBC72D1E3A47370028E936 /* Debug */ = {
373 | isa = XCBuildConfiguration;
374 | buildSettings = {
375 | ALWAYS_SEARCH_USER_PATHS = NO;
376 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
377 | CLANG_ANALYZER_NONNULL = YES;
378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
379 | CLANG_CXX_LIBRARY = "libc++";
380 | CLANG_ENABLE_MODULES = YES;
381 | CLANG_ENABLE_OBJC_ARC = YES;
382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
383 | CLANG_WARN_BOOL_CONVERSION = YES;
384 | CLANG_WARN_COMMA = YES;
385 | CLANG_WARN_CONSTANT_CONVERSION = YES;
386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
388 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
389 | CLANG_WARN_EMPTY_BODY = YES;
390 | CLANG_WARN_ENUM_CONVERSION = YES;
391 | CLANG_WARN_INFINITE_RECURSION = YES;
392 | CLANG_WARN_INT_CONVERSION = YES;
393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
398 | CLANG_WARN_STRICT_PROTOTYPES = YES;
399 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
400 | CLANG_WARN_UNREACHABLE_CODE = YES;
401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
402 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
403 | COPY_PHASE_STRIP = NO;
404 | CURRENT_PROJECT_VERSION = 1;
405 | DEBUG_INFORMATION_FORMAT = dwarf;
406 | ENABLE_STRICT_OBJC_MSGSEND = YES;
407 | ENABLE_TESTABILITY = YES;
408 | GCC_C_LANGUAGE_STANDARD = gnu99;
409 | GCC_DYNAMIC_NO_PIC = NO;
410 | GCC_NO_COMMON_BLOCKS = YES;
411 | GCC_OPTIMIZATION_LEVEL = 0;
412 | GCC_PREPROCESSOR_DEFINITIONS = (
413 | "DEBUG=1",
414 | "$(inherited)",
415 | );
416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
418 | GCC_WARN_UNDECLARED_SELECTOR = YES;
419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
420 | GCC_WARN_UNUSED_FUNCTION = YES;
421 | GCC_WARN_UNUSED_VARIABLE = YES;
422 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
423 | MTL_ENABLE_DEBUG_INFO = YES;
424 | ONLY_ACTIVE_ARCH = YES;
425 | SDKROOT = iphoneos;
426 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
427 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
428 | TARGETED_DEVICE_FAMILY = "1,2";
429 | VERSIONING_SYSTEM = "apple-generic";
430 | VERSION_INFO_PREFIX = "";
431 | };
432 | name = Debug;
433 | };
434 | C8DBC72E1E3A47370028E936 /* Release */ = {
435 | isa = XCBuildConfiguration;
436 | buildSettings = {
437 | ALWAYS_SEARCH_USER_PATHS = NO;
438 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
439 | CLANG_ANALYZER_NONNULL = YES;
440 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
441 | CLANG_CXX_LIBRARY = "libc++";
442 | CLANG_ENABLE_MODULES = YES;
443 | CLANG_ENABLE_OBJC_ARC = YES;
444 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
445 | CLANG_WARN_BOOL_CONVERSION = YES;
446 | CLANG_WARN_COMMA = YES;
447 | CLANG_WARN_CONSTANT_CONVERSION = YES;
448 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
449 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
450 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
451 | CLANG_WARN_EMPTY_BODY = YES;
452 | CLANG_WARN_ENUM_CONVERSION = YES;
453 | CLANG_WARN_INFINITE_RECURSION = YES;
454 | CLANG_WARN_INT_CONVERSION = YES;
455 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
456 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
457 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
458 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
459 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
460 | CLANG_WARN_STRICT_PROTOTYPES = YES;
461 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
462 | CLANG_WARN_UNREACHABLE_CODE = YES;
463 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
464 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
465 | COPY_PHASE_STRIP = NO;
466 | CURRENT_PROJECT_VERSION = 1;
467 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
468 | ENABLE_NS_ASSERTIONS = NO;
469 | ENABLE_STRICT_OBJC_MSGSEND = YES;
470 | GCC_C_LANGUAGE_STANDARD = gnu99;
471 | GCC_NO_COMMON_BLOCKS = YES;
472 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
473 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
474 | GCC_WARN_UNDECLARED_SELECTOR = YES;
475 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
476 | GCC_WARN_UNUSED_FUNCTION = YES;
477 | GCC_WARN_UNUSED_VARIABLE = YES;
478 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
479 | MTL_ENABLE_DEBUG_INFO = NO;
480 | SDKROOT = iphoneos;
481 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
482 | TARGETED_DEVICE_FAMILY = "1,2";
483 | VALIDATE_PRODUCT = YES;
484 | VERSIONING_SYSTEM = "apple-generic";
485 | VERSION_INFO_PREFIX = "";
486 | };
487 | name = Release;
488 | };
489 | C8DBC7301E3A47370028E936 /* Debug */ = {
490 | isa = XCBuildConfiguration;
491 | buildSettings = {
492 | CLANG_ENABLE_MODULES = YES;
493 | CODE_SIGN_IDENTITY = "";
494 | DEFINES_MODULE = YES;
495 | DYLIB_COMPATIBILITY_VERSION = 1;
496 | DYLIB_CURRENT_VERSION = 1;
497 | DYLIB_INSTALL_NAME_BASE = "@rpath";
498 | INFOPLIST_FILE = Hanson/Info.plist;
499 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
500 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
501 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
502 | PRODUCT_BUNDLE_IDENTIFIER = com.blendle.hanson;
503 | PRODUCT_NAME = "$(TARGET_NAME)";
504 | SKIP_INSTALL = YES;
505 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
506 | SWIFT_VERSION = 5.0;
507 | };
508 | name = Debug;
509 | };
510 | C8DBC7311E3A47370028E936 /* Release */ = {
511 | isa = XCBuildConfiguration;
512 | buildSettings = {
513 | CLANG_ENABLE_MODULES = YES;
514 | CODE_SIGN_IDENTITY = "";
515 | DEFINES_MODULE = YES;
516 | DYLIB_COMPATIBILITY_VERSION = 1;
517 | DYLIB_CURRENT_VERSION = 1;
518 | DYLIB_INSTALL_NAME_BASE = "@rpath";
519 | INFOPLIST_FILE = Hanson/Info.plist;
520 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
521 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
522 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
523 | PRODUCT_BUNDLE_IDENTIFIER = com.blendle.hanson;
524 | PRODUCT_NAME = "$(TARGET_NAME)";
525 | SKIP_INSTALL = YES;
526 | SWIFT_VERSION = 5.0;
527 | };
528 | name = Release;
529 | };
530 | C8DBC7331E3A47370028E936 /* Debug */ = {
531 | isa = XCBuildConfiguration;
532 | buildSettings = {
533 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
534 | INFOPLIST_FILE = HansonTests/Info.plist;
535 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
536 | PRODUCT_BUNDLE_IDENTIFIER = com.blendle.HansonTests;
537 | PRODUCT_NAME = "$(TARGET_NAME)";
538 | SWIFT_VERSION = 5.0;
539 | };
540 | name = Debug;
541 | };
542 | C8DBC7341E3A47370028E936 /* Release */ = {
543 | isa = XCBuildConfiguration;
544 | buildSettings = {
545 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
546 | INFOPLIST_FILE = HansonTests/Info.plist;
547 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
548 | PRODUCT_BUNDLE_IDENTIFIER = com.blendle.HansonTests;
549 | PRODUCT_NAME = "$(TARGET_NAME)";
550 | SWIFT_VERSION = 5.0;
551 | };
552 | name = Release;
553 | };
554 | /* End XCBuildConfiguration section */
555 |
556 | /* Begin XCConfigurationList section */
557 | C8DBC7151E3A47370028E936 /* Build configuration list for PBXProject "Hanson" */ = {
558 | isa = XCConfigurationList;
559 | buildConfigurations = (
560 | C8DBC72D1E3A47370028E936 /* Debug */,
561 | C8DBC72E1E3A47370028E936 /* Release */,
562 | );
563 | defaultConfigurationIsVisible = 0;
564 | defaultConfigurationName = Release;
565 | };
566 | C8DBC72F1E3A47370028E936 /* Build configuration list for PBXNativeTarget "Hanson" */ = {
567 | isa = XCConfigurationList;
568 | buildConfigurations = (
569 | C8DBC7301E3A47370028E936 /* Debug */,
570 | C8DBC7311E3A47370028E936 /* Release */,
571 | );
572 | defaultConfigurationIsVisible = 0;
573 | defaultConfigurationName = Release;
574 | };
575 | C8DBC7321E3A47370028E936 /* Build configuration list for PBXNativeTarget "HansonTests" */ = {
576 | isa = XCConfigurationList;
577 | buildConfigurations = (
578 | C8DBC7331E3A47370028E936 /* Debug */,
579 | C8DBC7341E3A47370028E936 /* Release */,
580 | );
581 | defaultConfigurationIsVisible = 0;
582 | defaultConfigurationName = Release;
583 | };
584 | /* End XCConfigurationList section */
585 | };
586 | rootObject = C8DBC7121E3A47370028E936 /* Project object */;
587 | }
588 |
--------------------------------------------------------------------------------