【Swift】API関連 0->1
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:)イニシャライザを独自に実装することでできるっちゃできる。
わからん
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
.....
リクエスト(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でいいよね
リクエスト(Data)
わからん
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
}
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経由でのデータ送信、取得
- URLSessionDataTask
- 基本タスク
- バックグラウンドで動作できず
- サーバから受け取るデータはメモリに保存する
- 短時間での小さいデータのやりとりを想定している
- URLSessionUploadTask
- アップロード用
- バックグラウンド動作可能
- 時間のかかる通信にも適している
- URLSessionDownloadTask
- ダウンロード用
- バックグラウンド動作可能
- 時間のかかる通信にも適している
- ダウンロードしたデータをファイルに保存する
- メモリ容量を抑えつつ大きいデータを受け取ることができる
同期・非同期
-
同期通信
サーバのレスポンスが来るまで後処理を待つ -
非同期通信
待たないで後続処理を行い、リクエストが返ってきた段階で割込処理を行う
URLSessionを使ったHTTP通信は非同期通信になる
let uploadSession = URLSession(configuration: .default, delegate: self, delegateQueue: .main)
let uploadTask = uploadSession.uploadTask(withStreamedRequest: urlRequest)
taskArray.append(uploadTask)
uploadTask.resume()