Skip to content

Instantly share code, notes, and snippets.

@jwamin
Last active March 20, 2021 21:11
Show Gist options
  • Save jwamin/58460ca50ebe42e885b5313ccdbe3b53 to your computer and use it in GitHub Desktop.
Save jwamin/58460ca50ebe42e885b5313ccdbe3b53 to your computer and use it in GitHub Desktop.
Login/Logout TaskManager
import Cocoa
//:Requirements
//1. Class that does jobs
//2. Keeps jobs in queue
//3. Perform Jobs in queue
//4. Can be torn down
//5. Can be switched between modes .anon, .loggedIn
//6. Changing modes causes tear down and build up
//7. Jobs are resumed
class TaskManager {
//: Internal Types
internal enum User {
case anon
case user(name: String)
}
internal class PendingTask: Hashable, CustomStringConvertible {
//: CSC confromance
var description: String {
"task \(id) - user: \(authentication)"
}
//: Hashable Confromance
let id = UUID()
static func == (lhs: TaskManager.PendingTask, rhs: TaskManager.PendingTask) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
let block: (Bool) -> Void
let complete: Bool = false
let authentication: User
init(userSpace: User = .anon, block: @escaping (Bool) -> Void) {
self.block = block
authentication = userSpace
}
}
public private(set) var user: User = .anon
public private(set) var destroyed = true {
didSet{
if !destroyed {
tasksToComplete += pendingTasks
pendingTasks.removeAll()
}
}
}
//background queue for task execution
private var taskQueue = DispatchQueue(label: "taskQueue",attributes: .concurrent)
//track all tasks done
private var dispatchGrp = DispatchGroup()
private init(){
destroyed = false
dispatchGrp.notify(qos: .default, flags: .barrier, queue: taskQueue) {
print("done")
}
}
public static let shared = TaskManager()
//tasks in this queue will execute when the manager resumes (appending to tasksToComplete)
public var pendingTasks = Set<PendingTask>() {
didSet {
print("pendingTasks now \(pendingTasks )")
}
}
//tasks in this queue will ececute serially
public private(set) var tasksToComplete = Array<PendingTask>() {
didSet{
print("tasks count now \(tasksToComplete.count)")
}
}
func setup(user: User) {
self.destroyed = true
self.user = user
dispatchGrp.enter()
if case .user(let name) = user {
print("\n\nreinitilizinge with \(name)\nwaiting 2 second\n")
}
//test to see if execution of queue stops
DispatchQueue.main.asyncAfter(deadline: .now() + 5){ [weak self] in
print("\nresuming...\n")
self?.destroyed = false
self?.continueTasks()
self?.taskComplete()
}
}
func continueTasks(){
if self.destroyed {
print("we are destroyed, rebuild")
return
}
if self.tasksToComplete.isEmpty {
precondition(pendingTasks.isEmpty)
print("we are empty, end")
return
}
let pendingTask = self.tasksToComplete.removeFirst()
dispatchGrp.enter()
taskQueue.async(flags:.barrier){ [weak self] in
if self!.destroyed {
self?.tasksToComplete.append(pendingTask)
return
}
if case .user(let name) = pendingTask.authentication {
print("LOGGED IN - performing \(pendingTask) for logged in user \(name)")
pendingTask.block(true)
self?.taskComplete()
} else {
print("LOGGED OUT - performing \(pendingTask) for anonymous user")
pendingTask.block(false)
self?.taskComplete()
}
if !self!.tasksToComplete.isEmpty {
DispatchQueue.main.async {
self!.continueTasks()
}
}
}
}
func performTask(task: PendingTask) {
task.block
}
func addTask(task: @escaping (Bool) -> Void){
let queuedItem = PendingTask(userSpace: self.user, block: task)
if self.destroyed {
pendingTasks.insert(queuedItem)
print("added \(queuedItem) to pending tasks")
return
}
tasksToComplete.append(queuedItem)
continueTasks()
}
//notify dispatch queue that tasks are complete
public func taskComplete(){
dispatchGrp.leave()
}
deinit{
print("task manager deinitialized")
}
}
//Testing
for call in 0...5 {
TaskManager.shared.addTask { returnedValue in
let timeout = Int.random(in: 0...10)
Thread.sleep(until: Date().addingTimeInterval(2))
print("\(call): returnedValue \(returnedValue) timeout was: \(timeout)")
}
}
let blockingQueue = DispatchQueue(label: "blockingQueue", qos: .default, attributes: .concurrent)
blockingQueue.asyncAfter(deadline: .now() + Double(5)) {
print("\n\nexecution should stop\n\n")
TaskManager.shared.setup(user: .user(name: "jossy"))
var secondCount = 0
for call in 0...5 {
let timeout = Int.random(in: 0...5)
blockingQueue.asyncAfter(deadline: .now() + Double(timeout),flags: .barrier ) {
TaskManager.shared.addTask { returnedValue in
print("\(call): returnedValue \(returnedValue)")
}
}
secondCount += 1
}
print("\ntasks registered for logged in: \(secondCount)\n")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment