A look inside blocks

来源:互联网 发布:快刀软件注册机 编辑:程序博客网 时间:2024/05/01 05:42

A look inside blocks

参考地址:http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-1/

Today I have been taking a look at the internals of how blocks work from a compiler perspective. By blocks, I mean the closure that Apple added to the C language and is now well and truly established as part of the language from a clang/LLVM perspective. I had been wondering just what a “block” was and how it magically seems to appear as an Objective-C object (you can copyretainrelease them for instance). This blog post delves into blocks a little.

The basics

This is a block:

123
void(^block)(void) = ^{    NSLog(@"I'm a block!");};

This creates a variable called block which has a simple block assigned to it. That’s easy. Done right? No. I wanted to understand what exactly the compiler does with that bit of code.

Further more, you can pass variables to block:

123
void(^block)(int a) = ^{    NSLog(@"I'm a block! a = %i", a);};

Or even return values from them:

1234
int(^block)(void) = ^{    NSLog(@"I'm a block!");    return 1;};

And being a closure, they wrap up the context they are in:

1234
int a = 1;void(^block)(void) = ^{    NSLog(@"I'm a block! a = %i", a);};

So just how does the compiler sort all of these bits out then? That is what I was interested in.

Diving into a simple example

My first idea was to look at how the compiler compiles a very simple block. Consider the following code:

123456789101112131415
#import <dispatch/dispatch.h>typedef void(^BlockA)(void);__attribute__((noinline))void runBlockA(BlockA block) {    block();}void doBlockA() {    BlockA block = ^{        // Empty block    };    runBlockA(block);}

The reason for the two functions is that I wanted to see both how a block is “called” and how a block is set up. If both of these were in one function then the optimiser might be too clever and we wouldn’t see anything interesting. I had to make the runBlockA function noinline so that the optimiser didn’t just inline that function in doBlockA reducing it to the same problem.

The relevant bits of that code compiles down to this (armv7, O3):

12345678
    .globl  _runBlockA    .align  2    .code   16                      @ @runBlockA    .thumb_func     _runBlockA_runBlockA:@ BB#0:    ldr     r1, [r0, #12]    bx      r1

This is the runBlockA function. So, that’s fairly simple then. Taking a look back up to the source for this, the function is just calling the block. r0 (register 0) is set to the first argument of the function in the ARM EABI. The first instruction therefore means that r1 is loaded from the value held in the adress stored in r0 + 12. Think of this as a dereference of a pointer, reading 12 bytes into it. Then we branch to that address. Notice that r1 is used, which means that r0 is still the block itself. So it’s likely that the function this is calling takes the block as its first parameter.

From this I can ascertain that the block is likely some sort of structure where the function the block should execute is stored 12 bytes into said structure. And when a block is passed around, a pointer to one of these structures is passed.

Now onto the doBlockA method:

12345678910
    .globl  _doBlockA    .align  2    .code   16                      @ @doBlockA    .thumb_func     _doBlockA_doBlockA:    movw    r0, :lower16:(___block_literal_global-(LPC1_0+4))    movt    r0, :upper16:(___block_literal_global-(LPC1_0+4))LPC1_0:    add     r0, pc    b.w     _runBlockA

Well, that’s pretty simple also. This is a program counter relative load. You can just think of this as loading the address of the variable called ___block_literal_global into r0. Then the runBlockA function is called. So given we know that the block object is being passed to runBlockA, this ___block_literal_global must be that block object.

Now we’re getting somewhere! But what exactly is ___block_literal_global? Well, looking through the assembly we find this:

1234567
    .align  2                       @ @__block_literal_global___block_literal_global:    .long   __NSConcreteGlobalBlock    .long   1342177280              @ 0x50000000    .long   0                       @ 0x0    .long   ___doBlockA_block_invoke_0    .long   ___block_descriptor_tmp

Ah ha! That looks very much like a struct to me. There’s 5 values in the struct, each of which are 4-bytes (long). This must be the block object that runBlockA was acting upon. And look, 12 bytes into the struct is what looks suspiciously like a function pointer as it’s called ___doBlockA_block_invoke_0. Remember that was what the runBlockA function was jumping to.

But what is __NSConcreteGlobalBlock? Well, we’ll come back to that. It’s ___doBlockA_block_invoke_0 and ___block_descriptor_tmp that are of interest since these also appear in the assembly:

123456789101112131415161718192021
    .align  2    .code   16                      @ @__doBlockA_block_invoke_0    .thumb_func     ___doBlockA_block_invoke_0___doBlockA_block_invoke_0:    bx      lr    .section        __DATA,__const    .align  2                       @ @__block_descriptor_tmp___block_descriptor_tmp:    .long   0                       @ 0x0    .long   20                      @ 0x14    .long   L_.str    .long   L_OBJC_CLASS_NAME_    .section        __TEXT,__cstring,cstring_literalsL_.str:                                 @ @.str    .asciz   "v4@?0"    .section        __TEXT,__objc_classname,cstring_literalsL_OBJC_CLASS_NAME_:                     @ @"\01L_OBJC_CLASS_NAME_"    .asciz   "\001"

That ___doBlockA_block_invoke_0 looks suspiciously like the actual block implementation itself, since the block we used was an empty block. This function just returns straight away, exactly how we’d expect an empty function to be compiled.

Then comes ___block_descriptor_tmp. This appears to be another struct, this time with 4 values in it. The second one is 20 which is how big the ___block_literal_global is. Maybe that is a size value then? There’s also a C-string called .str which has a value v4@?0. This looks like some form of encoding of a type. That might be an encoding of the block type (i.e. it returns void and takes no parameters). The other values I have no idea about.

But the source is out there, isn’t it?

Yes, the source is out there! It’s part of the compiler-rt project within LLVM. Trawling through the code I found the following definitions within Block_private.h:

123456789101112131415
struct Block_descriptor {    unsigned long int reserved;    unsigned long int size;    void (*copy)(void *dst, void *src);    void (*dispose)(void *);};struct Block_layout {    void *isa;    int flags;    int reserved;    void (*invoke)(void *, ...);    struct Block_descriptor *descriptor;    /* Imported variables. */};

Those look awfully familiar! The Block_layout struct is what our ___block_literal_global is and the Block_descriptor struct is what our ___block_descriptor_tmp is. And look, I was right about the size being the 2nd value of the descriptor. The bit that’s slightly strange is the 3rd and 4th values of the Block_descriptor. These look like they should be function pointers but in our compiled case they seemed to be 2 strings. I’ll ignore that little point for now.

The isa of Block_layout is interesting as that must be what _NSConcreteGlobalBlock is and also must be how a block can emulate being an Objective-C object. If _NSConcreteGlobalBlock is a Class then the Objective-C message dispatch system will happily treat a block object as a normal object. This is similar to how toll-free bridging works. For more information on that side of things, have a read of Mike Ash’s excellent blog post about it.

Having pieced all that together, the compiler looks like it’s treating the code as something like this:

123456789101112131415161718192021222324252627
#import <dispatch/dispatch.h>__attribute__((noinline))void runBlockA(struct Block_layout *block) {    block->invoke();}void block_invoke(struct Block_layout *block) {    // Empty block function}void doBlockA() {    struct Block_descriptor descriptor;    descriptor->reserved = 0;    descriptor->size = 20;    descriptor->copy = NULL;    descriptor->dispose = NULL;    struct Block_layout block;    block->isa = _NSConcreteGlobalBlock;    block->flags = 1342177280;    block->reserved = 0;    block->invoke = block_invoke;    block->descriptor = descriptor;    runBlockA(&block);}

That’s good to know. It makes a lot more sense now what’s going on under the hood of blocks.

What’s next?

Next up I will take a look at a block that takes a parameter and a block that captures variables from the enclosing scope. These will surely make things a bit different! So, watch this space for more.


参考地址:http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-2/

This is a follow on post to A look inside blocks: Episode 1 in which I looked into the innards of blocks and how the compiler sees them. In this article I take a look at blocks that are not constant and how they are formed on the stack.

Block types

In the first article we saw the block have a class of _NSConcreteGlobalBlock. The block structure and descriptor were both fully initialised at compile time since all variables were known. There are a few different types of block, each with their own associated class. However for simplicities sake, we just need to consider 3 of them:

  1. _NSConcreteGlobalBlock is a block defined globally where it is fully complete at compile time. These blocks are those that don’t capture any scope such as an empty block.

  2. _NSConcreteStackBlock is a block located on the stack. This is where all blocks start out before they are eventually copied onto the heap.

  3. _NSConcreteMallocBlock is a block located on the heap. After copying a block, this is where they end up. Once here they are reference counted and freed when the reference count drops to zero.

A block that captures scope

This time we’re going to look at the following bit of code:

1234567891011121314151617
#import <dispatch/dispatch.h>typedef void(^BlockA)(void);void foo(int);__attribute__((noinline))void runBlockA(BlockA block) {    block();}void doBlockA() {    int a = 128;    BlockA block = ^{        foo(a);    };    runBlockA(block);}

The function called foo is just there so that the block captures something, by having a function to call with a captured variable. Once again, we look at the armv7 assembly produced, relevant bits only:

1234567
    .globl  _runBlockA    .align  2    .code   16                      @ @runBlockA    .thumb_func     _runBlockA_runBlockA:    ldr     r1, [r0, #12]    bx      r1

First of all the runBlockA function is the same as before. It’s calling the invoke function of the block. Then onto doBlockA:

12345678910111213141516171819202122232425262728293031323334
    .globl  _doBlockA    .align  2    .code   16                      @ @doBlockA    .thumb_func     _doBlockA_doBlockA:    push    {r7, lr}    mov     r7, sp    sub     sp, #24    movw    r2, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))    movt    r2, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))    movw    r1, :lower16:(___doBlockA_block_invoke_0-(LPC1_1+4))LPC1_0:    add     r2, pc    movt    r1, :upper16:(___doBlockA_block_invoke_0-(LPC1_1+4))    movw    r0, :lower16:(___block_descriptor_tmp-(LPC1_2+4))LPC1_1:    add     r1, pc    ldr     r2, [r2]    movt    r0, :upper16:(___block_descriptor_tmp-(LPC1_2+4))    str     r2, [sp]    mov.w   r2, #1073741824    str     r2, [sp, #4]    movs    r2, #0LPC1_2:    add     r0, pc    str     r2, [sp, #8]    str     r1, [sp, #12]    str     r0, [sp, #16]    movs    r0, #128    str     r0, [sp, #20]    mov     r0, sp    bl      _runBlockA    add     sp, #24    pop     {r7, pc}

Well this is very different to before. Instead of seeing a block get loaded from a global symbol, it looks like a lot more work is being done. It might look daunting, but it’s pretty easy to see what’s going on. It’s probably best to consider the function rearranged, but believe me that this doesn’t alter anything functionally. The reason the compiler has emitted the instructions in the order it has is for optimisation to reduce pipeline bubbles, etc. So, rearranged the function looks like this:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
_doBlockA:        // 1        push    {r7, lr}        mov     r7, sp        // 2        sub     sp, #24        // 3        movw    r2, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))        movt    r2, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC1_0+4))LPC1_0:        add     r2, pc        ldr     r2, [r2]        str     r2, [sp]        // 4        mov.w   r2, #1073741824        str     r2, [sp, #4]        // 5        movs    r2, #0        str     r2, [sp, #8]        // 6        movw    r1, :lower16:(___doBlockA_block_invoke_0-(LPC1_1+4))        movt    r1, :upper16:(___doBlockA_block_invoke_0-(LPC1_1+4))LPC1_1:        add     r1, pc        str     r1, [sp, #12]        // 7        movw    r0, :lower16:(___block_descriptor_tmp-(LPC1_2+4))        movt    r0, :upper16:(___block_descriptor_tmp-(LPC1_2+4))LPC1_2:        add     r0, pc        str     r0, [sp, #16]        // 8        movs    r0, #128        str     r0, [sp, #20]        // 9        mov     r0, sp        bl      _runBlockA        // 10        add     sp, #24        pop     {r7, pc}

This is what that is doing:

  1. Function prologue. r7 is pushed onto the stack because it’s going to get overwritten and is a register which must be preserved across function calls. lr is the link register and contains the address of the next instruction to execute when this function returns. See the function epilogue for more on that. Also, the stack pointer is saved into r7.

  2. Subtract 24 from the stack pointer. This makes room for 24 bytes of data to be stored in stack space.

  3. This little block of code is doing a lookup of the L__NSConcreteStackBlock$non_lazy_ptr symbol, relative to the program counter such that it works wherever the code may end up in the binary when finally linked. The value is then stored to the address of the stack pointer.

  4. The value 1073741824 is stored to the stack pointer + 4.

  5. The value 0 is stored to the stack pointer + 8. By now it may be becoming clear what’s going on. A Block_layout structure is being created on the stack! Up until now there’s the isa pointer, the flags and the reserved values being set.

  6. The address of ___doBlockA_block_invoke_0 is stored at the stack pointer + 12. This is the invoke parameter of the block structure.

  7. The address of ___block_descriptor_tmp is stored at the stack pointer + 16. This is the descriptor parameter of the block structure.

  8. The value 128 is stored at the stack pointer + 20. Ah. If you look back at the Block_layout struct you’ll see that there’s only 5 values in it. So what is this being stored after the end of the struct then? Well, you’ll notice that the value is 128 which is the value of the variable captured in the block. So this must be where blocks store values that they use – after the end of the Block_layoutstruct.

  9. The stack pointer, which now points to a fully initialised block structure is put into r0 and runBlockA is called. (Remember that r0 contains the first argument to a function in the ARM EABI).

  10. Finally the stack pointer has 24 added back to it to balance out the subtraction at the start of the function. Then 2 values are popped off the stack into r7 and pc respectively. The r7 balances the push from the prologue and the pc will now get the value that was in lr when the function began. This effectively performs the return of the function as it sets the CPU to continue executing (the pc, program counter) from where the function was told to return to, lr the link register.

Wow! You still with me? Brilliant!

The final bit of this little section is to check what the invoke function and the descriptor look like. We would expect them to be not much different to the global block from episode 1. Here they are:

12345678910111213141516171819202122
    .align  2    .code   16                      @ @__doBlockA_block_invoke_0    .thumb_func     ___doBlockA_block_invoke_0___doBlockA_block_invoke_0:    ldr     r0, [r0, #20]    b.w     _foo    .section        __TEXT,__cstring,cstring_literalsL_.str:                                 @ @.str    .asciz   "v4@?0"    .section        __TEXT,__objc_classname,cstring_literalsL_OBJC_CLASS_NAME_:                     @ @"\01L_OBJC_CLASS_NAME_"    .asciz   "\001P"    .section        __DATA,__const    .align  2                       @ @__block_descriptor_tmp___block_descriptor_tmp:    .long   0                       @ 0x0    .long   24                      @ 0x18    .long   L_.str    .long   L_OBJC_CLASS_NAME_

And yep, there’s not much difference really. The only difference is the size parameter of the block descriptor. It’s now 24 rather than 20. This is because there’s an integer value captured by the block and so the block structure is 24 bytes rather than the standard 20. We saw the extra bytes being added to the end of the structure when it was created.

Also in the actual block function, i.e. __doBlockA_block_invoke_0, you can see the value being read out of the end of the block structure, i.e. r0 + 20. This is the variable captured in the block.

What about capturing object types?

The next thing to consider is what if instead of capturing an integer, it was an object type such as an NSString. To see what happens there, consider the following code:

1234567891011121314151617
#import <dispatch/dispatch.h>typedef void(^BlockA)(void);void foo(NSString*);__attribute__((noinline))void runBlockA(BlockA block) {    block();}void doBlockA() {    NSString *a = @"A";    BlockA block = ^{        foo(a);    };    runBlockA(block);}

I won’t go into the details of doBlockA because that doesn’t change much. What is interesting is the block descriptor structure that’s created:

123456789
    .section        __DATA,__const    .align  4                       @ @__block_descriptor_tmp___block_descriptor_tmp:    .long   0                       @ 0x0    .long   24                      @ 0x18    .long   ___copy_helper_block_    .long   ___destroy_helper_block_    .long   L_.str1    .long   L_OBJC_CLASS_NAME_

Notice there are pointers to functions called ___copy_helper_block_ and ___destroy_helper_block_. Here are the definitions of those functions:

12345678910111213141516
    .align  2    .code   16                      @ @__copy_helper_block_    .thumb_func     ___copy_helper_block____copy_helper_block_:    ldr     r1, [r1, #20]    adds    r0, #20    movs    r2, #3    b.w     __Block_object_assign    .align  2    .code   16                      @ @__destroy_helper_block_    .thumb_func     ___destroy_helper_block____destroy_helper_block_:    ldr     r0, [r0, #20]    movs    r1, #3    b.w     __Block_object_dispose

I assume these functions are what gets run when blocks are copied and destroyed. They must be retaining and releasing the object that was captured by the block. It looks like the copy function takes 2 parameters as both r0 and r1 are addressed as if they contain valid data. The destroy function looks like it just takes 1. All of the hard work looks like it’s done by _Block_object_assign and _Block_object_dispose. The code for that is within the block runtime code, part of the compiler-rt project within LLVM.

If you want to go away and have a read of the code for the blocks runtime then take a look at the source which can be downloaded from http://compiler-rt.llvm.org. In particular, runtime.c is the file to look at.

What next?

In the next episode I shall take a look into the blocks runtime by investigating the code for Block_copy and see just how that does its business. This will give an insight into the copy and destroy helper functions we’ve just seen get created for blocks that capture objects.


参考地址:http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/

This post has been a long time coming. It’s been a draft for many months, but I’ve been busy writing my book and didn’t have time to finish it off. But now I’ve finished it and here it is!

Following on from episode 1 and episode 2 of my look inside blocks, this post takes a deeper look at what happens when a block is copied. You’ve likely heard the terminology that “blocks start off on the stack” and “you must copy them if you want to save them for later use”. But, why? And what actually happens during a copy? I’ve long wondered exactly what the mechanism is for copying a block. For example, what happens to the values captured by the block? In this post I take a look.

What we know so far

From episodes 1 and 2, we found out that the memory layout for a block is like this:

Block layout diagram

In episode 2 we found out that this struct is created on the stack when the block is initially referenced. Since it’s on the stack, the memory can be reused after the enclosing scope of the block ends. So what happens then if you want to use that block later on? Well, you have to copy it. This is done with a call to Block_copy() or rather just send the Objective-C message copy to it, since a block poses as an Objective-C object. This just calls Block_copy().

So what better than to take a look at what Block_copy() does.

Block_copy()

First of all, we need to look in Block.h. Here there are the following definitions:

123
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))void *_Block_copy(const void *arg);

So Block_copy() is purely a #define that casts the argument passed in to a const void * and passes it to _Block_copy(). There is also the prototype for _Block_copy(). The implementation is in runtime.c:

123
void *_Block_copy(const void *arg) {    return _Block_copy_internal(arg, WANTS_ONE);}

So that just calls _Block_copy_internal() passing the block itself and WANTS_ONE. To see what this means, we need to look at the implementation. This is also in runtime.c. Here is the function, with the irrelevant stuff removed (mostly garbage collection stuff):

12345678910111213141516171819202122232425262728293031323334353637383940414243
static void *_Block_copy_internal(const void *arg, const int flags) {    struct Block_layout *aBlock;    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;    // 1    if (!arg) return NULL;    // 2    aBlock = (struct Block_layout *)arg;    // 3    if (aBlock->flags & BLOCK_NEEDS_FREE) {        // latches on high        latching_incr_int(&aBlock->flags);        return aBlock;    }    // 4    else if (aBlock->flags & BLOCK_IS_GLOBAL) {        return aBlock;    }    // 5    struct Block_layout *result = malloc(aBlock->descriptor->size);    if (!result) return (void *)0;    // 6    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first    // 7    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed    result->flags |= BLOCK_NEEDS_FREE | 1;    // 8    result->isa = _NSConcreteMallocBlock;    // 9    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {        (*aBlock->descriptor->copy)(result, aBlock); // do fixup    }    return result;}

And here is what that method does:

  1. If the passed argument is NULL then just return NULL. This makes the method safe to passing a NULL block.

  2. Cast the argument to a pointer to a struct Block_layout. You may remember what one of these is from episode 1. It’s the internal data structure that makes up a block including a pointer to the implementation function of the block and various bits of metadata.

  3. If the block’s flags includes BLOCK_NEEDS_FREE then the block is a heap block (you’ll see why shortly). In this case, all that needs doing is the reference count needs incrementing and then the same block returned.

  4. If the block is a global block (recall these from episode 1) then nothing needs doing and the same block is returned. This is because global blocks are effectively singletons.

  5. If we’ve gotten here, then the block must be a stack allocated block. In which case, the block needs to be copied to the heap. This is the fun part. In this first step, malloc() is used to create a portion of memory of the required size. If that fails, then NULL is returned, otherwise we carry on.

  6. Here, memmove() is used to copy bit-for-bit then current, stack allocated block to the portion of memory we just allocated for the heap allocated block. This just makes sure that all the metadata is copied over such as the block descriptor.

  7. Next, the flags of the block are updated. The first line ensures that the reference count is set to 0. The comment indicates that this is not needed – presumably because at this point the reference count should already be 0. I guess this line is left in just in case a bug ever exists where the reference count is not 0. The next line sets the BLOCK_NEEDS_FREE flag. This indicates that it’s a heap block and the memory backing it will, once the reference count drops to zero, require free-ing. The | 1 on this line sets the reference count of the block to 1.

  8. Here the block’s isa pointer is set to be _NSConcreteMallocBlock, which means it’s a heap block.

  9. Finally, if the block has a copy helper function then this is invoked. The compiler will generate the copy helper function if it’s required. It’s required for blocks that capture objects for example. In such cases, the copy helper function will retain the captured objects.

That’s pretty neat, eh! Now you know what happens when a block is copied! But that’s only half of the picture, right? What about when one is released?

Block_release()

The other half of the Block_copy() picture is Block_release(). Once again, this is actually a macro that looks like this:

1
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

Just like Block_copy()Block_release() calls through to a function after casting the argument for us. This just helps out the developer, so that they don’t have to cast themselves.

Let’s take a look at _Block_release() (with slight rearrangement for clarity and garbage collection specific code removed):

12345678910111213141516171819202122232425262728
void _Block_release(void *arg) {    // 1    struct Block_layout *aBlock = (struct Block_layout *)arg;    if (!aBlock) return;    // 2    int32_t newCount;    newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;    // 3    if (newCount > 0) return;    // 4    if (aBlock->flags & BLOCK_NEEDS_FREE) {        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);        _Block_deallocator(aBlock);    }    // 5    else if (aBlock->flags & BLOCK_IS_GLOBAL) {        ;    }    // 6    else {        printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);    }}

And here’s what each bit does:

  1. First the argument is cast to a pointer to a struct Block_layout, since that’s what it is. And if NULL is passed in, then we return early to make the function safe against passing in NULL.

  2. Here the portion of the block flags that signifies the reference count (recall from Block_copy() the part where the flags were set to indicate a reference count of 1) is decremented.

  3. If the new count is greater than 0, then there’s still things holding a reference to the block and so the block does not need to be freed yet.

  4. Otherwise, if the flags include BLOCK_NEEDS_FREE, then this is a heap allocated block, and the reference count is 0, so the block should be freed. First of all though, the dispose helper function of the block is invoked. This is the antonym of the copy helper function. It performs the reverse, such as releasing any captured objects. Finally, the block is deallocated through use of _Block_deallocator. If you go hunting in runtime.c then you’ll see that this ends up being a function pointer to free, which just frees memory allocated with malloc.

  5. If we made it here and the block is global, then do nothing.

  6. If we made it all the way to here, then something strange has happened because a stack block has attempted to be released, so a log line is printed to warn the developer. In reality, you should never see this being hit.

And that is that! There’s not really much more to it!

What’s next?

That concludes my tour into blocks, for now. Some of this material is covered in my book. It’s more about how to use blocks effectively, but there’s still a good portion of deep-dive material that should be of interest if you enjoyed this.

 May 26th, 2013  Programming, iOS