Exploring Hidden Constraints in the Public Interface of UITabBar

Apple as the owner of system frameworks is often considered as an example to follow when it comes to API design of iOS related libraries and applications. Knowledge of idioms and expectations established by Cocoa Touch helps iOS developers take full advantage of iOS frameworks. That’s why I decided to look deeply into the details of the implementation of UITabBar class when I encountered interesting note in its documentation:

Do not modify a tab bar that is managed by a tab bar controller (an instance of the UITabBarController class). If you do so, the system raises an exception. Modify a managed tab bar, or its items, only through the tab bar controller API.

You can directly modify a tab bar, using methods of this class, if the tab bar is not managed by a tab bar controller.

Even though tab bar controller exposes UITabBar instance as public property, it doesn’t allow for direct modifications of its tab bar. That’s interesting. I immediately came up with some ways I could implement that. Nevertheless, I wanted to know how the people behind UIKit implemented that specific kind of interface.

Diving into Internals of UITabBar

Public interface of UITabBar doesn’t include any hints regarding the way UITabBarController blocks the possibility to modify its instance of UITabBar. Because of that, I decided to look closely at UIKit runtime headers. Property locked, whose proof of existence can be found here, looked like a good point to start with.

@property (getter=isLocked, nonatomic) BOOL locked;

Its name strongly suggests that its purpose is to block something. I just needed to verify that it’s indeed used by UITabBarController to control access to its tab bar instance.

In order to do that, I decided to use good old lldb. I created sample project which I then built and launched using standard Xcode UI interface. Next, I paused the execution of the program by tapping ‘pause’ button in the debug area:

'Pause' button in Xcode console debug area

After that I set breakpoint on the setLocked: setter by entering the following command in the console text editor:

breakpoint set --selector '[UITabBar setLocked:]'

Succesful addition of the aforementioned breakpoint looks as follows:

Output in the debug console after successful setting of the breakpoint

Next, I allowed further execution of the program by tapping ‘resume’ button as presented below:

'Resume' button in Xcode console debug area

If the setLocked: property has anything in common with UITabBarController and its possibility to block modifications of the tab bar, it has to be called during the change of the selected tab bar item.

I decided to change the selected tab bar item with the hope that code execution would hit set breakpoint. It turned out that not only did it hit the breakpoint but it did it four times - that was promising.

The first two executions of the setLocked: selector were the result of execution of adjustButtonSelection method and they aren’t directly connected with UITabBarController.

Assembly code of `adjustButtonSelection` method

Because of that, I just went straight to the next calls.

They were initiated from within the setSelectedTabBarItem: method of the UITabBarController class.

Assembly code of `setSelectedTabBarItem:` method

The assembly looked promising. The execution of setLocked: took place first and was followed by the modification of the tab bar. Only then second execution of the aforementioned selector happened. In order to fully understand what happened I decided to check the value of parameters which were passed to both of these calls. To do so I used commands presented below: 1

po (BOOL)$rdx            //64 bit simulator
                         //or 
p (BOOL)*(int*)($esp+12) //32 bit simulator

As it turned out the former of these calls unlocks the tab bar - setLocked:NO - and the latter locks it - setLocked:YES. Pseudo code of the described case looks as follows:

tabBarItem.locked = false;
//Modify tab bar here
tabBarItem.locked = true;

It seems that locked property protects UITabBar from any modifications. Any change has to be preceded by unlocking the tab bar. Only after it has been unlocked can it be modified and locked again by the tab bar controller. Not a rocket science but still pretty interesting!

Looking for Similar Interfaces Within Cocoa Touch Framework

Since the behavior of the tab bar, when used together with a UITabBarController class, is pretty interesting, I thought that it would be worth to look for similar interfaces. As it turns out there are other classes in the Cocoa Touch framework that implement setLocked: and/or isLocked selector. All appearances of this kind of classes can be find by setting breakpoint on setLocked: property (without specifying class) and listing all existing breakpoints:

breakpoint set --selector 'setLocked:'
breakpoint list

In the case of my example project, output of these commands looks as follows (note that number of breakpoints depends on the frameworks imported within the project you are working on):

List of `setLocked:` selector breakpoints

The list presents two additional classes which implement setLocked: property - CAState and UINavigationBar classes.

I don’t want to dig into the details of the CAState as it is a private class (at least for now 😉) and its behavior shouldn’t have influence on API users.

What about the UINavigationBar class? Its public documentation states:

If the navigation bar was created by a navigation controller and is being managed by that object, you must not change the value of this property.

Navigation controllers act as the delegate for any navigation bars they create.

Aha! It looks that UINavigationBar behaves in a way really similar to the one known from UITabBar. In both of these cases the engineers had to face with similar problems and decided to use similar approaches.

Example of Possible Safer UITabBar Interface

Seeing some pattern applied twice by Apple engineers you may start wondering whether you should use it in your own code. In my opinion, the approach used by tab bar, initialized by tab bar controller, isn’t worth following. It results in mismatches between public interface of tab bar and its documentation. This, on the other hand, creates an opportunity for many programmer’s mistakes. That’s not cool. 😥

Despite the fact that we can’t change implementation of the UIKit, we can come up with our own interface for the tab bar. In fact, we will do that right now in order to prove that better approach is possible. Due to my personal preference and growing popularity of Swift, I decided to use it for the purpose of further discussion. 🤓

First things first. Let’s start with writing down some of the requirements regarding the solution we want to end up with:

  1. Only read access to the properties that are declared by UITabBar class itself. An attempt to modify that kind of property should lead to compilation errors.
  2. Read-write access to all properties of the UITabBar which are available in UIView class (UITabBar is a direct subclass of UIView). The access to view properties of an object that has visual representation on the screen is just convenient and should be as easy as possible. The example of UIBarButtonItem shows us that pretending that some view isn’t a view can be painful.

They look pretty straightforward. Let’s ship it! 😉

protocol UITabBarReadOnlyProtocol {
    var selectedItem: UITabBarItem? { get }
    var items: [UITabBarItem]? { get }
}

protocol UITabBarReadWriteProtocol {
    var selectedItem: UITabBarItem? { get set }
    var items: [UITabBarItem]? { get set }
}

Nothing fancy here. 2 Two introduced protocols will allow us to provide compiler with the information regarding the kind of actions a programmer can perform with tab bar properties. As the name suggests UITabBarReadOnlyProtocol limits possible interactions to reading, whereas UITabBarReadWriteProtocol allows for both, read and write access.

protocol UITabBarViewReadOnlyProtocol: UITabBarReadOnlyProtocol {
    var view: UIView { get }
}

extension UITabBarViewReadOnlyProtocol where Self: UIView {
    var view: UIView {
        get {
            return self
        }
    }
}

UITabBarViewReadOnlyProtocol is a result of some lacks of Swift type system when comparing it to the one known from Objective-C world. As currently Swift doesn’t allow for declarations which say something like: “I want to have a thing conforming to protocol X which is some kind of UIView” programmers has to use some workarounds. Additional protocol together with extension, which is limited to the objects which are some kind of UIView, seems to work just fine for that.

Pay attention that the user of UITabBarViewReadOnlyProtocol has to use .view accessor to access UIView instance. Not the most elegant solution but it works just fine.

extension UITabBar: UITabBarViewReadOnlyProtocol, UITabBarReadWriteProtocol {}

extension UITabBarController  {
    var safeTabBar: UITabBarViewReadOnlyProtocol {
        get {
            return self.tabBar
        }
    }
}

Once you have UITabBarViewReadOnlyProtocol the rest is simple. We inform compiler that UITabBar implements UITabBarViewReadOnlyProtocol and UITabBarReadWriteProtocol protocols. Additional property called safeTabBar makes tab bar available in the form of UITabBarViewReadOnlyProtocol to the users of UITabBarController.

In that way possible interactions with the tab bar instance are limited for the users of the public API of the tab bar controller to read only. Nevertheless, write access to the tab bar is still possible to the owner of UITabBarController class.

The whole code for proposed solution is available on gist. The example below presents created safe tab bar interface in actual use:

var tabBarController = UITabBarController()
//all properties of the UIView accessible 👯
tabBarController.safeTabBar.view.frame = CGRect(x: 0, y: 0, width: 100, height: 100) 
//compilation error 💀
tabBarController.safeTabBar.selectedItem = nil
//works like a charm! 👌                           
let selectedItem = tabBarController.safeTabBar.selectedItem

As we can see, the attempt to modify selectedItem property of the tab bar results in compilation error. This is exactly what we wanted to achieve. The new interface enforces constraints presented in Apple’s documentation and doesn’t allow its users to perform forbidden actions. That’s neat! 👊

Summary

iOS SDK has its years. It provides enormous set of frameworks and libraries which made it possible for developers to deliver over million of applications.

That being said, even such successful frameworks contain rough edges. Some of their public interfaces aren’t a great example to follow and the way they work creates possibility for programmers to introduce bugs into applications. UITabBar property of the UITabBarController is a good example of that. Its documentation is partly contrary to the headers of UITabBarController, what drastically decreases its easy of use.

Fortunately, today’s Objective-C and Swift languages are powerful enough to express interfaces that don’t contain this kind of quirks. We don’t have to always follow Apple as sometimes we simply can do better!


  1. It is possible to use po $arg1, po $arg2, ... to print values of parameters without worrying about the architecture details.
  2. Example code above doesn’t list all properties of the UITabBar for the sake of brevity.