In this chapter, under “Modifying External Variables”, the book says:
However, we do exactly the opposite in the Vowel Movement program - we modify the devowelizedStrings array, without error. Does this work, because devowelizedStrings is a pointer, rather than a primitive variable? Or, is there some other rule at play here?
[quote=“AkamaiApps”]In this chapter, under “Modifying External Variables”, the book says:
However, we do exactly the opposite in the Vowel Movement program - we modify the devowelizedStrings array, without error. Does this work, because devowelizedStrings is a pointer, rather than a primitive variable? Or, is there some other rule at play here?[/quote]
The book should have been more specific and said external local variables: that is, variables defined inside a function but outside the block.
// main.m
#import <Foundation/Foundation.h>
// Function returning a block of type void (^)()
void (^getBar ())();
unsigned long _global = 1;
int main (int argc, const char * argv[]) {
// Define a block to modify the global
void (^foo)() = ^ {
_global = 2;
};
// Get the bar block
void (^bar)() = getBar ();
// Invoke the blocks
printf ("Before: _global: %ld\n", _global);
foo ();
printf ("After: _global: %ld\n", _global);
bar ();
bar ();
bar ();
return 0;
}
// Function returning a block of type void (^)()
void (^getBar ())()
{
unsigned long __block local = 3;
printf ("Before: local: %ld\n", local);
void (^bar)() = ^ {
local += 1;
printf ("After: local: %ld\n", local);
};
return bar;
}
I understand that these are local variables within the function. What I do not understand is why I cannot modify an integer or a float inside the block without declaring them with the __block keyword; however, I can modify our NSMutableArray* with the addObject: method without any issues.
Try it:
#import <Foundation/Foundation.h>
typedef void (^ArrayEnumerationBlock)(id, NSUInteger, BOOL *);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Create array of strings and a container for devowelized ones
NSArray *originalStrings = @[@"Sauerkraut", @"Raygun", @"Big Nerd Ranch", @"Mississippi"];
NSLog(@"original strings: %@", originalStrings);
// Compose a block and assign it to the variable
NSMutableArray *devowelizedStrings = [NSMutableArray array];
// Create a list of characters to be removed from the string
NSArray *vowels = @[@"a", @"e", @"i", @"o", @"u"];
// Declare a local external variable
NSUInteger myInteger = 0;
ArrayEnumerationBlock devowelizer = ^(id string, NSUInteger i, BOOL *stop) {
NSRange yRange = [string rangeOfString:@"y" options:NSCaseInsensitiveSearch];
// Did I find a y?
if (yRange.location != NSNotFound)
{
*stop = YES; // Prevent further iterations
return; // End this iteration
}
NSMutableString *newString = [NSMutableString stringWithString:string];
// Iterate over the array of vowels, replacing occurrences of each with an empty string
for (NSString *s in vowels)
{
NSRange fullRange = NSMakeRange(0, [newString length]);
[newString replaceOccurrencesOfString:s withString:@"" options:NSCaseInsensitiveSearch range:fullRange];
}
// Try to modify the local external variable, and you get a compiler error about using __block
myInteger += 1;
// However, here we modify another local external variable with addObject: and it works
[devowelizedStrings addObject:newString];
};
// Iterate over the array with our block
[originalStrings enumerateObjectsUsingBlock:devowelizer];
NSLog(@"devowelized strings: %@", devowelizedStrings);
}
return 0;
}
The requirement to use __block on a value-type object (such as an integer or float object) created inside a function is purely for helping the compiler generate efficient code with less effort. If the compiler is given a hint that the block is not going to modify the value object, it can generate (with less effort) smaller code for the block. If however, the block is to modify the value object, then the compiler has to do more work to generate code for the block.
For a reference-type object such as an NSMutableArray* or and int *, the compiler does not have to spend more effort to generate code if you intend to mutate the object inside the block; this is because the block already knows the address of the object.