Deprecates UIBindingObserver in favor of Binder.

This commit is contained in:
Krunoslav Zaher 2017-09-17 13:58:02 +02:00
parent 0363de577f
commit a1c2f0dc83
40 changed files with 282 additions and 206 deletions

View File

@ -172,6 +172,7 @@ custom_categories:
- PrimitiveSequence
- name: RxCocoa/Common
children:
- Binder
- ControlTarget
- DelegateProxy
- DelegateProxyType
@ -278,7 +279,6 @@ custom_categories:
- ControlEvent
- ControlProperty
- PublishRelay
- UIBindingObserver
- name: RxCocoa/Traits/Driver
children:
- ControlEvent+Driver

View File

@ -1036,10 +1036,6 @@
C89AB1F71DAAC3350065FBE6 /* SharedSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1B91DAAC3350065FBE6 /* SharedSequence.swift */; };
C89AB1F81DAAC3350065FBE6 /* SharedSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1B91DAAC3350065FBE6 /* SharedSequence.swift */; };
C89AB1F91DAAC3350065FBE6 /* SharedSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1B91DAAC3350065FBE6 /* SharedSequence.swift */; };
C89AB1FE1DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */; };
C89AB1FF1DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */; };
C89AB2001DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */; };
C89AB2011DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */; };
C89AB2021DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1BD1DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift */; };
C89AB2031DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1BD1DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift */; };
C89AB2041DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89AB1BD1DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift */; };
@ -1178,9 +1174,9 @@
C8A81CA11E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81C9F1E05E82C0008DEF4 /* DispatchQueue+Extensions.swift */; };
C8A81CA21E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81C9F1E05E82C0008DEF4 /* DispatchQueue+Extensions.swift */; };
C8A81CA31E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81C9F1E05E82C0008DEF4 /* DispatchQueue+Extensions.swift */; };
C8A81CA61E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81CA51E05EAF70008DEF4 /* UIBindingObserver+Tests.swift */; };
C8A81CA71E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81CA51E05EAF70008DEF4 /* UIBindingObserver+Tests.swift */; };
C8A81CA81E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81CA51E05EAF70008DEF4 /* UIBindingObserver+Tests.swift */; };
C8A81CA61E05EAF70008DEF4 /* Binder+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81CA51E05EAF70008DEF4 /* Binder+Tests.swift */; };
C8A81CA71E05EAF70008DEF4 /* Binder+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81CA51E05EAF70008DEF4 /* Binder+Tests.swift */; };
C8A81CA81E05EAF70008DEF4 /* Binder+Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81CA51E05EAF70008DEF4 /* Binder+Tests.swift */; };
C8A9B6F41DAD752200C9B027 /* Observable+BindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A9B6F31DAD752200C9B027 /* Observable+BindTests.swift */; };
C8A9B6F51DAD752200C9B027 /* Observable+BindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A9B6F31DAD752200C9B027 /* Observable+BindTests.swift */; };
C8A9B6F61DAD752200C9B027 /* Observable+BindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A9B6F31DAD752200C9B027 /* Observable+BindTests.swift */; };
@ -1290,6 +1286,10 @@
C8E390681F379386004FC993 /* Observable+EnumeratedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E390671F379386004FC993 /* Observable+EnumeratedTests.swift */; };
C8E390691F379386004FC993 /* Observable+EnumeratedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E390671F379386004FC993 /* Observable+EnumeratedTests.swift */; };
C8E3906A1F379386004FC993 /* Observable+EnumeratedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E390671F379386004FC993 /* Observable+EnumeratedTests.swift */; };
C8E65EFB1F6E91D1004478C3 /* Binder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E65EFA1F6E91D1004478C3 /* Binder.swift */; };
C8E65EFC1F6E91D1004478C3 /* Binder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E65EFA1F6E91D1004478C3 /* Binder.swift */; };
C8E65EFD1F6E91D1004478C3 /* Binder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E65EFA1F6E91D1004478C3 /* Binder.swift */; };
C8E65EFE1F6E91D1004478C3 /* Binder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E65EFA1F6E91D1004478C3 /* Binder.swift */; };
C8E8BA401E2BBDC800A4AC2C /* (null) in Sources */ = {isa = PBXBuildFile; };
C8E8BA5A1E2C181A00A4AC2C /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8A56AD71AD7424700B4673B /* RxSwift.framework */; };
C8E8BA641E2C186200A4AC2C /* Benchmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E8BA621E2C186200A4AC2C /* Benchmarks.swift */; };
@ -2090,7 +2090,6 @@
C89AB1B71DAAC3350065FBE6 /* SharedSequence+Operators+arity.tt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "SharedSequence+Operators+arity.tt"; sourceTree = "<group>"; };
C89AB1B81DAAC3350065FBE6 /* SharedSequence+Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SharedSequence+Operators.swift"; sourceTree = "<group>"; };
C89AB1B91DAAC3350065FBE6 /* SharedSequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedSequence.swift; sourceTree = "<group>"; };
C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBindingObserver.swift; sourceTree = "<group>"; };
C89AB1BD1DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KVORepresentable+CoreGraphics.swift"; sourceTree = "<group>"; };
C89AB1BE1DAAC3350065FBE6 /* KVORepresentable+Swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KVORepresentable+Swift.swift"; sourceTree = "<group>"; };
C89AB1BF1DAAC3350065FBE6 /* KVORepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVORepresentable.swift; sourceTree = "<group>"; };
@ -2129,7 +2128,7 @@
C8A53AE41F09292A00490535 /* Completable+AndThen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Completable+AndThen.swift"; sourceTree = "<group>"; };
C8A56AD71AD7424700B4673B /* RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C8A81C9F1E05E82C0008DEF4 /* DispatchQueue+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Extensions.swift"; sourceTree = "<group>"; };
C8A81CA51E05EAF70008DEF4 /* UIBindingObserver+Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBindingObserver+Tests.swift"; sourceTree = "<group>"; };
C8A81CA51E05EAF70008DEF4 /* Binder+Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binder+Tests.swift"; sourceTree = "<group>"; };
C8A9B6F31DAD752200C9B027 /* Observable+BindTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+BindTests.swift"; sourceTree = "<group>"; };
C8B0F70C1F530A1700548EBE /* SharingSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingSchedulerTests.swift; sourceTree = "<group>"; };
C8B0F7101F530CA700548EBE /* PublishRelay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublishRelay.swift; sourceTree = "<group>"; };
@ -2171,6 +2170,7 @@
C8D970E21F532FD30058F2FE /* SharedSequence+OperatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SharedSequence+OperatorTest.swift"; sourceTree = "<group>"; };
C8E390621F379041004FC993 /* Enumerated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enumerated.swift; sourceTree = "<group>"; };
C8E390671F379386004FC993 /* Observable+EnumeratedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+EnumeratedTests.swift"; sourceTree = "<group>"; };
C8E65EFA1F6E91D1004478C3 /* Binder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Binder.swift; sourceTree = "<group>"; };
C8E8BA551E2C181A00A4AC2C /* Benchmarks.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Benchmarks.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
C8E8BA621E2C186200A4AC2C /* Benchmarks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Benchmarks.swift; sourceTree = "<group>"; };
C8E8BA631E2C186200A4AC2C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -2586,6 +2586,7 @@
C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */,
C88F76801CE5341700D5A014 /* TextInput.swift */,
C89AB1A51DAAC25A0065FBE6 /* RxCocoaObjCRuntimeError+Extensions.swift */,
C8E65EFA1F6E91D1004478C3 /* Binder.swift */,
);
path = Common;
sourceTree = "<group>";
@ -2721,7 +2722,7 @@
C83508F11C38706D0027C24C /* UIView+RxTests.swift */,
271A97421CFC99FE00D64125 /* UIViewController+RxTests.swift */,
4613456E1D9A4467001ABAF2 /* UIWebView+RxTests.swift */,
C8A81CA51E05EAF70008DEF4 /* UIBindingObserver+Tests.swift */,
C8A81CA51E05EAF70008DEF4 /* Binder+Tests.swift */,
);
path = RxCocoaTests;
sourceTree = "<group>";
@ -3052,7 +3053,6 @@
C89AB1AC1DAAC3350065FBE6 /* ControlProperty.swift */,
C89AB1AD1DAAC3350065FBE6 /* Driver */,
C89AB1B41DAAC3350065FBE6 /* SharedSequence */,
C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */,
);
path = Traits;
sourceTree = "<group>";
@ -4078,6 +4078,7 @@
C882542D1B8A752B00B02D69 /* UIImageView+Rx.swift in Sources */,
A520FFFC1F0D291500573734 /* RxPickerViewDataSourceProxy.swift in Sources */,
C882542A1B8A752B00B02D69 /* UIControl+Rx.swift in Sources */,
C8E65EFB1F6E91D1004478C3 /* Binder.swift in Sources */,
C8D132441C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */,
84E4D3921C9AFD3400ADFDC9 /* UISearchController+Rx.swift in Sources */,
C88254341B8A752B00B02D69 /* UITableView+Rx.swift in Sources */,
@ -4148,7 +4149,6 @@
C882541A1B8A752B00B02D69 /* RxCollectionViewDataSourceType.swift in Sources */,
C8A81CA01E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */,
C88254351B8A752B00B02D69 /* UITextField+Rx.swift in Sources */,
C89AB1FE1DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */,
4613457C1D9A4AEE001ABAF2 /* RxWebViewDelegateProxy.swift in Sources */,
C88254301B8A752B00B02D69 /* UISearchBar+Rx.swift in Sources */,
C89AB2121DAAC3350065FBE6 /* NotificationCenter+Rx.swift in Sources */,
@ -4165,11 +4165,11 @@
C89AB1C71DAAC3350065FBE6 /* ControlEvent.swift in Sources */,
C85E6FC31F5305E400C5681E /* Signal.swift in Sources */,
C89AB1F71DAAC3350065FBE6 /* SharedSequence.swift in Sources */,
C8E65EFC1F6E91D1004478C3 /* Binder.swift in Sources */,
C8B0F7161F530F5A00548EBE /* PublishRelay+Signal.swift in Sources */,
C89AB21B1DAAC3350065FBE6 /* NSObject+Rx+RawRepresentable.swift in Sources */,
C89AB1CF1DAAC3350065FBE6 /* ControlEvent+Driver.swift in Sources */,
C81772991E7F408100EA679B /* Deprecated.swift in Sources */,
C89AB1FF1DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */,
C8093EE41B8A732E0088E94D /* DelegateProxyType.swift in Sources */,
C86781AB1DB823B500B2029A /* NSTextField+Rx.swift in Sources */,
A520FFF81F0D258E00573734 /* RxPickerViewDataSourceType.swift in Sources */,
@ -4344,7 +4344,7 @@
C83509361C38706E0027C24C /* NSLayoutConstraint+RxTests.swift in Sources */,
C898147E1E75AD380035949C /* PrimitiveSequenceTest+zip+arity.swift in Sources */,
C8C4F1631DE9D0A800003FA7 /* UIProgressView+RxTests.swift in Sources */,
C8A81CA61E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */,
C8A81CA61E05EAF70008DEF4 /* Binder+Tests.swift in Sources */,
4613456F1D9A4467001ABAF2 /* UIWebView+RxTests.swift in Sources */,
C820A9BA1EB5097700D431BC /* Observable+TakeTests.swift in Sources */,
C835094C1C38706E0027C24C /* AssumptionsTest.swift in Sources */,
@ -4525,7 +4525,7 @@
C820AA031EB5134000D431BC /* Observable+DelaySubscriptionTests.swift in Sources */,
7EDBAEC31C89BCB9006CBE67 /* UITabBarItem+RxTests.swift in Sources */,
914FCD681CCDB82E0058B304 /* UIPageControl+RxTest.swift in Sources */,
C8A81CA71E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */,
C8A81CA71E05EAF70008DEF4 /* Binder+Tests.swift in Sources */,
C83509DD1C38754C0027C24C /* EquatableArray.swift in Sources */,
C83509F71C38755D0027C24C /* DisposableTest.swift in Sources */,
C8C4F18A1DE9DFA400003FA7 /* UISearchBar+RxTests.swift in Sources */,
@ -4682,7 +4682,7 @@
C834F6C41DB394E100C29244 /* Observable+BlockingTest.swift in Sources */,
C820AA141EB5145200D431BC /* Observable+DelayTests.swift in Sources */,
C8350A1D1C38756B0027C24C /* Observable+SubscriptionTest.swift in Sources */,
C8A81CA81E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */,
C8A81CA81E05EAF70008DEF4 /* Binder+Tests.swift in Sources */,
C820A9C01EB509B500D431BC /* Observable+TakeLastTests.swift in Sources */,
C820A9B41EB507D300D431BC /* Observable+TakeWhileTests.swift in Sources */,
C820A9B01EB5073E00D431BC /* Observable+FilterTests.swift in Sources */,
@ -5300,7 +5300,6 @@
844BC8B61CE4FD7500F5C7CB /* UIPickerView+Rx.swift in Sources */,
C89AB2251DAAC3350065FBE6 /* URLSession+Rx.swift in Sources */,
C89AB22A1DAAC33F0065FBE6 /* RxCocoa.swift in Sources */,
C89AB2011DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */,
54D213931CE08DDB0028D5B4 /* UINavigationItem+Rx.swift in Sources */,
C8F0C01A1BBBFBB9001B112F /* RxCollectionViewDelegateProxy.swift in Sources */,
C89AB1E51DAAC3350065FBE6 /* Variable+Driver.swift in Sources */,
@ -5345,6 +5344,7 @@
C89AB2111DAAC3350065FBE6 /* Logging.swift in Sources */,
C89AB1761DAAC1680065FBE6 /* ControlTarget.swift in Sources */,
D9080AD61EA05DEC002B433B /* UINavigationController+Rx.swift in Sources */,
C8E65EFE1F6E91D1004478C3 /* Binder.swift in Sources */,
C89AB21D1DAAC3350065FBE6 /* NSObject+Rx+RawRepresentable.swift in Sources */,
A5CD038D1F1660F40005A376 /* RxPickerViewAdapter.swift in Sources */,
C89AB2191DAAC3350065FBE6 /* NSObject+Rx+KVORepresentable.swift in Sources */,
@ -5469,7 +5469,6 @@
D203C4FE1BB9C53700D02D00 /* RxTableViewDataSourceProxy.swift in Sources */,
D203C5001BB9C53700D02D00 /* RxTextViewDelegateProxy.swift in Sources */,
844BC8AD1CE4FA6400F5C7CB /* RxPickerViewDelegateProxy.swift in Sources */,
C89AB2001DAAC3350065FBE6 /* UIBindingObserver.swift in Sources */,
C89AB2181DAAC3350065FBE6 /* NSObject+Rx+KVORepresentable.swift in Sources */,
ECBBA59C1DF8C0BA00DDDC2E /* UITabBarController+Rx.swift in Sources */,
D203C5091BB9C53E00D02D00 /* UIImageView+Rx.swift in Sources */,
@ -5479,6 +5478,7 @@
7EDBAEBE1C89B9B7006CBE67 /* UITabBarItem+Rx.swift in Sources */,
84E4D3931C9AFD3500ADFDC9 /* UISearchController+Rx.swift in Sources */,
D203C50F1BB9C53E00D02D00 /* UIStepper+Rx.swift in Sources */,
C8E65EFD1F6E91D1004478C3 /* Binder.swift in Sources */,
C89AB1E41DAAC3350065FBE6 /* Variable+Driver.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
@ -56,6 +57,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -0,0 +1,64 @@
//
// Binder.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 9/17/17.
// Copyright © 2017 Krunoslav Zaher. All rights reserved.
//
import RxSwift
/**
Observer that enforces interface binding rules:
* can't bind errors (in debug builds binding of errors causes `fatalError` in release builds errors are being logged)
* ensures binding is performed on a specific scheduler
`Binder` doesn't retain target and in case target is released, element isn't bound.
By default it binds elements on main scheduler.
*/
public struct Binder<Value>: ObserverType {
public typealias E = Value
private let _binding: (Event<Value>) -> ()
/// Initializes `Binder`
///
/// - parameter target: Target object.
/// - parameter scheduler: Scheduler used to bind the events.
/// - parameter binding: Binding logic.
public init<Target: AnyObject>(_ target: Target, scheduler: ImmediateSchedulerType = MainScheduler(), binding: @escaping (Target, Value) -> ()) {
weak var weakTarget = target
_binding = { event in
switch event {
case .next(let element):
_ = scheduler.schedule(element) { element in
if let target = weakTarget {
binding(target, element)
}
return Disposables.create()
}
case .error(let error):
bindingError(error)
case .completed:
#if DEBUG
print("Source observable sequence has completed and no further element will be bound. This may be something unexpected.")
#endif
break
}
}
}
/// Binds next element to owner view as described in `binding`.
public func on(_ event: Event<Value>) {
_binding(event)
}
/// Erases type of observer.
///
/// - returns: type erased observer.
public func asObserver() -> AnyObserver<Value> {
return AnyObserver(eventHandler: on)
}
}

View File

@ -257,7 +257,7 @@ extension DelegateProxyType
let subscription = self.asObservable()
.observeOn(MainScheduler())
.catchError { error in
bindingErrorToInterface(error)
bindingError(error)
return Observable.empty()
}
// source can never end, otherwise it would release the subscriber, and deallocate the data source
@ -273,7 +273,7 @@ extension DelegateProxyType
switch event {
case .error(let error):
bindingErrorToInterface(error)
bindingError(error)
unregisterDelegate.dispose()
case .completed:
unregisterDelegate.dispose()

View File

@ -21,16 +21,16 @@ import RxSwift
#if os(iOS) || os(macOS) || os(tvOS)
extension Reactive where Base: NSLayoutConstraint {
/// Bindable sink for `constant` property.
public var constant: UIBindingObserver<Base, CGFloat> {
return UIBindingObserver(UIElement: self.base) { constraint, constant in
public var constant: Binder<CGFloat> {
return Binder(self.base) { constraint, constant in
constraint.constant = constant
}
}
/// Bindable sink for `active` property.
@available(iOS 8, OSX 10.10, *)
public var active: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { constraint, value in
public var active: Binder<Bool> {
return Binder(self.base) { constraint, value in
constraint.isActive = value
}
}

View File

@ -273,3 +273,69 @@ extension DelegateProxy {
fatalError()
}
}
/**
Observer that enforces interface binding rules:
* can't bind errors (in debug builds binding of errors causes `fatalError` in release builds errors are being logged)
* ensures binding is performed on main thread
`UIBindingObserver` doesn't retain target interface and in case owned interface element is released, element isn't bound.
In case event binding is attempted from non main dispatch queue, event binding will be dispatched async to main dispatch
queue.
*/
@available(*, deprecated, renamed: "Binder")
public final class UIBindingObserver<UIElementType, Value> : ObserverType where UIElementType: AnyObject {
public typealias E = Value
weak var UIElement: UIElementType?
let binding: (UIElementType, Value) -> Void
/// Initializes `ViewBindingObserver` using
@available(*, deprecated, renamed: "UIBinder.init(_:scheduler:binding:)")
public init(UIElement: UIElementType, binding: @escaping (UIElementType, Value) -> Void) {
self.UIElement = UIElement
self.binding = binding
}
/// Binds next element to owner view as described in `binding`.
public func on(_ event: Event<Value>) {
if !DispatchQueue.isMain {
DispatchQueue.main.async {
self.on(event)
}
return
}
switch event {
case .next(let element):
if let view = self.UIElement {
binding(view, element)
}
case .error(let error):
bindingError(error)
case .completed:
break
}
}
/// Erases type of observer.
///
/// - returns: type erased observer.
public func asObserver() -> AnyObserver<Value> {
return AnyObserver(eventHandler: on)
}
}
#if os(iOS)
extension Reactive where Base: UIRefreshControl {
/// Bindable sink for `beginRefreshing()`, `endRefreshing()` methods.
@available(*, deprecated, renamed: "isRefreshing")
public var refreshing: Binder<Bool> {
return self.isRefreshing
}
}
#endif

View File

@ -64,8 +64,8 @@ extension RxCocoaError {
// MARK: Error binding policies
func bindingErrorToInterface(_ error: Swift.Error) {
let error = "Binding error to UI: \(error)"
func bindingError(_ error: Swift.Error) {
let error = "Binding error: \(error)"
#if DEBUG
rxFatalError(error)
#else

View File

@ -102,7 +102,7 @@ public struct ControlProperty<PropertyType> : ControlPropertyType {
public func on(_ event: Event<E>) {
switch event {
case .error(let error):
bindingErrorToInterface(error)
bindingError(error)
case .next:
_valueSink.on(event)
case .completed:

View File

@ -1,64 +0,0 @@
//
// UIBindingObserver.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 2/7/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import Dispatch
#if !RX_NO_MODULE
import RxSwift
#endif
/**
Observer that enforces interface binding rules:
* can't bind errors (in debug builds binding of errors causes `fatalError` in release builds errors are being logged)
* ensures binding is performed on main thread
`UIBindingObserver` doesn't retain target interface and in case owned interface element is released, element isn't bound.
In case event binding is attempted from non main dispatch queue, event binding will be dispatched async to main dispatch
queue.
*/
public final class UIBindingObserver<UIElementType, Value> : ObserverType where UIElementType: AnyObject {
public typealias E = Value
weak var UIElement: UIElementType?
let binding: (UIElementType, Value) -> Void
/// Initializes `ViewBindingObserver` using
public init(UIElement: UIElementType, binding: @escaping (UIElementType, Value) -> Void) {
self.UIElement = UIElement
self.binding = binding
}
/// Binds next element to owner view as described in `binding`.
public func on(_ event: Event<Value>) {
if !DispatchQueue.isMain {
DispatchQueue.main.async {
self.on(event)
}
return
}
switch event {
case .next(let element):
if let view = self.UIElement {
binding(view, element)
}
case .error(let error):
bindingErrorToInterface(error)
case .completed:
break
}
}
/// Erases type of observer.
///
/// - returns: type erased observer.
public func asObserver() -> AnyObserver<Value> {
return AnyObserver(eventHandler: on)
}
}

View File

@ -50,7 +50,7 @@ class RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S: Sequence>
}
func collectionView(_ collectionView: UICollectionView, observedEvent: Event<S>) {
UIBindingObserver(UIElement: self) { collectionViewDataSource, sectionModels in
Binder(self) { collectionViewDataSource, sectionModels in
let sections = Array(sectionModels)
collectionViewDataSource.collectionView(collectionView, observedElements: sections)
}.on(observedEvent)

View File

@ -37,7 +37,7 @@ class RxPickerViewSequenceDataSource<S: Sequence>
typealias Element = S
func pickerView(_ pickerView: UIPickerView, observedEvent: Event<S>) {
UIBindingObserver(UIElement: self) { dataSource, items in
Binder(self) { dataSource, items in
dataSource.items = items
pickerView.reloadAllComponents()
}

View File

@ -50,7 +50,7 @@ class RxTableViewReactiveArrayDataSourceSequenceWrapper<S: Sequence>
}
func tableView(_ tableView: UITableView, observedEvent: Event<S>) {
UIBindingObserver(UIElement: self) { tableViewDataSource, sectionModels in
Binder(self) { tableViewDataSource, sectionModels in
let sections = Array(sectionModels)
tableViewDataSource.tableView(tableView, observedElements: sections)
}.on(observedEvent)

View File

@ -16,8 +16,8 @@ import RxSwift
extension Reactive where Base: UIActivityIndicatorView {
/// Bindable sink for `startAnimating()`, `stopAnimating()` methods.
public var isAnimating: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { activityIndicator, active in
public var isAnimating: Binder<Bool> {
return Binder(self.base) { activityIndicator, active in
if active {
activityIndicator.startAnimating()
} else {

View File

@ -17,8 +17,8 @@ import RxSwift
extension Reactive where Base: UIAlertAction {
/// Bindable sink for `enabled` property.
public var isEnabled: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { alertAction, value in
public var isEnabled: Binder<Bool> {
return Binder(self.base) { alertAction, value in
alertAction.isEnabled = value
}
}

View File

@ -16,8 +16,8 @@
extension Reactive where Base: UIApplication {
/// Bindable sink for `networkActivityIndicatorVisible`.
public var isNetworkActivityIndicatorVisible: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { application, active in
public var isNetworkActivityIndicatorVisible: Binder<Bool> {
return Binder(self.base) { application, active in
application.isNetworkActivityIndicatorVisible = active
}
}

View File

@ -18,21 +18,21 @@ fileprivate var rx_tap_key: UInt8 = 0
extension Reactive where Base: UIBarButtonItem {
/// Bindable sink for `enabled` property.
public var isEnabled: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { UIElement, value in
UIElement.isEnabled = value
public var isEnabled: Binder<Bool> {
return Binder(self.base) { element, value in
element.isEnabled = value
}
}
/// Bindable sink for `title` property.
public var title: UIBindingObserver<Base, String> {
return UIBindingObserver(UIElement: self.base) { UIElement, value in
UIElement.title = value
public var title: Binder<String> {
return Binder(self.base) { element, value in
element.title = value
}
}
/// Reactive wrapper for target action pattern on `self`.
public var tap: ControlEvent<Void> {
public var tap: ControlEvent<()> {
let source = lazyInstanceObservable(&rx_tap_key) { () -> Observable<Void> in
Observable.create { [weak control = self.base] observer in
guard let control = control else {

View File

@ -51,22 +51,22 @@ extension Reactive where Base: UIButton {
extension Reactive where Base: UIButton {
/// Reactive wrapper for `setTitle(_:for:)`
public func title(for controlState: UIControlState = []) -> UIBindingObserver<Base, String?> {
return UIBindingObserver<Base, String?>(UIElement: self.base) { (button, title) -> () in
public func title(for controlState: UIControlState = []) -> Binder<String?> {
return Binder(self.base) { (button, title) -> () in
button.setTitle(title, for: controlState)
}
}
/// Reactive wrapper for `setImage(_:for:)`
public func image(for controlState: UIControlState = []) -> UIBindingObserver<Base, UIImage?> {
return UIBindingObserver<Base, UIImage?>(UIElement: self.base) { (button, image) -> () in
public func image(for controlState: UIControlState = []) -> Binder<UIImage?> {
return Binder(self.base) { (button, image) -> () in
button.setImage(image, for: controlState)
}
}
/// Reactive wrapper for `setBackgroundImage(_:for:)`
public func backgroundImage(for controlState: UIControlState = []) -> UIBindingObserver<Base, UIImage?> {
return UIBindingObserver<Base, UIImage?>(UIElement: self.base) { (button, image) -> () in
public func backgroundImage(for controlState: UIControlState = []) -> Binder<UIImage?> {
return Binder(self.base) { (button, image) -> () in
button.setBackgroundImage(image, for: controlState)
}
}
@ -84,8 +84,8 @@ extension Reactive where Base: UIButton {
extension Reactive where Base: UIButton {
/// Reactive wrapper for `setAttributedTitle(_:controlState:)`
public func attributedTitle(for controlState: UIControlState = []) -> UIBindingObserver<Base, NSAttributedString?> {
return UIBindingObserver<Base, NSAttributedString?>(UIElement: self.base) { (button, attributedTitle) -> () in
public func attributedTitle(for controlState: UIControlState = []) -> Binder<NSAttributedString?> {
return Binder(self.base) { (button, attributedTitle) -> () in
button.setAttributedTitle(attributedTitle, for: controlState)
}
}

View File

@ -16,15 +16,15 @@ import UIKit
extension Reactive where Base: UIControl {
/// Bindable sink for `enabled` property.
public var isEnabled: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { control, value in
public var isEnabled: Binder<Bool> {
return Binder(self.base) { control, value in
control.isEnabled = value
}
}
/// Bindable sink for `selected` property.
public var isSelected: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { control, selected in
public var isSelected: Binder<Bool> {
return Binder(self.base) { control, selected in
control.isSelected = selected
}
}
@ -95,7 +95,7 @@ extension Reactive where Base: UIControl {
}
.takeUntil((control as NSObject).rx.deallocated)
let bindingObserver = UIBindingObserver(UIElement: control, binding: setter)
let bindingObserver = Binder(control, binding: setter)
return ControlProperty<T>(values: source, valueSink: bindingObserver)
}

View File

@ -16,15 +16,15 @@ import UIKit
extension Reactive where Base: UIImageView {
/// Bindable sink for `image` property.
public var image: UIBindingObserver<Base, UIImage?> {
public var image: Binder<UIImage?> {
return image(transitionType: nil)
}
/// Bindable sink for `image` property.
/// - parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...)
public func image(transitionType: String? = nil) -> UIBindingObserver<Base, UIImage?> {
return UIBindingObserver(UIElement: base) { imageView, image in
public func image(transitionType: String? = nil) -> Binder<UIImage?> {
return Binder(base) { imageView, image in
if let transitionType = transitionType {
if image != nil {
let transition = CATransition()

View File

@ -16,15 +16,15 @@ import UIKit
extension Reactive where Base: UILabel {
/// Bindable sink for `text` property.
public var text: UIBindingObserver<Base, String?> {
return UIBindingObserver(UIElement: self.base) { label, text in
public var text: Binder<String?> {
return Binder(self.base) { label, text in
label.text = text
}
}
/// Bindable sink for `attributedText` property.
public var attributedText: UIBindingObserver<Base, NSAttributedString?> {
return UIBindingObserver(UIElement: self.base) { label, text in
public var attributedText: Binder<NSAttributedString?> {
return Binder(self.base) { label, text in
label.attributedText = text
}
}

View File

@ -16,8 +16,8 @@ import RxSwift
extension Reactive where Base: UINavigationItem {
/// Bindable sink for `title` property.
public var title: UIBindingObserver<Base, String?> {
return UIBindingObserver(UIElement: self.base) { navigationItem, text in
public var title: Binder<String?> {
return Binder(self.base) { navigationItem, text in
navigationItem.title = text
}
}

View File

@ -16,15 +16,15 @@ import UIKit
extension Reactive where Base: UIPageControl {
/// Bindable sink for `currentPage` property.
public var currentPage: UIBindingObserver<Base, Int> {
return UIBindingObserver(UIElement: self.base) { controller, page in
public var currentPage: Binder<Int> {
return Binder(self.base) { controller, page in
controller.currentPage = page
}
}
/// Bindable sink for `numberOfPages` property.
public var numberOfPages: UIBindingObserver<Base, Int> {
return UIBindingObserver(UIElement: self.base) { controller, page in
public var numberOfPages: Binder<Int> {
return Binder(self.base) { controller, page in
controller.numberOfPages = page
}
}

View File

@ -16,8 +16,8 @@ import UIKit
extension Reactive where Base: UIProgressView {
/// Bindable sink for `progress` property
public var progress: UIBindingObserver<Base, Float> {
return UIBindingObserver(UIElement: self.base) { progressView, progress in
public var progress: Binder<Float> {
return Binder(self.base) { progressView, progress in
progressView.progress = progress
}
}

View File

@ -14,16 +14,9 @@ import RxSwift
#endif
extension Reactive where Base: UIRefreshControl {
/// Bindable sink for `beginRefreshing()`, `endRefreshing()` methods.
@available(*, deprecated, renamed: "isRefreshing")
public var refreshing: UIBindingObserver<Base, Bool> {
return self.isRefreshing
}
/// Bindable sink for `beginRefreshing()`, `endRefreshing()` methods.
public var isRefreshing: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { refreshControl, refresh in
public var isRefreshing: Binder<Bool> {
return Binder(self.base) { refreshControl, refresh in
if refresh {
refreshControl.beginRefreshing()
} else {

View File

@ -29,7 +29,7 @@
public var contentOffset: ControlProperty<CGPoint> {
let proxy = RxScrollViewDelegateProxy.proxy(for: base)
let bindingObserver = UIBindingObserver(UIElement: self.base) { scrollView, contentOffset in
let bindingObserver = Binder(self.base) { scrollView, contentOffset in
scrollView.contentOffset = contentOffset
}
@ -37,8 +37,8 @@
}
/// Bindable sink for `scrollEnabled` property.
public var isScrollEnabled: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { scrollView, scrollEnabled in
public var isScrollEnabled: Binder<Bool> {
return Binder(self.base) { scrollView, scrollEnabled in
scrollView.isScrollEnabled = scrollEnabled
}
}

View File

@ -39,7 +39,7 @@ extension Reactive where Base: UISearchBar {
.startWith(text)
}
let bindingObserver = UIBindingObserver(UIElement: self.base) { (searchBar, text: String?) in
let bindingObserver = Binder(self.base) { (searchBar, text: String?) in
searchBar.text = text
}
@ -58,7 +58,7 @@ extension Reactive where Base: UISearchBar {
.startWith(index)
}
let bindingObserver = UIBindingObserver(UIElement: self.base) { (searchBar, index: Int) in
let bindingObserver = Binder(self.base) { (searchBar, index: Int) in
searchBar.selectedScopeButtonIndex = index
}

View File

@ -16,8 +16,8 @@
extension Reactive where Base: UITabBarItem {
/// Bindable sink for `badgeValue` property.
public var badgeValue: UIBindingObserver<Base, String?> {
return UIBindingObserver(UIElement: self.base) { tabBarItem, badgeValue in
public var badgeValue: Binder<String?> {
return Binder(self.base) { tabBarItem, badgeValue in
tabBarItem.badgeValue = badgeValue
}
}

View File

@ -42,7 +42,7 @@ extension Reactive where Base: UITextView {
.startWith(text)
}
let bindingObserver = UIBindingObserver(UIElement: self.base) { (textView, text: String?) in
let bindingObserver = Binder(self.base) { (textView, text: String?) in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input
// when IME input method is used.

View File

@ -15,22 +15,22 @@ import RxSwift
extension Reactive where Base: UIView {
/// Bindable sink for `hidden` property.
public var isHidden: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { view, hidden in
public var isHidden: Binder<Bool> {
return Binder(self.base) { view, hidden in
view.isHidden = hidden
}
}
/// Bindable sink for `alpha` property.
public var alpha: UIBindingObserver<Base, CGFloat> {
return UIBindingObserver(UIElement: self.base) { view, alpha in
public var alpha: Binder<CGFloat> {
return Binder(self.base) { view, alpha in
view.alpha = alpha
}
}
/// Bindable sink for `isUserInteractionEnabled` property.
public var isUserInteractionEnabled: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { view, userInteractionEnabled in
public var isUserInteractionEnabled: Binder<Bool> {
return Binder(self.base) { view, userInteractionEnabled in
view.isUserInteractionEnabled = userInteractionEnabled
}
}

View File

@ -16,8 +16,8 @@
extension Reactive where Base: UIViewController {
/// Bindable sink for `title`.
public var title: UIBindingObserver<Base, String> {
return UIBindingObserver(UIElement: self.base) { viewController, title in
public var title: Binder<String> {
return Binder(self.base) { viewController, title in
viewController.title = title
}
}

View File

@ -74,14 +74,14 @@ extension Reactive where Base: NSControl {
return Observable.just(getter(control))
}
let bindingObserver = UIBindingObserver(UIElement: control, binding: setter)
let bindingObserver = Binder(control, binding: setter)
return ControlProperty(values: source, valueSink: bindingObserver)
}
/// Bindable sink for `enabled` property.
public var isEnabled: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { (owner, value) in
public var isEnabled: Binder<Bool> {
return Binder(self.base) { (owner, value) in
owner.isEnabled = value
}
}

View File

@ -16,15 +16,15 @@ import Cocoa
extension Reactive where Base: NSImageView {
/// Bindable sink for `image` property.
public var image: UIBindingObserver<Base, NSImage?> {
public var image: Binder<NSImage?> {
return image(transitionType: nil)
}
/// Bindable sink for `image` property.
///
/// - parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...)
public func image(transitionType: String? = nil) -> UIBindingObserver<Base, NSImage?> {
return UIBindingObserver(UIElement: self.base) { control, value in
public func image(transitionType: String? = nil) -> Binder<NSImage?> {
return Binder(self.base) { control, value in
if let transitionType = transitionType {
if value != nil {
let transition = CATransition()

View File

@ -79,7 +79,7 @@ extension Reactive where Base: NSTextField {
delegate.textSubject.startWith(textField?.stringValue)
}.takeUntil(deallocated)
let observer = UIBindingObserver(UIElement: base) { (control, value: String?) in
let observer = Binder(base) { (control, value: String?) in
control.stringValue = value ?? ""
}

View File

@ -15,15 +15,15 @@
extension Reactive where Base: NSView {
/// Bindable sink for `hidden` property.
public var isHidden: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { view, value in
public var isHidden: Binder<Bool> {
return Binder(self.base) { view, value in
view.isHidden = value
}
}
/// Bindable sink for `alphaValue` property.
public var alpha: UIBindingObserver<Base, CGFloat> {
return UIBindingObserver(UIElement: self.base) { view, value in
public var alpha: Binder<CGFloat> {
return Binder(self.base) { view, value in
view.alphaValue = value
}
}

View File

@ -0,0 +1 @@
../../RxCocoa/Common/Binder.swift

View File

@ -1 +0,0 @@
../../RxCocoa/Traits/UIBindingObserver.swift

View File

@ -0,0 +1,48 @@
//
// Binder+Tests.swift
// Tests
//
// Created by Krunoslav Zaher on 12/17/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import RxCocoa
import XCTest
import RxSwift
final class BinderTests: RxTest {
}
extension BinderTests {
func testBindingOnNonMainQueueDispatchesToMainQueue() {
let waitForElement = self.expectation(description: "wait until element arrives")
let target = NSObject()
let bindingObserver = Binder(target) { (_, element: Int) in
MainScheduler.ensureExecutingOnScheduler()
waitForElement.fulfill()
}
DispatchQueue.global(qos: .default).async {
bindingObserver.on(.next(1))
}
self.waitForExpectations(timeout: 1.0) { (e) in
XCTAssertNil(e)
}
}
func testBindingOnMainQueueDispatchesToNonMainQueue() {
let waitForElement = self.expectation(description: "wait until element arrives")
let target = NSObject()
let bindingObserver = Binder(target, scheduler: ConcurrentDispatchQueueScheduler(qos: .default)) { (_, element: Int) in
XCTAssert(!DispatchQueue.isMain)
waitForElement.fulfill()
}
bindingObserver.on(.next(1))
self.waitForExpectations(timeout: 1.0) { (e) in
XCTAssertNil(e)
}
}
}

View File

@ -1,33 +0,0 @@
//
// UIBindingObserver+Tests.swift
// Tests
//
// Created by Krunoslav Zaher on 12/17/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import RxCocoa
import XCTest
import RxSwift
final class UIBindingObserverTests: RxTest {
}
extension UIBindingObserverTests {
func testBindingOnNonMainQueueDispatchesToMainQueue() {
let waitForElement = self.expectation(description: "wait until element arrives")
let target = NSObject()
let bindingObserver = UIBindingObserver<NSObject, Int>(UIElement: target) { (_, element: Int) in
MainScheduler.ensureExecutingOnScheduler()
waitForElement.fulfill()
}
DispatchQueue.global(qos: .default).async {
bindingObserver.on(.next(1))
}
self.waitForExpectations(timeout: 1.0) { (e) in
XCTAssertNil(e)
}
}
}

Binary file not shown.