Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Signals/Signal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,38 @@ fileprivate func signalsAssert(_ condition: Bool, _ message: String) {
#if DEBUG
var assertionHandlerOverride:((_ condition: Bool, _ message: String) -> ())?
#endif

/**
A wrapper for a variable that allows observing its value changes in the `KVO` fashion
### Usage Example: ###
````
class MyClass {
// Custom Access Control for setter
private(set) var property = Property(value: 5)
}
````
*/
public struct Property<T> {

/// The underlying signal.
public private(set) var signal: Signal<T>

/// The current value of the property.
public var value: T {
get { return signal.lastDataFired! }
set {
assert(Thread.isMainThread, "The property mutation must occur from the main thread because UI code might be observing changes")
signal.fire(newValue)
}
}

public init(value: T) {
signal = Signal<T>(retainLastData: true)
signal.fire(value)
}

/// Subscribes an observer to the Property and invokes its callback immediately with the current value
public func observe(with observer: AnyObject, callback: @escaping (T) -> Void) {
signal.subscribePast(with: observer, callback: callback)
}
}
73 changes: 73 additions & 0 deletions SignalsTests/SignalsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,76 @@ class SignalsTests: XCTestCase {
}
}
}

class PropertyTests: XCTestCase {

func test_observe_shouldFireCallbackWithCurrentValue() {
let sut = Property(value: 10)
XCTAssertEqual(sut.value, 10)
let subscription = NSObject()
let expectation = XCTestExpectation(description: "Subscriber should be notified about the update")
sut.observe(with: subscription) { value in
XCTAssertEqual(value, 10)
expectation.fulfill()
}
wait(for: [expectation], timeout: 0.1)
}

func test_valueSetter_shouldChangeTheValueAndFireSubscriptionCallback() {
var sut = Property(value: 10)
XCTAssertEqual(sut.value, 10)
let subscription = NSObject()
let expectation = XCTestExpectation(description: "Subscriber should be notified about the update")
var dispatchCount = 0
sut.observe(with: subscription) { value in
dispatchCount += 1
if dispatchCount > 1 {
XCTAssertEqual(value, 5)
expectation.fulfill()
}
}
sut.value = 5
wait(for: [expectation], timeout: 0.1)
}

func test_automaticCancellation() {
var sut = Property(value: "abc")
var subscription: NSObject? = NSObject()
var dispatchCount = 0
sut.observe(with: subscription!) { value in
dispatchCount += 1
if dispatchCount > 1 {
XCTFail("Subscription should be cancelled")
}
}
subscription = nil
let expectation = XCTestExpectation(description: "Delay")
DispatchQueue.main.async {
sut.value = "def"
expectation.fulfill()
}
wait(for: [expectation], timeout: 0.1)
}

func test_manualCancellation() {
var sut = Property(value: false)
let subscription = NSObject()
var dispatchCount = 0
sut.observe(with: subscription) { value in
dispatchCount += 1
if dispatchCount > 1 {
XCTFail("Subscription should be cancelled")
}
}
sut.signal.cancelSubscription(for: subscription)
sut.value = true
sut.observe(with: subscription) { value in
dispatchCount += 1
if dispatchCount > 2 {
XCTFail("Subscription should be cancelled")
}
}
sut.signal.cancelAllSubscriptions()
sut.value = false
}
}