Coding with Blocks in Objective-C (Part 2)

By Peng Xie

Part I of this series can be found here.

In Part One of this blog, we talked about the general idea of blocks and how to use them. We also discussed the concept of concurrency and two APIs in Objective-C for concurrency: Dispatch queues and operation queues. In this second part of the blog, we will dive deeper into concurrently executing blocks using NSBlockOperation objects. And finally, as promised in the first part, we will look at an example of a typical “gotcha” and show you how to avoid it when using blocks. Please still keep in mind that this blog is designed for readers with intermediate knowledges of Objective-C and we’re using Automatic Reference Counting when coding.

NSOperationQueue, NSOperation and NSBlockOperation

NSOperationQueue

As the name described, operation queues are queues of operations, specifically in Objective-C, they are NSOperationQueue and NSOperation objects. Have you noticed that both the queues and the operations are objects. That means we can use them just as any other Objective-C objects. NSOperationQueue* operationQueue = [[NSOperationQueue alloc] init]; [operationQueue addOperationWithBlock:^{NSLog(@”A block in an operation queue”);}];

The two lines of codes above show how to initialize a NSOperationQueue object and add an operation to the queue using a block. You can also add NSOperation objects to the queue but we will talk about it later after we discuss more on NSOperation class. NSOperationQueue roughly follows a first-in-first-out policy when managing the operations and will make its own decision of when to start an operation.

NSOperation

Now it’s time for some NSOperations. NSOperation is an abstract class which means it requires subclassing in order to use it. Don’t worry. It’s not complicated. Below is the content of our SampleOperation’s header file. #import <Foundation/Foundation.h> @interface SampleOperation : NSOperation

@end 
And the implementation.
#import "SampleOperation.h"
@implementation SampleOperation
– (void)main { @autoreleasepool { NSLog(@”I’m an operation”); }
}@end See? It can be as simple as overriding the “main” method in the concrete class you created, creating an “auto release pool” and putting your codes into it. Since the operation won’t be able to access the main thread’s auto release pool, you have to create your own for the operation. And yes, the syntax shown above is written under ARC environment.
There’re also two other important methods in NSOperation class. The “Start” and “Cancel” methods. You don’t usually override the “start” method and when you call the method of a method, the operation will begin to execute. When an operation is added to a queue, the queue will decide when to call the “start” method of the operation. The “cancel” method is used to cancel the operation. When you write your main method, you should frequently check if the operation is cancelled by checking “self.isCancelled”. Whenever the statement is true, you should stop the execution right away.

NSOperation + Blocks == NSBlockOperation

It seems like we haven’t talked about blocks for a long time. I know you miss them. And now, let’s combine our knowledge of NSOperation and blocks by looking at a concrete class of NSOperation provided in Objective-C, the NSBlockOperation class. A NSBlockOperation object can contain several blocks and concurrently execute the blocks just like an operation queue.

NSBlockOperation* blockOperation = [NSBlockOperation
blockOperationWithBlock:^{NSLog(@"A block in a NSBlockOperation
object");}]; // 1
[blockOperation addExecutionBlock:^{NSLog(@"Adding another
block");}]; // 2

NSArray* blocksInOperation = [blockOperation executionBlocks]; // 3  The first line is the code to create a NSBlockOperation object. Line 2 uses the “addExecutionBlock” method to add another block to the operation. And the last line shows how we can get all blocks inside a NSBlockOperation object by calling the “executionBlocks” method. Please notice that the blocks added to a NSBlockOperation object should take noparameter and return no value. And since the NSBlockOperation is just a concrete subclass of NSOperation, we can still use the “start” method to kick off the execution.

Bonus: Canceling a NSBlockOperation

Congratulations! You’ve earned yourself a bonus chapter. In this part of the blog, we will combine our knowledge of blocks, NSOperation and NSBlockOperation by looking at an example of canceling a NSBlockOperation. As we discussed earlier, when overriding the “main” method of a NSOperation subclass, we should check if the operation is canceled as frequently as possible. That guideline also holds true for NSBlockOperations. That sounds pretty easy. You may think it should be just like the following codes.

NSBlockOperation* blockOperation = [NSBlockOperation
blockOperationWithBlock:^{
    while ([blockOperation isCancelled]) {
     // do something

} }];

Oops, if you put the codes into Xcode, you’ll see the annoying warning sign shows up and tells you the blockOperation is uninitialized when captured by the block. Xcode is not happy.

__block NSBlockOperation* blockOperation = [NSBlockOperation
blockOperationWithBlock:^{
    while ([blockOperation isCancelled]) {
     // do something

} }];

What about this then? We said earlier in Part One that we can make variables mutable inside blocks by giving them a “__block” storage modifier. And we will get rid of the warning messages from Xcode if we modified our previous codes to the ones shown above. If we try to run the code, it will also work as expected. But… Have we really fixed everything? Let’s check the code from the fundamental stuff. “__block” variables will be retained for blocks to use them because it’s possible that the blocks are still running while the object containing the variables is already released. And how will the “__block” variables be retained? The block will keep a strong reference to them so that they won’t be released unless the block is released. From the NSBlockOperation’s perspective, NSBlockOperation relies on the blocks to perform its task. The last thing a NSBlockOperation object wants is to have the blocks released when they are still needed. Therefore, when you give the NSBlockOperation a block, the block will be copied into the object and the NSBlockOperation will keep a strong reference to the blocks it has. Have you smelled anything wrong with the codes we have yet? If you think it sounds like a memory issue, you are on the right track. Although ARC can dramatically simplify the memory management work for developers, it can’t do anything. Under ARC environment, a strong reference means increment by one to the reference count of the object. When the reference count becomes 0, the object will be released from the memory. In the codes we have, the block has a strong reference to the operation and the operation also has a strong reference to the block. This means the reference counts for the block and operation will never go to zero, which makes it impossible for either the operation or the block to be released from the memory. In Objective-C, this situation is called a “retain cycle”. Since the memory allocated for objects will never be deallocated, the retain cycle will cause memory leaks to the app. Well… What can we do to avoid the retain cycle then? Check out the codes below.
// 1

NSBlockOperation* blockOperation = [[NSBlockOperation alloc] init];
// 2
__weak NSBlockOperation* weakBlockOperation = blockOperation;
// 3
[blockOperation addExecutionBlock:^{
    while ([weakBlockOperation isCancelled]) {
     // do something

} }]; Let’s look at these codes line by line. First, we created a NSBlockOperation object. But this time, instead of using the “blockOperationWithBlock:” method, we used a pretty standard way to create the object by calling “alloc” and then “init” on the NSBlockOperation class. This is because we won’t be able to get a reference to the operation when using “blockOperationWithBlock:”. Then in the next line, we created another NSBlockOperation object but this time, we explicitly gave a “__weak” storage modifier to the new operation object and make it refer to the blockOperation object we created earlier. The magical word “__weak” will prevent the weakBlockOperation to have a owning relation with blockOperation and this will make sure no strong reference will be assigned between weakBlockOperation and blockOperation. Lastly, we added the block to the blockOperation. Be careful, in the block, we will check if the weakBlockOperation is cancelled not the blockOperation. Because the weakBlockOperation is assigned to the value (or rather memory contents) of the blockOperation, the value of their “cancel” properties should always be the same. This is the key to break the retain cycle. The blockOperation will still keep a strong reference to the block. But this time since the block is checking the weakBlockOperation’s “cancel” property, it won’t establish any strong reference to the blockOperation object. The retain cycle is broken. Without the retain cycle, the blockOperation will be released when its reference count is 0 and the block will be released with the blockOperation as it should. The weakBlockOperation is “weak” by its nature thus it will be released with no problem when it’s out of scope. We have solved this memory manage management issue.

Where To Go From Here?

In this part of the blog, we introduced NSOperationQueue, NSOperation and one of its concrete subclass, NSBlockOperation. We also solved a memory management problem in our bonus chapter by utilizing our knowledge of NSBlockOperation and blocks. Hopefully you can find some useful information from this blog. If you want to lean more about blocks and NSOperation, please refer to Apple’s guide on blocks programming and don’t forget there is alway the class reference for NSOperation.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s