Mercurial
Mercurial

Reputation: 2165

Extending target-action methods with RxSwift

class A{

     func addTarget(target: Any, action: Selector)
}

Let's say I don't have class A source available (framework). How would I extend this class reactively to emit Rx events through an Observable?

I can create a class that just forwards the events through a PublishSubject, but in that case I wouldn't be creating a Reactive extension but doing it through a proxy class.

let a = A()
let del = CustomClassThatAddsItselfAsATarget(a)
del.event.subscribe( ...

instead of

let a = A()
a.rx.event.subscribe( ...

Upvotes: 1

Views: 944

Answers (2)

Daniel T.
Daniel T.

Reputation: 33967

This was a fun exploration. I patterned the below off of how UIControl is set up in RxCocoa.

In answer to your followup questions:

Which object will add itself as a target(addTarget method) to base?

You have to create a class that is designed to do that. I named it ATarget.

Who retains that object?

You make the object conform to Disposable and then it will be retained until disposed of.

extension Reactive where Base: A {

    var event: Observable<A> {
        return Observable.create { [weak a = self.base] observer in
            guard let a = a else {
                observer.on(.completed)
                return Disposables.create()
            }

            let aTarget = ATarget(a: a, callback: { a in
                observer.on(.next(a))
            })

            return Disposables.create(with: aTarget.dispose)
        }
        .takeUntil(deallocated)
    }
}

class ATarget: NSObject, Disposable {

    typealias Callback = (A) -> Void
    let selector: Selector = #selector(ATarget.eventHandler)
    weak var a: A?
    var callback: Callback?

    init(a: A, callback: @escaping Callback) {
        self.a = a
        self.callback = callback
        super.init()
        a.addTarget(target: self, action: selector)
    }

    @objc func eventHandler() {
        if let callback = self.callback, let a = self.a {
            callback(a)
        }
    }

    func dispose() {
        self.a?.removeTarget(target: self)
        self.callback = nil
    }

}

Upvotes: 1

Maxim Volgin
Maxim Volgin

Reputation: 4077

RxCocoa and most other RxSwift-based frameworks take the following approach -

public extension Reactive where Base: TheOriginalClass {

See CKRecord+Rx or Bundle+Rx for an example of implementation.

Things get more complicated if you need to provide a proxy delegate, but this is out of scope of this question.

Upvotes: 0

Related Questions