質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

2255閲覧

URLSessionはシングルトンクラスとして実装すべきですか

thirdesr34

総合スコア36

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2021/05/09 10:09

編集2021/05/09 14:09

前提・実現したいこと

iOSとSwiftでURLsessionを利用してネットワークアクセスをするプログラムを作成しています。
サンプルを探すと、Apple公式などでシングルトンパターンで実装しているコードをよく見かけます。

しかし、シングルトンで実装されていると、デリゲートを一つしか指定できません。
これは異なるクラスから同時に利用するときなどに問題になる場合があると思います。
この解決策としては、さらにラッパークラス的なものを追加し、デリゲート先を都度切り替えるように実装するなどの案内も見かけます。

ですが、そもそもURLSessionを利用する場合になぜシングルトンで実装するのが一般的なのか、わかる方がいたら教えていただけないでしょうか。
よろしくお願いします。

該当のソースコード

Swift

1/*URLSessionとURLSessionDelegateをまとめたクラスのサンプル*/ 2import Foundation 3class NetworkDownloader: NSObject { 4 static let shared = NetworkDownloader() 5 var sessionConfiguration = URLSessionConfiguration.default 6 var urlSession: URLSession? 7 var runningURLs: [URL: Int] = [:] 8 var runningTasks: [URL: URLSessionDownloadTask] = [:] 9 weak var delegate: NetworkQueueDelegate? 10 override init() { 11 super.init() 12 sessionConfiguration.httpMaximumConnectionsPerHost = 5 13 urlSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: .main) 14 } 15 16 func startDownloadTask(with url: String, with opIndex: Int) { 17 print("Starting task: (url)") 18 guard let networkURL = URL(string: url) else { 19 return 20 } 21 runningURLs[networkURL] = opIndex 22 let downloadTask = urlSession?.downloadTask(with: networkURL) 23 runningTasks[networkURL] = downloadTask 24 downloadTask?.resume() 25 } 26 27 func cancelDownloadTask(with url: String, with opIndex: Int) { 28 guard let networkURL = URL(string: url) else { 29 return 30 } 31 let task = runningTasks[networkURL] 32 runningTasks.removeValue(forKey: networkURL) 33 runningURLs.removeValue(forKey: networkURL) 34 task?.cancel() 35 } 36 37} 38 39extension NetworkDownloader: URLSessionDownloadDelegate, URLSessionDelegate { 40 func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { 41 guard let networkURL = downloadTask.currentRequest?.url, 42 let index = runningURLs[networkURL] else { 43 return 44 } 45 // Remove the running task 46 runningTasks.removeValue(forKey: networkURL) 47 runningURLs.removeValue(forKey: networkURL) 48 self.delegate?.notifyOfNetworkOperationCompletion(with: networkURL.absoluteString, 49 fileURL: location.absoluteString, 50 operationIndex: index, error: nil) 51 } 52 func urlSession(_ session: URLSession, 53 downloadTask: URLSessionDownloadTask, 54 didWriteData bytesWritten: Int64, 55 totalBytesWritten: Int64, 56 totalBytesExpectedToWrite: Int64) { 57 guard let networkURL = downloadTask.currentRequest?.url, 58 let index = runningURLs[networkURL] else { 59 return 60 } 61 62 self.delegate?.notifyOfNetworkProgress(progress: "Bytes written: (totalBytesWritten) out of: (totalBytesExpectedToWrite)", 63 operationIndex: index) 64 } 65 func urlSession(_ session: URLSession, 66 task: URLSessionTask, 67 didCompleteWithError error: Error?) { 68 guard let networkURL = task.currentRequest?.url, 69 let index = runningURLs[networkURL] else { 70 return 71 } 72 // Remove the running task 73 runningTasks.removeValue(forKey: networkURL) 74 runningURLs.removeValue(forKey: networkURL) 75 self.delegate?.notifyOfNetworkOperationCompletion(with: networkURL.absoluteString, 76 fileURL: nil, 77 operation Index: index, error: error) 78 } 79} 80 81/*利用する場合*/ 82class ViewController: UIViewController { 83 @IBOutlet weak var testQueue: UIButton! 84 override func viewDidLoad() { 85 super.viewDidLoad() 86 // Do any additional setup after loading the view. 87 } 88 @IBAction func testOperationQueue() { 89 let urls = [ 90 "https:///xxxx/Test/1.jpg", 91 "https:///xxxx/Test/2.jpg", 92 "https:///xxxx/Test/3.jpg", 93 "https:///xxxx/Test/4.jpg", 94 "https:///xxxx/Test/5.jpg", 95 "https:///xxxx/Test/6.jpg", 96 "https:///xxxx/Test/7.jpg", 97 "https:///xxxx/Test/8.jpg", 98 "https:///xxxx/Test/9.jpg", 99 ] 100 101 NetworkDownloader.shared.delegate = self 102 var index = 1 103 for url in urls { 104 let nonCachedURL = "(url)" 105 NetworkDownloader.shared.startDownloadTask(with: nonCachedURL, with: index) 106 index += 1 107 } 108 } 109} 110extension ViewController: NetworkQueueDelegate { 111 func notifyOfNetworkOperationCompletion(with url: String, fileURL: String?, operationIndex: Int, error: Error?) { 112 guard let filePath = fileURL, error == nil else { 113 print("Error from operation: (error?.localizedDescription ?? "No Error")") 114 return 115 } 116 print("** Operation: (operationIndex) finished with downloaded file: (filePath)") 117 } 118 func notifyOfNetworkProgress(progress: String, operationIndex: Int) { 119 print("-- Operation: (operationIndex) : (progress)") 120 } 121}

補足情報(FW/ツールのバージョンなど)

Swift 5
Xcode 12.5

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tomato879241

2021/05/09 10:43

「よくあるシングルトンのサンプルの抜粋」で書いてあることは結構めちゃくちゃなんですけど? そもそもXcodeのdeveloper Documentに書いてあるURLSessionに関する説明を読みましたか?URLSessionをSingletonとして使うのはURLSession.sharedのように使う場合です。以下を自分で読めば、質問をするほどのことではないのでは? URLSession has a singleton shared session (which doesn’t have a configuration object) for basic requests. It’s not as customizable as sessions you create, but it serves as a good starting point if you have very limited requirements.
thirdesr34

2021/05/09 14:10

ご確認ありがとうございます。抜粋部分が少なすぎたかもしれず、伝わっていないようで恐縮です。一部省略していたサンプルコード部分を追記し、質問を修正しました。 紹介いただいたAppleのドキュメントを拝見するとsharedオブジェクトの利用が紹介されており、これ自体がシングルトンの設計となっていることは承知しています。ただ、本質問でお聞きしたいシングルトンパターンの意図とは別となっており、この点は混乱させてしまい申し訳ありません。 <お聞きしたい点> URLSessionとURLSessionDelegate周りをまとめたクラスを作成するとき、そのクラス自体をシングルトンパターンで実装する場合があるようです。サンプルコードとは別ですが下記も同様です。URLSessionのインスタンスは複数持たず共有するようになっています。 https://betterprogramming.pub/singleton-in-swift-8da9bea06339 このようにURLSessionのインスタンスを限定することで何らかの大きなアドバンテージがあるのではないかと思いますが、質問の冒頭で記載したようにデリゲートを複数指定できない制約もあり、そういった制約があってもこのように実装する理由について知りたいと考えています。 もしこのようなコードは一般的ではない(URLSessionの複数のインスタンスを持つのが一般的)ということであればその旨を教えてください。 長文で恐縮です。
guest

回答2

0

ベストアンサー

URLSession.sharedについて、Appleのドキュメントをよく読むことをお勧めします。

https://developer.apple.com/documentation/foundation/urlsession/1409000-shared

共有URLSessionを使用するメリットは、アプリ内のどこでリクエストしても、同一セッション内の再リクエストと認識され、取得済みのキャッシュ、クッキー、認証情報が自動的に再利用される。つまり、アプリ内全体を通して1つのセッションを簡単に継続できることにあると思います。(自分でURLSessionインスタンスを生成したら、それは別の新しいセッションになります。)

サンプルとして示されているNetworkDownloaderは、init時にデフォルトのURLSessionインスタンスを作成し、それをシングルトンオブジェクトのプロパティ(urlSession)に保存し、以後のダウンロード処理はurlSessionを使用していますから、NetworkDownloader.sharedを使用するだけで、キャッシュ、クッキー、認証情報が共有された1セッションとしてダウンロード処理を繰り返せるメリットがあるのだろうと思います。

投稿2021/05/10 14:29

TakeOne

総合スコア6299

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thirdesr34

2021/05/15 02:06

有難うございました。大変よく理解できました。
guest

0

手段に固執せず目的に合うものを選びましょう。

「水をためるにはタライがいいでしょうか?」と言われても誰も答えられません。
ペットボトルがいいかもしれないし、貯水場がいいかもしれません。
水源が何であって、どこに設置して、どれぐらいの水量を、何のために使うかなどで変わります。
はじめにちょうどいいと思って決めたものでも、使っていくうちに別のものに変更したほうがよくなる場合もあります。

シングルトンはあくまで手段です。

投稿2021/05/10 08:55

MasakiHori

総合スコア3391

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thirdesr34

2021/05/15 02:07

有難うございました。今回はタライやペットボトルを選ぶ指標を知りたかったものでした。質問が伝わりづらくお手数をおかけしました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問