1. Block classification

  • Block classification

    Blocks come in three types:

    • __NSGlobalBlock__globalblock
    • __NSStackBlock__The stack areablock
    • __NSMallocBlock__The heap areablock

Block in MRC and ARC environments:

  • MRC environment

    Introduce the following example:

        void(^block)(void);
        int aa = 0;
    
        NSLog(@"\n********A**********");
    
        void (^blockA)(void) = ^{
        };
        NSLog(@"blockA is : %@ -----retainCount : %lu", blockA, [blockA retainCount]);
        block = [blockA copy];
        NSLog(@"copy blockA is : %@ -----retainCount : %lu",  block, [blockA retainCount]);
    
        NSLog(@"\n********B**********");
    
        void (^blockB)(void) = ^{
            NSLog(@"hello aa %d", aa);
        };
        NSLog(@"blockB is : %@ -----retainCount : %lu", blockB, [blockB retainCount]);
        block = [blockB copy];
        NSLog(@"copy blockB is : %@ -----retainCount : %lu",  block, [block retainCount]);
    Copy the code

    Running results:

    As you can see, blockA differs from blockB only in whether or not external variables are called, which results in different types and storage locations.

    • NSGlobalBlock

      If no external variable is referenced inside a block, the block type is NSGlobalBlock, which is stored in the global data area and managed by the system. Retain, copy, and release operations are invalid. This type is also used if external static or global variables are accessed.

    • NSStackBlock

      The internal block refers to external variables. Retain and release operations are invalid and stored in the stack. When the scope of the variable ends, it is automatically released and destroyed by the system.

    • NSMallocBlock

      After the [blockB copy] operation in the above example, the variable type is changed to NSMallocBlock, which supports retain and release. Although retainCount is always 1, the memory manager still increases and decreases the count, and releases it when the reference count is zero.

  • The ARC environment

    Introduce the following example:

        void(^block)(void);
        int aa = 0;
    
        NSLog(@"\n********A**********");
    
        void (^blockA)(void) = ^{
        };
        NSLog(@"blockA is : %@ -----retainCount : %lu", blockA, CFGetRetainCount(((__bridge CFTypeRef)blockA)));
        block = [blockA copy];
        NSLog(@"copy blockA is : %@ -----retainCount : %lu",  block, CFGetRetainCount(((__bridge CFTypeRef)block)));
    
        NSLog(@"\n********B**********");
    
        void (^blockB)(void) = ^{
            NSLog(@"hello aa %d", aa);
        };
        NSLog(@"blockB is : %@ -----retainCount : %lu", blockB, CFGetRetainCount(((__bridge CFTypeRef)blockB)));
        block = [blockB copy];
        NSLog(@"copy blockB is : %@ -----retainCount : %lu",  block, CFGetRetainCount(((__bridge CFTypeRef)block)));
    
        NSLog(@"\n********C**********");
    
        void (^__weak blockC)(void) = ^{
            NSLog(@"hello aa %d", aa);
        };
        NSLog(@"blockC is : %@ -----retainCount : %lu", blockC, CFGetRetainCount(((__bridge CFTypeRef)blockC)));
        block = [blockB copy];
        NSLog(@"copy blockC is : %@ -----retainCount : %lu",  block, CFGetRetainCount(((__bridge CFTypeRef)block)));
    Copy the code

    Running results:

    We find that blockB is of type NSStackBlock under MRC and type NSMallocBlock under ARC.

    • NSGlobalBlock

      In this case, as in MRC, blocks that do not refer to external variables are of type NSGlobalBlock, stored in the global data area and managed by the system. If external static or global variables are accessed, this type is also used.

    • NSStackBlock

      An external variable is accessed, but there is no strong reference to the block, such as a block printed directly

    • NSMallocBlock

      An ARC environment automatically copies the __NSStackBlock type to the heap whenever an external variable is accessed and there is a strong reference to the block(or as a function return value)

2. Impact of block memory

  • Case study of capturing external variable reference counts

    To introduce a case:

        NSObject * objc = [NSObject alloc];
        NSLog(@"objc -----retainCount : %lu", CFGetRetainCount(((__bridge CFTypeRef)objc)));
    
        void (^__weak blockA)(void) = ^{
            NSLog(@"A objc -----retainCount : %lu", CFGetRetainCount(((__bridge CFTypeRef)objc)));
        };
        blockA();
    
        void (^blockB)(void) = ^{
            NSLog(@"B objc -----retainCount : %lu", CFGetRetainCount(((__bridge CFTypeRef)objc)));
        };
        blockB();
    Copy the code

    Running results:

    The ARC environment, in which two blocks are defined, tracks objC’s reference count changes. Let’s analyze the results:

    • First of all,objcAfter the object is created, callCFGetRetainCountFunction gets the reference count, which should be1;
    • blockAfor__weakSo there’s no strong reference to thisblockAnd access external variables, so yesNSStackBlockBecause theblockAThe variableobjcIs captured and created within its structureobjcAt this point the reference count should become2;
    • blockBIs aNSMallocBlock.ARCEnvironment as long as the external variable is accessed and there is a strong reference to that variableblockIt will automatically__NSStackBlocktypecopyTo the heap, so the process is done onceblockthecopy, so this place reference count plus2to4.
  • Stack releases difference 1

    Introduce the following case, whether it works properly:

    - (void)blockDemo{
        int a = 0;
        void(^__weak weakBlock)(void) = nil;
        {
            void(^__weak strongBlock)(void) = ^{
                NSLog(@"---%d", a);
            };
    
            weakBlock = strongBlock;
            NSLog(@"1 - %@ - %@", weakBlock,strongBlock);
        }
        weakBlock();
    }
    Copy the code

    The running result is shown in the following figure:

    Results analysis:

    • Declared aweakBlocktheblockforNSStackBlock;
    • In the code block, thestrongBlock, also for itNSStackBlock;
    • rightweakBlockI’m doing an assignment, and now I have twoblockThey all point to the same thingNSStackBlock;
    • Because these two stacksblockTo the life cycle ofblockDemoThe method is not released early when it finishes running.
    • So callweakBlock()It runs normally and can outputaThe value of the.
  • Stack releases difference 2

    Modify the above example:

    - (void)blockDemo{
        int a = 0;
        void(^__weak weakBlock)(void) = nil;
        {
            void(^strongBlock)(void) = ^{
                NSLog(@"---%d", a);
            };
            weakBlock = strongBlock;
            NSLog(@"1 - %@ - %@", weakBlock,strongBlock);
        }
        weakBlock();
    }
    Copy the code

    StrongBlock changed to NSMallocBlock, nothing else changed. What happens when you run it? Run the program:

    Run error, why? Analysis based on LLDB debugging results:

    • In the code blockstrongBlockforNSMallocBlock, its lifecycle ranges within the code block{}Inside, that is, when a block of code comes out it gets released;
    • In the code block rightweakBlockIt’s assigned, it’s copied, it’s pointing to the correspondingNSMallocBlockBut there is no strong reference to thisblock;
    • After the code block is executed, theNSMallocBlockWill be released, at this pointweakBlockThe object to which the pointer was pointed has been freed, forming a wild pointer, so it cannot execute properly.

    If __weak is removed from both weakBlock and NSMallocBlock, it will work because weakBlock makes a strong reference to the block in the heap when weakBlock is assigned. Therefore, the code block will not be released after running, and can run normally, as shown in the following figure:

3. Block circular reference

Cause the premise of circular quote – you have me, I have you!!

Normally, hold release follows the logic shown below:

However, in the case of causing a loop, the normal release cannot be completed, resulting in a memory leak. See below:

  • Examples of circular references

    typedef void(^TBlock)(void); @interface ViewController () @property (nonatomic, strong) TBlock block; @property (nonatomic, copy) NSString *name; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // loop with self.name = @"Hello"; self.block = ^(){ NSLog(@"%@", self.name); }; self.block(); }Copy the code

    Here self holds the block, and the block holds self, causing a circular reference. Xcode also gives a hint at compile time that capturing self strongly in this block can cause looping.

  • Matters needing attention

    The following code does not cause a circular reference, because currently self, the ViewController, is not holding the block strongly, and the lifetime of the block is only in viewDidLoad, and when viewDidLoad is done, the block is released.

    - (void)viewDidLoad { [super viewDidLoad]; // loop with self.name = @"Hello"; void(^block)(void); block = ^(){ NSLog(@"%@", self.name); }; block(); }Copy the code

1. Circular reference solutions

How to solve the circular reference problem? The most common and most familiar is _weak. The fundamental reason __weak works against circular references is that __weak does not operate on reference counting. Weak implementation principle View article: Weak implementation principle and destruction process

  • The use of the weak

    __weak typeof(self) weakSelf = self;
    Copy the code

    This is the way we usually do it. The above example can be modified to the following code:

    // loop with self.name = @"Hello"; __weak typeof(self) weakSelf = self; self.block = ^(){ NSLog(@"%@", weakSelf.name); }; self.block();Copy the code

    Because self holds the block, the block weakly references self, the weak reference automatically becomes nil, the strong hold breaks, so it doesn’t cause a circular reference.

    But there are some problems with this approach, as shown below:

    If the ViewController is destroyed, the block will not be able to obtain the ViewController property name. How to solve this problem?

  • Strength dance

    To improve on the above example:

        self.name = @"Hello";
    
        __weak typeof(self) weakSelf = self;
        self.block = ^(){
            __strong __typeof(weakSelf)strongWeak = weakSelf;
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"%@", strongWeak.name);
            });
        };
    
        self.block();
    Copy the code

    The running result is shown in the following figure:

    You can see from the results that the dealloc method is called only after the action in the block has been completed. After strongWeak is added, the holding relationship is: Self – > block – > strongWeak – > weakSelf – > self, weakSelf was strong application will not automatically release, why can release here, because strongWeak is just a temporary variable, only its statement cycle inside the block, When the block completes, strongWeak is released, and weakSelf is released automatically.

  • Manually disconnects the holding relationship

    To resolve the circular reference problem, manually break the holding relationship, see the following code:

        self.name = @"Hello";
    
        __block ViewController * ctrl = self;
        self.block = ^(){
            __strong __typeof(weakSelf)strongWeak = weakSelf;
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"%@", ctrl.name);
                ctrl = nil;
            });
        };
        self.block();
    Copy the code

    The running result is shown in the following figure:

    After using CTRL, the holding relationship is: self -> block -> CTRL -> self. CTRL is empty after the block is used.

  • Block mass participation

    Block pass to solve the circular reference problem, see the following code:

    // loop with self.name = @"Hello"; self.block = ^(ViewController * ctrl){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@", ctrl.name); }); }; self.block(self);Copy the code

    Self is taken as an argument to the block, the pointer is copied, and self is not held.

2. Block circulates reference cases

  • Static variable holding

    // staticSelf_ Definition: static ViewController *staticSelf_; - (void)blockWeak_static { __weak typeof(self) weakSelf = self; staticSelf_ = weakSelf; }Copy the code

    Does it lead to circular references? Will! WeakSelf is a weak reference, but staticSelf_ is a static variable, and weakSelf is held. StaticSelf_ cannot be released, so weakSelf cannot be released. Lead to circular references!

  • __strong holding problem

    - (void)block_weak_strong {
    
        __weak typeof(self) weakSelf = self;
    
        self.doWork = ^{
            __strong typeof(self) strongSelf = weakSelf;
            NSLog(@"B objc -----retainCount : %lu", CFGetRetainCount(((__bridge CFTypeRef)strongSelf)));
    
            weakSelf.doStudent = ^{
                NSLog(@"%@", strongSelf);
                NSLog(@"B objc -----retainCount : %lu", CFGetRetainCount(((__bridge CFTypeRef)strongSelf)));
            };
    
           weakSelf.doStudent();
        };
    
       self.doWork();
    }
    Copy the code

    Does this case cause circular references? Will! In doWork, __strong typeof(self) strongSelf = weakSelf; WeakSelf is held by strong reference. Similar to the previous situation, strongSelf’s life cycle is also within the doWork method. Note that the internal block doStudent calls an external variable, so it copies the stack block to the heap, causing the strongSelf reference count to increase and cannot be freed, resulting in circular references!