iOS底层原理(28)---block分析上

156 阅读5分钟

block的分类

1、NSGlobalBlock

位于全局区;

在Block内部不使用外部变量,或者只使用静态变量和全局变量;

2、NSMallocBlock

位于堆区;

在Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量

3、NSStackBlock

位于栈区;

与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量。

探索block

int a = 10;
void (^ block)(void) = ^{
    NSLog(@"Cooci - %d",a);
};

上面是堆block,因为引用了外部变量,且默认被强引用,注意=

int a = 10;
void (__weak ^ block)(void) = ^{
    NSLog(@"Cooci - %d",a);
};

加入__weak之后变为弱引用,因此是栈block。

面试题
#pragma mark - block 捕获外部变量-对外部变量的引用计数处理
- (void)blockDemo2{
    
    NSObject *objc = [NSObject new];
    NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 被引用一次,所以引用计数为1

    // block在编译时候在栈区,运行时候需要拷贝到堆区,引用计数+1
    // block捕获局部变量,引用计数+1
    void(^strongBlock)(void) = ^{ 
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    //这里是栈block,不需要拷贝到堆区,因此引用计数不变
    //捕获局部变量,引用计数+1
    void(^__weak weakBlock)(void) = ^{ // + 1
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
    
    NSLog(@" %@ \n %@ \n  %@ \n",strongBlock,weakBlock,mallocBlock);
}

打印:

---1
---3
---4
---5
<__NSGlobalBlock__: 0x10b9021b0> 
<__NSStackBlock__: 0x7ff7b45fd2c0> 
<__NSMallocBlock__: 0x6000009434e0>
小结

根据打印结果,也验证了 全局block存在全局区;栈block存在栈区;堆block存在堆区。

- (void)block_copy_usingBlock {
    
    void (^usingBlock)(id obj, NSUInteger idx, BOOL *stop) = ^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop){
    };
    NSArray *array = @[usingBlock];

    [array enumerateObjectsUsingBlock:usingBlock];
    
    NSLog(@"%@", array.firstObject);
}

打印:

2021-10-07 16:11:52.358633+0800 [36477:3894313] <__NSGlobalBlock__: 0x105fa8180>
#pragma mark - 内存拷贝的理解
- (void)blockDemo1{
    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    
    //重写block,LGBlock,便于对block的invoke进行修改,也就是hook到invoke
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
  
    // 深浅拷贝,如果不做copy会崩溃
    id __strong strongBlock = [weakBlock copy];
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
    // 预警什么 ???
}

打印:

2021-10-07 16:09:35.110759+0800 [36440:3891656] ----- 0
block 堆栈释放差异
- (void)blockDemo3{
    
    NSObject *a = [NSObject alloc];//a在堆区
    void(^__weak weakBlock)(void) = nil;//weakBlock 在栈区
    {
        // 栈区
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"---%@", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
}

打印:

2021-10-07 16:22:00.079784+0800 [36553:3901246] 1 - <__NSStackBlock__: 0x7ffee94f5390> - <__NSStackBlock__: 0x7ffee94f5390>
2021-10-07 16:22:00.079889+0800 [36553:3901246] ---(null) //因为strongBlock所拥有的内存空间已经不存在了,那么strongBlock里面的所有内容也都不存在了,所以对a的引用也就不存在了。

分析: 上面的 weakBlock 和 strongBlock 都是栈block

对比blockDemo3:

- (void)blockDemo4{
    
    // 压栈 内存平移
    NSObject *a = [NSObject alloc];
    void(^__weak weakBlock)(void) = nil;
    {
        // 堆区
        void(^ strongBlock)(void) = ^{
            NSLog(@"---%@", a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
}

打印:

崩溃

2021-10-07 16:39:48.781468+0800[36676:3912167] 1 - <__NSMallocBlock__: 0x600001be16b0> - <__NSMallocBlock__: 0x600001be16b0>
(lldb) 
- (void)blockDemo5{
    
    // 压栈 内存平移
    NSObject *a = [NSObject alloc];
    void(^strongBlock1)(void) = nil;
    {
        // 堆区
        void(^ strongBlock)(void) = ^{
            NSLog(@"---%@", a);
        };
        strongBlock1 = strongBlock;
        NSLog(@"1 - %@ - %@",strongBlock1,strongBlock);
    }
    weakBlock();
}

打印:

2021-10-07 16:48:20.985483+0800 [36780:3919533] 1 - <__NSMallocBlock__: 0x60000190cf90> - <__NSMallocBlock__: 0x60000190cf90>
2021-10-07 16:48:20.985592+0800 [36780:3919533] ---<NSObject: 0x6000015542f0>

小结:如果是堆block在底层会对block进行一次copy,因此objc在堆block中copy一次、捕获一次,引用计数最终加2。区分堆栈区,明确变量的作用域生命周期。

block拷贝到堆block

1、手动Copy

2、Block作为返回值

3、被强引用或者Copy修饰

4、系统柜API包含usingblock

栈Block -> 堆Block 源码
// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// KC重点提示: 这里是核心重点 block的拷贝操作: 栈Block -> 堆Block

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy. -> heap
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount -- 对象 isa 联合体位于
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
block flag标识

image.png

Block 捕获外界变量的操作
//  Block 捕获外界变量的操作
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}

循环引用

两个对象时间相互引用,都在等待对方释放,最终双方都不释放,造成循环引用,内存泄漏。

解决循环引用的方式

weak-strong-dance
__weak typeof(self) weakSelf = self;
self.block = ^(void){
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.name);
    });
};
self.block();
手动置为nil
// self -> block -> vc -> self
__block ViewController *vc = self;
self.block = ^(void){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
        vc = nil;
    });
};
self.block();
使用参数
self.block = ^(ViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
    });
};
self.block(self);

面试题

1、第一题

- (void)blockWeak_static {
    // weakSelf 和 self 是同一片内存空间
    __weak typeof(self) weakSelf = self;
    staticSelf_ = weakSelf;
    // staticSelf_ -> weakSelf -> self
}

以上代码会造成循环引用。weakSelf 和 self 是同一片内存空间,staticSelf_是全局静态变量,因此self不会释放。

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

以上代码会造成循环引用,因为doStudent的block对strongSelf进行了捕获等操作,引用计数会增加3。