This article is the author of Lefe, reprint please indicate the source, if you find problems in the reading time welcome to discuss together. This article will be updated.

Description:

When using Block, we usually have the following questions. We read this article with these questions in mind. There will inevitably be omissions or errors in this article. Lefe encounters the following problems when using blocks:

  • Should __weak be used whenever self is used to avoid circular references?
  • To avoid loop application, why use strong when using weak?
  • Why is it possible to modify a variable using __block and then modify its value within the Block?
  • When will the Block be released? How does it manage memory?
  • Block why copy?
  • Why do some blocks do not generate a circular reference even if they capture self?
  • How are automatic variables (local variables) captured by the Block, and how are objects captured by the Block?

With these questions in mind, let’s uncover the true face of Block. This article is long and can be read in sections. It is recommended that readers be patient and read it. Before we read, let’s know about Clang

Clang

Clang is mainly used in this article. What is Clang? It is the default compiler for Xcode. You can learn more about Clang in this article. Here we mainly use Clang to convert the implementation of blocks to C++, which is pretty much the same as C, except for the constructor.

Open shell, enter Lefe’s test project, and type:

Clang-rewrite-objc HelloLefe. M, this will produce a corresponding HelloLefe. CPP file in the current directory. Take a screenshot. Don’t just look at the girls.





Screenshot 2017-06-25 am 9.36.43.png

Memory allocation

Before reading the rest of the article, we need to know something about memory allocation

  • Stack: During the execution of a function, storage units of local variables in the function can be created on the stack, and these storage units are automatically released at the end of the function execution. Stack memory allocation operations are built into the processor’s instruction set, which is highly efficient, but the allocated memory capacity is limited.
  • Heap: Those blocks of memory allocated by new, which the compiler does not care about and our application controls. Usually a new corresponds to a release. If the programmer does not release it, the operating system automatically recycles it when the program ends. This part of memory needs to be released manually by the programmer. Of course we don’t need to deal with ARC.
  • Global/static storage: Global variables and static variables are allocated to the same block of memory. In previous C languages, global variables were divided into initialized and uninitialized. In C++ there is no such distinction. This part of the data does not need to be manually released by the programmer, he will be released with the disappearance of the program.
  • Constants store: this is a special store where constants are stored and cannot be modified.





Screen shot 2017-06-25 am 10.20.09.png

Read this article about memory allocation

How is a Block implemented

Now that we know the basics of Clange, let’s see what Block does. Let’s start with a simple example.

M (clang-rewrite-objc HelloLefe. M); / / Clang-rewrite-objc HelloLefe. / / clang-rewrite-objc HelloLefe.

- (void)lefeTestComplete
{
    void (^complete)(void) = ^(void){
        NSLog(@"Block\n");
    };
    complete();
}

@endCopy the code

The converted code looks like this:

  • Find that each structure is generated with a long, smelly name that uses the class nameHelloLefeAnd the method namelefeTestCompleteAnd so on generate a structure, which is the implementation of the block, which is a very important structure. It mainly consists of two constructs and a constructor.
struct __HelloLefe__lefeTestComplete_block_impl_0 { struct __block_impl impl; struct __HelloLefe__lefeTestComplete_block_desc_0* Desc; // constructor __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • __block_implThe structure, yes__HelloLefe__lefeTestComplete_block_impl_0The first variable of the structure
struct __block_impl { void *isa; // isa pointer, block is an OC object, each class has a pointer to its instance int Flags; int Reserved; void *FuncPtr; // a pointer to a function to be executed in a block};Copy the code
  • The structure of the body__HelloLefe__lefeTestComplete_block_impl_0The second variable of
static struct __HelloLefe__lefeTestComplete_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};Copy the code
  • Similarly, a function is generated from the class name and the method name, i.e^(void){ NSLog(@"Block\n"); };The result of the transformation,Cself is almost the same thing as self in OC, which is’HelloLefe__lefeTestComplete_block_impl_0 Theta is pointing to the structureHelloLefePointer to lefeTestComplete_block_impl_0 ‘
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__cv2l59cn5x90wh88hrkp35x80000gp_T_HelloLefe_b006ae_mi_0);
}Copy the code
  • Main function, which the compiler automatically adds two parameters to each method when compiledself_cmd
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {// This code is for void (^complete)(void) = ^(void){NSLog(@"Block\n"); }; Void (*complete)(void) = ((void (*)()) &__hellolefe__lefetestComplete_block_impl_0 ((void) *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA)); // This code corresponds to complete(); Conversion, ((void (*) (__block_impl *)) ((__block_impl *) complete) - > FuncPtr) ((__block_impl *) complete); }Copy the code

To declare a block, call the __HelloLefe__lefeTestComplete_block_impl_0 constructor, and get an IMP, equivalent to the IPM in OC. It holds the information needed for the block, and when a block is called, call IPM-> FuncPtr directly.

I’m sure you’re still unfamiliar with the Block implementation, so keep reading for a while and give it a try. Try to think of a Block as an NSObject in your head.

Block capture variable

I remember when I was first introduced to blocks, I just vaguely heard that blocks could automatically capture variables used in blocks. Yes, a Block can capture automatic variables or objects that it uses, but it only captures the variables that it uses. It does not capture other variables that it does not use. This is an important point that causes circular references, which will be covered in more detail below. The corresponding global variable Block does not or is not captured.

The implementation of the above block is somewhat clear, so how blocks capture variables, I will convert the code to:

- (void)lefeTestComplete
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";

    void (^complete)(void) = ^(void){
        printf(fmt, val);
    };
    complete();
}Copy the code

The converted code looks like this. The implementation observed that there was too much const char * FMT; int val; This is the variable that the block captures, but we find that the dmy variable is not captured because it is not used in the block at all. The constructor method of the structure also requires passing in captured variables to construct the structure.

struct __HelloLefe__lefeTestComplete_block_impl_0 { struct __block_impl impl; struct __HelloLefe__lefeTestComplete_block_desc_0* Desc; const char *fmt; int val; __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) { const char *fmt = __cself->fmt; // bound by copy int val = __cself->val; // bound by copy printf(fmt, val); } static struct __HelloLefe__lefeTestComplete_block_desc_0 { size_t reserved; size_t Block_size; } __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)}; static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) { int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, fmt, val)); ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete); }Copy the code

Modify the variables captured in the Block

In the above example, you can’t modify a captured variable in a Block, so how can you modify a captured variable in a Block? __block can be used. If you modify a variable in a Block, the compiler will directly report an error. Such as:

- (void)leftTestBlock
{
    int age = 0;
    void (^block)(void) = ^{
        age = 10;
    };
}Copy the code

The compiler will get an error for this code, and some of you might say, “I’m going to use a block,” but why is it ok to use a block? Take a look at the following code:

Int global_val = 1; Static int static_global_val = 2; - (void)lefeTestComplete {// static int static_val = 1; void (^complete)(void) = ^{ global_val *= 1; static_global_val *= 2; static_val *= 3; }; complete(); }Copy the code

There is no problem with this code, it compiles normally, it does not use blocks. Most of you will have a question when you read this. Why is this? Let’s take a look at his implementation. Instead of capturing the global variable ‘HelloLefe__lefeTestComplete_block_impl_0’, only the static_val is captured. But instead of capturing a static variable, it captures a pointer int *static_val; ‘Oh, yes, you can change it by using its pointer, but why can’t a normal variable use its pointer? Because a block must exist even if the scope of the captured variable is released, the variable is destroyed after the scope is released, which means that the block can no longer access the captured automatic variable. How to change? Static and global variables are not released.

int global_val = 1;
static int static_global_val = 2;


struct __HelloLefe__lefeTestComplete_block_impl_0 {
  struct __block_impl impl;
  struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
  int *static_val;
  __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

        global_val *= 1;
        static_global_val *= 2;
        (*static_val) *= 3;
    }

static struct __HelloLefe__lefeTestComplete_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};

static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
    static int static_val = 3;
    void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}Copy the code

You can modify a variable captured in a Block in the following ways:

  • Modify variables with __block
  • Using static variables
  • Use global variables, global static variables
  • With Pointers, static variables change their value through Pointers

Now that you know how to capture automatic variables, and how to modify variables captured in blocks, don’t you want to know why you can modify variables captured in blocks using __block decorations? Haha, hang in there!

How exactly is __block implemented?

__block, like static and auto modifiers, tells you where to store a variable. Let’s see how it works. Convert the following code:

- (void)lefeTestComplete { __block int val = 10; void (^complete)(void) = ^{val = 1; }; complete(); }Copy the code

There’s a lot more to the conversion, so why do you need to add so much code to use blocks? Lefe is curious. When block variables are used, they are copied from the stack onto the heap. When multiple blocks share a block variable, the block variable has a counter that keeps track of how many blocks refer to it. When the block is released, the reference count of the block variable is decreased by one until it reaches zero.

  • The __block converted structure
struct __Block_byref_val_0 { void *__isa; // __forwarding is mainly used to obtain the value of the __block variable, which points to different objects according to the memory location of the block. __Block_byref_val_0 *__forwarding; int __flags; int __size; int val; / / value};Copy the code
  • Block implementation, found more __Block_byref_val_0, which is a Block variable
struct __HelloLefe__lefeTestComplete_block_impl_0 { struct __block_impl impl; struct __HelloLefe__lefeTestComplete_block_desc_0* Desc; __Block_byref_val_0 *val; // by ref __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • ^{val = 1; }After the implementation is converted:
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}Copy the code
static void __HelloLefe__lefeTestComplete_block_copy_0(struct __HelloLefe__lefeTestComplete_block_impl_0*dst, struct __HelloLefe__lefeTestComplete_block_impl_0*src) {
_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __HelloLefe__lefeTestComplete_block_dispose_0(struct __HelloLefe__lefeTestComplete_block_impl_0*src) {
_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __HelloLefe__lefeTestComplete_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __HelloLefe__lefeTestComplete_block_impl_0*, struct __HelloLefe__lefeTestComplete_block_impl_0*);
  void (*dispose)(struct __HelloLefe__lefeTestComplete_block_impl_0*);
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0), __HelloLefe__lefeTestComplete_block_copy_0, __HelloLefe__lefeTestComplete_block_dispose_0};Copy the code
  • __block int val = 10;The transformed code looks like this, converted to a structure and initialized with a value of 10
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
        (void*)0,
        (__Block_byref_val_0 *)&val, 
        0, 
        sizeof(__Block_byref_val_0), 
        10
    };

    void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}Copy the code

The memory segment of a Block

The following figure shows that blocks are stored in stacks, heaps, and data areas.





Screen shot 2017-06-23 PM 10.06.35.png

What variables are dispatched to the stack, heap, or data area?

  • A block literal is a global block when written in a global scope.
  • 2. Global block when a block literal does not take any external variables;

Except in case 2 above, all others are dispatched to the stack. Blocks dispatched to the stack are freed when the scope ends. To solve this problem, Blocks provides a function that copies Blocks from the stack to the heap. This does not cause the block to be released even if the scope ends. Isa = &_NSConcretemalLocBlock. Blocks become blocks on the heap.

With ARC, the compiler automatically copies blocks in the stack onto the heap.

typedef int (^blk_t)(int); blk_t func(int rate) { return ^(int count){return rate * count; }; }Copy the code
blk_t func(int rate) { blk_t tmp = &__func_block_impl_0( __func_block_func_0, &__func_block_desc_0_DATA, rate); TMP = objc_retainBlock(TMP); // Copy a block directly to the heap, even if the block is not destroyed when the function ends. return objc_autoreleaseReturnValue(tmp); }Copy the code

But not all of the time, the compiler does copy. In the following cases, the compiler does not copy

  • When a Block is passed as an argument to a function or method.

For example, the following example will crash directly, so we need to copy the blocks in the array

+ (id)getBlockArray {
    int val = 10;
    return [[NSArray alloc] initWithObjects:
            ^{NSLog(@"blk0:%d", val);},
            ^{NSLog(@"blk1:%d", val);},
            nil
            ];
}

+ (void)lefeTestComplete
{
    id obj = [self getBlockArray];
    typedef void (^blk_t)(void);
    blk_t blk = (blk_t)[obj objectAtIndex:0];
    blk();
}Copy the code

However, copy is not required to use system-provided methods, such as GCD, because copy is already implemented within the function itself.

Capture object:

How does a Block capture an object? In the following array, print the result:

Array: (
    Lefe,
    Wang,
    Su,
    Yan
)Copy the code

The array is not released. The object is strongly referenced inside the Block until the Block is freed, and the referenced object is also freed.

@implementation HelloLefe

LefeBlock block;

+ (void)lefeTestComplete
{
    NSMutableArray *array = [NSMutableArray array];
    block = ^(NSString *name){
        [array addObject:name];

        NSLog(@"Array: %@", array);
    };
}

+ (void)addObject
{
    block(@"Lefe");
    block(@"Wang");
    block(@"Su");
    block(@"Yan");

}

@endCopy the code

Circular reference 1:

- (void)testMemoryLeakCase1 { self.logId = @"Hello logId"; /** This is easiest to detect because the compiler automatically prompts you for a circular reference to Why? Self (SecondViewController) holds finshBlock, so you can think of it as a normal property, a strong reference and finshBlock refers to self, so it's a closed loop. How? And since we have a closed loop, we can just break the closed loop, let finshBlock hold a weak reference, so self (SecondViewController) holds finshBlock, FinshBlock does not hold self */ / __weak Typeof (self) weakSelf = self; The general macro definition is __weak SecondViewController *wSelf = self; self.finshBlock = ^(BOOL isSuccess) { [wSelf loginTest]; }; __weak and __strong are used in our applications. So some people might say, "weak" and "strong", that's still quoting self, are you sure self is strong? */ ** Print:  (lldb) p weakSelf (SecondViewController *) $0 = 0x0000000101c16f10 (lldb) p self (SecondViewController *) $1 = 0x0000000101C16F10 (LLDB) p strongSelf (SecondViewController *) $2 = 0x0000000101C16F10 (LLDB) Find weakSelf Self and StrongSelf's memory address is the same, just a shallow copy; */ __weak typeof(self) weakSelf = self; Self. FinshBlock = ^(BOOL isSuccess) {// If there is no such statement, when self is released, weakSelf becomes empty, so some operations on weakSelf are meaningless. If you also want to make some of the methods weakSelf calls meaningful for so long, you need to strongly reference weakSelf; __strong typeof(self) strongSelf = weakSelf; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"weakSelf.logId: %@", strongSelf.logId); NSString *name = strongSelf.logId; if (name.length > 0) { NSLog(@"Hello world"); } [strongSelf loginTest]; }); }; self.finshBlock(YES); Self. FinshBlock = ^(BOOL isSuccess) {[self loginTest]; }; * /}Copy the code

Circular reference 2:

- (void)testMemoryLeakCase2 {/** Memory leak for task and self Task: The task has a property called blCOk, but the task is caught in the block, thus forming a closed loop self memory leak: Because self is trapped in this block, the block is not freed so how can self be freed? So as soon as you break this loop, self is released. */ AsyncTask *task = [AsyncTask new]; __weak AsyncTask *wTask = task; task.block = ^(BOOL isFinish) { NSString *name = wTask.lastLoginId; self.logId = name; }; [task sendLogin]; /** AsyncTask *task = [AsyncTask new]; task.block = ^(BOOL isFinish) { NSString *name = task; self.logId = name; }; [task sendLogin]; * /}Copy the code

Circular reference three:

The instance variable is actually accessed through self->name, so it can also cause circular references.

- (void)testMemoryLeakCase3 {/** Call the name instance variable self->name so that self holds finshBlock and finshBlock holds self, creating a closed loop. Create a loop referencing */ __weak SecondViewController *wSelf = self; self.finshBlock = ^(BOOL isFinish) { /* Dereferencing a __weak pointer is not allowed due to possible null value caused By race condition, assign it to strong variable first */ / // wSelf->name = @"Hello lefe"; /** Wself. logId = @"Hello logId"; No compilation errors? I want to estimate that wself. logId is equivalent to [wSelf logId], which is equivalent to calling a method. Nil calls methods without errors. Do you know the difference between properties and instance variables? The following line of code also generates an error: __weak AsyncTask *task; task->_sex; */ wSelf.logId = @"Hello logId"; __strong SecondViewController *strongSelf = wSelf; strongSelf->_name = @"Hello lefe"; }; /** You can also use the following method to remove the loop reference __block id temp = self; self.finshBlock = ^(BOOL isFinish) { temp = nil; }; self.finshBlock(YES); Self. finshBlock = ^(BOOL isFinish) {name = @"Hello lefe"; }; * /}Copy the code