diff --git a/Hanson.xcodeproj/project.pbxproj b/Hanson.xcodeproj/project.pbxproj index 389bb50..20a2312 100644 --- a/Hanson.xcodeproj/project.pbxproj +++ b/Hanson.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 857451EC235A360500473D97 /* ObservablePropertyWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857451EB235A360400473D97 /* ObservablePropertyWrapperTests.swift */; }; C8237A5C1ED82978003279DB /* NotificationObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8237A5B1ED82978003279DB /* NotificationObservable.swift */; }; C8237A5E1ED82BBA003279DB /* NotificationObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8237A5D1ED82BBA003279DB /* NotificationObservableTests.swift */; }; C8888E841E9CCA7C00803644 /* Bindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8888E741E9CCA7C00803644 /* Bindable.swift */; }; @@ -43,6 +44,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 857451EB235A360400473D97 /* ObservablePropertyWrapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservablePropertyWrapperTests.swift; sourceTree = ""; }; C8237A5B1ED82978003279DB /* NotificationObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObservable.swift; sourceTree = ""; }; C8237A5D1ED82BBA003279DB /* NotificationObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObservableTests.swift; sourceTree = ""; }; C8888E741E9CCA7C00803644 /* Bindable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bindable.swift; sourceTree = ""; }; @@ -174,12 +176,13 @@ isa = PBXGroup; children = ( C8DBC7601E3A4E2E0028E936 /* Helpers */, - C8DBC7541E3A4E170028E936 /* ObservationManagerTests.swift */, - C8DBC7531E3A4E170028E936 /* EventPublisherTests.swift */, - C8DBC7551E3A4E170028E936 /* ObservableTests.swift */, + C8EB01DB1E435A0F0036E3C9 /* CustomBindableTests.swift */, C8DBC7521E3A4E170028E936 /* DynamicObservableTests.swift */, + C8DBC7531E3A4E170028E936 /* EventPublisherTests.swift */, C8237A5D1ED82BBA003279DB /* NotificationObservableTests.swift */, - C8EB01DB1E435A0F0036E3C9 /* CustomBindableTests.swift */, + 857451EB235A360400473D97 /* ObservablePropertyWrapperTests.swift */, + C8DBC7551E3A4E170028E936 /* ObservableTests.swift */, + C8DBC7541E3A4E170028E936 /* ObservationManagerTests.swift */, C8DBC72B1E3A47370028E936 /* Info.plist */, ); path = HansonTests; @@ -335,6 +338,7 @@ C8DBC7641E3A4E2E0028E936 /* TestEventPublisher.swift in Sources */, C8DBC75B1E3A4E170028E936 /* ObservationManagerTests.swift in Sources */, C8DBC7631E3A4E2E0028E936 /* TestObject.swift in Sources */, + 857451EC235A360500473D97 /* ObservablePropertyWrapperTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Hanson/Observable/Observable.swift b/Hanson/Observable/Observable.swift index 5a8f3fc..a8175c9 100644 --- a/Hanson/Observable/Observable.swift +++ b/Hanson/Observable/Observable.swift @@ -10,6 +10,7 @@ import Foundation /// The `Observable` class represents a value that can be observed for changes. /// When changing the observable's value, the observable will publish a `ValueChange` event with the old and new value. +@propertyWrapper public class Observable: EventPublisher, Bindable { /// An alias for the event type that the observable publishes. @@ -22,8 +23,22 @@ public class Observable: EventPublisher, Bindable { _value = value } + /// Initializes the observable through a propertyWrapper's initial value assignment. + /// + /// - Parameter value: The observable's initial value. + public init(wrappedValue value: Value) { + _value = value + } + // MARK: Value + /// The wrapped value of the observable as accessed through the propertyWrapper. + /// When setting this to a new value, the observable will publish a `ValueChange` event with the old and new value. + public var wrappedValue: Value { + get { return value } + set { value = newValue } + } + /// The value of the observable. When setting this to a new value, the observable will publish a `ValueChange` event with the old and new value. public var value: Value { get { diff --git a/HansonTests/ObservablePropertyWrapperTests.swift b/HansonTests/ObservablePropertyWrapperTests.swift new file mode 100644 index 0000000..293b219 --- /dev/null +++ b/HansonTests/ObservablePropertyWrapperTests.swift @@ -0,0 +1,71 @@ +// +// ObservablePropertyWrapperTests.swift +// HansonTests +// +// Created by Niklas Holloh on 26.07.19. +// Copyright © 2019 Blendle. All rights reserved. +// + +import XCTest +@testable import Hanson + +class ObservablePropertyWrapperTests: XCTestCase { + + @Observable var value = "Hello World" + + func testObservingValue() { + var lastEvent: ValueChange! + _value.addEventHandler { event in + lastEvent = event + } + + // Verify that changing the value publishes an event with the old and new value. + value = "New Value" + XCTAssertEqual(lastEvent.oldValue, "Hello World") + XCTAssertEqual(lastEvent.newValue, "New Value") + + value = "Some Other Value" + XCTAssertEqual(lastEvent.oldValue, "New Value") + XCTAssertEqual(lastEvent.newValue, "Some Other Value") + } + + func testSilentlyUpdatingValue() { + var lastEvent: ValueChange! + _value.addEventHandler { event in + lastEvent = event + } + + // Verify that changing the value works via the silently update function. + _value.silentlyUpdateValue(to: "New Value") + XCTAssertEqual(value, "New Value") + + // Verify that no event has been published. + XCTAssertNil(lastEvent) + + } + + func testUpdatingValueOnMultipleQueues() { + var numberOfEvents = 0 + _value.addEventHandler { _ in + numberOfEvents += 1 + } + + // Update the value 100 times on different queues. + for i in 0..<100 { + let valueExpectation = expectation(description: "Updated value") + + let queue = DispatchQueue(label: "com.blendle.hanson.tests.observable.queue\(i)") + queue.async { + self.value = "New Value" + + valueExpectation.fulfill() + } + } + + waitForExpectations(timeout: 10, handler: nil) + + // Verify that the value has been updated 100 times. + XCTAssertEqual(numberOfEvents, 100) + } + +}