Using Custom Strongly Typed Values For Better Coding in Swift

Updated on March 8th, 2022

⏱ Reading Time: 5 mins

Not so long ago I discussed in this post about how to define enums with custom raw types in Swift. Creating enums with cases that can be either of a primitive data type, or a custom one as presented in that previous post, helps achieving a simple, yet quite important goal; to use values in our code that are strongly typed, instead of plain raw values. Doing so adds a lot to writing safer code, and avoid ambiguities and potential problems throughout the implementation process of an app.

Enum cases as custom strongly typed values

Say, for instance, that we have a variety of color values expressed as hexadecimal strings to use in various places around an app. Along with that, there’s also a hypothetical method that converts a hex string to an actual color supposedly named color(fromHex:). The simplest thing we could do in order to get an actual color would be to supply a hex string value as argument to that method:

There are two disadvantages here; the first is the unavoidable need to remember the various hex color values. Since that’s rarely feasible, we’ll be resorting to another list containing the full range of available color values, ending up with a significant unnecessary hassle.

The second negative point, which is more important in programming level, is that’s pretty easy to make typing errors, or accidentally pass invalid values as arguments.

Defining an enum with a raw type would lead to counterattack these two issues, as:

  • there would be no need to keep all possible color values in our memory, as Xcode would be suggesting them in autocompletion,
  • we would be providing the enum’s cases as arguments to color(fromHex:), so no more potential errors.

For example:

Note: To find out more about how to create enums with custom raw type as above, please have a read to this tutorial.

Even better, we could change the signature of the color(fromHex:) method, so it accepts an AvailableColors value, instead of the raw value of each case; it would be even simpler then to provide it with color values:

As you see, defining strongly typed values can be proved really helpful in the coding process; it ensures that no unwanted mistakes will take place, but besides safety, it also increases clarity, readability and implementation speed in the long term.

But, are enums the only available option in order to define custom strongly typed values?

Enums are not always the best solution

Enums are great candidates for use in cases like the scenario presented above, but they have a limitation; they cannot be extended with new cases.

Trying to do the following, for example, will fail with the error shown as a comment:

You might think that this is not a big deal, as we write all possible cases in the enum’s body at once in the majority of circumstances. However, what if the enum is defined in a separate module, such as a Swift package or a different project in a workspace, and we don’t have the possibility to introduce new cases within our own project?

It turns out that enums are out of the table in such situations, but we still know that it has to be possible somehow. After all, there are built-in types that allow us to define custom strongly typed values and use them wherever necessary in place of raw values with primitive data types.

Take as example the NotificationCenter and notifications; we almost never use string values to specify a notification name. We always define names by extending the Notification.Name type and declaring static properties, before we post or observe for notifications in the NotificationCenter:

Obviously the Notification.Name type does not reside in our project, but it becomes available through the built-in Foundation framework. Yet, we can extend it, and declare new notification names. Undoubtedly it would be great if we could do the same with custom types, and guess what? We can, and the technique to manage that is described right in the next part.

Properties in structs as strongly typed values

As with enums, structs can also be used as a similar tool in order to define properties that we’ll use as custom strongly typed values. But in contrast to enums, structs are extendible, just like the previously shown example regarding the Notification.Name type.

There is a quite straightforward technique to apply here, which resembles enums a lot; we deal with raw values as well, but instead of cases we have static properties. The most important part includes the conformance to two particular protocols; RawRepresentable and Hashable. Conforming to latter is not mandatory if we are not planning to include values of our custom type to dictionaries or sets, so we can occasionally omit it. Here’s an example:

The conformance to RawRepresentable protocol brings along two requirements; to declare a rawValue property with the data type that raw values are going to have, and a specific initializer:

The above default initializer will construct optional objects. It’s up to us to implement an additional initializer that will be creating non-optional objects, and without an argument label. If necessary, we may define as many parameters as needed:

Besides the two requirements of the RawRepresentable protocol and the additional initializer, such a custom type can contain everything else that’s considered to be useful in the implementation process. For instance, you can see right next that the demo Hex type also contains two read-only computed properties that indicate if the raw string value starts with a hashtag, and if it contains valid characters or not:

Having the above custom type in place, we can extend it anywhere it might be needed and declare static properties that we’ll be using as custom strongly typed values:

Moreover, we can now create functions, methods and other types that either accept or return Hex objects instead of raw values. Suppose that we have the following method that converts a hex color value to RGB:

Similarly to enum cases, providing a Hex value as argument leaves no room for misunderstandings about what the given value is, and eliminates any chance to make mistakes:

Of course, the raw value is always there in case we need it; it’s accessible through the rawValue property:

Overview

If there’s one thing that I have learnt all these years of programming, then that is that errors and mistakes are unavoidable. Since Swift gives us the tools to write safer code, why not to take advantage of that and minimize the risk of problems, even unwanted ones? On top of safety, and as I already mentioned at some point above, using custom defined strongly typed values makes the code more readable and comprehensive. I’m quite confident that enums had been in your arsenal already, but how about structs? I hope that this post has showed new coding paths to you, and ways for optimizations.

Thank you for reading! ????

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.