1. The nature of blocks

blockIs essentially an OC object.

Take a look at the following code for the basic use of blocks:

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        void (^ aBlock)(void) = ^{
            NSLog(@"this is a block");
        };

        aBlock();
    }
    return 0;
}
Copy the code
  • To define ablockIn theblockExecute a line of code in theblock
  • Can be achieved byclangCompiler to see the compiledblockHow is this implemented:
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};// Encapsulates the code executed by the block
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5k_slnd0jd17fsb0qlp4fp4w09m0000gn_T_main_91fc10_mi_0);
        
}

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

/ / the main function
int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (* aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    }
    return 0;
}
Copy the code
  • The compiled main function first calls __main_block_impl_0 and passes in two arguments, __main_block_func_0 and &__main_block_desc_0_data

  • The __main_block_IMPL_0 function is a constructor of the __main_block_IMPL_0 structure. The first argument takes a pointer to the __main_block_func_0 function, which encapsulates the code in the block. The second argument is passed in some description of the block containing information about how much memory the block occupies

  • The __main_block_IMPL_0 function, because it is a constructor, returns a variable of the __main_block_IMPL_0 structure, assigns a value to the __block_impl member in the constructor, and copies the function address to the FuncPtr pointer.

  • The __main_block_IMPL_0 structure is the essence of the block, it has a structure member __block_impl, in the __block_impl structure contains isa pointer, from this aspect shows that the essence of the block is an OC object, The isa pointer points to the block’s class, and the __block_impl structure also contains the FuncPtr pointer. When assigning to the structure, this pointer points to the __main_block_func_0 function, which is used to call the block.

In the above case, the block has no arguments and no return value. If arguments are passed inside the block, what is the structure of the block?

void (^ aBlock)(int, int) = ^(int a, int b){
    NSLog(@"a is %d, b is %d", a, b);
};

aBlock(10, 20);
Copy the code

Continue to look at the produced CPP code through the CLang compiler:

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5k_slnd0jd17fsb0qlp4fp4w09m0000gn_T_main_c68cfa_mi_0, a, b);
}
Copy the code
  • Only in the__main_block_func_0Two arguments are passed into the function,blockThere is no change in the structure of.

Second, Block variable capture

2.1 Local variable capture (Auto variable)

What is the print result of the following code?

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        int num = 6;
        void (^ aBlock)(void) = ^{
            NSLog(@"num is %d", num);
        };
        
        num = 66;
        aBlock();
    }
    return 0;
}
Copy the code
  • The answer is num is 6
  • Through the printed results above, there will be a question why I modified it firstnumThe value ofblockNum is 6. Here’s a look:

To find out, the CLang compiler generates CPP code

First look at the main function

int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int num = 6;
        void (* aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));

        num = 66;
        ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    }
    return 0;
}
Copy the code
  • __main_block_impl_0In addition to passing two arguments as before, the function also passes one argumentnum.
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int num;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • inblockThere is an extra member variable in the structure ofnum, the argument just passednumThe value 6 of is assigned to the structure.

So let’s modifynumThe value of theta is 66. We’re just changing local variablesnumIs not modifiedblockIn the structurenumThe value of the

To call a block, we essentially call the __main_block_func_0 function via the FuncPtr pointer:

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5k_slnd0jd17fsb0qlp4fp4w09m0000gn_T_main_dbf0b3_mi_0, num);
}
Copy the code
  • The function passesstruct __main_block_impl_0 *__cselfThe function pointer retrieves the inside membernumIs printed last, so the printed result is 6.

All of this leads to the conclusion that a block captures the value of a local variable inside itself.

2.2 Local Variable Capture (static variable)

What is the print result of the following code?

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        
        static int num = 10;
        void (^ aBlock)(void) = ^{
            NSLog(@"num is %d", num);
        };
        num = 20;
        aBlock();
    }
    return 0;
}
Copy the code

The answer is num is 20.

To continue looking at the compiled CPP code, start with the main function:

int main(int argc, const char *argv[])
{
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        static int num = 10;
        void (* aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &num));
                            
        num = 20;
        ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    }
    return 0;
}
Copy the code
  • This time to__main_block_impl_0Functionally transitiveThe third parameter is passednumThe address of the.
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *num;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_num, int flags=0) : num(_num) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • __main_block_impl_0There are many members of the structurenumThe pointer.

Look again at the block call:

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5k_slnd0jd17fsb0qlp4fp4w09m0000gn_T_main_a3338a_mi_0, (*num));
}
Copy the code
  • Access to thenumAfter the pointer to,Pass in the call phase*numGets the value in the memory address to which the pointer points.

From the above analysis, it is obvious that the value of num should change.

Conclusion: Both static modified local variables and auto modified local variables are captured by the block, except that static modified local variables are address passed and auto modified local variables are value passed.

2.3 Global Variables

What is the print result of running the following code?

int num = 10;
static int num2 = 10;

int main(int argc, const char *argv[])
{
    @autoreleasepool {
        
        void (^ aBlock)(void) = ^{
            NSLog(@"num is %d , num2 is %d", num, num2);
        };
        num = 20;
        num2 = 20;
        aBlock();
    }
    return 0;
}

Copy the code
  • This time, we’re going toblockInstead of using global variables, the printed result is 20

Keep looking at the nitty gritty:

int num = 10;
static int num2 = 10;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5k_slnd0jd17fsb0qlp4fp4w09m0000gn_T_main_9bca35_mi_0, num, num2);
}
Copy the code
  • inblockThe global variables are not captured in the structure ofblockGlobal variables are accessed directly in the executing code, because the memory of the global variable is always there, and the value of the global variable is directly accessed.

Type of Block

We already know that the essence of a block is an OC object, so an OC object must have a type. Block is no exception, look at the following code to print the result?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^aBlock)(void) = ^{
            NSLog(@"this is a block");
        };
        
        NSLog(@"%@", [aBlock class]);
        NSLog(@"%@", [[aBlock class] superclass]);
        NSLog(@"%@", [[[aBlock class] superclass] superclass]);
        NSLog(@"%@", [[[[aBlock class] superclass] superclass] superclass]);
    }
    return 0;
}
Copy the code
  • The printed results are as follows:__NSGlobalBlock__= >__NSGlobalBlock= >NSBlock= >NSObject
  • Through the layers of calls, we finally find oneblockAnd ultimately inheritanceNSObjectSo it can also be proved from the sideblockThe essence is an OC object.

There are three types of blocks. In addition to the __NSGlobalBlock__ type we printed above, there are two types, __NSStackBlock__ and __NSMallocBlock__.

Before exploring block types, we need to adjust the project to the MRC environment so that we can actually figure out what block types are, because the compiler does something for us in the ARC environment.

3.1 _NSGlobalBlock_

A global static block that does not access any external variables

In MRC environment, the code is as follows:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^ aBlock) (void) = ^{
            NSLog(@"this is a block");
        };
        
        NSLog(@"%@", [aBlock class]);
        
    }
    return 0;
}
Copy the code
  • inblockNo capture inautoModified local variable, then its type is__NSGlobalBlock__
  • theblockIs a segment of data stored in memory.

3.2 _NSStackBlock_

Blocks stored on the stack are destroyed when the function returns

In MRC environment, the code is as follows:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int num = 10;
        void (^aBlock)(void) = ^{
            NSLog(@"num is %d", num);
        };
        
        NSLog(@"%@", [aBlock class]);
        
    }
    return 0;
}
Copy the code
  • blockcaptureautoModifier local variable, in which case its type is__NSStackBlock__.
  • theblockIs stored in the stack area of memory, since it is stored in the stack area, indicating that its memory creation and destruction is not controlled by the programmer, so if we use it outside its scope, there will be problems
void (^aBlock)(void);

void test() {
    int num = 10;
    aBlock = ^{
        NSLog(@"num is %d", num);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        
        aBlock();
    }
    return 0;
}
Copy the code
  • inmainFunction firsttestThe function is createdblockandblockLocal variables are captured and then calledblockTake a look at the print:num is -272632744This is not the result we want. What is the reason for this?
  • Because theblockIt’s in the stack, and it did catch at firstnumThe value of existsblockastestFunction completionblockAlso destroyed in stack area, inside the membernumIt’s already assigned to junk data. So when we go through the global variable againaBlockCall theblockWhen printed is garbage data, does not make any sense.

How to keep Block alive? Blocks need to be moved to the heap and the programmer can control when they are destroyed.

3.3 _NSMallocBlock_

Blocks stored in the heap are destroyed when the reference count reaches zero

In MRC environment, the code is as follows:

void (^aBlock)(void);

void test() {
    int num = 10;
    aBlock = [^{
        NSLog(@"num is %d", num);
    } copy];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        
        aBlock();
    }
    return 0;
}
Copy the code
  • Num is 10
  • intestFunction of theblockforcopyOperation, then at this timeblockThe type is__NSMallocBlock__
  • __NSMallocBlock__In memory in the heap area.
  • If we look at__NSMallocBlock__thencopyOperation? It’s still in the heap, but the reference count is +1

We explored the types of blocks in the context of MRC. Our normal development is in ARC, where the compiler automatically copies blocks at certain points.

  1. blockAs a function return value andblockInternally capturedautoModified variable.
typedef void(^LLBlock)(void); LLBlock testBlock() { int num = 10; return ^{ NSLog(@"num is %d", num); }; } int main(int argc, const char * argv[]) { @autoreleasepool { LLBlock aBlock = testBlock(); NSLog(@"%@", [aBlock class]); } return 0; } Print the result: __NSMallocBlock__Copy the code
  • blockIs referenced by a strong pointer andblockInternally capturedautoModified variable
typedef void(^LLBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { int num = 20; LLBlock aBlock = ^{ NSLog(@"num is %d", num); }; NSLog(@"%@", [aBlock class]); / / __NSMallocBlock__ / / has not been strong pointer reference block NSLog (@ "% @", [^ {NSLog (@ "num is % d", num);} class]); // __NSStackBlock__ } return 0; }Copy the code
  • GCDAPIblockCocoaAPIwithUsingBlockThe wording of theblock
    • enumerateObjectsUsingBlock
    • void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

4. Object type auto variable memory management

Earlier we looked at block’s capture of auto-modified local variables using primitive data types. Now we’ll look at memory management for defining object types like Blocks.

First look at the following code:

// Person.m @implementation Person - (void)dealloc { NSLog(@"person - dealloc"); } @end // main.m typedef void(^MyBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { MyBlock myBlock; { Person *person = [[Person alloc] init]; myBlock = ^{ NSLog(@"person is %@", person); }; } myBlock(); } return 0; } // Print the result: person is < person: 0x10288f260> person-deallocCopy the code
  • To create aPersonClass, and rewrite itsdeallocMethod, the purpose is to monitorPersonWhen objects are destroyed.
  • inmainFunction, using the{}Code blocks generate local scopes when{}After the code is executed, to monitorpersonWhether the object is destroyed.
  • Because the use ofmyBlockStrong reference to theblockIn object,ARCThe compiler in the environment is going to doblockautomaticallycopyOperation, so at this pointblockinThe heap areaIn theblockAt the same timeautoModification of thepersonObject, so it will bepersonObject reference count +1, guaranteedpersonObjects in the{}It will not be destroyed after execution.

Use the xcrun-sdk iphoneos clang-arch arm64-rewrite-objc-fobjc-arc-fobjc-runtime =ios-8.0.0 main.m command to view the c++ code generated by the compiler.

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • blockInternally capturedpersonObject and is a strong reference

And look at the desc structure

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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};
Copy the code
  • The structure has two extra function Pointers. Call is__main_block_copy_0__main_block_dispose_0Two functions
  • in__main_block_copy_0Function, called_Block_object_assignThe function handles the increment of the object reference count
  • in__main_block_dispose_0Function, called_Block_object_disposeThe function handles the reduction of object reference counts

If we modify the above code:

typedef void(^MyBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { MyBlock myBlock; { Person *person = [[Person alloc] init]; // Change the person object to a weak reference __weak person *weakPerson = person; myBlock = ^{ NSLog(@"person is %@", weakPerson); }; } myBlock(); } return 0; } // Print the result: person-dealloc person is (null)Copy the code
  • Use weak Pointers to point topersonObject and theblockIn the call.
  • At this point,blockAlthough capturedpersonObject, but does not enablepersonObject reference count +1, so when{}When the code is executed,personThe object is destroyed and then calledblockPrint the resultpersonIt’s empty.
  • At this point,__main_block_impl_0personPointers are weakly referenced.

To sum up: inARCThe environment exists in the heap areablocktoautoThe modified object type carries a strong reference by default, and the purpose is inblockInternal usepersonObject time guaranteepersonThe object is not destroyed. If we don’t want toblockInternally, the object type is strongly applied, and a weak pointer can be used to point to the object.

What if the block is on the stack?

If a block is on the stack, its memory is out of our programmer’s control, and we need to copy the block to the heap so that the object type variables captured inside the block are not destroyed. If a block is not copied, the reference count +1 operation is not performed on the object type captured inside the block.

Five, the __block

Question: Can I change the value of a variable used in a block? If so, in what ways?

  1. Define the variable as static

  2. Define a global variable

  3. Use __block

Since the first two methods result in the variable’s memory never being destroyed, it is common for development to use __block to modify the variable in a block

int main(int argc, const char * argv[]) { @autoreleasepool { __block int num = 10; void (^block)(void) = ^{ num = 20; NSLog(@"num is %d", num); }; block(); } return 0; } // Num is 20Copy the code

Again, take a look at the compiled C++ code using the clang compiler

Let’s start with the main function:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0.sizeof(__Block_byref_num_0), 10};

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));

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

    }
    return 0;
}
Copy the code
  • Variables and__blockAfter that, the type of the variable defined is not simpleintType, but instead__Block_byref_num_0Type. in__Block_byref_num_0The second parameter will benumThe fourth parameter is passed innumThe value of the.
struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • __Block_byref_num_0The member containsisaIt’s of the same type__forwardingPointer, the last parameter is used for storagenumThe member of the variable, there areisaA pointer means it’s an object,__forwardingA pointer is used to point to itself.
  • Take a look atblockThe structure is no longer saved directlynumIt’s preserved__Block_byref_num_0Pointer to__Block_byref_num_0Structure.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

            (num->__forwarding->num) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5k_slnd0jd17fsb0qlp4fp4w09m0000gn_T_main_f080da_mi_0, (num->__forwarding->num));
}
Copy the code
  • blockThe essence of the call is to call the function, as we can seeblockthrough__Block_byref_num_0A pointer to find__forwardingfindnumEventually modifynumThe value of the.

From the above code analysis, we can see that adding a __block to the auto modified local variable is essentially wrapping the variable into an object that changes the value of num.

In the ARC context, block captures auto modified local variables that are copied to the heap by copy. Since __block wraps variables as __Block_byref_num_0 objects, __Block_byref_num_0 is also copied to the heap, so memory management must be done, Memory management uses the __main_block_copy_0 and __main_block_dispose_0 functions in the __main_block_desc_0 structure.

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/); }Copy the code
  • __main_block_copy_0The function is called internally_Block_object_assignFunction to thenumStrong reference
  • whenblockIt is called again when it is destroyed__main_block_dispose_0In the function_Block_object_disposeFunction,numConduct destruction operation

Next, let’s look at the more complex case where __block modifies object types. Let’s start with the following code:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block NSObject *obj = [[NSObject alloc] init];
        
        void (^block) (void) = ^{
            NSLog(@"%@", obj);
        };
        
        block();
        
    }
    return 0;
}
Copy the code

Use the clang compiler to generate the corresponding C++ code:

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *__strong obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
  • See firstblockThe structure of the__blockThe modified object type is also wrapped as an object type i.e__Block_byref_obj_0
  • __Block_byref_obj_0obj The pointer is pointing __Block_byref_obj_0Struct, there is a strong reference in structobjPointer toNSObject object

Since it is wrapped as an object, memory management must be involved:

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

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, 8/*BLOCK_FIELD_IS_BYREF*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/); }Copy the code
  • __Block_byref_obj_0Object memory passes__main_block_desc_0In structure__main_block_impl_0Functions and__main_block_impl_0manage
  • whenblockCalled when copied to the heap__main_block_copy_0In the function_Block_object_assignThe function is strongly referenced
  • whenblockCalled when destroyed__main_block_dispose_0In the function_Block_object_disposeThe destruct () function destroys an object

The __Block_byref_obj_0 structure also contains two functions, __Block_byref_id_object_copy and __Block_byref_id_object_dispose

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, * (void((* *)char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void((* *)char*)src + 40), 131);
}
Copy the code
  • __Block_byref_id_object_copy_131The function will be based on__Block_byref_obj_0objIs a pointer to a strong or weak pointerobjObject strong references and weak references.
  • __Block_byref_id_object_dispose_131Function in__Block_byref_obj_0Destroy when the object is destroyedobjObject.

Note that in the MRC environment, even if we actively copy block objects, using __block modified object types will not be strongly referenced within the block

Circular reference

A common problem encountered when using blocks is the problem of circular references causing memory leaks. Look at the following code:

// Person.h typedef void(^MyBlock)(void); @interface Person : NSObject @property (nonatomic, copy) MyBlock block; @implementation Person - (void)dealloc {NSLog(@"Person object destruction "); } @end // main function int main(int argc, const char * argv[]) {@autoreleasepool {Person * Person = [[Person alloc] init]; person.block = ^{ NSLog(@"%p", person); }; person.block(); } return 0; }Copy the code
  • To define aPersonClass and define oneblockProperties,person.mApproved in the file deallocMethods to observepersonWhether the object is destroyed
  • inmainIn the function, let’spersonblockAttribute strongly references oneblockIn theblockInternal callspersonObject,blockWill capturepersonObject and forcibly reference it.
  • whenmainWhen the function completes,personObjects don’t get destroyed becauseblockpersonA memory leak occurred in the circular reference caused by the cross-reference ofperson The object is not destroyed.

How do you solve circular references? There are three ways to solve the problem

  1. __weak

Circular references are caused by strong references between the block and the Person object. You can use __weak to replace one of the references with a weak reference

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *person = [[Person alloc] init];
        
        __weak typeof(person) weakPerson = person;
        person.block = ^{
            NSLog(@"%p", weakPerson);
        };
        
        person.block();
    }
    return 0;
}
Copy the code
  • whenmainWhen the function completes,personObjects are also destroyed
  • whenpersonAfter the object is destroyed, use__weakThe modified object points tonil
  1. _unsafe_unretained
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *person = [[Person alloc] init];
        
        __unsafe_unretained typeof(person) weakPerson = person;
        person.block = ^{
            NSLog(@"%p", weakPerson);
        };
        
        person.block();
    }
    return 0;
}
Copy the code
  • use_unsafe_unretained__weakIt does not strongly reference an object, but when the object is destroyed, it still holds the address of the object, and if we access the object again, we will generate wild Pointers.
  1. __block
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block Person *person = [[Person alloc] init];
        
        person.block = ^{
            NSLog(@"%p", person);
            person = nil;
        };
        
        person.block();
    }
    return 0;
}
Copy the code
  • through__blockResolving circular references must be calledblockAnd, inblockThe interior must bepersonObject tonil.
  • Due to the__blockNature rewraps an object that is strongly referencedpersonObject,personObject is strongly referencedblockblockThe three of them are in a state of referring to each other. If you want to solve circular references, take the initiativepersonObject tonilTo break the reference relationship between.

Changes in 2020.5.22