Search code examples
iphoneobjective-cblockasiformdatarequest

Accessing a property from a ^block causes silly behaviour


I've ran into some issues using ^blocks in Objective-C. I'm trying to set an instance variable from within a block - I've read through some Apple documentation on the topic, and I feel I have tried everything.

@interface MyClass
{
    // I have tried all possible combinations using __weak, __strong and __block.
    __weak __block NSMutableArray *filenames;
}

// *.m
static ASIFormDataRequest *g_request = nil;

@implementation MyClass
-(void) funnymethod
{
    filenames = [NSMutableArray array];
    [filenames addObject:@"This is a string."];
    NSLog(@"%@", filenames);

    g_request = [InitializerClass initializeRequest];
    [g_request setCompletionBlock:^
    {
        filenames = [NSMutableArray array];
        [filenames addObject:@"This is another string."];
        NSLog(@"%@", filenames);
    }];

    [g_object startASynchronous];
}
@end

The code above gives the following output: ( "This is a string." ) (null)

That sucks. So, I've tried different combinations of __weak, __strong and __block - and anything else gives the following output: ( "This is a string." ) ( "This is another string." ) BUT! There's a massive but. The completion block is never exited. The activity indicator in the top bar indicating an open connection keeps spinning, and the screen becomes unresponsive.

How can I set the filenames-object from within the block successfully? Thanks in advance.


Solution

  • What the qualifiers do:

    __block this qualifier allows a closure to modify the value stored in the given variable.

    __weak is a reference to an object that does not prevent the object from being de-allocated.

    __strong is a reference to an object that does prevent the object from being de-allocated.

    What you need to do:

    __weak doesn't do what you want because it doesn't prevent your array from being de-allocated after the current scope ends. Since you are making an async call, there is nothing preventing the run-time from reclaiming the memory used by the array before your block is executed.

    __strong will retain the object past the end of the current scope. This is what you want.

    __block will allow your block to modify the specified variable, however this isn't needed when referencing an instance variable since self will be automatically retained.

    In a reference-counted environment, by default when you reference an Objective-C object within a block, it is retained. This is true even if you simply reference an instance variable of the object. Object variables marked with the __block storage type modifier, however, are not retained.

    Note: In a garbage-collected environment, if you apply both __weak and __block modifiers to a variable, then the block will not ensure that it is kept alive. If you use a block within the implementation of a method, the rules for memory management of object instance variables are more subtle:

    If you access an instance variable by reference, self is retained;

    I think your problem lies here (relevant portions in bold):

    You can specify that an imported variable be mutable—that is, read-write— by applying the __block storage type modifier. __block storage is similar to, but mutually exclusive of, the register, auto, and static storage types for local variables.

    __block variables live in storage that is shared between the lexical scope of the variable and all blocks and block copies declared or created within the variable’s lexical scope. Thus, the storage will survive the destruction of the stack frame if any copies of the blocks declared within the frame survive beyond the end of the frame (for example, by being enqueued somewhere for later execution). Multiple blocks in a given lexical scope can simultaneously use a shared variable.

    As an optimization, block storage starts out on the stack—just like blocks themselves do. If the block is copied using Block_copy (or in Objective-C when the block is sent a copy), variables are copied to the heap. Thus, the address of a __block variable can change over time.

    There are two further restrictions on __block variables: they cannot be variable length arrays, and cannot be structures that contain C99 variable-length arrays.

    Instead of an NSMutableArray, try using a plain NSArray using

    + (id)arrayWithObject:(id)anObject.