ReactiveCocoa / ReactiveSwift

Streams of values over time

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Reactive extension for protocol not accessible?

nkristek opened this issue · comments

So lets say I define a protocol AProtocol with the implementation A:

protocol AProtocol {
    func doWork(completion handler: @escaping (Error?) -> ())
}

class A: AProtocol {
    func doWork(completion handler: @escaping (Error?) -> ()) {
        DispatchQueue.main.async {
            handler(nil)
        }
    }
}

Now I want to make a reactive extension for that method, but since I want to inject the AProtocol I define it for the protocol:

extension Reactive where Base: AProtocol {
    func doWork() -> SignalProducer<Void, Error> {
        SignalProducer { [base] observer, lifetime in
            base.doWork { error in
                if let error = error {
                    observer.send(error: error)
                } else {
                    observer.send(value: ())
                    observer.sendCompleted()
                }
            }
        }
    }
}

How do I access this reactive extension without knowing the specific type A?
I can't extend the AProtocol with ReactiveExtensionsProvider, only the specific implementation:

// works
extension A: ReactiveExtensionsProvider {
    
}
func testA(a: A) {
    lifetime += a.reactive.doWork().start()
}

// doesn't work, build error: Extension of protocol 'AProtocol' cannot have an inheritance clause
extension AProtocol: ReactiveExtensionsProvider {
    
}
func testAProtocol(a: AProtocol) {
    lifetime += a.reactive.doWork().start()
}

Because the initializer for Reactive<Base> is not accessible, as a workaround I've defined my own reactive proxy:

struct ReactiveA {
    let base: AProtocol
    
    fileprivate init(_ base: AProtocol) {
        self.base = base
    }
}

extension AProtocol {
    var reactive: ReactiveA {
        ReactiveA(self)
    }
}

extension ReactiveA {
    // the same as the extension on `Reactive where Base: AProtocol`
}

It's a pretty bad workaround and essentially is equivalent of copy-pasting the same code. At some point you might need other useful things declared on native Reactive, but accessing it will be a real pain. Been there! Done that!

Instead, use generic function in a plain reactive extension, see this thread for details:

protocol AProtocol: ReactiveExtensionsProvider {
    
}
extension Reactive {
    func doWork() -> SignalProducer<Void, Error> where Base: AProtocol {
        
    }
}

At one point I was also curios why Reactive is not available for public initialization. My understanding, if I remember everything correctly, because you can't do parametrized extensions (see here and here) it would be useless as you won't be able to customize it anyway.

So, the most effective approach is to do as above. The only drawback is when you need to define properties with generic constraints – you can't, again, because of the language limitation. So, you'll have to stick with functions.

Thanks for the tip with the generic constraints.
Only one thing is still problematic, I can't define it as this:

protocol AProtocol: ReactiveExtensionsProvider {
}

Since I don't own the AProtocol implementation (in another dependency) and would need to do something like:

extension AProtocol: ReactiveExtensionsProvider {
    
}

which unfortunately doesn't work.
Nevertheless this seems to be a shortcoming regarding Swift and thus I will close this PR.
Thanks again for your help!