ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

【iOS】APIKitを使ったXML取得

まえがき

初めまして。ペンギン村の通行人po_miyasakaです。

APIKitを使用してGoogle Suggest APIからXMLを取得する方法をまとめました。

結果

"hello"という文字に対するサジェスト結果をパースしてテーブルに表示

xml取得部分はこんな感じのコードになりました。

import APIKit
import SwiftyXMLParser

/* リクエスト */
struct GoogleSuggestionRequest: Request {
    typealias Response = [String]
    var baseURL: URL = URL(string: "https://www.google.com")!
    
    var path: String = "/complete/search"
    
    var method: HTTPMethod = .get
    
    var parameters: Any? = ["q": "hello", "hl": "ja", "output": "toolbar"]
    
    var dataParser: DataParser = MyXMLParser()
    
    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> [String] {
        return object as! [String] // 強気😼
    }
}

/* DataParser実装クラス */
class MyXMLParser: DataParser {
    public var contentType: String? {
        return "application/xml"
    }
    
    public func parse(data: Data) throws -> Any {
        guard let xmlString = String(data: data, encoding: .shiftJIS) else {throw ResponseError.unexpectedObject("文字列にキャストできなかった。")}
        let xml = try SwiftyXMLParser.XML.parse(xmlString)
        let suggestion: [String] = xml["toplevel", "CompleteSuggestion"].map{ $0["suggestion"]}.flatMap{$0.attributes["data"]}
        if suggestion.isEmpty {
            throw ResponseError.unexpectedObject("候補なし")
        }
        return suggestion
    }
}



/* 適当な通信するクラス */
class Connector {
    static func getSuggestion() {
        let request =  GoogleSuggestionRequest()
        Session.send(request){result in
            switch result {
            case .success(let suggestion):
                print(suggestion) // 成功した時の結果
            case .failure(let error):
                print(error) // 通信、パースのエラーはここでハンドリング
            }
        }
    }
}

つまづいたポイント

"https"から始まるURLじゃないと通信できない。

AppleによるATS(App Transport Security)のため、
”http“で始まるURLには原則アクセスできません。
以下のようなエラーログが出力されます。

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.String(data: data, encoding: .shiftJIS)

独自のXMLパーサーを実装する必要がある。

APIKitのRequestプロトコルはdataParserというプロパティを持っています。
(APIKitのDocumentにはたぶん記載がなく、コードを見て知りました。)
明示的に指定しない場合はデフォルトでJSONのパースを実行するようになっています。
(パースに失敗するとRequestクラスのresponseメソッドは実行されません。)

デフォルト実装のままxmlを取得するとパースに失敗し以下のようなエラーとなります。

responseError(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text

XMLを取得する場合はdataParserプロパティにDataParserプロトコルに準拠した独自のXMLParserを代入する必要があります。

var dataParser: DataParser = MyXMLParser()

また、今回はSwiftyXMLParserというライブラリを使用しXMLをパースしました。

文字エンコーディングに気をつけよう

取得したData型をString型に直す時は文字エンコーディングの形式を指定する必要があります。

String(data: data, encoding: .shiftJIS)

指定するエンコーディングはresponseメソッドの引数のurlResponseに格納されており、 print(urlResponse.textEncodingName)のようにして確認しました。