2011年10月10日星期一

Introduction to Blocks in Objective-C – Part 2

Introduction to Blocks in Objective-C – Part 2:


In the first post on blocks, Introduction to Blocks in Objective-C – Part 1, I covered the basics for creating block variables, working with __block modifier and using typedef to define blocks. In this post I will show how you can pass a block as a parameter, use enumeration with blocks, as well as take a quick look at blocks and recursion.


Passing Blocks as Parameters

Within a number of iOS frameworks, methods are provided that accept a block as a parameter. As it relates to working with blocks, this is one area where things start to get interesting and you get a perspective on a few unique features offered through blocks.


For example, in the code below, I create an array of string objects, nothing new there. However, in iOS 4+NSArray now includes a method to create a set (NSSet) object from the array by passing in a block into the indexesOfObjectsPassingTest method. The block that I’ve written checks the length of each object in the array (each is object is string in this example), and return YES, only for those objects where the string is greater than 5 in length.



// Create an array
NSArray *array = [NSArray arrayWithObjects: @"12345", @"12345678", @"abcd", @"123abc", nil];

// Create a set (an unordered collection of objects) with the indexes of the objects
// in the array where the length of the object in the array is > 5.
// Notice the parameter is a block.
NSIndexSet *set = [array indexesOfObjectsPassingTest: ^(id obj, NSUInteger idx, BOOL *stop)
{
if ([obj length] > 5)
return YES;
else
return NO;
}];


Now that we have a set that tells us which objects are of a specified length (or greater), let’s call a method in the NSSet object that will enumerate the values in the set and print the values of the relevant array entries – the method enumerateIndexesUsingBlock in NSSet will also accept a block as a parameter:



// Enumerate the set, printing the     
[set enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop)
{
NSLog(@"Array entry %d contains: %@", idx, [array objectAtIndex:idx]);
}];


The output from the above is:


Array entry 1 contains: 12345678

Array entry 3 contains: 123abc


Using Stop to End Processing

Let’s take a closer look at the definition of indexOfObjectPassingTest method in the NSSArray object:



- (NSUInteger)indexOfObjectPassingTest:
(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate


In the first example shown above, the only parameter in the block referenced was obj, where the length was compared to see if it was > 5. When you need to stop processing a block based on some condition check, you can use the stop parameter.


Let’s say we have an array of dictionary objects that contain an account number and a boolean flag that indicates if the account is active:



NSArray *arrayOfDictionaryObjects = [NSArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:@"1234", @"accountID", [NSNumber numberWithBool:NO], @"isActive", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"2345", @"accountID", [NSNumber numberWithBool:NO], @"isActive", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"3456", @"accountID", [NSNumber numberWithBool:YES], @"isActive", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"4567", @"accountID", [NSNumber numberWithBool:NO], @"isActive", nil], nil];


If we had a need to process the array and find the first account which was flagged as active, we could once again use the indexOfObjectPassingTest method of the NSArray object, passing in a block, and looking at the value of the isActive key to find the first active account:



NSUInteger activeAccount = [arrayOfDictionaryObjects indexOfObjectPassingTest: ^(id obj, NSUInteger idx, BOOL *stop)                                                                       
{
NSNumber *num = [obj valueForKey:@"isActive"];
if ([num boolValue] == YES)
{
// Stop processing the block
*stop = YES;

return YES;
}
else
return NO;
}];


Notice how once the condition is met, we set the stop value to YES, this will stop any further processing of the block (after the ‘return YES’ statement or course).


Blocks and Enumeration

Enumeration is a common operation on objects, and there are numerous variations of block operations to help. In the code below I create a dictionary of timezone abbreviations and their names:



// Create dictionary of timezone abbreviations and names
NSDictionary *dict = [NSTimeZone abbreviationDictionary];
NSLog(@"Abbreviations: %@", dict);


The output from the above looks like this:


Abbreviations: {

ADT = “America/Halifax”;

AKDT = “America/Juneau”;

AKST = “America/Juneau”;

ART = “America/Argentina/Buenos_Aires”;

AST = “America/Halifax”;



}


We can now enumerate over the dictionary keys and objects, using a block, to print out the abbreviations that have the prefix “America”:



[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
if ([obj hasPrefix:@"America"])
NSLog(@"America Timezone: %@", key);

// To view all key/value pairs
// NSLog(@"Key: %@ Value: %@",key, obj);
}];


The American timezones are shown as:


America Timezone: EDT

America Timezone: AST

America Timezone: PET

America Timezone: CLST

America Timezone: PST




Let’s look at one more example using the same dictionary that was defined above, where one value in the dictionary is the account number and one indicates if the account is active:



NSArray *arrayOfDictionaryObjects = [NSArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:@"1234", @"accountID", [NSNumber numberWithBool:YES], @"isActive", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"2345", @"accountID", [NSNumber numberWithBool:NO], @"isActive", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"3456", @"accountID", [NSNumber numberWithBool:YES], @"isActive", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"4567", @"accountID", [NSNumber numberWithBool:NO], @"isActive", nil], nil];


To print the active accounts from the array of dictionaries, (notice for this example both the 1st and 3rd entries are set as active), we can enumerate the array using a block and check the value of the “isActive” key in the dictionary:



[arrayOfDictionaryObjects enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop)
{
// Print the active accounts:
if ([object valueForKey:@"isActive"] == [NSNumber numberWithBool:YES])
NSLog(@"Active account: %@", [object valueForKey:@"accountID"]);
}];


The output is shown as:


Active account: 1234

Active account: 3456


Recursion and Blocks

Writing a recursive block is pretty straight-forward. Let’s look at a few examples.


Calculating factorials is a common example when speaking recursion. 4 factorial is expressed as 4!, which evaluates to the sum of 4 * 3 * 2 * 1. A block variable below is defined to calculate factorials:



__block int (^factorial)(int);

factorial = ^(int x)
{
if (x <= 1)
return 1;

return x * factorial(x - 1);
};

int y = 4;
NSLog(@"%d factorial is: %d", y, factorial(y));


There is really nothing special about recursion and blocks, with the one important exception, the __block modifier is required in the definition of the block.


A discussion on recursion wouldn’t be complete without visiting the Fibonacci sequence. If it’s been a while, the Fibonacci sequence goes as follows:


0 1 1 2 3 5 8 13 21 34 21…


where the next number in the series is the sum of the previous two values: 0 + 1 = 1; 1 + 1 = 2; 1 + 2 = 3 etc.


The sequence is written as:



A partial series follows:



Below is block that will generate a value in the Fibonacci sequence:



__block int (^fibonacci)(int);

fibonacci = ^(int y)
{
if (y == 0)
return 0;
else if (y == 1)
return 1;
else
return fibonacci(y - 1) + fibonacci(y - 2);
};


To get the value for F7:


int x = 7;

NSLog(@”Fibonacci: %d”, fibonacci(x));

The output is: Fibonacci: 13


没有评论:

发表评论