In iOS 4, Apple brought Blocks to iOS. Put simply, a block is a variable type that stores executable code. This quick tip will provide a brief, general overview of blocks and teach you how to use them for enumerating over objects.
Blocks Overview
As a programmer, you're used to data types like String
, which is a variable type for text. We also are aware of things like int
, float
, and double
, which are variable types to store numeric values. Finally, we know about things like NSObject
, which is a foundation level class that allows us to bundle various collections of both data types and methods (a.k.a. functions). Blocks bring something significantly different to the table, providing a data type for executable code storage.
If that doesn't make sense to you, don't worry: blocks can be tough. Their purpose and function becomes much more clear upon seeing examples of their use. One of the most popular types of patterns in which blocks are used is for providing "completion blocks" to objects who perform some kind of asynchronous operation. The following is an example of a method signature provided in my UIView that takes advantage of accepting a completion block from a user:
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
That final completion parameter there is a block. As with all Objective-C methods, the method has named parameters. The animations and completion parameter are the blocks. You can tell because of the ^ character. This character is used to define a block. While a block variable type holds executable code, the block type itself must be defined. Just like when writing code within a method, blocks can provide objects to the code which it holds. Here we see the animation block defined first:
animations:(void (^)(void))animations
We can see this block returns void and provides void. In this block you would change all the UIKit animatable properties you would want. The completion block is a bit different though.
completion:(void (^)(BOOL finished))completion
In this block we can still see that we will be returning void. However, this time our block will be returning the completion BOOL for us to utilize if we like. When actually implementing this method in Xcode, the IDE does a lot of the work to get you past the somewhat jarring syntax of blocks. Once tabbed over to the parameter input for a block when calling a method, in Xcode 4 and above, hit enter and Xcode will create the outline of the block for you.
Perviously, you might imagine providing this functionality through the delegate pattern, but blocks are here now to allow for greater flexibility and will only become more popular with future versions of the SDK.
Sample Code
Grab the sample code for this project on GitHub. Play around with the code on your own as you follow along with this tutorial.
Enumeration
So, blocks are awesome. You will see them a lot as an iOS developer. One very common operation that can be performed with Blocks is enumeration. There are three major Foundation objects you would want to enumerate with.
- NSArray/NSMutableArray: An ordered collection of objects
- NSDictionary/NSMutableDictionary: An unordered collection of key-value objects.
- NSSet/NSMutableSet: An unordered collection of unique objects
NSDictionaries
Each of these objects has different logical rules enforced on them to achieve different functions. As a result, enumerating through each is slightly different. We will look at how iOS 4 and greater provides block enumeration options for you to take advantage of. Let's first start by defining a sample dictionary to use.
NSDictionary *sampleDictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"Hello World", @"Test String 1", @"How are you?", @"Test String 2", @"I am fine.", @"Test String 3", @"Let's have lunch!", @"Test String 4", @"Open the pod bay doors HAL.", @"Test String 5", @"I am fine.", @"Test String 6", nil];
Great. So when we enumerate a dictionary we are going to need to be provided each key and value and hopefully have the possibility to break out of the enumeration if we choose. Since dictionaries are unordered we won't be provided with an index. This is exactly what we find when looking at the new block based enumeration method for dictionaries.
- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block
In this case, the block we provide will be injected with a key, obj, and stop BOOL pointer. This should give us all the tools we need to enumerate through this dictionary. Let's enumerate over all the keys and values and print them out.
NSLog(@"Printing my dictionary out by enumerating:"); [sampleDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { NSLog(@"\tKey: %@ Value: %@", key, obj); }];
And here's the output:
2011-11-24 07:42:35.562 MobileTutsBlockEnumerationExample[42652:f803] Printing my dictionary out by enumerating: 2011-11-24 07:42:35.564 MobileTutsBlockEnumerationExample[42652:f803] Key: Test String 4 Value: Let's have lunch! 2011-11-24 07:42:35.565 MobileTutsBlockEnumerationExample[42652:f803] Key: Test String 2 Value: How are you? 2011-11-24 07:42:35.565 MobileTutsBlockEnumerationExample[42652:f803] Key: Test String 5 Value: Open the pod bay doors HAL. 2011-11-24 07:42:35.566 MobileTutsBlockEnumerationExample[42652:f803] Key: Test String 3 Value: I am fine. 2011-11-24 07:42:35.567 MobileTutsBlockEnumerationExample[42652:f803] Key: Test String 1 Value: Hello World 2011-11-24 07:42:35.567 MobileTutsBlockEnumerationExample[42652:f803] Key: Test String 6 Value: I am fine.
NSArrays
Great! Dictionary enumeration was easy. Let's checkout out array enumeration now. Let's grab the array returned by the allKeys method on our sample dictionary and work with that first.
NSArray *sampleArray = [sampleDictionary allKeys];
An array is an ordered collection of objects. So when enumerating through an array, it would make sense to be provided the an object, its index, and a way to break out of the enumeration. Let's look at the NSArray instance method signature for enumeration.
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block
Looks like exactly what we need. Our block is provided an object, its index, and a stop BOOL pointer to escape enumeration. This specific enumeration method also allows us to pass in an NSEnumerationOption. To demonstrate this, I am going to pass in the option to do the enumeration in reverse. With this in place we should see print outs from highest index to lowest. Let's enumerate over this array and print out all its objects along with their instances.
NSLog(@"Printing my array out by enumerating:"); [sampleArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSLog(@"\tValue: %@ Index: %d", obj, idx); }];
And here's the output:
2011-11-24 07:42:35.568 MobileTutsBlockEnumerationExample[42652:f803] Printing my array out by enumerating: 2011-11-24 07:42:35.568 MobileTutsBlockEnumerationExample[42652:f803] Value: Test String 6 Index: 5 2011-11-24 07:42:35.569 MobileTutsBlockEnumerationExample[42652:f803] Value: Test String 1 Index: 4 2011-11-24 07:42:35.570 MobileTutsBlockEnumerationExample[42652:f803] Value: Test String 3 Index: 3 2011-11-24 07:42:35.571 MobileTutsBlockEnumerationExample[42652:f803] Value: Test String 5 Index: 2 2011-11-24 07:42:35.576 MobileTutsBlockEnumerationExample[42652:f803] Value: Test String 2 Index: 1 2011-11-24 07:42:35.577 MobileTutsBlockEnumerationExample[42652:f803] Value: Test String 4 Index: 0
NSSets
Ok, two down with one to go. NSSets are like arrays except they are unordered and hold a UNIQUE set of objects. This means that you can not have more than one of any given object in the collection. Our original dictionary had two different "I am fine." values. So, let's create a set from the allValues array provider on our dictionary.
NSSet *sampleSet = [NSSet setWithArray:[sampleDictionary allValues]];
Now that we have a set to work with, we should enumerate it. Since a set is an unordered collection of unique objects it would make sense that our enumeration block would be only supplied with an object and a way for us to escape enumeration.
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop))block
Looks perfect. Let's enumerate over the set and print out the values. Remember, we should only have 5 values printed here because the set holds only unique values and we had a repeat value in our dictionary.
And here's the output:
2011-11-24 07:42:35.577 MobileTutsBlockEnumerationExample[42652:f803] Printing my set out by enumerating: 2011-11-24 07:42:35.578 MobileTutsBlockEnumerationExample[42652:f803] Value: How are you? 2011-11-24 07:42:35.579 MobileTutsBlockEnumerationExample[42652:f803] Value: Open the pod bay doors HAL. 2011-11-24 07:42:35.579 MobileTutsBlockEnumerationExample[42652:f803] Value: Let's have lunch! 2011-11-24 07:42:35.580 MobileTutsBlockEnumerationExample[42652:f803] Value: I am fine. 2011-11-24 07:42:35.581 MobileTutsBlockEnumerationExample[42652:f803] Value: Hello World
Escaping Enumeration
So these all look pretty straightforward to use. But what about this BOOL *stop pointer we keep seeing. This is the flag you can set to stop your enumeration. Block based enumeration checks this flag on every loop before continuing. For a final example let's loop through our array again. This time, let's not do it backwards and stop once we get to index 1. We would accomplish that with:
NSLog(@"Printing my array out by enumerating and stopping at index 1:"); [sampleArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if(idx == 1) { *stop = YES; } NSLog(@"\tValue: %@ Index: %d", obj, idx); }];
It should be noted that when you set the flag to true, the block will execute all the way to the bottom. The enumeration is only escaped just before the next begins. You can see that in the output below because even though we set our stop flag before the NSLog, we still see the log for index 1. Here's the output:
2011-11-24 09:36:01.989 MobileTutsBlockEnumerationExample[43039:f803] Printing my array out by enumerating and stopping at index 1: 2011-11-24 09:36:01.989 MobileTutsBlockEnumerationExample[43039:f803] Value: Test String 4 Index: 0 2011-11-24 09:36:01.990 MobileTutsBlockEnumerationExample[43039:f803] Value: Test String 2 Index: 1
Comments