Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use the __block specifier on an object pointer even though it works without it?

I am using a CAAnimation completion block (using CAAnimationBlocks) to provide processing at the end of an animation and part of that completion block modifies the animated CALayer. This works even if layer isn't declared with the __block specifier, given the object pointer remains constant, however I am really treating the object as read/write.

One aspect of the Apple Guide that bothers me is:

__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.

Given the layer is a collection iterator, that looks to me like it will actually break if I do use the __block specifier.

Here is the code in question:

for (CALayer *layer in _myLayers)   // _myLayers is an ivar of the containing object
{
    CAAnimationGroup *group = ...;
    ...
    group.completion = ^(BOOL finished)
    {
        [CATransaction begin];
        [CATransaction setValue:(id)kCFBooleanTrue
                         forKey:kCATransactionDisableActions];
        layer.position = [self _findPosition];
        [CATransaction commit];

        [layer removeAnimationForKey:@"teleportEffect"];
    };

    [layer addAnimation:group forKey:@"teleportEffect"];
}

My actual question is: Am I doing this right (my spider sense is tingling).

EDIT I should also add that my app uses MRR, however there are no issues with retain/release given the layers are static in nature (their lifetime is that of the containing NSView). Also I appear to be doing precisely what the Patterns to Avoid section of the guide say I shouldn't do, although it's not clear (to me) why.

like image 343
trojanfoe Avatar asked Sep 16 '12 08:09

trojanfoe


2 Answers

__block variables are mutable within the block (and the enclosing scope) and are preserved if any referencing block is copied to the heap.

I don't think that in your case you need a block variable because you are changing the value of the object layer inside the block, since it belong to the _myLayers array that seems to be an instance variable it is difficult that the object will be released before each block performed ... However you can add the __block storage type modifier to retain the object, but if you are using ARC, object variables are retained and released automatically as the block is copied and later released.

like image 87
aleroot Avatar answered Oct 14 '22 22:10

aleroot


EDIT:

As to your concern with the Anti-patterns you mention, I think that in both anti-pattern examples, the critical point is the the variable declaration and the "block literal" assigned to it have different scope. Take the for case presented there:

void dontDoThis() {
  void (^blockArray[3])(void);  // an array of 3 block references

  for (int i = 0; i < 3; ++i) {
    blockArray[i] = ^{ printf("hello, %d\n", i); };
    // WRONG: The block literal scope is the "for" loop
  }
}
  1. blockArray is visible within the whole method body;

  2. in the for loop, you create a block; a block is an object (some storage in memory) and has an address; the block as an object has "stack-local data structure" (from the reference above), i.e., it is allocated on the stack when you enter the method;

  3. the fact that the "block literal" is treated as a variable local to the for loop, means that that storage can be reused at each successive iteration;

  4. the block addresses are assigned to blockArray elements;

  5. when you exit the for loop, blockArray will contain addresses of blocks that are possibly not there anymore and/or have been overwritten at each step depending on what the compiler does to stack data structure created within a for scope.

Your case is different, since your local variable is also within the for scope, and it will not be visible outside of it.

The case presented as an anti-pattern is similar to this:

 {
 int array[3];

 for (int i = 0; i < 3; ++i) {
    int a = i;
    array[i] = &a;
    // WRONG: The block literal scope is the "for" loop
 }

Very likely, the a variable within the for scope will be allocated on stack just once and then reused at each iteration of the loop. In principle, a (one copy) will be still there (I am not sure, actually, the C standard should be inspected) outside of the loop, but it is pretty clear that the meaning of that code is not really sensible.

OLD ANSWER:

__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.

I think this can be better understood like this: the lexical scope of the __block variable and all blocks (as per above definition) will share the same storage for that variable. So, if one block (or the original lexical scope) modifies the variable (I mean here the variable pointing to the object), that change will be visible to all others.

Given this, one effect of declaring a variable as __block is that in the non-ARC case the object pointed-to by it will not be automatically retained by each block where it is passed into (with ARC, the retain is done also for __block variables).

Both when using ARC and not using ARC, you need to use the __block specifier when you want to change the variable value and want that all blocks use the new value. Imagine that you had a block to initialize your _myLayers ivar: in this case, you would need to pass the _myLayers variable into the block as a __block variable, so that it (vs. a copy of it) can be modified.

In your case, if you are not using ARC, then, it all depends on whether the object pointed to by layer will be still there when the block is executed. Since layer comes from _myLayers, this converts into whether the object owning _myLayers will be still there when the block is executed. The answer to this is normally yes, since the block we are talking about is the completion block for an animation on that layer. (It would have been different, say, if it were the completion block for a network request).

Hope this helps.

like image 37
sergio Avatar answered Oct 14 '22 22:10

sergio