I have my UITableViewCells
in separate xibs, because I reuse them a lot throughout my app. I now have a little list of cells to register in every UITableViewController
, like this:
// viewdidload
tableView.register(UINib(nibName: "KeyTextCell", bundle: nil), forCellReuseIdentifier: "KeyTextCell")
tableView.register(UINib(nibName: "KeyValueCell", bundle: nil), forCellReuseIdentifier: "KeyValueCell")
tableView.register(UINib(nibName: "KeyEditFieldCell", bundle: nil), forCellReuseIdentifier: "KeyEditFieldCell")
tableView.register(UINib(nibName: "KeyTwoEditFieldCell", bundle: nil), forCellReuseIdentifier: "KeyTwoEditFieldCell")
tableView.register(UINib(nibName: "KeyThreeEditFieldCell", bundle: nil), forCellReuseIdentifier: "KeyThreeEditFieldCell")
tableView.register(UINib(nibName: "KeySwitchCell", bundle: nil), forCellReuseIdentifier: "KeySwitchCell")
tableView.register(UINib(nibName: "TextCell", bundle: nil), forCellReuseIdentifier: "TextCell")
//cellForRow...
let cell = tableView.dequeueReusableCell(withIdentifier: "KeyValueCell") as! KeyValueCell
I have to add any new cell to this list first before I'm able to use the cell in that controller, which is annoying and easy to forget. Aside from that, it's ugly to have a list of cells to register in my viewDidLoad and it gets quite long in some places.
So I want to make this better. I was thinking of making a small function in a UITableViewController
extension that registers all cells I have. That would mean only one list to maintain, but also registering cells I potentially don't need in that specific controller. Does registering unnecessary cells give a (big) performance hit? Are there any other downsides? What's best practice?
There are two ways this will cost you - at viewDidLoad time when you register all the nibs, and in tableView(_:cellForRowAt:)
where you create / recycle your cells.
1. In viewDidLoad
Let's look at the line in detail:
tableView.register(UINib(nibName: "KeyTextCell", bundle: nil), forCellReuseIdentifier: "KeyTextCell")
There are two things going on here:
UINib
objectCreating a UINib
is pretty fast - it doesn't even check that the nib exists until you ask for it's contents. You can test that with this code:
let nib = UINib(nibName: "Doesn't exist", bundle: nil)
print(nib) // Prints out something like `<UINib: 0x60000001fba0>`
// This line will load the nib and unpack it into the view.
// It's obviously going to fail :)
let view = nib.instantiate(withOwner: nil, options: [:])
Also, reading the documentation for UINib tells you it loads the contents from disk once and caches it so no matter how many times you create a cell from a nib, you're only going to access the first time you need to.
This is going to be pretty much inserting an item into a dictionary - given how small your dictionary is going to be (7 items ish?) this will be fast. So I wouldn't worry about registering times (though, you should test this, see below).
2. In tableView(_:cellFrorRowAt:)
What about creating/ dequeuing a cell in tableView(_:cellFrorRowAt:)
? We can ignore dequeuing an existing cell - that's got nothing to do with loading the cell from a nib - that cell is already loaded and in memory - so all we care about would be when you ask for a cell and there isn't one ready to be recycled. Given the average table view height, that's going to happen 10-15 times for the first screen full of rows. After that, they're going to start recycling.
If we assume that tableViews are storing their nibs in something like a dictionary, this will be very fast - dictionary access is O(1) so it doesn't matter how many cells you register. If, however, it's stored as something like an array or a set then access becomes slower as you add more items, but you're only adding 7 items so it's still pretty fast :)
3. Testing all this
This is mostly speculation - the best way to find out the impact is to test it on a real device (choose the worst device your customers are likely to be using!)
The dirty way to test this is to just register 10000 nibs (write a loop in viewDidLoad) and see how your app performs. If it's fine with 10,000 then you're going to be OK with 7 ;)
The best way to test this is to profile it with Instruments - run your code in Instruments by pressing CMD-i (instead of CMD+r) and instruments will open. Choose "Time Profiler" and press the red record button in the top left - this will run your app but will record all the method calls etc as they happen. Let the app startup and settle down (the activity graph will go mental as your app starts, and then should drop to nothing while your app just sits there waiting for you to interact with it). Then you can navigate to a view controller with all these nibs registered and you can see how much activity is needed to open the view controller, and how much activity is needed to scroll etc. Try this with only one nib registered, and try it with 10000 registered to see the difference.
You can also see the function calls broken down by time at the bottom half of the screen - if you click around in there you can find some useful information about which calls are taking a long time.
Hope this is helpful - there's a load of more useful things that Instruments can do - it's worth having a click around and seeing what's available. There's documentation here: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/index.html
The other thing to consider would be memory - if you're registering all these nibs in all your table views then you might be using memory you don't need, but again I would profile this in instruments and see what the hit is. My gut feeling is that you don't need to worry, UINib only loads what it needs, and I bet UINib will clear it's cache when a low memory warning arrives :)
tl;dr I wouldn't worry about 7 nibs if your app scrolls smoothly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With