📚
【Swift】完全版URLSessionメモ 外部ライブラリ未使用で同期非同期通信、JSON、バイナリーファイルを想定【Xcode12】
はじめに
今記事ではSwiftで使われるWebAPI通信用の処理、URLSession
についてまとめました。
想定されるHTTP通信の使用パターン
- 非同期処理でJSON形式を取得する処理
- 非同期処理でバイナリーファイルを取得する処理
- 非同期処理でデータモデルにデコードして取得する処理
- 同期処理でJSON形式を取得する処理
- 同期処理でバイナリーファイルを取得する処理
- 同期処理でデータモデルにデコードして取得する処理
前提条件
JSON形式をデコードするために用意したデータ型クラスUsersInfo
が存在します。
また、エラーハンドリングの助けの為にNetworkError
が存在します。
// JSONの結果をデコードする為のクラス Decobableクラスを継承するとstruct/classをデコード可能にする。
struct UsersInfo : Decodable {
var id : Int
var name : String
}
// 失敗時には単一の戻り値しか用意できないのでErrorの中で分岐させる
enum NetworkError: Error {
case unknown
case invalidResponse // デコードなど失敗した場合は無効なレスポンスデータに分類する。
case invalidURL // 無効なURL
}
※今回使用するサンプルコードです。JSON形式で返ってきます。2個目はバイナリファイルの為に画像を用意しました。
let urlString = "http://www.json-generator.com/api/json/get/bQIUnAqOKW?indent=2"
let photoUrlString = "https://pbs.twimg.com/media/E2iq9orVgAMFxL0?format=jpg&name=large"
非同期処理でJSON形式を取得する処理
これからは呼び出し側と、処理側で2つ書いていきます
処理側はNerworkManagerクラスのメソッドで用意しております。
// 1)非同期処理でJSON形式を取得する処理
NetworkManager.shared.requestAsyncJson(urlstring:urlString , success: {(dictionary) in
// 通信成功時のクロージャ
print(dictionary)
}) { (error) in
// 通信失敗時のクロージャ
print(error)
}
// 1)非同期処理でJSON形式を取得する処理
func requestAsyncJson(urlstring: String,
success: @escaping (Dictionary<String, Any>) -> (), // 通信成功時のクロージャ
failure: @escaping (Error) -> ()){ // 通信失敗時のクロージャ
guard let url = URL(string: urlstring) else {
return failure(NetworkError.invalidURL)
}
var request = URLRequest(url: url) //Requestを生成
/*
// 下記のような特別な事を行わない場合は以降のrequest部分がURL型でも処理自体は問題なく動作する
// GET,POST,PUT,DELETEなどHTTPメソッドを変更する場合
request.httpMethod = "GET" // 何も設定しないとGETリクエストになるがコードのサンプルのため、明示的に書いている。
// Bodyを付与する場合
request.httpBody = "usedid=hoge&password=hoge1234".data(using: .utf8) // useridとpasswordを付与する場合
// Headerを付与する場合
request.setValue("application/json", forHTTPHeaderField: "Content-Type") //jsonでヘッダーに付与
request.allHTTPHeaderFields = ["usedid": "hoge","password": "hoge1234"]
// Queryを付与する場合
var urlComponents = URLComponents(string: "http://www.json-generator.com/api/json/get/bQIUnAqOKW?indent=2")! //URLComponentsでURLを生成
urlComponents.queryItems = [
URLQueryItem(name: "usedid", value: "hoge"),
URLQueryItem(name: "password", value: "hoge1234")
]
var request = URLRequest(url: urlComponents.url!) // Qurtyを付与する場合はこちらのrequestを使用する。
*/
URLSession.shared.dataTask(with: request, completionHandler: {data , response, error in
if let error = error {
print(error.localizedDescription)
failure(error)
return
}
guard let data = data,
let response = response as? HTTPURLResponse else {
print("データもしくはレスポンスがnilの状態です")
failure(NetworkError.unknown)
return
}
if response.statusCode == 200 {
do {
let object = try JSONSerialization.jsonObject(with: data, options: []) as! Dictionary<String, Any>
print(object["id"] as! Int) // 辞書型に変換したJSONから値id(Int)を取り出す。
print(object["name"] as! String) // 辞書型に変換したJSONから値name(String)を取り出す。
// コールバックを行う
success(object)
} catch let error {
failure(error)
}
} else {
print("statusCode:\(response.statusCode)")
failure(NetworkError.unknown)
}
}).resume()
}
非同期処理でバイナリーファイルを取得し、UIImage型に変換する処理
NetworkManager.shared.requestAsyncJson(urlstring:photoUrlString , success: {(image) in
// 通信成功時のクロージャ
print(image)
}) { (error) in
// 通信失敗時のクロージャ
print(error)
}
// 2)非同期処理でバイナリーファイルを取得する処理
func requestAsyncBinaryFile(urlstring: String,
success: @escaping (UIImage) -> (), // 通信成功時のクロージャ
failure: @escaping (Error) -> ()){ // 通信失敗時のクロージャ
guard let url = URL(string: urlstring) else {
return failure(NetworkError.invalidURL)
}
var request = URLRequest(url: url) //Requestを生成
/*
// 下記のような特別な事を行わない場合は以降のrequest部分がURL型でも処理自体は問題なく動作する
// GET,POST,PUT,DELETEなどHTTPメソッドを変更する場合
request.httpMethod = "GET" // 何も設定しないとGETリクエストになるがコードのサンプルのため、明示的に書いている。
// Bodyを付与する場合
request.httpBody = "usedid=hoge&password=hoge1234".data(using: .utf8) // useridとpasswordを付与する場合
// Headerを付与する場合
request.setValue("application/json", forHTTPHeaderField: "Content-Type") //jsonでヘッダーに付与
request.allHTTPHeaderFields = ["usedid": "hoge","password": "hoge1234"]
// Queryを付与する場合
var urlComponents = URLComponents(string: "http://www.json-generator.com/api/json/get/bQIUnAqOKW?indent=2")! //URLComponentsでURLを生成
urlComponents.queryItems = [
URLQueryItem(name: "usedid", value: "hoge"),
URLQueryItem(name: "password", value: "hoge1234")
]
var request = URLRequest(url: urlComponents.url!) // Qurtyを付与する場合はこちらのrequestを使用する。
*/
URLSession.shared.dataTask(with: request, completionHandler: {data , response, error in
if let error = error {
print(error.localizedDescription)
failure(error)
return
}
guard let data = data,
let response = response as? HTTPURLResponse else {
print("データもしくはレスポンスがnilの状態です")
failure(NetworkError.unknown)
return
}
if response.statusCode == 200 {
guard let image = UIImage(data: data) else {
failure(NetworkError.invalidResponse)
return
}
success(image)
} else {
print("statusCode:\(response.statusCode)")
failure(NetworkError.unknown)
}
}).resume()
}
非同期処理でデータモデルにデコードして取得する処理
NetworkManager.shared.requestAsyncDecodeDatamodel(urlstring:urlString , success: {(userinfo) in
// 通信成功時のクロージャ
print(userinfo)
}) { (error) in
// 通信失敗時のクロージャ
print(error)
}
// 3)非同期処理でデータモデルにデコードして取得する処理
func requestAsyncDecodeDatamodel(urlstring: String,
success: @escaping (UsersInfo) -> (), // 通信成功時のクロージャ
failure: @escaping (Error) -> ()){ // 通信失敗時のクロージャ
guard let url = URL(string: urlstring) else {
return failure(NetworkError.invalidURL)
}
var request = URLRequest(url: url) //Requestを生成
/*
// 下記のような特別な事を行わない場合は以降のrequest部分がURL型でも処理自体は問題なく動作する
// GET,POST,PUT,DELETEなどHTTPメソッドを変更する場合
request.httpMethod = "GET" // 何も設定しないとGETリクエストになるがコードのサンプルのため、明示的に書いている。
// Bodyを付与する場合
request.httpBody = "usedid=hoge&password=hoge1234".data(using: .utf8) // useridとpasswordを付与する場合
// Headerを付与する場合
request.setValue("application/json", forHTTPHeaderField: "Content-Type") //jsonでヘッダーに付与
request.allHTTPHeaderFields = ["usedid": "hoge","password": "hoge1234"]
// Queryを付与する場合
var urlComponents = URLComponents(string: "http://www.json-generator.com/api/json/get/bQIUnAqOKW?indent=2")! //URLComponentsでURLを生成
urlComponents.queryItems = [
URLQueryItem(name: "usedid", value: "hoge"),
URLQueryItem(name: "password", value: "hoge1234")
]
var request = URLRequest(url: urlComponents.url!) // Qurtyを付与する場合はこちらのrequestを使用する。
*/
URLSession.shared.dataTask(with: request, completionHandler: {data , response, error in
if let error = error {
print(error.localizedDescription)
failure(error)
return
}
guard let data = data,
let response = response as? HTTPURLResponse else {
print("データもしくはレスポンスがnilの状態です")
failure(NetworkError.unknown)
return
}
if response.statusCode == 200 {
do {
// 結果:UserInfo構造体(Struct)を取得できる -> UserInfo
let userinfo = try JSONDecoder().decode(UsersInfo.self, from: data)
success(userinfo)
} catch let error {
failure(NetworkError.invalidResponse)
print(":エラー:\(error)") // JSONの値がIDがIntなのに、StructでIDをStringと宣言している時などエラーになる。
}
} else {
print("statusCode:\(response.statusCode)")
failure(NetworkError.unknown)
}
}).resume()
}
同期処理でJSON形式を取得する処理
let semaphore = DispatchSemaphore(value: 0)
NetworkManager.shared.requestAsyncJson(urlstring:urlString , success: {(dictionary) in
// 通信成功時のクロージャ
print(dictionary)
semaphore.signal()
}) { (error) in
// 通信失敗時のクロージャ
print(error)
semaphore.signal()
}
semaphore.wait()
同期処理でバイナリーファイルを取得する処理
let semaphoreHoge = DispatchSemaphore(value: 0)
NetworkManager.shared.requestAsyncJson(urlstring:photoUrlString , success: {(image) in
// 通信成功時のクロージャ
print(image)
semaphoreHoge.signal()
}) { (error) in
// 通信失敗時のクロージャ
print(error)
semaphoreHoge.signal()
}
semaphoreHoge.wait()
同期処理でデータモデルにデコードして取得する処理
let semaphorefuga = DispatchSemaphore(value: 0)
NetworkManager.shared.requestAsyncDecodeDatamodel(urlstring:urlString , success: {(userinfo) in
// 通信成功時のクロージャ
print(userinfo)
semaphorefuga.signal()
}) { (error) in
// 通信失敗時のクロージャ
print(error)
semaphorefuga.signal()
}
semaphorefuga.wait()
参考
こちらの記事を複合的に参考しました。
Discussion