Let's Get Meta: Swift Metatypes and Cell Reuse16 Nov 2015
For the past few weeks, I’ve been running a small project called Public Extension. Each weekday1, I come up with a handy Swift extension (to the Standard Library, UIKit, etc.) and tweet it out for everyone to follow along!
One particular extension has been on the back of my mind for a few days now. Public Extension #9 (initial draft below)
The goal was to make class registration for
UITableViewCells safer by avoiding a
Stringly-based API that requires code to be updated in multiple places, when reuse identifiers change. To tackle this, I took the following approach:
- Define a
Reusableprotocol that requires classes to have a static
- Wrap the traditional
registerClass(_:forCellReuseIdentifier:)to accept a
Reusable.Typemetatype, which hides the previously-needed
- In my initial extension, I omitted the wrapper for
dequeueReusableCellWithIdentifier(_:forIndexPath:), but we’ll discuss these soon!
Note: This approach only works in code. Setting reuse identifiers in Storyboards requires you to manually enter a
Before diving in and refining the above approach, let’s take a step back and define what
CustomCell.self mean in the snippet above. They’re making use of Swift’s Metatype Type.
Swift Metatype Type
In The Swift Programming Language book, the metatype type is defined as follows:
“A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.”
This is exactly what we need. Since our
Resuable protocol defines
reuseIdentifier as a static member, we can access it from the metatype of any class that conforms to it! To access a type as a value, we can use a postfix
self expression (i.e.
.self). For example, in our
CustomCell class above,
CustomCell.Type not an instance of
Refining the Approach
Now that we’ve gone over metatypes, let’s make our approach to safe cell reuse better! We’ll keep the same
Reusable protocol but redefine our
UITableView extension as follows:
While we could have used Swift’s error handling, cell misconfiguration is tricky to recover from. If misconfigured, it might be misleading to return a plain
UITableViewCell. So, it’s probably better to lean towards a
fatalError to catch these issues during development.
Note that the return types for the
dequeueReusable overloads are non-optional. This is really handy because we no longer have to deal with awkward casts in
tableView(_:cellForRowAtIndexPath:). Here is how the call site might look:
We can apply this same pattern to
UICollectionView as well:
A Note on Cell Subclassing
One of my friends, Hans, brought up a potential gotcha with regard to subclassing cells that implement
static keyword in Swift translates to
class final2, meaning that the property is attached to a class but cannot be overridden. However, when defining
Reusable, we have to use the
static keyword (even though we have the explicit
class requirement on the protocol) because the class keyword is only allowed within
class definitions. To allow for subclassing of cells that conform to
Reusable, we can define
reuseIdentifier as a computed3
So, there we have it! We’ve abstracted away a
Stringly-based API for cell reuse in table and collection views. Now, if we ever need to change reuse identifiers, all we have to do is update the the cell definition. This benefits from code locality. I had also thought about an
enum-based approach, but this would move reuse identifiers farther away from cell definitions, as
enums can’t be defined across multiple files. However, the
enum approach has one advantage, which is guaranteeing unique reuse identifiers.
Hope this pattern is useful! If you have any feedback or suggestions, I’d love to hear from you!
1: Took a break between November 4th through 16th for job interviews :)
2: In the case of classes
class properties aren’t supported yet.