The Danger of String Literals and Stringly Typed Code

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Object literals are very useful and they often make your code easier to read and understand. String literals are a bit different, though. It is true that a string literal is the easiest solution to create a String object. It is straightforward and everyone understands what is going on. But there is a price you pay every time you use a string literal. Did you know that?

Common Examples

Let me show you a few common examples of string literals as they are used in Swift and Cocoa development.

let image = UIImage(named: "icon_button_add")
let storyboard = UIStoryboard(name: "Main", bundle: e.main)
let text = NSLocalizedString("button_add", comment: "Add Button")
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let identifier = segue.identifier else { return }

    switch identifier {
    case "Profiles":
        guard let destination = segue.destination as? ProfilesViewController else {
            return
        }

        ...
    default: break
    }
}

I bet these examples look familiar. In each example, we use one or more string literals to carry out a task. What is wrong with these examples?

While there is nothing inherently wrong with the use of string literals, there is room for improvement. String literals very often lead to stringly typed code, a play on strongly typed.

What Is Stringly Typed Code

When strings, and very often string literals, are used instead of a more appropriate type, we speak of stringly typed code. Some forms of stringly typing are obvious while others are more subtle. This is an obvious example of stringly typed code.

func fetchWeatherData(latitude: String, longitude: String, completionHandler: @escaping (JSON) -> Void) {
    ...
}

When working with coordinates, there is a better solution than using strings for the location's latitude and longitude. This is a better implementation that adds safety and clarity.

func fetchWeatherData(latitude: Double, longitude: Double, completionHandler: @escaping (JSON) -> Void) {

}

We can take it one step further by asking the Core Location framework for help.

func fetchWeatherData(for location: CLLocation, completionHandler: @escaping (JSON) -> Void) {

}

Another obvious example is the use of a String object instead of a URL object. There is a reason why Apple pushes APIs that accept a URL object instead of a path, that is, a String object.

Subtle Forms of Stringly Typed Code

There are many subtle forms of stringly typed code that are often overlooked. These forms are easy to avoid with a tiny amount of refactoring. Let me show you how I get rid of the string literals in the examples I showed you earlier.

Images

Whenever I use an API that accepts a String object, I am on my guard. Why? It is an opportunity for a string literal to make its way into my codebase. This is a very common example.

let image = UIImage(named: "icon_button_add")

This is how the majority of developers create UIImage instances. The UIImage class explicitly asks for a String instance. What is wrong with this and how can we improve this example?

Let me ask you a few questions first. What happens if the asset's name changes? And what happens if you misspell the asset's name? Because init(named:) returns an optional UIImage instance, the compiler won't warn you if there isn't an asset with the name you passed to init(named:). How can we solve this problem?

There are several solutions. I usually create an extension for UIImage that looks something like this.

import UIKit

extension UIImage {

    enum Icons {

        enum Button {

            static let add = UIImage(named: "icon_button_add")!

        }

    }

}

Does this look overly complex to you? I can understand that it may look too complex of a solution for the problem. Bear with me, though. This solution makes clever use of enumerations to create namespaces. It is a pattern I use very, very often and it is one of the patterns I swear by.

The resulting API looks and feels elegant and intuitive. Don't you agree? This is what it looks like at the call site.

let image = UIImage.Icons.Button.add

Notice that I use an exclamation mark in the UIImage extension to forced unwrap the result of init(named:). As I write elsewhere, I rarely use Swift's exclamation mark. One exception is the loading of assets and resources from the application's bundle. Such an operation should never fail. If it fails, then I have other things to worry about and the application crashing due to a runtime error is the least of my worries.

static let add = UIImage(named: "icon_button_add")!

You may be wondering if instantiating a UIImage instance in an extension is a wise thing to do. Don't worry, though. Static properties like this are instantiated lazily. That is obviously a good thing since we want to load an asset the moment it is needed. This improves performance and reduces memory consumption.

If you are worried about memory consumption, then you can turn the static constant property into a static computed property.

import UIKit

extension UIImage {

    enum Icons {

        enum Button {

            static var add: UIImage {
                UIImage(named: "icon_button_add")!
            }

        }

    }

}

Storyboards

I use a similar solution for loading storyboards and instantiating the view controllers they contain. I create an extension for UIStoryboard and define a static constant property.

import UIKit

extension UIStoryboard {

    static let main = UIStoryboard(name: "Main", bundle: .main)

}

It is important to realize that the storyboard is kept in memory for the lifetime of the application once it is instantiated. If that isn't what you want, then you can turn the static constant property into a static computed property.

import UIKit

extension UIStoryboard {

    static var main: UIStoryboard {
        UIStoryboard(name: "Main", bundle: .main)
    }

}

A new instance of the storyboard is created every time the main computed property is accessed. You can use a similar strategy for the view controllers of a storyboard. Take a look at the following example and notice the use of the exclamation mark.

import UIKit

extension UIStoryboard {

    // MARK: - Storyboards

    static var main: UIStoryboard {
        UIStoryboard(name: "Main", bundle: .main)
    }

    static var onboarding: UIStoryboard {
        UIStoryboard(name: "Onboarding", bundle: .main)
    }

    // MARK: - View Controllers

    static var onboardingViewController: OnboardingViewController {
        UIStoryboard.onboarding.instantiateInitialViewController() as! OnboardingViewController
    }

}

An added benefit is that the pattern makes the instantiation of the OnboardingViewController class more concise at the call site.

let onboardingViewController = UIStoryboard.onboardingViewController

This is much better than the alternative, especially if you need to instantiate the view controller in multiple locations.

let storyboard = UIStoryboard(name: "Onboarding", bundle: .main)

guard let onboardingViewController = storyboard.instantiateInitialViewController() as? OnboardingViewController else {
    ...
}

Localization

You can use a similar strategy for localization. It is unfortunate that Apple doesn't add support for this in Xcode. I would be nice to take advantage of autocompletion when localizing an application.

Segues

The last example that is worth mentioning is the use of segues. The approach I am currently using is simple yet effective. I define a private enum, Segue, in the view controller. For every segue of the view controller, I define a static constant property. This means I no longer need to deal with string literals or constant stored properties.

class ViewController: UIViewController {

    // MARK: - Segues

    private enum Segue {

        static let profiles = "Profiles"

    }

    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let identifier = segue.identifier else { return }

        switch identifier {
        case Segue.profiles:
            guard let destination = segue.destination as? ViewController else {
                return
            }

            ...
            break
        default: break
        }
    }

}

What Do We Gain

The Xcode theme I use highlights string literals in bright red. It shows me an overview of the string literals I am using and, when I see too much red, it is time for some refactoring.

This is the Xcode theme I use.

But you may be wondering why this is necessary. I hope I have convinced you that the use of string literals can break your application without the compiler warning you about it. That is the inherent risk of string literals and stringly typed code.

The solutions I listed come with a few key benefits. You can take advantage of Xcode's autocompletion and, more important, the compiler helps you find common bugs that are the result of typos. Refactoring also becomes easier and less prone to errors. You don't need to rely on search and replace to update the project.

Automation

Even if you use the solutions I listed, you still need to be careful. For example, you need to make sure that the identifier of a segue matches the one you use in your codebase and that is where automation can prove very useful.

There are several libraries and frameworks that automatically generate the extensions I showed you. The code generation usually takes place at compile time. This means that you eliminate typos, benefit from code completion, and, when you make a change, it is automatically propagated.

I don't use any of these solutions, but I have to admit that it is tempting if you care about safety and consistency. To be honest, I believe it is Xcode's responsibility to handle such tasks. Many popular IDEs do.

Loading an image from an asset catalog, for example, should be easier and less prone to errors. It shouldn't involve string literals if the image is included in the application bundle. It could look something like this.

Assets(bundle: .main).iconButtonAdd
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy