As the saying goes: start successful, reach out.

Don’t say much, do something.


1. Basic use

I’ll just paste in a couple of block types

Void (^block1)(void) = ^{}; block1(); Int (^block2)(void) = ^{return 0; }; int res2 = block2(); Void (^block3)(int a) = ^(int b){}; block3(1); Int (^block4)(int a) = ^(int b){return 0; }; int res4 = block4(4);Copy the code

Don’t forget to call.

Of course, we need to use a block as an attribute, so we define a block first:

// define block typedef void (^MyBlock)(int a, int b); // declare @property (nonatomic, copy) MyBlock myBlockOne; // implement self.myBlockOne = ^int(int a, int b) {NSLog(@"%d",a + b); }; // Call self.myblockOne (10, 20);Copy the code

Of course, we can also use block to capture variables, code passing, code inline features, to do chain programming:

#import <UIkit/ uikit. h> typedef id(^transitionItemBlock)(id item); #import <UIkit/ uikit. h> typedef id(^transitionItemBlock)(id item); typedef NSArray *(^transitionArrayBlock)(transitionItemBlock item); @interface NSArray (transitionExtension) @property (nonatomic, copy, readonly) transitionArrayBlock transition; @implementation NSArray (transitionExtension) - (transitionArrayBlock)transition {transitionArrayBlock transition = ^id(transitionItemBlock item) { NSMutableArray *items = [NSMutableArray array]; for (id data in self) { [items addObject:item(data)]; } return items; }; return transition; SetTransition :(transitionArrayBlock)transition {} @end // use - (void)setTransition:(transitionArrayBlock)transition {} @end Array of the dictionary is converted into the corresponding data model NSArray < > NSDictionary * * orginalDatas = @ [@ {...}, @ {...}, @ {...}]. NSArray <Model *> *models = orginalDatas.transition(^id(id item) { return [[Model alloc] initWithDict:item]; });Copy the code

2. Circular references

Consider: Why do circular references occur?

  1. Circular references are generated because AB is held by each other so strongly that they cannot be released. This is essentially:self -> block -> self.someone
  2. So to break circular references we have to break them@ 1: self - > blockStrong holding relationship or break@2: block -> self.someoneOf the strong holding relationship.

How to solve circular reference

But @1 We received a warning when stating the weak keyword: Core block Literal to a weak property; Object will be released after Assignment The object will be freed after assignment. Weak, weak, weak, weak We’ll just have to start with @2 and paste it:

WeakSelf = self; weakSelf = self; weakSelf = self; weakSelf = self; Self. Block = ^{weakself. name = @" "; }; self.block(); Self. block = ^(ViewController *vc) {NSLog(@"%@",vc.name); self.block = ^(ViewController *vc) {NSLog(@"%@",vc.name); }; self.block(self); __block __block ViewController *vc = self; self.block = ^{ NSLog(@"%@",vc.name); vc = nil; }; self.block();Copy the code

In short, no matter how we use it, we find that the code block is communicating with self, so we can also use notifications or proxies.

In the above use, we found that blocks cannot be configured with weak. Does this mean that blocks are also objects? Let’s explore how blocks are implemented inside.


3. Internal structure

The following code is available:

int age = 20;
void (^block)(void) =  ^{
     NSLog(@"age is %d",age);
};     
block();
Copy the code

We execute this command in the current directory of the file: clang -x objective-c -rewrite-objc -isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator SDK XXX. M, We’ll get a.cpp file in the same directory. This part of the CPP file after the above code is compiled:

int age = 20; / / this is the definition of block void block (*) (void) = ((void (*) ()) & __main_block_impl_0 (__main_block_func_0 (void *), &__main_block_desc_0_DATA, age)); / / this is block of call ((void (__block_impl *) (*)) ((__block_impl *) block) - > FuncPtr) ((__block_impl *) block);Copy the code

Here’s what it looks like:

int age = 20; void (*block)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, age ); // This is a call to block block->FuncPtr(block);Copy the code

We found that block is the __main_block_IMPL_0 structure, so our focus is on this structure.

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

Let’s move on to the internal composition of this structure:

  • __block_impl:
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
Copy the code
  • __main_block_desc_0:
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; // struct __main_block_impl_0 size of memory}Copy the code

Construction method;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, struct __main_block_desc_0 *desc, int _age, int flags=0) : Age (_age) {//isa points to _NSConcreteStackBlock indicating that this block is of type impl. Isa = &_NSConcretestackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }Copy the code

A great god drew a picture:

Isa pointer: Points to a class that indicates the block type. Flags: Indicates additional information about blocks in bits, such as determining the block type, determining the block reference count, and determining whether a block needs to execute auxiliary functions. Reserved: Indicates the number of variables inside the block. Invoke: Function pointer to the function call address of a specific block implementation. Descriptor: Additional description of a block, such as the number of reserved variables, the size of the block, and the pointer to the auxiliary function to copy or dispose. Variables: Because blocks have closures, you can access local variables outside the block. These variables are external local variables or the addresses of variables copied into the structure.Copy the code

So you can see it very clearly.

Now that we know the structure of a block, we should analyze how variables are captured inside the block. The following code is available:

int a = 10; self.block = ^{ NSLog(@"%d",a); }; a = 20; self.block(); Output 2021-04-22 17:48:21.546838+0800 newTest[80807:6365980] 10Copy the code

Continue with the.cpp file:

int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;

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

struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_d36452_mi_5);

Copy the code

__main_block_IMPL_0 (age = 20) does not change the value of the block.

Next test the static modified local variable:

static int height = 30; int age = 20; self.block = ^{ NSLog(@"age is %d height = %d",age,height); }; age = 25; height = 35; self.block(); 2021-04-22 18:20:04.942832+0800 newTest[80942:6371469] age = 20 height = 35Copy the code

As you can see, in this case the external value changes the value inside the block. Let’s take a look at the. CPP here:

static int height = 30;
int age = 20;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 25;
height = 35;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
Copy the code

So you can see that when you pass, age is passed directly, height is passed &height.

Finally, let’s look at global variables:

int age1 = 11; static int height1 = 22; int main(int argc, const char * argv[]) { @autoreleasepool { void (^block)(void) = ^{ NSLog(@"age1 = %d height1 = %d",age1,height1); }; age1 = 25; height1 = 35; block(); } return 0; } 2021-04-22 18:20:04.942832+0800 newTest[80942:6371469] age1 = 25 Height1 = 35Copy the code

Same view. CPP:

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_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_4e8c40_mi_4,age1,height1); } 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; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); age1 = 25; height1 = 35; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }Copy the code

As you can see, age1 and Height1 are accessed directly, with no capture at all.

To summarize, local variables are destroyed as they leave scope. If it is passed as a pointer, the variable may already be destroyed by the time it is accessed. The program will go wrong. Global variables are accessible everywhere, so there is no need to capture them.


4. The type of block

To verify that a block is an object, we do the following:

void (^block)(void) = ^{ NSLog(@"123"); }; NSLog(@"block.class = %@",[block class]); NSLog(@"block.class.superclass = %@",[[block class] superclass]); NSLog(@"block.class.superclass.superclass = %@",[[[block class] superclass] superclass]); NSLog(@"block.class.superclass.superclass.superclass = %@",[[[[block class] superclass] superclass] superclass]); The log is:  newTest[18429:234959] block.class = __NSGlobalBlock__ newTest[18429:234959] block.class.superclass = __NSGlobalBlock newTest[18429:234959] block.class.superclass.superclass = NSBlock newTest[18429:234959] block.class.superclass.superclass.superclass = NSObjectCopy the code

And you can see that the root class of the block is also NSObject, just like our other classes.

In fact, there are three types of blocks:

  • __NSGlobalBlock__(_NSConcreteGlobalBlock) : Does not access the auto variable
  • __NSStackBlock__(_NSConcreteStackBlock) : Accesses the auto variable
  • __NSMallocBlock__(_NSConcreteMallocBlock) :__NSStackBlock__Call the copy

And we understood that in the previous exploration.

Next, we discuss block copy in memory in several cases:

  • When block is returned as a function:
// define Block typedef void (^YZBlock)(void); YZBlock myblock() {int a = 6; return ^{ NSLog(@"--------- %d",a); }; } YZBlock Block = myblock(); Block(); NSLog(@" [Block class] = %@", [Block class]); [25857:385868] --------- 6 [25857:385868] [Block class] = __NSMallocBlock__Copy the code

If the above code outputs __NSStackBlock__ under MRC, under ARC, it automatically copies, so __NSMallocBlock__.

  • When assigning a block to a __strong pointer:
// define Block typedef void (^YZBlock)(void); int b = 20; YZBlock Block2 = ^{ NSLog(@"abc %d",b); }; NSLog(@" [Block2 class] = %@", [Block2 class]); Output: [Block2 class] = __NSMallocBlock__Copy the code

If the above code outputs __NSStackBlock__ under MRC, under ARC, it automatically copies, so __NSMallocBlock__.

  • Block is used as a Cocoa API method name containing the usingBlock method argument:

Is the Foundation, apple’s own some methods, such as array traversal enumerateObjectsUsingBlock: this method is also introduced to block, so this is also __NSMallocBlock__ type.

  • Block as a method argument to the GCD API:

Whenever the method argument in GCD is block, it is __NSMallocBlock__.

Block properties under MRC:

  • @property (copy, nonatomic) void (^block)(void);

ARC block property:

  • @property (strong, nonatomic) void (^block)(void);
  • @property (copy, nonatomic) void (^block)(void);

Summary:

  • When the block is__NSStackBlock__When the type is in the stack space, strong references are not made to external objects regardless of whether strong or weak are used outside.
  • When the block is__NSMallocBlock__Type, in heap space, block is internal_Block_object_assignThe strong or weak reference function makes strong or weak references to external objects based on strong or weak.
  • When a block accesses the auto variable of object type internally, there is no strong reference to the auto variable if the block is on the stack. If a block is copied to the heap, the block’s internal copy function is called, which calls the _Block_object_assign function,_Block_object_assignThe function is based on the auto modifier (__strong,__weak,__unsafe_unretained) to form a strong or weak reference.
  • If a block is removed from the heap, its internal dispose function is called, which is called internally_Block_object_disposeThe function,_Block_object_disposeThe function automatically releases the referenced auto variable (release).

5. Say __block

Here’s a quick example:

// define block typedef void (^YZBlock)(void); int age = 10; YZBlock block = ^{ NSLog(@"age = %d", age); }; block(); The output age = 10Copy the code

So what if we want to change age?

There are three ways to modify local variables:

  1. Write as a global variable:
// define block typedef void (^YZBlock)(void); int age = 10; int main(int argc, const char * argv[]) { @autoreleasepool { YZBlock block = ^{ age = 20; NSLog(@" age = %d", age); }; block(); NSLog(@" age = %d", age); } return 0; } output: age = 20 after block internal modification age = 20 after block callCopy the code

Because global variables are accessible everywhere, the memory address of age can be manipulated directly inside a block. After the block is called, the address referenced by the global variable age has been changed to 20, so this is the print above.

  1. Static Modify local variables:
// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { static int age = 10; YZBlock block = ^{ age = 20; NSLog(@" age = %d", age); }; block(); NSLog(@" age = %d", age); } return 0; } age = 20 after block internal modificationCopy the code

Take a look at the.cpp file:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *age = __cself->age; // bound by copy (*age) = 20; NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_5dbaa1_mi_0, (*age)); }Copy the code

When static is applied to a local variable, the block contains an int *age member, which captures the address of age. That way, of course, you can change the local variable age inside the block.

The above two methods can achieve the goal of modifying local variables inside the block, but doing so will result in memory being unable to be freed. Global variables, or static variables, cannot be destroyed in time and will remain in memory. Most of the time, we just need to temporarily use, when not used, can be destroyed, so the third kind, that is today’s protagonist __block grand stage. Look at the code:

typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; YZBlock block = ^{ age = 20; NSLog(@" age = %d",age); }; block(); NSLog(@" age = %d",age); } return 0; } output: age = 20 after block internal modification age = 20 after block callCopy the code

Have a look. The CPP:

struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; __Block_byref_age_0 *age; __Block_byref_age_0 struct *_age flags __main_block_IMPL_0 (void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; //fp is the function address Desc = Desc; }};Copy the code

A closer look at the __Block_byref_age_0 structure shows that the first member variable is an isa pointer and the second isa pointer to itself, __forwarding.

Struct __Block_byref_age_0 {void *__isa; struct __Block_byref_age_0 {void *__isa; //isa pointer __Block_byref_age_0 *__forwarding; // a pointer to itself int __flags; int __size; int age; // Use the value};Copy the code

Take a look at the.cpp of main:

__Block_byref_age_0 __attribute__((__blocks__(byref)))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / this is to simplify the code __Block_byref_age_0 __Block_byref_age_0 age = {0, // assigns to __isa (__Block_byref_age_0 *)&age,// assigns to __forwarding, which is its own pointer 0, // Assign to __flags sizeof(__Block_byref_age_0),// assign to __size 10 // age uses the value}; Block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / 3. YZBlock block = (& __main_block_IMPL_0 (__main_block_func_0, &__main_block_desc_0_DATA, &age, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); // simplify to block->FuncPtr(block);Copy the code

Where the second (__Block_byref_age_0 *)&age in the __Block_byref_age_0 structure is assigned to the second __Block_byref_age_0 in the code structure above *__forwarding, so __forwarding stores Pointers to itself.

__Block_byref_age_0 __Block_byref_age_0 age = {0, // Assign to __isa (__Block_byref_age_0 *)&age,// assign to __forwarding, which is its own pointer 0, // Assign to __flags sizeof(__Block_byref_age_0),// assign to __size 10 // age uses the value}; Struct __Block_byref_age_0 {void *__isa; struct __Block_byref_age_0 {void *__isa; //isa pointer __Block_byref_age_0 *__forwarding; // a pointer to itself int __flags; int __size; int age; // Use the value};Copy the code

Age (age->__forwarding->age));

Summary:

  • __blockCan be used to solve the problem that the value of the auto variable cannot be modified inside the block
  • __blockYou cannot modify global or static variables(static)
  • The compiler will take__blockA variable is wrapped as an object. The call is from__Block_byref_age_0Pointer to find the memory where age is, and then modify the value

6. Memory management

Look at the code:

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

Or look. CPP:

Copy from stack to heap with copy and dispose in __main_block_desc_0 structure.

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

Copy calls __main_block_copy_0, and its internal _Block_object_assign strongly or weakly references the strong or weak modifiers in the code.

Check the __main_block_impl_0:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //strong strong reference 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; }};Copy the code

You can see that the modifier is strong, so it is strongly referenced when _Block_object_assign is called.

  • When a block is on the stack, there is no strong reference to a __block variable.

  • When a block is copied to the heap:

    • The copy function inside the block is called

    • Copy calls the _Block_object_assign function internally

    • The _Block_object_assign function forms a strong reference to the __block variable (retain)

  • When a block is removed from the heap:

    • Dispose function inside the block is called

    • The _Block_object_dispose function is called internally

    • The _Block_object_dispose function automatically releases the referenced __block variable (release)

__blockthe__forwardingPointer:

__forwarding 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; }; Age ->__forwarding->ageCopy the code

Age ->__forwarding->age

This is because if the __block variable is on the stack, it can be accessed directly, but if it has been copied to the heap, it can be accessed on the stack. Therefore, __forwarding should find the address on the heap first, and then value it.

conclusion

  • When blocks are on the stack, there are no strong references to any of them

  • When blocks are copied to the heap, they are handled by the copy function: __block variable (assuming the variable name is a) : _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • Auto variable of the object type (assuming the variable name is p) : _Block_object_assign((void*)& DST ->p, (void*) SRC ->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

  • Dispose (void*) SRC ->a, 8/*BLOCK_FIELD_IS_BYREF*/); dispose(void*) SRC ->a;

  • Auto variable of the object type (suppose the variable name isp) : _Block_object_dispose((void*) SRC ->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

Big guy light spray: finish