Deep study of Block capturing external variables and u block implementation

Keywords: iOS C OS X Mac

Preface

Blocks are an extension of the C language, and Apple introduced this new feature, Blocks, in OS X Snow Leopard and iOS 4.Since then, Block has appeared in various API s on iOS and Mac systems and has been widely used.A sentence for Blocks, an anonymous function with an automatic variable (a local variable).

Block is implemented in OC as follows:

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

isa is easy to see from the structure diagram, so OC handles blocks as objects.In iOS, isa is commonly referred to as _NSConcreteStackBlock, _NSConcreteMallocBlock, _NSConcreteGlobalBlock (plus three other _NSConcreteFinalizingBlock s, _NSConcreteAutoBlock, _NSConcreteWeakBlockVariable that are used only in GC environments, but which are not discussed in this article and are interesting to see the official documentation)

This is a brief implementation of Block. Next, let's take a closer look at the nature of Block capturing external variables and how u block works.

Research tool: clang
To study how the compiler works, we need to use the clang command.The clang command can rewrite Objetive-C's source code to C/C++, which allows you to explore the source implementation of each feature in a block.The command is

clang -rewrite-objc block.c

Catalog

  • 1.Block captures the essence of an external variable
  • 2.Block's copy and release
  • 3. Implementation principle of u block in Block

Block captures the essence of external variables


Pick up our Block to capture external variables.

When it comes to external variables, let's first talk about what kinds of variables are in C.There are generally five types:

  • Automatic Variable
  • Function parameters
  • Static variable
  • static global
  • global variable

To study Block's capture of external variables, we need to remove the function parameter. The following is an analysis based on the capture of these four types of variables.

Let's start with these four types

  • Automatic Variables
  • Static variable
  • static global
  • global variable

Write out the Block test code.


Quickly, there was an error indicating that the automatic variable did not add u block. Because u block is a bit complex, let's first experiment with static variables, static global variables and global variables.The test code is as follows:

#import <Foundation/Foundation.h>

int global_i = 1;

static int static_global_j = 2;

int main(int argc, const char * argv[]) {

    static int static_k = 3;
    int val = 4;

    void (^myBlock)(void) = ^{
        global_i ++;
        static_global_j ++;
        static_k ++;
        NSLog(@"Block in global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
    };

    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog(@"Block abroad global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);

    myBlock();

    return 0;
}

Run Results

Block abroad  global_i = 2,static_global_j = 3,static_k = 4,val = 5
Block in  global_i = 3,static_global_j = 4,static_k = 5,val = 4

There are two things to figure out here
1. Why is it not allowed to change variables without u bolck in the Block?
2. Why is the value of the automatic variable not increasing, while the value of several other variables is increasing?In what state is an automatic variable captured by a block?

To understand these two points, we use clang to convert the source code for analysis.

(main.m lines of code 37, file size 832 bype, clang to main.cpp after the conversion, the number of lines of code soared to 10,4810 lines, file size also became 3.1 MB)

The source code is as follows

int global_i = 1;

static int static_global_j = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_k;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
  int val = __cself->val; // bound by copy

        global_i ++;
        static_global_j ++;
        (*static_k) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};


int main(int argc, const char * argv[]) {

    static int static_k = 3;
    int val = 4;

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));

    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

It's understandable that global variables global_i and static global variables static_global_j increase in value first, and that they are captured by blocks, because they are global and have a wide scope, so when blocks capture them, they do ++ operations inside the blocks, and their values are still available after the blocks endSave it.

Next, take a closer look at the issue of automatic and static variables.
In u main_block_impl_0, you can see the static_k and the automatic variable val, which are captured outside by the Block and become member variables of the u main_block_impl_0 structure.

Next, look at the constructor.

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val)

In this constructor, automatic and static variables are captured as member variables and appended to the constructor.

The u main_block_impl_0 structure in the myBlock closure inside main is initialized as follows

void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));


impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_impl_0; 
Desc = &__main_block_desc_0_DATA;
*_static_k = 4;
val = 4;

This is how the u main_block_impl_0 structure captures automatic variables.That is, when the Block syntax is executed, the value of the automatic variable used by the Block syntax expression is stored in the structure instance of the Block, that is, in the Block itself.

One thing to note here is that if there are many automatic variables, static variables, and so on outside the block, they will not be used inside the block.These variables are not captured by Block, that is, their values are not passed in the constructor.

Block capturing an external variable captures only the values that will be used inside the Block closure. Other values that are not used are not captured.

Looking at the source code again, we notice the implementation of the function u main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
  int val = __cself->val; // bound by copy

        global_i ++;
        static_global_j ++;
        (*static_k) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
    }

We can see that the system automatically annotates us, bound by copy, and the automatic variable val, although captured, is accessed with u cself->val.Block captures only the value of val, not its memory address.So even if we override the value of the automatic variable Val in the u main_block_func_0 function, we still can't change the value of the automatic variable Val outside the block.

OC may be based on this to prevent possible errors at the compilation level, since automatic variables cannot change the value of external variables in a Block, compilation errors are reported during compilation.The error is the first screenshot.

Variable is not assignable(missing __block type specifier)

To summarize:
So far, the second question above has solved the question.Auto variables are passed to Block's constructor by value transfer.Block captures only the variables that are used in a Block.Since only the value of the automatic variable is captured, not the memory address, the value of the automatic variable cannot be changed inside the block.The external variables that Block captures can change the values are static variables, static global variables, global variables.This is also demonstrated by the above examples.

The remaining problem is one we haven't solved yet.

Returning to the example above, there are only static variables, static global variables among the four variables, and global variables which can be changed in a Block.Looking closely at the source code, we can see why these three variables can change values.

  1. Static global variables, global variables can be changed directly within a Block because of scope.They are also stored globally.


  2. Static variables passed to a Block are memory address values, so they can be changed directly within the Block.

according toOfficial Documents We can see that Apple requires us to add the u Block keyword (u block storage-class-specifier storage domain class descriptor) before the automatic variable to change the value of the external automatic variable inside the Block.

To summarize, there are two ways to change the value of a variable in a Block, one is to pass a memory address pointer to the Block, and the other is to change the storage mode (u block).

Let's experiment with the first method, passing in the memory address to the Block and changing the value of the variable.

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {

  NSMutableString * str = [[NSMutableString alloc]initWithString:@"Hello,"];

        void (^myBlock)(void) = ^{
            [str appendString:@"World!"];
            NSLog(@"Block in str = %@",str);
        };

    NSLog(@"Block abroad str = %@",str);

    myBlock();

    return 0;
}

Console Output:

Block abroad  str = Hello,
Block in  str = Hello,World!

Look at the result and change the value of the variable successfully. Convert the source code.

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableString *str;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSMutableString *str = __cself->str; // bound by copy

            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_1);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_2,str);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    NSMutableString * str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_0);

        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_3,str);

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

You can see in u main_block_func_0 that the pointer is passed.So we successfully changed the value of the variable.

The next section on copy and dispose in the source code will cover that.

The second way to change the value of an external variable is to add u Block, which is discussed in Chapter 3. Next, let's talk about the copy ing of a Block, because it concerns the u Block storage domain.

2. Block's copy and dispose


In OC, there are three general blocks, _NSConcreteStackBlock, _NSConcreteMallocBlock, _NSConcreteGlobalBlock.

Let's start by explaining the differences between the three.

1. From the perspective of capturing external variables
  • _NSConcreteStackBlock:
    Blocks that use only external local variables, member property variables, and do not have a strong pointer reference are StackBlock s.
    The life cycle of a StackBlock is controlled by the system and is destroyed once it returns.

  • _NSConcreteMallocBlock:
    Blocks with strong pointer references or copy-modified member attribute references are copied into the heap to become MallocBlock s, destroyed without strong pointer references, and the life cycle is programmer-controlled

  • _NSConcreteGlobalBlock:
    Blocks that use no external variables or only global and static variables are _NSConcreteGlobalBlock s, and the life cycle goes from creation to the end of the application.

It's understandable that the unused external variable must be _NSConcreteGlobalBlock.Blocks that use only global and static variables are also _NSConcreteGlobalBlocks.Examples are as follows:

#import <Foundation/Foundation.h>

int global_i = 1;
static int static_global_j = 2;

int main(int argc, const char * argv[]) {

    static int static_k = 3;

    void (^myBlock)(void) = ^{
            NSLog(@"Block Medium variable = %d %d %d",static_global_j ,static_k, global_i);
        };

    NSLog(@"%@",myBlock);

    myBlock();

    return 0;
}

Output:

<__NSGlobalBlock__: 0x100001050>
Variable in Block = 2 3 1

Obviously, block s that only use global and static variables can also be _NSConcreteGlobalBlock s.

Therefore, in the ARC environment, all three types can capture external variables.

2. From the perspective of the holder:
  • _NSConcreteStackBlock does not hold objects.
//The following is performed under MRC
    NSObject * obj = [[NSObject alloc]init];
    NSLog(@"1.Block abroad obj = %lu",(unsigned long)obj.retainCount);

    void (^myBlock)(void) = ^{
        NSLog(@"Block in obj = %lu",(unsigned long)obj.retainCount);
    };

    NSLog(@"2.Block abroad obj = %lu",(unsigned long)obj.retainCount);

    myBlock();

Output:

1.Block outer obj = 1
 2.Block outer obj = 1
 obj = 1 in Block
  • _NSConcreteMallocBlock is the holding object.
//The following is performed under MRC
    NSObject * obj = [[NSObject alloc]init];
    NSLog(@"1.Block abroad obj = %lu",(unsigned long)obj.retainCount);

    void (^myBlock)(void) = [^{
        NSLog(@"Block in obj = %lu",(unsigned long)obj.retainCount);
    }copy];

    NSLog(@"2.Block abroad obj = %lu",(unsigned long)obj.retainCount);

    myBlock();

    [myBlock release];

    NSLog(@"3.Block abroad obj = %lu",(unsigned long)obj.retainCount);

Output:

1.Block outer obj = 1
 2.Block outer obj = 2
 obj = 2 in Block
 3.Block outer obj = 1
  • _NSConcreteGlobalBlock also does not hold objects
//The following is performed under MRC
    void (^myBlock)(void) = ^{

        NSObject * obj = [[NSObject alloc]init];
        NSLog(@"Block in obj = %lu",(unsigned long)obj.retainCount);
    };

    myBlock();

Output:

obj = 1 in Block

Because the variable domain to which _NSConcreteStackBlock belongs is destroyed once it has ended.In the ARC environment, the compiler will automatically judge to copy the block from the stack to the stack.For example, when a Block is returned as a function, it must be copied to the heap.

1. Call copy manually
2.Block is the return value of a function
3.Block is strongly referenced and assigned to u strong or id type
4. Call methods that contain usingBlcok in system API input

In all four cases, the system will call the copy method by default to assign the Block to copy

But when Block is a function parameter, we need to manually copy it to the heap.We don't need to deal with API s of the system here, such as methods with usingBlock in GCD, etc. Other methods we customize need to manually copy a copy to the heap when passing a Block as a parameter.

The copy function copies the Block from the stack to the heap, and the dispose function destroys the function on the heap when it is abandoned.

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

The above are two commonly used macro definitions and four commonly used methods in the source code, which we will see in a moment.

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;
}

The above section is an implementation of Block_copy, which implements the process of copying from _NSConcreteStackBlock to _NSConcreteMallocBlock.There are nine steps.

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);
    }
}

The above section is an implementation of Block_release, which implements how to release a block.There are six steps.

A detailed analysis of these two methods can be found in this article. Article

Returning to the last example in the previous chapter, the string example, after converting the source code, we will see that there is an additional copy and dispose method.

Because in the C language structure, the compiler does not perform well in initializing and destroying operations.This is inconvenient for memory management.So add member variables void (*copy) (struct u main_block_impl_0*, struct u main_block_impl_0*) and void (*dispose) (struct u main_block_impl_0*) in the middle of the u main_desc_0 structure, and use OC's untime for memory management.

Two methods were added accordingly.

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

Here, the _Block_object_assign and _Block_object_dispose correspond to the retain and release methods.

BLOCK_FIELD_IS_OBJECT is a special token when a Block intercepts an object, and if u block is intercepted, it is BLOCK_FIELD_IS_BYREF.

3. Implementation principle of u block in Block

Let's move on to the u block implementation.

1. Common non-object variables

Let's first look at the general variable.

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {

    __block int i = 0;

    void (^myBlock)(void) = ^{
        i ++;
        NSLog(@"%d",i);
    };

    myBlock();

    return 0;
}

Convert the above code into source code using clang.

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

        (i->__forwarding->i) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

From the source code, we can see that variables with u block are also converted to a structure u Block_byref_i_0, which has five member variables.The first is an isa pointer, the second is a u forwarding pointer to its own type, the third is a flag tag, the fourth is its size, and the fifth is a variable value with the same name as the variable name.

__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

This is how it is initialized in the source code.The u forwarding pointer initially passes its own address.But does this u forwarding pointer really point to itself forever?Let's do an experiment.

//The following code runs in MRC
    __block int i = 0;
    NSLog(@"%p",&i);

    void (^myBlock)(void) = [^{
        i ++;
        NSLog(@"This is Block inside%p",&i);
    }copy];

We copied the Block to the heap, and at this point the addresses of the two i variables will be different.

0x7fff5fbff818
<__NSMallocBlock__: 0x100203cc0>
This is 0x1002038a8 inside the Block

The address difference makes it obvious that the u forwarding pointer does not point to itself.Where does the u forwarding pointer now point to?

The address of u block inside a block differs from that of a block by 1052.We can safely guess that u block is also on the heap now.

The reason for this difference is that the Block is copied to the heap here.

Blocks on the heap hold objects that are analyzed in detail in Chapter 2.We copy the block to the heap, and a copy of the block will be made on the heap, and the block will continue to hold the u block.When a block is released, u block is not referenced by any object and is also destroyed by release.

The u forwarding pointer here refers to the heap block, replacing the original u forwarding pointer with the replicated u block itself on the _NSConcreteMallocBlock.Then the u forwarding of the variable on the heap points to itself again.This allows the variable values to be accessed (i->u forwarding->i) regardless of how u block is copied to the stack or on the stack.



So it is written inside the u main_block_func_0 function (i->u forwarding->i).

There is one more thing to note here.Or start with an example:

//The following code runs in MRC
    __block int i = 0;
    NSLog(@"%p",&i);

    void (^myBlock)(void) = ^{
        i ++;
        NSLog(@"Block Inside%p",&i);
    };


    NSLog(@"%@",myBlock);

    myBlock();

The result is completely different from the previous copy example.

 0x7fff5fbff818
<__NSStackBlock__: 0x7fff5fbff7c0>**
 0x7fff5fbff818

Blocks do not copy u block variables to the stack after they have been captured, so the addresses are always on the stack.This is different from the ARC environment.

In an ARC environment, u Block changes to copy on the heap whether or not there is a copy, and the Block is also a u NSMallocBlock.

Thank you @Cool Palace for pointing out errors and @bestswifter for pointing out.That's a bit inappropriate. See the update at the end of the article for details.

In ARC environments, once a block assignment triggers copy, u block copies to the heap, and the block is also u NSMallocBlock.The u NSStackBlock is also present in the ARC environment, in which case it is on the stack.

In MRC environment, only copy, u block will be copied to the heap, otherwise, u block will always be on the stack, and the block will only be u NSStackBlock, at which point the u forwarding pointer will only point to itself.


At this point, the first question raised at the beginning of the article has also been answered.The implementation principle of u block is also clear.

2. Object variables

Let's start with an example:

//The following code is executed under ARC
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {

    __block id block_obj = [[NSObject alloc]init];
    id obj = [[NSObject alloc]init];

    NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);

    void (^myBlock)(void) = ^{
        NSLog(@"***Block in****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
    };

    myBlock();

    return 0;
}

output

block_obj = [<NSObject: 0x100b027d0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b03b50> , 0x7fff5fbff7b8]
Block****in********block_obj = [<NSObject: 0x100b027d0> , 0x100f000a8] , obj = [<NSObject: 0x100b03b50> , 0x100f00070]

Let's look at converting the above code into source code:

struct __Block_byref_block_obj_0 {
  void *__isa;
__Block_byref_block_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id block_obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id obj;
  __Block_byref_block_obj_0 *block_obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
  id obj = __cself->obj; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};


int main(int argc, const char * argv[]) {

    __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

    id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

The first thing to note is that objects in OC are declared by default with the u strong ownership modifier, so main begins with what we declare

__block id block_obj = [[NSObject alloc]init];
id obj = [[NSObject alloc]init];

Equivalent to

__block id __strong block_obj = [[NSObject alloc]init];
id __strong obj = [[NSObject alloc]init];

In the converted source code, we can also see that Block captures u block and strongly references it, because in the u Block_byref_block_obj_0 structure, there is a variable named id block_obj, which by default is also with the u strong ownership modifier.

According to the printed results, in ARC environment, blocks capture external object variables, which are copied to different addresses.Only variables with the u block modifier are captured and held inside the block.

Let's look again at the MRC environment, or run an example of the above code in the MRC.

Output:

block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff7b8]
Block****in********block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff790]

At this point, the block is on the stack, u NSStackBlock_u, which can be printed with a retainCount value of 1.When you copy this block, it becomes u NSMallocBlock_u, and the retainCount value of the object becomes 2.

Summary:

In an MRC environment, u block does not copy at all the objects pointed to by the pointer, but simply copies the pointer.
In the ARC environment, for external objects declared as u blocks, retain occurs inside the block so that they can safely reference external objects within the block environment, so circular reference problems arise!

Last

Block captures external variables for many purposes and a wide range of purposes. Block circular references cannot be resolved until the concepts of capturing and holding variables are understood.

Return to the beginning of the article, five variables, automatic variables, function parameters, static variables, static global variables, global variables, if strictly speaking, capture must have member variables in the Block structure u main_block_impl_0, Block can only capture variables with automatic variables and static variables.Objects captured in a Block are held by the Block.

For non-object variables,

The value of the auto variable is copied into the block, and the auto variable without u block can only be accessed inside, and it cannot change the value.


Automatic and static variables with u Block are direct address access.So you can directly change the value of a variable within a Block.


The remaining static global variables, global variables, and function parameters can also change the value of a variable directly in a Block, but they have not become a member variable of the Block structure u main_block_impl_0 because they have a large scope, so they can change their value directly.

It is worth noting that static global variables, global variables, and function parameters are not held by Block, that is, they do not increase the retainCount value.

For objects,

In an MRC environment, u block does not copy at all the objects pointed to by the pointer, but simply copies the pointer.
In the ARC environment, for external objects declared as u blocks, retain occurs inside the block so that the external objects can be safely referenced within the block environment.

Please give us more advice.

To update

Blocks also exist in the ARC environment when u NSStackBlocks exist. The most common blocks we see are _NSConcreteMallocBlock s, because we assign Blocks, so in ARC, when a block type is passed by = it causes a call to the objc_retainBlock->_Block_copy->_Block_copy_internal method chain.It also causes blocks of type u NSStackBlock_ to be converted to type u NSMallocBlock_.

Examples are as follows:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {

    __block int temp = 10;

    NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});

    return 0;
}

output

<__NSStackBlock__: 0x7fff5fbff768>

This is the type of u NSStack Block in the ARC environment.

Posted by 3yoon af on Fri, 28 Jun 2019 17:53:25 -0700