Như đã lưu ý ở đây và trong câu trả lời cho các câu hỏi SO khác, bạn KHÔNG muốn beginBackgroundTask
chỉ sử dụng khi ứng dụng của bạn chuyển sang chế độ nền; ngược lại, bạn nên sử dụng một nhiệm vụ nền cho bất kỳ hoạt động tốn nhiều thời gian mà kết thúc bạn muốn đảm bảo ngay cả khi ứng dụng không đi vào nền.
Do đó, mã của bạn có khả năng kết thúc với sự lặp lại của cùng một mã soạn sẵn để gọi beginBackgroundTask
và endBackgroundTask
mạch lạc. Để ngăn chặn sự lặp lại này, chắc chắn là hợp lý khi bạn muốn đóng gói bảng soạn sẵn thành một thực thể được đóng gói duy nhất nào đó.
Tôi thích một số câu trả lời hiện có để làm điều đó, nhưng tôi nghĩ cách tốt nhất là sử dụng lớp con Hoạt động:
Bạn có thể xếp hàng đợi Hoạt động vào bất kỳ Hàng đợi Hoạt động nào và thao tác hàng đợi đó khi bạn thấy phù hợp. Ví dụ: bạn có thể tự do hủy sớm bất kỳ hoạt động nào hiện có trên hàng đợi.
Nếu bạn có nhiều việc phải làm, bạn có thể xâu chuỗi nhiều Hoạt động tác vụ nền. Hoạt động hỗ trợ phụ thuộc.
Hàng đợi Hoạt động có thể (và nên) là một hàng đợi nền; do đó, không cần phải lo lắng về việc thực hiện mã không đồng bộ bên trong tác vụ của bạn, bởi vì Hoạt động là mã không đồng bộ. (Thật vậy, không có ý nghĩa gì khi thực thi một mức mã không đồng bộ khác bên trong một Thao tác, vì Thao tác sẽ kết thúc trước khi mã đó có thể bắt đầu. Nếu bạn cần làm điều đó, bạn sẽ sử dụng một Thao tác khác.)
Đây là một lớp con Hoạt động có thể có:
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
}
}
Rõ ràng là làm thế nào để sử dụng điều này, nhưng trong trường hợp không, hãy tưởng tượng chúng ta có một OperationQueue toàn cầu:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Vì vậy, đối với một loạt mã tốn thời gian điển hình, chúng tôi sẽ nói:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Nếu lô mã tốn thời gian của bạn có thể được chia thành các giai đoạn, bạn có thể muốn bỏ cuộc sớm nếu nhiệm vụ của bạn bị hủy. Trong trường hợp đó, chỉ cần trả lại sớm sau khi đóng cửa. Lưu ý rằng tham chiếu của bạn đến nhiệm vụ từ bên trong quá trình đóng cần phải yếu hoặc bạn sẽ nhận được chu kỳ lưu giữ. Đây là một minh họa nhân tạo:
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)
Trong trường hợp bạn phải dọn dẹp trong trường hợp bản thân tác vụ nền bị hủy sớm, tôi đã cung cấp một thuộc tính cleanup
trình xử lý tùy chọn (không được sử dụng trong các ví dụ trước). Một số câu trả lời khác đã bị chỉ trích vì không bao gồm điều đó.