Objective C – Concurrency, NSOperationQueue & Grand Central Dispatch

Objective C – Concurrency, NSOperationQueue & Grand Central Dispatch

Written By : Debasis Das (17-Jan-2015)

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

  • What is Concurrency?
  • 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.

 

- (NSOperation*)taskWithData:(id)data {
    NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self
                    selector:@selector(myTaskMethod:) object:data];
 
   return theOp;
}
 
// This is the method that does the actual work of the task.
- (void)myTaskMethod:(id)data {
    // Perform the task.
}

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.

NSBlockOperation Sample Code

NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
      NSLog(@"Beginning operation.\n");
      // Do some work.
   }];

NSOperation – Writing a Custom Operation

       Custom Operation is a subclass of NSOperation and developers have to implement a couple of methods themselves.
       KSCustomOperation *customOperation = [[KSCustomOperation alloc] initWithData:[NSDictionary dictionaryWithObjectsAndKeys:@"Debasis",@"firstName",@"Das",@"lastName",nil]];
       [customOperation start]; //Either we can use start or we can add this custom operation to an Operation Queue
@interface KSCustomOperation : NSOperation
{
    BOOL executing;
    BOOL finished;
}

@property  (strong) NSDictionary *mainDataDictionary;
-(id)initWithData:(id)dataDictionary;
@end
/***********************************************
 CUSTOM OPERATION IMPLEMENTATION
 **********************************************/
@implementation KSCustomOperation
-(id)initWithData:(id)dataDictionary
{
    if (self = [super init])
    {
        _mainDataDictionary = dataDictionary;
        executing = NO;
        finished = NO;
        
    }
    return self;
}

-(void)start
{
    if ([self isCancelled])
    {
        // Must move the operation to the finished state if it is canceled.
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    // If the operation is not canceled, begin executing the task.
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

-(void)main
{
    //This is the method that will do the work
    @try {
        NSLog(@"Custom Operation - Main Method isMainThread?? ANS = %@",[NSThread isMainThread]? @"YES":@"NO");
        NSLog(@"Custom Operation - Main Method [NSThread currentThread] %@",[NSThread currentThread]);
        NSLog(@"Custom Operation - Main Method Try Block - Do Some work here");
        NSLog(@"Custom Operation - Main Method The data that was passed is %@",_mainDataDictionary);
        for (int i = 0; i<5; i++)
        {
            NSLog(@"i%d",i);
            sleep(1); //Never put sleep in production code until and unless the situation demands. A sleep is induced here to demonstrate a scenario that takes some time to complete
        }
        [self willChangeValueForKey:@"isExecuting"];
        executing = NO;
        [self didChangeValueForKey:@"isExecuting"];
        
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        
    }
    @catch (NSException *exception) {
        NSLog(@"Catch the exception %@",[exception description]);
    }
    @finally {
        NSLog(@"Custom Operation - Main Method - Finally block");
    }
}

-(BOOL)isConcurrent
{
    return YES;    //Default is NO so overriding it to return YES;
}

-(BOOL)isExecuting{
    return executing;
}

-(BOOL)isFinished{
    return finished;
}
@end

Now that we know how to create a NSInvocationOperation, NSBlockOperation and Custom Operations, its time to try out a couple of examples

  • Sample code for
    • NSOperationQueue
    • NSInvocationOperation
    • NSBlockOperation
    • NSOperation – Custom Operation
    • UI Elements Update from a background thread

Sample Code 1 NSOperationQueue using NSInvocationOperation

-(void)sampleCodeOne
{
    NSDictionary *dataDict = [NSDictionary dictionaryWithObjectsAndKeys:@"Debasis",@"firstName",@"Das",@"lastName", nil];
    NSOperationQueue *operationQueue = [NSOperationQueue new];
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testMethodOne:) object:dataDict];
    [operationQueue addOperation:invocationOperation];
    
}

-(void)testMethodOne:(id)obj
{
    NSLog(@"is testMethodOne running on main thread? ANS - %@",[NSThread isMainThread]? @"YES":@"NO");
    NSLog(@"obj %@",obj);
    //Do something using Obj or with Obj
}


Sample Code 2 –  NSOperationQueue using NSBlockOperation

-(void)sampleCodeTwo
{
    NSOperationQueue *operationQueue = [NSOperationQueue new];
    NSBlockOperation *blockCompletionOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"The block operation ended, Do something such as show a successmessage etc");
        //This the completion block operation
    }];
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        //This is the worker block operation
        [self methodOne];
    }];
    [blockCompletionOperation addDependency:blockOperation];
    [operationQueue addOperation:blockCompletionOperation];
    [operationQueue addOperation:blockOperation];
    
    
}

-(void)methodOne
{
    NSLog(@"is testMethodOne running on main thread? ANS - %@",[NSThread isMainThread]? @"YES":@"NO");
    for (int i = 0; i<5; i++)
    {
        NSLog(@"sleeps for 1 sec and i is %d",i);
        sleep(1);
    }
}

Sample Code 3 – NSOperationQueue using a Custom NSOperation

-(void)sampleCodeThree
{
    NSOperationQueue *operationQueue = [NSOperationQueue new];
    KSCustomOperation *customOperation = [[KSCustomOperation alloc] initWithData:[NSDictionary dictionaryWithObjectsAndKeys:@"Debasis",@"firstName",@"Das",@"lastName",nil]];
    //You can pass any object in the initWithData method. Here we are passing a NSDictionary Object

    NSBlockOperation *blockCompletionOperation = [NSBlockOperation blockOperationWithBlock:^{
        //This is the completion block that will get called when the custom operation work is completed.
        NSLog(@"Do Something here. Probably alert the user that the work is complete");
    }];

    customOperation.completionBlock =^{
        NSLog(@"Completed");
        NSLog(@"Operation Completion Block. Do something here. Probably alert the user that the work is complete");
        //This is another way of catching the Custom Operation completition.
        //In case you donot want to catch the completion using a block operation as state above. you can catch it here and remove the block operation and the dependency introduced in the next line of code
    };
  
    [blockCompletionOperation addDependency:customOperation];
    [operationQueue addOperation:blockCompletionOperation];
    [operationQueue addOperation:customOperation];
    //[customOperation start]; //Uncommenting this line of code will run the custom operation twice one using the NSOperationQueue and the other using the custom operations start method   
}

Sample Code 4 – Updating UI Element from a secondary/background thread created from NSInvocationOperation

-(void)showCurrentTime
{
    NSOperationQueue * operationQueue = [NSOperationQueue new];
    NSInvocationOperation *operationOne = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(showTime) object:nil];
    [operationQueue addOperation:operationOne];
    
}

-(void)showTime
{
    // This method will be called on a new thread.
    // As UI elements are not supposed to be updated from a background thread and thus we do performSelectorOnMainThread to update the UI Elements
    NSDateFormatter *timeFormat = [[NSDateFormatter alloc] init];
    [timeFormat setDateFormat:@"HH:mm:ss"];
    NSLog(@"Custom Operation - Main Method isMainThread?? ANS = %@",[NSThread isMainThread]? @"YES":@"NO");
    while (TRUE)
    {
        NSString *theTime = [timeFormat stringFromDate:[NSDate date]];
        [self.systemTimeTextField performSelectorOnMainThread:@selector(setStringValue:) withObject:theTime waitUntilDone:YES];
    }
}

DISPATCH QUEUES

  • Grand central dispatch – dispatch queues allows us to execute arbitrary blocks of code either asynchronously or synchronously
  • All Dispatch Queues are first in – first out
  • All the tasks added to dispatch queue are started in the order they were added to the dispatch queue.
  • Dispatch Queue Types
    • Serial (also known as private dispatch queue)
    • Concurrent
    • Main Dispatch Queue

Serial Queues / Private Dispatch Queue

  • Also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue.
  • The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue.
  • You can create as many serial queues as you need, and each queue operates concurrently with respect to all other queues.

Concurrent / Global Dispatch Queue

  • Concurrent queues (also known as a type of global dispatch queue) execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue. The currently executing tasks run on distinct threads that are managed by the dispatch queue.

Main dispatch queue

  • The main dispatch queue is a globally available serial queue that executes tasks on the application’s main thread.

Sample Code 5 – Dispatch Block from a serial dispatch queue

-(void)sampleCode5
{
    dispatch_block_t myBlock = ^{
        int i = 0;          /* counter, to print numbers */
        for (i=0; i<10; i++)
        {
            NSLog(@"i is %d",i);
            sleep(1);
        }
    };
    // Create the queue
    dispatch_queue_t queue1 = dispatch_queue_create("com.concurrency.sampleQueue1", NULL);
    NSLog(@"%s",dispatch_queue_get_label(queue1));
    // Lets execute the block
    dispatch_async(queue1, myBlock);
    //dispatch_release(queue1); //No need to release in ARC
}

Sample Code 6 – Serial Dispatch Queue

-(void)sampleCode6
{
    NSLog(@"testDispatchQueues isMainThread %@",[NSThread isMainThread]? @"YES":@"NO");
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MySerialQueue",NULL);
    dispatch_async(serialQueue,^{
        NSLog(@"%s",dispatch_queue_get_label(serialQueue));
        NSLog(@"Block 1 Do some work here");
        
    });
    NSLog(@"Do something else outside the blocks");

    dispatch_async(serialQueue,^{
        NSLog(@"%s",dispatch_queue_get_label(serialQueue));
        NSLog(@"Block 2 Do some more work here");
    });
    NSLog(@"Both blocks might or might not have completed. This might get printed before Block 2 is completed");
}

Sample Code 7 – Global Dispatch Queue

-(void)sampleCode7
{
    dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(aQueue,^{
        NSLog(@"%s",dispatch_queue_get_label(aQueue));
        NSLog(@"This is the global Dispatch Queue");
    });
    
    dispatch_sync(aQueue,^{
        NSLog(@"%s",dispatch_queue_get_label(aQueue));
        for (int i =0; i<5;i++)
        {
            NSLog(@"i %d",i);
            sleep(1);
        }
    });
    
    dispatch_async(aQueue,^{
        NSLog(@"%s",dispatch_queue_get_label(aQueue));
        for (int j =0; j<5;j++)
        {
            NSLog(@"This is j %d",j);
            sleep(1);
        }
    });
}

References and Further Reads
https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html

Posted in Cocoa, Generic, iOS, Objective C Tagged with: , , , , , , , , ,
9 comments on “Objective C – Concurrency, NSOperationQueue & Grand Central Dispatch
  1. hamsternik says:

    Thanks for great article, especially examples.

  2. ritesh kumar says:

    This is great website. Its gave me lots of information in short time.

  3. Deepak says:

    What is wrong in my code

    dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_sync(aQueue,^{
    NSLog(@”is this taks performed in Main-thread :%@”,[NSThread isMainThread] ? @”YES” : @”NO” );

    NSLog(@”%s”,dispatch_queue_get_label(aQueue));
    NSLog(@”Global Q block – 1″);
    });

    dispatch_sync(aQueue,^{
    NSLog(@”%s”,dispatch_queue_get_label(aQueue));
    for (int i =0; i<15;i++)
    {
    NSLog(@"\nis running on main thread? ANS – %@",[NSThread isMainThread]? @"YES":@"NO");

    NSLog(@"Global Q block – 2");
    sleep(1);
    }
    });

    dispatch_async(aQueue,^{

    NSLog(@"%s",dispatch_queue_get_label(aQueue));
    for (int j =0; j<25;j++)
    {
    NSLog(@"\nis running on main thread? ANS – %@",[NSThread isMainThread]? @"YES":@"NO");

    NSLog(@"Global Q block – 3");
    sleep(1);
    }
    });

    __________
    Output:

    2016-04-10 11:45:35.893 NSoperationQueuePOC[2175:152029] is this taks performed in Main-thread :YES
    2016-04-10 11:45:35.894 NSoperationQueuePOC[2175:152029] com.apple.root.default-qos
    2016-04-10 11:45:35.894 NSoperationQueuePOC[2175:152029] Global Q block – 1
    2016-04-10 11:45:35.894 NSoperationQueuePOC[2175:152029] com.apple.root.default-qos
    2016-04-10 11:45:35.895 NSoperationQueuePOC[2175:152029]
    is running on main thread? ANS – YES
    2016-04-10 11:45:35.895 NSoperationQueuePOC[2175:152029] Global Q block – 2
    2016-04-10 11:45:36.896 NSoperationQueuePOC[2175:152029]
    is running on main thread? ANS – YES
    2016-04-10 11:45:36.896 NSoperationQueuePOC[2175:152029] Global Q block – 2
    2016-04-10 11:45:37.898 NSoperationQueuePOC[2175:152155] com.apple.root.default-qos
    2016-04-10 11:45:37.898 NSoperationQueuePOC[2175:152155]
    is running on main thread? ANS – NO
    2016-04-10 11:45:37.898 NSoperationQueuePOC[2175:152155] Global Q block – 3
    2016-04-10 11:45:38.901 NSoperationQueuePOC[2175:152155]
    is running on main thread? ANS – NO
    2016-04-10 11:45:38.902 NSoperationQueuePOC[2175:152155] Global Q block – 3
    2016-04-10 11:45:39.907 NSoperationQueuePOC[2175:152155]
    is running on main thread? ANS – NO
    2016-04-10 11:45:39.908 NSoperationQueuePOC[2175:152155] Global Q block – 3

    Question : Global Q block – 1,2 are running in main thread but Global Q block – 3 is not not running in main thread!
    it supposed not to run Global Q block – 1 and 2 in main thread!
    why Global Q block – 1 Global Q block – 2 are running in main thread?

    • iNasir says:

      Because of dispatch_sync(….) block. You can see on left panel “show debug navigator” by putting breakpoints inside blocks. Initially in viewDidLoad the Thread1(as per left panel) picks “com.apple.main.thread(serial) queue”, but when control goes inside dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)^{…}) block then Thread1 picks/updates “com.apple.root.default-qos(concurrent) queue”. (as I can see on left panel). When control reaches to dispatch_async(….) block for Q-3, then Thread 7 picks/update “com.apple.root.default-qos(concurrent) queue”.

      Here the more precise question is weather Thread 1 is Main Thread or Thread 7 or any other Thread. ?

  4. Atul Khatri says:

    Nice article. I was always scared of NSOperations. Now I know how useful they are. Thanks a lot!

  5. Naveen says:

    Excellent!!!!

  6. Suruchi says:

    Thanks for this article. I was actually looking for an example to give a start to these topics but failed to find a suitable one. This helped at last :)

2 Pings/Trackbacks for "Objective C – Concurrency, NSOperationQueue & Grand Central Dispatch"
  1. […] about Concurrency, NSOperation, Operation Queue & Grand Central Dispatch can be found in this article. The concepts remain the same, there is a slight change to […]

  2. URL says:

    … [Trackback]

    […] Informations on that Topic: knowstack.com/concurrency-nsoperationqueue-grand-central-dispatch/ […]

Leave a Reply

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

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Recent Posts


Hit Counter provided by technology news