Swift 3.1 Concurrency, Operation Queue, Grand Central Dispatch

Created By: Debasis Das (23-May-2017)

In this article we will cover the following topics in Cocoa with examples and sample code in Swift depicting the usage of each.

  • What is Concurrency?
  • Grand Central Dispatch
  • What is NSOperationQueue?
  • Introduction to Operation Queue
    • NSInvocationOperation
    • NSBlockOperation
    • Custom Operation
  • Dispatch Queues
    • Types of Dispatch Queues
  • NSOperationQueue vs DispatchQueues
  • Examples

What is Concurrency?

  • Doing multiple things at the same time.
  • Taking advantage of number of cores available in multicore CPUs.
  • Running multiple programs in parallel.

Objectives of Concurrency

  • Running program in background without hogging CPU.
  • Define Tasks, Define Rules and let the system take the responsibility of performing them.
  • Improve responsiveness by ensuring that the main thread is free to respond to user events.
  • Leverage more cores to do more work in the same amount of time.

Problems with the Threaded Approach

  • Threads are low level tool that needs to be managed manually.
  • Creating a correct threading solution is difficult.
  • Thread synchronization adds complexity to the project
  • Incorrectly implemented threading solution might make the system even worse
  • Scalability is an issue when it comes to utilizing multiple available cores.

When to Use Threads?

  • Threads are still a good way to implement code that must run in real time
  • Dispatch Queues make every attempt to run their tasks as fast as possible but they do not address the real time constraints

Operations and Operation Queue

  • Object oriented way to encapsulate work that needs to be performed asynchronously
  • An Operation object is an instance of NSOperation(abstract) class.
  • NSOperation class has two concrete subclasses that can be used as is
    • NSInvocationOperation (used to execute a method)
    • NSBlockOperation (used for executing one or more blocks concurrently)
  • An operation can be executed individually/manually by calling its start method or it can be added to an OperationQueue.

NSInvocationOperation

  • A class we can use as-is to create an operation object based on an object and selector from your application.
  • We can use this class in cases where we have an existing method that already performs the needed task. Because it does not require subclassing, we can also use this class to create operation objects in a more dynamic fashion.

NSBlockOperation

  • A class we use as-is to execute one or more block objects concurrently.
  • Because it can execute more than one block, a block operation object operates using a group semantic; only when all of the associated blocks have finished executing is the operation itself considered finished.

Grand Central Dispatch

Grand Central Dispatch provides queues to which the application can submit tasks in the form of block objects. The block of code submitted to dispatch queues are executed on a pool of threads managed by the system. Each task can either be executed synchronously or asynchronously. In case of synchronous execution the program waits for the execution to be finished before the call returns. In case of asynchronous call the method call returns immediately.

Dispatch Queues can be serial or concurrent. In case of serial dispatch queues the work items are executed one at a time and in case of concurrent although the tasks are dequeued in order but they run in parallel  and can finish in any order.

Main Queue – When the app is launched the system automatically creates a special queue called as main queue. Work items on the main queue is executed serially on the applications main thread

Swift Grand Central Dispatch – Dispatch Queues

Lets define 3 tasks of printing different fruits & objects and see how they run in different threads when using GCD and different settings of GCD.

//The below methods prints the thread on which they are being executed Main Thread or Background?
    func printApples(){
        print("printApples is running on = \(Thread.isMainThread ? "Main Thread":"Background Thread")")
        for i in 0..<3{
            print("🍏\(i)")
        }
    }
    
    func printStrawberries(){
        print("printStrawberries is running on = \(Thread.isMainThread ? "Main Thread":"Background Thread")")
        for i in 0..<3{
            print("🍓\(i)")
        }
    }
    
    func printBalls(){
        print("printBalls is running on = \(Thread.isMainThread ? "Main Thread":"Background Thread")")
        for i in 0..<3{
            print("🎱\(i)")
        }
    }

This would be a normal implementation without using GCD where everything runs on the main thread.

    func testPrintMethods(){
        printApples()
        printStrawberries()
        printBalls()
    }
    /* Output
     printApples running on = Main Thread
     🍏0
     🍏1
     🍏2
     printStrawberries running on = Main Thread
     🍓0
     🍓1
     🍓2
     printBalls running on = Main Thread
     🎱0
     🎱1
     🎱2
     */

Using one Dispatch Queue to run 3 tasks in a background thread

    func queueTest1(){
        let queue = DispatchQueue(label: "com.knowstack.queue1")
        queue.async {
            self.printApples()
        }
        queue.async {
            self.printStrawberries()
        }
        queue.async {
            self.printBalls()
        }
        
    }
    /* Output - Note all the methods are running in the background thread, but as the same queue is being used they are running in serial.
     printApples is running on = Background Thread
     🍏0
     🍏1
     🍏2
     printStrawberries is running on = Background Thread
     🍓0
     🍓1
     🍓2
     printBalls is running on = Background Thread
     🎱0
     🎱1
     🎱2
     */

Using three Dispatch Queue to run 3 different tasks in a background thread

    func queueTest2(){
        let queue1 = DispatchQueue(label: "com.knowstack.queue1")
        let queue2 = DispatchQueue(label: "com.knowstack.queue2")
        let queue3 = DispatchQueue(label: "com.knowstack.queue3")
        queue1.async {
            self.printApples()
        }
        queue2.async {
            self.printStrawberries()
        }
        queue3.async {
            self.printBalls()
        }
        
    }
    
    /*
     Output - All the tasks are running in parallel
     printStrawberries is running on = Background Thread
     printApples is running on = Background Thread
     printBalls is running on = Background Thread
     🎱0
     🍓0
     🍏0
     🎱1
     🍓1
     🍏1
     🎱2
     🍓2
     🍏2
     */

Using Dispatch Queue to run tasks on the main thread itself

    func queueTest3(){
        let queue1 = DispatchQueue(label: "com.knowstack.queue1")
        let queue2 = DispatchQueue(label: "com.knowstack.queue2")
        let queue3 = DispatchQueue(label: "com.knowstack.queue3")
        queue1.sync {
            self.printApples()
        }
        queue2.async {
            self.printStrawberries()
        }
        queue3.async {
            self.printBalls()
        }
    }
    /*
     Output
     printApples is running on = Main Thread
     🍏0
     🍏1
     🍏2
     printStrawberries is running on = Background Thread
     printBalls is running on = Background Thread
     🍓0
     🎱0
     🍓1
     🎱1
     🍓2
     🎱2
     */

Main Queue, Global Queue

    func queueTest4(){
        let globalQueue = DispatchQueue.global()
        globalQueue.async {
            self.printApples()
        }
        globalQueue.async {
            self.printStrawberries()
        }
        globalQueue.async {
            self.printBalls()
        }

    }
    /* Output - When we are using a global queue we can see that the three different tasks are running in parallel
     printStrawberries is running on = Background Thread
     printApples is running on = Background Thread
     printBalls is running on = Background Thread
     🍓0
     🍏0
     🎱0
     🍓1
     🍏1
     🎱1
     🍓2
     🍏2
     🎱2
     */

Below code forces to run a task on the main thread on the global queue

    func queueTest5(){
        let globalQueue = DispatchQueue.global()
        globalQueue.sync {
            self.printApples()
        }
        globalQueue.async {
            self.printStrawberries()
        }
        globalQueue.async {
            self.printBalls()
        }
        
    }
    /*Output
     printApples is running on = Main Thread
     🍏0
     🍏1
     🍏2
     printStrawberries is running on = Background Thread
     printBalls is running on = Background Thread
     🍓0
     🎱0
     🍓1
     🎱1
     🍓2
     🎱2
     */

Below code runs the tasks on the main thread by using the main queue

    func queueTest6(){
        let mainQueue = DispatchQueue.main
        mainQueue.async {
            self.printApples()
        }
        
        mainQueue.async {
            self.printStrawberries()
        }
        mainQueue.async {
            self.printBalls()
        }
    }
    //In the above sample, as all the tasks are running on the main thread
    /* Output
     printApples is running on = Main Thread
     🍏0
     🍏1
     🍏2
     printStrawberries is running on = Main Thread
     🍓0
     🍓1
     🍓2
     printBalls is running on = Main Thread
     🎱0
     🎱1
     🎱2
     */

Quality of Service

In the next set of examples we will create Dispatch Queues using a thread priority using QOS.
Before we begin please read through the summary offering of the QoS parameters

User-interactive used for user interaction, refreshing user interface, performing animations. The focus is on responsiveness and performance
User-initiated – Work is nearly instantaneous, Opening or saving docuement
Utility – Work that may take some time to complete and doesn’t require an immediate result, such as downloading or importing data.
Background– Work that operates in the background and isn’t visible to the user, such as indexing, synchronizing, and backups.

In Addition to the above there are 2 more QoS
Default– The priority level of this QoS falls between user-initiated and utility. Work that has no QoS information assigned is treated as default, and the GCD global queue runs at this level.

Unspecified – This represents the absence of QoS information and cues the system that an environmental QoS should be inferred. Threads can have an unspecified QoS if they use legacy APIs that may opt the thread out of QoS.

For more details on Quality of Service refer to
https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html

   func queueTest7(){
    let queue1 = DispatchQueue(label: "com.knowstack.queue1", qos: .userInteractive, attributes: .concurrent, autoreleaseFrequency: .inherit, target: DispatchQueue.global())
    let queue2 = DispatchQueue(label: "com.knowstack.queue1", qos: .utility, attributes: .concurrent, autoreleaseFrequency: .inherit, target: DispatchQueue.global())

        queue1.async {
            self.printStrawberries()
        }
        queue2.async {
            self.printBalls()
        }

    }
    /*
     Output - In the above sample. The thread priority or quality of service plays a key role as User Interactive is of highest priority and although both the queues are configured to run on the background thread, the print strawberries completes faster
     printStrawberries is running on = Background Thread
     printBalls is running on = Background Thread
     🍓0
     🍓1
     🍓2
     🎱0
     🎱1
     🎱2
     
     */
    func queueTest8(){
        let queue1 = DispatchQueue(label: "com.knowstack.queue1", qos:.utility, attributes: .concurrent, autoreleaseFrequency: .inherit, target: DispatchQueue.global())
        let queue2 = DispatchQueue(label: "com.knowstack.queue1", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: DispatchQueue.global())
        
        queue1.async {
            self.printStrawberries()
        }
        queue2.async {
            self.printBalls()
        }
        
    }
    /*
     Output - Utility is of higher priority than the background QoS
     
     printStrawberries is running on = Background Thread
     printBalls is running on = Background Thread
     🍓0
     🎱0
     🍓1
     🍓2
     🎱1
     🎱2
     
     */

Delaying the execution of a Queue

   func queueTest9(){
        let queue1 = DispatchQueue(label: "com.knowstack.queue1", qos:.utility, attributes: .concurrent, autoreleaseFrequency: .inherit, target: DispatchQueue.global())
        let queue2 = DispatchQueue(label: "com.knowstack.queue1", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: DispatchQueue.global())
        
        queue1.asyncAfter(deadline: .now()+5.0) {
            print("In Print Strawberries = \(Date())")
            self.printStrawberries()
            
        }
        
        queue2.async {
            print("In Print Balls = \(Date())")
            self.printBalls()
            
        }
    }
    /*
     Output
     In Print Balls = 2017-05-22 19:03:37 +0000
     printBalls is running on = Background Thread
     🎱0
     🎱1
     🎱2
     In Print Strawberries = 2017-05-22 19:03:42 +0000
     printStrawberries is running on = Background Thread
     🍓0
     🍓1
     🍓2
     */

Operation Queues – Coming Soon
Pre Swift 3.1 implementation can be found at http://www.knowstack.com/swift-concurrency-nsoperation-nsoperationqueue/

Posted in Swift, Swift 3.1 Tagged with: , , , ,
4 comments on “Swift 3.1 Concurrency, Operation Queue, Grand Central Dispatch
  1. Imran khan says:

    Hi deb, I am regular visitor for your blog.

    This post is awesome and really cleared many doubts .

    Thanks for posting

  2. Olivier says:

    Hi Deb,
    Yes, really clear and helpfully! Many thanks for your help, it’s amazing!

Leave a Reply

Your email address will not be published. Required fields are marked *

*