Example of RxSwift implementing a method to replace delegate

  • 2020-06-12 10:46:36
  • OfStack

The target

I recently wrote a project about a case where I needed to add an rx subscription to a control I wrote.

Currently there is one agent:


//  The proxy method gets the results 
@objc public protocol ZZPhotoPickerControllerDelegate : NSObjectProtocol {
 @objc optional func photoPickerController(_ photoPickerController: ZZPhotoPickerController, didSelect assets: [Any])
}

You need to write an extension that implements the following approach


photoPickerController.rx.assetsSelected.subscribe(onNext: { assets in
 // do something
}

Train of thought

At first I was completely confused. Then it occurred to Rx to write an extension of UICollectionViewDelegate:


collectionView.rx.itemSelected.subscribe(onNext: { indexPath in
 // do something
}

It's the same thing as what I need.

So look at the itemSelected source code:


/// Reactive wrapper for `delegate` message `collectionView(_:didSelectItemAtIndexPath:)`.
 public var itemSelected: ControlEvent<IndexPath> {
  let source = delegate.methodInvoked(#selector(UICollectionViewDelegate.collectionView(_:didSelectItemAt:)))
   .map { a in
    return try castOrThrow(IndexPath.self, a[1])
   }
  
  return ControlEvent(events: source)
 }

souce is 1 Observable, produced by ES31en.ES32en. What is delegate delegate? Why is there an methodInvoked method? So let's go ahead and click.


extension Reactive where Base: UIScrollView {
   /// ... This part of the code is omitted 

  /// Reactive wrapper for `delegate`.
  ///
  /// For more information take a look at `DelegateProxyType` protocol documentation.
  public var delegate: DelegateProxy<UIScrollView, UIScrollViewDelegate> {
   return RxScrollViewDelegateProxy.proxy(for: base)
  }
  
  /// ... We don't need to look at the rest of the code for now 
}

You can see that delegate is 1 DelegateProxy < UIScrollView, UIScrollViewDelegate > A type, which is literally an agent of an agent. Then you can see that rx here is extended from UIScrollView and UICollectionView is inherited from UIScrollView, so you can see that delegate here is also inherited. You can also see the RxScrollViewDelegateProxy thing, and you can imagine that if we were to copy it, we would have written a proxy class that looked like this. Check it out first:


open class RxScrollViewDelegateProxy
 : DelegateProxy<UIScrollView, UIScrollViewDelegate>
 , DelegateProxyType 
 , UIScrollViewDelegate {

 /// Typed parent object.
 public weak private(set) var scrollView: UIScrollView?

 /// - parameter scrollView: Parent object for delegate proxy.
 public init(scrollView: ParentObject) {
  self.scrollView = scrollView
  super.init(parentObject: scrollView, delegateProxy: RxScrollViewDelegateProxy.self)
 }

 // Register known implementations
 public static func registerKnownImplementations() {
  self.register { RxScrollViewDelegateProxy(scrollView: $0) }
  self.register { RxTableViewDelegateProxy(tableView: $0) }
  self.register { RxCollectionViewDelegateProxy(collectionView: $0) }
  self.register { RxTextViewDelegateProxy(textView: $0) }
 }

 /// ... It doesn't matter what it feels like in the back 
}

You can see it's actually 1 DelegateProxy < UIScrollView, UIScrollViewDelegate > , and comply with DelegateProxyType and UIScrollViewDelegate protocols, you can feel that it is a link between rx and delegate. There are 1 instance variable scrollView, 1 init method, and 1 registerKnownImplementations static method.

At present, I have a vague idea in my mind: we will first create a bond delegateProxy object, then create an instance of delegateProxy in the extension of the target class. Finally, we will use the methodInvoked of delegateProxy to intercept the target method in delegate in our assetsSelected event stream, and generate subscribed Observable to return to controlEvent, so that the link can get through.

To start the

First create 1 RxPhotoPickerControllerDelegateProxy


class RxPhotoPickerControllerDelegateProxy: DelegateProxy<ZZPhotoPickerController, ZZPhotoPickerControllerDelegate>, DelegateProxyType, ZZPhotoPickerControllerDelegate {
 
 /// Typed parent object.
 public weak private(set) var photoPickerController: ZZPhotoPickerController?
 
 /// - parameter scrollView: Parent object for delegate proxy.
 public init(photoPickerController: ParentObject) {
  self.photoPickerController = photoPickerController
  super.init(parentObject: photoPickerController, delegateProxy: RxPhotoPickerControllerDelegateProxy.self)
 }
 
 static func registerKnownImplementations() {
  self.register { RxPhotoPickerControllerDelegateProxy(photoPickerController: $0) }
 }
 
 //  After writing the above, the editor will prompt you for the implementation 1 The next two methods, 1 One is to get, 1 These are Settings, so it's easy to understand what to implement in a method. 
 static func currentDelegate(for object: ZZPhotoPickerController) -> ZZPhotoPickerControllerDelegate? {
  return object.zzDelegate
 }
 
 static func setCurrentDelegate(_ delegate: ZZPhotoPickerControllerDelegate?, to object: ZZPhotoPickerController) {
  object.zzDelegate = delegate
 }
 
}

Then write an instance of delegateProxy for the target rx extension:


extension Reactive where Base: ZZPhotoPickerController {
 
 public var zzDelegate: DelegateProxy<ZZPhotoPickerController, ZZPhotoPickerControllerDelegate> {
  return RxPhotoPickerControllerDelegateProxy.proxy(for: base)
 }
 
}

Finally, write our assetsSelected:


extension Reactive where Base: ZZPhotoPickerController {
 
 var assetsSelected: ControlEvent<[Any]> {
  let source: Observable<[Any]> = self.zzDelegate.methodInvoked(#selector(ZZPhotoPickerControllerDelegate.photoPickerController(_:didSelect:))).map { a in
   return a[1] as! [Any]
  }
  return ControlEvent.init(events: source)
 }
 
}

Note that there is a method castOrThrow, and this method castOrThrow is not open, it is an internal method, if it is written as an error. It can be concluded that this method is only a type inference, so it can be written simply.

complete

Then you can happily subscribe to assetsSelected.


vc.rx.assetsSelected.subscribe(onNext: { (assets) in
    // do something
   }).disposed(by: self.disposeBag)

conclusion


Related articles: