Home > front end >  Same protocol extension for different classes (but same baseclass)?
Same protocol extension for different classes (but same baseclass)?

Time:11-22

I'm having a hard time explaining the issue, so I'll try my best.

I have this code:

protocol MyListener
{
    func setupMyViewControllers()   //code I would like to share with any UIViewController that conforms to this protocol

    func receiveUpdateA()           //each protocol-conforming class should have its own implementation
    func receiveUpdateB()           //each protocol-conforming class should have its own implementation
}

//my classes hierarchy
class A: UIViewController {}
class B: UIViewController {}
class b: B {}

So, instead of writing duplicate code like the following:

extension A: MyListener
{
    func setupMyViewControllers()
    {
        NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateA(_:)), name: "receiveUpdateA", object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateB(_:)), name: "receiveUpdateB", object: nil)
    }

    //rest of protocol implementations
    func receiveUpdateA() {/*...*/}
    func receiveUpdateB() {/*...*/}
}

extension B: MyListener
{
    func setupMyViewControllers()
    {
        NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateA(_:)), name: "receiveUpdateA", object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateB(_:)), name: "receiveUpdateB", object: nil)
    }

    //rest of protocol implementations
    func receiveUpdateA() {/*...*/}
    func receiveUpdateB() {/*...*/}
}

I tried the following (with no success):

extension MyListener where Self: UIViewController
{
    func setupMyViewControllers()
    {
        NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateA(_:)), name: "receiveUpdateA", object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(receiveUpdateB(_:)), name: "receiveUpdateB", object: nil)
    }
}

extension A: MyListener
{
    //rest of protocol implementations
    func receiveUpdateA() {/*...*/}
    func receiveUpdateB() {/*...*/}
}

extension B: MyListener
{
    //rest of protocol implementations
    func receiveUpdateA() {/*...*/}
    func receiveUpdateB() {/*...*/}
}

I thought that this way I could share the setup code, but still be able to extend the remaining "receiveUpdate" functions in separate extensions. But I get stuck in a loop of errors. I get "Cannot use 'receiveUpdateA' as a selector because protocol 'MyListener' is not exposed to Objective-C. However, exposing it to obj-c then gives me the error that protocol extensions can't have obj-c functions.

Is there any way of achieving this without extending the entirety of UIViewController to MyListener? (I'm not even sure that would work. It seems like the core issue is the use of Selectors)

CodePudding user response:

Don't know what Swift/XCode Version you are using right now, but the following appears to work just fine with Swift 5.7.1.

@objc protocol MyListener {

    func receiveUpdateA()
    func receiveUpdateB()
}

extension MyListener {
    
    func setupMyViewControllers() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(receiveUpdateA),
            name: NSNotification.Name("receiveUpdateA"),
            object: nil
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(receiveUpdateB),
            name: NSNotification.Name("receiveUpdateB"),
            object: nil
        )
    }
    
}


//my classes hierarchy
class A: UIViewController {}
class B: UIViewController {}

extension A: MyListener {

    //rest of protocol implementations
    @objc func receiveUpdateA() {/*...*/}
    @objc func receiveUpdateB() {/*...*/}
}

extension B: MyListener {

    //rest of protocol implementations
    @objc func receiveUpdateA() {/*...*/}
    @objc func receiveUpdateB() {/*...*/}
}

Even though I'd say that you shouldn't consider DRY as a religious rule, here's an alternative implementation using Apple's own equivalent to Rx:

import UIKit
import Combine

protocol ObservesNotificiationCenter {
    
}

extension ObservesNotificiationCenter {
    
    func onNotification(withName name: String) -> AnyPublisher<Notification, Never> {
        return NotificationCenter.default.publisher(
            for: NSNotification.Name(name)
        ).eraseToAnyPublisher()
    }
    
}

@objc protocol ReceivesUpdates {
    func receiveUpdateA(notificiation: Notification)
    func receiveUpdateB(notificiation: Notification)
}

extension ReceivesUpdates where Self: ObservesNotificiationCenter {
    
    func setupNotifications() -> Set<AnyCancellable> {
        var cancellables: Set<AnyCancellable> = []
        onNotification(withName: "receiveUpdateA")
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: receiveUpdateA(notificiation:))
            .store(in: &cancellables)
        onNotification(withName: "receiveUpdateB")
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: receiveUpdateB(notificiation:))
            .store(in: &cancellables)
        return cancellables
    }
    
}


class ViewController: UIViewController, ObservesNotificiationCenter, ReceivesUpdates {

    private var cancellables: Set<AnyCancellable>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        cancellables = setupNotifications()
        
    }
    
    func receiveUpdateA(notificiation: Notification) {
        
    }
    
    func receiveUpdateB(notificiation: Notification) {
        
    }

}
  • Related