Skip to content

Commit

Permalink
Improves safety of binding code by tolerating binding from non main d…
Browse files Browse the repository at this point in the history
…ispatch queue.
  • Loading branch information
kzaher committed Dec 17, 2016
1 parent 729f66f commit 5830c6f
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .jazzy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ custom_categories:
- NSSlider+Rx
- NSTextField+Rx
- NSView+Rx
- name: RxCocoa/Platform
children:
- DispatchQueue+Extensions
- name: RxCocoa
children:
- RxCocoa
38 changes: 32 additions & 6 deletions Rx.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,13 @@
C89CFA4F1DAABBE20079D23B /* XCTest+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89CFA1D1DAABBE20079D23B /* XCTest+Rx.swift */; };
C89CFA501DAABBE20079D23B /* XCTest+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89CFA1D1DAABBE20079D23B /* XCTest+Rx.swift */; };
C89CFA511DAABBE20079D23B /* XCTest+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89CFA1D1DAABBE20079D23B /* XCTest+Rx.swift */; };
C8A81CA01E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A81C9F1E05E82C0008DEF4 /* DispatchQueue+Extensions.swift */; };
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 */; };
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 */; };
Expand Down Expand Up @@ -1807,6 +1814,8 @@
C89CFA1C1DAABBE20079D23B /* TestableObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestableObserver.swift; sourceTree = "<group>"; };
C89CFA1D1DAABBE20079D23B /* XCTest+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTest+Rx.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>"; };
C8A9B6F31DAD752200C9B027 /* Observable+BindTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+BindTests.swift"; sourceTree = "<group>"; };
C8B144FA1BD2D44500267DCE /* ConcurrentMainScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcurrentMainScheduler.swift; sourceTree = "<group>"; };
C8B144FF1BD2D80100267DCE /* ImmediateScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImmediateScheduler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2030,7 +2039,6 @@
C8093C471B8A72BE0088E94D /* RxSwift */ = {
isa = PBXGroup;
children = (
C8093C661B8A72BE0088E94D /* Info.plist */,
C8093C491B8A72BE0088E94D /* Cancelable.swift */,
C8093C4D1B8A72BE0088E94D /* ConnectableObservableType.swift */,
C8093C521B8A72BE0088E94D /* Disposable.swift */,
Expand All @@ -2055,6 +2063,7 @@
C8093CB41B8A72BE0088E94D /* Schedulers */,
C8093CBD1B8A72BE0088E94D /* Subjects */,
C85106851C2D54B70075150C /* Extensions */,
C8093C661B8A72BE0088E94D /* Info.plist */,
);
path = RxSwift;
sourceTree = "<group>";
Expand Down Expand Up @@ -2229,14 +2238,15 @@
isa = PBXGroup;
children = (
C89AB2781DAACE490065FBE6 /* RxCocoa.h */,
C89AB2261DAAC33F0065FBE6 /* RxCocoa.swift */,
C8A81C9E1E05E82C0008DEF4 /* Platform */,
C89AB1AA1DAAC3350065FBE6 /* CocoaUnits */,
C8093E811B8A732E0088E94D /* Common */,
C89AB1BC1DAAC3350065FBE6 /* Foundation */,
C88253EE1B8A752B00B02D69 /* iOS */,
C86781901DB823B500B2029A /* macOS */,
C89AB22B1DAAC3A60065FBE6 /* Runtime */,
C8093E9D1B8A732E0088E94D /* Info.plist */,
C89AB2261DAAC33F0065FBE6 /* RxCocoa.swift */,
);
path = RxCocoa;
sourceTree = "<group>";
Expand All @@ -2260,13 +2270,13 @@
C8093F571B8A73A20088E94D /* RxBlocking */ = {
isa = PBXGroup;
children = (
C8093F591B8A73A20088E94D /* README.md */,
C85B01661DB2ACAF006043C3 /* Platform */,
A111CE961B91C97C00D0DCEE /* Info.plist */,
C8093F581B8A73A20088E94D /* ObservableConvertibleType+Blocking.swift */,
C8941BDE1BD5695C00A0E874 /* BlockingObservable.swift */,
C8941BE31BD56B0700A0E874 /* BlockingObservable+Operators.swift */,
C88E296A1BEB712E001CCB92 /* RunLoopLock.swift */,
C8093F591B8A73A20088E94D /* README.md */,
A111CE961B91C97C00D0DCEE /* Info.plist */,
);
path = RxBlocking;
sourceTree = "<group>";
Expand Down Expand Up @@ -2379,6 +2389,7 @@
C83508F11C38706D0027C24C /* UIView+RxTests.swift */,
271A97421CFC99FE00D64125 /* UIViewControler+RxTests.swift */,
4613456E1D9A4467001ABAF2 /* UIWebView+RxTests.swift */,
C8A81CA51E05EAF70008DEF4 /* UIBindingObserver+Tests.swift */,
);
path = RxCocoaTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -2716,18 +2727,18 @@
isa = PBXGroup;
children = (
C86781801DB8143A00B2029A /* Platform */,
C89CFA171DAABBE20079D23B /* Schedulers */,
C89CFA101DAABBE20079D23B /* Any+Equatable.swift */,
C89CFA111DAABBE20079D23B /* ColdObservable.swift */,
C89CFA121DAABBE20079D23B /* Event+Equatable.swift */,
C89CFA131DAABBE20079D23B /* HotObservable.swift */,
C89CFA141DAABBE20079D23B /* Info.plist */,
C89CFA151DAABBE20079D23B /* Recorded.swift */,
C89CFA161DAABBE20079D23B /* RxTests.swift */,
C89CFA171DAABBE20079D23B /* Schedulers */,
C89CFA1A1DAABBE20079D23B /* Subscription.swift */,
C89CFA1B1DAABBE20079D23B /* TestableObservable.swift */,
C89CFA1C1DAABBE20079D23B /* TestableObserver.swift */,
C89CFA1D1DAABBE20079D23B /* XCTest+Rx.swift */,
C89CFA141DAABBE20079D23B /* Info.plist */,
);
path = RxTest;
sourceTree = "<group>";
Expand Down Expand Up @@ -2782,6 +2793,14 @@
name = Products;
sourceTree = "<group>";
};
C8A81C9E1E05E82C0008DEF4 /* Platform */ = {
isa = PBXGroup;
children = (
C8A81C9F1E05E82C0008DEF4 /* DispatchQueue+Extensions.swift */,
);
path = Platform;
sourceTree = "<group>";
};
C8BF34C81C2E426800416CAE /* Platform */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -3653,6 +3672,7 @@
C88254331B8A752B00B02D69 /* UISwitch+Rx.swift in Sources */,
C88254291B8A752B00B02D69 /* UICollectionView+Rx.swift in Sources */,
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 */,
Expand All @@ -3677,6 +3697,7 @@
C86781AB1DB823B500B2029A /* NSTextField+Rx.swift in Sources */,
C89AB1E31DAAC3350065FBE6 /* Variable+Driver.swift in Sources */,
C89AB1D31DAAC3350065FBE6 /* ControlProperty+Driver.swift in Sources */,
C8A81CA11E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */,
C88F76821CE5341700D5A014 /* TextInput.swift in Sources */,
C89AB1DF1DAAC3350065FBE6 /* ObservableConvertibleType+Driver.swift in Sources */,
C89AB1D71DAAC3350065FBE6 /* Driver+Subscription.swift in Sources */,
Expand Down Expand Up @@ -3807,6 +3828,7 @@
C83509361C38706E0027C24C /* NSLayoutConstraint+RxTests.swift in Sources */,
C835095E1C38706E0027C24C /* Observable+StandardSequenceOperatorsTest.swift in Sources */,
C8C4F1631DE9D0A800003FA7 /* UIProgressView+RxTests.swift in Sources */,
C8A81CA61E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */,
4613456F1D9A4467001ABAF2 /* UIWebView+RxTests.swift in Sources */,
C835094C1C38706E0027C24C /* AssumptionsTest.swift in Sources */,
C835092D1C38706E0027C24C /* Control+RxTests.swift in Sources */,
Expand Down Expand Up @@ -3925,6 +3947,7 @@
7EDBAEC31C89BCB9006CBE67 /* UITabBarItem+RxTests.swift in Sources */,
914FCD681CCDB82E0058B304 /* UIPageControl+RxTest.swift in Sources */,
C83509C11C3875220027C24C /* Driver+Extensions.swift in Sources */,
C8A81CA71E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */,
C83509DD1C38754C0027C24C /* EquatableArray.swift in Sources */,
C83509F71C38755D0027C24C /* DisposableTest.swift in Sources */,
C8350A101C38756A0027C24C /* Observable+MultipleTest.swift in Sources */,
Expand Down Expand Up @@ -4023,6 +4046,7 @@
C83509E31C3875580027C24C /* MainThreadPrimitiveHotObservable.swift in Sources */,
C834F6C41DB394E100C29244 /* Observable+BlockingTest.swift in Sources */,
C8350A1D1C38756B0027C24C /* Observable+SubscriptionTest.swift in Sources */,
C8A81CA81E05EAF70008DEF4 /* UIBindingObserver+Tests.swift in Sources */,
C8350A041C38755E0027C24C /* CurrentThreadSchedulerTest.swift in Sources */,
C8353CE81DA19BC500BE3F5C /* Recorded+Timeless.swift in Sources */,
C8A9B6F61DAD752200C9B027 /* Observable+BindTests.swift in Sources */,
Expand Down Expand Up @@ -4601,6 +4625,7 @@
C8F0C0121BBBFBB9001B112F /* UIImageView+Rx.swift in Sources */,
C89AB23B1DAAC3A60065FBE6 /* _RX.m in Sources */,
C8F0C0151BBBFBB9001B112F /* UIControl+Rx.swift in Sources */,
C8A81CA31E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */,
C8D132471C42D15E00B59FFF /* SectionedViewDataSourceType.swift in Sources */,
C8F0C0161BBBFBB9001B112F /* UITableView+Rx.swift in Sources */,
C89AB1D11DAAC3350065FBE6 /* ControlEvent+Driver.swift in Sources */,
Expand Down Expand Up @@ -4742,6 +4767,7 @@
C89AB1E01DAAC3350065FBE6 /* ObservableConvertibleType+Driver.swift in Sources */,
C89AB2241DAAC3350065FBE6 /* URLSession+Rx.swift in Sources */,
D203C50D1BB9C53E00D02D00 /* UISegmentedControl+Rx.swift in Sources */,
C8A81CA21E05E82C0008DEF4 /* DispatchQueue+Extensions.swift in Sources */,
C89AB1CC1DAAC3350065FBE6 /* ControlProperty.swift in Sources */,
D2138C861BB9BEBE00339B5C /* Observable+Bind.swift in Sources */,
AAE623771C82475700FC7801 /* UIProgressView+Rx.swift in Sources */,
Expand Down
11 changes: 10 additions & 1 deletion RxCocoa/CocoaUnits/UIBindingObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import Foundation
import Dispatch
#if !RX_NO_MODULE
import RxSwift
#endif
Expand All @@ -17,6 +18,9 @@ Observer that enforces interface binding rules:
* 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 class UIBindingObserver<UIElementType, Value> : ObserverType where UIElementType: AnyObject {
public typealias E = Value
Expand All @@ -33,7 +37,12 @@ public class UIBindingObserver<UIElementType, Value> : ObserverType where UIElem

/// Binds next element to owner view as described in `binding`.
public func on(_ event: Event<Value>) {
MainScheduler.ensureExecutingOnScheduler(errorMessage: "Element can be bound to user interface only on MainThread.")
if !DispatchQueue.isMain {
DispatchQueue.main.async {
self.on(event)
}
return
}

switch event {
case .next(let element):
Expand Down
2 changes: 1 addition & 1 deletion RxCocoa/Common/DelegateProxyType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ extension DelegateProxyType {
let unregisterDelegate = P.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object)

let subscription = self.asObservable()
.observeOn(MainScheduler())
.catchError { error in
bindingErrorToInterface(error)
return Observable.empty()
Expand All @@ -240,7 +241,6 @@ extension DelegateProxyType {
.concat(Observable.never())
.takeUntil(object.rx.deallocated)
.subscribe { [weak object] (event: Event<E>) in
MainScheduler.ensureExecutingOnScheduler()

if let object = object {
assert(proxy === P.currentDelegateFor(object), "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(P.currentDelegateFor(object))")
Expand Down
1 change: 1 addition & 0 deletions RxCocoa/Platform/DispatchQueue+Extensions.swift
1 change: 1 addition & 0 deletions Sources/RxCocoa/DispatchQueue+Extensions.swift
34 changes: 34 additions & 0 deletions Tests/RxCocoaTests/UIBindingObserver+Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// UIBindingObserver+Tests.swift
// Tests
//
// Created by Krunoslav Zaher on 12/17/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//

import Foundation
import RxCocoa
import XCTest
import RxSwift

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)
}
}
}
1 change: 1 addition & 0 deletions scripts/package-spm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ try packageRelativePath([
"RxCocoa/Foundation",
"RxCocoa/iOS",
"RxCocoa/macOS",
"RxCocoa/Platform",
], targetDirName: "RxCocoa")
try packageRelativePath([
"RxCocoa/Runtime/include",
Expand Down

0 comments on commit 5830c6f

Please sign in to comment.