Articles, podcasts and news about Swift development, by John Sundell.

Organizing default argument values

Published on 20 Jun 2020

Using default arguments can be a great way to make a given type or function easier to use, and can let us achieve a certain level of consistency between call sites that don’t require any form of customization.

As an example, let’s say that we’re working on an ImageLoader, which is initialized with a ImageLoadingSettings value that determines how each image loader instance should behave. Since we don’t want to require such a settings value to be explicitly passed, we’ll default to creating a new instance of it as a convenience — like this:

class ImageLoader {
    private let settings: ImageLoadingSettings

    init(settings: ImageLoadingSettings = .init()) {
        self.settings = settings
    }
    
    ...
}

For structs, such as the ImageLoadingSettings type that we used above, we can also make use of default parameter values — which will automatically get translated into default initializer arguments by the compiler:

struct ImageLoadingSettings {
    var baseURL = URL(string: "https://my-cdn.com")!
    var timeoutInterval: TimeInterval = 120
    var defaultFormat = ImageFormat.png
}

Note that the memberwise initializers that the compiler automatically synthesizes are only visible internally within the same module that a given type is defined in. To learn more, check out “When can a struct’s memberwise initializer be used?”.

Another option, which also works for public APIs that should be accessible outside of the module that they’re defined in, is to use static properties or factory methods. For example, here’s how we could extend ImageLoadingSettings with a static default property that returns a new instance populated with the default settings values:

extension ImageLoadingSettings {
    static var `default`: ImageLoadingSettings {
        ImageLoadingSettings(
            baseURL: URL(string: "https://my-cdn.com")!,
            timeoutInterval: 120,
            defaultFormat: .png
        )
    }
}

With the above in place, we can now use this really neat dot-syntax to define our default argument from before:

class ImageLoader {
    private let settings: ImageLoadingSettings

    init(settings: ImageLoadingSettings = .default) {
        self.settings = settings
    }
    
    ...
}

While either of the above two approaches tend to work great for types that we’ve created ourselves — they can be a bit tricky to use when it comes to types that ship as part of the standard library.

For example, let’s now say that we’re looking to provide a convenient way to initialize an ImageTransformer with an array of default ImageEffect values. One way to model those values would be to use a slight twist on the static property approach that we took above, and add such a property to the Array type itself, using a same-type constrained extension:

extension Array where Element == ImageEffect {
    static var defaultEffects: Self {
        [.increaseContrast, .normalizeColors, .cropToSquare]
    }
}

class ImageTransformer {
    private let effects: [ImageEffect]

    // We can again use this really nice dot-syntax to specify
    // our default argument value:
    init(effects: [ImageEffect] = .defaultEffects) {
        self.effects = effects
    }
    
    ...
}

However, while the above initializer looks really nice, it’s arguably a bit strange to have the Array type be aware of what our image transformer’s defaults are — even if we make that extension private (which works as long as our ImageTransformer remains internal in terms of access level).

So an alternative way to accomplish the exact same thing would be to instead add that static property to our ImageTransformer type itself — because it turns out that we can reference a type’s static properties directly within its initializer parameter list, like this:

class ImageTransformer {
    private let effects: [ImageEffect]

    init(effects: [ImageEffect] = defaultEffects) {
        self.effects = effects
    }
    
    ...
}

extension ImageTransformer {
    static var defaultEffects: [ImageEffect] {
        [.increaseContrast, .normalizeColors, .cropToSquare]
    }
}

Of course, we could’ve also defined the above defaultEffects property within our main type declaration, rather than using an extension — but the above approach sort of gives us a nice separation between our main type declaration and its default values.