Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improves auto implementing observing mechanism for DelegateProxy. #…
Browse files Browse the repository at this point in the history
kzaher committed Feb 12, 2017

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
1 parent ff524bb commit 238e6a1
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
@@ -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
100 changes: 77 additions & 23 deletions RxCocoa/Common/DelegateProxy.swift
Original file line number Diff line number Diff line change
@@ -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?
@@ -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
9 changes: 1 addition & 8 deletions RxCocoa/Common/DelegateProxyType.swift
Original file line number Diff line number Diff line change
@@ -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()

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

0 comments on commit 238e6a1

Please sign in to comment.