diff --git a/RxCocoa/Common/DelegateProxy.swift b/RxCocoa/Common/DelegateProxy.swift index 94c62af46d..8b3e850dfa 100644 --- a/RxCocoa/Common/DelegateProxy.swift +++ b/RxCocoa/Common/DelegateProxy.swift @@ -15,16 +15,16 @@ #endif #endif -var delegateAssociatedTag: UInt8 = 0 -var dataSourceAssociatedTag: UInt8 = 0 +var delegateAssociatedTag: UnsafeRawPointer = UnsafeRawPointer(UnsafeMutablePointer.allocate(capacity: 1)) +var dataSourceAssociatedTag: UnsafeRawPointer = UnsafeRawPointer(UnsafeMutablePointer.allocate(capacity: 1)) /// Base class for `DelegateProxyType` protocol. /// /// This implementation is not thread safe and can be used only from one thread (Main thread). open class DelegateProxy : _RXDelegateProxy { - private var sentMessageForSelector = [Selector: PublishSubject<[Any]>]() - private var methodInvokedForSelector = [Selector: PublishSubject<[Any]>]() + private var sentMessageForSelector = [Selector: MessageDispatcher]() + private var methodInvokedForSelector = [Selector: MessageDispatcher]() /// Parent object associated with delegate proxy. weak private(set) var parentObject: AnyObject? @@ -85,17 +85,18 @@ open class DelegateProxy : _RXDelegateProxy { - returns: Observable sequence of arguments passed to `selector` method. */ open func sentMessage(_ selector: Selector) -> Observable<[Any]> { + MainScheduler.ensureExecutingOnScheduler() checkSelectorIsObservable(selector) let subject = sentMessageForSelector[selector] if let subject = subject { - return subject + return subject.asObservable() } else { - let subject = PublishSubject<[Any]>() + let subject = MessageDispatcher(delegateProxy: self) sentMessageForSelector[selector] = subject - return subject + return subject.asObservable() } } @@ -142,17 +143,18 @@ open class DelegateProxy : _RXDelegateProxy { - returns: Observable sequence of arguments passed to `selector` method. */ open func methodInvoked(_ selector: Selector) -> Observable<[Any]> { + MainScheduler.ensureExecutingOnScheduler() checkSelectorIsObservable(selector) let subject = methodInvokedForSelector[selector] if let subject = subject { - return subject + return subject.asObservable() } else { - let subject = PublishSubject<[Any]>() + let subject = MessageDispatcher(delegateProxy: self) methodInvokedForSelector[selector] = subject - return subject + return subject.asObservable() } } @@ -161,15 +163,10 @@ open class DelegateProxy : _RXDelegateProxy { if hasWiredImplementation(for: selector) { print("Delegate proxy is already implementing `\(selector)`, a more performant way of registering might exist.") + return } - // It's important to see if super class reponds to selector and not self, - // because super class (_RxDelegateProxy) returns all methods delegate proxy - // can respond to. - // Because of https://github.com/ReactiveX/RxSwift/issues/907 , and possibly - // some other reasons, subclasses could overrride `responds(to:)`, but it shouldn't matter - // for this case. - if !super.responds(to: selector) { + guard (self.forwardToDelegate()?.responds(to: selector) ?? false) || voidDelegateMethodsContain(selector) else { rxFatalError("This class doesn't respond to selector \(selector)") } } @@ -188,7 +185,7 @@ open class DelegateProxy : _RXDelegateProxy { /// /// - returns: Associated object tag. open class func delegateAssociatedObjectTag() -> UnsafeRawPointer { - return _pointer(&delegateAssociatedTag) + return delegateAssociatedTag } /// Initializes new instance of delegate proxy. @@ -223,7 +220,11 @@ open class DelegateProxy : _RXDelegateProxy { /// - parameter forwardToDelegate: Reference of delegate that receives all messages through `self`. /// - parameter retainDelegate: Should `self` retain `forwardToDelegate`. open func setForwardToDelegate(_ delegate: AnyObject?, retainDelegate: Bool) { + #if DEBUG // 4.0 all configurations + MainScheduler.ensureExecutingOnScheduler() + #endif self._setForward(toDelegate: delegate, retainDelegate: retainDelegate) + self.reset() } /// Returns reference of normal delegate that receives all forwarded messages @@ -233,7 +234,36 @@ open class DelegateProxy : _RXDelegateProxy { open func forwardToDelegate() -> AnyObject? { return self._forwardToDelegate } + + private func hasObservers(selector: Selector) -> Bool { + return (sentMessageForSelector[selector]?.hasObservers ?? false) + || (methodInvokedForSelector[selector]?.hasObservers ?? false) + } + override open func responds(to aSelector: Selector!) -> Bool { + return super.responds(to: aSelector) + || (self._forwardToDelegate?.responds(to: aSelector) ?? false) + || (self.voidDelegateMethodsContain(aSelector) && self.hasObservers(selector: aSelector)) + } + + internal func reset() { + guard let delegateProxySelf = self as? DelegateProxyType else { + rxFatalErrorInDebug("\(self) doesn't implement delegate proxy type.") + return + } + + guard let parentObject = self.parentObject else { return } + + let selfType = type(of: delegateProxySelf) + + let maybeCurrentDelegate = selfType.currentDelegateFor(parentObject) + + if maybeCurrentDelegate === self { + selfType.setCurrentDelegate(nil, toObject: parentObject) + selfType.setCurrentDelegate(self, toObject: parentObject) + } + } + deinit { for v in sentMessageForSelector.values { v.on(.completed) @@ -245,13 +275,37 @@ open class DelegateProxy : _RXDelegateProxy { _ = Resources.decrementTotal() #endif } +} + +fileprivate let mainScheduler = MainScheduler() + +fileprivate final class MessageDispatcher { + private let dispatcher: PublishSubject<[Any]> + private let result: Observable<[Any]> + + init(delegateProxy _delegateProxy: DelegateProxy) { + weak var weakDelegateProxy = _delegateProxy - // MARK: Pointer + let dispatcher = PublishSubject<[Any]>() + self.dispatcher = dispatcher - final class func _pointer(_ p: UnsafeRawPointer) -> UnsafeRawPointer { - return p + self.result = dispatcher + .do(onSubscribed: { weakDelegateProxy?.reset() }, onDispose: { weakDelegateProxy?.reset() }) + .share() + .subscribeOn(mainScheduler) } -} -#endif + var on: (Event<[Any]>) -> () { + return self.dispatcher.on + } + var hasObservers: Bool { + return self.dispatcher.hasObservers + } + + func asObservable() -> Observable<[Any]> { + return self.result + } +} + +#endif diff --git a/RxCocoa/Common/DelegateProxyType.swift b/RxCocoa/Common/DelegateProxyType.swift index f0faf0d795..7766bd6978 100644 --- a/RxCocoa/Common/DelegateProxyType.swift +++ b/RxCocoa/Common/DelegateProxyType.swift @@ -176,7 +176,7 @@ extension DelegateProxyType { assert(Self.currentDelegateFor(object) === proxy) assert(proxy.forwardToDelegate() === currentDelegate) } - + return proxy } @@ -200,13 +200,6 @@ extension DelegateProxyType { proxy.setForwardToDelegate(forwardDelegate, retainDelegate: retainDelegate) - // refresh properties after delegate is set - // some views like UITableView cache `respondsToSelector` - Self.setCurrentDelegate(nil, toObject: object) - Self.setCurrentDelegate(proxy, toObject: object) - - assert(proxy.forwardToDelegate() === forwardDelegate, "Setting of delegate failed:\ncurrent:\n\(String(describing: proxy.forwardToDelegate()))\nexpected:\n\(forwardDelegate)") - return Disposables.create { MainScheduler.ensureExecutingOnScheduler() diff --git a/RxCocoa/Runtime/_RXDelegateProxy.m b/RxCocoa/Runtime/_RXDelegateProxy.m index d0333a05df..36338a5751 100644 --- a/RxCocoa/Runtime/_RXDelegateProxy.m +++ b/RxCocoa/Runtime/_RXDelegateProxy.m @@ -18,11 +18,11 @@ @interface _RXDelegateProxy () { @end -static NSMutableDictionary *forwardableSelectorsPerClass = nil; +static NSMutableDictionary *voidSelectorsPerClass = nil; @implementation _RXDelegateProxy -+(NSSet*)collectSelectorsForProtocol:(Protocol *)protocol { ++(NSSet*)collectVoidSelectorsForProtocol:(Protocol *)protocol { NSMutableSet *selectors = [NSMutableSet set]; unsigned int protocolMethodCount = 0; @@ -41,7 +41,7 @@ +(NSSet*)collectSelectorsForProtocol:(Protocol *)protocol { Protocol * __unsafe_unretained * pSubprotocols = protocol_copyProtocolList(protocol, &numberOfBaseProtocols); for (unsigned int i = 0; i < numberOfBaseProtocols; ++i) { - [selectors unionSet:[self collectSelectorsForProtocol:pSubprotocols[i]]]; + [selectors unionSet:[self collectVoidSelectorsForProtocol:pSubprotocols[i]]]; } free(pSubprotocols); @@ -51,11 +51,11 @@ +(NSSet*)collectSelectorsForProtocol:(Protocol *)protocol { +(void)initialize { @synchronized (_RXDelegateProxy.class) { - if (forwardableSelectorsPerClass == nil) { - forwardableSelectorsPerClass = [[NSMutableDictionary alloc] init]; + if (voidSelectorsPerClass == nil) { + voidSelectorsPerClass = [[NSMutableDictionary alloc] init]; } - NSMutableSet *allowedSelectors = [NSMutableSet set]; + NSMutableSet *voidSelectors = [NSMutableSet set]; #define CLASS_HIERARCHY_MAX_DEPTH 100 @@ -70,8 +70,8 @@ +(void)initialize { Protocol *__unsafe_unretained *pProtocols = class_copyProtocolList(targetClass, &count); for (unsigned int i = 0; i < count; i++) { - NSSet *selectorsForProtocol = [self collectSelectorsForProtocol:pProtocols[i]]; - [allowedSelectors unionSet:selectorsForProtocol]; + NSSet *selectorsForProtocol = [self collectVoidSelectorsForProtocol:pProtocols[i]]; + [voidSelectors unionSet:selectorsForProtocol]; } free(pProtocols); @@ -84,7 +84,7 @@ +(void)initialize { #endif } - forwardableSelectorsPerClass[CLASS_VALUE(self)] = allowedSelectors; + voidSelectorsPerClass[CLASS_VALUE(self)] = voidSelectors; } } @@ -106,20 +106,14 @@ -(BOOL)hasWiredImplementationForSelector:(SEL)selector { return [super respondsToSelector:selector]; } --(BOOL)canRespondToSelector:(SEL)selector { +-(BOOL)voidDelegateMethodsContain:(SEL)selector { @synchronized(_RXDelegateProxy.class) { - NSSet *allowedMethods = forwardableSelectorsPerClass[CLASS_VALUE(self.class)]; - NSAssert(allowedMethods != nil, @"Set of allowed methods not initialized"); - return [allowedMethods containsObject:SEL_VALUE(selector)]; + NSSet *voidSelectors = voidSelectorsPerClass[CLASS_VALUE(self.class)]; + NSAssert(voidSelectors != nil, @"Set of allowed methods not initialized"); + return [voidSelectors containsObject:SEL_VALUE(selector)]; } } --(BOOL)respondsToSelector:(SEL)aSelector { - return [super respondsToSelector:aSelector] - || [self._forwardToDelegate respondsToSelector:aSelector] - || [self canRespondToSelector:aSelector]; -} - -(void)forwardInvocation:(NSInvocation *)anInvocation { BOOL isVoid = RX_is_method_signature_void(anInvocation.methodSignature); NSArray *arguments = nil; diff --git a/RxCocoa/Runtime/include/_RXDelegateProxy.h b/RxCocoa/Runtime/include/_RXDelegateProxy.h index 523e7ae032..f82d612820 100644 --- a/RxCocoa/Runtime/include/_RXDelegateProxy.h +++ b/RxCocoa/Runtime/include/_RXDelegateProxy.h @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN -(void)_setForwardToDelegate:(id __nullable)forwardToDelegate retainDelegate:(BOOL)retainDelegate; -(BOOL)hasWiredImplementationForSelector:(SEL)selector; +-(BOOL)voidDelegateMethodsContain:(SEL)selector; -(void)_sentMessage:(SEL)selector withArguments:(NSArray*)arguments; -(void)_methodInvoked:(SEL)selector withArguments:(NSArray*)arguments; diff --git a/RxCocoa/iOS/Proxies/RxCollectionViewDataSourceProxy.swift b/RxCocoa/iOS/Proxies/RxCollectionViewDataSourceProxy.swift index 071d121147..01df5327d5 100644 --- a/RxCocoa/iOS/Proxies/RxCollectionViewDataSourceProxy.swift +++ b/RxCocoa/iOS/Proxies/RxCollectionViewDataSourceProxy.swift @@ -72,7 +72,7 @@ public class RxCollectionViewDataSourceProxy /// For more information take a look at `DelegateProxyType`. public override class func delegateAssociatedObjectTag() -> UnsafeRawPointer { - return _pointer(&dataSourceAssociatedTag) + return dataSourceAssociatedTag } /// For more information take a look at `DelegateProxyType`. @@ -97,10 +97,12 @@ public class RxCollectionViewDataSourceProxy private func refreshCollectionViewDataSource() { if self.collectionView?.dataSource === self { - self.collectionView?.dataSource = nil if _requiredMethodsDataSource != nil && _requiredMethodsDataSource !== collectionViewDataSourceNotSet { self.collectionView?.dataSource = self } + else { + self.collectionView?.dataSource = nil + } } } } diff --git a/RxCocoa/iOS/Proxies/RxTableViewDataSourceProxy.swift b/RxCocoa/iOS/Proxies/RxTableViewDataSourceProxy.swift index 177a4efb17..d90b37f2f0 100644 --- a/RxCocoa/iOS/Proxies/RxTableViewDataSourceProxy.swift +++ b/RxCocoa/iOS/Proxies/RxTableViewDataSourceProxy.swift @@ -37,45 +37,6 @@ public class RxTableViewDataSourceProxy /// Typed parent object. public weak fileprivate(set) var tableView: UITableView? - // issue https://github.com/ReactiveX/RxSwift/issues/907 - private var _numberOfObservers = 0 - private var _commitForRowAtSequenceSentMessage: CachedCommitForRowAt? = nil - private var _commitForRowAtSequenceMethodInvoked: CachedCommitForRowAt? = nil - - fileprivate final class Counter { - var hasObservers: Bool = false - } - - fileprivate final class CachedCommitForRowAt { - let sequence: Observable<[Any]> - let counter: Counter - - var hasObservers: Bool { - return counter.hasObservers - } - - init(sequence: Observable<[Any]>, counter: Counter) { - self.sequence = sequence - self.counter = counter - } - - static func createFor(commitForRowAt: Observable<[Any]>, proxy: RxTableViewDataSourceProxy) -> CachedCommitForRowAt { - let counter = Counter() - - let commitForRowAtSequence = commitForRowAt.do(onSubscribe: { [weak proxy] in - counter.hasObservers = true - proxy?.refreshTableViewDataSource() - }, onDispose: { [weak proxy] in - counter.hasObservers = false - proxy?.refreshTableViewDataSource() - }) - .subscribeOn(MainScheduler()) - .share() - - return CachedCommitForRowAt(sequence: commitForRowAtSequence, counter: counter) - } - } - fileprivate weak var _requiredMethodsDataSource: UITableViewDataSource? = tableViewDataSourceNotSet /// Initializes `RxTableViewDataSourceProxy` @@ -108,7 +69,7 @@ public class RxTableViewDataSourceProxy /// For more information take a look at `DelegateProxyType`. public override class func delegateAssociatedObjectTag() -> UnsafeRawPointer { - return _pointer(&dataSourceAssociatedTag) + return dataSourceAssociatedTag } /// For more information take a look at `DelegateProxyType`. @@ -131,69 +92,17 @@ public class RxTableViewDataSourceProxy refreshTableViewDataSource() } - override open func methodInvoked(_ selector: Selector) -> Observable<[Any]> { - MainScheduler.ensureExecutingOnScheduler() - - // This is special behavior for commit:forRowAt: - // If proxy data source responds to this selector then table view will show - // swipe to delete option even when nobody is observing. - // https://github.com/ReactiveX/RxSwift/issues/907 - if selector == #selector(UITableViewDataSource.tableView(_:commit:forRowAt:)) { - guard let commitForRowAtSequenceMethodInvoked = _commitForRowAtSequenceMethodInvoked else { - let commitForRowAtSequenceMethodInvoked = CachedCommitForRowAt.createFor(commitForRowAt: super.methodInvoked(selector), proxy: self) - _commitForRowAtSequenceMethodInvoked = commitForRowAtSequenceMethodInvoked - return commitForRowAtSequenceMethodInvoked.sequence - } - - return commitForRowAtSequenceMethodInvoked.sequence - } - - return super.methodInvoked(selector) - } - - override open func sentMessage(_ selector: Selector) -> Observable<[Any]> { - MainScheduler.ensureExecutingOnScheduler() - - // This is special behavior for commit:forRowAt: - // If proxy data source responds to this selector then table view will show - // swipe to delete option even when nobody is observing. - // https://github.com/ReactiveX/RxSwift/issues/907 - if selector == #selector(UITableViewDataSource.tableView(_:commit:forRowAt:)) { - guard let commitForRowAtSequenceSentMessage = _commitForRowAtSequenceSentMessage else { - let commitForRowAtSequenceSentMessage = CachedCommitForRowAt.createFor(commitForRowAt: super.sentMessage(selector), proxy: self) - _commitForRowAtSequenceSentMessage = commitForRowAtSequenceSentMessage - return commitForRowAtSequenceSentMessage.sequence - } - - return commitForRowAtSequenceSentMessage.sequence - } - - return super.sentMessage(selector) - } - // https://github.com/ReactiveX/RxSwift/issues/907 private func refreshTableViewDataSource() { if self.tableView?.dataSource === self { - self.tableView?.dataSource = nil if _requiredMethodsDataSource != nil && _requiredMethodsDataSource !== tableViewDataSourceNotSet { self.tableView?.dataSource = self } + else { + self.tableView?.dataSource = nil + } } } - - override open func responds(to aSelector: Selector!) -> Bool { - // https://github.com/ReactiveX/RxSwift/issues/907 - let commitForRowAtSelector = #selector(UITableViewDataSource.tableView(_:commit:forRowAt:)) - if aSelector == commitForRowAtSelector { - // without `as? UITableViewDataSource` `responds(to:)` fails, 🍻 compiler team - let forwardDelegateResponds = (self.forwardToDelegate() as? UITableViewDataSource)?.responds(to: commitForRowAtSelector) - return (_commitForRowAtSequenceSentMessage?.hasObservers ?? false) - || (_commitForRowAtSequenceMethodInvoked?.hasObservers ?? false) - || (forwardDelegateResponds ?? false) - } - - return super.responds(to: aSelector) - } } #endif diff --git a/Sources/RxCocoaRuntime/include/_RXDelegateProxy.h b/Sources/RxCocoaRuntime/include/_RXDelegateProxy.h index 523e7ae032..f82d612820 100644 --- a/Sources/RxCocoaRuntime/include/_RXDelegateProxy.h +++ b/Sources/RxCocoaRuntime/include/_RXDelegateProxy.h @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN -(void)_setForwardToDelegate:(id __nullable)forwardToDelegate retainDelegate:(BOOL)retainDelegate; -(BOOL)hasWiredImplementationForSelector:(SEL)selector; +-(BOOL)voidDelegateMethodsContain:(SEL)selector; -(void)_sentMessage:(SEL)selector withArguments:(NSArray*)arguments; -(void)_methodInvoked:(SEL)selector withArguments:(NSArray*)arguments; diff --git a/Tests/RxCocoaTests/DelegateProxyTest+UIKit.swift b/Tests/RxCocoaTests/DelegateProxyTest+UIKit.swift index 99a807ba37..bc44356e6a 100644 --- a/Tests/RxCocoaTests/DelegateProxyTest+UIKit.swift +++ b/Tests/RxCocoaTests/DelegateProxyTest+UIKit.swift @@ -140,18 +140,10 @@ final class UITableViewSubclass1 (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - + func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { return RxScrollViewDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self) } @@ -181,16 +173,8 @@ final class UITableViewSubclass2 } } - var testSentMessage: Observable { - return rx.dataSource - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.dataSource - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.dataSource } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -220,16 +204,8 @@ final class UICollectionViewSubclass1 (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -261,16 +237,8 @@ final class UICollectionViewSubclass2 } } - var testSentMessage: Observable { - return rx.dataSource - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.dataSource - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.dataSource } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -300,16 +268,8 @@ final class UIScrollViewSubclass (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -341,16 +301,8 @@ final class UISearchBarSubclass (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -381,16 +333,8 @@ final class UITextViewSubclass (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -418,19 +362,10 @@ final class UISearchControllerSubclass (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } - func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { return RxSearchControllerDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self) } @@ -457,18 +392,10 @@ final class UIPickerViewSubclass (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { return RxPickerViewDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, @@ -494,15 +421,8 @@ final class UIWebViewSubclass: UIWebView, TestDelegateControl { (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -538,16 +458,8 @@ final class NSTextStorageSubclass (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -590,16 +502,8 @@ final class UITabBarControllerSubclass (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { @@ -616,16 +520,8 @@ final class UITabBarSubclass: UITabBar, TestDelegateControl { (delegate as! TestDelegateProtocol).testEventHappened?(value) } - var testSentMessage: Observable { - return rx.delegate - .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } - } - - var testMethodInvoked: Observable { - return rx.delegate - .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) - .map { a in (a[0] as! NSNumber).intValue } + var delegateProxy: DelegateProxy { + return self.rx.delegate } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable { diff --git a/Tests/RxCocoaTests/DelegateProxyTest.swift b/Tests/RxCocoaTests/DelegateProxyTest.swift index a9f81b2eed..5755275f48 100644 --- a/Tests/RxCocoaTests/DelegateProxyTest.swift +++ b/Tests/RxCocoaTests/DelegateProxyTest.swift @@ -33,12 +33,26 @@ import UIKit protocol TestDelegateControl: NSObjectProtocol { func doThatTest(_ value: Int) - var testSentMessage: Observable { get } - var testMethodInvoked: Observable { get } + var delegateProxy: DelegateProxy { get } func setMineForwardDelegate(_ testDelegate: TestDelegateProtocol) -> Disposable } +extension TestDelegateControl { + + var testSentMessage: Observable { + return delegateProxy + .sentMessage(#selector(TestDelegateProtocol.testEventHappened(_:))) + .map { a in (a[0] as! NSNumber).intValue } + } + + var testMethodInvoked: Observable { + return delegateProxy + .methodInvoked(#selector(TestDelegateProtocol.testEventHappened(_:))) + .map { a in (a[0] as! NSNumber).intValue } + } +} + // MARK: Tests final class DelegateProxyTest : RxTest { @@ -49,7 +63,7 @@ final class DelegateProxyTest : RxTest { view.delegate = mock let _ = view.rx.proxy - + XCTAssertEqual(mock.messages, []) XCTAssertTrue(view.rx.proxy.forwardToDelegate() === mock) } @@ -84,23 +98,44 @@ final class DelegateProxyTest : RxTest { var observedFeedRequestSentMessage = false var observedMessageInvoked = false var events: [MessageProcessingStage] = [] - - _ = view.rx.proxy.sentMessage(#selector(ThreeDSectionedViewProtocol.threeDView(_:didLearnSomething:))) + + var delegates: [NSObject?] = [] + var responds: [Bool] = [] + + _ = view.rx.observeWeakly(NSObject.self, "delegate").skip(1).subscribe(onNext: { delegate in + delegates.append(delegate) + if let delegate = delegate { + responds.append(delegate.responds(to: #selector(ThreeDSectionedViewProtocol.threeDView(_:didLearnSomething:)))) + } + }) + + let sentMessage = view.rx.proxy.sentMessage(#selector(ThreeDSectionedViewProtocol.threeDView(_:didLearnSomething:))) + let methodInvoked = view.rx.proxy.methodInvoked(#selector(ThreeDSectionedViewProtocol.threeDView(_:didLearnSomething:))) + + XCTAssertArraysEqual(delegates, [view.rx.proxy]) { $0 === $1 } + XCTAssertEqual(responds, [true]) + + _ = methodInvoked .subscribe(onNext: { n in - observedFeedRequestSentMessage = true - events.append(.sentMessage) + observedMessageInvoked = true + events.append(.methodInvoked) }) + XCTAssertArraysEqual(delegates, [view.rx.proxy, nil, view.rx.proxy]) { $0 === $1 } + XCTAssertEqual(responds, [true, true]) + mock.invoked = { events.append(.invoking) } - - _ = view.rx.proxy.methodInvoked(#selector(ThreeDSectionedViewProtocol.threeDView(_:didLearnSomething:))) + + _ = sentMessage .subscribe(onNext: { n in - observedMessageInvoked = true - events.append(.methodInvoked) + observedFeedRequestSentMessage = true + events.append(.sentMessage) }) + XCTAssertArraysEqual(delegates, [view.rx.proxy, nil, view.rx.proxy, nil, view.rx.proxy]) { $0 === $1 } + XCTAssertEqual(responds, [true, true, true]) XCTAssertTrue(!observedFeedRequestSentMessage) XCTAssertTrue(!observedMessageInvoked) @@ -176,32 +211,63 @@ final class DelegateProxyTest : RxTest { var receivedArgumentMethodInvoked: IndexPath? = nil var events: [MessageProcessingStage] = [] + + var delegates: [NSObject?] = [] + var responds: [Bool] = [] + + _ = view.rx.observeWeakly(NSObject.self, "delegate").skip(1).subscribe(onNext: { delegate in + delegates.append(delegate) + if let delegate = delegate { + responds.append(delegate.responds(to: #selector(ThreeDSectionedViewProtocol.threeDView(_:didGetXXX:)))) + } + }) + + let sentMessage = view.rx.proxy.sentMessage(#selector(ThreeDSectionedViewProtocol.threeDView(_:didGetXXX:))) + let methodInvoked = view.rx.proxy.methodInvoked(#selector(ThreeDSectionedViewProtocol.threeDView(_:didGetXXX:))) + + XCTAssertArraysEqual(delegates, [view.rx.proxy]) { $0 == $1 } + XCTAssertEqual(responds, [false]) - _ = view.rx.proxy.sentMessage(#selector(ThreeDSectionedViewProtocol.threeDView(_:didGetXXX:))) + let d1 = sentMessage .subscribe(onNext: { n in let ip = n[1] as! IndexPath receivedArgumentSentMessage = ip events.append(.sentMessage) }) - _ = view.rx.proxy.methodInvoked(#selector(ThreeDSectionedViewProtocol.threeDView(_:didGetXXX:))) + XCTAssertArraysEqual(delegates, [view.rx.proxy, nil, view.rx.proxy]) { $0 == $1 } + XCTAssertEqual(responds, [false, true]) + + let d2 = methodInvoked .subscribe(onNext: { n in let ip = n[1] as! IndexPath receivedArgumentMethodInvoked = ip events.append(.methodInvoked) }) + XCTAssertArraysEqual(delegates, [view.rx.proxy, nil, view.rx.proxy, nil, view.rx.proxy]) { $0 === $1 } + XCTAssertEqual(responds, [false, true, true]) + mock.invoked = { events.append(.invoking) } - view.delegate?.threeDView?(view, didGetXXX: sentArgument) XCTAssertTrue(receivedArgumentSentMessage == sentArgument) XCTAssertTrue(receivedArgumentMethodInvoked == sentArgument) XCTAssertEqual(mock.messages, []) XCTAssertEqual(events, [.sentMessage, .methodInvoked]) + + d1.dispose() + + XCTAssertArraysEqual(delegates, [view.rx.proxy, nil, view.rx.proxy, nil, view.rx.proxy, nil, view.rx.proxy]) { $0 === $1 } + XCTAssertEqual(responds, [false, true, true, true]) + + d2.dispose() + + XCTAssertArraysEqual(delegates, [view.rx.proxy, nil, view.rx.proxy, nil, view.rx.proxy, nil, view.rx.proxy, nil, view.rx.proxy]) { $0 === $1 } + XCTAssertEqual(responds, [false, true, true, true, false]) } func test_delegateProxyCompletesOnDealloc() { @@ -343,7 +409,7 @@ final class Food: NSObject { } final class ThreeDSectionedView: NSObject { - var delegate: ThreeDSectionedViewProtocol? + dynamic var delegate: ThreeDSectionedViewProtocol? } // }