Operationクラスを使うとキュー管理が簡単に
アプリの開発をしているとキュー管理に迫られることは多々ありますが、再帰関数を使って書くなどいくつかやり方はあると思います。ただ自前で書こうとするとプログラムが複雑になり実装コストがかかったりするので、できればappleが既に用意している仕組みに乗っかりたいところです。そこで紹介したいのがOperationクラスを使ったキュー管理です。
Operationクラスとは
コアライブラリであるFoundationに含まれている非同期処理実行用のクラスになります。タスクを実行するキューとなるOperationQueueクラスと組み合わせることで、非同期的にキュー管理を行ったプログラムが組めるようになります。
サンプル
説明だけではイメージしにくいと思うので、サンプルコードを基に説明していきます。このサンプルでは、URLからデータをダウンロードする処理を、キュー管理を用いて実装しています。CacheDownloadManagerは全体のダウンロード管理を行う役割を持たせていて、downloadCacheが呼び出される度にOperationQueueに対してOperationクラスを継承したCacheDownloadOperationを追加しています。このように書くことでCacheDownloadOperationに定義したプログラムがキュー管理式に実行されます。maxConcurrentOperationCountは最大何個まで並列実行を行うか指定するためのオプションでここでは2に指定しています。
// キュー管理を行うクラス
struct CacheDownloadManager {
static let shared = CacheDownloadManager()
private var downloadQueue = OperationQueue()
init() {
self.downloadQueue.maxConcurrentOperationCount = 2
}
func downloadCache(from url: URL, completion: ((Data?) -> Void)? = nil) {
let operation = CacheDownloadOperation(url: url) { data in
Task {
completion?(data)
}
}
self.downloadQueue.addOperation(operation)
}
}
Operationクラス側の実装を見ていきます。main()はOperationQueueに追加されキューの順番が来たタイミングで自動的に実行されます。処理にあるDispatchSemaphoreは非同期処理を同期的に待つようにする為の機能で、Operationクラスはデフォルトで同期的にタスクを実行することを前提としています。今回のケースだと動画ダウンロード(非同期)の完了を待ち、完了次第ダウンロードしたデータをクロージャーで返却して次のキュー処理に移るということを行いたいので採用しています。DispatchSemaphoreは、初期値としてカウンタを持ちます。このカウンタが0である限りはwait()を呼び出しているところまでで処理が止まります。defer { }でコードブロックの終了時にsemaphore.signal()を呼び出しています。signal()が実行されるとカウンタがインクリメントされ、wait()が解除され次の処理に移るといった動きになります。
class CacheDownloadOperation: Operation {
private let url: URL
private let completion: ((Data?) -> Void)?
init(url: URL, completion: ((Data?) -> Void)? = nil) {
self.url = url
self.completion = completion
}
override func main() {
if isCancelled { return }
let semaphore = DispatchSemaphore(value: 0)
var downloadedData: Data?
let task = URLSession.shared.dataTask(with: url) { data, response, error in
defer { semaphore.signal() }
if let error {
print("[CacheDownload] Network error occurred: \(error)")
}
if let httpResponse = response as? HTTPURLResponse {
print("[CacheDownload] HTTP Status Code: \(httpResponse.statusCode)")
}
guard error == nil,
let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200
else {
return
}
downloadedData = data
}
task.resume()
semaphore.wait()
if isCancelled { return }
completion?(downloadedData)
}
}
終わりに
以上がOperationクラスを使用したキュー管理の実装例になります。1度理解すると次からキュー管理が簡単にできるようになりますので非常におすすめです。上記以外に良いやり方がありましたら、ぜひシェアをして頂けるとありがたいです。