読者です 読者をやめる 読者になる 読者になる

【swift】api通信処理を共通化/エラー処理もちゃんとやる

swift ios iPhoneアプリ開発

はじめに

以前、こちらで同じようなことを書いたのですが。
そのときはまだswiftやりたてというのとエラー処理とかちゃんと考慮できてなかったので今回もう1度ちゃんとまとめてみようと思いました

実装したい機能としては以下
>サーバーからのレスポンス

array(
    "status" => "OK" // または"NG" バリデーション等でエラーの場合はNGをかえす
    "result" => array(
        "friends" => array("id" => "1", "友達1", ・・・)
    ),
)

こんな感じでjson形式でかえす

>クライアント側
・サーバーエラー(HTTPステータス200以外)に対応
・タイムタウトに対応
・アプリケーションエラーに対応(処理としては正常だけどアプリケーションがNGを返す)
・エラー時はデフォルトで、アラートダイアログまたはトーストを表示

ライブラリのインストール

今回つかったライブラリは以下
・AFNetworking
・Toast

Podfileを作って、pod installを実行

use_frameworks!
pod 'AFNetworking'
pod 'Toast'

実装

ディレクトリ構成

├── Api
│   ├── ApiBase.swift // 共通処理をココにまとめる
│   ├── ApiSample.swift // 用途ごとにファイルを追加していく
│   ├── ・・・
├── Controller
│   ├── ViewController.swift // Api以下のクラスを呼び出してAPIへアクセス


ApiBase.swift

import UIKit
import AFNetworking
import Toast

class ApiBase: NSObject {
    
    let API_URL = "http://example.com"
    let API_USER = "user"
    let API_PASS = "pass"
    let API_TIMEOUT: Double = 20 // タイムアウト

    func callApi(callback:((result: Dictionary<String, AnyObject>?)->Void)?, callbackAppError:(()->Void)?, callbackStatusError:(()->Void)?, errorType: Int?, url: String, params: Dictionary<String,AnyObject>?) {
        
        let manager: AFHTTPRequestOperationManager = AFHTTPRequestOperationManager()
        manager.requestSerializer.setAuthorizationHeaderFieldWithUsername(API_USER, password: API_PASS)
        manager.requestSerializer.timeoutInterval = API_TIMEOUT
        manager.POST(API_URL + url, parameters: params,
            success: {(operation: AFHTTPRequestOperation!, responsobject: AnyObject!) in
                
                if let status = responsobject["status"] as? String {
                    if status == "OK" {
                        callback?(result: responsobject["result"] as? Dictionary<String, AnyObject>)
                    } else if status == "NG" {
                        if let message = responsobject["message"] as? String {
                            if 0 < message.characters.count {
                                if errorType == 1 {
                                    self.showAlert(message)
                                } else if errorType == 2 {
                                    self.showToast(message)
                                }
                            }
                        }
                        callbackAppError?()
                    }
                }
            },
            failure: {(operation: AFHTTPRequestOperation?, error: NSError?) in
                let message = "サーバーに接続できませんでした"
                if errorType == 1 {
                    self.showAlert(message)
                } else if errorType == 2 {
                    self.showToast(message)
                }
                callbackStatusError?()
            }
        )
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertView(title: "エラー", message: message, delegate: self, cancelButtonTitle: "OK")
        alert.show()
    }
    
    private func showToast(message: String) {
        let vc = UIApplication.sharedApplication().keyWindow?.rootViewController
        vc?.view.makeToast(message, duration: 1.0, position: CSToastPositionBottom)
    }
}

Api***.swiftはこのクラスを継承して追加してゆく
・エラー時のコールバックはそれぞれ必要であれば実装する形とする

ApiSample.swift

import UIKit

class ApiSample: ApiBase {

    // 正常の場合のみコールバック指定
    func get(callback:(result: Dictionary<String,AnyObject>?)->Void, params: Dictionary<String,AnyObject>?) {
        callApi({ (result) -> Void in
            callback(result: result)
            }, callbackAppError: nil, callbackStatusError: nil, errorType: 2, url: "/test.php", params: params)
    }
    
    // 正常、エラーの場合でコールバック指定
    func get(callback:(result: Dictionary<String,AnyObject>?)->Void, callbackAppError:()->Void, callbackStatusError:()->Void, params: Dictionary<String,AnyObject>?) {
        callApi({ (result) -> Void in
            callback(result: result)
            }, callbackAppError: { () -> Void in
                callbackAppError()
            }, callbackStatusError: { () -> Void in
                callbackStatusError()
            }, errorType: 1, url: "/test.php", params: params)
    }
}
||< 

ViewController.swift
>||
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        ApiSample().get({ (result) -> Void in
            print(result)
            }, params: nil)
    }
}

さいごに

実際にはデータの乖離などが起きないようにModelクラスを作ってModelからApi***を呼び出す。
ViewControllerからModelを呼び出す。
みたいな形になるかと思われますが、今回はViewControllerから直接Apiを呼び出しています。
それについても今度まとめてみたいと思います

以上です