本文主要内容

一.Block介绍
二.Block截获变量
三.__block润饰符
四.Block的内存办理
五.Block的循环引证

全方位剖析iOS高级技术问题(五)之Block相关问题

一.Block介绍

1、什么是Block

  • Block是将函数及其履行上下文封装起来的目标。

2、Block的本质及其调用

  • Block是一个目标,它封装了函数函数的履行上下文

MCBlock.m

- (void)method {
    int multiplier = 6;
    int(^Block)(int) = ^int(int num) {
        return num * multiplier;
    };
    Block(2);
}

源码解析

  • 运用【clang -rewrite-objc file.m】编译后,可检查编译之后的.cpp文件内容

MCBlock.cpp

// I表示当时类的实例方法,MCBlock当时类名,method当时方法名method
static void _I_MCBlock_method(MCBlock *self, SEL _cmd) {
    int multiplier = 6;
    /*
     int(^Block)(int) = ^int(int num) {
         return num * multiplier;
     };
    */
    int(*Block)(int) = (int((int (*)))&__MCBlock__method_block_impl_0((void *)__MCBlock__method_block_func_0, &__MCBlock__method_block_desc_0_DATA, multiplier));
    /*
    Block(2);
    */
    ((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);
}
// __MCBlock__method_block_impl_0
struct __MCBlock__method_block_impl_0 {
    struct __block_impl impl;
    struct __MCBlock__method_block_desc_0* Desc;
    int multiplier;
    // 结构体结构函数
    __MCBlock__method_block_impl_0(void *fp, struct __MCBlock__method_block_desc_0 *desc, int _mutiplier, int flags=0) : multiplier(_multiplier) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
// __block_impl:Block结构体
struct __block_impl {
    void *isa; // isa指针,Block是目标的标志
    int Flags;
    int Reserved;
    void *FuncPtr; // 函数指针
};
Static int __MCBlock__method_block_func_0(struct __MCBlock__method_block_impl_0 *__cself, int num) {
    int multiplier = __cself->multiplier; // bound by copy
    return num * multiplier;
}

二.Block截获变量

  • 局部变量:关于根本数据类型截获其值,关于目标类型连同所有权润饰符一起截获。

  • 静态局部变量:以指针形式截获

  • 大局变量:不截获

  • 静态大局变量:不截获

1、截获变量源码解析

  • 运用【clang -rewrite-objc -fobjc-arc file.m】命令编译

MCBlock.m

// 大局变量
int global_var = 4;
// 静态大局变量
static int static_global_var = 5;
- (void)method {
    // 根本数据类型的局部变量
    int var = 1;
    // 目标类型的局部变量
    __unsafe_unretained id unsafe_obj = nil;
    __strong id strong_obj = nil;
    局部静态变量
    static int static_var = 3;
    void(^Block)(void) = ^{
        NSLog(@"局部变量<根本数据类型> var %d", var);
        NSLog(@"局部变量<__unsafe_unretained 目标类型> var %d", unsafe_obj);
        NSLog(@"局部变量<__strong 目标类型> var %d", strong_obj;
        NSLog(@"静态变量 %d", static_var);
        NSLog(@"大局变量 %d", global_var);
        NSLog(@"静态大局变量 %d", static_global_var);
    };
    Block();
}

MCBlock.cpp

int global_var = 4;
static int static_global_var = 5;
struct __MCBlock__method_block_impl_0 {
    struct __block_impl impl;
    struct __MCBlock_method_block_desc_0* Desc;
    // 截获根本数据类型局部变量的值
    int var;
    // 连同所有权润饰符一起截获
    __unsafe_unretained id unsafe_obj;
    __strong id strong_obj;
    // 以指针形式截获局部变量
    int *static_var;
    // 对大局变量和静态大局变量不截获
}

2、截获变量实例

  • 根本数据类型变量
- (void)method {
    int multiplier = 6;
    int(^Block)(int) = ^int(int num) {
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d",Block(2));
}
成果:result is 12
  • 静态局部变量
- (void)method {
    static int multiplier = 6;
    int(^Block)(int) = ^int(int num) {
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d",Block(2));
}
成果:result is 8

三.__block润饰符

1、什么场景下需求运用__block润饰符?

一般状况下,对被截获变量进行`赋值`操作需求增加`__block润饰符`

如下状况是否需求增加__block润饰符

示例1

{
    NSMutableArray *array = [NSMutableArray array];
    void(^Block)(void) = ^{
        [array addObject: @123];
    };
    Block();
}
答:不需求。当时是对array变量的运用,而非赋值操作。

示例2

{
   NSMutableArray *array = nil;
    void(^Block)(void) = ^{
        array = [NSMutableArray array];
    };
    Block(); 
}
答:当时是对array变量的赋值操作,所以需求在array的声明处增加__block润饰符,否则编译会报错。

2、对变量进行赋值

  • 需求__block润饰符:根本数据类型和目标类型的局部变量。
  • 不需求__block润饰符:静态局部变量、大局变量、静态大局变量。

3、__block机制与原理

  • __block润饰的变量变成了目标;

全方位剖析iOS高级技术问题(五)之Block相关问题
示例3

{
    __block int multiplier = 6;
    int(^Block)(int) = ^int(int num) {
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d",Block(2));
}
成果:result is 8

全方位剖析iOS高级技术问题(五)之Block相关问题

留意:block在栈上创立,__forwarding指针指向自己;堆上创立的block,__forwarding指针会指向其他地方。

四.Block的内存办理

  • 栈Block:放在栈上
  • 堆Block:放在堆上
  • 大局Block:放在已初始化数据中

全方位剖析iOS高级技术问题(五)之Block相关问题

1、Block的Copy操作

全方位剖析iOS高级技术问题(五)之Block相关问题

1.1 栈上的Block

栈上Block的Copy

  • 栈上有一个Block,Block中运用了__block变量,当对栈上的Block进行copy操作后,会在堆上发生和栈上一摸相同的堆Block和__block变量。跟着变量效果域的结束,栈上的Block和__block变量都会随之毁掉,而堆上的Block和__block变量依然存在。
    全方位剖析iOS高级技术问题(五)之Block相关问题

问题:在MRC环境下,当对栈上的Block进行Copy操作之后,是否会引起内存泄漏?

答:会。对栈上的Block进行Copy操作后,堆上的Block没有其他成员指向它,相当于alloc目标后,没有调用release效果相同。

栈上Block的毁掉

  • 栈上有一个__block变量和一个Block,在变量的效果域结束后或栈上的函数退出后,栈上的__block变量和Block都会被毁掉。留意⚠️,由于__block润饰的变量已经成为目标,所以会被毁掉。

全方位剖析iOS高级技术问题(五)之Block相关问题

1.2 栈上__block变量的Copy

  • 对栈上__block变量进行Copy操作后,会在堆上发生一摸相同的__block变量,由于栈上__forwarding指针也指向堆上的__block变量,所以对__block变量的任何修改,最终都是对堆上__block变量的修改。

全方位剖析iOS高级技术问题(五)之Block相关问题

__forwarding指针存在的含义?
对栈上的__block变量进行Copy操作后,栈上的__forwarding指针指向堆上的__block变量,堆上的__forwarding指针也指向自身。

1.3 __forwarding的总结

typedef int(^Block)(int num);
@property (nonatomic, copy, readwrite) Block blk;
{
    // 变成了目标multiplier
    __block int multiplier = 10;
    // Copy操作:则下面操作都是对堆上的multiplier进行操作
    _blk = ^int(int num) {
        return num * multiplier;
    };
    // multiplier目标的__forwarding指针对其成员变量multiplier赋值
    multiplier = 6;
    [self executeBlock];
}
- (void)executeBlock {
    // 堆上的blk 
    int result = _blk(4);
    NSLog(@"result is %d", result);
}
成果:result is 24

2、__forwarding存在的含义

  • 不管在任何内存位置
  • 都可以顺利的拜访同一个__block变量
  • 没有对__block变量进行Copy,操作的就是栈上的__block变量;假如进行了Copy操作,无论是在栈上还是堆上,对变量的修改或赋值操作都是堆上的__block变量,

五.Block的循环引证

1、如下代码会不会引起循环引证

{
    _array = [NSMutableArray arrayWithObject: @"block"];
    _strBlk = ^NSString*(NSString *num) {
        return [NSString stringWithFormat:@"helloc_%@", array[0]];
    };
    _strBlk(@"hello");
}
  • 会发生自循环方式循环引证。由于当时目标是经过Copy特点关键字声明的strBlk,所以当时目标对Block存在强引证,而Block表达式中运用到当时目标的array成员变量,依据Block截获变量部分内容,Block目标中运用到目标类型的局部变量,会连同其特点关键字一起被截获,array的特点关键字为strong,所以在Block中有一个strong类型的指针指向当时目标,由此就发生了循环引证。

全方位剖析iOS高级技术问题(五)之Block相关问题

  • 解决方案:在当时栈上声明(创立)一个__weak润饰的weakArray指针(变量),来指向原目标的array成员变量,在Block中运用weakArray,由此避免循环引证。
  • 为什么经过运用__weak润饰符就可以避免循环引证?依据Block截获变量原理,对目标的截获是连同所有权润饰符一起截获,当被截获的目标是__weak润饰的目标时,在Block中发生的结构体中持有的截获目标也是__weak类型的。

全方位剖析iOS高级技术问题(五)之Block相关问题

{
    _array = [NSMutableArray arrayWithObject: @"block"];
    __weak NSArray *weakArray = _array;
    _strBlk = ^NSString*(NSString *num) {
        return [NSString stringWithFormat:@"helloc_%@", weakArray[0]];
    };
    _strBlk(@"hello");
}

2、如下代码有何问题

{
    __block MCBlock *blockSelf = self;
    _blk = ^int(int num) {
        // var = 2
        return num * blockSelf.var;
    };
    _blk(3);
}
  • 在MRC下,不会发生循环引证。
  • 在ARC下,会发生循环引证,引起内存泄漏。

全方位剖析iOS高级技术问题(五)之Block相关问题

  • 解决方案:

全方位剖析iOS高级技术问题(五)之Block相关问题

{
    __block MCBlock *blockSelf = self;
    _blk = ^int(int num) {
        // var = 2
        int result = num * blockSelf.var;
        blockSelf = nil;
        return result;
    };
    _blk(3);
}
留意⚠️:假如没有调用此Block,则此循环引证环会一向存在!

本文总结

问题1:什么是Block?

Block是关于函数及其履行上下文封装的目标;

问题2:为什么Block会发生循环引证?

  • 当时Block对当时目标的某一成员变量进行截获,Block会对对应变量有前引证,而当时目标对Block有前引证,就发生自循环引证。声明成员变量为__weak变量来解决
  • 界说__block说明符也或许会引起循环引证。MRC下不会发生循环引证,ARC下会发生循环引证。解决方案:在Block中将__block润饰的变量置为nil,但是留意:假如没有调用此Block,则此循环引证环会一向存在!

问题3:怎样了解Block截获变量的特性?

  • 根本数据类型变量只截获其值
  • 目标类型局部变量连同所有权润饰符一起被截获
  • 对静态局部变量以指针形式截获
  • 对大局变量和静态大局变量不截获

问题4:开发中遇到过哪些循环引证?怎么解决?

五.Block的循环引证

有任何问题,欢迎各位谈论指出!觉得博主写的还不错的麻烦点个赞喽