[Swift]GitHub検索プログラムをprotocol, associatedTypesで改良する
先日、Swiftの基本的な内容を記した本「改訂新版 Swift実践入門(石川洋資・西山勇世、技術評論社、2018)」に従って、GitHubのリポジトリの検索プログラムを実装しました。
GitHub検索用APIには豊富な検索オプションが用意されているのですが、この本ではユーザーとリポジトリの検索のみ記載されていたのでとりあえずそれに従って実装しました。
検索結果を表すために、ジェネリクスを利用したSearchResponse構造体を実装しています。
struct SearchResponse<Item: Decodable>: Decodable {
let totalCount: Int
let items: [Item]//構造体User, 構造体Repositoryを収められる
//中略
}
この構造体は、SearchRepositories
, SearchUsers
という、APIへのリクエストを表す構造体にそれぞれassociatedTypesを用いて関連づけられています。
final class GitHubAPI {
struct SearchRepositories: GitHubRequest {
//中略
//リクエストに、対応するレスポンスを関連付けしている
typealias Response = SearchResponse<Repository>
// The word you would like to search with.
let keyword: String
}
struct SearchUsers: GitHubRequest {
//中略
//リクエストに、対応するレスポンスを関連付けしている
typealias Response = SearchResponse<User>
let keyWord: String
}
}
protocol GitHubRequest {
associatedtype Response: Decodable
//後略
}
associatedTypesとは、swiftのprotocolに任意の型を関連づけられるというものです。protocol版のジェネリクスのようなものと大雑把に捉えています(認識がおかしかったら教えてください)。
ところが本書の例では、プログラムのエントリポイントとなるmain.swiftで、SearchReponse<Repository>
を使ってリポジトリは検索できるようになっているのに、せっかくのSearchReponse<User>
は使っておらず、ユーザーでは検索できないようになっていたのです👇
//中略
//SearchRepository構造体を用いてリクエストを作成
let request = GitHubAPI.SearchRepositories(keyword: "\(keyWord)")
//リクエストをサーバへ送る
client.send(request: request, completion: {(result) in
switch result {
case let .success(response):
response.items.forEach {print($0)}
exit(0)
case let .failure(error):
print(error)
exit(1)
}
})
//中略
せっかくユーザーで検索する仕組みを用意しているのに使わないではもったいない。
そこで、ユーザかリポジトリかどちらか選んで検索できるようにしようと実装してみました。
具体的には、SearchReponse<Repository>
もSearchReponse<User>
もどちらも渡して処理できるsendClient<T: GitHubRequest>(request: T)
関数を実装しようとしました。
しかしコンパイルエラーが発生しました。
func sendClient<T: GitHubRequest>(request: T) {
let client = GitHubClient()
client.send(request: request, completion: {(result) in
switch result {
case let .success(response):
response.items.forEach {print($0)} //Error: Value of type 'T.Response' has no member 'items'
exit(0)
case let .failure(error):
print(error)
exit(1)
}
})
}
は?
どうやら他の箇所にも手を加えないと実現できないようです。解決策を考えてみました。
エラー内容はValue of type 'T.Response' has no member 'items'
。
原因は、GitHubRequest
プロトコルにて、associatedtype Response: Decodable
とのみ規定していた事、つまりコンパイラからはResponse
という型に対する制約は「Decodable
である」事以外に規定されていなかった、という事でした。
言い換えれば、Response
という型がitems
という変数を持つという制約は今の所どこにもないため、コンパイラからはそのことが分からないのです。だからエラーが発生したのです。
まずは、Response
という型に対して「Decodable
である」のみならず「items
という変数を持つ」という制約も付け加える必要があります。そのため、新しいプロトコルAbstructResponse
を作成し、Response
型をこれに準拠させることにしました。
//新たなプロトコルに準拠させる
struct SearchResponse<Item: Decodable>: AbstructResponse {
typealias Item = Item
let totalCount: Int
let items: [Item]
//中略
}
}
//新たなプロトコルを定義
protocol AbstructResponse: Decodable {
associatedtype Item: Decodable
var totalCount: Int {get}
var items: [Item] {get} //itemsという変数を持つという制約が加わる
}
はい、このようにするとコンパイラはResponse
型を見ただけで、それがitems
という変数を持つことが分かるようになり、エラーが解消します。あとは、変更点に合わせて他の部分も変更するだけです。
全ての変更点をまとめると下記のようになります。
main.swift
コードをコピペなどしたい場合はこちら参照: https://gitlab.com/Satoru_Aikawa/githubsearchproject2/commit/6591bce7dfca238e47afc658a3e3b4e7e6bbd1c0
参考文献・ウェブサイト
www.amazon.co.jp/dp/477419414X
Author And Source
この問題について([Swift]GitHub検索プログラムをprotocol, associatedTypesで改良する), 我々は、より多くの情報をここで見つけました https://qiita.com/satoru_pripara/items/90848de506c364a933b9著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .