Swift と Objective-C とを混在させてプログラミングしようとしたとき、Swift 標準の String
型と Objective-C 標準の NSString
型とを意識して使い分ける必要が出てきたりします。
iOS アプリ作りで必須な Foundation フレームワークも今は Objective-C で作られているので、その内部で使われる文字列型は NSString
になっています。今でこそ Swift の Objective-C との相互運用性もかなり向上し、Swift で NSString
を意識する機会も減った印象ですけど、それでも稀に相互変換する必要が出てきたりします。
今回はそんなことを発端に、そこから繋がる様々なことを Objective-C Bridge という観点で考察してみます。
2種類の文字列型にみる特徴と変換
まずはそんな2種類の文字列型について、特徴を簡単に整理してみます。
String 型の特徴
String
型は Swift 標準ライブラリに定義されている文字列型です。
文字列を表現することを見据えて作られた型で、こういったデータを表現する型を Swift では構造体で作成するのが一般的なため、String
型もそれに倣って 構造体 で実装されています。
NSString 型の特徴
NSString
型は iOS アプリ作りなどでお馴染みの Foundation フレームワークに定義されている文字列型です。
Foundation フレームワークといえば、もともと Objective-C 言語が前提の環境で用意されたものでした。Objective-C では全てのものを NSObject
型から派生されたオブジェクト型、つまりクラスで作る流儀になっているため、NSString
型もそれに倣って クラス で実装されています。
文字列型の相互変換
そんな2つの文字列型ですけれど、厳密には幾らか違いはあるものの基本的には同じ 文字列 を表現するために使うものなので、それらを 別の型 として扱わないといけないというのは厄介です。Swift の場合は特に 型が違う ということが直接的に煩わしさに繋がります。
Foundation が必須な iOS アプリ開発においては、その2つを使い分ける必要がどうしても出てくるのですけど、それを見越してなのか as
演算子で簡単に変換できるようになっています。
// NSString 型から String 型への変換
let swiftString = nsstring as String
// String 型から NSString 型への変換
let nsstring = swiftString as NSString
Objective-C Bridge
このように as
を使って2つの文字列型を変換するとき、どんなことが起こっているのでしょう。
そんなことが気になったので調べてみると、これはかつての CFString
型を NSString
型に変換するときに使っていたトールフリーブリッジような透過的な変換とは違い、具体的な変換処理が行われている様子でした。
そして、それを実現しているのが _ObjectiveCBridgeable
というプロトコルです。
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
クラスにブリッジできるようにするには、次のように実装します。コードが少し長いのですけど、それぞれの意味については追って記して行くのでまずはざっと読み流して、後の説明の補足として見るくらいの気持ちで見てもらえれば充分です。
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
演算子で変換するところから見てみます。
MyStruct(100) as MyClass // OK
このように MyStruct
型のインスタンスを as
演算子で MyClass
型にキャストすると、先ほど実装したうちの _bridgeToObjectiveC()
だけが呼ばれて、その中に実装されている通りのコードで、新しい MyClass
型のインスタンスが生成される様子でした。
is MyClass
続いて MyStruct
型のインスタンスを MyClass
型のインスタンスとして利用できるかを判定するのに使う is
演算子の動きです。
MyStruct(100) is MyClass // true
この時は is
演算子が実行されると、そこでまず _isBridgedToObjectiveC()
が呼ばれて、そこで true
を返すと続けて _bridgeToObjectiveC()
が呼び出される様子でした。false
を返すと _bridgeToObjectiveC()
は呼び出されません。
この時に気になったのが、なぜか _bridgeToObjectiveC()
で true
を返すと _bridgeToObjectiveC()
が呼ばれて MyStruct
のインスタンスが作られるところです。わざわざ変換先のインスタンスを作る必要はないと思うのですけど、とりあえず今はそんな仕組みになってる様子でした。
is AnyObject
ところで、作成した MyStruct
は構造体なのでオブジェクトではないのですけど、次のように AnyObject
型であるかをは判定すると true
になります。
MyStruct(100) is AnyObject // true
これはきっと MyStruct
に _ObjectiveCBridgeable
を適用したことによって MyClass としても扱えるから という風に捉えると自然な感じがします。実際にもし _ObjectiveCBridgeable
の適用をやめると false
になります。
なお、判定時の機能の呼び出され方は、先ほどの is MyClass の時と同じです。
is MyProtocol
ところで MyStruct
型のインスタンスを相手に AnyObject
であることが確認できるなら、もしかして MyClass
に任意のプロトコルを適用して、それを MyStruct
側から判定できるかもしれません。
そう思ってやってみましたが、結果は false
になりました。どうやら先ほどの AnyObject
で判定する場合にだけ、特別な扱いがされているようです。
protocol MyProtocol {
}
extension MyClass : MyProtocol {
}
MyStruct(100) is MyProtocol // false
MyClass からの変換の動き
as MyStruct
今度は逆に MyClass
のインスタンスを MyStruct
に変換してみます。
MyClass(100) as MyStruct // OK
この時は _forceBridgeFromObjectiveC(_:result:)
が呼ばれ、その中で result
に設定した MyStruct
のインスタンスが得られます。ここで result
に nil
を入れると 変換できなかった という意思表示になります。
ただ、意思表示ができるとはいっても、ここで nil
を入れたからといって as?
で失敗を加味した変換ができるわけでもないようで、失敗すればそのまま実行時エラーになるので、事実上、変換した値をここで用意しないといけなそうです。
is MyStruct
続いて MyClass
のインスタンスが MyStruct
として扱えるかを is
判定した場合です。
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
型に変換する仕組みも備えています。
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
続いて、先ほどとは逆に NSArray
を Swift の配列 Array<MyStruct>
に変換することを試してみます。
このような、内容によって成功するかが試してみるまで判らない場合は as?
演算子や as!
演算子を使って変換することになります。
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
演算子 as!
を使って NSArray
から Array<MyStruct>
に変換する流れは、先ほどの as?
のときとほとんど同じ処理の流れになりました。
ただ、ひとつだけ大きな違いがあるみたいで、変換しようとした最初のところで、変換先の型が持つ _isBridgedToObjectiveC()
が呼び出される様子でした。ここで true
を返せばあとは as?
と同じ処理の流れになり、ここで false
を返すと変換失敗としてランタイムエラーになる様子でした。
is Array
演算子 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
プロトコルに準拠した型をエラーを表現するのに使います。そしてこのとき、これら2種類のエラー型の間には互換性があって、Objective-C の NSError
クラスは Swift の ErrorType
にも準拠しているため、そのまま Swift のエラーとして扱えます。
そんなエラー型の互換性について、少しばかり見ていってみることにします。
as NSError
まず、Swift のエラー型、つまり ErrorType
に準拠した型は、そのインスタンスを as
演算子を使って NSError
クラスに変換できるようになっています。
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
プロパティを実装することで、自由に指定できる様子でした。
struct MyError : ErrorType {
var _code: Int
}
変換後のエラードメインの調整
同様に domain
で表現されるエラードメインも、型に _domain
プロパティを実装することで、好きなものを指定できるようになっているようです。
struct MyError : ErrorType {
var _domain: String
}
ちなみにこれを実装しない場合は、エラードメインとして型名が使われる様子です。たとえば今回の場合であれば "MyError" が既定のエラードメインになります。
変換後の localizedDescription の調整
今まで Objective-C でエラーを表現するときには、分かりやすいエラーメッセージが localizedDescription
に設定されることが多く、それをそのまま表示することも多かったですけど、先ほど記したように Swift のエラーを NSError
に変換すると、その情報が失われてしまいます。
Swift のエラー型を NSError
に変換しても、変換先の NSError
で localizedDescription
から分かりやすいメッセージを取得したい場合には NSError
クラスに用意されている setUserInfoValueProviderForDomain
メソッドを使って任意の情報を返すように設定することも可能です。
それには setUserInfoValueProviderForDomain
メソッドの引数として、対象のエラードメインを明記し、クロージャーで対象の NSError
インスタンスと情報を userInfo
のどの情報を取得するかを意味するキーを受け取り、対応する情報を AnyObject?
型で返すコードを実装します。任意の値を返す必要がないときは nil
を返すようにします。
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
型から変換できるようになる様子でした。
struct MyError : _ObjectiveCBridgeableErrorType {
var _code: Int
init?(_bridgedNSError error: NSError) {
guard error.domain == "MyError" else {
return nil
}
self._code = error.code
}
}
変換処理は自分で作らないといけないので、言うなれば エラードメインとエラーコードから Swift エラー型に変換する 機能を提供するものになります。その2つの情報だけで不足なく表現できるエラーであれば、このプロトコルを使えば 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 を使えば、独立した2つを手軽に相互乗り入れできるようになります。
エラー型に対する印象
そして、エラー型に対する 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
の2つをしっかり固めることが、エラーに求められる唯一の大事なところなのでしょう。それだけで十分なエラー情報になり得るはずですし、実際にそれを体現しているのが Swift の列挙型を使ったエラー型の表現なのかなと思いました。
そんなふうに思えてみたとき、Swift のエラー型の設計の仕方や、エラー型の Objective-C Bridge の仕方、それらを適切に処理する仕方といったあたりの視界が、なんとなく拓けてきそうな予感が湧いてきました。