52

In Objective-C, I know that blocks are considered objects, so I was wondering if it was possible to store them in an array. This begs the question, are blocks first class objects or are they just treated like objects for the sake of passing them between objects? If they are first class objects, then shouldn't they be storable in arrays?

bbum
  • 162,346
  • 23
  • 271
  • 359
noizetoys
  • 2,923
  • 5
  • 24
  • 15

2 Answers2

91

EDIT: Without going into too much detail, under ARC, you can now add blocks to collections like any other object (see discussion).

I've left the original answer intact below, since it contains some interesting technical details.


This begs the question, are blocks first class objects or are they just treated like objects for the sake of passing them between objects? If they are first class objects, then shouldn't they be storable in arrays?

Blocks are Objective-C objects that very much behave like every other NSObject, with a couple of key differences:

  • Blocks are always generated by the compiler. They are effectively "alloc/init"ed at runtime as execution passes over the blocks declaration.

  • Blocks are initially created on the stack. Block_copy() or the copy method must be used to move the Block to the heap if the Block is to outlive the current scope (see ARC point below).

  • Blocks don't really have a callable API beyond memory management.

  • To put a Block into a Collection, it must first be copied. Always. Including under ARC. (See comments.) If you don't, there is risk that the stack allocated Block will be autoreleased and your app will later crash.

  • Copying a stack based block will copy all of the captured state, too. If you are making multiple copies of a block, it is more efficient to copy it once, then copy the copy (because copying the copy just bumps the retain count since Blocks are immutable).

  • Under ARC, returning a Block from a method or function "just works"; it'll be automatically copied to the heap and the return will effectively be an autoreleased Block (the compiler may optimize away the autorelease in certain circumstances). Even with ARC, you still need to copy the block before sticking it into a collection.

I've written a couple of blog posts both providing an introduction to blocks and some tips and tricks. You might find them interesting.

And, yes, adding 'em to dictionaries is quite useful. I've written a couple of bits of code where I dropped blocks into dictionaries as command handlers where the key was the command name. Very handy.

Cœur
  • 37,241
  • 25
  • 195
  • 267
bbum
  • 162,346
  • 23
  • 271
  • 359
  • 18
    Under ARC, a block does not have to be copied anymore when being added to a collection! Apple said that in their "*ARC Transition Guide*", but that guide is rather dated. Please check out http://blog.parse.com/2013/02/05/objective-c-blocks-quiz/ - especially the answers to example B and example C, as well as the "*Conclusion*" at the end. The need for copying blocks even under ARC arose from a compiler bug in clang (which has been fixed long ago) and was just a work-around since Apple couldn't wait for this bug to be fixed first before releasing the next Xcode version. – Mecki Aug 23 '13 at 09:28
  • 2
    Thanks @Mecki. I need to go back and edit all my answers about blocks. :) – bbum Sep 07 '13 at 21:44
  • This answer by @bbum (a guy who really knows) of all people really points out the lack of definitive and up-to-date information about blocks, ARC and retain cycles. – zaph Sep 14 '13 at 00:20
  • @Zaph There is definitive information in the `llvm` documentation. I suspect, but haven't yet reviewed, that the iOS7 dev docs will be updated, too (at least, I hope they have). – bbum Sep 14 '13 at 16:28
  • 3
    I just tested what Mecki said on the latest `llvm` version provided with Xcode 5 (Apple LLVM 5.0). The bug is not there anymore, so blocks can safely be stored in containers without an explicit copy under ARC. – Gabriele Petronella Oct 07 '13 at 19:55
  • Here's an answer related to this specific issue: http://stackoverflow.com/a/17385402/846273 – Gabriele Petronella Oct 07 '13 at 20:21
  • 1
    @GabrielePetronella: no number of tests can show that C code is correct, because it can be undefined behavior. – newacct Nov 13 '13 at 01:48
  • @newacct sure, but that specific behavior was recognized as a bug and fixed. Test can be meaningless, but in this case they support what stated by the clang specification. – Gabriele Petronella Nov 13 '13 at 02:26
  • 1
    @GabrielePetronella: But nowhere in the ARC specification does it say that a block literal passed directly as an argument to `addObject:` (for example) is copied. – newacct Nov 13 '13 at 02:42
  • @newacct you are looking at it from the wrong perspective. `NSArray` holds a strong reference to its elements when they are added to the collection. This means that ARC will insert a `retain` to comply with the semantic of the strong reference the elements. In case of block pointer types it will insert a `Block_Copy`, as per the clang specification. – Gabriele Petronella Nov 13 '13 at 02:45
  • 1
    @GabrielePetronella: What `NSArray` does with an element you give to it is in its internal implementation. Foundation is compiled by Apple and you don't compile this code yourself; so whether you use ARC or MRC in your program is irrelevant. Even if we consider how Foundation is compiled by Apple, no matter if they used ARC or MRC to compile the NSArray code, it will not perform a copy, per the ARC specification, because the element will have type `id`, i.e. a generic object pointer type, and the ARC specification does not convert a retain to copy for a value of this type. – newacct Nov 13 '13 at 06:08
  • The same considerations applies to any object. In MRC when you store an object into an array, you can release it and expect the object to be still accessible from the array, don't you? Again, this was a compiler bug, fixed months ago, as explicitly stated by one the LLVM maintainers: http://lists.apple.com/archives/objc-language/2012/Feb/msg00071.html – Gabriele Petronella Nov 13 '13 at 07:15
  • 1
    @GabrielePetronella: When you respond to a comment, you need to put "@" and the name of the person you're responding to. Otherwise, nobody will see your response. This question is not about LLVM; this question is about ARC. They did not say what they mean by "bug". They probably mean it does not conform to what they want in LLVM. If they said that it is a bug because it is inconsistent with the ARC specification (which they didn't say), then they would be wrong, because it is consistent with the ARC specification. – newacct Nov 18 '13 at 09:07
  • 1
    @GabrielePetronella: What you don't seem to get is that ARC is a *compile-time feature*. It works by re-writing your code to write calls to retain and release, and in the case where the compile-time type of the expression has block-pointer type, it writes a call to copy instead of retain. That's it. It cannot magically change a call to retain to a call to copy at runtime. That's not how ARC works. That's not what the ARC specification says. – newacct Nov 18 '13 at 09:11
  • 1
    @GabrielePetronella: NSArray cannot possibly send a `copy` message to an element at runtime, because NSArray code operates on `id` and does the exact same thing (sends the exact same messages) for all objects. The message it sends to added objects is `retain`. That's the message that is called at runtime. Period. A `retain` message called on a stack block does not return a copy of it. ARC is irrelevant because ARC is compile-time. – newacct Nov 18 '13 at 09:16
  • @bbum You mention "Copying a stack based block will copy all of the captured state, too. If you are making multiple copies of a block, it is more efficient to copy it once, then copy the copy (because copying the copy just bumps the retain count since Blocks are immutable)." Is it because if you make multiple copies from the original which is on the stack, a new copy on the heap needs to be created every time but if you copy a copy it's already on the heap and hence retain count bumping can kick in. – SayeedHussain Mar 07 '16 at 08:42
46

Yes, blocks are indeed objects, and you can put them in arrays:

NSMutableArray *arr = [NSMutableArray new];
[arr addObject:^(){NSLog(@"my block");}];
void (^ myblock)() = [arr objectAtIndex:0];
myblock();

this will put the "my block" in the console.

Eduardo Scoz
  • 24,653
  • 6
  • 47
  • 62
  • Thanks for the quick response. I assume that the same would be true for adding blocks to any type of collection, for instance a Dictionary. This might have some interesting implications. – noizetoys Nov 03 '11 at 15:54
  • Correct. I use that all the time in C# (blocks inside arrays).. If you have hundreds of items to select from, I think it's much cleaner than having switch statements, plus its dynamic. – Eduardo Scoz Nov 03 '11 at 15:56
  • 19
    Blocks *must be copied* before being added to a collection. Always. Even under ARC. – bbum Nov 03 '11 at 17:11
  • This answer is fantastic. Thanks. – Duck Nov 01 '12 at 18:09
  • nice code and define: +(void)reset:(void(^)())block;[arr addObject:[block copy]]; – zszen Nov 13 '12 at 05:39
  • 5
    @bbum: Blocks don't have to be copied under ARC any longer, not even when being added to a collection. Apple said so in their transition guide, but that was only to work around a clang compiler bug which has been fixed ages ago. See http://blog.parse.com/2013/02/05/objective-c-blocks-quiz/ - especially answers to *Example B*, *Example C* and the *Conclusion* at the end (which also links to a post of a LLVM maintainer explaining the situation). – Mecki Aug 23 '13 at 09:32
  • @Mecki great to hear that. Would you by any chance have any information if this bug was fixed on Apple's LLVM already, or at least a bug report for this? I can't find much info. – Eduardo Scoz Aug 23 '13 at 15:21
  • @EduardoScoz I don't know the exact release number, but the issue was fixed in LLVM over a year ago and whenever Apple ships a new Xcode version, their LLVM is pretty close to bleeding edge (Xcode 4.6 has the fix for sure, even Xcode 4.5 may have it). I can confirm that adding a stack-block to an array works fine with Xcode 4.6 even without a copy (clang-425.0.28, based on LLVM 3.2svn) – Mecki Aug 26 '13 at 17:44
  • Thanks @Mecki, i removed the copy from the example in case people find this useful. Originally when I posted the answer I didn't have the copy call there, so I guess I was ahead of the times! :) – Eduardo Scoz Aug 26 '13 at 21:00
  • 5
    @Mecki: This is completely wrong. In C, just because something doesn't crash doesn't mean it's right -- it could be undefined behavior. Nothing in the ARC specification guarantees that the block will be copied in this case. Just because this version of this ARC compiler happens to inserts a copy does not mean you can rely on it. – newacct Aug 27 '13 at 03:50
  • 1
    @newacct If the LLVM Developer say that the fact the array had to be copied was a bug and that the correct behavior for ARC is to copy the array for you in that case, than there is nothing to argue about. LLVM has an own ARC specification and nobody but the LLVM developers decide what is correct ARC behavior in clang and what not. Apple can claim the opposite, but the fact is Apple has no own compiler, they use clang and clang works the way LLVM.org wants it to work, not the way Apple wants it to work (Apple has no own branch of clang). – Mecki Sep 24 '13 at 13:30
  • 1
    @Mecki: The ARC specification (which is by LLVM) is right here: http://clang.llvm.org/docs/AutomaticReferenceCounting.html Anyone can read it and decide what is or is not correct behavior. – newacct Sep 25 '13 at 21:38
  • @newacct Yes, it is. And this specification says that I'm right. `NSArrays` do `retain` their objects internally (they've always done so and still do so) and if you now read the last paragraph of chapter 7.5, it says that every retain of a block-pointer type shall have the same effect as `Block_copy()`. So when `NSArray` retains an object that is in fact a block pointer, this retain should behave like a `Block_copy()`. Thus there is no need to explicitly copy blocks prior to passing them to a collection like `NSArray` and if this was required in the past, it was a bug in the compiler. – Mecki Nov 08 '13 at 20:46
  • 1
    @Mecki: No. You are wrong. `NSArray` retains elements added to it in its internal code that you do not see. In that internal code, even assuming it is written using ARC, the variable will not have block-pointer type, because all the `NSArray` methods take type `id`. Actually, ARC/MRC is a compile-time thing and is irrelevant for code that is already compiled, like Foundation classes. – newacct Nov 08 '13 at 21:52
  • @newacct Okay, what do I have to do to silence you? Get a bunch of clang developers here, that will shout it in your face? I will do so, if you really insist on that. But honestly, I believe that even if all clang developers sign a piece of paper saying that I'm right, you still will not believe it, because for some totally sick reason, you just don't want to believe it. Okay, do it in whatever way you want, in 20 years nobody will remember your statements here or this discussion and everyone will do it the way I say, because this is the correct way to do it and it works perfectly fine today. – Mecki Nov 11 '13 at 16:53
  • @newsact And whether NSArray has been built with ARC or not is irrelevant. The `retain` method of a stack block created under ARC points to `Block_copy` internally, so calling retain on such a block (from within ARC code or from within non-ARC code) will copy the block, always (after that the pointer is changed to `Block_retain`). You can easily proof that using a debugger. See here: http://pastebin.com/rcCncCy4 Calling `retain` on the block causes the block to be copied, but only because the block itself was created in ARC code. – Mecki Nov 11 '13 at 17:31
  • 1
    @Mecki: What kind of sick problem do you have, that you are making personal attacks on me, when you cannot reason about the language like an normal person? Nowhere in the official ARC specification does it support what you claim. The specification is what matters. Everything else is undefined behavior. As I have already explained to you, the ARC specification says that when a retain is otherwise needed on a "value of block-pointer type", it performs a copy instead. Nowhere does it say that a retain on a object pointer, that happens to point to a block, copies it. – newacct Nov 11 '13 at 21:53
  • 1
    @Mecki: Your code does not even test what you claim, because by assigning the block to `someBlock`, a `__strong` variable of block pointer type, you are causing a copy there, in the ARC code. – newacct Nov 11 '13 at 21:54
  • @newacct What kind of sick problem do **YOU** have? I linked to a quote directly from one of the developers that said calling copy on blocks explicitly should never be required in ARC code, this is the intended behavior and always has been, and still you argue. Basically you are saying the people that write the compiler, and also have written all the specifications you keep referring to, have no idea what they are doing or talking about. Show me just one piece of code where not calling Block_copy crashes or behaves unexpected. I bet you can't, because it doesn't exist anymore. – Mecki Nov 12 '13 at 11:27
  • 11
    @newacct Just for the record (everything is archived) and for everyone coming across this in the future: John McCall said you don't need Block_copy in ARC. See http://bit.ly/1br5J7Z And if you don't know who John McCall is, see http://bit.ly/19gnnGD and search for his name. I'd trust John McCall about one million times more than what any SO user tells you. With ARC enabled the compiler should always known when it has to copy blocks to heap (any failure to do so when required would be a bug), there is no need to ever call `Block_copy`; it may even defeat compiler optimizations to do so. – Mecki Nov 12 '13 at 11:42
  • Great thread guys. For now, I'll go with @Mecki's point that you don't need to copy blocks. I've been doing that for a while and it works correctly, and the documentation (http://clang.llvm.org/docs/AutomaticReferenceCounting.html#blocks) makes it clear that's the intended behavior. Maybe it has been updated recently? – Eduardo Scoz Nov 12 '13 at 16:04
  • 1
    @EduardoScoz: The ARC specification, which you linked to, clearly says that the block may not be copied in this case. "The optimizer may remove such copies when it sees that the result is used only as an argument to a call." I don't understand how there can be any uncertainty about it. – newacct Nov 12 '13 at 19:56
  • 1
    @Mecki: This is programming languages. We have technical specifications. Some person's claims cannot override what is written in the specification. Again and again, I have presented what is written in the specification and how it does not guarantee the block is copied here. And you have not pointed to a single place in the specification to support your claims; and instead keep bringing in other irrelevant issues. – newacct Nov 12 '13 at 20:07
  • @newacct The detail you seem to be missing is that at compile-time, ARC *does* know the type of the object being added to the array, and can correctly emit a `Block_copy` in place of the retain if it is a block. The fact that the method is declared as taking `id` does not change that. If you don’t understand that, I’m not sure I can help you. – jbg Jul 09 '14 at 00:08
  • @newacct And regarding the spec. The part of the spec you have been quoting "again and again" merely says that the block will not be copied at the time of being passed as a parameter to `[NSMutableArray addObject:]` or similar. However, later NSArray itself will store that block with "strong" semantics (as it does with all objects stored in it), and the ARC specification requires that this is equivalent to a `Block_copy`. LLVM didn’t used to follow that part of the spec, which was a bug, and now it does. – jbg Jul 09 '14 at 00:12
  • 1
    @JasperBryant-Greene: "and can correctly emit a Block_copy in place of the retain if it is a block" There is no "retain" by the caller involved in passing an argument to a function. – newacct Jul 09 '14 at 00:33
  • 1
    @JasperBryant-Greene: "the ARC specification requires that this is equivalent to a Block_copy" ARC is *compile-time*. "emitting" is done by the compiler. The ARC specification requires code that is compiled under ARC, where storing a value of *block pointer compile-time type*, to be changed by the compiler to a `Block_copy`. NSArray is not compiled by you. You don't know whether it is compiled in ARC, and in any case it doesn't matter because either way it would be the same -- inside NSArray's method implementations there is no variable of block-pointer compile time type. – newacct Jul 09 '14 at 00:36
  • 1
    @JasperBryant-Greene: ARC does not change a *runtime* call to the method `retain` to have copy semantics. This is according to the ARC specification (which talks about changing retain to copy semantics compile-time) and can be readily verified on any version of the compiler. It's hard to imagine how that could possibly work anyway, because a project can mix ARC and non-ARC code, so how is the internal NSArray method (compiled by Apple) supposed to know whether it is being used from ARC or not? – newacct Jul 09 '14 at 00:37
  • I don’t know how to help you any further. You’re arguing that things don’t work the way that they quite clearly, and verifiably, do. I suggest that you write some basic code and test it yourself, as I already have done, and satisfy yourself that things work the way that they are documented to. – jbg Jul 09 '14 at 23:26
  • 1
    @JasperBryant-Greene: Please respond to comments with `@`. I am not "arguing" anything, and I don't need any "help". I am the one who is correcting an error in the answer. I am saying what the spec clearly says. No amount of "verifying" can show that something is correct, because it can be undefined behavior. You have not pointed out and justified anything you disagree with in what I said. – newacct Aug 11 '14 at 06:58
  • The spec clearly says the opposite of what you keep insisting, and the compiler behaves accordingly. The developers have repeatedly stated that the current behaviour is correct. All of these things are clear to everyone else, whether you arrogantly think you are "correcting an error" in the answer or not. – jbg Aug 26 '14 at 01:28