型名の変更に使えるSwiftのtypealiasを使うと、型に対して柔軟な設計ができます。どういった場面で使えるかを2つご紹介したいと思います。
【追記】
この記事はSwift2.1を前提にしています。Swift2.2からProtocolのtypealiasは「associatedtype」になりそうです。
https://github.com/apple/swift-evolution/blob/master/proposals/0011-replace-typealias-associated.md
あとからの型変更に対応できるようにする
具体型の内部で仕様する型を一箇所で管理できるため、後で仕様変更をしたい場合に便利なケースがあります。
例を考えてみます。
ある製品を表す構造体を作る時に、そのIDを整数で管理しようと思ったとします。メモリを少しでも削減するため、IDの型をInt32
にして次のように定義しました。
struct Product {
/// 製品ID
let id: Int32
/// 関連する製品のID
var relatedProductIds = [Int32]()
init(id: Int32) {
self.id = id
}
}
Int32
がたくさん見えます。
ところが後になってIDが32bitでは足りないことに気づき、Int64
へ変更したいと思ったとします。ところが、至るところでInt32
で宣言しているので、修正が大変になってしまいます。
そうならないよう、最初の時点で型変更のリスクを考え、あえてtypealiasでIDの型を宣言します。
struct Product {
/// 製品IDの型
typealias ProductIdType = Int32
/// 製品ID
let id: ProductIdType
/// 関連する製品のID
var relatedProductIds = [ProductIdType]()
init(id: ProductIdType) {
self.id = id
}
}
こうしておくことで、後で64bitにしたくなってもProductIdType
をInt64
にするだけで、修正は完了です。
後で型の変更が予想されるときは、typealiasで宣言しておくと良さそうです。
ジェネリクスを使うときに関連する型も含めてインタフェースを決める
プロトコルではtypealiasに型を指定しないまま宣言することができます。
protocol MyProtocol {
typealias T
}
ここで気をつけたいのが、このプロトコルは変数の型指定には使えません。この場合は、通常のプロトコルとは異なり、ジェネリクスの制限にだけ使えます。
protocol OtherProtocol {
typealias U: MyProtocol // typealias(ジェネリクスの仲間)の型を制限
}
struct MyStructure<U: MyProtocol> { // ジェネリクスの型を制限
}
// コンパイルエラーになります
// let object: MyProtocol
// let objects: [MyProtocol]
このようなプロトコルの利用シーンとして一番わかりやすいのは、Web APIへのアクセスと結果の受け取りだと思います。
例として、いろんな種類の本を検索するWeb APIを考えてみます。こんな感じで、Web APIを呼ぶクラスとパースを実行するプロトコルを定義しておきます。
// サーバ応答をパースしてResponseTypeで指定した型で返すプロトコル
protocol ResponseSerializerType {
typealias ResponseType
func deserialize(data: NSData) -> ResponseType
}
// Web APIを呼ぶクラス
class BookApiClient {
private let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
func request<Serializer: ResponseSerializerType>(
url: NSURL,
serializer: Serializer,
completionHandler: (Serializer.ResponseType) -> Void)
{
let task = session.dataTaskWithURL(url) { (data, response, error) in
guard let data = data else { return }
let object = serializer.deserialize(data)
completionHandler(object)
}
task.resume()
}
}
ここでのポイントは、ジェネリクスで指定した型(Serializer
)だけでなく、関連する型(Serializer.ResponseType
)使って関数を定義している点です。
この構造体を使って、Web APIに対応する型とパースロジックをResponseSerializerType
に適合した型に実装します。
// 雑誌用
struct Magazine {
let id: String
let title: String
}
struct SearchMagazinesResponseSerializer: ResponseSerializerType {
typealias ResponseType = [Magazine]
func deserialize(data: NSData) -> ResponseType {
let magazines = [Magazine]()
// dataをパースしてmagazinesに入れていく処理
return magazines
}
}
// 漫画用
struct Comic {
let id: String
let title: String
}
struct SearchComicsResponseSerializer: ResponseSerializerType {
typealias ResponseType = [Comic]
func deserialize(data: NSData) -> ResponseType {
let comics = [Comic]()
// dataをパースしてcomicsに入れていく処理
return comics
}
}
こうすると、ResponseSerializerType
の関連型でWeb APIのレスポンスを受け取れるようになります。
let client = BookApiClient()
// 雑誌を検索
let magazineSerializer = SearchMagazinesResponseSerializer()
client.request(NSURL(string: "http://ysn.bookstore.com/magazines/")!, serializer: magazineSerializer) {
print($0.dynamicType) // -> Array<Magazine>
}
// 漫画を検索
let comicSerializer = SearchComicsResponseSerializer()
client.request(NSURL(string: "http://ysn.bookstore.com/comics/")!, serializer: comicSerializer) {
print($0.dynamicType) // -> Array<Comic>
}
このように、セットしたserializer
に合わせて、コールバックの$0
の型が決まります。使いやすいですね。
まとめ
以上少ないですが、typealiasを活用した設計をご紹介しました。
特に2つ目の例は応用が利くと思いますので、知らなかった方は導入していただけると嬉しいです。