-
Notifications
You must be signed in to change notification settings - Fork 84
/
Copy pathAsyncTaskScheduler.swift
94 lines (84 loc) · 3.15 KB
/
AsyncTaskScheduler.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//
// AsyncTaskScheduler.swift
// Proton
//
// Created by Rajdeep Kwatra on 11/9/2023.
// Copyright © 2023 Rajdeep Kwatra. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class AsyncTaskScheduler {
typealias VoidTask = () -> Void
private var executing = false
private var isCancelling = false
private var tasks = SynchronizedArray<(id: String, task: VoidTask)>()
private var scheduled = SynchronizedArray<String>()
weak var delegate: AsyncTaskSchedulerDelegate?
var runID = UUID().uuidString
private var pending = false {
didSet {
guard pending == false else { return }
executeNext()
}
}
func cancel() {
isCancelling = true
runID = UUID().uuidString
tasks.removeAll()
pending = false
isCancelling = false
}
func enqueue(id: String, task: @escaping VoidTask) {
guard tasks.contains(where: { $0.id == id }) == false else { return }
self.tasks.append((id, task))
}
func dequeue(_ completion: @escaping (String, VoidTask?) -> Void) {
if let priorityList = delegate?.getIDsToPrioritize() {
let pendingTasks = tasks.filter({ taskID, _ in priorityList.contains(where: { $0 == taskID }) })
if pendingTasks.isEmpty == false,
let priorityTaskIndex = tasks.firstIndex(where: {id, _ in priorityList.first == id }),
let task = self.tasks.remove(at: priorityTaskIndex) {
completion(task.id, task.task)
return
}
}
guard let task = self.tasks.remove(at: 0) else {
completion("", nil)
return
}
completion(task.id, task.task)
}
func executeNext() {
guard !pending, !isCancelling else { return }
dequeue { id, task in
if let task {
self.pending = true
// A delay is required so that tracking mode may be intercepted.
// Intercepting tracking allows handling of user interactions on UI
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) { [weak self, runID = self.runID] in
guard let self, runID == self.runID else { return }
if RunLoop.current.currentMode != .tracking {
task()
} else {
self.tasks.insert((id: id, task: task), at: 0)
}
self.pending = false
}
}
}
}
}
protocol AsyncTaskSchedulerDelegate: AnyObject {
func getIDsToPrioritize() -> [String]
}