Last active
April 21, 2021 17:50
-
-
Save vitonzhangtt/84335dafb162e91eab5d1a1628cff8ac to your computer and use it in GitHub Desktop.
RxSwift Internals Serials: DelegateProxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// RxSwift/RxCocoa: 5.1.1 | |
/// DelegateProxyType.swift | |
public protocol DelegateProxyType: class { | |
associatedtype ParentObject: AnyObject | |
associatedtype Delegate | |
/// ... ... | |
} | |
// default implementations | |
extension DelegateProxyType { | |
/// Unique identifier for delegate | |
public static var identifier: UnsafeRawPointer { | |
/// Generate `delegateIdentifer` from `Delegate` metaType. | |
let delegateIdentifier = ObjectIdentifier(Delegate.self) | |
let integerIdentifier = Int(bitPattern: delegateIdentifier) | |
return UnsafeRawPointer(bitPattern: integerIdentifier)! | |
} | |
} | |
extension DelegateProxyType { | |
/// Store DelegateProxy subclass to factory. | |
/// When make 'Rx*DelegateProxy' subclass, call 'Rx*DelegateProxySubclass.register(for:_)' 1 time, or use it in DelegateProxyFactory | |
/// 'Rx*DelegateProxy' can have one subclass implementation per concrete ParentObject type. | |
/// Should call it from concrete DelegateProxy type, not generic. | |
public static func register<Parent>(make: @escaping (Parent) -> Self) { | |
self.factory.extend(make: make) | |
} | |
/// Creates new proxy for target object. | |
/// Should not call this function directory, use 'DelegateProxy.proxy(for:)' | |
public static func createProxy(for object: AnyObject) -> Self { | |
return castOrFatalError(factory.createProxy(for: object)) | |
} | |
/// Returns existing proxy for object or installs new instance of delegate proxy. | |
/// | |
/// - parameter object: Target object on which to install delegate proxy. | |
/// - returns: Installed instance of delegate proxy. | |
/// | |
/// | |
/// extension Reactive where Base: UISearchBar { | |
/// | |
/// public var delegate: DelegateProxy<UISearchBar, UISearchBarDelegate> { | |
/// return RxSearchBarDelegateProxy.proxy(for: base) | |
/// } | |
/// | |
/// public var text: ControlProperty<String> { | |
/// let source: Observable<String> = self.delegate.observe(#selector(UISearchBarDelegate.searchBar(_:textDidChange:))) | |
/// ... | |
/// } | |
/// } | |
public static func proxy(for object: ParentObject) -> Self { | |
MainScheduler.ensureRunningOnMainThread() | |
/// Get the DelegateProxy instance for `object`. | |
/// In fact, the DelegateProxy instance is an associated object of `object`. | |
/// see: `assigndProxy(for:)` method. | |
let maybeProxy = self.assignedProxy(for: object) | |
let proxy: AnyObject | |
if let existingProxy = maybeProxy { | |
proxy = existingProxy | |
} | |
else { | |
/// self.createProxy(for:) | |
/// --> `factory` property | |
/// --> DelegateProxyFactory.sharedFactory(for:) // return a `DelegateProxyFactory` instance | |
/// --> DelegateProxyFactory.createProxy(for:) | |
/// --> | |
proxy = castOrFatalError(self.createProxy(for: object)) | |
self.assignProxy(proxy, toObject: object) | |
assert(self.assignedProxy(for: object) === proxy) | |
} | |
let currentDelegate = self._currentDelegate(for: object) | |
let delegateProxy: Self = castOrFatalError(proxy) | |
if currentDelegate !== delegateProxy { | |
delegateProxy._setForwardToDelegate(currentDelegate, retainDelegate: false) | |
assert(delegateProxy._forwardToDelegate() === currentDelegate) | |
self._setCurrentDelegate(proxy, to: object) | |
assert(self._currentDelegate(for: object) === proxy) | |
assert(delegateProxy._forwardToDelegate() === currentDelegate) | |
} | |
/// Return the `delegateProxy` instance. | |
return delegateProxy | |
} | |
} | |
// private extensions | |
extension DelegateProxyType { | |
/// Return a factory instance for each DelegateProxyType type. | |
private static var factory: DelegateProxyFactory { | |
/// Find or create a factory for each DelegateProxyType. | |
/// `self` in Type Method refers to a type (rather than to an instance). | |
return DelegateProxyFactory.sharedFactory(for: self) | |
} | |
private static func assignedProxy(for object: ParentObject) -> AnyObject? { | |
let maybeDelegate = objc_getAssociatedObject(object, self.identifier) | |
return castOptionalOrFatalError(maybeDelegate) | |
} | |
private static func assignProxy(_ proxy: AnyObject, toObject object: ParentObject) { | |
objc_setAssociatedObject(object, self.identifier, proxy, .OBJC_ASSOCIATION_RETAIN) | |
} | |
} | |
private class DelegateProxyFactory { | |
/// The key is the `Delegate metaType`, The value is the DelegateProxyFactory instance. | |
/// Each Delegate Type has a DelegateProxyFactory instance. | |
private static var _sharedFactories: [UnsafeRawPointer: DelegateProxyFactory] = [:] | |
fileprivate static func sharedFactory<DelegateProxy: DelegateProxyType>(for proxyType: DelegateProxy.Type) -> DelegateProxyFactory { | |
MainScheduler.ensureRunningOnMainThread() | |
/// Refer to: default implementation of `identifier` property. | |
/// Generate `identifer` from `Delegate` metaType. | |
let identifier = DelegateProxy.identifier | |
if let factory = _sharedFactories[identifier] { | |
return factory | |
} | |
/// Create `factory` for DelegateProxyType type, `proxyType` is the metaType of `DelegateProxy`. | |
let factory = DelegateProxyFactory(for: proxyType) | |
/// Record the mapping from `Delegate` metaType to factory instance. | |
_sharedFactories[identifier] = factory | |
/// Call DelegateProxy subclass's registerKnownImplementations() method. | |
/// In subclass's registerKnownImplementations(), calling `register(make:)`, | |
/// Then register(make:) calls DelegateProxyFactory.extend(make:). | |
DelegateProxy.registerKnownImplementations() | |
return factory | |
} | |
private var _factories: [ObjectIdentifier: ((AnyObject) -> AnyObject)] | |
private var _delegateProxyType: Any.Type | |
private var _identifier: UnsafeRawPointer | |
private init<DelegateProxy: DelegateProxyType>(for proxyType: DelegateProxy.Type) { | |
self._factories = [:] | |
self._delegateProxyType = proxyType /// metaType of DelegateProxyType subclass. | |
self._identifier = proxyType.identifier /// Ref to: line 13. | |
} | |
/// | |
fileprivate func extend<DelegateProxy: DelegateProxyType, ParentObject>(make: @escaping (ParentObject) -> DelegateProxy) { | |
MainScheduler.ensureRunningOnMainThread() | |
precondition(self._identifier == DelegateProxy.identifier, "Delegate proxy has inconsistent identifier") | |
guard self._factories[ObjectIdentifier(ParentObject.self)] == nil else { | |
rxFatalError("The factory of \(ParentObject.self) is duplicated. DelegateProxy is not allowed of duplicated base object type.") | |
} | |
/// The key is the `metaType` of ParentObject. | |
/// The `factory` create a `DelegateProxy` instance from a ParentObject instance. | |
/// Install factory for ParentObject, each `ParentObject` type can only have a factory. | |
/// | |
/// For example: | |
/// If the `Delegate` is UIScrollViewDelegate, there is only a DelegateProxyFactory instance for | |
/// this `Delegate` metaType. But the DelegateProxyFactory instance has different factory for different | |
/// ParentObject type. The `ParentObject` maybe any type in the set: [UIScrollView, UITableView, | |
/// UICollectionView, UITextView]. | |
/// | |
self._factories[ObjectIdentifier(ParentObject.self)] = { make(castOrFatalError($0)) } | |
} | |
fileprivate func createProxy(for object: AnyObject) -> AnyObject { | |
MainScheduler.ensureRunningOnMainThread() | |
var maybeMirror: Mirror? = Mirror(reflecting: object) | |
while let mirror = maybeMirror { | |
if let factory = self._factories[ObjectIdentifier(mirror.subjectType)] { | |
/// The type of `object` is `ParentObject`. | |
return factory(object) | |
} | |
maybeMirror = mirror.superclassMirror | |
} | |
rxFatalError("DelegateProxy has no factory of \(object). Implement DelegateProxy subclass for \(object) first.") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment