Five ways to improve code with type aliases

Published by donnywals on

Swift grants developers the ability to shadow certain types with an alternative name using the typealias keyword. We can use this feature to create tuples and closures that look like types, or we can use them to provide alternate names to existing objects.

Today, I will show you five different applications of typealias that can dramatically improve your code when applied correctly. Let’s dive right in, shall we?

1. Improving readability with type aliases

Perhaps this is the most obvious yet also somewhat underused way to use a typealias. When you have a type in your code that is very long or deeply nested, you could end up with rather long type declarations, the following code snippet is an example of a rather long type name:

UICollectionViewDiffableDataSource<CardSection.Diffable, Card.Diffable>

I have made the previous declaration nice and long on purpose, but it’s not far from code that I have actually written. You can make the type declaration above much shorter with a simple typealias:

typealias CardDataSource = UICollectionViewDiffableDataSource<CardSection.Diffable, Card.Diffable>

After defining this in your code, the Swift compiler will now know that whenever you write CardDataSource, you really mean UICollectionViewDiffableDataSource<CardSection.Diffable, Card.Diffable> instead. This is really neat because instead of writing the same long, hard to read type declaration over and over, you can instead use the shorter, more descriptive type alias you created.

The same idea can be applied to functions that take a completion closure:

func fetchCards(_ completion: @escaping (Result<[CardSection], Error>) -> Void) {
  // implementation
}

While the code above isn’t horrible, we can make it a bit more readable with a typealias:

typealias FetchCardsHandler = (Result<[CardSection], Error>) -> Void

func fetchCards(_ completion: @escaping FetchCardsHandler) {
  // implementation
}

Reading this alternative function that uses a typealias is easier on the eyes since there is much less information to unpack in the function declaration.

Before we move on to the second tip, please note that you shouldn’t go and create type aliases for every long type or closure in your code. Type aliases improve readability but in some cases, they also hurt discoverability. In the case of the diffable data source typealias, this isn’t much of a problem. In the closure case, this can be a bit more problematic. As with any technique, always consider your options carefully.

2. Communicate intention

A well-placed type alias can help developers determine what valid inputs for a certain method are, or what the inputs are used for. A good example is the TimeInterval alias that’s used on iOS. Whenever you see that a certain method or property has the type TimeInterval you immediately get a sense of what kind of inputs you can use, and what will be done with them. Internally, however, a TimeInterval is nothing more than a typealias.

Another example of a type alias that communicates an intention to the user is a special ID type that’s used inside of a struct:

typealias ID = Int

struct Card {
  let id: ID
  // more properties and code
}

struct CardSection {
  let id: ID
  // more properties and code
}

In the preceding snippet, you can see that the Card and CardSection objects both have an id property who’s type is ID. While this doesn’t look that impressive, it’s pretty neat. We can now enforce that Card and CardSection use the same type as their ID by ensuring that they both use the same aliased type. In this case, both identifiers have to be integers. This can provide a lot of meaning to other developers reading this code and when used carefully can really make a big difference in your code.

3. Combining protocols

This is possibly one of the more interesting and obscure ways to use a typealias that I actually use regularly myself. I even wrote an article about it a while ago so I won’t go in-depth too much. In Swift, you can use a typealias to create a placeholder for something that conforms to one or more protocols. For example, you can do the following:

typealias CommonDataSource = UICollectionViewDataSource & UITableViewDataSource

The result of the code above is that any object that claims to be a CommonDataSource must conform to both UICollectionViewDataSource and UITableViewDataSource. Since this example is cool but not very useful, let me give you another brief example:

public typealias Codable = Decodable & Encodable

Does the above look familiar? The idea of it probably does! If you declare a type to be Codable, it’s both Encodable and Decodable. The way this is defined in Swift is through a typealias that looks exactly like the one in this code snippet.

4. Naming tuples

A language feature in Swift that’s not used or needed a lot is the ability to define tuples. A tuple is a set of values that are grouped together. For example:

func divide(_ lhs: Int, _ rhs: Int) -> (result: Int, remainder: Int) {
  return (lhs/rhs, lhs % rhs)
}

The code above returns a pair of values as a result. Since this in itself doesn’t always mean much, we can use a typealias to give a name to this tuple to make its intent clear:

typealias DivisionResultAndRemainder = (result: Int, remainder: Int)

func divide(_ lhs: Int, _ rhs: Int) -> DivisionResultAndRemainder {
  return (lhs/rhs, lhs % rhs)
}

Users of this code now understand what the returned tuple is, and how they can use it.

An interesting side effect of using tuples and type aliases like this is that you could even swap out the DivisionResultAndRemainder tuple for a struct with the same properties without making any changes to callers of divide(_:_:) which is pretty neat.

5. Defining a protocol’s associated type

The fifth and last tip is a more advanced one that is used a lot in the Swift source code. Sometimes a protocol has an associated type:

protocol Sequence {
  associatedtype Element
  associatedtype Iterator: IteratorProtocol where Iterator.Element == Element

  // more code...
}

Note:
If you’re not familiar with associated types or if you want to learn more, I recommend that you read the following posts on this topic:

The Swift compiler is usually pretty good at inferring what the concrete types of these associated types should be. However, this can be rather confusing, especially if the associated type is used in more than one place. The most common use of type aliases in the Swift standard library is (by far) the explicit definition of associated types:

extension ClosedRange: Sequence where Bound: Strideable, Bound.Stride: SignedInteger {
  public typealias Element = Bound
  public typealias Iterator = IndexingIterator<ClosedRange<Bound>>
}

The code above is directly from the Swift standard library. It shows how ClosedRange conforms to Sequence under certain conditions, and in the extension body, the Element and Iterator associated types from Sequence are defined. What’s really interesting is that all Sequence functionality can now be assigned to ClosedRange through extensions on Sequence rather than explicitly defining them on ClosedRange.

This a great example of how protocol-oriented programming works in Swift, and it’s made possible by typealias! Pretty cool, right?

In Summary

In today’s quick tip you learned about five different use cases for the typealias keyword in Swift. Some of these use cases can significantly improve your code’s readability while others have a more minor impact. You even learned that typealias is used to power large parts of the Swift standard library and that without typealias, protocol-oriented programming probably wouldn’t look like it does today.

If you have more cool uses of typealias to improve your code, if you have questions or feedback, don’t hesitate to send me a Tweet.

Receive weekly updates about my posts

Categories: Quick Tip