【Swift】AlamofireでAPI通信をする


はじめに

今回はQiitaAPIを叩いてAlamofireで通信していみたいと思います。

作るもの

GitHub

実装

モデル

struct Article: Codable {
    let title: String
    let user: User
}

struct User: Codable {
    let id: String
}

コントローラー

final class ArticleListViewController: UIViewController {

    @IBOutlet private weak var tableView: UITableView!
    private var articles = [Article]()

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        getArticles()

    }

}

private extension ArticleListViewController {

    func getArticles() {
        APIClient().request { result in
            switch result {
                case .success(let articles):
                    self.articles = articles
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                case .failure(let error):
                    self.showAPIAlert(error: error)
            }
        }
    }

    func showAPIAlert(error: APIError) {
        let alert = UIAlertController(title: error.title, message: nil, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "閉じる", style: .cancel, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }

}

// MARK: - UITableViewDataSource
extension ArticleListViewController: UITableViewDataSource {

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = articles[indexPath.row].title
        return cell
    }

}

API

import Alamofire

typealias ResultHandler<T> = (Result<T, APIError>) -> Void

struct APIClient {

    func request(handler: @escaping ResultHandler<[Article]>) {
        let urlString = "https://qiita.com/api/v2/items"
        let url = URL(string: urlString)
        guard let url = URL(string: urlString) else {
            handler(.failure(.invalidURL))
            return
        }
        AF.request(urlString)
            .responseJSON { response in
                guard let data = response.data else {
                    handler(.failure(.invalidResponse))
                    return
                }
                do {
                    let articles = try JSONDecoder().decode([Article].self, from: data)
                    handler(.success(articles))
                } catch {
                    handler(.failure(.unknown(error)))
                }
            }
    }

}

その他

enum APIError: Error {
    case invalidURL
    case invalidResponse
    case unknown(Error)
}

extension APIError {

    var title: String {
        switch self {
            case .invalidResponse: return "無効なレスポンスです。"
            case .invalidURL: return "無効なURLです。"
            case .unknown(let error): return "予期せぬエラーが発生しました。\(error)"
        }
    }

}

解説

大切なところのみ解説します。
本来はurlをそのまま書いておいたりするのは良くないのですが、今回はAamofireの解説ということで、省略します。

ここでリクエストを送信しています。

AF.request(urlString)

以下のようにして、通信を行います。

APIClient().request { result in
    switch result {
        case .success(let articles):
            self.articles = articles
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        case .failure(let error):
            self.showAPIAlert(error: error)
    }
}

URLSessionとの比較

let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let error = error {
        handler(.failure(.unknown(error)))
        return
    }
    if let data = data {
        do {
            let articles = try JSONDecoder().decode([Article].self, from: data)
            handler(.success(articles))
        } catch {
            handler(.failure(.unknown(error)))
        }
    }
}
task.resume()

Alamofireの方が明らかに簡潔にかけていますね。

おわりに

終わりです。