Swift の文字列表現プロトコルまとめ

Swift で値の文字列表現に関わる主なプロトコルをまとめます。

### [ExpressibleByStringLiteral](https://developer.apple.com/documentation/swift/expressiblebystringinterpolation)

以下のように定義されているプロトコルです。

```swift
public protocol ExpressibleByStringLiteral : ExpressibleByExtendedGraphemeClusterLiteral {
    associatedtype StringLiteralType : _ExpressibleByBuiltinStringLiteral
    init(stringLiteral value: Self.StringLiteralType)
}
```

StringLiteralType は String もしくは StaticString のどちらかです。多くの場合は String を使用すると思います。

ExpressibleByStringLiteral プロトコルに準拠すると変数や定数を文字列リテラルで初期化できます。

以下の例では、User の id を UserId 型にしつつ文字列リテラルで初期化可能にしています。

```swift
struct UserId {
    let value: String
}

extension UserId: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
        self = UserId(value: value)
    }
}

struct User {
    let id: UserId
    // ...
}

let user1 = User(id: "taro")
print(user1)

let userId = UserId(value: "taro")
let user2 = User(id: userId) 
print(user1)

let id = "taro"
let user3 = User(id: id) // Error
```

### [CustomStringConvertible](https://developer.apple.com/documentation/swift/customstringconvertible)

以下のように定義されているプロトコルです。

```swift
public protocol CustomStringConvertible {
    var description: String { get }
}
```

print で表示する際の形式をカスタマイズできます。また、[String(describing:)](https://developer.apple.com/documentation/swift/string/2427941-init) は description を利用して文字列を生成します。print は String(describing:) を利用します。

String(describing:) はプロトコルの準拠によって以下のいずれかの方法でで生成する文字列を決定します。

1. CustomStringConvertible に準拠していれば description の値
2. TextOutputStreamable に準拠してれば write された値
3. CustomDebugStringConvertible に準拠していれば debugDescription の値
4. 上記プロトコルに準拠、Swift standard library により自動生成された値

優先順位は明示的にリファレンスには記述されていないのですが、

- print では 
    - TextOutputStreamable > CustomStringConvertible > CustomDebugStringConvertible 
- String(:describing:) では 
    - CustomStringConvertible > CustomDebugStringConvertible > TextOutputStreamable 

でした。
 

以下の例では、User の文字列表現を description で指定しています。

```swift
struct User {
    let id: String
    let name: String
}

extension User: CustomStringConvertible {
    var description: String {
        return "\(name) [id=\(id)]"
    }
}

let user = User(id: "123", name: "taro")
print(user) // -> taro [id=123]
```

### CustomDebugStringConvertible

以下のように定義されているプロトコルです。

```swift
public protocol CustomDebugStringConvertible {
    var debugDescription: String { get }
}
```

debugPrint で表示する際の形式をカスタマイズできます。

また、[String(reflecting:)](https://developer.apple.com/documentation/swift/string/1541282-init) が生成する文字列が debugDescription になります。String(reflecting:) は値の詳細な表現を返すイニシャライザです。

String(reflecting:) はprotocol が準拠されているかどうかにより以下の方法で文字列を決定します。

1. CustomDebugStringConvertible に準拠していれば debugDescription の値
2. CustomStringConvertible に準拠していれば description の値
3. TextOutputStreamable に準拠してれば write された値
4. 上記プロトコルに準拠し絵地なければ、Swift standard library が自動生成した値

優先順位はリファレンスには明記されていないのですが、試してみると debugPrint も String(refrecting:) も　1, 2, 3 の順でした。

debugDescription プロパティを直接利用することは推奨されていません。

以下の例では、debugPrint の出力を debugDescription で指定しています。

```swift
struct User {
    let id: String
    let name: String
}

extension User: CustomDebugStringConvertible {
    var debugDescription: String {
        return "\(name), \(id)"
    }
}

 let user = User(id: "123", name: "taro")
debugPrint(user)
```

### [LosslessStringConvertible](https://developer.apple.com/documentation/swift/losslessstringconvertible)

以下のように定義されているプロトコルです。CustomStringConvertible を継承しています。

```swift
public protocol LosslessStringConvertible : CustomStringConvertible {
    init?(_ description: String)
}
```

あいまいさなく、また、情報の欠落なく文字列として表現可能であることを表すプロトコルです。CustomStringConvertible の description で文字列化し、LosslessStringConvertible の init?(_: String) で文字列から値を生成するようにします。

[String の init(_:)](https://developer.apple.com/documentation/swift/string/2431675-init) は LosslessStringConvertible に準拠した型の値を引数に取ります。

以下の例では、User を文字列と相互変換可能にしています。

```swift
struct User {
    let id: String
    let name: String
}

extension User: CustomStringConvertible {
    var description: String {
        return "\(id), \(name)"
    }
}

extension User: LosslessStringConvertible {
    init?(_ description: String) {
        let v = description.components(separatedBy: ", ")
        if v.count != 2 {
            return nil
        }

        self = User(id: v[0], name: v[1])
    }
}

print(User("123, taro") ?? "error")
let user = User(id: "456", name: "hanako")
print(String(user))
print(String(user))
```

### [TextOutputStreamable](https://developer.apple.com/documentation/swift/textoutputstreamable)

以下のように定義されているプロトコルです。

```swift
public protocol TextOutputStreamable {
    func write<Target>(to target: inout Target) where Target : TextOutputStream
}
```

このプロトコルに準拠すると [TextOutputStream](https://developer.apple.com/documentation/swift/textoutputstream) に対し値を write できるようになります。

以下の例では User の値を文字列に write し追加しています。

```swift
struct User {
    let id: String
    let name: String
}

extension User: TextOutputStreamable {
    func write<Target>(to target: inout Target) where Target : TextOutputStream {
        target.write("id: \(id), name: \(name)")
    }
}

let user1 = User(id: "123", name: "taro")
let user2 = User(id: "456", name: "hanako")

var s = ""

user1.write(to: &s)
"\n".write(to: &s)
user2.write(to: &s)

print(s)
```

## String Interpolation をカスタマイズ

[StringInterpolationProtocol](https://developer.apple.com/documentation/swift/stringinterpolationprotocol) のページにカスタマイズ方法が記載されています。

- `\(x)` translates to appendInterpolation(x)
- `\(x, y)` translates to appendInterpolation(x, y)
- `\(foo: x)` translates to appendInterpolation(foo: x)
- `\(x, foo: y)` translates to appendInterpolation(x, foo: y)

拡張は `String.StringInterpolation` に対して行い、カスタマイズしたい型やパラメータを受け取る appendInterporation をオーバーロードしていきます。出力は appendLiteral を呼びます。

```swift
struct User {
    let id: String
    let name: String

    enum NameFormat {
        case uppercase
        case lowercase
    }
}

extension String.StringInterpolation {
    mutating func appendInterpolation(_ user: User, format: User.NameFormat) {
        switch format {
        case .lowercase:
            self.appendLiteral(user.name.lowercased())
        case .uppercase:
            self.appendLiteral(user.name.uppercased())
        }
    }
}

let user = User(id: "123", name: "taro")
print("\(user, format: .uppercase)")
```