preface
In the <
> note :Blocks I write about Blocks that we use in our daily development process. Here’s a deeper look at Blocks’ related implementation.
Convert OC code to C++ structure code
To make it easier to see what’s going on inside the Block, we need to convert the OC code into C++ code with a structure. This is where we need to use the clang-rewrite-objc directive. There are two steps.
- Open the terminal and use the CD command to enter the file directory to be converted. For example, I want to convert the main.m file under the Test project on the desktop. Terminal instructions are similar to those shown below.
- Then execute the following terminal command, clang-rewrite-objc main.m, as shown below.
A C++ executable file with the suffix.cpp will then appear in the current folder. As shown below.
The realization of the Block
First, we write a simple block anonymous function in the main function and call it, as shown below.
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^blk)(void) = ^{printf("Block\n"); }; blk(); }return 0;
}
Copy the code
We then convert mian.m into a C++ file using the clang-rewrite-objc main.m directive. There’s a lot of code in there, so let’s go to the bottom of the file.
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; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");
}
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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
Copy the code
We can see that the block we wrote has been converted to a C++ function, as shown below.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
Copy the code
The argument to a conceptual function **__cself is equivalent to the C++ instance method’s this variable pointing to the instance itself, or the Objective-C instance method’s self variable pointing to the object itself, i.e. ____cself is the variable pointing to the Block value. However, we find that ____cself is not used here. Here we do not do research, we first look at the nature of the argument ____cself**.
struct __main_block_impl_0 *__cself
Copy the code
-
The structure of a Block
We see that the argument **____cself** is a pointer to the __main_block_IMPL_0 structure, as shown below.
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; }};Copy the code
By <<iOS and OS X multithreading and memory management >> we can see what information each of the two member variables contains.
-
A member variable of the Block structure
Let’s start by looking at the structure of the member variable impl (at the top of the.cpp file). As shown below.
struct __block_impl { void *isa; int Flags; int Reserved; Void *FuncPtr; void *FuncPtr; // function pointer};Copy the code
The second member variable Desc stores the area and Block size required for future version upgrades. The details are as follows.
static struct __main_block_desc_0 { size_t reserved; Size_t Block_size; } / / Block sizeCopy the code
-
The structure of the Block
Let’s look at how the ** __main_block_IMPL_0 ** constructor is constructed. The source code called in the main function is shown below.
In order to make it easier for you to understand this code call, the following transformation is performed. BLK is actually a pointer to a TMP structure of type __main_block_IMPL_0.
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
Copy the code
Next we look at the arguments to the constructor of the structure. The first is the **__main_block_desc_0_DATA** argument. We found the assignment in the code. As shown below.
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
Copy the code
With the constructor above, the value of __main_block_IMPL_0 is shown below.
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.Reserved = 0;
impl.FuncPtr = ___main_block_func_0;
Desc = &__main_block_desc_0_DATA;
Copy the code
-
The process of calling a Block
Let’s take a look at the code that uses blocks.
blk();
Copy the code
Find the corresponding code for the.cpp file as shown below.
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
Copy the code
Let’s get rid of the transformation part. After simplifying the code, it looks like this. What does this code mean? This is calling a function using a pointer to a function. As we just showed. As in the previous module, the function pointer to ___main_block_func_0 is assigned to the FuncPtr of the structure. In addition, the required argument to ___main_block_func_0 is of type __main_block_IMPL_0, which is BLK. So there are the following function calls.
(*blk->FuncPtr)(blk);
Copy the code
-
The essence of the Block
At this point we need to come back to the ISA pointer in the __main_block_IMPL_0 structure member variable IMPl.
We know that the ISA pointer is assigned to ** &_NSConcretestackBlock ** in the constructor. As shown in the figure below.
Blocks are objective-C objects. Why do you say that? First let’s look at what objective-C objects are.
In Objective-C, the definition of any class is an object. There is no essential difference between a class and an instance (object) of a class. Any object has an ISA pointer.
Suppose we create an object that looks like this.
@interface MyObject : NSObject
{
int val0;
int val1;
}
@end
Copy the code
The objective-C object based structure should look like this.
struct MyObject
{
Class isa;
int val0;
int val1;
}
Copy the code
The ISA pointer points as follows. For details, see page 98.
_NSConcreteStackBlock corresponds to the class_t struct instance shown above. So a Block is an Objective-C object.
Block intercepts the implementation of automatic variable values
For Block interception of automatic variable values, we have covered this in <
> Note :Blocks, now let’s give an example. Let’s see how the process of capturing automatic variable values is implemented.
int number = 1;
void (^blk)(void) = ^{
printf("value:%d\n",number);
};
number = 3;
blk();
Copy the code
Run the program. Print the result as shown below.
Compile to C++ files with the clang-rewrite-objc main.m directive. The core code is as follows.
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; int number; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : number(_number) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int number = __cself->number; // bound by copyprintf("value:%d\n",number);
}
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;
int number = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number));
number = 3;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
Copy the code
Let’s take the structure of the Block out and look at it. We find that a new member variable number has been added and that the constructor has added an assignment to number.
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int number; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : number(_number) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
Then look at the construction of the __main_block_IMPL_0 constructor in main.
int number = 1;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number));
Copy the code
The __main_block_IMPL_0 structure has stored the value of number in its member variable number, so that the number printed by the Block does not change after the construction is complete.
Why can’t you change the value of a variable directly in a Block? For example, it is shown in the picture below.
Why is that? Let’s look at the implementation of the **__main_block_func_0 function, as shown below. We know that we are passing the value of a member variable of the __main_block_IMPL_0 ** structure. Instead of a pointer, it has nothing to do with the original number variable. So we can’t change the number variable in the function directly.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int number = __cself->number; // bound by copy
printf("value:%d\n",number);
}
Copy the code
An implementation of the __block specifier
The root cause of assigning a variable to a block is that the block structure passes a variable value instead of a pointer. The **__block specifier ** appears. Let’s take a look at the C code, as shown below.
__block int number = 1;
void (^blk)(void) = ^{
printf("value:%d\n",number);
number = 6;
};
blk();
Copy the code
But the C++ code transformed by the clang-rewrite-objc main.m directive has changed a lot. The core code is shown below.
Struct __Block_byref_number_0 {void *__isa; struct __Block_byref_number_0 {void *__isa; __Block_byref_number_0 *__forwarding; int __flags; int __size; int number; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_number_0 *number; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_number_0 *_number, int flags=0) : number(_number->__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_number_0 *number = __cself->number; // bound by refprintf("value:%d\n",(number->__forwarding->number)); (number->__forwarding->number) = 6; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->number, (void*)src->number, 8/*BLOCK_FIELD_IS_BYREF*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->number, 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_number_0 number = {(void*)0,(__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 1}; void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_number_0 *)&number, 570425344)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); }return 0;
}
Copy the code
Int number = 1; int number = 1; __block int number = 1; After that, the C++ code looks like this. The amount of code is not doubled or tripled
struct __Block_byref_number_0 { void *__isa; __Block_byref_number_0 *__forwarding; // a pointer to itself int __flags; int __size; int number; };Copy the code
Then let’s look at the construction code in the main function. As shown below.
__attribute__((__blocks__(byref))) __Block_byref_number_0 number = {(void*)0,(__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 1};
Copy the code
After simplifying the code, this is shown below.
__Block_byref_number_0 number = {
0,
&number,
0,
sizeof(__Block_byref_number_0),
1
};
Copy the code
The constructors and new member variables of the Block structure also change. The member variable becomes a structure pointing to the **__Block_byref_number_0** type.
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_number_0 *number; // Add member variable __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_number_0 *_number, int flags=0) : number(_number->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
So what happens when you do an assignment in a block? This is done primarily through the **__Block_byref_number_0 member variable __forwarding, which is a pointer to itself. We can find the value of the member variable number by __forwarding. So in the __main_block_func_0** function implementation there is the following code.
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_number_0 *number = __cself->number; // bound by refprintf("value:%d\n",(number->__forwarding->number));
(number->__forwarding->number) = 6;
}
Copy the code
For the __forwarding pointer in the __Block_byref_number_0 structure, we can look at the following diagram.
Block storage domain
From the following table we can see that Block and __block variables are stored on the stack as struct type automatic variables (in general).
The name of the | The essence |
---|---|
Block | Struct instance of Block on stack |
__block | Struct instance of a __block variable on the stack |
Let’s look at the ISA pointer to the Block structure. In the previous example, the ISA pointer points to _NSConcreteStackBlock. There are many other classes like this. Let’s start with a table to illustrate the differences between each class
class | Sets the storage domain of the object | The configuration storage domain of the copy source | Print effect |
---|---|---|---|
_NSConcreteStackBlock | The stack | The stack | Copy from stack to heap |
_NSConcreteMallocBlock | The heap | The heap | Reference count increment |
_NSConcreteGlobaBlock | The global area | The global area | Do nothing |
From the table above, we can know the answers to two interview questions.
Q: How many classes are there for blocks? A: three types: _NSConcreteStackBlock, _NSConcreteMallocBlock, _NSConcreteGlobaBlock
Q: Why is Block decorated with copy? A: You should use copy when defining a block as an attribute. Blocks are usually stored on the stack (or globally). The block in the stack is freed when it is out of scope, and if we continue calling after the block is freed, crash will occur. In theory, we don’t need to copy blocks in the global area. However, most blocks are stored on the stack, so we use copy to modify the block property for standardized management.
The __block variable stores fields
The previous module explained blocks, so what effect does a __block variable have on a Block being copied from the stack to the heap?
Configure storage domains for __block variables | The effect of a Block being copied from the stack to the heap |
---|---|
The stack | Copied from the stack to the heap and held by the Block |
The heap | Be Block to retain |
What does the table above mean? In other words:
- If a Block uses a __block variable, the __block variable is copied from the stack to the heap and held by the Block.
- If multiple blocks use a __block variable, then in the first Block the __block variable is copied from the stack to the heap and held by the first Block. The second Block holds the __block variable, which only increases the reference count of the __block variable.
For the **__forwarding pointer **(a pointer to itself), we said,” The __block variable can be accessed correctly whether it is configured on the stack or heap.” We can use the following example to illustrate the situation.
__block int val = 0;
void (^blk)(void) = [^{ ++val; } copy];
++val;
blk();
NSLog(@"%d",val);
Copy the code
By BLK Block copy, the __block modified val variable is successfully copied from the stack to the heap.
So ^ {+ + val. } and + + val. Can be converted to the following form.
++(val.__forwarding->val);
Copy the code
The above transformation process can be illustrated in the diagram below.
The implementation of intercepting objects
We’ve talked about intercepting variable values, now let’s talk about the implementation of intercepting objects. The demo source code is shown below.
void (^blk)(id obj); {// Array scope ID array = [[NSMutableArray alloc] init]; blk = [^(id obj){ [array addObject:obj]; NSLog(@"array count = %ld",[array count]); } copy]; }// Array is scoped out BLK ([NSObject new]); blk([NSObject new]); blk([NSObject new]);Copy the code
We know that array is out of scope (by the time we reach the comment position), but we can still access array by calling block. As shown below, why is this?
In fact, in the implementation of BLK. <<iOS and OS X multithreading and memory management >> there is the following code.
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id __strong array; // strongly referenced array member variables};Copy the code
In Objective-C,C constructs cannot contain variables with __strong modifiers. Because the compiler does not know when C constructs should be initialized and discarded. Does not manage memory very well. The Objective-C runtime library is good at timing when blocks are copied from the stack to the heap and blocks on the heap are discarded. This effectively manages the holding and release of member variables. To do this, __main_block_desc_0 adds two member variables copy and dispose, which have corresponding functions. Used for holding and releasing member variables. As shown in the figure below.
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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
However, I have no **__strong** modifier in the actual process. My guess is that the default has already been done. The __strong modifier is omitted. The source code screenshot is shown below. You can experiment with it yourself.
The nature of circular references
We talked about that in the last module. Blocks can hold objects. If an object contains a Block member attribute (strong modifier). __main_block_IMPL_0 ** in the ** __main_block_IMPL_0 structure is a strong reference to obj,self is a strong reference to the Block variable, and the two reference each other, resulting in a circular reference To use.
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id __strong obj; // strongly referenced obj member variable};Copy the code
The schematic diagram is shown below.
The end of the
The implementation of this Block took three days to write, plus their own verification, a lot of sales, I hope this blog is helpful to everyone. Or I hope we have a look at iOS and OS X multithreading and memory management >> the original book, their own knock over the implementation of the source code, so help a lot, will deepen the impression. Finally, thank you for reading this article. If you have any questions, please contact SAO Dong. Welcome to instruct and criticize.
<<iOS and OS X multithreading and Memory management >> PDF version of portal 🚪