What is a Block

A Block is simply a Block of code, often referred to as a “closure”, that encapsulates the function call and its environment so that it can be called at the appropriate time. In OC, a Block is simply an OC object that can be passed as an argument.

The structure of a Block is as follows:

The nature of the Block

The underlying structure of a Block without access to external variables

  • First, create a Demo and add the following code to main.m:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void(^test)(void) = ^{
            NSLog(@"Block");
        };
        test(a); }return 0;
}
Copy the code
  • The main.m file is then converted into C++ code using the xcrun directive
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
Copy the code
  • The generated main. CPP file, first look at the main function, converted to C++, the structure is as follows, remove redundant strong operations, easy to read
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; //Block's definition void(*test)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA ); / / block callstest->FuncPtr(test);
    }
    return 0;
}
Copy the code
  • When the Block is compiled, it is converted to a __main_block_IMPL_0 structure, which has the following internal structure
Struct __main_block_impl_0 {struct __block_impl impl; Struct __main_block_desc_0* Desc; struct __main_block_desc_0* Desc; // Constructor __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {ipl. isa = &_nsConcretestackblock;  impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

Since the __block_impl structure is stored directly inside the __main_block_IMPL_0 structure, the __main_block_IMPL_0 structure can also be converted to the following form

struct __block_impl { void *isa; Int Flags; // int Flags; // flag, default is 0 int Reserved; Void *FuncPtr; // function memory address}; struct __main_block_impl_0 { void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0* Desc; };Copy the code

Block encapsulates the code we are calling as a function __main_block_func_0 and stores the memory address of __main_block_func_0 in void *FuncPtr as follows

Static void __main_block_func_0(struct __main_block_impl_0 *__cself) {// NSLog((NSString) *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_4f0065_mi_0); }Copy the code

The __main_block_DESc_0 structure stores information about the memory used by the block

static struct __main_block_desc_0 { size_t reserved; // Keep the field size_t Block_size; } __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0)};Copy the code
  • The underlying block is an OC object because it has an ISA pointer inside it. At the same time, blocks encapsulate internal code to execute as a function, and store the memory address of the function in the structure, so that it can be called at the appropriate time

The underlying structure of a Block when accessing external variables

When we use a Block, we can access external variables inside the Block, including local variables, static variables (equivalent to private global variables), global variables, and so on. Let’s take a look at how the underlying block accesses external variables with a Demo.

  • First create Demo and add the following code to the main.m file
Int c = 30; Int main(int argc, const char * argv[]) {@autoreleasepool {int a = 10; Static int b = 20; void(^test)(void) = ^{
            NSLog(@"Block - %d, %d, %d", a, b, c);
        };
        test(a); }return 0;
}
Copy the code
  • After converting main.m to C++ code, I look at the main function again and see the following results
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;
        static int b = 20;
        void(*test)(void) = (&__main_block_impl_0(
                                                  __main_block_func_0,
                                                  &__main_block_desc_0_DATA,
                                                  a,
                                                  &b));
        test->FuncPtr(test);
    }
    return 0;
}
Copy the code

The __main_block_IMPL_0 structure has two more parameters, which are the value of local variable A and the pointer to static variable B, namely its memory address.

  • View the memory structure of the __main_block_IMPL_0 structure
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; int *b; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The __main_block_IMPL_0 structure contains two additional member variables, one is int A, the other is int *b

  • FuncPtr(test) : FuncPtr(test) : FuncPtr(test) : FuncPtr(test) : FuncPtr(test);
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_064cd6_mi_0, a, (*b), c);
}
Copy the code

In the __main_block_func_0 function, local variables A and static variables B are accessed by passing the __main_block_IMPL_0 structure to the corresponding member variables, but the global variable C is not stored in the structure, but is accessed directly.

  • From this we can conclude that there is a mechanism for variable capture in blocks
    • When a local variable is accessed, the value of the local variable is captured in a block and stored in a member variable of the same name.
    • When a static variable is accessed, its address is captured in a block, stored in a member variable of the same name.
    • When accessing a global variable, it is accessed directly in a block, without capturing it, because the global variable is always there and never destroyed

It should be noted that OC actually has a default keyword auto. When we create local variables, we will add the keyword auto before local variables by default. For example, int A above is actually equivalent to auto int A. The auto keyword means that the variable it modifies will be released automatically, also means that the variable it modifies will be stored in the stack space, and the system will automatically release it.

Block to summarize

Summary of block underlying structure

The member variables in the structure are as follows. In the structure pointed to by member variable descriptor, there are two Pointers to copy and Dispose, which are related to the memory management of internal objects in the block, as explained later.

Block variable capture summary

Blocks use a variable capture mechanism to ensure that external variables can be accessed from within the block.

  • When a block accesses a local variable of type auto, the local variable is captured in a structure inside the block, and the value of the variable is captured directly.
  • When a block accesses a static variable of type static, the static variable is captured in a structure inside the block and the address of the static variable is captured.
  • When a block accesses a global variable, it does not capture it and accesses it directly.

The type of the block

Three types of blocks

There are three types of blocks in OC. You can use class or ISA Pointers to see the specific type of block

  • Start by adding the following sample code to main.m
Int main(int argc, const char * argv[]) {@autoreleasepool {// The first type NSGlobalBlock NSLog(@"% @",[^{
            NSLog(@"NSGlobalBlock"); } class]); NSStackBlock int a = 10; NSLog(@"% @",[^{
            NSLog(@"%d", a); } class]); // NSMallocBlock - 1 void(^test2)(void) = ^{
            NSLog(@"NSMallocBlock - %d", a);
        };
        NSLog(@"% @"[test2 class]); // The third type is nsmallocblock-2 NSLog(@)"% @",[[^{
            NSLog(@"%d", a);
        } copy] class]);
    }
    return 0;
}
Copy the code

The running results are as follows:

  • There are three types of blocks: NSGlobalBlock, NSStackBlock, and NSMallocBlock. These three types are stored in the. Data, stack, and heap area respectively. The corresponding structure is shown as follows

The mapping between block types in the figure and those printed above is as follows

The class method returns the type Isa pointing type
NSGlobalBlock _NSConcreteGlobalBlock
NSStackBlock _NSConcreteStackBlock
NSMallocBlock _NSConcreteMallocBlock

But whatever block type it is, ultimately it inherits from NSBlock, and NSBlock inherits from NSObject, so that also tells you that a block itself is an object.

How to distinguish block types

In the example above, there are four ways to generate different types of blocks mentioned as follows:

  1. A block that does not access a local variable and has no strong pointer to it is NSGlobalBlock
  2. A block that accesses a local variable’s block, but has no strong pointer to it, is an NSStackBlock
  3. A block that accesses a local variable and has a strong pointer to the block is NSMallocBlock
  4. A block of the type NSStackBlock. After copy is executed, the generated block is NSMallocBlock

In fact, the third and fourth points generate nsmallocblocks, which leads us to the following conclusion

The type of the block The operation performed by block
NSGlobalBlock No variable of type auto is accessed
NSStackBlock A variable of type auto is accessed
NSMallocBlock Blocks of type __NSStackBlock__ copy

Block copy operation

Block Memory changes after the copy operation is performed

NSGlobalBlock, NSStackBlock, and NSMallocBlock are the three types of blocks that store data, stack, and heap, respectively. Copy the three types of blocks separately. The result is as follows:

  • Copy a block from an NSGlobalBlock, and nothing happens, and you still get a block of type NSGlobalBlock, right
  • Operating on a block of type NSStackBlock copies the block from the stack to the heap, producing a block of type NSMallocBlock
  • Copy a block of type NSMallocBlock. The reference count of this block is increased by one

The structure diagram is as follows

Which operations in ARC automatically copy?

In the example above, copy is performed on a block of type NSStackBlock, and the resulting block is NSMallocBlock. This is not the only way to generate NSMallocBlock.

  1. When a block is returned, it is automatically copied
typedef void(^block)(void);
block test() {return ^{
        NSLog(@"NSMallocBlock");
    };
}
Copy the code
  1. Copy is performed when a pointer to a block of type __strong is used
void(^test2)(void) = ^{
            NSLog(@"NSMallocBlock - %d", a);
        };
Copy the code
  1. Copy is performed when a block is an argument to a Cocoa API method that contains a usingBlock
NSArray *arr = @[@"1"The @"2"];
[arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
     NSLog(@"NSMallocBlock");
}];
Copy the code
  1. Copy is performed when a block is an argument to a GCD method
dispatch_async(dispatch_get_main_queue(), ^{
     NSLog(@"NSMallocBlock");
});
Copy the code

When we use a block as a property, we usually use the copy modifier to modify the block, which is actually a copy of the block to the heap, so that we can manually manage the memory of the block

Block accesses the object type

The underlying structure of a block when accessing the auto variable of an object type

The external variables accessed by a Block are primitive data types, so there is no memory management involved. What is the internal structure of a Block when accessing an external object within a Block?

  • Start by adding the following sample code to main.m
Int main(int argc, const char * argv[]) {@autoreleasepool {// Default NSObject *obj1 = [[NSObject alloc] init]; void(^test1)(void) = ^{
            NSLog(@"NSMallocBlock - %@", obj1);
        };
        test(1); NSObject *obj2 = [[NSObject alloc] init]; __weak typeof(obj2) weakObj = obj2; void(^test2)(void) = ^{
            NSLog(@"NSMallocBlock - %@", weakObj);
        };
        test(2); }return 0;
}
Copy the code
  • Convert the main.m file to C++ code using the following instructions
Xcrun-sdk iphoneOS clang-arch arm64-rewrite-fobjc-arc-fobjc-Runtime =ios-8.0.0 main.mCopy the code

Since the __weak keyword is used to modify the object, the runtime version needs to be specified.

  • After converting to the main. CPP file, view the underlying structure of the block as
Struct __main_block_impl_0 {struct __block_impl impl; struct __main_block_desc_0* Desc; NSObject *__strong obj; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; Struct __main_block_impl_1 {struct __block_impl impl; struct __main_block_desc_1* Desc; NSObject *__weak weakObj; NSObject *__weak weakObj; __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, NSObject *__weak _weakObj, int flags=0) : weakObj(_weakObj) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The default is to generate a strong pointer to an external object, such as __main_block_IMPL_0, if you access an external object of type auto directly from a block. If an external object modified by the __weak modifier is accessed in a block, a pointer of type weak is generated inside it to the external object, such as the __main_block_IMPL_1 structure.

In the __main_block_IMPL_0 constructor, obj(_obj) means that the _obj argument passed by the constructor is automatically assigned to the member variable obj in the structure.

  • Since __main_block_desc_0 and __main_block_DESc_1 have the same structure, the following uses __main_block_desc_0 as an example. Check the __main_block_desc_0 structure, and you can find two new function Pointers inside it, as follows
static struct __main_block_desc_0 { size_t reserved; // Keep the field size_t Block_size; Void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); // Copy void (*dispose)(struct __main_block_impl_0*); Dispose function} __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};Copy the code

Two function Pointers copy and Dispose are added, corresponding to functions __main_block_copy_0 and __main_block_dispose_0, as follows

Static void __main_block_copy_0(struct __main_block_impl_0* DST, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); } // Dispose pointer to the function static void __main_block_dispose_0(struct __main_block_impl_0* SRC) { _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code

As mentioned earlier, a block encapsulates the function call and the function call environment, which means that if it refers to an external object, it needs to perform memory management operations on the external object. The _Block_object_assign function is called internally from the __main_block_copy_0 function. Its main function is to perform operations on externally referenced objects based on their modifiers. If the external object is modified with __strong, The _Block_object_assign function performs a retain operation on this object, making the reference count of the external object +1.

_Block_object_dispose is called inside the __main_block_dispose_0 function, which releases the external objects referenced inside the block after the execution of the internal functions inside the block.

conclusion

Block on the stack

If the block is on the stack, there is no strong reference to the auto variable of the object type when accessed in the block. Set Automatic Reference Counting under Build Settings in Xcode to NO, indicating that the MRC environment is in use.

  • Start by creating the XLPerson class and overriding the Dealloc method for easy testing
@implementation XLPerson

- (void)dealloc{
    [super dealloc];
    NSLog(@"%s", __func__);
}

@end
Copy the code
  • Add the following test code to main.m
typedef void(^TestBlock)(void); Int main(int argc, const char * argv[]) {@autoreleasepool {// create block TestBlock block; { XLPerson *person = [[XLPerson alloc] init]; block = ^{ NSLog(@"block --- %p", &person);
            };
            NSLog(@"% @", [block class]);
            [person release];
        }
        NSLog(@"Before block execution");
        block();
        [block release];
        NSLog(@"After block execution");
        
    }
    return 0;
}
Copy the code
  • Run the program and get the following print

In the MRC environment, the system does not copy blocks by default even if there is a strong pointer to a block. Therefore, the current block type is still NSStackBlock. Also, the XLPerson is freed before the block executes, indicating that the block on the stack is not strongly referencing the Person object.

Blocks are added to the copy heap

  • First, if a block is copied to the heap, the copy function is automatically called when accessing the auto-modified object variable. It internally calls the _Block_object_assign function, The _Block_object_assign function relies on the __strong, __weak, and __unsafe_unretained modifier of the auto variable. If the __strong modifier is used, the internal component strongly references the external component. __weak or __unsafe_unretained, a weak reference is generated
  • When the block is removed from the heap, its dispose function is called internally. The _Block_object_dispose function automatically releases the referenced auto variable. That is, make a release on the referenced auto variable.
  • The call timing of copy and Dispose functions is as follows
function Call time
copy Blocks on the stack are copied to the heap
dispose When a block on the heap is released

__block

The role of the __block

When using a block, we usually use __block to modify external variables if they are accessed from the block. The main function of __block is to modify external variables inside the block. Of course, blocks can only modify auto variables. Cannot be used to modify global and static variables.

  • First, create Demo and view the source code
typedef void(^TestBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block XLPerson *person = [[XLPerson alloc] init];
        __block int a = 10;
        TestBlock block = ^{
            person = nil;
            a = 20;
            NSLog(@"block -- a:%d, person:%@",a,person);
        };
        block();
        NSLog(@"A :%d, person:%@",a,person);
    }
    return 0;
}
Copy the code
  • Convert to C++ code with the following instructions
Xcrun-sdk iphoneOS clang-arch arm64-rewrite-fobjc-arc-fobjc-Runtime =ios-8.0.0 main.mCopy the code
  • First look at the block structure source, found that the block internal more than two Pointers, __Block_byref_person_0 type pointer person and __Block_byref_a_1 type pointer A
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __Block_byref_a_1 *a; // by ref
};
Copy the code
  • Look again at how local variables and blocks are created in main
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; Person = {0, // &person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend(objc_msgSend(objc_getClass("XLPerson"), sel_registerName("alloc")), sel_registerName("init"))}; / / encapsulation variable a __Block_byref_a_1 a = {0, (__Block_byref_a_1 *) & a, 0, sizeof (__Block_byref_a_1), 10}; Block = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &person, &a, 570425344)); block->FuncPtr(block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_115560_mi_1,(a.__forwarding->a),(person. __forwarding->person)); }return 0;
}
Copy the code

__block modifies the object type auto variable

A person object decorated with a __block is compiled into a structure of type __Block_byref_person_0 with multiple member variables, as shown below

# Wrap the person object into a structure __Block_byref_person_0struct __Block_byref_person_0 { void *__isa; //isa pointer __Block_byref_person_0 *__forwarding; // Forwarding pointer int __flags; // Flag bit int __size; Void (*__Block_byref_id_object_copy)(void*, void*); Void (*__Block_byref_id_object_dispose)(void*); // Copy void (*__Block_byref_id_object_dispose)(void*); // The pointer to dispose XLPerson *__strong person; // strong reference XLPerson instance object}; __Block_byref_person_0 person = {0, // corresponds to isa pointer, passes 0 &person, // corresponds to forwarding pointer, // Flags sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, // Dispose dispose objc_msgSend(objc_msgSend(objc_getClass))"XLPerson"),    
                              sel_registerName("alloc")), 
                              sel_registerName("init") // Create the XLPerson object with objc_msgSend and pass the pointer to the object into the structure};Copy the code

It is obvious that in the __Block_byref_person_0 structure, there are the following member variables

  • Isa pointer, where the value is 0, also indicates that this structure is also an OC object
  • The forwarding pointer points to the structure’s own memory address
  • Flags: flag bit, pass 33554432
  • Size, the sizeof the structure, is obtained by sizeof(__Block_byref_person_0), where it is easy to calculate that the sizeof memory required by the structure is 48 bytes
  • The __Block_byref_id_object_copy function is called to retain the person object based on the reference type of the person pointer because the person object is referenced in the structure. Here the person object is decorated with __strong, so copy does a retain operation on the person object with a reference count of +1.
  • __Block_byref_id_object_dispose_131, dispose function, which makes a release operation on the Person object when the structure is removed from memory, reference count -1
  • Person pointer, because we are creating an instance object of XLPerson externally, the person pointer is stored directly inside the structure to point to the XLPerson object we created.

As mentioned above, as block encapsulates the function call environment, once it internally references the external auto object, it needs to manage the memory of the external object, hence the copy function and Dispose function. Here too, because Pointers to XLPerson objects decorated with __block are stored inside the structure, you need to use copy and dispose functions to manage the object’s memory.

__block modifies the base data type auto variable

If you use __block to decorate the auto variable of the base data type, the variable is encapsulated in a structure of type __Block_byref_a_1, with the following internal structure

Encapsulate variable A into a structure __Block_byref_a_1struct __Block_byref_a_1 { void *__isa; //isa pointer __Block_byref_a_1 *__forwarding; // Forwarding pointer int __flags; // Flag bit int __size; // struct size int a; // a}; __Block_byref_a_1 a = {0, Pass 0 (__Block_byref_a_1 *)&a, // pass the address of the current structure a, 0, //flags sizeof(__Block_byref_a_1), // the sizeof the structure 10 // the value of the external variable a};Copy the code

As opposed to the __block modifier auto object, if you modify the base data type, there is no copy and dispose function in the structure. Since the base data type does not require memory management, you do not need to call these two functions.

  • Isa pointer, pass 0 here
  • The forwarding pointer points to the structure’s own memory address
  • Flags, pass 0 here
  • Size, the sizeof the structure, obtained using sizeof(__Block_byref_a_1), where the structure occupies 32 bytes of memory (the structure itself requires 28 bytes of memory)
  • A, holds the value of the external variable A, which is 10

conclusion

  • __block can be used to solve the problem of not being able to change the value of the auto variable inside a block.
  • __block can only be used to modify auto variables, not global, static, and so on
  • Using the __block modified auto variable, the compiler wraps the variable into a structure (which is also an object) that contains the following member variables
    • Isa pointer
    • The forwarding pointer points to its own memory address
    • flags
    • Size, the size of the structure
    • Val (The external variable used, if a primitive data type, is the value of the variable, if an object type, is a pointer to the object)
  • __block modifies the auto variable of the basic data type, such as __block int A, then encapsulates the internal member variable of the structure as shown above. If it modifies the auto variable of the object type, such as __block XLPerson *person, Copy and Dispose functions are added to the generated structure to manage the memory of the Person object.

Memory management of __block

When a block accesses an external __block-modified auto variable, it wraps the variable into a structure and stores the address value of the structure inside the block

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __Block_byref_a_1 *a; // by ref
};
Copy the code

Where person and a are Pointers to two __block structures, and because there are references to __Block_byref_person_0 and __Block_byref_a_1 in the block, the block must manage the memory of these two structures. Accordingly, copy and dispose are generated in __main_block_desc_0 to manage the memory of the structure (which is also the object) that person and A point to. The following

static struct __main_block_desc_0 { size_t reserved; // Keep the field size_t Block_size; Void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); // Copy void (*dispose)(struct __main_block_impl_0*); Dispose function} __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0), __main_block_copy_0, //copy function __main_block_dispose_0 //dispose function};Copy the code

The corresponding copy function and dispose function are as follows

Static void __main_block_copy_0(struct __main_block_impl_0* DST, struct __main_block_impl_0*src) { _Block_object_assign(&dst->person,src->person, 8/*BLOCK_FIELD_IS_BYREF*/); _Block_object_assign(&dst->a, src->a, 8/*BLOCK_FIELD_IS_BYREF*/); } // Dispose function static void __main_block_dispose_0(struct __main_block_impl_0* SRC) { _Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/); _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }Copy the code

This is the same as the memory management for accessing external objects in blocks mentioned above

  • When a block is on the stack, there is no strong reference to the __block variable inside the block, as demonstrated by demo above
  • When a block is copied to the heap, the copy function is first called. Inside the copy function, the _Block_object_assign function is called to make a strong reference to the __block variable. If the block accesses an external auto object, _Block_object_assign determines whether a strong reference is made to the external object based on whether the external object’s modifier is __strong or __weak. But if you access a __block variable, the block must make a strong reference to the __block variable.

When Block0 in the diagram is assigned to the heap, it is assigned to the heap along with the __block variable it references, and a strong reference is made to the __block variable on the heap

When Block1 in the figure is copied to the heap, it simply makes a strong reference to the __block variable on the heap because it has already been copied to the heap.

  • When a block is removed from the heap, the dispose function inside the block is called, which in turn calls the _Block_object_dispose function to automatically release the referenced __block variable, which is equivalent to a release operation.

When both Block0 and Block1 are deprecated, references to the __block variable from Block0 and Block1 are released, so the __block variable is eventually deprecated because it has no holder

The __forwarding pointer in __block

The __block modified auto variable has the following structure

There is a __forwarding pointer to itself in the structure, and subsequent access to a __block variable is also through the __forwarding pointer

static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_a_1 *a = __cself->a; // bound by ref __Block_byref_person_0 *person = __cself->person; // bound by ref (a->__forwarding->a) = 20; Person ->__forwarding->person = __null; // Use the __forwarding pointer to get the person to change the NSLog((NSString) *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_fbc4b7_mi_0,(a->__forwarding->a),(person ->__forwarding->person)); }Copy the code

When a block is on the stack, the __block variable is also on the stack, and its internal __forwarding pointer points to itself

When a block is copied to the heap, the __block variable referenced by the block is also copied to the heap, so that there are two copies of the __block variable on the stack and on the heap. At this time, the __forwarding pointer in the __block variable on the stack points to the address of the __block variable on the heap. At the same time, The __forwarding pointer in the __block variable on the heap points to itself, so whether we are accessing the value of an attribute in the __block variable on the stack or in the __block variable on the heap, we are accessing the __block variable on the heap through the __forwarding pointer.

Summary of memory management for object types modified by __block

  • When a __block variable is on the stack, there is no internal strong reference to the object to which it points
  • When a block is copied to the heap, the __block variables it accesses are also copied to the heap
    • The copy function inside the __block variable is called first, which calls the _Block_object_assign function inside
    • The _Block_object_assign function relies on the __strong, __weak, and __unsafe_unretained modifier of the specified object. If the __strong modifier is used, it is strongly referenced
  • If a block is removed from the heap
    • The dispose function inside the __block variable is called, which calls the _Block_object_dispose function inside
    • The _Block_object_dispose function releases the objects referenced inside the __block variable.

Block accesses the auto and __block variables of the object type

The same

When a block is on the stack, there is no strong reference to either the auto or __block variables of the object type

The difference between

When a block is copied to the heap

  • When accessing the auto variable of the object type, the block invokes the copy function. The block determines whether to strongly reference the object based on the __strong, __weak, and __unsafe_unretained modifier. If the __strong modifier is used, the block strongly references the object. The copy function looks like this
Static void __main_block_copy_0(struct __main_block_impl_0* DST, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code
  • When accessing a __block variable, the block directly calls the copy function to strongly reference the __block variable. The copy function is as follows
Static void __main_block_copy_0(struct __main_block_impl_0* DST, struct __main_block_impl_0*src) { _Block_object_assign(&dst->a, src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }Copy the code

When a block is removed from the heap

When a block is removed from the heap, the dispose function is called to dispose of the referenced object

  • Dispose function called when referencing the auto variable of the object type
// Dispose function static void __main_block_dispose_0(struct __main_block_impl_0* SRC) {_Block_object_dispose((void*) SRC ->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code
  • Dispose function called when referring to the __block variable
// Dispose function static void __main_block_dispose_0(struct __main_block_impl_0* SRC) {_Block_object_dispose((void*) SRC ->a, 8/*BLOCK_FIELD_IS_BYREF*/); }Copy the code

The copy function is called, but the parameter type is different. The parameter type is 3 (BLOCK_FIELD_IS_OBJECT) when accessing the auto variable of the object type, and 8 (BLOCK_FIELD_IS_BYREF) when accessing the __block variable.

supplement

Block circular reference problem

When a block is used, if the block is an object property and the object is used in the block, a circular reference is generated, causing the block and object to refer to each other and cannot be freed. The Demo is as follows

typedef void(^TestBlock)(void);
@interface XLPerson : NSObject

@property(nonatomic, copy)NSString *name;
@property(nonatomic, copy)TestBlock block;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *person = [[XLPerson alloc] init];
        person.name = @"Zhang";
        person.block = ^{
            NSLog(@"% @",person.name);
        };
        person.block();
    }
    return 0;
}
Copy the code

There are two solutions (mainly in the case of ARC) :

  • The object is decorated with __weak
__weak typeof(person) weakPerson = person;
person.block = ^{
    NSLog(@"% @",weakPerson.name);
};
Copy the code
  • Use __unsafe_unretained to modify the object
__unsafe_unretained XLPerson *weakPerson = person;
person.block = ^{
    NSLog(@"% @",weakPerson.name);
};
Copy the code

__weak and __unsafe_unretained

__weak and __unsafe_unretained both have the ultimate effect of preventing a block from being strong references to externally accessed objects, but rather weak references. That is, the reference count for external objects does not increase. __weak and __unsafe_unretained are also different. The __weak pointer is automatically set to nil after being destroyed. However, __weak and __unsafe_unretained cannot be deleted. Subsequent access to this pointer will result in an error with the wild pointer, so use __weak in preference to circular references. More on __weak in a future article.

The interview questions

1. What is the nature of blocks?

A block is an OC object that encapsulates a function call and its calling environment. Its underlying structure is actually a structure.

2. What does __block do?

If you want to modify the externally accessed auto variable in a block, you need to modify the auto variable with __block, which wraps the modified variable into a structure containing the value of the variable. If __block modifies the object type, Pointers to the memory address of the object are stored in the structure. At the same time, there are two Pointers to copy and dispose to manage the memory of the object.

3. Why copy when block is a property?

In ARC, if a block is decorated with copy, it copies the block from the stack to the heap, so that we can manage the block’s memory manually. If we do not decorate with copy, the block is stored on the stack and freed automatically.

4. What are the problems with using blocks? How to solve it?

When using blocks, the problem of circular references can be solved by using __weak or __unsafA_unretain to modify externally referenced objects. __weak is preferred.

conclusion

The above content is purely personal understanding, if there is anything wrong, welcome to comment.

Learn together and make progress together