diff --git a/Documentation/GettingStarted.md b/Documentation/GettingStarted.md index abd9e4c7..c656fc64 100644 --- a/Documentation/GettingStarted.md +++ b/Documentation/GettingStarted.md @@ -700,7 +700,7 @@ This isn't something that should be practiced often, and is a bad code smell, bu let kittens = Variable(firstKitten) // again back in Rx monad - kittens + kittens.asObservable() .map { kitten in return kitten.purr() } @@ -873,12 +873,14 @@ Variable wraps a [`Subject`](http://reactivex.io/documentation/subject.html). Mo It will also broadcast it's current value immediately on subscription. +After variable is deallocated, it will complete the observable sequence returned from `.asObservable()`. + ```swift let variable = Variable(0) print("Before first subscription ---") -variable +variable.asObservable() .subscribeNext { n in print("First \(n)") } diff --git a/Rx.playground/Pages/Combining_Observables.xcplaygroundpage/Contents.swift b/Rx.playground/Pages/Combining_Observables.xcplaygroundpage/Contents.swift index b25d7ea8..7887749b 100644 --- a/Rx.playground/Pages/Combining_Observables.xcplaygroundpage/Contents.swift +++ b/Rx.playground/Pages/Combining_Observables.xcplaygroundpage/Contents.swift @@ -255,9 +255,10 @@ example("switchLatest") { let var2 = Variable(200) // var3 is like an Observable> - let var3 = Variable(var1) + let var3 = Variable(var1.asObservable()) let d = var3 + .asObservable() .switchLatest() .subscribe { print($0) @@ -268,7 +269,7 @@ example("switchLatest") { var1.value = 3 var1.value = 4 - var3.value = var2 + var3.value = var2.asObservable() var2.value = 201 diff --git a/Rx.playground/Pages/Subjects.xcplaygroundpage/Contents.swift b/Rx.playground/Pages/Subjects.xcplaygroundpage/Contents.swift index 77737bbc..e74015e4 100644 --- a/Rx.playground/Pages/Subjects.xcplaygroundpage/Contents.swift +++ b/Rx.playground/Pages/Subjects.xcplaygroundpage/Contents.swift @@ -93,10 +93,10 @@ example("BehaviorSubject") { example("Variable") { let disposeBag = DisposeBag() let variable = Variable("z") - writeSequenceToConsole("1", sequence: variable).addDisposableTo(disposeBag) + writeSequenceToConsole("1", sequence: variable.asObservable()).addDisposableTo(disposeBag) variable.value = "a" variable.value = "b" - writeSequenceToConsole("2", sequence: variable).addDisposableTo(disposeBag) + writeSequenceToConsole("2", sequence: variable.asObservable()).addDisposableTo(disposeBag) variable.value = "c" variable.value = "d" } diff --git a/RxCocoa/Common/CocoaUnits/Driver/Variable+Driver.swift b/RxCocoa/Common/CocoaUnits/Driver/Variable+Driver.swift index 8d683b09..f2c038f4 100644 --- a/RxCocoa/Common/CocoaUnits/Driver/Variable+Driver.swift +++ b/RxCocoa/Common/CocoaUnits/Driver/Variable+Driver.swift @@ -19,7 +19,7 @@ extension Variable { */ @warn_unused_result(message="http://git.io/rxs.uo") public func asDriver() -> Driver { - let source = self + let source = self.asObservable() .observeOn(driverObserveOnScheduler) return Driver(source) } diff --git a/RxExample/RxExample/Examples/PartialUpdates/PartialUpdatesViewController.swift b/RxExample/RxExample/Examples/PartialUpdates/PartialUpdatesViewController.swift index 0d172f66..527ebc8a 100644 --- a/RxExample/RxExample/Examples/PartialUpdates/PartialUpdatesViewController.swift +++ b/RxExample/RxExample/Examples/PartialUpdates/PartialUpdatesViewController.swift @@ -45,39 +45,6 @@ class PartialUpdatesViewController : ViewController { var sections = Variable([NumberSection]()) - func skinTableViewDataSource(dataSource: RxTableViewSectionedDataSource) { - dataSource.cellFactory = { (tv, ip, i) in - let cell = tv.dequeueReusableCellWithIdentifier("Cell") - ?? UITableViewCell(style:.Default, reuseIdentifier: "Cell") - - cell.textLabel!.text = "\(i)" - - return cell - } - - dataSource.titleForHeaderInSection = { [unowned dataSource] (section: Int) -> String in - return dataSource.sectionAtIndex(section).model - } - } - - func skinCollectionViewDataSource(dataSource: RxCollectionViewSectionedDataSource) { - dataSource.cellFactory = { (cv, ip, i) in - let cell = cv.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: ip) as! NumberCell - - cell.value!.text = "\(i)" - - return cell - } - - dataSource.supplementaryViewFactory = { [unowned dataSource] (cv, kind, ip) in - let section = cv.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "Section", forIndexPath: ip) as! NumberSectionView - - section.value!.text = "\(dataSource.sectionAtIndex(ip.section).model)" - - return section - } - } - override func viewDidLoad() { super.viewDidLoad() @@ -162,6 +129,39 @@ class PartialUpdatesViewController : ViewController { .addDisposableTo(disposeBag) } + func skinTableViewDataSource(dataSource: RxTableViewSectionedDataSource) { + dataSource.cellFactory = { (tv, ip, i) in + let cell = tv.dequeueReusableCellWithIdentifier("Cell") + ?? UITableViewCell(style:.Default, reuseIdentifier: "Cell") + + cell.textLabel!.text = "\(i)" + + return cell + } + + dataSource.titleForHeaderInSection = { [unowned dataSource] (section: Int) -> String in + return dataSource.sectionAtIndex(section).model + } + } + + func skinCollectionViewDataSource(dataSource: RxCollectionViewSectionedDataSource) { + dataSource.cellFactory = { (cv, ip, i) in + let cell = cv.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: ip) as! NumberCell + + cell.value!.text = "\(i)" + + return cell + } + + dataSource.supplementaryViewFactory = { [unowned dataSource] (cv, kind, ip) in + let section = cv.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "Section", forIndexPath: ip) as! NumberSectionView + + section.value!.text = "\(dataSource.sectionAtIndex(ip.section).model)" + + return section + } + } + override func viewWillDisappear(animated: Bool) { self.timer?.invalidate() } diff --git a/RxExample/RxExample/Operators.swift b/RxExample/RxExample/Operators.swift index 99f5cd3a..a444eca8 100644 --- a/RxExample/RxExample/Operators.swift +++ b/RxExample/RxExample/Operators.swift @@ -18,7 +18,7 @@ infix operator <-> { } func <-> (property: ControlProperty, variable: Variable) -> Disposable { - let bindToUIDisposable = variable + let bindToUIDisposable = variable.asObservable() .bindTo(property) let bindToVariable = property .subscribe(onNext: { n in diff --git a/RxExample/RxExample/Services/ActivityIndicator.swift b/RxExample/RxExample/Services/ActivityIndicator.swift index 6feebf17..92b274e7 100644 --- a/RxExample/RxExample/Services/ActivityIndicator.swift +++ b/RxExample/RxExample/Services/ActivityIndicator.swift @@ -44,7 +44,7 @@ class ActivityIndicator : DriverConvertibleType { private let _loading: Driver init() { - _loading = _variable + _loading = _variable.asObservable() .map { $0 > 0 } .distinctUntilChanged() .asDriver { (error: ErrorType) -> Driver in diff --git a/RxSwift/Subjects/Variable.swift b/RxSwift/Subjects/Variable.swift index e648b15a..4409eaa6 100644 --- a/RxSwift/Subjects/Variable.swift +++ b/RxSwift/Subjects/Variable.swift @@ -11,9 +11,13 @@ import Foundation /** Variable is a wrapper for `BehaviorSubject`. -Unlike `BehaviorSubject` it can't terminate with error. +Unlike `BehaviorSubject` it can't terminate with error, and when variable is deallocated + it will complete it's observable sequence (`asObservable`). */ -public class Variable : ObservableType { +@available(*, deprecated=2.0.0, message="Variable will remain in the 2.0.0 API, but just use `variable.asObservable()` because it won't be `ObservableType` (no way to warn about deprecated interface implementation). Just do, `variable.asObservable().map { _ in ...}` and ignore this warning.") +public class Variable + : ObservableType { // << -- this part and subscribe method will be deprecated + public typealias E = Element private let _subject: BehaviorSubject @@ -73,4 +77,8 @@ public class Variable : ObservableType { public func asObservable() -> Observable { return _subject } + + deinit { + _subject.on(.Completed) + } } \ No newline at end of file diff --git a/Tests/RxCocoaTests/Control+RxTests+UIKit.swift b/Tests/RxCocoaTests/Control+RxTests+UIKit.swift index 7406fd59..95bea265 100644 --- a/Tests/RxCocoaTests/Control+RxTests+UIKit.swift +++ b/Tests/RxCocoaTests/Control+RxTests+UIKit.swift @@ -172,7 +172,8 @@ extension ControlTests { // UILabel extension ControlTests { func testLabel_HasWeakReference() { - ensureControlObserverHasWeakReference(UILabel(), { (label: UILabel) -> AnyObserver in label.rx_attributedText }, { Variable(nil).asObservable() }) + let variable = Variable(nil) + ensureControlObserverHasWeakReference(UILabel(), { (label: UILabel) -> AnyObserver in label.rx_attributedText }, { variable.asObservable() }) } func testLabel_NextElementsSetsValue() { diff --git a/Tests/RxCocoaTests/Control+RxTests.swift b/Tests/RxCocoaTests/Control+RxTests.swift index 8f6d9d79..b8698ace 100644 --- a/Tests/RxCocoaTests/Control+RxTests.swift +++ b/Tests/RxCocoaTests/Control+RxTests.swift @@ -26,7 +26,7 @@ class ControlTests : RxTest { let property = propertySelector(control) - let disposable = variable.bindTo(property) + let disposable = variable.asObservable().bindTo(property) _ = property.subscribe(onNext: { n in lastReturnedPropertyValue = n diff --git a/Tests/RxSwiftTests/Tests/VariableTest.swift b/Tests/RxSwiftTests/Tests/VariableTest.swift index 67cb722b..e066a467 100644 --- a/Tests/RxSwiftTests/Tests/VariableTest.swift +++ b/Tests/RxSwiftTests/Tests/VariableTest.swift @@ -15,7 +15,7 @@ class VariableTest : RxTest { let a = Variable(1) let b = Variable(2) - let c = Observable.combineLatest(a, b, resultSelector: +) + let c = Observable.combineLatest(a.asObservable(), b.asObservable(), resultSelector: +) var latestValue: Int? @@ -41,6 +41,26 @@ class VariableTest : RxTest { XCTAssertEqual(latestValue!, 14) } + func testVariable_sendsCompletedOnDealloc() { + var a = Variable(1) + + var latest = 0 + var completed = false + a.asObservable().subscribe(onNext: { n in + latest = n + }, onCompleted: { + completed = true + }) + + XCTAssertEqual(latest, 1) + XCTAssertFalse(completed) + + a = Variable(2) + + XCTAssertEqual(latest, 1) + XCTAssertTrue(completed) + } + func testVariable_READMEExample() { // Two simple Rx variables @@ -49,7 +69,7 @@ class VariableTest : RxTest { let b /*: Observable*/ = Variable(2) // Computed third variable (or sequence) - let c /*: Observable*/ = Observable.combineLatest(a, b) { $0 + $1 } + let c /*: Observable*/ = Observable.combineLatest(a.asObservable(), b.asObservable()) { $0 + $1 } // Reading elements from c. // This is just a demo example.