Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Some questions about objective-c block and copy

I write some code use objective-c block, but the result confused me.

@interface MyTest : NSObject

@end

@implementation MyTest

- (void)test {
    NSArray *array = [self array1];  // ok
//    NSArray *array = [self array2];// crash
//    NSArray *array = [self array3];// ok again

    dispatch_block_t block0 = (dispatch_block_t)[array objectAtIndex:0];
    block0();

    dispatch_block_t block1 = (dispatch_block_t)[array objectAtIndex:1];
    block1();
}

- (NSArray *)array1 {
    int a = 10;
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:^{
        NSLog(@"block0: a is %d", a);
    }];
    [array addObject:^{
        NSLog(@"block1: a is %d", a);
    }];
    return array;
}

- (NSArray *)array2 {
    int a = 10;

    return [NSArray arrayWithObjects:^{
        NSLog(@"block0: a is %d", a);
    }, ^{
        NSLog(@"block1: a is %d", a);
    }, nil];
}

- (NSArray *)array3 {
    int a = 10;

    return [NSArray arrayWithObjects:^{
        NSLog(@"block0: a is %d", a);
    },[^{
        NSLog(@"block0: a is %d", a);
    } copy], nil];
}
@end

I am confused about:

  1. why array2 crash? what's the REAL difference between array1 and array2 ?
  2. I read some article said block copy will move a block from stack to heap, but in method array1, I do not copy it and it still works. in array3 I just copy the second block it become ok. why ?
  3. where I must use copy when I use block?

BTW, I run the code in Xcode 4.6, under ARC. Thanks

like image 564
zhaoyk10 Avatar asked Jun 08 '13 15:06

zhaoyk10


People also ask

What is the purpose of block in Objective-C?

Blocks are a language-level feature added to C, Objective-C and C++, which allow you to create distinct segments of code that can be passed around to methods or functions as if they were values. Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary .

What is the difference between copy and retain in IOS?

Retain increases the retain count of an object by 1 and takes ownership of an object. Whereas copy will copy the data present in the memory location and will assign it to the variable so in the case of copy you are first copying the data from a location assign it to the variable which increases the retain count.

How do you declare a block in Objective-C?

Declaring Block VariablesStart with a return type followed by the variable name and a parameter list. You'll need a caret (^) before the variable name and two sets of parentheses. With that in mind, the following example declares a completion block, identical to the one you just saw, but without using custom typedefs.

What is ID in Obj C?

id is the generic object pointer, an Objective-C type representing "any object". An instance of any Objective-C class can be stored in an id variable.


2 Answers

You seem to have found a case of type loss in relation to blocks which the compiler does not handle. But we need to start at the beginning...

The following relates to the use of blocks under ARC. Other scenarios (MRC, GC) are not considered.

That some blocks are created on the stack rather than the heap is an optimisation that could technically be implemented in such a way that programmers never need to be aware of it. However when blocks were first introduced the decision was made that the optimisation would not be transparent to the user, hence the introduction of blockCopy(). Since that time both the specification and the compiler have evolved (and the compiler actually goes beyond the spec), and blockCopy() is not (by the specification) needed it places it used to be, and may not (as the compiler may exceed the spec) be needed in others.

How can the optimisation be implemented transparently?

Consider:

  1. The compiler knows when it creates a stack allocated block; and
  2. The compiler knows when it assigns such a block to another variable; so
  3. Can the compiler figure out for each assignment whether the block needs to be moved to the heap?

The trivial answer is "yes" - move to the heap on any assignment. But that would negate the whole purpose of the optimisation - create a stack block, pass it to another method, which involves and assignment to the parameter...

The easy answer is "don't try" - introduce blockCopy() and let the programmer figure it out.

The better answer is "yes" - but do it smartly. In pseudo-code the cases are:

// stack allocated block in "a", consider assignment "b = a"
if ( b has a longer lifetime than a )
{
   // case 1: assigning "up" the stack, to a global, into the heap
   // a will die before b so we need to copy
   b = heap copy of a;
}
else
{
   if (b has a block type)
   {
      // case 2: assigning "down" the stack - the raison d'être for this optimisation
      // b has shorter life (nested) lifetime and is explicitly typed as a block so
      // can accept a stack allocated block (which will in turn be handled by this
      // algorithm when it is used)
      b = a;
   }
   else
   {
      // case 3: type loss - e.g. b has type id
      // as the fact that the value is a block is being lost (in a static sense)
      // the block must be moved to the heap
      b = heap copy of a;
   }
}

At the introduction of blocks cases 1 & 3 required the manual insertion of blockCopy(), and case 2 was where the optimisation paid off.

However as explain in an earlier answer the specification now covers case 1, while the compiler appeared to cover case 3 but no documentation confirming that was known.

(BTW if you follow that link you will see it contains a link to an older question on this topic. The case described there is now handled automatically, it is an example of case 1 above.)

Phew, got all that? Let's get back to the examples in the question:

  • array1, array3 and array4 are all examples of case 3 where there is type loss. They are also the scenario tested in the previous question and found to be handled by the current compiler. That they work is not an accident or luck, the compiler inserts the required block copies explicitly. However I don't know this is officially documented anywhere.
  • array2 is also an example of case 3 where there is type loss, but it is a variation not tested in the previous question - type loss by passing as a part of a variable argument list. This case does not appear to be handled by the current compiler. So now we have a clue as to why handling of case 3 is not documented - the handling is not complete.

Note that, as mentioned previously, it is possible to test what your compiler does - you can even incorporate some simple tests in your code to immediately abort an application if the tests fail. So you can, if you wish, write code based on what you know the compiler currently handles automatically (so far everything considered accept variadic functions) and which will abort your code should you update the compiler and the replacement lacks the support.

Hope this was helpful and makes sense!

like image 178
CRD Avatar answered Sep 22 '22 18:09

CRD


All three of these crash for me (although I suspect the lack of a copy on the first element of array3 is probably an oversight.) A block has to be copied if you want it to outlive the scope in which it was created. Unless you specifically know that a method copies the object you pass into it, you need to copy it yourself.

like image 37
ipmcc Avatar answered Sep 23 '22 18:09

ipmcc