Search code examples
swiftgenericsrx-swift

Using Swift generics in a RxSwift getter function - various problems


Here's the function:

func registerFor<Element>(relayId id: String) -> Driver<Element>? {
    guard let relay = relays[id] as? BehaviorRelay<Element> else { return nil }
    return relay.asObservable()
        .distinctUntilChanged { a, b in
            return a != b
        }.flatMapLatest { value in
            return Observable.create { observer in
                observer.on(.next(value))
                return Disposables.create()
            }
        }.asDriver(onErrorJustReturn: Element())
}

The distinctUntilChanged line throws the following error:

Contextual closure type '(Element) throws -> _' expects 1 argument, 
but 2 were used in closure body

The asDriver line throws the following error (of course):

Non-nominal type 'Element' does not support explicit initialization

Context: I have a class that ideally has a collection of BehaviorRelays of various types (Strings, Ints, etc). Element stands in generically for these types, but that creates two problems:

  1. distinctUntilChanged insists of having a closure (eg: if this method returned Driver<String> it would be content simply to use distinctUntilChanged() but the generic Element makes it complain about missing a closure);
  2. onErrorJustReturn requires a concrete value, but Element is generic.

The following "workaround" might work but I suspect there are better solutions

protocol Inii {
    init()
}

func registerFor(relayId id: String, def: Inii.Type) -> Driver<Inii>? {
    return relays[id]?.asObservable()
        .distinctUntilChanged { _, _ in
            return true
        }.flatMapLatest { value in
            return Observable.create { observer in
                observer.on(.next(value))
                return Disposables.create()
            }
        }.asDriver(onErrorJustReturn: def.init())
}

Although I'm still unsure what to put in the distinctUntilChanged closure.


Appendix A

I believe that the following is what is required if one is implementing the distinctUntilChanged closure for a non-generic type:

.distinctUntilChanged { previousValue, currentValue in
    return previousValue == currentValue
}

However, when used with the generic Element the following error is still thrown:

Contextual closure type '(Inii) throws -> _' expects 1 argument, 
but 2 were used in closure body

Appendix B

Here's another alternative with a slightly different problem:

protocol Inii {
    init()
}

var relay = BehaviorRelay<String>(value: "")

func registerFor<Element>(def: Element.Type) -> Driver<Element> where Element: Inii {
    return relay.asObservable()
        .distinctUntilChanged { previousValue, currentValue in
            return previousValue == currentValue
        }.flatMapLatest { value in
            return Observable.create { observer in
                observer.on(.next(value))
                return Disposables.create()
            }
        }.asDriver(onErrorJustReturn: def.init())
}

Error in this case being:

Member 'next' in 'Event<_>' produces result of type 'Event<Element>', 
but context expects 'Event<_>'

at the observer.on line


Solution

  • You can use distinctUntilChanged() without a closure as long as Element conforms to Equatable:

    protocol EmptyInit {
        init()
    }
    
    func registerFor<Element>(relayId id: String) -> Driver<Element>? where Element: Equatable, Element: EmptyInit {
        guard let relay = relays[id] as? BehaviorRelay<Element> else { return nil }
        return relay.asObservable()
            .distinctUntilChanged()
            .flatMapLatest { value in
                return Observable.create { observer in
                    observer.on(.next(value))
                    return Disposables.create()
                }
            }.asDriver(onErrorJustReturn: Element())
    }