Using Key-Value Observing in Swift to debug your app

Recently, I was looking into a bug where the UITabBar was inconsistently disappearing on specific pages. I tried different approaches but I couldn’t get where it got displayed and hidden. That’s where I thought about KVO.

Some background on my issue, I had a custom TabBarController inheriting UITabBarController which hides or shows its tabBar based on specific behavior. But then, for one specific journey, the tabBar was hidden when it shouldn’t be.

class TabBarController: UITabBarController {
  // custom code
}

On a specific controller, from couple breakpoints, the value isHidden was false (displayed) at the end of viewDidLoad but true (hidden) at the start of viewWillAppear. So I was cornered. Where does this value come from?

class MyViewController: AnotherViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    // self.tabBarController?.tabBar.isHidden is "false"
  }

  override func viewWillAppear() {
    // self.tabBarController?.tabBar.isHidden is "true"
    super.viewDidAppear()
  }
}

Since tabBar property is accessible through getter only, I can’t just override it with didSet to know whenever its own isHidden property got changed.

So why not using KVO?

Key-Value Observing

If you’re not too familiar with this concept, Key-Value Observing (or KVO) is an Observer Pattern that notifies object about changes to the properties of other objects. Exactly what I was looking for.

Inherited from Objective-C and Cocoa, it was one (and still is) of the common ways to communicate changes along with notification pattern, delegate pattern, or callbacks.

So I applied it to my own problem.

class TabBarController: UITabBarController {
  // custom code

  var observation: NSKeyValueObservation?

  convenience init() {
      self.init(nibName: nil, bundle: nil)
      
      observation = observe(
          \.tabBar.isHidden,
          options: [.old, .new]
      ) { object, change in
          print("TabBar isHidden changed from : \(change.oldValue!), updated to: \(change.newValue!)")
      }
  }
}

Note that I’m using a key path to get access to the property I want to observer here \.tabBar.isHidden.

From a new breakpoint set next to the print(..) I can finally know where the value is overwritten and fix my problem. It was actually a bit more tricky since some views used direct access to the tabBar via self.tabBarController?.tabBar.isHidden and others hides it only when pushing through self.hidesBottomBarWhenPushed.

Seems great, so why KVO is not more popular?

Key-Value Observing is a very powerful tool, but it comes with some very strict rules. The simplest one: for any added observer, you need to make sure you’ll remove it too, or you’ll encounter may crashes during deallocation.

It’s always good to find alternative ways to find the source of a problem and be creative in the way to find that solution. In this aspect, KVO is a great tool for introspection of an element. I rarely use it on production but in that case described above, it’s very reliable which makes it perfect.


Where to go from here

© 2023 Benoit Pasquier. All Rights Reserved
Author's picture

Benoit Pasquier

Software Engineer πŸ‡«πŸ‡·, writing about career development, mobile engineering and self-improvement

ShopBack πŸ’°

Singapore πŸ‡ΈπŸ‡¬