Skip to content

Commit

Permalink
Improves auto implementing observing mechanism for DelegateProxy. R…
Browse files Browse the repository at this point in the history
  • Loading branch information
kzaher committed Feb 19, 2017
1 parent 5549587 commit cfe403b
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 303 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file.

* Adds `AsyncSubject` implementation

## Anomalies
* #1081, #1087 - Improves DelegateProxy `responds(to:)` selector logic to only respond to used selectors.

## [3.2.0](https://github.com/ReactiveX/RxSwift/releases/tag/3.2.0) (Xcode 8 / Swift 3.0 compatible)

* Adds `groupBy` operator
Expand Down
100 changes: 77 additions & 23 deletions RxCocoa/Common/DelegateProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@
#endif
#endif

var delegateAssociatedTag: UInt8 = 0
var dataSourceAssociatedTag: UInt8 = 0
var delegateAssociatedTag: UnsafeRawPointer = UnsafeRawPointer(UnsafeMutablePointer<UInt8>.allocate(capacity: 1))
var dataSourceAssociatedTag: UnsafeRawPointer = UnsafeRawPointer(UnsafeMutablePointer<UInt8>.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?
Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -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()
}
}

Expand All @@ -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)")
}
}
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
9 changes: 1 addition & 8 deletions RxCocoa/Common/DelegateProxyType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ extension DelegateProxyType {
assert(Self.currentDelegateFor(object) === proxy)
assert(proxy.forwardToDelegate() === currentDelegate)
}

return proxy
}

Expand All @@ -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()

Expand Down
32 changes: 13 additions & 19 deletions RxCocoa/Runtime/_RXDelegateProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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

Expand All @@ -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);
Expand All @@ -84,7 +84,7 @@ +(void)initialize {
#endif
}

forwardableSelectorsPerClass[CLASS_VALUE(self)] = allowedSelectors;
voidSelectorsPerClass[CLASS_VALUE(self)] = voidSelectors;
}
}

Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions RxCocoa/Runtime/include/_RXDelegateProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions RxCocoa/iOS/Proxies/RxCollectionViewDataSourceProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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
}
}
}
}
Expand Down
Loading

0 comments on commit cfe403b

Please sign in to comment.