diff --git a/.jazzy.yml b/.jazzy.yml index 0051442f..20e9b32f 100644 --- a/.jazzy.yml +++ b/.jazzy.yml @@ -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 diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj index dbc52ecc..c252706d 100644 --- a/Rx.xcodeproj/project.pbxproj +++ b/Rx.xcodeproj/project.pbxproj @@ -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 = ""; }; C89AB1B81DAAC3350065FBE6 /* SharedSequence+Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SharedSequence+Operators.swift"; sourceTree = ""; }; C89AB1B91DAAC3350065FBE6 /* SharedSequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedSequence.swift; sourceTree = ""; }; - C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBindingObserver.swift; sourceTree = ""; }; C89AB1BD1DAAC3350065FBE6 /* KVORepresentable+CoreGraphics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KVORepresentable+CoreGraphics.swift"; sourceTree = ""; }; C89AB1BE1DAAC3350065FBE6 /* KVORepresentable+Swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KVORepresentable+Swift.swift"; sourceTree = ""; }; C89AB1BF1DAAC3350065FBE6 /* KVORepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVORepresentable.swift; sourceTree = ""; }; @@ -2129,7 +2128,7 @@ C8A53AE41F09292A00490535 /* Completable+AndThen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Completable+AndThen.swift"; sourceTree = ""; }; 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 = ""; }; - C8A81CA51E05EAF70008DEF4 /* UIBindingObserver+Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBindingObserver+Tests.swift"; sourceTree = ""; }; + C8A81CA51E05EAF70008DEF4 /* Binder+Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binder+Tests.swift"; sourceTree = ""; }; C8A9B6F31DAD752200C9B027 /* Observable+BindTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+BindTests.swift"; sourceTree = ""; }; C8B0F70C1F530A1700548EBE /* SharingSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingSchedulerTests.swift; sourceTree = ""; }; C8B0F7101F530CA700548EBE /* PublishRelay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublishRelay.swift; sourceTree = ""; }; @@ -2171,6 +2170,7 @@ C8D970E21F532FD30058F2FE /* SharedSequence+OperatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SharedSequence+OperatorTest.swift"; sourceTree = ""; }; C8E390621F379041004FC993 /* Enumerated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enumerated.swift; sourceTree = ""; }; C8E390671F379386004FC993 /* Observable+EnumeratedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+EnumeratedTests.swift"; sourceTree = ""; }; + C8E65EFA1F6E91D1004478C3 /* Binder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Binder.swift; sourceTree = ""; }; 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 = ""; }; C8E8BA631E2C186200A4AC2C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -2586,6 +2586,7 @@ C8D132431C42D15E00B59FFF /* SectionedViewDataSourceType.swift */, C88F76801CE5341700D5A014 /* TextInput.swift */, C89AB1A51DAAC25A0065FBE6 /* RxCocoaObjCRuntimeError+Extensions.swift */, + C8E65EFA1F6E91D1004478C3 /* Binder.swift */, ); path = Common; sourceTree = ""; @@ -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 = ""; @@ -3052,7 +3053,6 @@ C89AB1AC1DAAC3350065FBE6 /* ControlProperty.swift */, C89AB1AD1DAAC3350065FBE6 /* Driver */, C89AB1B41DAAC3350065FBE6 /* SharedSequence */, - C89AB1BB1DAAC3350065FBE6 /* UIBindingObserver.swift */, ); path = Traits; sourceTree = ""; @@ -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; diff --git a/Rx.xcodeproj/xcshareddata/xcschemes/RxSwift-macOS.xcscheme b/Rx.xcodeproj/xcshareddata/xcschemes/RxSwift-macOS.xcscheme index c1f87ea5..ae318924 100644 --- a/Rx.xcodeproj/xcshareddata/xcschemes/RxSwift-macOS.xcscheme +++ b/Rx.xcodeproj/xcshareddata/xcschemes/RxSwift-macOS.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES" codeCoverageEnabled = "YES"> @@ -56,6 +57,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/RxCocoa/Common/Binder.swift b/RxCocoa/Common/Binder.swift new file mode 100644 index 00000000..f5db211b --- /dev/null +++ b/RxCocoa/Common/Binder.swift @@ -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: ObserverType { + public typealias E = Value + + private let _binding: (Event) -> () + + /// Initializes `Binder` + /// + /// - parameter target: Target object. + /// - parameter scheduler: Scheduler used to bind the events. + /// - parameter binding: Binding logic. + public init(_ 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) { + _binding(event) + } + + /// Erases type of observer. + /// + /// - returns: type erased observer. + public func asObserver() -> AnyObserver { + return AnyObserver(eventHandler: on) + } +} diff --git a/RxCocoa/Common/DelegateProxyType.swift b/RxCocoa/Common/DelegateProxyType.swift index d32559d9..8f59ade3 100644 --- a/RxCocoa/Common/DelegateProxyType.swift +++ b/RxCocoa/Common/DelegateProxyType.swift @@ -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() diff --git a/RxCocoa/Common/NSLayoutConstraint+Rx.swift b/RxCocoa/Common/NSLayoutConstraint+Rx.swift index 8dfa956e..da10474b 100644 --- a/RxCocoa/Common/NSLayoutConstraint+Rx.swift +++ b/RxCocoa/Common/NSLayoutConstraint+Rx.swift @@ -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 { - return UIBindingObserver(UIElement: self.base) { constraint, constant in + public var constant: Binder { + 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 { - return UIBindingObserver(UIElement: self.base) { constraint, value in + public var active: Binder { + return Binder(self.base) { constraint, value in constraint.isActive = value } } diff --git a/RxCocoa/Deprecated.swift b/RxCocoa/Deprecated.swift index 2e2940b9..213ad2eb 100644 --- a/RxCocoa/Deprecated.swift +++ b/RxCocoa/Deprecated.swift @@ -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 : 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) { + 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 { + 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 { + return self.isRefreshing + } + } +#endif diff --git a/RxCocoa/RxCocoa.swift b/RxCocoa/RxCocoa.swift index 95e6dfa2..ffdc26ea 100644 --- a/RxCocoa/RxCocoa.swift +++ b/RxCocoa/RxCocoa.swift @@ -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 diff --git a/RxCocoa/Traits/ControlProperty.swift b/RxCocoa/Traits/ControlProperty.swift index 2449e174..a42f552f 100644 --- a/RxCocoa/Traits/ControlProperty.swift +++ b/RxCocoa/Traits/ControlProperty.swift @@ -102,7 +102,7 @@ public struct ControlProperty : ControlPropertyType { public func on(_ event: Event) { switch event { case .error(let error): - bindingErrorToInterface(error) + bindingError(error) case .next: _valueSink.on(event) case .completed: diff --git a/RxCocoa/Traits/UIBindingObserver.swift b/RxCocoa/Traits/UIBindingObserver.swift deleted file mode 100644 index cc711177..00000000 --- a/RxCocoa/Traits/UIBindingObserver.swift +++ /dev/null @@ -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 : 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) { - 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 { - return AnyObserver(eventHandler: on) - } -} diff --git a/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift b/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift index b67a94e1..366f8e1b 100644 --- a/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift +++ b/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift @@ -50,7 +50,7 @@ class RxCollectionViewReactiveArrayDataSourceSequenceWrapper } func collectionView(_ collectionView: UICollectionView, observedEvent: Event) { - UIBindingObserver(UIElement: self) { collectionViewDataSource, sectionModels in + Binder(self) { collectionViewDataSource, sectionModels in let sections = Array(sectionModels) collectionViewDataSource.collectionView(collectionView, observedElements: sections) }.on(observedEvent) diff --git a/RxCocoa/iOS/DataSources/RxPickerViewAdapter.swift b/RxCocoa/iOS/DataSources/RxPickerViewAdapter.swift index 97f2cdfe..39085288 100644 --- a/RxCocoa/iOS/DataSources/RxPickerViewAdapter.swift +++ b/RxCocoa/iOS/DataSources/RxPickerViewAdapter.swift @@ -37,7 +37,7 @@ class RxPickerViewSequenceDataSource typealias Element = S func pickerView(_ pickerView: UIPickerView, observedEvent: Event) { - UIBindingObserver(UIElement: self) { dataSource, items in + Binder(self) { dataSource, items in dataSource.items = items pickerView.reloadAllComponents() } diff --git a/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift b/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift index 57928287..69fb2d27 100644 --- a/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift +++ b/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift @@ -50,7 +50,7 @@ class RxTableViewReactiveArrayDataSourceSequenceWrapper } func tableView(_ tableView: UITableView, observedEvent: Event) { - UIBindingObserver(UIElement: self) { tableViewDataSource, sectionModels in + Binder(self) { tableViewDataSource, sectionModels in let sections = Array(sectionModels) tableViewDataSource.tableView(tableView, observedElements: sections) }.on(observedEvent) diff --git a/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift b/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift index 03f01685..7b11d282 100644 --- a/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift +++ b/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift @@ -16,8 +16,8 @@ import RxSwift extension Reactive where Base: UIActivityIndicatorView { /// Bindable sink for `startAnimating()`, `stopAnimating()` methods. - public var isAnimating: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { activityIndicator, active in + public var isAnimating: Binder { + return Binder(self.base) { activityIndicator, active in if active { activityIndicator.startAnimating() } else { diff --git a/RxCocoa/iOS/UIAlertAction+Rx.swift b/RxCocoa/iOS/UIAlertAction+Rx.swift index 23ffd26b..d622f84d 100644 --- a/RxCocoa/iOS/UIAlertAction+Rx.swift +++ b/RxCocoa/iOS/UIAlertAction+Rx.swift @@ -17,8 +17,8 @@ import RxSwift extension Reactive where Base: UIAlertAction { /// Bindable sink for `enabled` property. - public var isEnabled: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { alertAction, value in + public var isEnabled: Binder { + return Binder(self.base) { alertAction, value in alertAction.isEnabled = value } } diff --git a/RxCocoa/iOS/UIApplication+Rx.swift b/RxCocoa/iOS/UIApplication+Rx.swift index 9c643894..24d9b29c 100644 --- a/RxCocoa/iOS/UIApplication+Rx.swift +++ b/RxCocoa/iOS/UIApplication+Rx.swift @@ -16,8 +16,8 @@ extension Reactive where Base: UIApplication { /// Bindable sink for `networkActivityIndicatorVisible`. - public var isNetworkActivityIndicatorVisible: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { application, active in + public var isNetworkActivityIndicatorVisible: Binder { + return Binder(self.base) { application, active in application.isNetworkActivityIndicatorVisible = active } } diff --git a/RxCocoa/iOS/UIBarButtonItem+Rx.swift b/RxCocoa/iOS/UIBarButtonItem+Rx.swift index 18beee7e..c2c1783b 100644 --- a/RxCocoa/iOS/UIBarButtonItem+Rx.swift +++ b/RxCocoa/iOS/UIBarButtonItem+Rx.swift @@ -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 { - return UIBindingObserver(UIElement: self.base) { UIElement, value in - UIElement.isEnabled = value + public var isEnabled: Binder { + return Binder(self.base) { element, value in + element.isEnabled = value } } /// Bindable sink for `title` property. - public var title: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { UIElement, value in - UIElement.title = value + public var title: Binder { + return Binder(self.base) { element, value in + element.title = value } } /// Reactive wrapper for target action pattern on `self`. - public var tap: ControlEvent { + public var tap: ControlEvent<()> { let source = lazyInstanceObservable(&rx_tap_key) { () -> Observable in Observable.create { [weak control = self.base] observer in guard let control = control else { diff --git a/RxCocoa/iOS/UIButton+Rx.swift b/RxCocoa/iOS/UIButton+Rx.swift index e465d400..a2a7187c 100644 --- a/RxCocoa/iOS/UIButton+Rx.swift +++ b/RxCocoa/iOS/UIButton+Rx.swift @@ -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 { - return UIBindingObserver(UIElement: self.base) { (button, title) -> () in + public func title(for controlState: UIControlState = []) -> Binder { + return Binder(self.base) { (button, title) -> () in button.setTitle(title, for: controlState) } } /// Reactive wrapper for `setImage(_:for:)` - public func image(for controlState: UIControlState = []) -> UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { (button, image) -> () in + public func image(for controlState: UIControlState = []) -> Binder { + return Binder(self.base) { (button, image) -> () in button.setImage(image, for: controlState) } } /// Reactive wrapper for `setBackgroundImage(_:for:)` - public func backgroundImage(for controlState: UIControlState = []) -> UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { (button, image) -> () in + public func backgroundImage(for controlState: UIControlState = []) -> Binder { + 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 { - return UIBindingObserver(UIElement: self.base) { (button, attributedTitle) -> () in + public func attributedTitle(for controlState: UIControlState = []) -> Binder { + return Binder(self.base) { (button, attributedTitle) -> () in button.setAttributedTitle(attributedTitle, for: controlState) } } diff --git a/RxCocoa/iOS/UIControl+Rx.swift b/RxCocoa/iOS/UIControl+Rx.swift index 90ea45d0..e5bced14 100644 --- a/RxCocoa/iOS/UIControl+Rx.swift +++ b/RxCocoa/iOS/UIControl+Rx.swift @@ -16,15 +16,15 @@ import UIKit extension Reactive where Base: UIControl { /// Bindable sink for `enabled` property. - public var isEnabled: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { control, value in + public var isEnabled: Binder { + return Binder(self.base) { control, value in control.isEnabled = value } } /// Bindable sink for `selected` property. - public var isSelected: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { control, selected in + public var isSelected: Binder { + 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(values: source, valueSink: bindingObserver) } diff --git a/RxCocoa/iOS/UIImageView+Rx.swift b/RxCocoa/iOS/UIImageView+Rx.swift index e9f26abd..107ded70 100644 --- a/RxCocoa/iOS/UIImageView+Rx.swift +++ b/RxCocoa/iOS/UIImageView+Rx.swift @@ -16,15 +16,15 @@ import UIKit extension Reactive where Base: UIImageView { /// Bindable sink for `image` property. - public var image: UIBindingObserver { + public var image: Binder { 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 { - return UIBindingObserver(UIElement: base) { imageView, image in + public func image(transitionType: String? = nil) -> Binder { + return Binder(base) { imageView, image in if let transitionType = transitionType { if image != nil { let transition = CATransition() diff --git a/RxCocoa/iOS/UILabel+Rx.swift b/RxCocoa/iOS/UILabel+Rx.swift index 879f4646..fedc73c6 100644 --- a/RxCocoa/iOS/UILabel+Rx.swift +++ b/RxCocoa/iOS/UILabel+Rx.swift @@ -16,15 +16,15 @@ import UIKit extension Reactive where Base: UILabel { /// Bindable sink for `text` property. - public var text: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { label, text in + public var text: Binder { + return Binder(self.base) { label, text in label.text = text } } /// Bindable sink for `attributedText` property. - public var attributedText: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { label, text in + public var attributedText: Binder { + return Binder(self.base) { label, text in label.attributedText = text } } diff --git a/RxCocoa/iOS/UINavigationItem+Rx.swift b/RxCocoa/iOS/UINavigationItem+Rx.swift index f18342bf..1c9e0a72 100644 --- a/RxCocoa/iOS/UINavigationItem+Rx.swift +++ b/RxCocoa/iOS/UINavigationItem+Rx.swift @@ -16,8 +16,8 @@ import RxSwift extension Reactive where Base: UINavigationItem { /// Bindable sink for `title` property. - public var title: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { navigationItem, text in + public var title: Binder { + return Binder(self.base) { navigationItem, text in navigationItem.title = text } } diff --git a/RxCocoa/iOS/UIPageControl+Rx.swift b/RxCocoa/iOS/UIPageControl+Rx.swift index ad7f496c..88713d67 100644 --- a/RxCocoa/iOS/UIPageControl+Rx.swift +++ b/RxCocoa/iOS/UIPageControl+Rx.swift @@ -16,15 +16,15 @@ import UIKit extension Reactive where Base: UIPageControl { /// Bindable sink for `currentPage` property. - public var currentPage: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { controller, page in + public var currentPage: Binder { + return Binder(self.base) { controller, page in controller.currentPage = page } } /// Bindable sink for `numberOfPages` property. - public var numberOfPages: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { controller, page in + public var numberOfPages: Binder { + return Binder(self.base) { controller, page in controller.numberOfPages = page } } diff --git a/RxCocoa/iOS/UIProgressView+Rx.swift b/RxCocoa/iOS/UIProgressView+Rx.swift index a2ecd3c6..e683a365 100644 --- a/RxCocoa/iOS/UIProgressView+Rx.swift +++ b/RxCocoa/iOS/UIProgressView+Rx.swift @@ -16,8 +16,8 @@ import UIKit extension Reactive where Base: UIProgressView { /// Bindable sink for `progress` property - public var progress: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { progressView, progress in + public var progress: Binder { + return Binder(self.base) { progressView, progress in progressView.progress = progress } } diff --git a/RxCocoa/iOS/UIRefreshControl+Rx.swift b/RxCocoa/iOS/UIRefreshControl+Rx.swift index 9a38cb93..935a3b57 100644 --- a/RxCocoa/iOS/UIRefreshControl+Rx.swift +++ b/RxCocoa/iOS/UIRefreshControl+Rx.swift @@ -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 { - return self.isRefreshing - } - - /// Bindable sink for `beginRefreshing()`, `endRefreshing()` methods. - public var isRefreshing: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { refreshControl, refresh in + public var isRefreshing: Binder { + return Binder(self.base) { refreshControl, refresh in if refresh { refreshControl.beginRefreshing() } else { diff --git a/RxCocoa/iOS/UIScrollView+Rx.swift b/RxCocoa/iOS/UIScrollView+Rx.swift index a44a01a0..83ba7301 100644 --- a/RxCocoa/iOS/UIScrollView+Rx.swift +++ b/RxCocoa/iOS/UIScrollView+Rx.swift @@ -29,7 +29,7 @@ public var contentOffset: ControlProperty { 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 { - return UIBindingObserver(UIElement: self.base) { scrollView, scrollEnabled in + public var isScrollEnabled: Binder { + return Binder(self.base) { scrollView, scrollEnabled in scrollView.isScrollEnabled = scrollEnabled } } diff --git a/RxCocoa/iOS/UISearchBar+Rx.swift b/RxCocoa/iOS/UISearchBar+Rx.swift index 5f6103da..6884bb2e 100644 --- a/RxCocoa/iOS/UISearchBar+Rx.swift +++ b/RxCocoa/iOS/UISearchBar+Rx.swift @@ -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 } diff --git a/RxCocoa/iOS/UITabBarItem+Rx.swift b/RxCocoa/iOS/UITabBarItem+Rx.swift index 0e6ee972..0ed66f64 100644 --- a/RxCocoa/iOS/UITabBarItem+Rx.swift +++ b/RxCocoa/iOS/UITabBarItem+Rx.swift @@ -16,8 +16,8 @@ extension Reactive where Base: UITabBarItem { /// Bindable sink for `badgeValue` property. - public var badgeValue: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { tabBarItem, badgeValue in + public var badgeValue: Binder { + return Binder(self.base) { tabBarItem, badgeValue in tabBarItem.badgeValue = badgeValue } } diff --git a/RxCocoa/iOS/UITextView+Rx.swift b/RxCocoa/iOS/UITextView+Rx.swift index 7507c2a5..05c2f0bc 100644 --- a/RxCocoa/iOS/UITextView+Rx.swift +++ b/RxCocoa/iOS/UITextView+Rx.swift @@ -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. diff --git a/RxCocoa/iOS/UIView+Rx.swift b/RxCocoa/iOS/UIView+Rx.swift index efa55550..ec1dd2b3 100644 --- a/RxCocoa/iOS/UIView+Rx.swift +++ b/RxCocoa/iOS/UIView+Rx.swift @@ -15,22 +15,22 @@ import RxSwift extension Reactive where Base: UIView { /// Bindable sink for `hidden` property. - public var isHidden: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { view, hidden in + public var isHidden: Binder { + return Binder(self.base) { view, hidden in view.isHidden = hidden } } /// Bindable sink for `alpha` property. - public var alpha: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { view, alpha in + public var alpha: Binder { + return Binder(self.base) { view, alpha in view.alpha = alpha } } /// Bindable sink for `isUserInteractionEnabled` property. - public var isUserInteractionEnabled: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { view, userInteractionEnabled in + public var isUserInteractionEnabled: Binder { + return Binder(self.base) { view, userInteractionEnabled in view.isUserInteractionEnabled = userInteractionEnabled } } diff --git a/RxCocoa/iOS/UIViewController+Rx.swift b/RxCocoa/iOS/UIViewController+Rx.swift index 026edfba..a846c579 100644 --- a/RxCocoa/iOS/UIViewController+Rx.swift +++ b/RxCocoa/iOS/UIViewController+Rx.swift @@ -16,8 +16,8 @@ extension Reactive where Base: UIViewController { /// Bindable sink for `title`. - public var title: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { viewController, title in + public var title: Binder { + return Binder(self.base) { viewController, title in viewController.title = title } } diff --git a/RxCocoa/macOS/NSControl+Rx.swift b/RxCocoa/macOS/NSControl+Rx.swift index a8e70b12..2ecb7247 100644 --- a/RxCocoa/macOS/NSControl+Rx.swift +++ b/RxCocoa/macOS/NSControl+Rx.swift @@ -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 { - return UIBindingObserver(UIElement: self.base) { (owner, value) in + public var isEnabled: Binder { + return Binder(self.base) { (owner, value) in owner.isEnabled = value } } diff --git a/RxCocoa/macOS/NSImageView+Rx.swift b/RxCocoa/macOS/NSImageView+Rx.swift index c0787d9d..0695a9aa 100644 --- a/RxCocoa/macOS/NSImageView+Rx.swift +++ b/RxCocoa/macOS/NSImageView+Rx.swift @@ -16,15 +16,15 @@ import Cocoa extension Reactive where Base: NSImageView { /// Bindable sink for `image` property. - public var image: UIBindingObserver { + public var image: Binder { 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 { - return UIBindingObserver(UIElement: self.base) { control, value in + public func image(transitionType: String? = nil) -> Binder { + return Binder(self.base) { control, value in if let transitionType = transitionType { if value != nil { let transition = CATransition() diff --git a/RxCocoa/macOS/NSTextField+Rx.swift b/RxCocoa/macOS/NSTextField+Rx.swift index 00169556..04f47e4a 100644 --- a/RxCocoa/macOS/NSTextField+Rx.swift +++ b/RxCocoa/macOS/NSTextField+Rx.swift @@ -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 ?? "" } diff --git a/RxCocoa/macOS/NSView+Rx.swift b/RxCocoa/macOS/NSView+Rx.swift index f2dbeb7a..31b63b5d 100644 --- a/RxCocoa/macOS/NSView+Rx.swift +++ b/RxCocoa/macOS/NSView+Rx.swift @@ -15,15 +15,15 @@ extension Reactive where Base: NSView { /// Bindable sink for `hidden` property. - public var isHidden: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { view, value in + public var isHidden: Binder { + return Binder(self.base) { view, value in view.isHidden = value } } /// Bindable sink for `alphaValue` property. - public var alpha: UIBindingObserver { - return UIBindingObserver(UIElement: self.base) { view, value in + public var alpha: Binder { + return Binder(self.base) { view, value in view.alphaValue = value } } diff --git a/Sources/RxCocoa/Binder.swift b/Sources/RxCocoa/Binder.swift new file mode 120000 index 00000000..627fbb45 --- /dev/null +++ b/Sources/RxCocoa/Binder.swift @@ -0,0 +1 @@ +../../RxCocoa/Common/Binder.swift \ No newline at end of file diff --git a/Sources/RxCocoa/UIBindingObserver.swift b/Sources/RxCocoa/UIBindingObserver.swift deleted file mode 120000 index 81f54023..00000000 --- a/Sources/RxCocoa/UIBindingObserver.swift +++ /dev/null @@ -1 +0,0 @@ -../../RxCocoa/Traits/UIBindingObserver.swift \ No newline at end of file diff --git a/Tests/RxCocoaTests/Binder+Tests.swift b/Tests/RxCocoaTests/Binder+Tests.swift new file mode 100644 index 00000000..127ebaa2 --- /dev/null +++ b/Tests/RxCocoaTests/Binder+Tests.swift @@ -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) + } + } +} diff --git a/Tests/RxCocoaTests/UIBindingObserver+Tests.swift b/Tests/RxCocoaTests/UIBindingObserver+Tests.swift deleted file mode 100644 index 282d9dc3..00000000 --- a/Tests/RxCocoaTests/UIBindingObserver+Tests.swift +++ /dev/null @@ -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(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) - } - } -} diff --git a/default.profraw b/default.profraw index 5b1ac3d1..b91f3c00 100644 Binary files a/default.profraw and b/default.profraw differ