Objective-C Bridge の仕組とそこから感じたこと

Swift と Objective-C とを混在させてプログラミングしようとしたとき、Swift 標準の `String` 型と Objective-C 標準の `NSString` 型とを意識して使い分ける必要が出てきたりします。

iOS アプリ作りで必須な Foundation フレームワークも今は Objective-C で作られているので、その内部で使われる文字列型は `NSString` になっています。今でこそ Swift の Objective-C との相互運用性もかなり向上し、Swift で `NSString` を意識する機会も減った印象ですけど、それでも稀に相互変換する必要が出てきたりします。

今回はそんなことを発端に、そこから繋がる様々なことを Objective-C Bridge という観点で考察してみます。

# ２種類の文字列型にみる特徴と変換

まずはそんな２種類の文字列型について、特徴を簡単に整理してみます。

## String 型の特徴

`String` 型は Swift 標準ライブラリに定義されている文字列型です。

文字列を表現することを見据えて作られた型で、こういったデータを表現する型を Swift では構造体で作成するのが一般的なため、`String` 型もそれに倣って **構造体** で実装されています。

## NSString 型の特徴

`NSString` 型は iOS アプリ作りなどでお馴染みの Foundation フレームワークに定義されている文字列型です。

Foundation フレームワークといえば、もともと Objective-C 言語が前提の環境で用意されたものでした。Objective-C では全てのものを `NSObject` 型から派生されたオブジェクト型、つまりクラスで作る流儀になっているため、`NSString` 型もそれに倣って **クラス** で実装されています。

## 文字列型の相互変換

そんな２つの文字列型ですけれど、厳密には幾らか違いはあるものの基本的には同じ **文字列** を表現するために使うものなので、それらを **別の型** として扱わないといけないというのは厄介です。Swift の場合は特に *型が違う* ということが直接的に煩わしさに繋がります。

Foundation が必須な iOS アプリ開発においては、その２つを使い分ける必要がどうしても出てくるのですけど、それを見越してなのか `as` 演算子で簡単に変換できるようになっています。

```swift
// NSString 型から String 型への変換
let swiftString = nsstring as String

// String 型から NSString 型への変換
let nsstring = swiftString as NSString
```

# Objective-C Bridge

このように `as` を使って２つの文字列型を変換するとき、どんなことが起こっているのでしょう。

そんなことが気になったので調べてみると、これはかつての `CFString` 型を `NSString` 型に変換するときに使っていたトールフリーブリッジような透過的な変換とは違い、具体的な変換処理が行われている様子でした。

そして、それを実現しているのが `_ObjectiveCBridgeable` というプロトコルです。

```swift
protocol _ObjectiveCBridgeable {

	typealias _ObjectiveCType : AnyObject

	static func _isBridgedToObjectiveC() -> Bool
	static func _getObjectiveCType() -> Any.Type

	func _bridgeToObjectiveC() -> _ObjectiveCType

	static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: Self?)
	static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: Self?) -> Bool
}

```

このプロトコルに準拠した型は `as` 演算子を使って、その中の `_ObjectiveCType` で指定した型との相互変換ができるようになる様子です。

## 自作の型を Objective-C Bridge に対応させる

せっかくなので `_ObjectiveCBridgeable` を使って、自作の型を他の型に変換できるようにしてみます。

たとえば `MyStruct` 構造体を `MyClass` クラスにブリッジできるようにするには、次のように実装します。コードが少し長いのですけど、それぞれの意味については追って記して行くのでまずはざっと読み流して、後の説明の補足として見るくらいの気持ちで見てもらえれば充分です。

```swift
class MyClass {
    
    var value: Int
    
    init(_ value: Int) {
            
    	self.value = value
    }
}

struct MyStruct : _ObjectiveCBridgeable {
    
    var value: Int
    
    init(_ value: Int) {
            
    	self.value = value
    }
    
    typealias _ObjectiveCType = MyClass
    
    static func _isBridgedToObjectiveC() -> Bool {
        
        return true
    }
    
    static func _getObjectiveCType() -> Any.Type {
        
        return MyClass.self
    }
    
    func _bridgeToObjectiveC() -> _ObjectiveCType {
        
        return MyClass(self.value)
    }
    
    static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyStruct?) {
        
        result = MyStruct(source.value)
    }
    
    static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyStruct?) -> Bool {

        result = MyStruct(source.value)
        return true
    }
}
```

ちなみにプロトコル名は `_ObjectiveCBridgeable` ですけど、ブリッジ先の型はオブジェクト型でさえあれば、別に Objective-C 互換クラスではなくても大丈夫なようです。

# Objective-C Bridge の挙動を探る

これらの実装のひとつひとつを説明しようと思ったのですけど、実際に試してみても動作が微妙で把握しきれなかったので、まずは **何をするとどんな動作になるか** という観点で調べてみることにしました。

今回の検証には Xcode 7.2 + Swift 2.1.1 を使用しています。

## MyStruct からの変換の動き

### as MyClass

まずは、先ほどのコードで定義した `MyStruct` を `MyClass` に `as` 演算子で変換するところから見てみます。

```swift
MyStruct(100) as MyClass    // OK
```

このように `MyStruct` 型のインスタンスを `as` 演算子で `MyClass` 型にキャストすると、先ほど実装したうちの `_bridgeToObjectiveC()` だけが呼ばれて、その中に実装されている通りのコードで、新しい `MyClass` 型のインスタンスが生成される様子でした。

### is MyClass

続いて `MyStruct` 型のインスタンスを `MyClass` 型のインスタンスとして利用できるかを判定するのに使う `is` 演算子の動きです。

```swift
MyStruct(100) is MyClass    // true
```

この時は `is` 演算子が実行されると、そこでまず `_isBridgedToObjectiveC()` が呼ばれて、そこで `true` を返すと続けて `_bridgeToObjectiveC()` が呼び出される様子でした。`false` を返すと `_bridgeToObjectiveC()` は呼び出されません。

この時に気になったのが、なぜか `_bridgeToObjectiveC()` で `true` を返すと `_bridgeToObjectiveC()` が呼ばれて `MyStruct` のインスタンスが作られるところです。わざわざ変換先のインスタンスを作る必要はないと思うのですけど、とりあえず今はそんな仕組みになってる様子でした。

### is AnyObject

ところで、作成した `MyStruct` は構造体なのでオブジェクトではないのですけど、次のように `AnyObject` 型であるかをは判定すると `true` になります。

```swift
MyStruct(100) is AnyObject    // true
```

これはきっと `MyStruct` に `_ObjectiveCBridgeable` を適用したことによって **MyClass としても扱えるから** という風に捉えると自然な感じがします。実際にもし `_ObjectiveCBridgeable` の適用をやめると `false` になります。

なお、判定時の機能の呼び出され方は、先ほどの *is MyClass* の時と同じです。

### is MyProtocol

ところで `MyStruct` 型のインスタンスを相手に `AnyObject` であることが確認できるなら、もしかして `MyClass` に任意のプロトコルを適用して、それを `MyStruct` 側から判定できるかもしれません。

そう思ってやってみましたが、結果は `false` になりました。どうやら先ほどの `AnyObject` で判定する場合にだけ、特別な扱いがされているようです。

```swift
protocol MyProtocol {
}

extension MyClass : MyProtocol {
}

MyStruct(100) is MyProtocol    // false
```

## MyClass からの変換の動き

### as MyStruct

今度は逆に `MyClass` のインスタンスを `MyStruct` に変換してみます。

```swift
MyClass(100) as MyStruct    // OK
```

この時は `_forceBridgeFromObjectiveC(_:result:)` が呼ばれ、その中で `result` に設定した `MyStruct` のインスタンスが得られます。ここで `result` に `nil` を入れると **変換できなかった** という意思表示になります。

ただ、意思表示ができるとはいっても、ここで `nil` を入れたからといって `as?` で失敗を加味した変換ができるわけでもないようで、失敗すればそのまま実行時エラーになるので、事実上、変換した値をここで用意しないといけなそうです。

### is MyStruct

続いて `MyClass` のインスタンスが `MyStruct` として扱えるかを `is` 判定した場合です。

```swift
MyClass(100) is MyStruct    // true
```

このときは `_conditionallyBridgeFromObjectiveC(_:result:)` が呼び出されます。ここで `result` に `MyStruct` を設定することで `is` 演算子は `true` を示す様子でした。代わりにここで `result` に `nil` を設定すると、結果は `false` を示すようになります。

なお `_conditionallyBridgeFromObjectiveC(_:result:)` は戻り値を返す仕様になっていて、ここで `true` を返しても `false` を返しても `is` 判定の結果に影響しない様子でした。ただ、この戻り値は **変換の成否** を返すために使うものだそうなので、適切な戻り値を返しておくのが無難そうです。

## NSArray と絡めた変換

また `_ObjectiveCBridgeable` は Foundation フレームワークの `NSArray` や `NSDictionary` でのブリッジも想定されているようなので、それについても確認してみます。

### as NSArray

Swift の配列で `MyStruct` 型を扱うときに、それを `NSArray` 型に変換する仕組みも備えています。

```swift
let array = [MyStruct(10), MyStruct(20), MyStruct(30)]

array as NSArray
```

このようにして、用意した Swift の配列 `Array<MyStruct>` を `NSarray` にブリッジするとき、それぞれの要素に対して `_isBridgedToObjectiveC()` が呼び出されます。そしてそれが `true` を返すと次に `_bridgeToObjectiveC()` が呼ばれ、内容が変換されたオブジェクトが格納された `NSArray` ができあがります。

ここでもし `_isBridgedToObjectiveC()` が `false` を返すと、変換に失敗してランタイムエラーになる様子でした。そのため、少なくとも現時点では `_isBridgedToObjectiveC()` で必ず `true` を返せるような型にだけ Objective-C Bridge を実装するのが無難そうです。

### as? Array<MyStruct>

続いて、先ほどとは逆に `NSArray` を Swift の配列 `Array<MyStruct>` に変換することを試してみます。

このような、内容によって成功するかが試してみるまで判らない場合は `as?` 演算子や `as!` 演算子を使って変換することになります。

```swift
let array:NSArray = [MyClass(10), MyClass(20), MyClass(30)]

array as? Array<MyStruct>
```

そして、今回の場合であれば `NSArray` の中身を全て `MyStruct` に変換できる場合に限り、変換が成功して `Array<MyStruct>` が得られます。

そんなブリッジの動きを外から眺めてみると、配列内のそれぞれの要素に対して、まずは変換先の型が持っている `_getObjectiveCType()` が呼び出された後、そうして得られた型の情報と現在の要素の型とが一致した場合に、変換先の型が持つ `_conditionallyBridgeFromObjectiveC(_:result:)` を使って、現在の要素が変換先の型のインスタンスに変換される様子でした。

このとき、型情報が一致しなかったり、型情報が一致しても変換できない要素が見つかると、その時点で変換処理が打ち切られる様子でした。変換処理が打ち切られると、演算子 `as?` を使った変換であれば `nil` になり、演算子 `as!` を使った演算であればランタイムエラーになるようです。

### as! Array<MyStruct>

演算子 `as!` を使って `NSArray` から `Array<MyStruct>` に変換する流れは、先ほどの `as?` のときとほとんど同じ処理の流れになりました。

ただ、ひとつだけ大きな違いがあるみたいで、変換しようとした最初のところで、変換先の型が持つ `_isBridgedToObjectiveC()` が呼び出される様子でした。ここで `true` を返せばあとは `as?` と同じ処理の流れになり、ここで `false` を返すと変換失敗としてランタイムエラーになる様子でした。

### is Array<MyStruct>

演算子 `is` を使って `NSArray` が `Array<MyStruct>` にブリッジできるか判定したい場合は、処理の流れは `as?` 演算子と同じ流れになりました。そして変換できなければ `false` と判定されます。

注意したいのが、先ほど述べた `as!` の動きとは違うところです。`as!` 演算子での変換の場合は、各要素を変換する前に、変換できるかを `_isBridgedToObjectiveC()` で判断する機会が与えられるのですけど、今回の `is` ではそれが与えられません。そのため `is` なら `true` と判断されるのに、いざ `as!` で変換するとランタイムエラーで落ちるみたいなことが起こり得るかもしれません。

そんな場面を避けるためには、とりあえず今のところは `_isBridgedToObjectiveC()` で必ず `true` を返せるような型にだけ Objective-C Bridge を実装するのが良さそうです。

# Objective-C Bridge に求められる実装

これで変換に関する一般的な動きは一通りチェックできたと思うので、改めてそこから窺えた `_ObjectiveCBridgeable` で実装する各機能の役目について整理してみます。

## typealias _ObjectiveCType

この型がどのオブジェクト型にブリッジできるかを指定するのに使います。名前からして Objective-C 互換クラスの指定が必要そうに思いますけど、普通の Swift クラスも扱えます。

## static _isBridgedToObjectiveC()

これを実装した型が `_ObjectiveCType` に変換できる可能性がある場合に `true` を返します。実際に変換できるかは、この段階では保証しません。また、この機能での判定はインスタンスではなく型に対して注目しているところもポイントです。

メソッドの名前的に変換できるかを判定するもののように見えますけど、現時点で試す感じでは、必ずここで `true` を返さないと、たとえば `as?` 演算子や `is` 演算子みたいな変なところでランタイムエラーが発生する場合が出てくる様子でした。そのため、ここで `false` を返さないといけなくなる可能性がある変換はそもそも実装しないようにするのが無難そうです。

## static _getObjectiveCType()

この型をブリッジした時の型を返します。通常は `_ObjectiveCType.self` を返すことになるようです。

使われるタイミングは `NSarray` 型から `Array<Element>` 型に変換するときに、変換先の `Element` が持つこのメソッドが呼び出され、それと `NSArray` の要素の型とが一致するか（変換対象か）を判定するのに使う様子です。

## _bridgeToObjectiveC()

自身のインスタンスから `_ObjectiveCType` で指定した型のインスタンスを生成する処理を実装します。

この処理は自身の型から `_ObjectiveCType` 型へ向けた `as` 演算によるブリッジや `is` 演算で判定に成功したりすると呼び出される様子です。

## static _forceBridgeFromObjectiveC(source:, inout result:)

`_ObjectiveCType` 型のインスタンスから自身の型のインスタンスに変換する処理を実装します。実装方法は、変換できる場合は変換後の値を、変換できない場合は `nil` を、引数の `result` に代入します。

この処理は `_ObjectiveCType` 型から自身の型へ `as` 演算子で変換するときに使われる様子です。

## static _conditionallyBridgeFromObjectiveC(source:, inout result:)

`_ObjectiveCType` 型のインスタンスを自身の型として扱えるかを演算子 `is` を使って判定するときに呼び出されます。他にも `NSArray` から `Array<Element>` に演算子 `as?` や `as!` を使って変換したり `is` 演算で変換可能性を判定したりするときにも呼び出される様子です。


# Objective-C Bridge の何が嬉しいのか

そんな Objective-C Bridge についてみてきましたけど、これで何が嬉しくなるのかを少し考えてみました。

## 互換性と文化を両立する

Swift を始めて間もない頃は `String` と `NSString` との共存とブリッジは煩わしさ以外のなにものでもなかった印象でしたけど、それから Swift を学んでみると、案外それが良い感じに機能しているのかなと思えてきます。

Swift は Objective-C との相互運用性を重視した言語で、相互運用を意識したときにはとりあえず Objective-C の文化に合わせてコードを書けば、それだけで Objective-C でも Swift でも扱える型が作れたりします。

ただ、Swift では文字列みたいなデータ型を表現するときに **構造体** を使う文化があります。そして Objective-C ではそれを **クラス型** で表現します。つまり Objective-C に頼ったコードを書く限り、実質的に Swift が Objective-C に足を引っ張られてしまいます。

そんな文化的な差異を吸収し、相互の文化に障壁なく乗り入れるための仕組みとして Objective-C Bridge が提供されているように感じました。

## Objective-C Bridge に感じるメリット

これまでの印象を整理すると **両方の環境を想定しているデータ型でも Swift では構造体で扱いたい** みたいな、そんな希望を叶えるのが Objective-C Bridge の役目なのかなって感じました。

これによって Swift と Objective-C のどちらで使ってもそれぞれの文化を尊重できるというメリットもありますし、将来すべてを Swift 環境にシフトしようとしたときに、例えばこれまでクラスで作っていたデータ型を構造体に書き直すみたいな困難な課題にぶつかることなく、簡単に Objective-C から切り離すだけで済みそうなのも嬉しいところです。

# Swift のエラー型と Objective-C との互換性

せっかくなのでもうひとつ、エラー型の相互変換についても少し見ておくことにします。

Objective-C では `NSError` クラスを使ってエラーを表現するのが一般的でしたが、Swift では `ErrorType` プロトコルに準拠した型をエラーを表現するのに使います。そしてこのとき、これら２種類のエラー型の間には互換性があって、Objective-C の `NSError` クラスは Swift の `ErrorType` にも準拠しているため、そのまま Swift のエラーとして扱えます。

そんなエラー型の互換性について、少しばかり見ていってみることにします。

## as NSError

まず、Swift のエラー型、つまり `ErrorType` に準拠した型は、そのインスタンスを `as` 演算子を使って `NSError` クラスに変換できるようになっています。

```swift
struct MyError : ErrorType {

}

let error = MyError()
let nserror = error as NSError
```

このとき注意したいのが `NSError` で取得できる情報は、もともと `NSError` 型に用意されている `code`, `domain` だけになるところです。もしその他に `MyError` が情報を持っていたとしても `NSError` に変換した時点で **失われる** ことになります。

もちろん詳細情報を持つのに使われる `userInfo` も空になるので、それに連動している `localizedDescription` プロパティからも有益な情報は得られなくなります。

具体的には `userInfo` に `NSLocalizedDescriptionKey` を設定しなかったときに得られる "The operation couldn’t be completed." といったテキストになるため、たとえばアプリで `localizedDescription` の内容をエラーメッセージとして通知してたりすると、ぜんぜん価値のないメッセージ表示になってしまったりします。

### 変換後のエラーコードの調整

ちなみに `NSError` への変換時に `code` で表現されるエラーコードは、列挙型で作ったエラー型の場合は、列挙型が持つ列挙子を上から順に 0, 1, 2, ... というように決まる様子です。

列挙型でのエラーコードを自分で設定したい場合や、構造体などで作ったエラー型の場合は、自分で `_code` プロパティを実装することで、自由に指定できる様子でした。

```swift
struct MyError : ErrorType {

	var _code: Int
}
```

### 変換後のエラードメインの調整

同様に `domain` で表現されるエラードメインも、型に `_domain` プロパティを実装することで、好きなものを指定できるようになっているようです。

```swift
struct MyError : ErrorType {

	var _domain: String
}
```

ちなみにこれを実装しない場合は、エラードメインとして型名が使われる様子です。たとえば今回の場合であれば "MyError" が既定のエラードメインになります。

## 変換後の localizedDescription の調整

今まで Objective-C でエラーを表現するときには、分かりやすいエラーメッセージが `localizedDescription` に設定されることが多く、それをそのまま表示することも多かったですけど、先ほど記したように Swift のエラーを `NSError` に変換すると、その情報が失われてしまいます。

Swift のエラー型を `NSError` に変換しても、変換先の `NSError` で `localizedDescription` から分かりやすいメッセージを取得したい場合には `NSError` クラスに用意されている `setUserInfoValueProviderForDomain` メソッドを使って任意の情報を返すように設定することも可能です。

それには `setUserInfoValueProviderForDomain` メソッドの引数として、対象のエラードメインを明記し、クロージャーで対象の `NSError` インスタンスと情報を `userInfo` のどの情報を取得するかを意味するキーを受け取り、対応する情報を `AnyObject?` 型で返すコードを実装します。任意の値を返す必要がないときは `nil` を返すようにします。

```swift
NSError.setUserInfoValueProviderForDomain("MyError") { error, userInfoKey in

	switch userInfoKey {

	case NSLocalizedDescriptionKey:

		switch error.code {

		case 0:
			return "Failed to open."

		case 1:
			return "Not found."

		default:
			return "Unexpected error."
	}

	default:
	
		return nil
	}
}
```

たとえばこのように `NSLocalizedDescriptionKey` に対して文字列を返すことで、それをこのエラードメインを持つ `NSError` の `localizedDescription` プロパティの値として使えるようになります。

該当する情報を `NSError` のインスタンスが持っている場合は、ここで登録した内容に限らず、設定されているものが優先される様子でした。

## Objective-C Bridge 可能なエラー型

Swift のエラー型を `NSError` に変換できても、逆に `NSError` から Swift のエラー型へと変換することはできません。ただ、Swift のエラー型を `_ObjectiveCBridgeableErrorType` に準拠させることで `NSError` 型から変換できるようになる様子でした。

```swift
struct MyError : _ObjectiveCBridgeableErrorType {

	var _code: Int

	init?(_bridgedNSError error: NSError) {

		guard error.domain == "MyError" else {
		
			return nil
		}
		
		self._code = error.code
	}
}
```

変換処理は自分で作らないといけないので、言うなれば **エラードメインとエラーコードから Swift エラー型に変換する** 機能を提供するものになります。その２つの情報だけで不足なく表現できるエラーであれば、このプロトコルを使えば `NSError` から `as?` や `as!` 演算子を使って簡単に Swift エラー型に変換することができます。

### NSError への変換時は注意

その逆の変換（つまり Swift のエラー型から `NSError` への変換）は、特別な準備をしなくても `as` 演算子でできるようになっています。

そのため今回の `_ObjectiveCBridgeableErrorType` を適用すると、まるで相互に変換するための準備が整ったかのように見えてしまいがちですけど、このプロトコルが取り計らうのは `NSError` から自身の型への変換だけです。このときは、元になる `NSError` を受け取って変換処理を行えるので、すべての情報を踏まえた新しいエラーを表現できます。

ただ、その逆の変換は先ほど記した通り、変換時に `code` と `domain` 以外の情報は抜け落ちるので、安易に相互変換を重ねてしまうと、必要な情報がなくなってしまう恐れがあるのに注意です。

# Objective-C Bridge 全般の印象

こんな Swift と Objective-C との相互運用を支援する Objective-C Bridge ですけど、これらを見ていて感じた印象としては、なんとなく **Swift への移行を円滑にし、かつ Objective-C の切り離しを容易にする** もののようにも感じられました。

たとえば、何かデータ型を作りたいとき `NSObject` を継承したクラスで表現すれば Objective-C と Swift の両方で使えますけど、Objective-C Bridge を使えば、独立した２つを手軽に相互乗り入れできるようになります。

## エラー型に対する印象

そして、エラー型に対する Objective-C Bridge について、幾つか思うところがありました。

### Objective-C エラー型の優位性

まず、`_ObjectiveCBridgeableErrorType` を適用することで作れる Objective-C Bridge 可能なエラー型は、先ほども触れたように **NSError を Swift エラー型に完全に取り込む手立て** を提供しているように見えました。

Swift のエラー型はもともと Objective-C 由来の `NSErrorPointer` を Swift で扱いやすくするために生まれたものと自分は認識しているのですけど、そのせいか何も準備をしなくても Swift のエラーを暗黙的に `NSError` とみなして扱えるようになっています。

そのため、たとえば OSX アプリ開発みたいなときには、最終的には全てのエラーを `NSError` で取り回すのが、コードを書く負担の面では楽な気がします。

### Swift エラー型の優位性へ

それが `_ObjectiveCBridgeableErrorType` によって、それまでとは逆の、従来の `NSError` を Swift のエラー型としてまとめて扱う手段を提供しているようにも感じられます。

しかもこの `_ObjectiveCBridgeableErrorType` を使えば `NSError` の `userInfo` も加味して Swift のエラー型に変換できるので、少なくとも Swift から Objective-C のエラーに変換するのとは違い、すべての情報を踏まえた変換ができるので、それを踏まえれば明らかに `NSError` から Swift のエラーへの変換の方向が順当です。

### Objective-C がエラーに求めていたもの

そのように眺めてみたとき、そもそも Objective-C がエラーに何を求めていたかが掴めた感じがしました。

Objective-C のエラー型は `domain` と `code` と `userInfo` を持っていましたけど、少なくとも自分がそれを使うときには、もっぱら `localizedDescription` に頼っていたように思います。ただ `localizedDescription` で取得できる情報は自由な文字列型です。他にも稀に `userInfo` を使うことはありましたけど、それも自由な形式のデータ型でした。

Swift で遊ぶようになって気づいたのですけど、自由すぎる情報は肝心な場面で全く頼りになりません。Swift の `Any` が良い例ですけど、これだとあまりに自由度が高すぎて、確実な処理の遂行を担保するのがとても難しくなってきます。

そんなあたりに気がついたとき、もしかしてエラー通知をするときに頼るべきは `domain` と `code` であって `localizedDescription` は単なる楽をしていただけなんじゃないかと思えるようになりました。もちろん付加情報も大切ですけど、その場合でも次に選ぶのは `userInfo` で、それらを踏まえて適切なエラーメッセージを作るのが道理なように思えてきました。

もし `NSError` に `localizedDescription` がなかったとしたら、エラーの様子を `domain` と `code` で捉えた上で、補足情報を `userInfo` から取得しつつ、適切なエラーメッセージを作って丁寧に作っていたはず。

それってつまり逆に言えば **エラー情報を表現するとき `domain` と `code` が重要な位置を占める** ということになりそうです。改めてそう思ってみればなんだか当たり前なことでしたけど、すっかり `localizedDescription` を使うことに慣れてしまって、肝心なところを疎かにしていたことに気がつきました。

### Swift がエラーに求めていたもの

Swift のエラー型は、今でこそ構造体やクラスでも表現できますけど、登場した当初は列挙型に限られていました。列挙型でエラーを表現すると、列挙型そのものがエラードメインに相当し、列挙子がエラーコードに相当します。

つまりそれって、先ほどの Objective-C がエラーに求めていたものと同じです。そして Swift のエラー型と `NSError` とを相互に変換したときに唯一維持される情報とも合致します。Swift でエラー型を作るときに使う `ErrorType` プロトコルが内部で `_code` と `_domain` しか規定していないというところにも合致します。

つまり Swift のエラーを `NSError` に変換すると `localizedDescription` を含む `userInfo` 情報が欠落するわけですけど、これまでで見えてきたことを踏まえたときに、Swift にとっては変換によって **重要な情報は何も落ちていない** と言えたりするかもしれません。

### エラー情報に求めるもの

つまり `code` と `domain` の２つをしっかり固めることが、エラーに求められる唯一の大事なところなのでしょう。それだけで十分なエラー情報になり得るはずですし、実際にそれを体現しているのが Swift の列挙型を使ったエラー型の表現なのかなと思いました。

そんなふうに思えてみたとき、Swift のエラー型の設計の仕方や、エラー型の Objective-C Bridge の仕方、それらを適切に処理する仕方といったあたりの視界が、なんとなく拓けてきそうな予感が湧いてきました。