This is the 23rd day of my participation in the August More Text Challenge.More challenges in August

Write in front: iOS underlying principle exploration is my usual development and learning in the accumulation of a section of advanced road. Record my continuous exploration of the journey, I hope to be helpful to all readers.Copy the code

The directory is as follows:

  1. IOS underlying principles of alloc exploration
  2. The underlying principles of iOS are explored
  3. The underlying principles of iOS explore the nature of objects & isa’s underlying implementation
  4. Isa-basic Principles of iOS (Part 1)
  5. Isa-basic Principles of iOS (Middle)
  6. Isa-class Basic Principles of iOS Exploration (2)
  7. IOS fundamentals explore the nature of Runtime Runtime & methods
  8. Objc_msgSend: Exploring the underlying principles of iOS
  9. Slow lookups in iOS Runtime
  10. A dynamic approach to iOS fundamentals
  11. The underlying principles of iOS explore the message forwarding process
  12. Dyld (part 1)
  13. IOS Basic Principles of application loading principle dyld (ii)
  14. IOS basic principles explore the loading of classes
  15. The underlying principles of iOS explore the loading of categories
  16. IOS underlying principles to explore the associated object
  17. IOS underlying principle of the wizard KVC exploration
  18. Exploring the underlying principles of iOS: KVO Principles | More challenges in August
  19. Exploring the underlying principles of iOS: Rewritten KVO | More challenges in August
  20. The underlying principles of iOS: Multi-threading | More challenges in August
  21. GCD functions and queues in iOS
  22. GCD principles of iOS (Part 1)
  23. IOS Low-level – What do you know about deadlocks?
  24. IOS Low-level – Singleton destruction is possible?
  25. IOS Low-level – Dispatch Source
  26. IOS bottom – a fence letter blocks the number
  27. IOS low-level – Be there or be Square semaphore
  28. IOS underlying GCD – In and out into a scheduling group
  29. Basic principles of iOS – Basic use of locks
  30. IOS underlying – @synchronized Flow analysis
  31. IOS low-level – The principle of lock exploration
  32. IOS Low-level – allows you to implement a read/write lock
  33. Implementation of Objective-C Block

Summary of the above column

  • Summary of iOS underlying principles of exploration

Sort out the details

  • Summary of iOS development details

preface

In the last article, we covered the basics of blocks (how to define them and how to use them). Today we are going to focus on the interview questions and see what the Block contains, which are details that we don’t pay attention to in normal development. And common interview questions.

Block details you didn’t notice

There are several types of blocks

Let’s print it with the following code:

  • We define a Block with no arguments and no return value:
    void(^myBlock)(void) = ^ () {}; NSLog(@"% @", myBlock); Print content: <__NSGlobalBlock__:0x1043b0160>
Copy the code

This is a global Block, located in the global area, and uses no external variables or only static and global variables inside the Block.

  • We define a Block with no arguments and no return values, and internally print external variables:
    int a = 20;
    void(^myBlock)(void) = ^(){

        NSLog(@"myBlock -- %d", a); }; Print content: <__NSMallocBlock__:0x600003767120>
Copy the code

This is a heap Block, located in the heap area, that uses attributes of a variable or OC inside the Block and assigns values to strongly referenced or copy-modified variables. (It captures external variables and is a strong hold by default; The block holds the memory space of the block implementation part.

  • To build on this, we add an __weak before myBlock:
    int a = 20;
    void(^__weak myBlock)(void) = ^(){

        NSLog(@"myBlock -- %d", a); }; Print content: <__NSStackBlock__:0x7ffeecd3e028>
Copy the code

This is a stack Block, located in the stack area. Like mallocblocks, local variables or OC attributes can be used internally, but cannot be assigned to strongly referenced or copy-modified variables.

Block-reference counting problems

Let’s look at the interview questions:

    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); / / 1

    void(^strongBlock)(void) = ^{ 
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    void(^__weak weakBlock)(void) = ^{ 
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
Copy the code

So let’s analyze, first of all, in the first place that I print, the output is 1, that’s okay;

The second print, what’s going to print on the strongBlock? In this step, the block actually captures objC (the underlying capture of objC actually generates the member variable to hold) and the reference count is +1 (this is the holding of the property of the step). Second, strongBlock is a heap block in the underlying source code, As you can see, when a block captures an external variable, it makes a memory copy, so the reference count has to be +1, so it’s going to be 3;

Third, we have a block with an __weak modifier, which is a stack block and doesn’t do memory copying, so it’s +1;

And then finally, we copied the stack block, put it on top of the heap, so it’s going to +1, which is going to add up to the third part, which is the second part.

Ok, one last look at printing (see here, if you still don’t understand the second line of printing, we’ll look at it in detail in the next section on Block underlying structure) :

1
---3
---4
---5
Copy the code

Block – Memory copy understanding

Let’s look at the interview questions:

    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
  
    id __strong strongBlock = weakBlock;
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
Copy the code

In general, we define an int and print a in __weak weakBlock. Next, we customize a block to convert the weakBlock type. In the next line, We hold a weakBlock by __strongBlock; The BLC invoke is then set to nil, that is, the block pair is set to nil.

If BLC and weakBlock are the same piece of memory space on the stack, BLC ->invoke = nil will be used later. After that, the effect of weakBlock operation will also be achieved, so weakBlock cannot be called in the end.

Next, perform the validation:

It crashed when it was called.

So how not to collapse? We need to do a copy (assign a new copy to the strongBlock) so it’s on the heap (they don’t have the same memory) :

    id __strong strongBlock = [weakBlock copy];
Copy the code

Run it again and it won’t crash.

Block-stack releases differences

The same interview question begins:

- (void)blockDemo {
    NSObject *a = [NSObject alloc];
    void(^__weak weakBlock)(void) = nil;
    {
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"% @" -- -, a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
}
Copy the code

Here, print what?

1 - <__NSStackBlock__: 0x7ffee27d3000> - <__NSStackBlock__: 0x7ffee27d3000>
---(null)
Copy the code

Is it what you think it is?

First of all, the declaration cycle of weakBlock is in our blockDemo method. In our blockDemo method, strongBlock is defined in a code block within the method, and it is meaningless after the complex value is assigned to weakBlock.

Next, let’s make a change (at this point, what will be printed) :

    // remove __weak from here
    void(^strongBlock)(void) = ^{
        NSLog(@"% @" -- -, a);
    };
Copy the code

Print the following:

After printing 1-, it will crash.

So let’s analyze why that is. First, the strongBlock is a heap Block at this point, and its lifetime exists in the code Block inside our method:

    {
        void(^strongBlock)(void) = ^{
            NSLog(@"% @" -- -, a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
Copy the code

WeakBlock and strongBlock point to the same memory space here. So with the breakpoint line, once you get out of the code block, the memory space that strongBlock points to is at the end of its life, it’s reclaimed by the system, so,

Let’s debug and see why __weak can be printed.

These are all stack blocks, and they live in the stack memory space, and the stack frame is in the current function stack frame area. About the stack frame, we in the iOS underlying principle of exploration stage summary to share with you a detailed exploration of the underlying principle of iOS, if necessary, please move to read (parameter into the stack, structure into the stack).

Block – Copy to heap Block

  • Manually copy
  • Block as the return value
  • Modified by strong reference or Copy
  • The system API contains usingBlock

Heap blocks can be distinguished from global blocks by capturing external variables. Heap blocks differ from stack blocks by internally using local variables or attributes, and by not assigning values to strongly referenced or copy-modified variables.

Block circular reference problem

A circular reference

Let’s look at the circular reference problem through a diagram

Circular reference problem legend

  • In normal case, when A holds B, B’s retainCount is +1. When A is released, the dealloc is sent to B. In this case, if B’s retainCount==0, B calls dealloc.

  • If object A and object B hold each other, in this case, neither object A nor object B can call dealloc to send A release signal to each other, which leads to the problem of circular references.

Let’s take one of the most common examples of circular references:

Case 1

typedef void (^myBlock)(void); . @interface ViewController () @property (nonatomic, copy) NSString *name; @property (nonatomic, strong) myBlock block; @end ... - (void)viewDidLoad {

    [super viewDidLoad];
    self.name = @"superman";
    
    self.block = ^{
      
        NSLog(@"% @", self.name); }; self.block(); }... - (void)dealloc {
   
    NSLog(@"Destroy it...");
}
Copy the code

However, we do not raise a circular reference in the following block:

Case 2

    [UIView animateWithDuration:1 animations:^{
        NSLog(@"% @", self.name);
    }];
Copy the code

So what’s the difference between these two points?

Because in example 2, UIView holds the block, not self. In example 1, the block is held by self, and the block captures self during execution. This is the problem of circular references in the circular reference legend.

How to solve

To solve the problem of circular references, you need to break the state of two hairs holding each other.

The most common method is to use __weak:

    __weak __typeof__(self) weakSelf = self;
    self.block = ^{
      
        NSLog(@"% @", weakSelf.name);
    };
    
    self.block();
Copy the code

It was successfully destroyed

To deal with this step is not perfect, for example:

Example 3

    __weak __typeof__(self) weakSelf = self;
    self.block = ^{
              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            NSLog(@"% @", weakSelf.name);
        });
    };
    
    self.block();
Copy the code

When our self is released before the delay function, we print something like this:

Destroy ~ ~ ~ (null)
Copy the code

How do you solve it? Weak_strong_dance strong and weak dance:

    __weak __typeof__(self) weakSelf = self;
    self.block = ^{
        __strong __typeof(weakSelf) strongSelf = weakSelf;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            NSLog(@"% @", strongSelf.name);
        });
    };
    
    self.block();
Copy the code

This fixes the null print above (strongSelf is just a snack variable, scoped as part of the block implementation, released when the code is done, and the state of the circular references holding each other is broken).

Explore other solutions

I like manual gear 🚗

Right now our self and block are holding each other, so we’re wondering if we can manually set self to nil to break this mutual-holding state.

How do you implement this idea? It’s also simple:

    __block UIViewcontroller *vc = self;
    self.block = ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            NSLog(@"% @", vc.name);
            vc = nil;
        });
    };
    
    self.block();
Copy the code

Also perfectly solved (where vc is a temporary variable held against self; Block implementation is its scope until vc is nil, after which self is a normal self, so it can be freed normally).

Parameter transfer mode

Obviously, the circular reference is because inside the block we’re going to use the property of self. In the viewDieLoad method, self is passing in a stack frame, so we’re going to do a communication operation, and since it’s a communication operation, we can do it through proxies, protocols, parameters, notifications, etc. Here we use the parameters:

Example 4

typedef void(^myBlock)(UIViewController *); . self.name = @"superman";
    
    self.block = ^(UIViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            NSLog(@"% @", vc.name);
        });
    };
    
    self.block(self);
Copy the code

Again, the circular reference problem is solved perfectly (blocks can catch external variables, but we pass them by parameters, so there is no catch problem).

Block the interview questions

staticViewController *staticSelf_; ./ / 1
- (void)blockWeak_static {

    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
}

/ / 2
- (void)block_weak_strong {

    __weak typeof(self) weakSelf = self;
    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        weakSelf.doStudent = ^{
            NSLog(@"% @", strongSelf);
        };
       weakSelf.doStudent();
    };
   self.doWork();
}
Copy the code

Q: Do 1 and 2 cause circular references?

When tested, the ViewController will not be released, but will cause a circular reference. According to?


First: __weak We are a weak reference, why do we still have circular reference? Static The global static variable holds the value of weakSelf. WeakSelf holds the value of self.

WeakSelf and self are a mapping relationship; They’re the same memory space; How do you explain that? Self, weakSelf, and staticSelf_ all point to the same memory space. StaticSelf holds the memory space pointed to by self, which is equivalent to holding self.


Second: After our doWork block comes in, strongSelf (stongSelf is a temporary variable) has a hold on weakSelf, and strongSelf’s life cycle is within doWork’s implementation scope. Then the code will execute to weakSelf’s doStudent. In this case doStudent would have a hold on strongSelf, so strongSelf’s retainCount would be +1; StrongSelf’s retainCount does not =0, although outside doWork’s scope strongSelf is released. So it can’t be released;

We’re going to end up, after using strongSelf in doStudent, manually setting strongSelf to nil, and that’s it. Or don’t use strongSelf in doStudent.

conclusion

Today, we explored the classification of blocks in combination with the interview questions, as well as the problems and solutions in the use of Blcok. The focus of this film is a detailed analysis of Block’s interview questions. It is sure that up to now, everyone still has some problems in understanding them. So, the next article, we in-depth Blcok implementation of the source code to analyze, I think that at that time, many of the problems in our minds will lead to the solution. Ok, everyone come on !!!!