Expressible literals allow you to initialize types by making use of literals. There are multiple protocols available in the Swift standard library and chances are big that you’ve already been using one of those.
An example is the ExpressibleByStringLiteral
allowing us to initialize a String
using surrounding double quotes instead of using the String(init:)
method. We can all benefit from the fact that Swift is built through these protocols by adopting the protocols in our own defined types.
What is a literal?
A literal is a notation for representing a fixed value such as integers, strings, and booleans. Literals in Swift are made possible by several available protocols. Standard types conform to these protocols and allow us to initialize values as follows:
var integer = 0 // ExpressibleByIntegerLiteral
var string = "Hello!" // ExpressibleByStringLiteral
var array = [0, 1, 2] // ExpressibleByArrayLiteral
var dictionary = ["Key": "Value"] // ExpressibleByDictionaryLiteral
var boolean = true // ExpressibleByBooleanLiteral
Those are the most commonly used but a few more exist. It’s worth exploring and while you do, you might run into a special protocol called ExpressibleByNilLiteral
.
The ExpressibleByNilLiteral
is used to make it possible to initialize optionals using nil
:
var optional: String? = nil // ExpressibleByNilLiteral
This protocol is used for optionals only and should not be used for custom types. Basically, just ignore this one.
Adding literal support to custom types
In most cases, you’ll be fine by making use of the default integrated adoption of the protocols. However, it could be that you want to add custom literal support to existing types or to custom types defined in your project.
To explain how this works we’ll use the example of adding string literal support to URLs. This allows us to initialize a URL directly from a string.
To do this, we need to adopt the ExpressibleByStringLiteral
protocol for the URL
type:
extension URL: ExpressibleByStringLiteral {
public init(stringLiteral value: StaticString) {
self.init(string: "\(value)")!
}
}
It’s worth pointing out the limitations here as we can’t create a failable or throwing initializer. This requires us to force unwrap the created URL which might not always be what you want. If so, you can always fall back to using the URL(string:)
initializer directly.
If it makes sense to force unwrap in your case you can start using the literal as follows:
let url: URL = "https://www.avanderlee.com"
This can simplify the creation of URLs, especially if you’re sure that the URL is valid. Note that we’re making use of a StaticString
type so that dynamic strings aren’t supported. Dynamic strings would add extra complexity like URL encoding parameters. You could support this but it’s not the focus of this blog post.
Different types of strings
When you add support for string literals you should consider adopting the ExpressibleByUnicodeScalarLiteral
for Unicode scalar support and ExpressibleByExtendedGraphemeClusterLiteral
for supporting extended grapheme clusters. An extended grapheme cluster is a group of one or more Unicode scalar values that become a single visible character.
If you want to support all three cases at once you can consider adding the following extensions:
extension ExpressibleByUnicodeScalarLiteral where Self: ExpressibleByStringLiteral, Self.StringLiteralType == String {
public init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
}
extension ExpressibleByExtendedGraphemeClusterLiteral where Self: ExpressibleByStringLiteral, Self.StringLiteralType == String {
public init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
}
Useful examples of using literals in custom types
To inspire you how you can make use of literals in your custom types I’ll show you a few examples that you might find useful.
Initializing dates directly from a string
When you’re often using the same date format you can initialize your dates from a String
directly:
extension Date: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
self = formatter.date(from: value)!
}
}
let date: Date = "09-12-2020"
print(date) // Prints: 2020-12-09 00:00:00 +0000
Initializing a custom cache from a dictionary
If you defined your own custom caching store you might want to initialize it directly from a dictionary:
final class Cache<T> {
private var store: [String: T]
init(objects: [String: T]) {
self.store = objects
}
func cache(_ object: T, forKey key: String) {
store[key] = object
}
func object(for key: String) -> T? {
return store[key]
}
}
extension Cache: ExpressibleByDictionaryLiteral {
convenience init(dictionaryLiteral elements: (String, T)...) {
self.init(objects: [String: T](uniqueKeysWithValues: elements))
}
}
let cache: Cache<URL> = ["SwiftLee": URL(string: "https://www.avanderlee.com")!]
print(cache.object(for: "SwiftLee")!) // Prints: https://www.avanderlee.com
Initializing custom auto-layout priorities
When you’re writing auto-layout in code you might need to set a custom layout priority:
extension UILayoutPriority: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self.init(rawValue: Float(value))
}
}
let superview = UIView()
let view = UIView()
let constraint = view.leadingAnchor.constraint(equalTo: superview.leadingAnchor)
constraint.priority = 200 // We can now set the priority directly
Note that we can’t make use of the ExpressibleByFloatLiteral
protocol as the input value will always be seen as an integer. Therefore, we need to make use of the ExpressibleByIntegerLiteral
protocol.
Conclusion
That was it! Hopefully, you’ve been inspired by what you can do with literals in Swift. You might have your own custom types that go great together with the available literal protocols.
If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!