iOS Advanced——Block

iOS Advanced——Block

Take a few minutes to look at the following three questions and write down your answers.

I gave these three small questions to three friends to answer before compiling this blog post. The result was that except for one friend who answered all 3 questions correctly, the other two friends only answered 1 question each.

This shows that many iOS users still do not have a thorough understanding of Blocks. This blog post will explain Blocks in detail.

1 Simple rules for Block usage

First understand the simple rules, then analyze the principles and implementation:

In a Block, the Block expression captures the value of the automatic variable used, that is, saves the instantaneous value of the automatic variable.

When a variable modified as __block is captured, the instantaneous value is no longer obtained.

As for why, I will continue to talk about it later.

2 Block Implementation

Block is an anonymous function with automatic variables (local variables).

Block expressions are very simple and can be generally described as: "^ return value type parameter list expression".

But Block is not a syntax unique to Objective-C. What’s going on?

The clang compiler provides programmers with a way to understand the mechanism behind Objective-C. Through clang's conversion, you can see the implementation principle of Block.

By clang -rewrite-objc yourfile.m clang will convert the Objective-C code into C language code.

2.1 Analysis of basic implementation of Block

Use Xcode to create a Command Line project and write the following code:

  1. int main( int argc, const char * argv[]) {
  2.  
  3. void (^blk)(void) = ^{NSLog(@ "Block" )};
  4.  
  5. blk();
  6.  
  7. return 0;
  8.  
  9. }

Convert with clang:

The above is the converted code. Don’t be confused, read it section by section.

It can be seen that the content inside the Block has been converted into a common static function __main_func_0.

Look at other parts:

main.cpp __block_impl:

  1. struct __block_impl {
  2.  
  3. void *isa;
  4.  
  5. int Flags;
  6.  
  7. int Reserved;
  8.  
  9. void *FuncPtr;
  10.  
  11. };

The __block_impl structure includes some flags, variables reserved for future version upgrades, and function pointers.

main.cpp __main_block_desc_0:

  1. static struct __main_block_desc_0 {
  2.  
  3. size_t reserved;
  4.  
  5. size_t Block_size;
  6.  
  7. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

The __main_block_desc_0 structure includes variables and block sizes reserved for future version upgrades.

main.cpp __main_block_impl_0:

The __main_block_impl_0 structure contains two member variables, namely the __block_impl and __main_block_desc_0 instance variables.

In addition, there is a constructor. The constructor is called in the main function as follows:

main.cpp __main_block_impl_0 constructor call:

  1. void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
  2.  
  3. &__main_block_desc_0_DATA));

Remove all kinds of forced conversions and simplify:

main.cpp __main_block_impl_0 The constructor call is simplified:

  1. struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
  2.  
  3. struct __main_block_impl_0 *blk = &tmp;

The above code assigns the pointer of the __main_block_impl_0 structure instance to the variable blk of the __main_block_impl_0 structure pointer type. This is our initial structure definition:

  1. void (^blk)(void) = ^{NSLog(@ "Block" );};

In addition, there is another section in the main function:

  1. ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

Remove various conversions:

  1. (*blk->impl.FuncPtr)(blk);

Actually, it is the original:

  1. blk();

All the code in this section is in block_implementation (https://github.com/summertian4/iOS-ObjectiveC/tree/master/ObjcMemory/ObjcMemory-Test-Code/block_implementation)

2.2 Analysis of the implementation of Block intercepting the instantaneous value of external variables

In 2.1, clang conversion was performed on the simplest parameterless block declaration and call. Next, let's look at a section of code for "intercepting automatic variables" (you can use the command clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.7 main.m):

  1. int main( int argc, const char * argv[]) {  
  2.   
  3.  
  4. int val = 10;
  5.  
  6. const char *fmt = "val = %d\n" ;
  7.  
  8. void (^blk)(void) = ^{printf(fmt, val);};  
  9.   
  10.  
  11. val = 2;
  12.  
  13. fmt = "These values ​​were changed, val = %d\n" ;  
  14.   
  15.  
  16. blk();  
  17.   
  18.  
  19. return 0;
  20.  
  21. }

After clang conversion:

Compared with the conversion code in Section 2.1, we can find that there are some more codes.

First, __main_block_impl_0 adds a variable val and adds the value of val to the constructor's parameters:

main.cpp __main_block_impl_0:

  1. struct __main_block_impl_0 {
  2.  
  3. struct __block_impl impl;
  4.  
  5. struct __main_block_desc_0* Desc ;
  6.  
  7. const char *fmt;
  8.  
  9. int val;
  10.  
  11. __main_block_impl_0(void *fp, struct __main_block_desc_0 * desc , const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
  12.  
  13. impl.isa = &_NSConcreteStackBlock;
  14.  
  15. impl.Flags = flags;
  16.  
  17. impl.FuncPtr = fp;
  18.  
  19. Desc = desc ;
  20.  
  21. }
  22.  
  23. };

In the main function, the declaration of Block becomes this sentence:

main.cpp __main_block_impl_0 constructor call:

  1. void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

Remove the conversion:

main.cpp __main_block_impl_0 The constructor call is simplified:

  1. struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, val);
  2.  
  3. struct __main_block_impl_0 *blk = &tmp;

_Therefore, when Block is declared, Block has saved val as an internal variable of __main_block_impl_0. No matter how the value of val is changed after the declaration, it will not affect the internal value of val accessed when Block is called. This is the principle of Block capturing the instantaneous value of the variable. _

All the codes in this section are in EX05

2.3 Analysis of __block variable access implementation

We know that a local variable can be read but not changed in a Block. If you try to change it, Xcode will prompt you that you cannot change the variable inside a Block.

The local variables in a Block are read-only, but the Block can read and write the following variables:

  1. Static variables
  2. Static global variables
  3. Global variables

This means that the following code is fine:

  1. int global_val = 1;
  2.  
  3. static   int static_global_val = 2;
  4.   
  5.  
  6. int main( int argc, const char * argv[]) {
  7.  
  8. static   int static_val = 3;
  9.   
  10.  
  11. void (^blk)(void) = ^ {
  12.  
  13. global_val = 1 * 2;
  14.  
  15. static_global_val = 2 * 2;
  16.  
  17. static_val = 3 * 2;
  18.  
  19. }
  20.  
  21. return 0;
  22.  
  23. }

If you want to write local variables inside a Block, you need to add the __block modifier to the local variables you want to access.

The __block modifier is similar to the static, auto, and register modifiers in C. It is used to specify the storage domain where the variable value is set.

We can write code to test what changes are made after __block:

EX07:

  1. int main( int argc, const char * argv[]) {  
  2.   
  3.  
  4. __block int val = 10;
  5.  
  6. void (^blk)(void) = ^{val = 1;};  
  7.   
  8.  
  9. return 0;
  10.  
  11. }

After clang conversion:

Compared with 2.2, it seems that a lot of code has been added. Two more structures have been found.

main.cpp __Block_byref_val_0:

  1. struct __Block_byref_val_0 {
  2.  
  3. void *__isa;
  4.  
  5. __Block_byref_val_0 *__forwarding;
  6.  
  7. int __flags;
  8.  
  9. int __size;
  10.  
  11. int val;
  12.  
  13. };

I was surprised to find that the val of block type became an instance of the structure Block_byref_val_0. This instance contains the isa pointer, a flag, and a size. Most importantly, there is an additional forwarding pointer and val variable. What's going on?

In the main function, the structure is instantiated:

main.cpp main.m part:

  1. __Block_byref_val_0 val = {(void*)0,
  2.  
  3. (__Block_byref_val_0 *)&val,
  4.  
  5. 0,
  6.  
  7. sizeof(__Block_byref_val_0),
  8.  
  9. 10};

We can see that when the structure object is initialized:

  1. __forwarding points to the address of the structure instance itself in memory
  2. val = 10

In the main function, the assignment statement val = 1 becomes:

main.cpp val = 1; corresponding function

  1. (val->__forwarding->val) = 1;

The essence can be seen here, val = 1, actually changes the val variable pointed to by the __forwarding pointer (that is, itself) in the __Block_byref_val_0 structure instance val.

The same is true for accessing val. You can think of it as changing the value of a variable by taking its address, which is similar to changing a variable by taking its address in C language.

Therefore, the variables in the declaration block can be changed. As for the other great effects of forwarding, we will continue to analyze them.

This section of the code is in EX05

3 Block storage domain

There are three types of Blocks:

  1. __NSConcreteStackBlock ————————In the stack
  2. __NSConcreteGlobalBlock ————————In the data area
  3. __NSConcreteMallocBlock ————————In the heap

__NSConcreteGlobalBlock appears in:

  1. When there is a Block syntax in the place where global variables are set
  2. When no external variables are used in the expression of the block syntax

If the scope of the variable to which the block is set on the stack ends, the block will be discarded. If the block is used in it, the variable scope to which the block belongs will also be discarded when the scope ends.

To solve this problem, the Block needs to be moved from the stack to the heap when necessary. When ARC is effective, in many cases, the compiler will help complete the copy of the Block, but in many cases, we need to copy the Block manually.

When copying blocks in different storage domains, the impact is as follows:

When copying, the impact on the accessed __block type object is as follows:

At this point, we can see the great role of __forwarding - no matter whether the Block is in the heap or in the stack, since __forwarding points to the real address of the structure instance converted from the local variable, correct access can be ensured.

Specifically:

  1. When a block variable is used by a Block, the Block is copied from the stack to the heap, and the block variable is also copied and held by the Block.
  2. When a block variable is used by multiple blocks, when any block is copied from the stack to the heap, the block variable will also be copied to the heap and held by the block. However, due to the existence of the __forwarding pointer, the block variable can be correctly accessed regardless of whether the block variable and the block are in the same storage domain.
  3. If the Block on the heap is discarded, the __block variable it uses will also be released.

As mentioned above, the compiler will help complete some block copying, and there are also manual block copying. Then the block is copied to the heap in the following situations (this paragraph is excerpted from "Objective-C Advanced Programming iOS and OS X Multithreading and Memory Management"):

  1. When calling the copy method of Block
  2. When Block is used as the return value
  3. When assigning a Block to a member variable with the __strong modifier (id type or Block type)
  4. When passing a Block in a Cocoa framework method or GCD API that contains usingBlock in the method name

4 Block Circular Reference

Block circular reference is a very common problem in programming. Many times, we don’t even know that a circular reference has occurred until we suddenly find out one day that “this object does not call delloc” and then realize that there is a problem.

It is also explained in "Block Storage Domain" that Block will retain the __block object once after copying it.

Then a circular reference will occur in the following situations:

  1. block_retain_cycle:
  2.  
  3.  
  4. @interface MyObject : NSObject  
  5.   
  6.  
  7. @property (nonatomic, copy) blk_t blk;
  8.  
  9. @property (nonatomic, strong) NSObject *obj;  
  10.   
  11.  
  12. @ end    
  13.   
  14.  
  15. @implementation MyObject  
  16.   
  17.  
  18. - (instancetype)init {
  19.  
  20. self = [super init];
  21.  
  22. _blk = ^{NSLog(@ "self = %@" , self);};
  23.  
  24. return self;
  25.  
  26. }
  27.  
  28.   
  29.  
  30. - (void)dealloc {
  31.  
  32. NSLog(@ "%@ dealloc" , self.class);
  33.  
  34. }  
  35.   
  36.  
  37. @ end    
  38.   
  39.  
  40. int main( int argc, const char * argv[]) {
  41.  
  42. id myobj = [[MyObject alloc] init];
  43.  
  44. NSLog(@ "%@" , myobj);
  45.  
  46. return 0;
  47.  
  48. }

Since self -> blk, blk -> self, neither side can be released.

But please note that circular references can also occur in the following cases:

  1. block_retain_cycle
  2.   
  3.  
  4. @interface MyObject : NSObject
  5.   
  6.  
  7. @property (nonatomic, copy) blk_t blk;
  8.   
  9.  
  10. // The following is an additional sentence
  11.  
  12. @property (nonatomic, strong) NSObject *obj;
  13.   
  14.  
  15. @ end  
  16.   
  17.  
  18. @implementation MyObject
  19.   
  20.  
  21. - (instancetype)init {
  22.  
  23. self = [super init];
  24.   
  25.  
  26. // The following is an additional sentence
  27.  
  28. _blk = ^{NSLog(@ "self = %@" , _obj);};
  29.   
  30.  
  31. return self;
  32.  
  33. }
  34.   
  35.  
  36. - (void)dealloc {
  37.  
  38. NSLog(@ "%@ dealloc" , self.class);
  39.  
  40. }
  41.   
  42.  
  43. @ end  
  44.   
  45.  
  46. int main( int argc, const char * argv[]) {
  47.  
  48. id myobj = [[MyObject alloc] init];
  49.  
  50. NSLog(@ "%@" , myobj);
  51.  
  52. return 0;
  53.  
  54. }

This is due to self -> obj, self -> blk, blk -> obj. This situation is very easy to be overlooked.

5 Review Questions

Let’s look at the first few small topics:

***question:

Since the Block captures the instantaneous value, the output is in block val = 0

Question 2:

Since val is __block, external changes will affect internal access, so the output is in block val = 1

Question 3:

Similar to the second question, val = 1 can affect the internal access of the Block, so first output in block val = 1, then change the val value inside the Block, and output after block val = 2 when accessing again.

Other

I wrote this article after reading the book "Objective-C Advanced Programming iOS and OS X Multithreading and Memory Management". A lot of the content in this blog post is also derived from "Objective-C Advanced Programming iOS and OS X Multithreading and Memory Management".

I highly recommend this book to everyone. This book records in-depth content about iOS memory management. But please note that many knowledge points in this book are not very detailed, and you need to learn with an open mind. In places where the explanation is not detailed, take the initiative to explore, expand, and find more information. Finally, you will find that you have a deeper understanding of iOS memory management.

For the test code in the article, all are at (https://github.com/summertian4/iOS-ObjectiveC/tree/master/ObjcMemory).

<<:  10 Practical Tips for iOS (There are always things you don’t know and things you will use)

>>:  iOS Advanced - iOS Memory Management

Recommend

How attractive are nuclear-powered rockets?

With the continuous advancement of manned space t...

The most effective content marketing promotion skills and methods!

01. The History of Content Marketing The term con...

What should people who cannot gain weight do? Huaxi nutrition experts say...

After a Spring Festival, Looking at the flesh on ...

How to screen high-quality APP promotion channels?

With the rapid development of the mobile Internet...

Zhao Lei uses lyrics to teach you how to write copy!

There is a saying in the folk music circle: It wo...

How to build a user data system from 0 to 1?

We have already entered the era of big data. The ...

Four marketing techniques for jewelry industry on Xiaohongshu

Some time ago, a friend of mine on Xiaohongshu to...