これは、犬の写真を取得する既存のコードです。
URLSessionで completionHandlerベースの便利なメソッドを使用しています。
コードは簡単なように見えて、私のテストでうまくいきましたが、少なくとも3つの間違いがあります。流れ順に見てみましょう。
dataTask を作成して resume します。そして、タスクが完了したら、completionHandler で応答を確認し、画像を作成し終了します。前後しながら流れていきます。
スレッドはどうでしょう。
小さなコードなのに驚くほど複雑で合計で3つの異なる実行コンテキストがあります。
最も外側のレイヤーは呼び出し元のスレッドまたはキューで実行され、completionHandler はセッションのデリゲートキューで実行され、最後に completionHandler はメインキューで実行されます。コンパイラでは捕捉できないので、スレッドの問題を避けるために細心の注意を払う必要があります。
今気づきましたが、completionHandler の呼び出しは、メインキューに一貫してディスパッチされません。これはバグかもしれません。
また、早期リターンをしていないのでエラーが発生した場合、completionHandler を2回呼び出すことになります。これは、作成者の意図と違う可能性があります。
また、最後の UIImage の作成は失敗する可能性があります。データが誤った形式の場合、この UIImage は nil を返すので、nil 画像と nil エラーの両方で completionHandler を呼び出すでしょう。
💡 新しい API
URLSession.shared.data()
URLSession.shared.upload()
URLSession.shared.download()
URLSession.shared.bytes()
👉 URLSession | Apple Developer Documentation
以下、参考にしたい新しいAPIのサンプルコードです。
// Fetch photo with async/await
func fetchPhoto(url: URL) async throws -> UIImage {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw WoofError.invalidServerResponse
}
guard let image = UIImage(data: data) else {
throw WoofError.unsupportedImage
}
return image
}
// URLSession.data
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200
else {
throw MyNetworkingError.invalidServerResponse
}
// URLSession.upload
var request = URLRequest(url: url)
request.httpMethod = "POST"
let (data, response) = try await URLSession.shared.upload(for: request, fromFile: fileURL)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 201
else {
throw MyNetworkingError.invalidServerResponse
}
// URLSession.download
let (location, response) = try await URLSession.shared.download(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200
else {
throw MyNetworkingError.invalidServerResponse
}
try FileManager.default.moveItem(at: location, to: newLocation)
// Cancellation
let task = Task {
let (data1, response1) = try await URLSession.shared.data(from: url1)
let (data2, response2) = try await URLSession.shared.data(from: url2)
}
task.cancel()
// asyncSequence demo
let (bytes, response) = try await URLSession.shared.bytes(from: Self.eventStreamURL)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw WoofError.invalidServerResponse
}
for try await line in bytes.lines {
let photoMetadata = try JSONDecoder().decode(PhotoMetadata.self, from: Data(line.utf8))
await updateFavoriteCount(with: photoMetadata)
}
// task specific delegate demo
class AuthenticationDelegate: NSObject, URLSessionTaskDelegate {
private let signInController: SignInController
init(signInController: SignInController) {
self.signInController = signInController
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic {
do {
let (username, password) = try await signInController.promptForCredential()
return (.useCredential, URLCredential(user: username, password: password, persistence: .forSession))
} catch {
return (.cancelAuthenticationChallenge, nil)
}
} else {
return (.performDefaultHandling, nil)
}
}
}
最初の async/await のコードをベースに、どれかのパターンで対応できそうです。
確かに使いやすそうです。
💡 まとめ
最近のプログラミング言語は仕様の変化が速いので、ネットで検索するとどれを使ったらいいのか、私たち初心者は混乱します。
ありがたい Apple 公式の公開資料からでした。
👉 Use async/await with URLSession - WWDC21 - Videos - Apple Developer
次は、人気の
Alamofire/Alamofire: Elegant HTTP Networking in Swift
を使ってみたいと思っています。
// Automatic String to URL conversion, Swift concurrency support, and automatic retry.
let response = await AF.request("https://httpbin.org/get", interceptor: .retryPolicy)
// Automatic HTTP Basic Auth.
.authenticate(username: "user", password: "pass")
// Caching customization.
.cacheResponse(using: .cache)
// Redirect customization.
.redirect(using: .follow)
// Validate response code and Content-Type.
.validate()
// Produce a cURL command for the request.
.cURLDescription { description in
print(description)
}
// Automatic Decodable support with background parsing.
.serializingDecodable(DecodableType.self)
// Await the full response with metrics and a parsed body.
.response
// Detailed response description for easy debugging.
debugPrint(response)
どうぞよろしくおねがいします。