Modifying external variables w.r.t. pointers


#1

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?


#2

[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;
}

#3

ibex10 - thank you for the reply.

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;
}

#4

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.


#5

Thank you for some great insight - I appreciate it!