A literal is a notation of fixed value in program. 3, Hello and true are literals in the code below:

let aNumber = 3
let aString = "Hello"
let aBool = true

Just like modern Objective-C, literal assignment for Array and Dictionaryis also supported in Swift:

let anArray = [1,2,3]
let aDictionary = ["key1": "value1", "key2": "value2"]

Swift does one step further on supporting literal. There is a set of interesting protocols Swift exposes to us. By conforming them, we could assign a literal value and change it to the type we want. It is a seamless workflow that we even won't notice the type creating. These protocols contain conversion of all primitive types. The most important ones would be:

  • ArrayLiteralConvertible
  • BooleanLiteralConvertible
  • DictionaryLiteralConvertible
  • FloatLiteralConvertible
  • NilLiteralConvertible
  • IntegerLiteralConvertible
  • StringLiteralConvertible

All literal convertible protocols define a typealias and the corresponding init method. Take BooleanLiteralConvertible as an example, which is:

protocol BooleanLiteralConvertible {
    typealias BooleanLiteralType

    /// Create an instance initialized to `value`.
    init(booleanLiteral value: BooleanLiteralType)
}

The BooleanLiteralType in the protocol is already defined in Swift standard library:

/// The default type for an otherwise-unconstrained boolean literal
typealias BooleanLiteralType = Bool

As a result, we could just implement an init method if we need this literal conversion. As an example, we can implement a customized Bool type as below:

enum MyBool: Int {
    case myTrue, myFalse
}

extension MyBool: BooleanLiteralConvertible {
    init(booleanLiteral value: Bool) {
        self = value ? myTrue : myFalse
    }
}

It allows us to use true and false in Swift's Bool to assign our customized MyBool type:

let myTrue: MyBool = true
let myFalse: MyBool = false

myTrue.rawValue    // 0
myFalse.rawValue   // 1

BooleanLiteralType might be the simplest in literal convertible protocols. As we go deeper, we will find things like StringLiteralConvertible is more complicated. Analogous to the bool situation, there is a StringLiteralType and an init method defined. But additionally, it should conform to these two protocols as well:

ExtendedGraphemeClusterLiteralConvertible
UnicodeScalarLiteralConvertible

We are not likely to use these two protocols directly in our project. They are convertible protocols for string cluster and character. The two additional protocols define their own typealias and init methods, which we should also implement for the convertible type.

Say we have a Person class, which contains the name string as a property:

class Person {
    let name: String
    init(name value: String) {
        self.name = value
    }
}

If we want to assign a person object with String, we should make it conform to StringLiteralConvertible:

class Person: StringLiteralConvertible {
    let name: String
    init(name value: String) {
        self.name = value
    }

    required init(stringLiteral value: String) {
        self.name = value
    }

    required init(extendedGraphemeClusterLiteral value: String) {
        self.name = value
    }

    required init(unicodeScalarLiteral value: String) {
        self.name = value
    }
}

You may notice we added required keyword in front of every init method defined in the protocol. This is required by the need of completeness for init method. All subclass of Person should be capable of being literal convertible, for keeping the type safety.

There is quite a lot of duplicated self.name. A better way to improve the code is calling init(name value: String) instead. If we add convenience to them, we can convert these init methods to convenient init:

class Person: StringLiteralConvertible {
    let name: String
    init(name value: String) {
        self.name = value
    }

    required convenience init(stringLiteral value: String) {
        self.init(name: value)
    }

    required convenience init(extendedGraphemeClusterLiteral value: String) {
        self.init(name: value)
    }

    required convenience init(unicodeScalarLiteral value: String) {
        self.init(name: value)
    }
}

let p: Person = "Tom"
print(p.name)

// Output:
// Tom

In the Person example above, we did not extend the Person class with an extension like we did with MyBool. That is because we are not permitted to define a required init method in an extension. In other words, it is not possible to add a literal conversion feature to an existing non-final class. Only those classes we have the source can be used as literal convertible right now. (And I hope Apple can do something with it in an update version.)

Conclusion. Literal-convertible is really a powerful feature in Swift. It would be a big help to make code neat and clean if used in a proper way. As indicated, everything gets its dark side. There are no explicit creation and initialization in your code. It may confuse new developers in your team: Why a string can be assigned to a Person? What is the relationship between the string and the name property? He or she probably just has to dive into the code and jump here and there to search what happens. It will grow up to be a big problem in particular in a large code base, since the literal-convertible code cannot be jumped into with a Cmd + Click.

The same as some other new features in Swift, the best practice of literal-convertible is still under discussion. When using it now in your project, you should pay attention to keeping the distinct meaning of your code. Or it might become hard to understand and maintain.