The basic use

Common ways to use block are as follows:

// No parameter, no return value
void(^MyBlockOne)(void) = ^(void) {
    NSLog(@"No arguments, no return value");
};
MyBlockOne(a);// There is no return value
void (^MyBlockTwo)(int a) = ^(int a) {
    NSLog(@"a = %d", a);
};
MyBlockTwo(10);

// There are parameters and return values
int (^MyBlockThree)(int, int) = ^(int a, int b) {
    
    NSLog(@"return %d", a + b);
    return 10;
};
MyBlockThree(10.20);

// No parameter returns a value
int (^MyBlockFour)(void) = ^(void) {
    NSLog(@"return 10");
    return 10;
};

// Declare as a type
typedef int (^MyBlock) (int, int);
@property (nonatomic, copy) MyBlock block;
Copy the code

Essence of Block – OC object

Conclusion: There is an ISA pointer inside the block, which is essentially an OC object that encapsulates the function call and the function call environment.

Proof method 1: underlying structure snooping

The main function defines a block as follows

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^ (void) {
            NSLog(@"this is first block");
        };
        block(a); }return 0;
}
Copy the code

Go to the project directory and convert OC code to C++ code with the xcrun command:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
Copy the code

The conversion results are as follows:

// 1. Block structure
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; }};// The impL structure inside the block stores the ISA pointer, the address of the block method.
struct __block_impl {
  void *isa;      
  int Flags;
  int Reserved;
  void *FuncPtr;  // Method address
};

// Description of the block, for example, the size of the block! [](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c441779a345740a98accb31ac195d61f~tplv-k3u1fbpfcp-zoom-1.image)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)};

// 2. Block method implementation
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_main_cf18a7_mi_0);
}

// 3. Implementation of the main method
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
Copy the code

Simplifying the generated main method yields:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
        block->FuncPtr(block); 
    }
    return 0;
}
Copy the code

FuncPtr(block) is a member of the __block_impl type, but FuncPtr(block) is a member of the __block_impl type.

The reason is simple: __block_impl is the first member variable in the block structure __main_block_IMPL_0, so the address of the block and the address of the impl are the same. Both can be cast.

According to the conversion results:

  1. OCDefined in theblockThe underlying structure is a C++ structure__main_block_impl_0. The structure has two member variablesimpl,Desc, respectively are structure types__block_impl,__main_block_desc_0.
  2. The structure of the body__block_implContained within theisaPointers and Pointers to function implementationsFuncPtr.
  3. The structure of the body__main_block_desc_0Within theBlock_sizeMember storesThe size of the Block.

As you can see, a block has an ISA pointer inside it, so a block is essentially an OC object.

Proof method two: code level

If the block is an OC object, then it must eventually inherit from NSObject (except NSProxy), so we can just print out the block inheritance chain and see.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^ (void) {
            NSLog(@"this is first block");
        };
        
        NSLog(@"class = %@", [block class]); 
        NSLog(@"superclass = %@", [block superclass]); 
        NSLog(@"superclass superclass = %@", [[block superclass] superclass]);
        NSLog(@"superclass superclass superclass = %@", [[[block superclass] superclass] superclass]);
    }
    return 0; } Output:202007 -- 28 19:25:24.475317+0800 LearningBlock[39445:591948] class = __NSGlobalBlock__
202007 -- 28 19:25:24.475707+0800 LearningBlock[39445:591948] superclass = __NSGlobalBlock
202007 -- 28 19:25:24.475762+0800 LearningBlock[39445:591948] superclass superclass= NSBlock
202007 -- 28 19:25:24.475808+0800 LearningBlock[39445:591948] superclass superclass superclass= NSObject
Copy the code

Block inheritance chain: __NSGlobalBlock -> NSBlock -> NSObject

So you can see that block ultimately inherits from NSObject. The ISA pointer is actually from NSObject. So a block is essentially an OC object.

Capture variable of Block

To ensure that external values can be accessed from within the block, the block has a variable capture mechanism. Let’s explore the variable capture mechanism for the following blocks.

Code:

int a = 10;   // Global variables are stored in memory during the program running.
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        auto int b = 20;       // Local variables, which default to auto, can not be written auto, the end of the scope will be destroyed.
        static int c = 30;     // Static variables that remain in memory for the duration of the program.
        
        void(^block)(void) = ^ (void) {
            NSLog(@"a = %d, b = %d, c = %d", a, b, c);
        };
        
        // What are the values of a,b, and c when the block is called?
        a = 11;
        b = 21;
        c = 31;   
        
        block(a);/ / call block
    }
    return 0; } Print output:202007 -- 28 19:43:41.729849+0800 LearningBlock[39648:603167] a = 11, b = 20, c = 31
Copy the code

The print shows that B has not changed, while both a and C have changed. The reason? Let’s see

To convert the current OC code to C++ by running the following conversion statement, we can see more essential things:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
Copy the code

The converted code is as follows:


int a = 10;  // Global variables

struct __main_block_impl_0 {   // The structure of the block
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int b;    // The newly generated member variable b is used to hold the value of the external local variable b
  int *c;   // The newly generated member variable c, pointer type, is used to store references to the external static local variable C.
  
  // The constructor
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, int *_c, int flags=0) : b(_b), c(_c) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int b = __cself->b;  // Access the internal member variable b via cself
  int *c = __cself->c;   // Get a reference to the static local variable c via cself
  
  // Access the global variable a directly
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_256a11_mi_0, a , b, (*c)); 
}

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        auto int b = 20;
        static int c = 30;

        void (*Myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b, &c));

        a = 11;
        b = 21;
        c = 31;

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

    }
    return 0;
}
Copy the code

It can be observed that:

The block structure __main_block_IMPL_0 internally generates new member variables b and *c, which are used to hold the addresses of b and C, respectively, passed in from the outside. This is called capture. The global variable A is not captured and is accessed directly when used.

It follows that:

  1. blockWithin theautoandstaticType of variable performedcapture, but will not be capturedThe global variable.
  2. Although the block ofautoandstaticVariables are captured, but the difference is,autoVariables are value passes, andstaticVariables are address passing. So when the externalstaticWhen the value of a variable changes,blockThe inside will change, and the outsideautoThe value of the variable changes,blockThe internal values do not change.

Thinking 🤔

Why do blocks capture local variables of type Auto and static, but not global variables? (How do you differentiate between global variables and blocks?) , so what is the importance of variable capture for blocks?

Here’s the thing

  • First of all forautoA local variable of type whose lifetime is so short that when it leaves its scope,autoThe memory of the variable will be reclaimed by the system, andblockThe call timing is uncertain ifblockIf you don’t capture it, you can access it when the block is runningautoVariable, because the variable has been reclaimed by the systemBad memory accessorYou get an incorrect value.
  • For localstaticA variable, since it is initialized, will remain in memory for the duration of the program and will not be collected by the system, but because it is a local variable, it has limited scope to access, so a block needs to know where to access it, so a block needs to capture itautoThe variable is different,blockOnly need to capturestaticThe address of the variable.
  • forThe global variable, because it is present throughout the program and its access scope is globalblockYou can find it without having to capture it.

If a variable can be accessed directly from within a block, it should not be captured (it is also a waste of space). If a variable cannot be accessed directly from within a block, it should be captured (it is useless if not captured).

The type of the Block

There are three types of blocks, which you can see by calling a class method or an ISA pointer, all of which ultimately inherit from the NSBlock type.

  • NSGlobalBlock
  • NSStackBlock
  • NSMallocBlock

For the sake of accurate analysisblockThe type of the firstARCGive close, useMRC.

int main(int argc, const char * argv[]) { @autoreleasepool { auto int age = 10; // Local variable, default is auto, generally can not write auto, after the end of the scope will be destroyed. static int height = 20; // Static variables that remain in memory for the duration of the program. void(^block1)(void) = ^(void) { NSLog(@"1111111111"); // The auto variable is not captured}; void(^block2)(void) = ^(void) { NSLog(@"age = %d", age); // Capture the auto variable}; void(^block3)(void) = ^(void) { NSLog(@"height = %d", height); // Catch the static variable}; NSLog(@"block1 class: %@", [block1 class]); // __NSGlobalBlock__ NSLog(@"block2 class: %@", [block2 class]); // __NSStackBlock__ NSLog(@"block2 copy class: %@", [[block2 copy] class]); //__NSMallocBlock__ NSLog(@"block3 class: %@", [block3 class]); //__NSGlobalBlock__ } return 0; } // Output: 2020-07-28 20:41:43.283331+0800 LearningBlock[40390:637401] block1 class: __NSGlobalBlock__ 2020-07-28 20:41:43.283755+0800 LearningBlock[40390:637401] Block2 class: __NSStackBlock__ 2020-07-28 20:41:43.283877+0800 LearningBlock[4039:637401] block2 copy class: __NSMallocBlock__ 2020-07-28 20:41:43.283924+0800 LearningBlock[40390:637401] Block3 class: __NSGlobalBlock__Copy the code

From the above, we can know:

  1. The block type can be:

    • There is no catchautoVariable, soblockFor the__NSGlobalBlock__Type.
    • To capture theautoVariable, soblockfor__NSStackBlock__Type.
    • right__NSStackBlock__The type ofblockforcopyOperation,blockIt will become a__NSMallocBlock__Type.

  2. The main difference between these types of blocks is that they are stored in different areas of memory. (the life cycle is different)

    • __NSGlobalBlock__The data segment exists.
    • __NSStackBlock__Store in the stack space.
    • __NSMallocBlock__Stored in the heap space.

Inspection:

Create a new Person class like this:

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;

- (void)test;

@end

@implementation Person

- (void)test {
    void (^block)(void) = ^{
        NSLog(@"person name = %@", _name);
    };
}
@end
Copy the code

Question: Does the block in the test method of person. m capture self?

The answer is yes, and the block will capture self.

First, convert person. m to C++ using the xcrun command to get the following:

// Block method within test method
struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};/ / test methods
static void _I_Person_test(Person * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_Person_14871d_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
}
Copy the code

Looking at the converted code, you can see:

  1. The OC method that we normally write has two hidden parameters by default,(Person *self, SEL _cmd), respectively,Method caller self 和 Method selector sel.
  2. The parameters to a method are usually local variables, which are captured by a block.

Copy operation of Block

The blocks we use every day are of type __NSMallocBlock__ for the following reasons:

  • for__NSGlobalBlock__The type ofblockBecause there is no captureautoVariables, so normal is usually directly using the function implementation.
  • for__NSStackBlock__The type ofblockBecause it is stored on the stack, its internal use variables are easy to be reclaimed by the system, resulting in some abnormal situations. For example :(cut the project to MRC first, because the ARC compiler will copy according to the situation, which will affect the analysis)
typedef void (^MJBlock)(void); MJBlock block; void test() { int a = 10; // After the test method is finished, the memory of a is reclaimed. block = ^(void) { NSLog(@"a = %d", a); }; } int main(int argc, const char * argv[]) { @autoreleasepool { test(); block(); } return 0;} return 0; } 2020-09-27 10:05:28.616920+0800 interview01-block copy[7134:29679] a = -272632776Copy the code
  • for__NSMallocBlock__The type ofblockBecause it is stored on the heap, it does not exist__NSStackBlock__typeblockThe problem.

The above is shown in the MRC environment, how does it work in the ARC environment?

In an ARC environment, the compiler automatically copies blocks on the stack onto the heap as appropriate. Here’s an example:

  • blockAs a function return value.
  • willblockAssigned to__strongWhen the pointer.
  • blockAs aCocoa APIThe Chinese legal name containsusingBlockMethod parameter.
  • blockAs aGCD APIMethod parameter.
typedef void (^MJBlock)(void); MJBlock myblock() { int a = 10; return ^{ NSLog(@"--------- %d", a); // 1. As a method return value. Copy}; } int main(int argc, const char * argv[]) { @autoreleasepool { int age = 10; MJBlock block = ^ {/ / 2. Assignment to strong pointer, automatically copy NSLog (@ "-- -- -- -- -- -- -- -- - % d", the age); }; NSArray *arr = @[@10, @20]; [arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {// 3. Block is copied as a Cocoa API method parameter containing usingBlock}]; Dispatch_async (dispatch_get_global_queue(0, 0), ^{// 4. block is used as the GCD API method parameter. Copy}); } return 0; }Copy the code

Depending on the situation above, block properties can be written differently in MRC and ARC:

@property (copy, nonatomic) void (^block)(void); @property (strong, nonatomic) void (^block)(void); @property (copy, nonatomic) void (^block)(void);Copy the code

The auto variable of the object type

We have already analyzed the auto variable of the basic data type, so is the auto variable of the object type the same as the basic data type or is there something special about it? (Remember to switch back to ARC mode first)

The following code:

@interface LCPerson : NSObject @property (nonatomic, assign) int age; @end @implementation LCPerson - (void)dealloc { NSLog(@"%s", __func__); } @end int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@"11111111"); { LCPerson *person = [[LCPerson alloc] init]; person.age = 10; } NSLog(@"22222222"); } return 0; } // Output: 2020-09-27 10:36:43.856070+0800 LearningBlock[16016:56873] 2020-09-27 10:36:43.856442+0800 LearningBlock[16016:56873] -[LCPerson dealloc] 2020-09-27 10:36:43.856474+0800 LearningBlock[16016:56873] 22222222Copy the code

We defined an LCPerson class, tested it in main.m, and the output shows that the person object is released between 1111111 and 22222222, which we can all understand. (Local scope)

Let’s go on ~

Let’s take a look at that after we add Block.

typedef void (^MyBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"11111111"); MyBlock block; { LCPerson *person = [[LCPerson alloc] init]; person.age = 10; block = ^(void){ NSLog(@"person age = %d", person.age); }; } NSLog(@"22222222"); } NSLog(@"3333333"); return 0; } Output: 2020-09-27 10:52:27.578241+0800 LearningBlock[20478:70040] 11111111 2020-09-27 10:52:27.578627+0800 LearningBlock[20478:70040] 22222222 2020-09-27 10:52:27.578688+0800 LearningBlock[20478:70040] -[LCPerson dealloc] The 2020-09-27 10:52:27. 578729 + 0800 LearningBlock (20478-70040), 3333333Copy the code

Based on the result, we can see that after the block is added, the person object is destroyed after 222222, that is, the person object is not released immediately after the end of the scope in which the person is located. So what did the block do to the person to cause the person object not to be released in time? For the sake of analysis, let’s simplify the above code. Simplify the following

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

Convert the above OC code to C++ code :(ARC support, specify runtime system version)

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0. 0 main.m
Copy the code

The converted C++ code looks like this:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; LCPerson *__strong person; Void *fp, struct __main_block_desc_0 *desc, LCPerson *__strong _person, struct __main_block_desc_0 *desc, LCPerson *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { LCPerson *__strong person = __cself->person; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_5882d6_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age"))); } 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}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; LCPerson *person = ((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10); void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }Copy the code

As you can see from the observation, the Person is captured inside the block. In addition, unlike the auto variable that captures the basic data type, the __main_block_desc_0 structure has two more functions, namely copy and Dispose, which are related to the handling of the reference count of the captured object.

  • whenblockfromThe stackCopy toThe heapWhen on,copyThe function is called, and then it calls_Block_object_assignFunction that handles the reference count of the captured object, if used when capturing a variable__strongModifier, then the reference count of the object will+ 1. If yes when captured__weakModifies, the reference count does not change. (This will be verified below)
  • whenblockWhen it’s recycled, that is, when it’s released,disposeThe function is called, and then it calls_Block_object_disposeFunction if the capture variable is used when__strongModifier, then the reference count of the object will- 1. If yes when capturing variables__weakModifies, the reference count does not change. (This will be verified below)

We know that in ARC, when a block is assigned to a __strong pointer, the block automatically calls copy. So it’s clear that the reason the Person object is not released after it leaves the local scope is because when block calls copy, it increases the reference count of the Person object by one, so when the local scope ends, the reference count of the Person object is not zero, so it’s not released. When the scope of the block ends, the block calls Dispose, which reduces the reference count of Person to zero, and then the person is released.

As mentioned above, in an MRC environment, the Person object will be destroyed when it leaves the local scope. In an MRC environment, assigning a block to a __strong pointer does not trigger copy, so the Person object should be freed.

Verify 1: Switch the project to MRC mode and test the code as follows:

int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"11111111"); MyBlock block; { LCPerson *person = [[LCPerson alloc] init]; person.age = 10; block = ^(void){ NSLog(@"person age = %d", person.age); }; [person release]; // MRC requires manual memory management} NSLog(@22222222); } NSLog(@"3333333"); return 0; } // Output: 2020-09-27 11:39:05.493388+0800 LearningBlock[33422:105156] 11111111 2020-09-27 11:39:05.493800+0800 LearningBlock[33422:105156] -[LCPerson dealloc] 2020-09-27 11:39:05.493833+0800 LearningBlock[33422:105156] 22222222 The 2020-09-27 11:39:05. 493857 + 0800 LearningBlock (33422-105156), 3333333Copy the code

Observe the output, as expected. The Person object is normally released when it leaves the local scope.

Validation 2: An auto variable of object type with weak modification (remember to switch back to ARC)

int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"11111111"); MyBlock block; { LCPerson *person = [[LCPerson alloc] init]; person.age = 10; // Weak pointer __weakperson *weakPerson = person; block = ^(void){ NSLog(@"person age = %d", weakPerson.age); }; } NSLog(@"22222222"); } NSLog(@"3333333"); return 0; } // Output: 2020-09-27 12:00:20.461929+0800 LearningBlock[39325:122309] 11111111 2020-09-27 12:00:20.462321+0800 LearningBlock[39325:122309] -[LCPerson dealloc] 2020-09-27 12:00:20.462361+0800 LearningBlock[39325:122309] 22222222 The 2020-09-27 12:00:20. 462391 + 0800 LearningBlock (39325-122309), 3333333Copy the code

Observe the output, as expected. The Person object is normally released when it leaves the local scope.

Conclusion:

  • When a variable of object type auto is accessed inside a block

    • ifblockIs on the stack, will not be rightautoVariables generate strong references
  • If the block is copied to the heap

    • Will be calledblockThe inside of thecopyfunction
    • copyThe function is called internally_Block_object_assignfunction
    • _Block_object_assignThe function will be based onautoModifiers for variables (__strong,__weak,__unsafe_unretained) to form a strong reference (retain) orA weak reference
  • If the block is removed from the heap

    • Will be calledblockThe inside of thedisposefunction
    • disposeThe function is called internally_Block_object_disposefunction
    • _Block_object_disposeThe function automatically frees the referenceautoVariable (release)

__block qualifier

  • __blockCan be used to solveblockInternal cannot be modifiedAuto variableThe problem of value
  • __blockCan’t modifyThe global variable,A static variable(static)
  • The compiler is going to__blockA variable is wrapped as an object.

Let’s check it out:

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

Convert the above OC code to C++ code :(ARC support, specify runtime system version)

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0. 0 main.m
Copy the code

The result after conversion is obtained:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref this captures the reference to a
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a) = 20;     // Modify the value of a.
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_ca9eb0_mi_0, (a->__forwarding->a));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0.sizeof(__Block_byref_a_0), 10};  // This is the variable a modified by __block.

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));  // Pass in the address of the variable a.

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
Copy the code

OC __block int a = 10

	__Block_byref_a_0 a = {0, &a, 0, sizeof(__Block_byref_a_0), 10};
Copy the code

__Block_byref_a_0 is a structure with the following structure:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
}
Copy the code

So in OC__blockWhen you modify a variable, the compiler automatically generates a whole new OC object.

Memory management of __block

The memory management of __block in a block is similar (but also different) to that of the auto variable of the object type.

  • whenblockOn the stack, it’s not going to be right__blockVariables generate strong references
  • whenblockbecopyTo the heap
    • Will be calledblockThe inside of thecopyfunction
    • copyThe function is called internally_Block_object_assignfunction
    • _Block_object_assignFunction to__blockVariables form a strong reference (retain). This point is related to object typesautoThere is a difference between variables for object typesautoVariables,_Block_object_assignThe function is based on the modifier (__strong,__weak,__unsafe_unretained), and__blockIs a direct strong reference)

  • whenblockWhen removed from the heap
    • Will be calledblockThe inside of thedisposefunction
    • disposeThe function is called internally_Block_object_disposefunction
    • _Block_object_disposeThe function automatically frees the reference__blockVariable (release)

__forwarding pointer to __block

The type of object that is __block

From above we know the handling of basic data types with __block. Does __block treat object types the same? Let’s take a look:

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

With the xcrun command:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0. 0 main.m
Copy the code

When converted to C++, the result is as follows:

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);  // Manage person's memory
 void (*__Block_byref_id_object_dispose)(void*);      // Manage person's memory
 LCPerson *__strong person;   // In the ARC environment, the copy and Dispose functions are used to manage the memory of persons based on their __strong and __weak decorations.
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref //

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_main_213c56_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("age")));
        }
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, 8/*BLOCK_FIELD_IS_BYREF*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432.sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init"))};
        ((void (*)(id, SEL, int(a))void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setAge:"), 10);

        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
Copy the code

When a block is copied from the stack to the heap, the block’s copy method is called, along with the __Block_byref_person_0 structure’s __Block_byref_id_object_copy method. __Block_byref_id_object_copy calls the _Block_object_assign method inside the __Block_byref_person_0 structure to handle the reference count of the object to which the Person pointer points.

Summary:

  • When __block is on the stack, there is no strong reference to the object to which it refers

  • When the __block variable is copied to the heap

    • Will be called__blockintra-variablecopyfunction
    • copyThe function is called internally_Block_object_assignfunction
    • _Block_object_assignThe function is based on the modifier (__strong,__weak,__unsafe_unretained) to form a strong reference (retain) or weak references (Note:This is limited to AN ARC retain, not an MRC retain)
  • If the __block variable is removed from the heap

    • Will be called__blockintra-variabledisposefunction
    • disposeThe function is called internally_Block_object_disposefunction
    • _Block_object_disposeThe function automatically releases the pointed object (release)

Object type auto and __block

  • When blocks are on the stack, there is no strong reference to either of them

  • When blocks are copied to the heap, they are processed by the copy function

    • The __block variable (suppose the variable name is a)

    • _Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);

    • Auto variable of object type (suppose the variable name is p)

    • _Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);

  • When blocks are removed from the heap, they are disposed of by the Dispose function

    • The __block variable (suppose the variable name is a)

    • _Block_object_dispose((void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);

    • Auto variable of object type (suppose the variable name is p)

    • _Block_object_dispose((void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);

Circular reference problem

In the development process, we often encounter block circular reference problems, as follows:

typedef void (^MyBlock)(void); @interface LCPerson : NSObject @property (nonatomic, assign) int age; @property (nonatomic, copy) MyBlock block; @end @implementation LCPerson - (void)dealloc { NSLog(@"%s", __func__); } @end int main(int argc, const char * argv[]) { @autoreleasepool { LCPerson *person = [[LCPerson alloc] init]; person.age = 10; person.block = ^{ NSLog(@"person age %d", person.age); }; NSLog(@"211212121122"); } return 0; } // Output: 2020-09-28 20:01:48.358822+0800 LearningBlock[41115:298402] 211212121122Copy the code

As you can see from the print, the person is not released (the person’s dealloc method is not called). So what causes it? Is a circular reference. Let’s analyze:

  • @property (nonatomic, copy) MyBlock block;As you can see from this sentence,personStrong reference to theblock.
  • blockInternal accesspersonThe object’sageProperty, we know from the aboveblocktopersonTo capture, and inarcEnvironment,blockAssigned to__strongPointer is automatically calledcopyMethods,blockCopy from the stack to the heap, which causespersonThe reference count of is increased by 1, i.eblockStrong reference to theperson.

So the person and the block are strongly referenced to each other, circular references occur, so the Person object is not released.

So what’s the solution? How do you deal with it in the ARC environment and MRC environment?

Resolve circular reference issues – ARC

In the ARC environment, this can be resolved by using the keys __weak, __unsafe_unretained. As follows:

int main(int argc, const char * argv[]) { @autoreleasepool { LCPerson *person = [[LCPerson alloc] init]; person.age = 10; __weak LCPerson *weakPerson = person; // Or __unsafe_unretained LCPerson *weakPerson = person; person.block = ^{ NSLog(@"person age %d", weakPerson.age); }; NSLog(@"211212121122"); } return 0; } // Print the result: 2020-09-28 20:30:19.659679+0800 LearningBlock[41212:307877] 211212121122 2020-09-28 20:30:19.660256+0800 LearningBlock[41212:307877] -[LCPerson dealloc]Copy the code

The schematic is as follows:

You can also use __block, as follows:

int main(int argc, const char * argv[]) { @autoreleasepool { __block LCPerson *person = [[LCPerson alloc] init]; person.age = 10; person.block = ^{ NSLog(@"person age %d", person.age); person = nil; }; person.block(); NSLog(@"211212121122") must be called; } return 0; } // Print the result: 2020-09-28 20:35:32.531704+0800 LearningBlock[41256:310297] Person age 10 2020-09-28 20:35:32.532221+0800 LearningBlock[41256:310297] -[LCPerson dealloc] 2020-09-28 20:35:32.532310+0800 LearningBlock[41256:310297] 211212121122Copy the code

use__blockResolve, must call block, otherwise cannotA circular referenceBroken.

Retained: What is the difference between __weak and __unsafe_unretained?

Using the __weak and __unsafe_unretained keywords both works as a weak reference. The main difference between the two is that a pointer modified with __unretained will be erased (nil) when the object to which it refers is destroyed, while __unsafe_unretained will not be retained.

Solve the circular reference problem – MRC

  • The MRC environment is not__weakKeyword, so can be used__unsafe_unretainedKeyword solved. (Similar to ARC, I won’t show you here)
  • It could also be__blockKeyword solved. As follows:
int main(int argc, const char * argv[]) { @autoreleasepool { __block LCPerson *person = [[LCPerson alloc] init]; person.age = 10; person.block = ^{ NSLog(@"person age %d", person.age); person = nil; }; [person release]; // MRC needs to manually add the memory management code NSLog(@"211212121122"); } return 0; }Copy the code

Unlike ARC, __block is used under MRC to solve the circular reference problem, and block is not required to be called. __block = object type;

The _Block_object_assign function will retain or retain the object’s modifier (__strong, __weak, __unsafe_unretained) to form a strong or weak reference (note: Only retain for ARC but not for MRC)

The latter

This article is a bit of a mess, it needs to be improved. Blogging really takes time, but it’s good to make an impression.

A moment of relief:

reference

  1. MJ underlying principle