用 APIKit 的 DataParser 組出 Codable 的資料物件（筆記）

在繼承 APIKit 必須實作的 Request 方法，通常會在這個地方做 mapping 成 model 物件這件事：

``` swift
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {}
```

雖然在 iOS 12.* 這樣寫是可行的，但是在 iOS 11 會報錯，就來看看解決辦法：

``` swift
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
    if let data = object as? Data {
        return try JSONDecoder().decode(Response.self, from: data)
    }

    throw DataCastingError // 丟自訂轉型的錯誤
}
```


## APIKit.DataParser

找了一些文章發現有這個 protocol 可以用 （[官方程式碼](https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/DataParser/DataParser.swift))，自訂義完之後，可以設定到 [Request#dataParser](https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/Request.swift#L42-L43) 去。

### 預設值

先來看這個 [dataParser 屬性的預設值](https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/Request.swift#L88-L90)，預設是用了 [JSONDataParser](https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/DataParser/JSONDataParser.swift) 這個類別的物件

``` swift
// Request.swift#L88-L90
// https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/Request.swift#L88-L90
var dataParser: DataParser {
    return JSONDataParser(readingOptions: [])
}
```

在 `parse(data:)` 可以看出來是用 JSONSerialization 來處理，回傳值可能會是 Array 或是 Dictionary

``` swift
// JSONDataParser.swift#L22-L29
// https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/DataParser/JSONDataParser.swift#L22-L29
public func parse(data: Data) throws -> Any {
    guard data.count > 0 else {
        return [:]
    }

    return try JSONSerialization.jsonObject(with: data, options: readingOptions)
}
```

### 原理

在 DataParser 裡面可以直接拿到原始的 Data 物件，接著在 parse 裡面可以預先處理這個資料
處理完之後作為返回值回傳，接著在 Request＃response 這個方法就可以拿到了

### 動手做 DataParser

| 想做的事 |
|---|
| 寫一次、所有的 Response 是 Codable 的 Request 都可以套用這個 DataParser

#### DataParser

想要做到 [解耦合](http://terms.naer.edu.tw/detail/1252377/) ，所以就把 decoder 另外宣告成一個屬性：

``` swift:ModelDataParser.swift
import Foundation
import APIKit

struct ModelDataParser<T: Codable>: APIKit.DataParser {
    var decoder: JSONDecoder

    // MARK: APIKit.DataParser
    
    var contentType: String? {
        return "application/json"
    }

    func parse(data: Data) throws -> Any {
        return try decoder.decode(T.self, from: data)
    }
}
```

#### Request 的設定

條件是「Response 是 Codable 的 Request 就必須採用這個 DataParser」
因此可以透過 extension 和條件判斷來設定：

``` swift
import APIKit

extension Request where Response: Codable {
    var decoder: JSONDecoder {
        // 如果有客製化的 Decoder 可以直接放在這邊
        return JSONDecoder()
    }

    var dataParser: DataParser {
        return ModelDataParser<Response>(decoder: decoder)
    }
}
```

### 使用方法

回到 Request 的 response 方法，寫法如下：

``` swift
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
    if let result = object as? Response {
        return result
    }

    throw DataParsingError // 丟符合情境的錯誤
}
```

因為這邊資料出口都是 Any 這個型別，所以還需要再 `as ?` 轉型一次才有辦法回傳

## 參考資料

- [[iOS]RxSwift + APIKit + Decodableでインクリメンタルサーチ | otas](https://dev.classmethod.jp/smartphone/iphone/rxswift-apikit-decodable-incremental-search/)
- [APIKitとCodableとの連携 | @matsuda](https://qiita.com/matsuda/items/812321141a7c3b5f22bd)