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) {
}
}