【Swift】URLSession.shared.dataTask() をうまく使いこなせない

これは、犬の写真を取得する既存のコードです。

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 hatena-bookmark

以下、参考にしたい新しい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 hatena-bookmark

次は、人気の

Alamofire/Alamofire: Elegant HTTP Networking in Swift hatena-bookmark

を使ってみたいと思っています。


// 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)

どうぞよろしくおねがいします。


【Mac Menubar アプリ】System Monitoring + Keyboard Cleaning 🧹 + Window Manager = OneMenu

キーボードをロック(無効化)して拭き掃除したい。

これですかね。

【Mac Menubar アプリ】System Monitoring + Keyboard Cleaning + Window Manager = OneMenu

👉 folivora.ai - Great Tools for your Mac! hatena-bookmark

ネット上にもたくさんの記事を見かけます。

ちょっとあれなんで他を探します。

 

🧹 OneMenu

ありました。メニューバーから使います。

こんな感じです。

【Mac Menubar アプリ】System Monitoring + Keyboard Cleaning + Window Manager = OneMenu

CPU、メモリ、ストレージの使用率と2つのスイッチが表示されています。

【Mac Menubar アプリ】System Monitoring + Keyboard Cleaning 🧹 + Window Manager = OneMenu
👉 OneMenu for Mac — Marko hatena-bookmark

 

🧹 Keyboard Cleaning

キーボードの無効化です。

マウス、トラックパッドは生きていますので、ON / OFF はキーボードを無効化した後でも、メニューバーから操作可能です。

マウス、トラックパッドは生きていますので、ON / OFF はキーボードを無効化した後でも、メニューバーから操作可能です。

 

🧹 Window Manager

ウィンドウをマウスで掴んで、画面上で動かすと、全画面、1/2 (左、右)、1/4 (左上、左下、右上、右下) サイズに自動でリサイズしてくれます。

また、それらは、キーボードショートカットからも利用できます。

【Mac Menubar アプリ】System Monitoring + Keyboard Cleaning + Window Manager = OneMenu

すべて、Control + Option + の組み合わせなので覚えやすいし使いやすいです。

 

🧹 Preferences

【Mac Menubar アプリ】System Monitoring + Keyboard Cleaning + Window Manager = OneMenu

【Mac Menubar アプリ】System Monitoring + Keyboard Cleaning + Window Manager = OneMenu

「Window spacing」は、ウィンドウのサイズを調整後にそのまわりにあけるスペースの間隔。

「Shift key mode」は、ウインドウをマウスで掴んだときの自動サイズ変更を随時 Shift キーで有効化、無効化。

 

🧹 まとめ

Mac を使っていて不便に思えることの解消をまとめてくれるツールです。

作者が「欲しかったので自分で作っちゃいました。」という感じでしょうか。

YouTube や Instagramで作者のライフスタイルを見ることもできます。



👉 Marko(@withmarko) • Instagram写真と動画 hatena-bookmark


【Mac】メニューバーからすばやく使えるテキストアプリ「Tyke」がこういうのでいい感

A little bit of scratch paper 📝 that lives on your Mac menu bar.

【Mac】メニューバーからすばやく使えるテキストアプリ「Tyke 」がこういうのでいい感
👉 Tyke hatena-bookmark
👉 torrez/tyke hatena-bookmark

Homebrew からもインストールできます。


❯ brew info tyke
==> tyke: 1.0
https://tyke.app/
/opt/homebrew/Caskroom/tyke/1.0 (118B)
From: https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/t/tyke.rb
==> Name
Tyke
==> Description
Scratch paper that lives on your menu bar
==> Artifacts
tyke.app (App)
==> Analytics
install: 25 (30 days), 60 (90 days), 129 (365 days)

 

📝 ログイン時に起動させる

このアプリは手動で起動させるとメニューバーに入りますがそれだけです。

Dock にも表示されません。

ログイン時に自動でメニューバーに入るようにしておくと便利です。

【Mac】メニューバーからすばやく使えるテキストアプリ「Tyke 」がこういうのでいい感

 

📝 メモ (Notes.app) に保存する

記入したテキストはメモリ上にのみ存在しているようです。

iCloud でクラウド上に共有できる Mac / iPhone 標準の「メモ」アプリにそのまま保存すると便利です。

右クリックで「共有... (Share...)」から。

【Mac】メニューバーからすばやく使えるテキストアプリ「Tyke 」がこういうのでいい感

【Mac】メニューバーからすばやく使えるテキストアプリ「Tyke 」がこういうのでいい感

【Mac】メニューバーからすばやく使えるテキストアプリ「Tyke 」がこういうのでいい感

 

📝 まとめ

シンプルなアプリなので使い方次第で便利に使うことができそうです。

起動も速いので気持ちが良いです。

こういうのでいいと思うんだけども。

👉 【Mac 無料アプリ】書式なしでコピー・ペーストする方法 - Pure Paste hatena-bookmark