swift

【APIKit】新プロトコルのCodableで通信処理を行う 基本編

APIKitと言うライブラリを用いた通信処理の実装手順と共に、Codableと言うプロトコルについての解説もしています。

Jsonの返り値を入力すれば自動でモデルを定義するコードを生成してくれるWebサービスです。Quicktype

今回はQiitaのAPIを使って記事情報を取得するという処理を実装する手順を説明していきます。WebAPIの仕様について予め理解しておく必要があります。

ポイント

これから通信処理についての解説をするにあたって、次の二つに焦点を当てて解説していきます、

  • Codable
    確かSwift4で登場したプロトコルです。Codableの中身はEncodableDecodableの特徴を兼ね備えたものとして実装されています。

    protocol Codable: Encodable && Decodable {}
  • APIKit
    自力での実装だと手間がかかる通信処理を簡単に行うためのライブラリです。carthageでインストールします。ちなみに製作者は日本人です。carthageの使い方についてはこちらを参照してください。
手順

どのライブラリを使おうと基本的に次のような流れで実装することになります。

  1. 受け取るデータモデルを定義する
  2. リクエストを定義する
  3. ViewControllerでリクエストを送信する

この流れに沿って解説をしていきます。

1. 受け取るデータモデルを定義する

まずは受け取るであろうデータモデルを構造体として定義します。Codableに準拠させることで自動的にマッピングを行ってくれます。但し、レスポンスとして返ってくるjsonデータと定義しているプロパティ名が一致している必要があります。

//記事のデータ構造を定義
struct Article: Codable {
    let title: String
    let url: String
    let user: User  //プロパティが入れ子になっている場合は別個で構造体を定義する↓
}

struct User: Codable {
    let name: String
    let profile_image_url: String
}

2. APIリクエストを定義する

リクエストを送る際に必要な情報は次の通りです。

  • httpメソッド
  • url(APIKitではbaseURLpathの組み合わせで決定されます)
  • レスポンスのデータ構造
  • データの加工とエラーハンドリング

これらをコードで定義します。

//FetchQiitaArticleRequest.swift
import APIKit

struct FetchQiitaArticleRequest: Request {
  	//レスポンスのデータ構造
  	typealias Response = [Article]

  	//リクエスト先のurl
    var baseURL: URL {
        return URL(string: "https://qiita.com/api/v2/")!
    }
  
    var path: String {
        return "items"
    }
    
  
  //httpメソッド
    var method: HTTPMethod {
        return .get
    }
  
  //レスポンスデータのデコード
    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
        guard let data = object as? Data else {
            throw ResponseError.unexpectedObject(object)
        }
        return try JSONDecoder().decode(Response.self, from: data)  //Rssponse.self→[Article]
    }
}
DataParserのカスタム

Codableによる自動マッピングを利用する際は、APIKitで実装されているDataParserを少し修正したものを使います。DecodableDataparserとし、ParseにはAPIKitのDataParserの代わりにこれを噛ませます。

//DecodableDataPareser.swift

import APIKit

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

    func parse(data: Data) throws -> Any {
        return data  //parseを行わずにそのままデータを返す
    }
}

先ほど定義したFetchQiitaArticleRequestに追加します。

import APIKit

struct FetchQiitaArticleRequest: Request {

  ///  ....省略
  
      var dataParser: DataParser {
        return DecodableDataParser()
    }
}

3. リクエストを送る

実際にリクエストを送ります。リクエストはSession.send()で送ることができます。

open class func send<Request>(_ request: Request, callbackQueue: APIKit.CallbackQueue? = nil, handler: @escaping (Result<Request.Response, APIKit.SessionTaskError>) -> Void = { _ in }) -> APIKit.SessionTask? where Request : APIKit.Request

このような実装になっていますが、とりあえず引数としてRequest型をとっていることがわかれば良いです。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.        
      
      //リクエストを定数として定義する
        let request = FetchQiitaArticleRequest()
 
      //Session.sendを呼び出す
        Session.send(request) { result in
            switch result {
            case .success(let response):
              print(response)

            case .failure(let error):
                print(error)
            }
        }
    }
}
成功した場合
失敗した場合
補足

わかりやすさを考慮して省いた部分を補足として載せておきます。クエリパラメータについての解説は役立つと思います。

共通部分のプロトコル化

QiitaAPIというプロトコルを定義し、そちらに

//  QiitaAPI.swift

import APIKit

protocol QiitaAPI: Request { }

extension QiitaAPI {
    var baseURL: URL {
        return URL(string: "https://qiita.com/api/v2/")!
    }
}

extension QiitaAPI where Response: Decodable {
    var dataParser: DataParser {
        return DecodableDataParser()
    }

    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
        guard let data = object as? Data else {
            throw ResponseError.unexpectedObject(object)
        }
        return try JSONDecoder().decode(Response.self, from: data)  //Rssponse.self→[Article]
    }
}

定義したプロトコルQiitaAPIをFetchQiitaArticleRequestに適応させます。そうすることで記述量は少なくなります。

import APIKit

struct FetchQiitaArticleRequest: QiitaAPI {
  	//レスポンスのデータ構造
  	typealias Response = [Article]

  	//リクエスト先のurl  
    var path: String {
        return "items"
    }
  
  //httpメソッド
    var method: HTTPMethod {
        return .get
    }
}

リクエストヘッダ・クエリパラメーターの付与

クエリパラメータを付与します。

import APIKit

struct FetchQiitaArticleRequest: QiitaAPI {
    typealias Response = [Article]

	var method: HTTPMethod {
    	return .get
	}

	var path: String {
    return "items"
	}

  let query: String?
  var queryParameters: [String : Any]? {

   guard let query = query else {
       return ["page": 1, "per_page": 20 ]
   }
        return ["query": "title: \(query)"]
   }
}

読んでいただきありがとうございます!!

記事として取り上げたトピックを体系化してまとめた内容を電子書籍として販売しています。購入していただくことで執筆の応援ができます。詳細はこちらから