GraphQL用のiOSクライアントの準備

画像



少なくとも一度はREST APIで問題が発生したと確信しています。 目的のAPI形式のサポート、いくつかの画面要求など、永遠の戦い。 これは珍しいことではなく、日常的なことです。 そして最近、Tribuna Digitalは新しいプロジェクト-Betting Insiderを立ち上げました。 当初、このプロジェクトはiOSとAndroidに実装され、後にWebバージョンの開発が始まりました。 既存のAPIはWebにとって非常に不便でした。 このすべてが、実験を準備し、Apolloのクライアントと一緒にGraphQLを試すことにしたという事実につながりました。 iOSでこのテクノロジーを詳しく知りたい場合は、catへようこそ!



GraphQLについて少し



GraphQL-(Graph Query Language)は、これらのクエリを処理するためのクエリ言語およびインスタンスです。 簡単に言えば、これはサーバーとクライアント間のレイヤーであり、サーバーに必要なものをクライアントに提供します。 このレイヤーとの通信は、GraphQLで直接行われます。 GraphQL言語などについてもっと直接読みたい場合、またはまったく知らない場合は、 こちらこちらをお読みください 。 写真ですべてが十分に詳しく説明されています。



何が必要ですか?



さらに作業を進めるにはNodeJSNode Package ManagerCocoaPodsが必要です

graphqlクエリの強調表示が必要な場合は、このプラグインをインストールします 。 また、事前に準備されたプロジェクトをダウンロードする必要があります。 そして最も重要なのは、個人キーを取得することです。 ここに指示がありますが、ここではそのようなミサゴを要求します:







どうする?



github用の小さなクライアントを作成します。このクライアントでは、任意のキーワードの上位20のリポジトリを見つけることができます。 Github APIとGraphQLを体験するには、 ここで遊んでください



プロジェクトのセットアップ



コードの記述を開始する前に、いくつかのパッケージをインストールし、プロジェクトを構成します。

Apolloライブラリは、独自のapollo-codegenツールを生成するコードを使用します。 npmを使用してインストールします。



npm install -g apollo-codegen
      
      





次に、プロジェクトフォルダーに移動し、ソースを開きます。 GraphQLサーバーのスキームをダウンロードし、<token>の代わりに個人キーを挿入します。これは上記で行ったはずです。



 apollo-codegen download-schema https://api.github.com/graphql --output schema.json --header "Authorization: Bearer <token>"
      
      





SourceフォルダーにGraphQLフォルダーを作成します。



次に、コードジェネレーターのスクリプトを登録する必要があります。



 APOLLO_FRAMEWORK_PATH="$(eval find $FRAMEWORK_SEARCH_PATHS -name "Apollo.framework" -maxdepth 1)" if [ -z "$APOLLO_FRAMEWORK_PATH" ]; then echo "error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project." exit 1 fi cd "${SRCROOT}/Source" $APOLLO_FRAMEWORK_PATH/check-and-run-apollo-codegen.sh generate $(find . -name '*.graphql') --schema schema.json --output GraphQL/API.swift
      
      













プロジェクトアセンブリの最初の開始時に、このスクリプトはAPI.swiftファイルを作成します。このファイルには、リクエストを処理するために必要なコードが含まれ、各アセンブリで更新されます。 現時点では、Xcodeは何かが欠落しているため、ビルド段階でエラーをスローします。



お問い合わせ



APIを作成するには、コードジェネレーターにGraphQLのすべてのリクエストが書き込まれるファイルが必要です。 Source / GraphQLフォルダー(ファイルリストの一番下)にgithub.graphqlという名前の新しい空のファイルを作成します。 次に、このファイルにクエリを作成します。これにより、検索クエリの最初の20個のリポジトリが提供され、リポジトリのフラグメントもすぐに決定されます。



 query SearchRepos($searchText: String!) { search(first: 20, query: $searchText, type: REPOSITORY) { nodes { ... on Repository { ...RepositoryDetail } } } } fragment RepositoryDetail on Repository { id nameWithOwner viewerHasStarred stargazers { totalCount } }
      
      





次に、必要なコードが生成されるようにプロジェクトをビルドします。 結果のAPI.swiftファイルをプロジェクトに追加します。



重要:* .graphqlに新しいコードを追加した場合、最初にプロジェクトをビルドしてから、新しいコードの作成を開始してください!



お客様



Apolloクライアントを作成する必要があります。 AppDelegate宣言の上にあるAppDelegate.swiftで作成します。 githubとの通信には認証が必要なため、すべてのリクエストにヘッダーを追加する必要があります。 私たちはそのようなものを得ます:



 let apollo: ApolloClient = { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = ["Authorization": "bearer <your token>"] let url = URL(string: "https://api.github.com/graphql")! return ApolloClient(networkTransport: HTTPNetworkTransport(url: url, configuration: configuration)) }()
      
      





検索クエリ



ここで、[検索]ボタンをクリックしたときに、サーバーにリクエストを送信することを確認する必要があります。 ファイルReposListViewModel.swiftを作成してから、ReposListViewModelを作成します。 これは、リクエストをサーバーに送信し、既存のリクエストのステータスを管理するクラスになります。



まず、Apolloをインポートしてから、Cancellable型の変数を作成し、currentSearchCancellableと呼びます。



 import Apollo class ReposListViewModel { private var currentSearchCancellable: Cancellable? }
      
      





次に、検索するテキストを受け取る関数を作成し、結果の配列をコールバックに渡します。 最後に、リクエストの送信に直接進みます! まず、クエリを作成する必要があります。 コードジェネレーターは、graphqlファイルで指定した名前SearchReposQueryに対応するクエリを生成し、検索テキストを使用して初期化します。 次に、答えを得るために、クエリを渡すApolloクライアントのフェッチ関数を呼び出し、メイン実行キューと対応するキャッシュポリシーを選択します。 コールバックでは、fetchはオプションの結果とエラーを返します。 今のところ、エラーの処理方法については考えませんが、発生した場合は単に印刷します。 結果からRepositoryDe​​tailを抽出します。これにより、コードジェネレーターも生成され、コールバック検索関数に渡されます。 この機能は判明します



 func search(for text: String, completion: @escaping ([RepositoryDetail]) -> Void) { currentSearchCancellable?.cancel() let query = SearchReposQuery(searchText: text) currentSearchCancellable = apollo.fetch(query: query, cachePolicy: .returnCacheDataAndFetch, queue: .main, resultHandler: { (result, error) in if let result = result, let data = result.data { let repositoryDetails = (data.search.nodes ?? [SearchReposQuery.Data.Search.Node?]()).map{$0?.asRepository}.filter{$0 != nil}.map{($0?.fragments.repositoryDetail)!} completion(repositoryDetails) } else { print(error as Any) completion([RepositoryDetail]()) } }) }
      
      





次に、ReposListViewControllerパラメーターとしてviewModelを作成し、RepositoryDe​​tailを保存するための配列を作成します。



 let viewModel = ReposListViewModel() var repositoryDetails = [RepositoryDetail]()
      
      





次に、numberOfRowsInSectionを次のように変更します。



 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return repositoryDetails.count }
      
      





ReposListCellを更新することも必要です。



 class ReposListCell: UITableViewCell { @IBOutlet weak var repoName: UILabel! @IBOutlet weak var starCount: UILabel! @IBOutlet weak var starButton: UIButton! var repositoryDetail: RepositoryDetail! { didSet { repoName.text = repositoryDetail.nameWithOwner starCount.text = "\(repositoryDetail.stargazers.totalCount)" if repositoryDetail.viewerHasStarred { starButton.setImage( imageLiteral(resourceName: "ic_full_star"), for: .normal) } else { starButton.setImage( imageLiteral(resourceName: "ic_empty_star"), for: .normal) } } } }
      
      





cellForRowは次の形式を取ります。



 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ReposListCell") as! ReposListCell cell.repositoryDetail = repositoryDetails[indexPath.row] return cell }
      
      





クリックするために必要なデータをviewModelに要求することは残ります。



  func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { viewModel.search(for: searchBar.text ?? "") { [unowned self] repositoryDetails in self.repositoryDetails = repositoryDetails self.tableView.reloadData() } }
      
      





できた! 上位20のリポジトリをコンパイルして、リクエストを検索します!



星を入れて



既に理解したように、セルのレイアウトの星は単なるものではありません。 この機能をアプリケーションに追加しましょう。 これを行うには、新しいミューテーションリクエストを作成しますが、その前に、 ここで IDを取得しましょう。 これで、適切なclientMutationIDを使用して検索クエリを挿入し、プロジェクトをビルドできます。



 mutation AddStar( $repositoryId: ID!) { addStar(input: {clientMutationId: “<your ID>”, starrableId: $repositoryId}) { starrable { ... on Repository { ...RepositoryDetail } } } } mutation RemoveStar($repositoryId: ID!) { removeStar(input: {clientMutationId: “<your ID>”, starrableId: $repositoryId}) { starrable { ... on Repository { ...RepositoryDetail } } } }
      
      





これらの要求の実行をViewModelに追加します。 ロジックはクエリと同じですが、フェッチの代わりに、performを呼び出します。



 func addStar(for repositoryID: String, completion: @escaping (RepositoryDetail?) -> Void ) { currentAddStarCancellable?.cancel() let mutation = AddStarMutation(repositoryId: repositoryID) currentAddStarCancellable = apollo.perform(mutation: mutation, queue: .main, resultHandler: { (result, error) in if let result = result, let data = result.data { let repositoryDetails = data.addStar?.starrable.asRepository?.fragments.repositoryDetail completion(repositoryDetails) } else { print(error as Any) completion(nil) } }) } func removeStar(for repositoryID: String, completion: @escaping (RepositoryDetail?) -> Void ) { currentRemoveStarCancellable?.cancel() let mutation = RemoveStarMutation(repositoryId: repositoryID) currentAddStarCancellable = apollo.perform(mutation: mutation, queue: .main, resultHandler: { (result, error) in if let result = result, let data = result.data { let repositoryDetails = data.removeStar?.starrable.asRepository?.fragments.repositoryDetail completion(repositoryDetails) } else { print(error as Any) completion(nil) } }) }
      
      





次に、ボタンをクリックしたことをViewControllerに知らせる必要があります。 セルデリゲートにします。 これを行うには、プロトコルを作成し、ReposListCell.swiftファイルに追加します。



 protocol ReposListCellDelegate: class { func starTapped(for cell: ReposListCell) }
      
      





新しいセルクラスパラメーターを追加し、星をクリックするための処理を行います。

 weak var delegate: ReposListCellDelegate? override func awakeFromNib() { super.awakeFromNib() starButton.addTarget(self, action: #selector(starTapped), for: .touchUpInside) } @objc func starTapped() { delegate?.starTapped(for: self) }
      
      





ここで、コントローラをcellForRowのデリゲートとして割り当てます。



 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ReposListCell") as! ReposListCell cell.repositoryDetail = repositoryDetails[indexPath.row] cell.delegate = self return cell }
      
      





テーブル内のデータを更新する関数を追加します。

 func updateTableView(for newDetail: RepositoryDetail?) { if let repositoryDetail = newDetail { for (index, detail) in repositoryDetails.enumerated() { if detail.id == repositoryDetail.id { self.repositoryDetails[index] = repositoryDetail for visibleCell in tableView.visibleCells { if (visibleCell as! ReposListCell).repositoryDetail.id == repositoryDetail.id { (visibleCell as! ReposListCell).repositoryDetail = repositoryDetail } } } } } }
      
      





そして、以前に作成したコントローラーのコントローラー拡張を作成することは残ります。



 extension ReposListViewController: ReposListCellDelegate { func starTapped(for cell: ReposListCell) { if cell.repositoryDetail.viewerHasStarred { viewModel.removeStar(for: cell.repositoryDetail.id) { [unowned self] repositoryDetail in self.updateTableView(for: repositoryDetail) } } else { viewModel.addStar(for: cell.repositoryDetail.id) { [unowned self] repositoryDetail in self.updateTableView(for: repositoryDetail) } } } }
      
      





できた! アプリケーションを起動して、上位20のリポジトリを検索し、星を配置/削除できます!



それだけではありません。 このライブラリをRxSwiftと組み合わせて使用​​し、ページネーションの問題を解決することは興味深いことです。Apolloはキャッシングもサポートしていますが、次回はすべてについてです!



このような問題を当社で解決したい場合は、ご参加ください! 質問と履歴書は、jobs @ tribuna.digitalに送信できます。



ここでもっと仕事を



All Articles