I recently started writing an app in Swift, and one of the first things I needed to do was develop some infrastructure for implementing table view data sources. My goals were to decouple the data source implementation from the table view controller, reduce the boilerplate required to implement a typical data source, and to maximize type safety (since Swift is all about that type safety).
The main challenge was building something that would bridge to Objective-C so that it could be used with
UITableView. Since code that uses Swift-specific features like generics doesn’t bridge to Objective-C, achieving my goal of maximizing type safety turned out to be a non-trivial task. Read on to learn about my solution to this problem.
Since table views use data that is organized in sections, the first thing to do is to implement a
Section is implemented as a struct because it is a value type that contains no mutable state. Read Andy Matuschak’s article on structs and value types for more information on this topic.
Section is a generic type, for the same reason why
Dictionary in Swift are generic types — to make them type safe by restricting their elements to a single type that is known at compile-time.
Next up is implementing the data source itself. From the methods defined in the
UITableViewDataSource protocol, we understand that the table view data source has two primary responsibilites:
- Tell the table view how much data it will display, in terms of sections and rows
- Create and configure instances of
UITableViewCellto display data
Since I wanted to allow for the possibility of also implementing a collection view data source at some point, I first defined a general data source for data organized in sections:
This struct isn’t very useful at the moment, but the idea is that in the future, I may want to implement additional functionality that is common to anything that uses sections.
TableViewDataSource is implemented as follows:
- Since the data source needs to know the reuse identifier of the cell to be able to create an instance of it or dequeue it from the reuse pool, the
ReusableCellprotocol is defined so that a cell class conforming to it can define its own reuse identifier.
TableViewDataSourceis parametrized over
T, the type of element that each section contains, and
U, the type of a
UITableViewCellsubclass that conforms to
CellConfiguratoris the type of a closure that configures a cell of type
Uusing a section item of type
The next step is to implement the
UITableViewDataSource methods. Easy, right? Not exactly.
TableViewDataSource is a struct and also a generic type, which means that it does not bridge to Objective-C. This means that any methods declared inside it — like implementations of
UITableViewDataSource methods — also do not bridge to Objective-C. This is obviously a problem, since this needs to bridge in order to be able to use it as the
dataSource of a
The solution to this problem is to strip away the bits that make it incompatible. In other words, the object implementing
UITableViewDataSource needs to be, well… an object, not a struct, and it can’t be a generic type. In order to do this without losing type safety, we need to implement a way to transform instances of Swift-only types like
TableViewDataSource into instances of types that can be bridged to Objective-C.
First up is
SectionObjCis a class that is very similar to the generic
Sectiontype, except that it gets rid of the need for a type argument by using
[AnyObject], which can safely be bridged to an
@objcattribute is used tell the compiler that this class should be bridged to Objective-C.
- The type parameter
Sectionhas the new constraint that it must conform to
AnyObject, since Objective-C doesn’t support structs and enums.
toObjC()method is defined on
Sectionto transform it into an instance of
TableViewDataSource needs a similar treatment:
The changes here are essentially the same as the changes made to
Section. A new class is defined in order to facilitate bridging by removing type parameters (using
AnyObject instead), and a
toObjC() function is defined on
TableViewDataSource to transform it into an instance of the Objective-C compatible
You might ask, doesn’t removing the type parameters from the *ObjC classes also kill the type safety? In this case, the answer is no, because
TableViewDataSourceObjC are implementation details.
TableViewDataSourceObjC is only exposed as “some object that conforms to
UITableViewDataSource" so that it can be assigned as the
UITableView. In order to create an instance of
TableViewDataSourceObjC, the API consumer must first create an instance of
TableViewDataSource, which is completely type-safe.
UITableViewDataSource methods can now be trivially implemented on
And we’re done! Here’s what basic usage looks like:
This may not be a perfect solution, but it ended up satisfying all of my goals, so I’m pretty happy with it. This also isn’t intended to cover all of the possible use cases for a table view — think of it as a case study on how common tasks in iOS development can be made much simpler and less error-prone by reimagining them using all of the power that Swift and static typing give us.
The full Xcode project is available on GitHub.