krishaamer / TaskQueue

A Task Queue Class developed in Swift

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TaskQueue (Swift)

ver 0.7

New in 0.7: GCD queue control - you can select on which GCD queue each of the tasks in the TaskQueue should run. Read about TaskQueue and GCD in the GCD section below.

https://raw.githubusercontent.com/icanzilb/TaskQueue/master/tq-schema1.png

I've been using for a long time in my iOS projects a class called Sequencer which makes executing async processes in synchronious fashion very easy. Here's the source code of Sequencer by berzniz.

However Sequencer lacks any flow control features - i.e. if you would like to add a new tasks to the queue, skip a task, re-try a task (very important when dealing with network calls), etc.

That's why I wrote TaskQueue for Swift - it employees the Sequencer approach in a more flexible way allowing for greater control and more powerful task management.

Simple Example

Synchronious tasks

Here's the simplest way to use TaskQueue in Swift:

let queue = TaskQueue()

queue.tasks += {
	... time consuming task ...
}

queue.tasks += {
	... another time consuming task ...
}

queue.run()

TaskQueue will execute the tasks one after the other waiting for each task to finish and the will execute the next one.

Asynchronious tasks

More interesting of course is when you have to do some asynchronious work in the background in your tasks. Then you can fetch the next parameter in your task and call it whenever your async work is done:

let queue = TaskQueue()

queue.tasks += { result, next in
    
    var url = NSURL(string: "http://jsonmodel.com")

    NSURLSession.sharedSession().dataTaskWithURL(url,
        completionHandler: {_,_,_ in
            //process the response
            next(nil)
        })
}

queue.tasks += { result, next in
    println("execute next task after network call is finished")
}

queue.run {result in
    println("finished")
}

Few things to highlight in the example above:

  1. The first task closure gets two parameters result is the result from the previous task (nil in the case of the first task of course) and next. next is a closure you need to call whenver your async task has finished executing

  2. Task nr.2 doesn't get started until you call next() in your previous task

  3. The run function can also take a closure as a parameter - if you pass one it will always get executed after all other tasks has finished.

GCD Queue control

Do you want to run couple of heavy duty tasks in the background and then switch to the main queue to update your app UI? Easy. Study the example below, which showcases GCD queue control with TaskQueue:

let queue = TaskQueue()

//
// "+=" adds a task without specifying on which queue it should run
// NB: the tasks will run on the GCD queue the previous tasks ran,
// i.e. if you change to background queue - all tasks that follow will run
// on that queue
//
queue.tasks += {
    //Update the App UI
}

//
// "+=~" adds a task to be executed in the background, e.g. low prio queue
// "~" stands for so~so priority
//
queue.tasks +=~ {
    //do heavy work
}

//
// "+=!" adds a task to be executed on the main queue
// "!" stands for High! priority
//
queue.tasks +=! {
    //update the UI again
}

// to start the taskqueue on the current GCD queue
queue.run()

// to start the taskqueue on the main GCD queue
queue.run(.MainQueue)

// to start the taskqueue on a low prio GCD queue
queue.run(.BackgroundQueue)

Extensive example

let queue = TaskQueue()

//
// Simple sync task, just prints to console
//
queue.tasks += {
    println("====== tasks ======")
    println("task #1: run")
}

//
// A task, which can be asynchronious because it gets
// result and next params and can call next() when ready 
// with async work to tell the queue to continue running
//
queue.tasks += { result, next in
    println("task #2: begin")
    
    delay(seconds: 2) {
        println("task #2: end")
        next(nil)
    }
    
}

//
// A task which retries the same task over and over again
// until it succeeds (i.e. util when you make network calls)
// NB! Important to capture **queue** as weak to prevent 
// memory leaks!
//
var cnt = 1
queue.tasks += {[weak queue] result, next in
    println("task #3: try #\(cnt)")
    
    if ++cnt > 3 {
        next(nil)
    } else {
        queue!.retry(delay: 1)
    }
}

//
// This task skips the next task in queue
// (no capture cycle here)
//
queue.tasks += ({
    println("task #4: run")
    println("task #4: will skip next task")
    
    queue.skip()
    })

queue.tasks += {
    println("task #5: run")
}

//
// This task removes all remaining tasks in the queue
// i.e. util when an operation fails and the rest of the queueud
// tasks don't make sense anymore
// NB: This does not remove the completions added
//
queue.tasks += {
    println("task #6: run")
    
    println("task #6: will append one more completion")
    queue.run {
        _ in println("completion: appended completion run")
    }
    
    println("task #6: will skip all remaining tasks")
    queue.removeAll()
}

queue.tasks += {
    println("task #7: run")
}

//
// This either runs or resumes the queue
// If queue is running doesn't do anything
//
queue.run()

//
// This either runs or resumes the queue
// and adds the given closure to the lists of completions.
// You can add as many completions as you want (also half way)
// trough executing the queue.
//
queue.run {result in
    println("====== completions ======")
    println("initial completion: run")
}

Run the included demo app to see some of these examples above in action.

Misc

Author: Marin Todorov

This code is distributed under the MIT license (included in the repository as LICENSE)

TODO:

  1. tests coverage
  2. more detailed description in README
  3. add a way to specify on which GCD queue the completeion should fall

About

A Task Queue Class developed in Swift

License:MIT License