SwiftでType Erasureを使いgenericなAPI protocolを作成する


## I. はじめに

APIをViewControllerなどに持たせる場合、直接APIを持たせるのではなく、modelを取得するインターフェースを持つprotocolを作成し、それに準拠した実態としてのAPIをDIすると言うやり方で実装する人が多いのではないだろうか。

protocolをgenericにしてAPIのレスポンスの型を差し込み使いまわしたいところだが、protocolをgenericにしてしまうと、それを変数として宣言できなくなってしまうため、毎回個別にprotocolを定義しなければいけない。



今回はこの問題をType Erasureと言うテクニックを使い、genericだが変数として保持可能なprotocolを作成することで解決したと思う。

## II. よくある実装例

### protocolを作成
ユーザーを取得するインターフェースをprotocolとして作成

```UsersRequestable.swift
protocol UsersRequestable {
    func fetch(completion: ((Result<[User], Error>) -> Void))
}
```

### 実態のAPIクラスを作成
本番用のものとStubの２つのAPIを先ほどのUsersRequestableへ準拠させて作成。

```UsersRequest.swift
/// 実際にサーバーへ繋げるAPI
class UsersRequest: UsersRequestable {
    func fetch(completion: ((Result<[User], Error>) -> Void)) {
        // サーバーからデータを取ってくる
    }
}
```

```StubUsersRequest.swift
/// 開発や自動化テスト用のStubを返すAPI
class StubUsersRequest: UsersRequestable {
    func fetch(completion: ((Result<[User], Error>) -> Void)) {
        // Stubの情報を返す
    }
}
```

### ViewControllerへDI
先ほど作成したUsersRequestおよびStubUsersRequestは、ViewControllerへ状況に応じてDIされる。

#### UsersRequestableを変数として保持
実際に中に何が入っているかは感知しない。

```MyViewController.swift
class MyViewController: UIViewController {
    var usersRequest: UsersRequestable?
}
```

#### 状況に応じたAPIを差し込む

```Configurator.swift
// 本番APIを差し込む例
let myViewController = MyViewController()
myViewController.userRequest = UsersRequest() // 本番APIを差し込む
        
// Stub APIを差し込む例
let myViewController = MyViewController()
myViewController.userRequest = StubUsersRequest() // Stubを差し込む
```
#### モデル毎にprotocolの作成が必要
この例の実装ではUserに対してはUsersRequestable, 他のモデルがある場合はそのモデルに対してのRequestable protocolを作らなければいけない。すなわち全てのモデルに対してRequestable protocolを作成しなければならないことになる。これは完全なDRY違反である。



## III. generic protocolは変数保持が不可
モデル毎にRequestableを作らなければいけないという問題に対処するためにgenericなprotocolを作成すると言う方法が考えられる。しかしgenericなprotocolはいくつかの問題があり、Requestableとして使えない。

### 単純なGeneric protocolは作れない
protocol名<T>という宣言のgeneric protocolはそのそも作成できない。Associated Typeを使えと言われてしまう。

```AnyRequestable.swift

protocol AnyRequestable<T> {
    func fetch(completion: ((Result<[T], Error>) -> Void))
}

// コンパイルエラー: Protocols do not allow generic parameters; use associated types instead 
```

### Associated Typeのprotocolは変数保持は不可
associated typeを使いgenericなprotocolは以下のように作成することができる。このケースではAnyRequestableのprotocolはコンパイルを通る。

```AnyRequestable.swift
// これはコンパイルを通る
protocol AnyRequestable {
    associatedtype T

    func fetch(completion: ((Result<[T], Error>) -> Void))
}
```

しかしAnyRequestableを変数として保持させようとするとコンパイルエラーになってしまう。

```MyViewController.swift
class MyViewController: UIViewController {
    var usersRequest: AnyRequestable?
    // コンパイルエラー: Protocol 'AnyRequestable' can only be used as a generic constraint because it has Self or associated type requirements
}
```


## IV. TypeErasureで解決
Type Erasureとはgenericなprotocolを作成してそれを変数として保持することを可能にするワークアラウンドである。

### Requestableの作成
associated typeを使ったgenericなrequestableをまず作成する。
このままでは、このprotocolを変数として宣言することはできない。

```Requestable.swift
protocol Requestable {
    associatedtype ResponseType
    func request(completion: @escaping (Result<ResponseType, Error>) -> Void)
}

```

### Type EraserでRequestableをラップ
ここでいよいよType Erasureのテクニックが登場する。

```AnyRequestable.swift
class AnyRequestable<Model>: Requestable {
    
    typealias ResponseType = Model
    
    
    init<Inner: Requestable>(_ inner: Inner) where ResponseType == Inner.ResponseType {
        self._request = inner.request
    }
    
    
    
    private let _request: ( (_ completion: @escaping (Result<Model, Error>) -> Void) -> Void )
    
    func request(completion: @escaping (Result<Model, Error>) -> Void) {
        self._request(completion)
    }
}

```

このclass自体はgenericなクラスで、APIのレスポンスのモデルの型を設定できる。

先ほど作成したgenericなprotocolであるRequestableに準拠した何らかのクラスのインスタンスを初期化時に差し込む。そして差し込んだインスタンスのrequestableのメソッドをclosureとしてAnyRequestable内部で保持する。

そして次がトリッキーな部分だ。

AnyRequestable自身もRequestableに準拠している。そのためAnyRequestableにもRequestableのメソッドであるrequestメソッドが実装されている。AnyRequestable実装のrequestメソッドが呼ばれた場合、AnyRequestableが保持しているclosureのrequestを実装する。これはAnyRequestableが初期化時に保持した、外部から差し込まれたRequestableの実態のrequestメソッドである。

この一連の処理は注意深く読めば理解できると思う。


### 実際のrequestの作成
UserとBookに関するそれぞれのRequestを作成した。これらはAnyRequestableではなくRequestableに準拠している。

```Requests.swift
class UsersRequest: Requestable {
    typealias ResponseType = [User]
    
    func request(completion: @escaping (Result<[User], Error>) -> Void) {
        // ここに実際にUserを取ってくる処理を書く
    }
}

class BooksRequest: Requestable {
    typealias ResponseType = [Book]
    
    func request(completion: @escaping (Result<[Book], Error>) -> Void) {
        // ここに実際にBookを取ってくる処理を書く
    }
}


```

### ViewControllerへの差し込み
ViewControllerは各requestをAnyRequestableとして保持。
外部からrequestを差し込む場合は、それぞれのrequestをAnyRequestableでラップしたものを差し込む。

```Configurator.swift
class MyViewController: UIViewController {
    var usersRequest: AnyRequestable<[User]>?
    var booksRequest: AnyRequestable<[Book]>?
}


let myViewController = MyViewController()
 
/*
 Users Requestの差し込み

 AnyRequestableが[User]のgenericで宣言されている点に注目
 さらに初期化時にAnyRequestableへusersRequestを差し込む。
 myViewControllerへはanyUsersRequestを差し込む。
 */
let usersRequest = UsersRequest()
let anyUsersRequest = AnyRequestable<[User]>(usersRequest)
myViewController.usersRequest = anyUsersRequest



// Books Requestの差し込み: Usersと同じように差し込む

let booksRequest = BooksRequest()
let anyBooksRequest = AnyRequestable<[Book]>(booksRequest)
myViewController.booksRequest = anyBooksRequest
```


## V. 参考文献
[Type-Erased Wrappers in Swift](https://academy.realm.io/posts/type-erased-wrappers-in-swift/)
[try! Swift: Type Erasureのユースケースを考えてみた話](https://ameblo.jp/principia-ca/entry-12142313027.html)