Closed8

【Swift】API関連 0->1

yoshitakayoshitaka

JSONEncoderとDecoder

基本

import Foundation

let encoder = JSONEncoder()
let decoder = JSONDecoder()

let encoded = try encoder.encode(["key" : "value"])
let jsonString = String(data: encoded, encoding: .utf8)!

let decoded = try decoder.decode([String: String].self, from: encoded)

Codable

struct SomeStruct: Codable {
    let value: int
}
let someStruct = SomeStruct(value: 1)
let encodedJSONData = try! jsonEncoder.encode(someStruct)
let decodedSomeStruct = try! jsonDecoder.decode(SomeStruct.self, from: encodedJSONData)

Codableプロトコルに準拠することで、コンパイラがコードを自動生成する。
例えば、Codableプロトコルに準拠させられない場合は、encode(to:)メソッドとinit(from:)イニシャライザを独自に実装することでできるっちゃできる。

yoshitakayoshitaka

わからん

var baseURL: URL {
    return URL.baseURL
}

あ、これか

extension URL {
    static var baseURL: URL {
        return URL(string: "https://\(apiHoseName)/......API")!
    }
}

で真ん中のは状況によってか

#if DEBUG
    let apiHostName = "........co.jp"
#elseif ****
    let apiHostName = "........co.jp"
#elseif ****
    let apiHostName = "........co.jp"
#else
    let apiHostName = ".....co.jp"
#endif
.....
yoshitakayoshitaka

リクエスト(URL)

private let json: [String: Any]

func toData() throws -> Data {
    let string = self.sting(from: json)
    return string.data(using: String.Encoding.utf8, allowLossyConversion: false) ?? Data()
}

なんだ allowLossyConversion: って

data(using:allowLossyConversion:)

Returns an NSData object containing a representation of the receiver encoded using a given encoding.

Declaration

func data(using encoding: UInt, 
allowLossyConversion lossy: Bool) -> Data?

Parameters

encoding
A string encoding. For possible values, see NSStringEncoding.
flag
If true, then allows characters to be removed or altered in conversion.

つまりTrueならコレがÁ’ becomes コレ‘A’, になったりする。(アクセントがない)

func string(from dictionary: [String: Any]) -> String {
    let pairs = dictionary.map { key , value -> String in 
         if value is nil {
            return "\(key)"
        }
        if let array = value as? [String] {
            let arrayPairs = array.map {
                return "\(key)=\($0.urlEncoded)"
            }
            return arrayPairs.joined(separator: "&")
        } else {
           let valueAsString = (value as? String) ?? "\(value)"
           return "\(key)=\(valueAsString.urlEncoded)"
        }
    }
    return pair.joined(separator: "&")
}

dictionaryのデータを文字列に変換しているのか
NSNull は nilでいいよね
https://stackoverflow.com/questions/25575030/how-to-convert-nsnull-to-nil-in-swift

yoshitakayoshitaka

わからん

public protocol RequestBody {
    var contentType: String { get }
    func toData() throws -> Data
}

public protocol RequestProtocol {
    associatedtype Response
    var body: RequestBody? { get }
    func processRequest( _ request: URLRequest) throws -> URLRequest
    func processResponse(data: Data, response: HTTPURLResponse) throws -> Data
    func decodeResponse(from data: Data, response: HTTPURLResponse) throws -> Response
}
yoshitakayoshitaka

HTTP通信

URLRequest リクエスト情報の表現

let url = URL(string: "https://api.github.com/search/repositories?q=swift")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")

URLRequestというクラスは、urlオブジェクトを渡すと、上記のHTTPリクエストを生成してくれる。
URLRequestはデフォルトではGETメソッドになっている。
*複雑なデータをプログラマが扱いやすいようにラップしてくれるクラスをラッパークラスと呼ぶ。
コレがURLSessionで実際にサーバーに送信される時には、

GET /search/repositories HTTP/1.1
Host: api.github.com
Accept: application/json
Accept-Encoding: gzip, deflate
Accept-Lamguage: ja-jp
Connection: keep-alive
User-Agent: Demo/1 CFNetwork/760.1.2 Darwin/15.0.0 (x86_64)

ようになるとか、ならないとか
URLSessionによって自動生成されるものが多い

HTTPURLResponse HTTPレスポンスのメタデータ

ex)HTTPレスポンス

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Date: Thu, 16 Jan 2020 14:02:31 GMT
.......
let url = URL(string: "https://api.github.com/search/repositories?q=swift")!
var urlRequest = URLRequest(url: url)

let session = URLSession.shared
// デフォルト値が設定されたインスタンスを取得
//やっていることは
//var config = URLSessionConfiguration.default
//let session = URLSession(configuration: config)
//と一緒
// キャッシュやCookieなどの動作をより細かく設定したい場合は、新規のインスタンスを生成する事も可
let task = session.dataTask(with: urlRequest) { data, urlResponse, error in
    if let urlResponse = urlRequest as? HTTPURLResponse {
        urlResponse.statusCode //200
        urlResponse.allHeaderFields["Date"] //"Thu, 16 Jan....
        urlResponse.allHeaderFields["Content-Type"] //"application/json; cha....
    }
//func dataTask(with request: URLRequest, 
//completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
//定義はこれ
//1つめの引数にHTTPリクエストオブジェクトを、
//2つめの引数に「completionHandler」というクロージャをとって、
//戻り値にURLSessionDataTaskオブジェクトを返すメソッドです。
//completionHandlerはめ、通信が終了した後の完了処理をするためのクロージャ
//completionHandlerは3つの引数をとって、戻り値を返さないクロージャ
//このクロージャが非同期処理のキモ
//URLSessionは、サーバ通信が終わると、このcompletionHandlerを実行する
//@escapingは「関数に引数として渡されたクロージャが、
//関数のスコープ外で保持される可能性があることを示す属性」
}
task.resume()
// 戻り値のURLSessionDataTaskクラスのインスタンスに対してresume()メソッド呼び出しで実行

コールバックのURLResponse型はHTTPURLResponse型のスーパークラスで
HTTPに限らないレスポンス。HTTPの場合は、HTTPURLResponse型にダウンキャスト。
APIから非同期で受け取ったデータは、この中でDecodeしてCodableな構造体に入れて扱う。
本来は、dateをチェックしてからクラススコープの変数にでも入れておいてあとで処理する形にするのか

URLSession URL経由でのデータ送信、取得

  1. URLSessionDataTask
    • 基本タスク
    • バックグラウンドで動作できず
    • サーバから受け取るデータはメモリに保存する
    • 短時間での小さいデータのやりとりを想定している
  2. URLSessionUploadTask
    • アップロード用
    • バックグラウンド動作可能
    • 時間のかかる通信にも適している
  3. URLSessionDownloadTask
    • ダウンロード用
    • バックグラウンド動作可能
    • 時間のかかる通信にも適している
    • ダウンロードしたデータをファイルに保存する
    • メモリ容量を抑えつつ大きいデータを受け取ることができる

同期・非同期

  • 同期通信
    サーバのレスポンスが来るまで後処理を待つ

  • 非同期通信
    待たないで後続処理を行い、リクエストが返ってきた段階で割込処理を行う
    URLSessionを使ったHTTP通信は非同期通信になる

yoshitakayoshitaka
        let uploadSession = URLSession(configuration: .default, delegate: self, delegateQueue: .main)
        let uploadTask = uploadSession.uploadTask(withStreamedRequest: urlRequest)
        taskArray.append(uploadTask)
        uploadTask.resume()
このスクラップは2021/03/05にクローズされました