Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - How to use KVO's 'observe' method on protocol

Tags:

xcode

ios

swift

I need an observable property on certain view controllers in my application, I'm trying to standardize this through a protocol.

protocol MyProtocol: UIViewController {
    dynamic var observeMe: Bool { get set }
}

But when trying to observe the property like so:

let viewController = UIViewController()
if let observableViewController = viewController as? MyProtocol {
    observableViewController.observe(\.observeMe, options: [.old, .new], changeHandler: { object, change in
        // Do something
    })
}

Xcode throws the compiler error:

"Member 'observe' cannot be used on value of protocol type 'MyProtocol'; use a generic constraint instead"

like image 519
Alejandro Cotilla Avatar asked Nov 23 '25 11:11

Alejandro Cotilla


1 Answers

You can declare a function and apply the generic constraint to it.

In Swift 5.7 you can simply do:

func observeViewController(_ vc: some MyProtocol) {
  vc.observe(\.observeMe, options: [.old, .new], changeHandler: { object, change in
  })
}

Then you can call it:

let viewController = UIViewController()
if let observableViewController = viewController as? MyProtocol {
  observeViewController(viewController)
}

Prior to Swift 5.7, some MyProtocol is not supported and you need to use type erasure:

class AnyMyProtocol: MyProtocol & UIViewController {
  var observeMe: Bool

  private var base: MyProtocol

  init(_ base: MyProtocol) {
    self.base = base
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

func observeViewController(_ vc: MyProtocol) {
  vc.observe(\.observeMe, options: [.old, .new], changeHandler: { object, change in
  })
}

And call it like so:

let viewController = UIViewController()
observeViewController(vc: AnyMyProtocol(base: observableViewController))

By the way, you will receive a warning reading:

Passing reference to non-'@objc dynamic' property 'observeMe' to KVO method 'observe(_:options:changeHandler:)' may lead to unexpected behavior or runtime trap

But in your case you can't declare it as @objc because an @objc protocol can't inherit from a non-protocol type.

like image 120
byohay Avatar answered Nov 26 '25 01:11

byohay