Background – From function pointer to block

1.1 Introduction of Blocks

Blocks are a common syntactic component in Objective-C and are typically used to pass behavior (such as callbacks).

But in fact, C already has a syntactic component that transmits behavior: function Pointers. It points to executable code in memory. Dereference of a function pointer invokes the corresponding function, which can be called with arguments just like a normal function call. Instead of “directly” leading from a specified function name to executable code, a function pointer calls a function “indirectly” by calling it from a variable (that is, the value of the function pointer). This indirect call allows it to dynamically select the corresponding function to execute at run time based on the value of the function pointer.

The advantage of function Pointers is that a set of behaviors, or rather, operations on data can be abstracted into data, which is more suitable for object-oriented design concepts. But it’s a long way from true encapsulation — function Pointers don’t actually contain data.

Function pointer contains external data through parameter list. This method requires the programmer to maintain by himself, which has poor flexibility and high maintenance cost and reuse difficulty.

We wondered if we could create a syntax that, like a function pointer, contains a set of executable code but automatically captures the variables used.

In Objective-C, blocks provide this capability.

1.2 Brief introduction to Block

Blocks can be used as follows:

Here, we can see that blocks and function Pointers are declared similarly, except that the asterisk (*) has been changed to the stripper (^). In fact, Apple’s extension of Block to C also mentions this difference in their claims [2].

In fact, Block syntax introduces four new concepts to C [3]:

  1. A new compound type, Block reference;
  2. Block literal;
  3. A new storage type and its corresponding modifiers__block;
  4. Language primitivesBlock_copyandBlock_release.

This article is not going to discuss the four new concepts and components directly. Instead, we will design a syntactic component, GuBlock, that is similar to a Block, from a designer’s point of view. In the process, we will lead you to a better understanding of what blocks do. “Why do you do this?” and “What are the benefits of doing this?”

1.3 GuBlock = Function pointer + variable capture + lifecycle management

We build on the function pointer’s ability to “pass a piece of executable code” by adding the ability to automatically capture the variables it uses in the scope in which it is declared.

So there is:


GuBlock = A function pointer + Variable to capture . \text{GuBlock} = \text{function pointer} + \text{variable capture}.

But in fact, as stated in [1], the reason a function pointer is less error-prone than a normal pointer is that it doesn’t require the program writer to manage memory manually because it has no memory to manage, and once GuBlock has added its own variables to the function pointer, there are life cycle issues to consider. So this “equation” should be modified to look like this with “side effects” added:


GuBlock = A function pointer + Variable to capture + (Side effects) Lifecycle management . \text{GuBlock} = \text{function pointer} + \text{variable capture} + \text{(side effects) life cycle management}.

This was our starting point and principle in designing GuBlock.

2 GuBlock conceptual design and code implementation

From the above discussion, we can conclude that we need to add variables to function Pointers in C/C++.

A function pointer represents behavior, and dereferencing it represents triggering related behavior. On this basis, we package the data used by it into a structure.

We also declare the GuBlock form to create objects of type GuBlock using the unary constructor destracter (^).

// main.m
int main()
{
    int a = 1; 
    void (^myGuBlock)(void) = ^ { printf("a = %d", a); }; 
    myGuBlock(); 

    return 0; 
}
Copy the code

The compiler will rewrite myGuBlock as a structure containing a pointer to a function that completes {printf(“a = %d”, a); } The behavior of the function body. Also, since the function body {printf(“a = %d”, a); } contains the same scoped variable a as myGuBlock. According to the above design, this structure will have the value of this data A.

So, when main.m is rewritten, it looks like this:

// main.cpp
struct rewritten_myGuBlock {
    void (*funcPtr)(struct rewritten_myGuBlock *);      // The actual function pointer.
                                                        // Add a structure pointer of the same type as the function.
                                                        // the first argument to a C++ class member function is always this.

    int capturedA;                                      // The captured variable A.
};
Copy the code

It should be assigned and used as follows:

// main.cpp

void function_of_myGuBlock_in_main(struct rewritten_myGuBlock * _cself) 
{
    printf("a = %d", _cself->capturedA); 
}

int main(a) 
{
    int a = 1; 
    struct rewritten_myGuBlock rewrittenMyGuBlock = { function_of_myGuBlock_in_main, a }; 
    rewrittenMyGuBlock->function_of_myGuBlock_in_main(rewrittenMyGuBlock); 
}
Copy the code

That is:

So we now have a simple compound type called GuBlock. The compiler rewrites it as a structure that contains the pointer to the function in which the corresponding code block is located and the data captured in that code block.

At this point, Block’s design draft looks like this:

2.1 GuBlock optimization for variable scope

The following is a preliminary design of GuBlock, which packages all captured variables in the same way.

This is a wasteful approach because not every captured variable needs to be stored in such a complex way.

2.1.1 Processing of automatic variables

Automatic variables, or local variables, have block scope because their scope is limited to the code block in which they are located. The function pointer in GuBlock points to a code block that has a different scope than the local variable:

Therefore, we need to store a copy of the local variable in the overwritten “GuBlock” structure.

2.1.2 Processing of global variables

Global variables are file-level scopes, so they are actually naturally accessible in the block of code to which GuBlock’s function pointer points, and therefore do not need to be stored specifically in the “overwritten GuBlock” structure.

Static global variables are also global variables and are treated exactly the same as global variables.

2.1.3 Processing of static local variables

Finally, static local variables.

The identifier of a static local variable is visible only in the block of code it declares, but its memory space is not destroyed and returned even if the block ends. Because it survives the end of its declared block of code, there is a possibility that it will continue to be used beyond the block declared by a static local variable. To maintain this property of a static local variable, we can’t just use its value, but need to record its address (the address of a static local variable points to memory that is not in the stack frame, but shares the data area with the global variable, and therefore is not destroyed or freed as the code block ends).

Based on the above discussion of the three variables, we can obtain a GuBlock optimized for the scope of the identifier:

2.1.4 Discussion on automatic variable interception

Since static global variables are treated exactly the same as global variables, can automatic variables (local non-static variables) be treated the same as static local variables? Can we divide the whole scope in two, as shown below, using only the “global/local” difference as the field of how we treat the data?

Static local variables store their “address” and not their “value” as described in the previous section: A static local variable is still alive in its declared block, so it can be accessed by a pointer, and outside of its declared block, it cannot be accessed by an identifier (variable name) call, only by a pre-stored address value. If the address of an automatic variable is stored, the automatic variable is destroyed outside the code block it declares. Therefore, we prefer to keep the value of the automatic variable when GuBlock is created. That’s why Kazuki Sakamoto, in his book Objective-C Advanced Programming iOS and OS X Multithreading and Memory Management, uses the word “capture” for automatic variables instead of the common term “capture” — the capture of automatic variables, We get the value of the automatic variable at the time the Block was created. We don’t care what happens to it after that.

2.1.5 summary

The above discussion is summarized as follows:

  1. For global variables, because they can be accessed from anywhere, GuBlock does not capture them;
  2. For static variables:
    1. A static global variable is exactly like a global variable, and we can always access it correctly by identifier (variable name), so GuBlock doesn’t capture it;
    2. The memory space of a static local variable always exists, but its identifier becomes invisible when it leaves the code block it declares. Therefore, to ensure access to it, we need to store its address in advance.
  3. For automatic variables: when leaving the scope in which they are declared, its identifier becomes invisible and the memory space is destroyed and returned, so we need to capture its value at GuBlock creation.

2.2 Storage of GuBlock

GuBlock becomes a structure after being rewritten by the compiler, and a structure variable is a variable whose scope and life span should be the same as any other variable.

That is, there should also be global gublocks, automatic gublocks, and static gublocks. Gublocks are classified in this way so that they behave like normal variables, their identifiers are visible and the storage space they occupy can be freed up properly.

2.2.1 Stack GuBlock – Mimics the design of automatic variables

We’ll talk about automatic gublocks first, because that’s what we used before. We put a GuBlock declaration in the body of the function, and its overwritten structure is therefore placed in the body of the function. Automatic gublocks that live within the stack frame of this function, and therefore live on the stack, are called stack gublocks.

2.2.2 Global GuBlock – Mimics the design of global variables

Global variables appear and can be accessed from anywhere in the program, each time using one variable. Therefore, a global GuBlock should also have these two properties:

  1. Can be accessed from anywhere in the program;
  2. The same GuBlock is accessed each time.

To satisfy the first, we store it in the data area.

To satisfy the second, its contents are independent of the state at which it was created.

Based on these two points, we make GuBlock objects that are declared in a code block but do not capture any automatic variables also global guBlocks. Global gublocks are placed in the data area.

We use a flag bit GUBLOCK_IS_GLOBAL to indicate whether the current GuBlock is “global GuBlock”. The structure rewritten by GuBlock becomes:

When the corresponding GUBLOCK_IS_GLOBAL is set to non-zero, it is a global variable.

2.2.3 Heap GuBlock – the design of imitation static local variables

Static local variables are still accessible by address outside of their scope. So when you design this type of GuBlock as a static local variable, move it onto the heap. This way, it is not automatically destroyed outside of the block of code in which it is declared, but we manage the life cycle ourselves.

Since an object can be copied, there should be an operation to destroy it.

So, not only do we need to copy the GuBlock, we also need to release it.

Copy of GuBlock

The copy of GuBlock is simple, we just need to grasp three main points: “who AM I “,” where am I from “, “where am I going”.

In order, answer “Who am I?” first. Obviously, the variable being copied is a GuBlock, overwritten as a structure. Therefore, we just need to know the size of the structure and apply the corresponding size on the heap using malloc().

And then “Where do I come from?” that is, from the stack. Gublocks that trigger copy should come from the stack. If the GuBlock is from a data block (that is, a global GuBlock), the copy of the global variable should do nothing, because we want to ensure that there is only one copy of the global variable. For gublocks on the heap, reminiscent of smart Pointers and ARC designs, we add a reference count to it. At the same time, to identify the specific location of the GuBlock (reference counting only makes sense for heap gublocks), we change the previous GUBLOCK_IS_GLOBAL to an enumeration to get the specific life cycle and scope of the GuBlock:

And finally, “Where am I going?” Go to the pile. After leaving the code block declared by the GuBlock, the stack frame is destroyed and freed, but the GuBlock is preserved because it is on the heap, not “under the nest” of the stack frame.

So, here we can define the basic operation Block_copy() :

/** * description: Copy the passed GuBlock once: * Copy the global GuBlock, do nothing; * If a stack GuBlock is copied, it is copied to the heap; * * @param aGuBlock The GuBlock to be copied. * * @return The copied result. */

void *Block_copy(const void *aGuBlock)
{
    struct rewritten_myGuBlock *src = (struct rewritten_myGuBlock *)aGuBlock; 
    if (src->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        SRC is a global GuBlock
        // Do nothing, return directly.
        return src; 
    }
    else if (src->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP)
    {
        SRC is a GuBlock on the heap
        // Increment the reference count and return.
        ++ (src->referenceCount); 
        return src; 
    }
    else
    {
        // In this case, rewrittenMyGuBlock is a stack GuBlock that emulates an automatic variable
        // Create the same size space and copy it by bit.
        size_t sizeOfSrc = sizeof(struct rewritten_myGuBlock); 
        struct rewritten_myGuBlock *dest = (struct rewritten_myGuBlock *)malloc(sizeOfSrc); 
        if(! dest)return NULL;
        memmove(dest, src, sizeOfSrc);
        // Also, the GUBLOCK_TYPE and reference count of the copied heap GuBlock should be set correctly.
        dest->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP; 
        dest->referenceCount = 1; 
        returndest; }}Copy the code

The release of GuBlock

The reference count of GuBlock variables on the heap increases with the copy operation. Since we need to destroy and release a heap object when its reference count reaches zero, we need at least one operation to reduce its reference count. That’s the release operation.

Obviously, for a global GuBlock, there is always only one copy and its life cycle is not managed by us; For the stack GuBlock, it is designed to resemble an automatic variable, that is, it will destroy itself when the stack frame corresponding to the code block in which it is declared is destroyed. So all we really have to deal with is the heap GuBlock.

With this classification discussion, we actually have the code for Block_release() :

/** * description: Release the passed GuBlock: release the global GuBlock, do nothing; * If you release the stack GuBlock, do nothing; * * @param aGuBlock The GuBlock to be released. */
void Block_release(const void *aGuBlock)
{
    struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; 
    if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        AGuBlockToRelease is a global GuBlock
        // Do nothing, return directly.
        return; 
    }
    else if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_ON_STACK)
    {
        SRC is a GuBlock on the heap
        // Do nothing, return directly.
        return; 
    }
    else
    {
        AGuBlockToRelease is aGuBlock on the heap
        // Reduce the reference count.
        -- (aGuBlockToRelease->referenceCount); 
        if (aGuBlockToRelease->referenceCount == 0)
        {
            // If the reference count is returned to zero after this reduction, the memory space is freed.
            
            free(aGuBlockToRelease);	
        }
        return; }}Copy the code

With copy and release, the design of memory management for heap Gublocks is complete.

2.2.4 summary

After the classification discussion in this section, we divide gublocks into global gublocks, stack gublocks and heap gublocks.

Global GuBlock

After the above discussion, we get the classification results as follows.

Depending on the different destruction times we expect, we can all define the correct location of the GuBlock and the copy/release operation:

  • Global GuBlock:
    • Since the global GuBlock requires only one copy, nothing is done with its copy;
    • Its destruction timing is controlled by the program, and its release operation should be oneno-op;
  • Heap GuBlock:
    • Since the lifetime of a heap GuBlock needs to be managed by ourselves, copies and releases of it affect its reference count, and when the reference count goes to zero, its space on the heap is freed.
  • Stack GuBlock:
    • Since the purpose of a copy of a stack GuBlock is to create a new GuBlock on the heap, a copy of a stack GuBlock copies it to the heap and initializes the GuBlock reference count on the heap to 1.
    • Its destruction timing is controlled by the program, and its release operation should be oneno-op.

2.3 GuBlock’s capture of non-native types

After the classification discussion and processing in the previous section, all primitive types — ordinary scalars, structs, unions, function Pointers, and so on — can be correctly introduced. They are captured simply by value copy. But if you need to copy more complex objects that require deep copies, such as C++ stack objects, objective-C objects, and GuBlock objects themselves, shallow copies won’t do the job.

Therefore, we need to discuss them in categories.

2.3.1 GuBlock capturing Objective-C objects

Capturing an Objective-C object is actually a little bit easier, because objective-C objects must live on the heap, and there’s a pointer to it, and we access that Objective-C object through that pointer. Therefore, the Objective-C object we capture is nothing more than a normal pointer scoped with a GuBlock.

Therefore, we don’t need to make any changes to the structure of the GuBlock as captured variables change from primitive types to objective-C object Pointers. We just need to change copy and release a little bit, from shallow copies to deep copies.

Modify GuBlock copy:

For the previous three gublocks, we can get exactly the same definition as before:

  • The global GuBlock does not capture any objective-C object Pointers as automatic variables
  • The stack GuBlock captures Pointers to Objective-C objects as automatic variables
  • The heap GuBlock is derived from the copy of the stack GuBlock

Therefore, we just need to “create heap gublocks from the copy of stack gublocks” to be done correctly. All we need to do is add a copy of the Objective-C object pointer to Block_copy() ‘s operation on the stack GuBlock: create a new pointer inside the heap GuBlock that points to the objective-C object pointer of the stack GuBlock.

In the previous Block_copy() code section, we added processing of captured objects of type ID to the copy logic of the stack GuBlock:

/** * description: Copy the passed GuBlock once: * Copy the global GuBlock, do nothing; * If a stack GuBlock is copied, it is copied to the heap; * * @param aGuBlock The GuBlock to be copied. * * @return The copied result. */
void *Block_copy(const void *aGuBlock)
{
    struct rewritten_myGuBlock *src = (struct rewritten_myGuBlock *)aGuBlock; 
    if (src->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        SRC is a global GuBlock
        // Do nothing, return directly.
        return src; 
    }
    else if (src->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP)
    {
        SRC is a GuBlock on the heap
        // Increment the reference count and return.
        ++ (src->referenceCount); 
        return src; 
    }
    else
    {
        // In this case, rewrittenMyGuBlock is a stack GuBlock that emulates an automatic variable
        // Create the same size space and copy it by bit.
        size_t sizeOfSrc = sizeof(struct rewritten_myGuBlock); 
        struct rewritten_myGuBlock *dest = (struct rewritten_myGuBlock *)malloc(sizeOfSrc); 
        if(! dest)return NULL;
        memmove(dest, src, sizeOfSrc);
        // Also, the GUBLOCK_TYPE and reference count of the copied heap GuBlock should be set correctly.
        dest->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP; 
        dest->referenceCount = 1; 
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
        GuBlock_copy_helper_for_objc_object(dest, src);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
        returndest; }}/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void GuBlock_copy_helper_for_objc_object(void *dest, const void *src);
{
    struct rewritten_myGuBlock *destGuBlock = (struct rewritten_myGuBlock *)dest; 
    struct rewritten_myGuBlock *srcGuBlock = (struct rewritten_myGuBlock *)src; 
    destGuBlock->capturedObj = srcGuBlock->capturedObj; 
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Copy the code

We’ve wrapped the new part of the code in conspicuous comments, and obviously, with ARC’s help, just point a new pointer to the area where the NSObject object is, and the reference count is automatically incresed by 1. Thus, we have completed a copy(actually a retain) of the NSObject object on the heap.

A copy of a GuBlock that is already on the heap increases its reference count, exactly as it would have done:

Since we can copy gublocks that capture Objective-C objects, we should also provide release behavior.

Modify the release of GuBlock:

For gublocks that capture Objective-C objects, from the previous section we get definitions of three storage types of Gublocks, namely:

  • Global GuBlock does not capture any automatically typed objective-C object Pointers
  • The stack GuBlock captures the auto-typed pointer to objective-C objects. The stack GuBlock is destroyed as well as the auto-variable when the stack frame is destroyed, and the captured Objective-C object decreases its reference count due to the loss of a holder — it may even be destroyed and returned because the reference count goes to zero
  • Heap gublocks are not destroyed due to scoped changes and need to be passed through manuallyBlock_release()Method to reduce its reference count and even destroy it when appropriate. If the heap GuBlock is destroyed, the reference count of the Objective-C objects it captures is reduced accordingly

So, all we really need to do is Block_release() on the heap GuBlock.

In the code for Block_release() in Section 2.2.3.2, add processing of captured objects of type ID to the release logic of heap GuBlock:

/** * description: Release the passed GuBlock: release the global GuBlock, do nothing; * If you release the stack GuBlock, do nothing; * * @param aGuBlock The GuBlock to be released. */
void Block_release(const void *aGuBlock)
{
    struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; 
    if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        AGuBlockToRelease is a global GuBlock
        // Do nothing, return directly.
        return; 
    }
    else if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_ON_STACK)
    {
        SRC is a GuBlock on the heap
        // Do nothing, return directly.
        return; 
    }
    else
    {
        AGuBlockToRelease is aGuBlock on the heap
        // Reduce the reference count.
        -- (aGuBlockToRelease->referenceCount); 
        if (aGuBlockToRelease->referenceCount == 0)
        {
            // If the reference count is returned to zero after this reduction, the memory space is freed.
            
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
            // First release the contained objects to prevent this from happening:
            That is, the GuBlock is currently the sole holder of the Objective-C object it captures.
            // In this case, memory leaks occur if the reference count of the captured Objective-C objects is not reduced.
        	GuBlock_release_helper_for_objc_object(aGuBlockToRelease);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
            free(aGuBlockToRelease);	
        }
        return; }}/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void GuBlock_release_helper_for_objc_object(void *aGuBlock);
{
    ARC automatically reduces the reference count of captured Objective-C objects after the space is freed.
    /* struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; aGuBlockToRelease->capturedObj = NULL; * /
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Copy the code

We’ve wrapped the new part of the code in conspicuous comments, and obviously, with ARC’s help, just point to the area where the NSObject object is, and the reference count is automatically reduced by 1. So, we’re done releasing the NSObject object on the heap.

2.3.2 GuBlock’s capture of C++ stack objects (if you do not know or need this knowledge, you can skip it, which does not affect subsequent reading)

Similarly, C++ objects can be captured. And as discussed in Section 2.3.1, we are only concerned with the automatic variables part (because their handling requires additional modification), while for global and static variables, their handling does not change, and the previous reasoning perfectly applies, so we use them as usual.

Unlike Objective-C objects that only live on the heap, C++ objects may live on the stack. We won’t talk about what happens on the heap, because the life cycle of an object on the heap (without the use of smart Pointers) is completely under the control of the program writer, so there’s no need to worry about capturing it affecting its life cycle — even if GuBlock escapes from its life cycle, Its use of C++ objects on the heap is not guaranteed to be always effective, but depends on proper and reasonable control of the C++ object life cycle by the program writer.

C++ objects on the stack are different. During capture, copying occurs, generating a C++ constant object in GuBlock with the exact same value as the captured C++ stack object, and therefore using the copy constructor, the constant copy constructor.

Also, if a GuBlock captures automatic variables and the GuBlock needs to escape from the stack frame, it needs to make a copy of each captured automatic variable. A value copy (shallow copy) of a variable of a native type is sufficient, but for C++ objects on the stack, we need to copy the variable of that type in the space allocated in the heap. You also need to generate a copy/release helper for it, Their functions are exactly the same as GuBlock_copy_helper_for_objc_object and GuBlock_release_helper_for_objc_object in 2.3.1.

2.3.3 GuBlock Capture of variables of GuBlock type

As we saw in the previous discussion, the only reason we need special treatment for variables of non-native types is because shallow copies are no longer sufficient for the GuBlock lifecycle. So we added reference-counting for Objective-C objects and (constant) copy constructors and destructors for C++ objects.

So similarly, when capturing an automatic variable of type GuBlock, we also add deep copy and destruction support for it:

Modify GuBlock copy:

Because there is a ready-made method for deep copy of GuBlock, Block_copy() itself. Therefore, you just need to call the Block_copy() method on the GuBlock in the GuBlock once more during the replication process.

Therefore, Block_copy() is modified as follows, adding nested Block_copy() calls to GuBlock objects in the copy of stack GuBlock:

/** * description: Copy the passed GuBlock once: * Copy the global GuBlock, do nothing; * If a stack GuBlock is copied, it is copied to the heap; * * @param aGuBlock The GuBlock to be copied. * * @return The copied result. */
void *Block_copy(const void *aGuBlock)
{
    struct rewritten_myGuBlock *src = (struct rewritten_myGuBlock *)aGuBlock; 
    if (src->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        SRC is a global GuBlock
        // Do nothing, return directly.
        return src; 
    }
    else if (src->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP)
    {
        SRC is a GuBlock on the heap
        // Increment the reference count and return.
        ++ (src->referenceCount); 
        return src; 
    }
    else
    {
        // In this case, rewrittenMyGuBlock is a stack GuBlock that emulates an automatic variable
        // Create the same size space and copy it by bit.
        size_t sizeOfSrc = sizeof(struct rewritten_myGuBlock); 
        struct rewritten_myGuBlock *dest = (struct rewritten_myGuBlock *)malloc(sizeOfSrc); 
        if(! dest)return NULL;
        memmove(dest, src, sizeOfSrc);
        // Also, the GUBLOCK_TYPE and reference count of the copied heap GuBlock should be set correctly.
        dest->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP; 
        dest->referenceCount = 1; 
        GuBlock_copy_helper_for_objc_object(dest, src);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
		GuBlock_copy_helper_for_nested_GuBlock(dest, src)
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
        returndest; }}void GuBlock_copy_helper_for_objc_object(void *dest, const void *src);
{
    struct rewritten_myGuBlock *destGuBlock = (struct rewritten_myGuBlock *)dest; 
    struct rewritten_myGuBlock *srcGuBlock = (struct rewritten_myGuBlock *)src; 
    destGuBlock->capturedObj = srcGuBlock->capturedObj; 
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void GuBlock_copy_helper_for_nested_GuBlock(void *dest, const void *src);
{
    struct rewritten_myGuBlock *destGuBlock = (struct rewritten_myGuBlock *)dest; 
    struct rewritten_myGuBlock *srcGuBlock = (struct rewritten_myGuBlock *)src; 
    destGuBlock->capturedGuBlock = Block_copy(srcGuBlock->capturedGuBlock); 
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Copy the code

We wrap the newly added code in conspicuous comments, and we call Block_copy() once on a GuBlock captured by a GuBlock to make a deep copy.

Modify the release of GuBlock:

In the case of C++ objects, we use copy to copy the C++ objects captured on the stack to the heap, while we need to release the C++ objects on the heap. Similarly, we have the release action for the captured GuBlock object on the stack, executed when the captured GuBlock will be destroyed.

Keep in mind the correspondence between copy and release.

/** * description: Release the passed GuBlock: release the global GuBlock, do nothing; * If you release the stack GuBlock, do nothing; * * @param aGuBlock The GuBlock to be released. */
void Block_release(const void *aGuBlock)
{
    struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; 
    if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        AGuBlockToRelease is a global GuBlock
        // Do nothing, return directly.
        return; 
    }
    else if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_ON_STACK)
    {
        SRC is a GuBlock on the heap
        // Do nothing, return directly.
        return; 
    }
    else
    {
        AGuBlockToRelease is aGuBlock on the heap
        // Reduce the reference count.
        -- (aGuBlockToRelease->referenceCount); 
        if (aGuBlockToRelease->referenceCount == 0)
        {
            // If the reference count is returned to zero after this reduction, the memory space is freed.
            
            // First release the contained objects to prevent this from happening:
            That is, the GuBlock is currently the sole holder of the Objective-C object it captures.
            // In this case, memory leaks occur if the reference count of the captured Objective-C objects is not reduced.
        	GuBlock_release_helper_for_objc_object(aGuBlockToRelease);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
            GuBlock_release_helper_for_nested_GuBlock(aGuBlockToRelease);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
            free(aGuBlockToRelease);	
        }
        return; }}void GuBlock_release_helper_for_objc_object(void *aGuBlock);
{
    ARC automatically reduces the reference count of captured Objective-C objects after the space is freed.
    /* struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; aGuBlockToRelease->capturedObj = NULL; * /
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void GuBlock_release_helper_for_nested_GuBlock(void *aGuBlock);
{
    struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; 
    Block_release(aGuBlockToRelease->capturedGuBlock); 
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Copy the code

We wrapped the newly added code in conspicuous comments and called Block_release() once on a GuBlock captured by a GuBlock to complete the release operation.

2.3.4 summary

In this section we extend the types that GuBlock can capture from primitive types (plain scalars, structs, unions, function Pointers, etc.) to Objective-C objects, C++ stack objects. Further work is done on variables that are not simply shallow copies when copied, but involve deep copies.

After covering all types of variables, we further extend the types of variables that GuBlock can capture — GuBlock itself. A GuBlock can capture another GuBlock variable, and this capture can be supported simply by nesting Block_copy() and Block_release().

As a result of this improvement, GuBlock is able to capture all the types that are possible in Objective-C.

2.4 Changes to GuBlock external variables

GuBlock is designed so that it can capture all the types of automatic variables it needs, but it cannot yet write the values of all the types of variables it uses.

Similar to the handling of static local variables, we move automatic variables that need to be written to the heap and make the same variable accessible to multiple Gublocks (both on the heap and on the stack) through indirect access.

Add an intermediate layer to the stack GuBlock and the captured automatic variables, requiring the stack GuBlock to access the actual automatic variables through this intermediate layer. When a stack GuBlock is copied to the heap, the copy operation should be responsible for changing the direction of the middle layer so that the middle layer on the stack shared by all the stack Gublocks has the same direction as the newly copied middle layer on the heap. This ensures that gublocks that capture the same read-write variable access the value of the same automatic variable.

At this point, we have a further optimized GuBlock model.

In this model, there are primitives — function Pointers to the code block to be executed, GuBlock types, reference counts (meaningful only for heap gublocks), values of captured read-only automatic variables, addresses of Pointers to automatic variables to be read and written (forwarding), Contains the address of the captured static local variable, and the global variable is normally accessible.

At this point, Block_copy() and Block_dispose() for GuBlock also change a bit: After the copy is completed, the forwarding pointing of GuBlock on the stack should be changed so that the forwarding on the stack also points to the variable value on the heap (as shown in Figure 2.4.1.3-1).

We need to determine in GuBlock whether the variable it captures is a writable variable. However, our previous design only made this variable accessible through a pointer, and this automatic variable in the “overwritten GuBlock” structure is not distinguishable from other captured read-only automatic variables. So we need to go a step further by adding a type description to a structure that represents readable and writable automatic variables.

Package the forwarding pointer into a structure and add an enumeration to indicate the type of the automatic variable.

For such read-write variables, we decorate them with __block.

Thus, when the __block-modified BYREF(meaning “by reference”) structure says its type is objective-C object or GuBlock, it needs to do an extra step (deep copy).

Add its own deep-copy step for objective-C object type and GuBlock type read-write variables.

/** * description: Copy the passed GuBlock once: * Copy the global GuBlock, do nothing; * If a stack GuBlock is copied, it is copied to the heap; * * @param aGuBlock The GuBlock to be copied. * * @return The copied result. */
void *Block_copy(const void *aGuBlock)
{
    struct rewritten_myGuBlock *src = (struct rewritten_myGuBlock *)aGuBlock; 
    if (src->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        SRC is a global GuBlock
        // Do nothing, return directly.
        return src; 
    }
    else if (src->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP)
    {
        SRC is a GuBlock on the heap
        // Increment the reference count and return.
        ++ (src->referenceCount); 
        return src; 
    }
    else
    {
        // In this case, rewrittenMyGuBlock is a stack GuBlock that emulates an automatic variable
        // Create the same size space and copy it by bit.
        size_t sizeOfSrc = sizeof(struct rewritten_myGuBlock); 
        struct rewritten_myGuBlock *dest = (struct rewritten_myGuBlock *)malloc(sizeOfSrc); 
        if(! dest)return NULL;
        memmove(dest, src, sizeOfSrc);
        // Also, the GUBLOCK_TYPE and reference count of the copied heap GuBlock should be set correctly.
        dest->GUBLOCK_TYPE = GUBLOCK_IS_ON_HEAP; 
        dest->referenceCount = 1; 
        GuBlock_copy_helper_for_objc_object(dest, src);
		GuBlock_copy_helper_for_nested_GuBlock(dest, src);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
        GuBlock_copy_helper_for_auto_variable_byref(dest, src);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
        returndest; }}void GuBlock_copy_helper_for_objc_object(void *dest, const void *src);
{
    struct rewritten_myGuBlock *destGuBlock = (struct rewritten_myGuBlock *)dest; 
    struct rewritten_myGuBlock *srcGuBlock = (struct rewritten_myGuBlock *)src; 
    destGuBlock->capturedObj = srcGuBlock->capturedObj; 
}
void GuBlock_copy_helper_for_nested_GuBlock(void *dest, const void *src);
{
    struct rewritten_myGuBlock *destGuBlock = (struct rewritten_myGuBlock *)dest; 
    struct rewritten_myGuBlock *srcGuBlock = (struct rewritten_myGuBlock *)src; 
    destGuBlock->capturedGuBlock = Block_copy(srcGuBlock->capturedGuBlock); 
}

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void GuBlock_copy_helper_for_auto_variable_byref(void *dest, const void *src)
{
    struct rewritten_myGuBlock *destGuBlock = (struct rewritten_myGuBlock *)dest; 		/ / the heap
    struct rewritten_myGuBlock *srcGuBlock = (struct rewritten_myGuBlock *)src; 		/ / on the stack
    
    switch (srcGuBlock->capturedAutoVariableByref->BYREF_TYPE) 
    {
        case BYREF_IS_OBJC_OBJECT:
        case BYREF_IS_GUBLOCK:			// Both cases are the same here, just different from the native type
            							// You need to apply for space manually
            
            // Here, GuBlock actually has no size attribute and should be computed manually, but for code brief purposes, we assume that we can somehow get the size of the GuBlock
            destGuBlock->capturedAutoVarByref = (struct BYREF *)malloc(srcGuBlock->size);

            // Byref ->forwading on the heap should point to itself
            destGuBlock->capturedAutoVarByref->forwarding = destGuBlock->capturedAutoVarByref;
            // Byref ->forwading on the stack should point to byref on the heap
            destGuBlock->capturedAutoVarByref->forwarding = destGuBlock->capturedAutoVarByref;
            
            break; 
        default:
            break; }}/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Copy the code

Here ARC is used to retain the object.

Now that we’re done adding copy, we need to add release.

/** * description: Release the passed GuBlock: release the global GuBlock, do nothing; * If you release the stack GuBlock, do nothing; * * @param aGuBlock The GuBlock to be released. */
void Block_release(const void *aGuBlock)
{
    struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; 
    if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_GLOBAL)
    {
        AGuBlockToRelease is a global GuBlock
        // Do nothing, return directly.
        return; 
    }
    else if (aGuBlockToRelease->GUBLOCK_TYPE = GUBLOCK_IS_ON_STACK)
    {
        SRC is a GuBlock on the heap
        // Do nothing, return directly.
        return; 
    }
    else
    {
        AGuBlockToRelease is aGuBlock on the heap
        // Reduce the reference count.
        -- (aGuBlockToRelease->referenceCount); 
        if (aGuBlockToRelease->referenceCount == 0)
        {
            // If the reference count is returned to zero after this reduction, the memory space is freed.
            
            // First release the contained objects to prevent this from happening:
            That is, the GuBlock is currently the sole holder of the Objective-C object it captures.
            // In this case, memory leaks occur if the reference count of the captured Objective-C objects is not reduced.
        	GuBlock_release_helper_for_objc_object(aGuBlockToRelease);
            GuBlock_release_helper_for_nested_GuBlock(aGuBlockToRelease);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
            GuBlock_release_helper_for_auto_variable_byref(aGuBlockToRelease);
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
            free(aGuBlockToRelease);	
        }
        return; }}void GuBlock_release_helper_for_objc_object(void *aGuBlock);
{
    ARC automatically reduces the reference count of captured Objective-C objects after the space is freed.
    /* struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; aGuBlockToRelease->capturedObj = NULL; * /
}

void GuBlock_release_helper_for_nested_GuBlock(void *aGuBlock);
{
    struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; 
    Block_release(aGuBlockToRelease->capturedGuBlock); 
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * code of new part to start * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void GuBlock_copy_helper_for_auto_variable_byref(void *aGuBlock)
{
    struct rewritten_myGuBlock *aGuBlockToRelease = (struct rewritten_myGuBlock *)aGuBlock; 
    
    // Make sure aGuBlockToRelease is a BYREF on the heap -- so that its release makes sense
    aGuBlockToRelease = aGuBlockToRelease->forwarding; 		If aGuBlockToRelease points to BYREF on the stack, make it point to the heap
    														// If aGuBlockToRelease points to BYREF on the stack, the reference remains the same
    switch (aGuBlockToRelease->BYREF_TYPE)
    {
        case BYREF_IS_OBJC_OBJECT:
        case BYREF_IS_GUBLOCK:			// Both cases are the same here, just different from the native type
            							// Manually release the allocated memory
            free(aGuBlockToRelease); 	
            break; 
        default:
            break; }}/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * end of the code of new part * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Copy the code

2.5 summarize

At this point, we have a GuBlock that behaves exactly like the Blocks provided by Apple and works in much the same way as blocks.

To summarize the entire design flow from function Pointers to gublocks.

We start with a function pointer and expect the compiler to convert objective-C code to C++ or C code before compiling, so we define the following rules for it:

  • Delimit an escaped character (^The GuBlock identifier is rewritten as a structure containing function Pointers, type hints, and captured variables for the corresponding code block.
  • The behavior of “capture” is optimized based on the variable type.
  • Implement the copy/release behavior, and judge the depth of the copy according to the variable type.

The difference between GuBlock and Block in implementation at this time mainly lies in that Block puts each flag bit (such as whether it is a global Block, whether it needs free, etc.) and reference count into flags to manage, and uses bit operation to operate them. At the same time, a desc(“description”) is used to store size and so on. These factors have no effect on the specific principles.

At this point, we have a syntax component called GuBlock that behaves like a Block and works basically the same way as a Block.

3 conclusion

The motivation for writing this article is as follows: While learning the source code of Block, Gu encountered a lot of confusion, often not from “what does this code mean, what does it do “, but more from” why does it do it “, “what good does it do it “. Therefore, Gu consulted some data, combined with the code, want to restore the designer’s intention as far as possible. Fortunately, based on these data and code, we did obtain a well-structured and self-consistent design idea, on the basis of satisfying the use of a relatively comprehensive solution to some of the language level caused by the use of the possible problems.

When xiao Gu wrote this article, he was also constantly exploring his own blind spots in thinking and marveling at the completeness and comprehensiveness of the designer’s ideas. Hopefully, this article will give readers a new perspective on blocks.

Appendix References

[1]: “The Function Pointer Tutorials”.

[2]: Apple’s Extension to C. http://www.open-std.org/JTC1/SC22/WG14/www/documents – N1370.

[3] : Blocks Proposal. www.open-std.org/JTC1/SC22/W… – N1451.

Hi, I am a small customer of Kuaishou e-commerce ~ Kuaishou e-commerce wireless technology team is recruiting talents 🎉🎉🎉! We are the core business line of the company, which is full of talents, opportunities and challenges. With the rapid development of the business, the team is also expanding rapidly. We welcome you to join us and create world-class e-commerce products together. Hot positions: Android/iOS Senior Developer, Android/iOS expert, Java Architect, Product Manager (e-commerce background), test development… A lot of HC waiting for you ~ internal recommendation please send resume to >>> our email: [email protected] <<<, note my roster success rate is higher oh ~ 😘