Multithreading on iOS And Mac OS X Using NSOperations

Multithreading in iOS and OS X

(This tutorial was written with Apple’s Documentation as a “root” base text, combined with my own knowledge on the topic. I have essentially taken bits and pieces of Apple’s documentation and modified them with what I know. Additionally, I have created full examples, instead of the mere snippets Apple tends to add to it’s documentation. All in all, this tutorial should be really easy and pleasant to follow, but there’s a slight chance I have made a few mistakes too. If you find something out of place, please let me know.)

Multithreading: Easy Implementation on iOS And OS X.

Since the introduction of multithreading, people have found the whole thing to be intimidating and hard, because well, it IS intimidating and hard. Most of the time, anyway. Multithreading implementations vary a lot, from super low level APIs (such as the POSIX pthread APIs), to very high level APIs that are friendlier and easier to use (like NSOperations, which is what we will be talking about today!). The high level APIs are naturally built on top of the low level ones. Multithreading on iOS and Multithreading on OS X is really easy with high level APIs nowadays.

In this tutorial, I will teach you how to do multithreading on iOS and OS X using a higher level API than GCD. You will learn how to use NSOperations and NSOperationQueues to write multithreading programs as fast and painlessly possible. Nowadays, there’s very little use for programmers to drop at a very low level to deal with threads, (we won’t be touching NSThreads because they are not really needed), so a high level API that is powerful and easier to use, will be welcomed by most. The GCD is a powerful API for multithreading, but it still has it’s complexities, and actually NSOperation is at a higher level than the GCD.

For this tutorial, I foolishly assume that you are a “high intermediate” developer at the very least. If you are just learning about iOS/OS X development, then this tutorial is not for you. Build an app or two with no concurrency first and then come back to this tutorial – Like usual, it won’t be going anywhere.

”Wait wait wait, what’s all this concurrent chit-chat Yoda lingo you are talking about, anyway?”

Concurrency is about executing multiple paths of code at once. This is useful to speed up your application and making it much more responsive to the user. Suppose you want to create a game – It would be outrageous to make it play sound and deal with the gameplay on the same thread. Instead, you deal with your game events in one thread and let it play the soundtrack in another (in case you’re writing your game from scratch without using any frameworks such as Cocos2D).

So, let’s get started!

First, a word on terminology is due

Before we move to the real sausage of this tutorial, you need to have three concepts clear in your mind. These concepts adapt to modern Apple technologies. The terminology of these words are different in very old or basically obsolete Apple technologies. We will use as a reference iOS 4.0+ and latest versions of OS X starting on Intel chips to call them “modern”. This is an estimate of mine, I cannot guarantee the meanings of this words are different or the same on older versions of the aforementioned technologies. If you are supporting anything pre-iOS 4 or pre-Intel based Macs you probably should adapt your code to newer technologies because they are not going to last very long.

Anyway, here are a couple of definitions. Make sure you burn them in your head:

  • A “thread” is a different path of execution for code.
  • Every UI code is ran in what we call the “main thread“. The main thread takes care of changing of updating your user interface. That’s it’s main job. If you want to calculate the distance between the Earth and the Black Sun, you should probably run that in a new thread as to not block your main thread.
  • A “process” is a running executable – any running executable.. A process can have one to many threads.
  • A task is simply a fancy way of saying “work” – some work that has to be performed.
  • A “Concurrent Operation” takes place in a different thread than the one it was started in. If you start a concurrent operation on your main thread, the operation will take place in a different thread, either by creating it or by getting it handled to by someone else.
  • A “Condition” will prevent a thread from proceeding if it is not met. This is done for synchronisation purposes.
  • A “Critical Section” can only be accessed by one thread at any given time.
  • A “Mutex” is a ‘lock’ that provides mutually exclusive access to a shared resource. Only one thread may have an specific lock at any time. If another thread attempts to get the lock that is currently held by another thread, the attempting thread will be sent to sleep until the other thread gives the lock up.
  • A “Semaphore” is a protected variable that restricts access to a shared source. Both mutexes and conditions are semaphores.

Swallowing a Little Bit More of Multithreading Theory

NSOperation and its Subclasses

Concurrent programming using NSOperations and such are all about creating “operation objects”. Operation objects are instances of NSOperation and they encapsulate the code of your work you want to perform. NSOperation can be considered an “abstract class” (strictly speaking it really isn’t an abstract class because Objective-C has no support for marking classes as abstract) – that means that this class cannot be directly instantiated but rather it must be subclassed if you want to use it. The Foundation framework provides you with two subclasses ready to be used for you. You will use them most of the time. If they don’t suit your needs, you can subclass NSOperation yourself.

Here is a list of the operation classes provided by Foundation, along with a small description of them. Note that unlike their parent class these are not considered abstract so you can use them as-is.

  • NSInvocationOperation: You create an instance of this class by using an already existing object and an existing selector from your application. This is helpful when you have an object with a method that can be called both on a concurrent thread or on the main thread. This allows you to create operation objects more dynamically and with less restrictions. You could check for a condition (in this sentence I’m using “condition” as an “if-else” block, not condition in the threading terminology) to see if you will need to execute the method either concurrently or in your current thread. Very helpful and easy to use.
  • NSBlockOperation: As its name implies, you can use this class to execute various block objects at once. Note that due to the nature of this class, this operation is only considered completed when all its block objects have finished executing.

All operation objects support the following key features:

  • Dependencies. You can make a given operation execute after another one has finished. A practical example of this would be a program that downloads an image from the internet and applies an image filter to it. If your program download images from the internet and applies filters to them in the main thread, your app is going to be really, REALLY bulky and slow. Because we don’t want that to happen, we can separate the downloading and filtering into two different operations. Now, it wouldn’t make sense to apply a filter to an image that hasn’t been downloaded yet. So, to make sure that filtering operation takes places after the image is downloaded, you can add the download operation as a dependency of the filtering operation. That is, the filtering operation “depends” on the downloading operation.
  • Completion blocks. These are optional. If you are a “high intermediate” iOS developer like I assumed I assume you know why you would want to use this, but if you don’t, they are a way of asking your code to do something after an operation has finished.
  • You can monitor changes on the execution state of the operation using KVO (Key-Value Observing notifications – read my tutorial in KVO in case you don’t know this).
  • Prioritizing. You can define which tasks should be executed earlier than others. This will naturally alter the execution order of your operations.
  • You can cancel an operation at any time while it is executing. You can implement your own cancelation events if you need them.

It is pretty common to execute operations by adding them to an operation queue (NSOperationQueue), but it’s not strictly the only way to do it. You can start an operation anytime by calling its “start” method; but there’s a little gotcha – Doing this will not guarantee that your operation will run concurrently with the rest of your code. To determine whether your operation is running in a concurrent thread or not, you can call the isConcurrent method of the NSOperation class. This will return NO in case the thread is running synchronously (IE not in a different thread). So, in short, you will use NSOperationQueues 99.9% of the time.

Let’s Play With Some Operations

As usual, this is where you get your hands dirty. In this section we are going to play with NSInvocationOperation, NSBlockOperation, and a custom NSOperation. I have written this section of the tutorial thinking about you. Learning about Concurrency can be really energy draining, so at this point, I recommend you take breaks if you want or need to. Did you start reading this tutorial at 2:00 AM in the morning? That’s no problem, if you have energy left to read about NSInvocationOperations go ahead, but if by the time you are done reading that you are tired, you should definitely go to sleep and commit this knowledge to your brain, because attempting to go any further won’t do any good.

All the projects here are going to be done in simple command line tool projects. One of the bad things I noticed about concurrent programming tutorials for iOS or OS X is that people do a lot of platform-specific example. That doesn’t help when you just need a simple push to understand everything. Personally, I find general-purpose tutorials that only program for an specific platform dreadful. If a topic supports both OS X and iOS, then the logical thing is to keep it simple, because after all, you may be an iOS developer but the tutorial you found only uses examples for OS X. Because of this, the command line examples here should be useful for everyone, since every person reading this has written a command line tool writing “Hello World” to the Terminal (if you haven’t, you have offended the Tiki Gods!).

Buuuut let’s move on:

NSInvocationOperation

Like I have said above, an invocation operation works with an object and a selector. This is helpful when you have a task that may be called concurrently many times, or when you need to decide whether you can call it on your main thread or somewhere else.

For now just, just create a Command Line Tool (you should SERIOUSLY know this by now!) and write the code below. I will explain everything in a sec.

And a final note: For the sake of simplicity I have omitted details such as memory management. I trust you know how to deal with memory management both manually or ARC, and that you will always code responsibly in your projects.

//
//  main.mm
//  invocationoperation
//
//  Created by Andy Ibanez on 10/6/12.
//  Copyright (c) 2012 Andy Ibanez. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface numberCounter : NSObject
{

}
- (void)startCounting;
- (void)printCount;
@end

@implementation numberCounter
-(void)startCounting
{
    //First we have to create the invocation operation.
    NSInvocationOperation *countingOp = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(printCount)
                                                                              object:nil];
    NSOperationQueue *theQueue = [[NSOperationQueue alloc] init];
    theQueue.name = @"Counting Queue";
    [theQueue addOperation:countingOp];
}

-(void)printCount
{
    for(int i = 1; i <= 10; i++)
    {
        NSLog(@"%d", i);
    }
}
@end

int main(int argc, const char * argv[])
{
    numberCounter *numbah = [[numberCounter alloc] init];
    [numbah startCounting];
    for(int i = 1; i <= 10; i++)
    {
        NSLog(@"Sakura Kinomoto");
    }
    return 0;
}

The code here isn’t useful but I believe it illustrates it’s purpose really well. First, you may have noticed I have created an NSOperationQueue. This class takes care of managing operations. I have told you can run concurrent code without these queues, but that is barely needed. addOperation:, like its name states, adds an operation to the queue. The operation you add to the queue will start executing immediately if there are any threads available for you to use. This class also includes many helpful methods such us operations (an NSArray with all the operations on queue), operationCount (The number of operations left to execute), and cancelAllOperations. You can set the max amount of operation count by setting it’s maxConcurrentOperationCount property, you can suspend all the operations too. A very useful method included by this class is waitUntilAllOperationsAreFinished, which will freeze the thread that spawned the new thread until all the operations in the spawned thread are finished.

It is a helpful thing to give the new queue a name, since it provides you an easy way to refer to it.

You may have noticed that the only thing we ask our new thread to do is to print numbers from 1 to 10 to the Terminal window. We create this operation by passing it the selector and the object with that selector (for the curious, the “object” parameter is an object you can pass to the selector. It is of type id so you can pass any object your selector may take. Since in our case it doesn’t our selector doesn’t need any additional parameters, we pass in nil).

Once the operation is created, we add it to our newly created queue. In this example’s case, the operation starts executing right away.

I want you to look at the main function now. Here we create our numberCounter object (sorry for breaking the naming convention – I started writing this tutorial late at night and I sure ate some details). Then we call the method that creates and spawns the new thread. The loop below this one prints “Sakura Kinomoto” 10 times to the console. What are you expecting the result to be? You have a minute to think about it.

Got the answer? Oh, you got a solid answer? The truth is that you don’t know. Will it print the numbers 1 – 10 an then “Sakura Kinomoto” 10 times? Nope. Will it print a number, followed by “Sakura Kinomoto”, followed by another name, and followed by “Sakura Kinomoto” again? Nope. The answer is that this can be arbitrary. In fact, running the program many times will barely if ever print the same result.

I ran the program once. I got this result:

2012-10-07 01:06:21.341 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.342 invocationoperation[2180:1903] 1
2012-10-07 01:06:21.345 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.345 invocationoperation[2180:1903] 2
2012-10-07 01:06:21.346 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.346 invocationoperation[2180:1903] 3
2012-10-07 01:06:21.347 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.347 invocationoperation[2180:1903] 4
2012-10-07 01:06:21.347 invocationoperation[2180:1903] 5
2012-10-07 01:06:21.347 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.348 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.348 invocationoperation[2180:1903] 6
2012-10-07 01:06:21.349 invocationoperation[2180:1903] 7
2012-10-07 01:06:21.349 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.349 invocationoperation[2180:1903] 8
2012-10-07 01:06:21.349 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.350 invocationoperation[2180:1903] 9
2012-10-07 01:06:21.350 invocationoperation[2180:303] Sakura Kinomoto
2012-10-07 01:06:21.350 invocationoperation[2180:1903] 10
2012-10-07 01:06:21.351 invocationoperation[2180:303] Sakura Kinomoto

But if I run it again I will get a different output:

2012-10-07 01:08:08.681 invocationoperation[2190:1a03] 1
2012-10-07 01:08:08.680 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.689 invocationoperation[2190:1a03] 2
2012-10-07 01:08:08.689 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.689 invocationoperation[2190:1a03] 3
2012-10-07 01:08:08.690 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.690 invocationoperation[2190:1a03] 4
2012-10-07 01:08:08.690 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.691 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.691 invocationoperation[2190:1a03] 5
2012-10-07 01:08:08.691 invocationoperation[2190:1a03] 6
2012-10-07 01:08:08.691 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.692 invocationoperation[2190:1a03] 7
2012-10-07 01:08:08.692 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.692 invocationoperation[2190:1a03] 8
2012-10-07 01:08:08.693 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.693 invocationoperation[2190:1a03] 9
2012-10-07 01:08:08.694 invocationoperation[2190:303] Sakura Kinomoto
2012-10-07 01:08:08.694 invocationoperation[2190:1a03] 10
2012-10-07 01:08:08.695 invocationoperation[2190:303] Sakura Kinomoto

This is very important to notice, because after all, this is the point of concurrency. The threads are running in parallel printing output to the console at the same time. It is important to see that things will not work as you expect them to work. Even with all the beautiful threading abstraction provided by Apple, your code will not be predictable when working with threads.

What if you wanted to print all the numbers first and then print “Sakura Kinomoto” 10 times? You could create a new selector that will print Sakura Kinomoto and then create another NSInvocationOperation object using this selector, or you could freeze the thread “Sakura Kinomoto” prints on until the numbers thread is done printing.

We will do the latter first. Remember I told you about NSOperationQueue’s “waitUntilAllOperationsAreFinished” method? Simply call this method at the end of the method:

-(void)startCounting
{
    //First we have to create the invocation operation.
    NSInvocationOperation *countingOp = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(printCount)
                                                                              object:nil];
    NSOperationQueue *theQueue = [[NSOperationQueue alloc] init];
    theQueue.name = @"Counting Queue";
    [theQueue addOperation:countingOp];
    [theQueue waitUntilAllOperationsAreFinished];
}

This will print all the numbers 1 – 10 first and then “Sakura Kinomoto” 10 times, as expected:

2012-10-07 01:15:55.158 invocationoperation[2208:1703] 1
2012-10-07 01:15:55.166 invocationoperation[2208:1703] 2
2012-10-07 01:15:55.166 invocationoperation[2208:1703] 3
2012-10-07 01:15:55.167 invocationoperation[2208:1703] 4
2012-10-07 01:15:55.167 invocationoperation[2208:1703] 5
2012-10-07 01:15:55.168 invocationoperation[2208:1703] 6
2012-10-07 01:15:55.168 invocationoperation[2208:1703] 7
2012-10-07 01:15:55.169 invocationoperation[2208:1703] 8
2012-10-07 01:15:55.169 invocationoperation[2208:1703] 9
2012-10-07 01:15:55.169 invocationoperation[2208:1703] 10
2012-10-07 01:15:55.171 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.172 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.172 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.172 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.173 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.173 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.173 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.174 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.174 invocationoperation[2208:303] Sakura Kinomoto
2012-10-07 01:15:55.174 invocationoperation[2208:303] Sakura Kinomoto

Okay, this works wonderfully.

But suppose after a couple of months your boss asks you he wants a change in the program. He wants you to get rid of the loop printing “Sakura Kinomoto” 10 times. Instead, he wants you to print the numbers 11 – 20. The gotcha is that he doesn’t want you to wait until all threads are finished, and he wants you to print the numbers in order, too.

How to tackle this? Well naturally first you get rid of the “Sakura Kinomoto” loop in the main function. Then you will write the selector that prints 11 – 20 and add it to the queue you added the other selector to:

Here is the full code:

//
//  main.mm
//  invocationoperation
//
//  Created by Andy Ibanez on 10/6/12.
//  Copyright (c) 2012 Andy Ibanez. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface numberCounter : NSObject
{

}
- (void)startCounting;
- (void)printCount;
- (void)printCountTo20;
@end

@implementation numberCounter
-(void)startCounting
{
    //First we have to create the invocation operation.
    NSInvocationOperation *countingOp = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(printCount)
                                                                              object:nil];
    NSOperationQueue *theQueue = [[NSOperationQueue alloc] init];
    theQueue.name = @"Counting Queue";
    [theQueue addOperation:countingOp];
    
    NSInvocationOperation *to20 = [[NSInvocationOperation alloc] initWithTarget:self
                                                                       selector:@selector(printCountTo20)
                                                                         object:nil];
    [theQueue addOperation:to20];
    [theQueue waitUntilAllOperationsAreFinished];
}

-(void)printCount
{
    for(int i = 1; i <= 10; i++)
    {
        NSLog(@"%d", i);
    }
}

-(void)printCountTo20
{
    for(int i = 11; i <= 20; i++)
    {
        NSLog(@"%d", i);
    }
}
@end

int main(int argc, const char * argv[])
{
    numberCounter *numbah = [[numberCounter alloc] init];
    [numbah startCounting];
    return 0;
}

(One important thing – in this specific case it is very important you call waitUntilAllOperationsAreFinished. If you don’t, main will return with 0, meaning the program finished successfully, and the background thread won’t be executed).

Ahhh, you’re finished doing what your boss asked you to – or at least that’s what you think. When you run the program… Ohh the horror.

2012-10-07 01:28:57.076 invocationoperation[2237:1b03] 1
2012-10-07 01:28:57.076 invocationoperation[2237:1703] 11
2012-10-07 01:28:57.095 invocationoperation[2237:1b03] 2
2012-10-07 01:28:57.095 invocationoperation[2237:1703] 12
2012-10-07 01:28:57.096 invocationoperation[2237:1703] 13
2012-10-07 01:28:57.096 invocationoperation[2237:1b03] 3
2012-10-07 01:28:57.096 invocationoperation[2237:1703] 14
2012-10-07 01:28:57.096 invocationoperation[2237:1b03] 4
2012-10-07 01:28:57.097 invocationoperation[2237:1b03] 5
2012-10-07 01:28:57.097 invocationoperation[2237:1703] 15
2012-10-07 01:28:57.098 invocationoperation[2237:1703] 16
2012-10-07 01:28:57.098 invocationoperation[2237:1b03] 6
2012-10-07 01:28:57.099 invocationoperation[2237:1b03] 7
2012-10-07 01:28:57.099 invocationoperation[2237:1703] 17
2012-10-07 01:28:57.099 invocationoperation[2237:1b03] 8
2012-10-07 01:28:57.099 invocationoperation[2237:1703] 18
2012-10-07 01:28:57.100 invocationoperation[2237:1b03] 9
2012-10-07 01:28:57.100 invocationoperation[2237:1703] 19
2012-10-07 01:28:57.100 invocationoperation[2237:1703] 20
2012-10-07 01:28:57.100 invocationoperation[2237:1b03] 10

Okay now that’s what you wouldn’t call “printing the numbers in order”. How to solve this? One way to solve this would be to call the queue’s waitUntilAllOperationsAreFinished method after adding the first operation, but then this really wouldn’t be concurrent, as the current would be frozen until the other one was done executing, so you wouldn’t be able to do more work on it.

The right solution is to add a dependency. The operation that prints the numbers 11 – 20 depends on the operation that prints 1 – 10. That is, in this specific case, “to20″ depends on “countingOp” to run.

To add dependencies to an operation, simply call it’s “addOperation” method, which takes an NSOperation as a parameter.

Since to20 depends on countingOp, we do this:

[to20 addDependency:countingOp];

The full method would look like this:

-(void)startCounting
{
    //First we have to create the invocation operation.
    NSInvocationOperation *countingOp = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(printCount)
                                                                              object:nil];
    NSOperationQueue *theQueue = [[NSOperationQueue alloc] init];
    theQueue.name = @"Counting Queue";
    [theQueue addOperation:countingOp];
    
    NSInvocationOperation *to20 = [[NSInvocationOperation alloc] initWithTarget:self
                                                                       selector:@selector(printCountTo20)
                                                                         object:nil];
    [to20 addDependency:countingOp];
    [theQueue addOperation:to20];
    [theQueue waitUntilAllOperationsAreFinished];
}

And finally, the result will be as expected. The code will run beautifully and concurrently with the right dependencies.

2012-10-07 01:36:02.639 invocationoperation[2259:1b03] 1
2012-10-07 01:36:02.658 invocationoperation[2259:1b03] 2
2012-10-07 01:36:02.659 invocationoperation[2259:1b03] 3
2012-10-07 01:36:02.660 invocationoperation[2259:1b03] 4
2012-10-07 01:36:02.660 invocationoperation[2259:1b03] 5
2012-10-07 01:36:02.660 invocationoperation[2259:1b03] 6
2012-10-07 01:36:02.661 invocationoperation[2259:1b03] 7
2012-10-07 01:36:02.661 invocationoperation[2259:1b03] 8
2012-10-07 01:36:02.662 invocationoperation[2259:1b03] 9
2012-10-07 01:36:02.663 invocationoperation[2259:1b03] 10
2012-10-07 01:36:02.670 invocationoperation[2259:1803] 11
2012-10-07 01:36:02.671 invocationoperation[2259:1803] 12
2012-10-07 01:36:02.671 invocationoperation[2259:1803] 13
2012-10-07 01:36:02.672 invocationoperation[2259:1803] 14
2012-10-07 01:36:02.672 invocationoperation[2259:1803] 15
2012-10-07 01:36:02.674 invocationoperation[2259:1803] 16
2012-10-07 01:36:02.675 invocationoperation[2259:1803] 17
2012-10-07 01:36:02.675 invocationoperation[2259:1803] 18
2012-10-07 01:36:02.676 invocationoperation[2259:1803] 19
2012-10-07 01:36:02.676 invocationoperation[2259:1803] 20

You can have threads in different queues, and the operations contained in this queues can have as dependencies operations in different queues.

Also note that cancelling an operation with dependencies means that the dependant code won’t be run.

Creating NSBlockOperation Objects

Apple has been moving away from the target-selector model in favour of blocks starting on iOS 4. Because of this the NSBlockOperation class has been introduced to deal with operations based on blocks. This provides a much better design in some cases – You no longer have to create a whole shiny new method if you know you will only execute a task concurrently once.

NSBlockOperations can have one or more blocks. Normally you provide the first block the moment you create the block. The operation is considered finished when all it’s contained blocks are done executing.

Luckily, I have already taught you most things about concurrency on the previous section so I don’t have much to say here, but there’s some things.

First, have this code:

//
//  main.mm
//  invocationoperation
//
//  Created by Andy Ibanez on 10/6/12.
//  Copyright (c) 2012 Andy Ibanez. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface numberCounter : NSObject
{

}
- (void)startCounting;
@end

@implementation numberCounter
-(void)startCounting
{
    //First we have to create the invocation operation.
    NSOperationQueue *theQueue = [[NSOperationQueue alloc] init];
    theQueue.name = @"Counting Queue";
    NSBlockOperation *theBlock = [NSBlockOperation blockOperationWithBlock:^{
        for(int i = 1; i <= 10; i++)
        {
            NSLog(@"%d", i);
        }
    }];
    [theBlock addExecutionBlock:^{
        for(int i = 11; i <= 20; i++)
        {
            NSLog(@"%d", i);
        }
    }];
    [theQueue addOperation:theBlock];
    [theQueue waitUntilAllOperationsAreFinished];
}
@end

int main(int argc, const char * argv[])
{
    numberCounter *numbah = [[numberCounter alloc] init];
    [numbah startCounting];
    return 0;
}

You create an NSBlockOperation by passing it the initial block. Then you can add more blocks as needed. What you need to know is that the blocks added to the operation are executed concurrently, so the example above doesn’t print the numbers in the right order. To do so, you will need to create a new NSBlockOperation and add the dependencies as needed.

Second, if you add any block objects to the operation after creating it, you have to add them before the operation starts executing (IE before adding the whole operation to the queue). If you add more blocks to the operation while it is running or after it has finished, the program will raise an exception.

Your Own NSOperation

In case the previous classes don’t suit your needs, as an emergency measure you may create your own NSOperation. We are not going to cover a full example of this because it is rarely needed, but I will give you the base so you can be prepared in case you stumble with a rare case where you will need your own NSOperation.

Your NSOperations should at least:

  • have a custom initialisation method.
  • Override NSOperation’s main method.

And of course, you may add any other methods your operation will need.

The initialisation method is mainly used to put your custom operation into a known state.

The main methods contains all the code your operation will execute. That is, here is where you would put any asynchronous URL downloading code or image filtering code.

Your own subclasses of NSOperation can start without being added in a queue. The only gotcha is that, if you want to create concurrent NSOperations, a lot of work has to be done. Firstly, you will have to observe the operation’s change state using Key-Value Observing, and responds to this changes accordingly.

If you’re planning on creating concurrent tasks by subclassing this class, then you have to override 4 additional methods that id with the management of the thread:

  • start
  • isConcurrent
  • isExecuting
  • isFinished

start should start your operation in an asynchronous manner. It should do whatever is necessary to do this, such as spawning a new thread for your operation. This method is also responsible for updating the thread state according to what the last 3 methods report.

The other three are pretty much self explanatory: One checks if the operation is concurrent, isExecuting checks if the operation is currently executing, and isFinished should check if the operation is finished.

I Need To Update My UI! What Do I Do?

It is very likely you will want to update your UI from a background thread. To update your UI according to the background thread, you need to get the main operation queue, and submit all UI-updating operations to this queue.

To get a handle to this thread, simply call the mainQueue method of NSOperationQueue.

[NSOperationQueue mainQueue]

And submit your operations as you would submit them to any other queue.

Cancelling Operations.

It is important to note that once you submit an operation to a queue, that operation is out of your control. The queue takes care of it. But one of the advantages of NSOperations over the lower-level GCD is that you can actually cancel operations anytime.

Individual NSOperations have a method called cancel, which as its name states, cancels the operation from executing. NSOperationQueues have a method called cancelAllOperations, which calls cancel in all the operations that are currently present in the queue.

It is also important to note that cancelled operations won’t stop their work right away. You are responsible for checking the value returned by isCancelled and abort your operation accordingly. Additionally, when you abort an operation manually, you should set isExecuting and isFinished to YES. Indeed in other words, you are responsible for managing your thread’s state at this point.

That’s it! This is a very large post. I tried to make it as easy as possible by using extremely trivial examples. So I hope you find it useful.

CHANGELOG:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值