Cocoa blocks as strong pointers vs copy

2019-01-13 03:30发布

问题:

I did work several times with blocks as with pointers to which i had strong reference

I heard that you should use copy, but what is the implication in working with blocks as pointers and not with the raw object?

I never got a complain from the compiler, that i should not use

@property (nonatomic, strong) MyBlock block;

but should use

@property (nonatomic, copy) MyBlock block;

as far as i know, the block is just an object, so why to preferrer copy anyway?

回答1:

Short Answer

The answer is it is historical, you are completely correct that in current ARC code there is no need to use copy and a strong property is fine. The same goes for instance, local and global variables.

Long Answer

Unlike other objects a block may be stored on the stack, this is an implementation optimisation and as such should, like other compiler optimisations, not have direct impact on the written code. This optimisation benefits a common case where a block is created, passed as a method/function argument, used by that function, and then discarded - the block can be quickly allocated on the stack and then disposed of without the heap (dynamic memory pool) being involved.

Compare this to local variables, which (a) created on the stack, (b) are automatically destroyed when the owning function/method returns and (c) can be passed-by-address to methods/functions called by the owning function. The address of a local variable cannot be stored and used after its owning function/method has return - the variable no longer exists.

However objects are expected to outlast their creating function/method (if required), so unlike local variables they are allocated on the heap and are not automatically destroyed based on their creating function/method returning but rather based on whether they are still needed - and "need" here is determined automatically by ARC these days.

Creating a block on the stack may optimise a common case but it also causes a problem - if the block needs to outlast its creator, as objects often do, then it must be moved to the heap before its creators stack is destroyed.

When the block implementation was first released the optimisation of storing blocks on the stack was made visible to programmers as the compiler at that time was unable to automatically handle moving the block to the heap when needed - programmers had to use a function block_copy() to do it themselves.

While this approach might not be out-of-place in the low-level C world (and blocks are C construct), having high-level Objective-C programmers manually manage a compiler optimisation is really not good. As Apple released newer versions of the compiler improvements where made. Early on it programmers were told they could replace block_copy(block) with [block copy], fitting in with normal Objective-C objects. Then the compiler started to automatically copy blocks off stack as needed, but this was not always officially documented.

There has been no need to manually copy blocks off the stack for a while, though Apple cannot shrug off its origins and refers to doing so as "best practice" - which is certainly debatable. In the latest version, Sept 2014, of Apple's Working with Blocks, they stated that block-valued properties should use copy, but then immediately come clean (emphasis added):

Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.

There is no need to "show the resultant behavior" - storing the block on the stack in the first place is an optimisation and should be transparent to the code - just like other compiler optimisations the code should gain the performance benefit without the programmer's involvement.

So as long as you use ARC and the current Clang compilers you can treat blocks like other objects, and as blocks are immutable that means you don't need to copy them. Trust Apple, even if they appear to be nostalgic for the "good old days when we did things by hand" and encourage you to leave historical reminders in your code, copy is not needed.

Your intuition was right.

HTH



回答2:

You are asking about the ownership modifier for a property. This affects the synthesized (or auto-synthesized) getter and/or setter for the property if it is synthesized (or auto-synthesized).

The answer to this question will differ between MRC and ARC.

  • In MRC, property ownership modifiers include assign, retain, and copy. strong was introduced with ARC, and when strong is used in MRC, it is synonymous with retain. So the question would be about the difference between retain and copy, and there is a lot of difference, because copy's setter saves a copy of the given value.

    Blocks need to be copied to be used outside the scope where it was created (with a block literal). Since your property will be storing the value as an instance variable that persists across function calls, and it's possible that someone will assign an unoccupied block from the scope where it was created, the convention is that you must copy it. copy is the right ownership modifier.

  • In ARC, strong makes the underlying instance variable __strong, and copy also makes it __strong and adds copying semantics to the setter. However, ARC also guarantees that whenever a value is saved into a __strong variable of block-pointer type, a copy is done. Your property has type MyBlock, which I assume is a typedef for a block pointer type. Therefore, a copy will still be done in the setter if the ownership qualifier were strong. So, in ARC, there is no difference between using strong and copy for this property.

If this declaration might be used in both MRC and ARC though (e.g. a header in a library), it would be a good idea to use copy so that it works correctly in both cases.



回答3:

what is the implication in working with blocks as pointers and not with the raw object?

You are never using the raw value, you always have a pointer to a block: a block is an object.

Looking at your specific example, I am assuming you want to keep the block around, "so why to preferrer copy anyway"enter code here? Well, it's a matter of safety (this example is taken from Mike Ash blog). Since blocks are allocated on the stack (and not on the heap as the rest of the objects in objective-c), when you do something like this:

[dictionary setObject: ^{ printf("hey hey\n"); } forKey: key];

You are allocating the block on the stack frame of your current scope, so when the scope ends (for example your returning the dictionary), the stack frame is destroyed and the block goes with it. So you got yourself a dangling pointer. I would advise reading Mike's article fully. Anyway, you can go with a strong property if when you are assigning the block you copy it:

self.block = [^{} copy];

Edit: After looking at Mike's article date, I am assuming this was the behaviour Pre-ARC. On ARC it seems it's working as expected, and it won't crash.

Edit2: After experimenting with Non-ARC it doesn't crash as well. But this example shows different results depending on the use of ARC or not:

void (^block[10])();

int i = -1;
while(++i < 10)
    block[i] = ^{ printf("%d\n", i); };


for(i = 0; i < 10; i++)
    block[i]();

Quoting Mike Ashe on the different outcomes:

The reason it prints out ten 9s in the first case is quite simple: the block that's created within the loop has a lifetime that's tied to the loop's inner scope. The block is destroyed at the next iteration of the loop, and when leaving the loop. Of course, "destroy" just means that its slot on the stack is available to be overwritten. It just happens that the compiler reuses the same slot each time through the loop, so in the end, the array is filled with identical pointers, and thus you get identical behavior.



回答4:

Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior. For more information, see Blocks Programming Topics.



回答5:

As far as I understand copy is required when the object is mutable. Use this if you need the value of the object as it is at this moment, and you don't want that value to reflect any changes made by other owners of the object. You will need to release the object when you are finished with it because you are retaining the copy.

On the other hand, strong means that you own the object until it is needed. It is a replacement for the retain attribute, as part of ARC.

Source: Objective-C declared @property attributes (nonatomic, copy, strong, weak)