-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Generics parameter in DelegateProxy #1379
Implement Generics parameter in DelegateProxy #1379
Conversation
Generated by 🚫 Danger |
assert(Self.currentDelegateFor(object) === proxy) | ||
assert(proxy.forwardToDelegate() === currentDelegate) | ||
let currentDelegate = self.currentDelegateFor(object) | ||
let delegateProxy = unsafeDowncast(proxy, to: self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used unsafeDowncast
instead of common force casting.
Let's think about UITableView
.
We can get instance of DelegateProxy
using RxScrollViewDelegateProxy.proxyForObject(scrollView)
or RxTableViewDelegateProxy.proxyForObject(tableView)
and if scrollView
and tableView
are same object, then DelegateProxy instances should be same object.
In this case, proxyForObject
should cast two types that has different generics parameter.
Common force casting is failed, but unsafeDowncast
is succeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want to understand, what did you force cast to here? To what type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, in RxTableViewDelegateProxy
case,
(tableView as UITableView).rx.delegate // DelegateProxy<UITableView, UIScrollViewDelegate>
(tableView as UIScrollView).rx.delegate // DelegateProxy<UIScrollView, UIScrollViewDelegate>
In this case, actual type is RxTableViewDelegateProxy<UITableView>
because it is checked on runtime in DelegateProxyFactory
.
And in this case, I force cast from RxTableViewDelegateProxy<UITableView>
to RxScrollViewDelegateProxy<UIScrollView>
.
I think it is not good now...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think this is the biggest problem right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could I make commit that avoid unsafeDowncast
way?
I think it will be better than current implemetation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We definitely need to figure this thing out, otherwise I'm not sure we can merge this :)
, UIScrollViewDelegate | ||
, DelegateProxyType { | ||
open class RxScrollViewDelegateProxy<P: UIScrollView> | ||
: DelegateProxy<P, UIScrollViewDelegate> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the Delegate
should fix in a type declared DelegateProxyType
.
Delegate
affect DelegateProxy.installForwardDelegate
implementation, and of course we can call DelegateProxy.installForwardDelegate
from upper casted parent object.
// e.g.
let tableView = UITableView()
RxScrollViewDelegateProxy.installForwardDelegate(myScrollViewDelegate, retainDelegate: false, onProxyForObject: tableView)
tableView.rx.delegate.forwardToDelegate() // -> It is myScrollViewDelegate.
We know same pattern in UIKit, it seems strange.
let tableView = UITableView()
(tableView as UIScrollView).delegate = myScrollViewDelegate
tableView.delegate // -> myScrollViewDelegate, but it behave as UITableViewDelegate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @tarunon ,
I'm sorry but I don't think I understand what did you want to say here 😅 ? Is there some problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is no problem here.
I mean it is difficult making RxTableViewDelegateProxy<UITableView, UITableViewDelegate>
.
I'm sorry confused you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is really a bunch of code here. It will take some time for me to go through this more thoroughly.
This is my initial feedback.
I have a general impression that this is a step in good direction, but we'll need to simplify this PR and make API a bit clearer to use.
RxCocoa/Common/DelegateProxy.swift
Outdated
@@ -21,18 +21,20 @@ var dataSourceAssociatedTag: UnsafeRawPointer = UnsafeRawPointer(UnsafeMutablePo | |||
/// 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 { | |||
|
|||
open class DelegateProxy<P: AnyObject, D: NSObjectProtocol>: _RXDelegateProxy, DelegateProxyBase { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why DelegateProxyBase
. Maybe DelegateProxyProtocol
is more idiomatic to Swift?
RxCocoa/Common/DelegateProxy.swift
Outdated
/// | ||
/// - parameter object: Object that has delegate property. | ||
/// - returns: Value of delegate property. | ||
open class func currentDelegateFor(_ object: ParentObject) -> Delegate? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there some reason we are conforming to DelegateProxy
to DelegateProxyBase
. It seems to me we aren't getting anything from that and using abstract methods in Swift is usually avoided.
Yes, we use them in a couple of places, but we don't need here IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually not sure we need both DelegateProxyBase
and DelegateProxyType
. Is there some reason we have two?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can merge it, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that we don't need this method defined here.
RxCocoa/Common/DelegateProxy.swift
Outdated
@@ -224,8 +253,8 @@ open class DelegateProxy : _RXDelegateProxy { | |||
/// through `self`. | |||
/// | |||
/// - returns: Value of reference if set or nil. | |||
open func forwardToDelegate() -> AnyObject? { | |||
return self._forwardToDelegate | |||
open func forwardToDelegate() -> D? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using associated type is probably more readable: Delegate
.
- parameter extends: Extend DelegateProxyFactory if needs. See 'DelegateProxyType'. | ||
- returns: DelegateProxyFactory shared instance. | ||
*/ | ||
public static func sharedFactory<DelegateProxy: DelegateProxyBase & DelegateProxyType>(for proxyType: DelegateProxy.Type, extends: (() -> Void)? = nil) -> DelegateProxyFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should rename this. The name, intended usage and implementation are confusing to me.
/// When make 'RxXXXDelegateProxy' subclass, call 'RxXXXDelegateProxySubclass.prepareForFactory()' 1 time, or use it in DelegateProxyFactory | ||
/// 'RxXXXDelegateProxy' can have one subclass implementation per concrete ParentObject type. | ||
/// Should call it from concrete DelegateProxy type, not generic. | ||
public static func prepareForFactory() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just call it register
?
MainScheduler.ensureExecutingOnScheduler() | ||
if let factory = _sharedFactories[ObjectIdentifier(DelegateProxy.Delegate.self)] { | ||
return factory | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks weird to me, if factory
already exists, extends
is ignored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should rename extends
to knownImplementations
, make it a static method on DelegateProxyType
and create a default implementation that has void
implementation.
This API doesn't prevent anyone from calling sharedFactory
twice with different extends
parameter, which is not how this API show work IMHO.
|
||
/** | ||
Shared instance of DelegateProxyFactory, if isn't exist shared instance, make DelegateProxyFactory instance for proxy type and extends. | ||
DlegateProxyFactory have a shared instance per Delegate type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo DlegateProxyFactory
.
Thank you, @kzaher I have a discussion. // Root DelegateProxy implementation
class RxScrollViewDelegateProxy
: DelegateProxy<UIScrollView, UIScrollViewDelegate>, ... { ... }
// Subclass of Root DelegateProxy
class RxTableViewDelegateProxy: RxScrollViewDelegateProxy {
require init(parentObject: UIScrollView) {
self.tableView = castOrFatalError(parentObject)
super.init(parentObject: parentObject)
}
}
// Extends Root DelegateProxy's factory
RxTableViewDelegateProxy.register(for: UITableView) |
RxTableViewDelegateProxy(parentObject: parentObject) | ||
static var factory: DelegateProxyFactory { | ||
return DelegateProxyFactory.sharedFactory(for: RxScrollViewDelegateProxy<UIScrollView>.self) { | ||
RxTableViewDelegateProxy<UITableView>.register() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It think we've changed this part.
static var factory: DelegateProxyFactory { get } | ||
|
||
/// Creates new proxy for target object. | ||
/// Should not call this function directory, use 'DelegateProxy.proxyForObject' | ||
static func createProxy(for object: AnyObject) -> AnyObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove this from DelgateProxyType
since protocol extension is already implementing it depending on factory
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this now have the following signature?
static func createProxy(for object: ParentObject) -> Self
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I use static func createProxy(for object: ParentObject) -> Self
instead,
I can see compile error like Method 'createProxy(for:)' in non-final class 'RxScrollViewDelegate'
.
/// Store DelegateProxy subclass to factory. | ||
/// When make 'RxXXXDelegateProxy' subclass, call 'RxXXXDelegateProxySubclass.register()' 1 time. | ||
/// 'RxXXXDelegateProxy' can have one subclass implementation per concrete ParentObject type. | ||
static func register() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that register
could be also removed from this interface.
/// } | ||
/// | ||
/// public var text: ControlProperty<String> { | ||
/// let source: Observable<String> = self.delegate.observe(#selector(UISearchBarDelegate.searchBar(_:textDidChange:))) | ||
/// ... | ||
/// } | ||
/// } | ||
public static func proxyForObject(_ object: AnyObject) -> Self { | ||
public static func proxyForObject(_ object: ParentObject) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make this proxy(for:)
/// public var delegate: DelegateProxy { | ||
/// return RxSearchBarDelegateProxy.proxyForObject(base) | ||
/// public var delegate: DelegateProxy<Base, UISearchBarDelegate> { | ||
/// return RxSearchBarDelegateProxy<Base>.proxyForObject(base) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we should make this RxSearchBarDelegateProxy<Base>.proxy(for: base)
assert(Self.currentDelegateFor(object) === proxy) | ||
assert(proxy.forwardToDelegate() === currentDelegate) | ||
let currentDelegate = self.currentDelegateFor(object) | ||
let delegateProxy = unsafeDowncast(proxy, to: self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want to understand, what did you force cast to here? To what type?
/// - parameter object: Object that has delegate property. | ||
/// - returns: Value of delegate property. | ||
static func currentDelegateFor(_ object: AnyObject) -> AnyObject? | ||
static func currentDelegateFor(_ object: ParentObject) -> Delegate? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should now be func currentDelegate(for object: ParentObject) -> Delegate?
/// Returns assigned proxy for object. | ||
/// | ||
/// - parameter object: Object that can have assigned delegate proxy. | ||
/// - returns: Assigned delegate proxy or `nil` if no delegate proxy is assigned. | ||
static func assignedProxyFor(_ object: AnyObject) -> AnyObject? | ||
|
||
static func assignedProxyFor(_ object: ParentObject) -> Delegate? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should now be static func assignedProxy(for object: ParentObject) -> Delegate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it, let's add it to Deprecated.swift
also.
static var factory: DelegateProxyFactory { get } | ||
|
||
/// Creates new proxy for target object. | ||
/// Should not call this function directory, use 'DelegateProxy.proxyForObject' | ||
static func createProxy(for object: AnyObject) -> AnyObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this now have the following signature?
static func createProxy(for object: ParentObject) -> Self
, UIScrollViewDelegate | ||
, DelegateProxyType { | ||
open class RxScrollViewDelegateProxy<P: UIScrollView> | ||
: DelegateProxy<P, UIScrollViewDelegate> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @tarunon ,
I'm sorry but I don't think I understand what did you want to say here 😅 ? Is there some problem?
static func createProxy(for object: AnyObject) -> AnyObject | ||
|
||
/// Store DelegateProxy subclass to factory. | ||
/// When make 'RxXXXDelegateProxy' subclass, call 'RxXXXDelegateProxySubclass.register()' 1 time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can use Rx*DelegateProxy
or some other wildcard instead of 'RxXXXDelegateProxy
. Seems a bit nicer 😄 .
I think the same would apply to other parts of docs.
rename `assignedProxyFor(_:)` -> `assignedProxy(for:)` rename `currentDelegateFor(_:)` -> `currentDelegate(for:)` rename `proxyForObject(_:)` -> `proxy(for:)`
…feature/type_safe_delegateproxy
assert(Self.currentDelegateFor(object) === proxy) | ||
assert(proxy.forwardToDelegate() === currentDelegate) | ||
let currentDelegate = self.currentDelegate(for: object) | ||
let delegateProxy = unsafeDowncast(proxy, to: self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment thread was closed by github, but still problem is here.
This is to solve cast between RxTableViewDelegateProxy<UITableView>
and RxScrollViewDelegateProxy<UIScrollView>
.
But I think it's better that quit using it.
Remove ParentObject
type generics parameter from RxScrollViewDelegateProxy
, then we don't need to use unsafeDowncast
anymore.
…feature/type_safe_delegateproxy
Hi @tarunon , are you still working on this? I think that we first need to figure out how does |
Hello @kzaher , I'm still considering about what is the best way. Let's consider 3 cases when we use
We declare
In master repo, I suggest avoid using |
Hi @tarunon , we definitely have to do this properly. Swift compiler doesn't support covariance as far as I can tell. So I'm assuming I think extension mechanism will also need to be modified a bit since it relies on |
I think so too, and it is better that implement |
…y subclasses subclass.
@tarunon do you want me to take a look now? |
@kzaher Looks like test is going now, but maybe it will pass. |
Hi @kzaher , I checked your refactoring. I like it, |
Hello there,
This proposal is first implementation of #1287, added generics parameter on DelegateProxy.
Motivation
I wish avoid to use force casting when implement DelegateProxy subclasses.
Every DelegateProxy subclass had same force casting implementation, I have thought can move these force casting into DelegateProxy core implementation, and use Generics parameter instead.
Changes
DelegateProxy
DelegateProxy
have 2 generics parameter thatParentObject
andDelegate
.DelegateProxyBase
for protocol extension conformance.DelegateProxyType
DelegateProxyBase
.DelegateProxyFactory
static func sharedFactory(for proxyType: DelegateProxy.Type) -> DelegateProxyFactory
because generics class cannot define static stored property.DelegateProxySubclass.prepareForDelegateProxy
instead ofDelegateProxyFactory.extended
.