あたも技術ブログ

セットジャパンコーポレーションの社員が運営しています。

【iOS】Alamofire + Himotokiでいい感じのAPIクライアントを作る

どうも、社内開発でiOSを担当してる朝倉です。

現在、夏期休業中ですが、そういえばブログ更新しないといけないなと思い出したので書きます。

Alamofire + HimotokiでAPIクライアントを作るということで、n番煎じだしもっといい方法がネット上にはゴロゴロある気がするのですが、 現状、(僕が)いい感じだと思っている実装を書いてみたいと思います。

導入

CocoaPodsでAlamofireとHimotokiを導入します。

「そんなの知ってるよ」とかCarthage派の人やSPM派の人は読み飛ばしてください。

ターミナルでプロジェクトディレクトリへ移動し、

pod init

Podfileが作成されるので以下の2行を追加します。

pod 'Alamofire', '~> 4.4'
pod 'Himotoki', '~> 3.0'

このようになればOKです。

target 'プロジェクト名' do
  use_frameworks!
  pod 'Alamofire', '~> 4.4'
  pod 'Himotoki', '~> 3.0'
end

Podfileを保存し、

pod install

これで、AlamofireとHimotokiがプロジェクトに追加されたので、プロジェクト名.xcworkspaceファイルを開きます。

APIクライアントを作る

今回は、GitHubAPIを使用します。 まずは共通の通信部分を作成します。

import Alamofire
import Himotoki

enum Result<T> {
    case Success(T)
    case Error(Error)
}

protocol APIRequest {
    var baseeUrl: String { get }
    var headerTemplate: [String : String] { get }
    
    associatedtype Response
    var path: String { get }
    var method: HTTPMethod { get }
    
    var parameters: [String : Any]? { get set }
    var headers: [String : String]? { get set }
    
    func response(from object: Any) throws -> Self.Response
    
}

extension APIRequest {
    var baseeUrl: String {
        return "https://api.github.com"
    }
    var headerTemplate: [String : String] {
        return ["X-Requested-With" : "XMLHttpRequest"]
    }
    var parameters: Any? {
        return nil
    }
    var headers: [String : String]? {
        return nil
    }
    
    func request(finished: @escaping (Result<Self.Response>)->Void) {
        let encoding = JSONEncoding.default
        var header = headerTemplate as Dictionary<String, String>
        headerTemplate.forEach() {
            header[$0.0] = $0.1
        }
        
        let encodedURLString = (baseeUrl + path).addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
        
        Alamofire.request(encodedURLString!, method: method, parameters: parameters, encoding: encoding, headers: header).responseJSON { response in
            switch response.result {
            case .success(let value):
                print(value)
                do {
                let res = try self.response(from: value)
                    finished(Result.Success(res))
                } catch {
                    finished(Result.Error(error))
                }
            case .failure(let error):
                finished(Result<Self.Response>.Error(error))
            }
        }
    }
}

続いて、Decodableプロトコルを適応したモデルとAPIRequestプロトコルを適応したリクエストオブジェクトを作成していきます。

ユーザのリポジトリを取得する

import Himotoki
import Alamofire

struct Repository: Decodable {
    let fullName: String
    let ownerAvatarUrl: String
    let language: String?
    let url: String
    let htmlUrl: String
    
    static func decode(_ e: Extractor) throws -> Repository {
        return try Repository(
            fullName: e <| "full_name",
            ownerAvatarUrl: e <| ["owner", "avatar_url"],
            language: e <|? "language",
            url: e <| "url",
            htmlUrl: e <| "html_url"
        )
    }
}

struct  UserRepository: APIRequest {
    var headers: [String : String]? = nil
    var parameters: [String : Any]? = nil
    var userName: String
    
    var path: String {
        return "/users/\(self.userName)/repos"
    }
    var method: HTTPMethod {
        return .get
    }
    typealias Response = [Repository]
    
    init(userName: String) {
        self.userName = userName
    }
    
    func response(from object: Any) throws -> Response {
        return try decodeArray(object)
    }
}

今後、エンドポイントが増えた場合にはDecodableプロトコルを適応したレスポンスモデルとAPIRequestプロトコルを適応したリクエストオブジェクトを増やしていけばいいだけなのでお手軽です。

使い方

UserRepository(userName: "asashin227").request() { result in
    switch result {
    case .Success(let responce):
        print(responce)
    case .Error(let error):
        print(error.localizedDescription)
    }
}

ソース

とりあえずGitHubにあげたので参考にご利用ください。

github.com