Seperti disebutkan di sini dan dalam jawaban atas pertanyaan SO lainnya, Anda TIDAK ingin menggunakan beginBackgroundTask
hanya saat aplikasi Anda akan masuk ke latar belakang; sebaliknya, Anda harus menggunakan tugas latar belakang untuk setiap operasi memakan waktu yang selesai Anda ingin memastikan bahkan jika aplikasi tidak masuk ke latar belakang.
Oleh karena itu, kode Anda kemungkinan besar akan dibumbui dengan pengulangan kode boilerplate yang sama untuk memanggil beginBackgroundTask
dan secara endBackgroundTask
koheren. Untuk mencegah pengulangan ini, tentu masuk akal untuk ingin mengemas boilerplate menjadi beberapa entitas terenkapsulasi tunggal.
Saya suka beberapa jawaban yang ada untuk melakukan itu, tetapi menurut saya cara terbaik adalah dengan menggunakan subkelas Operasi:
Anda dapat memasukkan Operation ke dalam OperationQueue mana pun dan memanipulasi antrean itu sesuai keinginan Anda. Misalnya, Anda bebas membatalkan sebelum waktunya semua operasi yang ada di antrian.
Jika Anda memiliki lebih dari satu hal yang harus dilakukan, Anda dapat merangkai beberapa Operasi tugas latar belakang. Dependensi dukungan operasi.
Operation Queue dapat (dan seharusnya) menjadi antrian latar belakang; jadi, tidak perlu khawatir untuk melakukan kode asinkron di dalam tugas Anda, karena Operasi adalah kode asinkron. (Memang, tidak masuk akal untuk mengeksekusi level lain dari kode asinkron di dalam Operasi, karena Operasi akan selesai bahkan sebelum kode itu dapat dimulai. Jika Anda perlu melakukannya, Anda akan menggunakan Operasi lain.)
Berikut subclass Operation yang mungkin:
class BackgroundTaskOperation: Operation {
var whatToDo : (() -> ())?
var cleanup : (() -> ())?
override func main() {
guard !self.isCancelled else { return }
guard let whatToDo = self.whatToDo else { return }
var bti : UIBackgroundTaskIdentifier = .invalid
bti = UIApplication.shared.beginBackgroundTask {
self.cleanup?()
self.cancel()
UIApplication.shared.endBackgroundTask(bti) // cancellation
}
guard bti != .invalid else { return }
whatToDo()
guard !self.isCancelled else { return }
UIApplication.shared.endBackgroundTask(bti) // completion
}
}
Seharusnya jelas bagaimana menggunakan ini, tetapi jika tidak, bayangkan kita memiliki OperationQueue global:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Jadi untuk kumpulan kode yang memakan waktu, kami akan mengatakan:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Jika kumpulan kode Anda yang memakan waktu dapat dibagi menjadi beberapa tahap, Anda mungkin ingin mundur lebih awal jika tugas Anda dibatalkan. Dalam hal ini, kembalilah sebelum waktunya dari penutupan. Perhatikan bahwa referensi Anda ke tugas dari dalam closure harus lemah atau Anda akan mendapatkan siklus retensi. Berikut ilustrasi buatannya:
let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
guard let task = task else {return}
for i in 1...10000 {
guard !task.isCancelled else {return}
for j in 1...150000 {
let k = i*j
}
}
}
backgroundTaskQueue.addOperation(task)
Jika Anda memiliki pembersihan yang harus dilakukan jika tugas latar belakang itu sendiri dibatalkan sebelum waktunya, saya telah menyediakan cleanup
properti penangan opsional (tidak digunakan dalam contoh sebelumnya). Beberapa jawaban lain dikritik karena tidak memasukkan itu.