美文网首页
iOS Block的三种类型 globalBlock、stack

iOS Block的三种类型 globalBlock、stack

作者: 齐玉婷 | 来源:发表于2019-03-27 15:21 被阅读0次


    在 ARC 中,捕获了外部变量的 block 的类会是 __NSMallocBlock__ 或者 __NSStackBlock__,如果 block 被赋值给了某个变量,在这个过程中会执行 __Block__copy 将原有的 __NSStakeBlock__ 变成 __NSMallocBlock__ ;但是如果 block没有赋值给某个变量,那他的类型就是 __NSStakeBlock__ ;没有捕获外部变量的 block 的类会是 __NSGlobalBlock__ 即不再对上,也不在栈上,它类似 C 语言函数一样会在代码段中。

    在非 ARC 中,捕获了外部变量的 block 的类会是 __NSStackBlock__ ,放在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。

    block 中的 isa 指向的是该 block 的 Class 。在 block runtime 中,第一了6种类型:

    _NSConcreteStackBlock     栈上创建的block

    _NSConcreteMallocBlock  堆上创建的block

    _NSConcreteGlobalBlock   作为全局变量的block

    _NSConcreteWeakBlockVariable

    _NSConcreteAutoBlock

    _NSConcreteFinalizingBlock

    我们能接触到的主要是前三种,后三种用于 GC 不再讨论...

    当 struct 第一次被创建时,它是存在于该函数的栈帧上的,其 Class 是固定的 __NSStackBlock 。其捕获的变量是会赋值到结构体的成员上,所以当 block 初始完成后,捕获到的变量不能更改。 

    当函数返回时,函数的栈帧被销毁,这个 block 的内存也会被清除。所以在函数结束后,如果仍需要这个 block ,就需要用到 Block_copy() 方法将它拷贝到堆上。这个方法的核心动作是:申请内存,将栈数据复制过去,将 Class 改一下,最后向捕获到的对象发送 retain ,增加 block 的引用计数。

    1.全局 block 

    定义在函数外部的 block 是 global 类型的

    定义在函数内部的 block ,但是没有捕获任何自动变量,那也是全局的。

    全局静态 block,不会访问任何外部变量,执行完就销毁。

    typedef

    int(^blk_t)(int);

    for(...){

    blk_t blk = ^(intcount) {returncount;};

    }或

    ^{ NSLog(@"Hello World!"); }();

    2.栈 block

    保存在栈中的 block,当函数返回时会被销毁,和第一种的区别就是调用了外部变量。

    typedef void (^block_t)() ;

    -(block_t)returnBlock{

        __block int add=10;

        return ^{printf("add=%d\n",++add);};

    }

    这是因为:block捕获了栈上的add自动变量,此时add已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下可以编译过,是因为ARC使用了autorelease了。

    再说一个场景:

    - (block_t) returnBlock{

        __block int add=10;

        block_t blk_h =^{printf("add=%d\n",++add);};

        return blk_h;

    }

    block_t bb = [self returnBlock];

    bb();

    这段代码,只是使用了一个自动block变量,可以编过,但是造成程序崩溃了。

    如果在返回block的时候加上copy,可以输出正确的数值11

    [UIView animateWithDuration:3 animations:^{ self.view.backgroundColor = [UIColor redColor]; }];

    3.堆内存

    保存在堆中的 block,当引用计数为 0 时会被销毁。例如按钮的点击事件,一直存在,即使执行过,也不销毁,因为按钮还可能被点击。直到持有按钮的View被销毁,它才会被销毁。

    有时候我们需要调用 block 的 copy 函数,将 block 拷贝到堆上。如下代码:

    - (id) getBlockArray{

        int val =10;

        return [NSArray arrayWithObjects:

            ^{NSLog(@"blk0:%d",val);},

            ^{NSLog(@"blk1:%d",val);},nil];

    }

    id obj = getBlockArray();

    typedef void (^blk_t)(void);

    blk_t blk = (blk_t){obj objectAtIndex:0};

    blk();

    这段代码在最后一行 blk() 会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。

    这种场景,ARC 也不会为你添加 copy,因为ARC不确定,采取了保守的措施:不添加copy。所以ARC下也是会异常退出。

    Block 在栈和堆中的区别

    1.默认情况下,block 默认存储在栈中;如果将 block 进行一次 copy 操作,block 会转移到堆中。

    2.如果 block 在栈中,block 访问了外界变量,那么不会对外界进行 retain 操作。

    3.在 property 中使用 block 时,一般都是用 copy 修饰 block ,将 block 拷贝到堆上,减少无意中的堆 block 释放。

    Copy 的作用

    1. 防止外界修改内部数据

    2.可以使用 copy 保存 block ,这样可以保住 block 中使用外界对象的生命。

    Block的声明语法

    一、block的声明

    int multiplier = 10;

    int (^MyBlock) (int) = ^(int num) { return num*multiplier };

    int Myblock 是块对象,返回整形值

    "^" 符号将 MyBlock 声明为一个块对象

    (int) 它有一个参数,参数的类型也是整形

    ^(int num) 参数的名称是num

    ^(int num) { return num*multiplier } 这是定义块对象的语法结构,这部分就是赋给MyBlock变量的值。

    { return num*multiplier } 这是块对象的主体部分

    1.写在方法里作为局部变量

    returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

    returnType是返回值

    blockName是block名称

    parameterTypes是参数

    //无参数无返回值

    void (^myblock)() = ^()

    {

        NSLog(@"Hello, World!");

    };

    myblock();

    //带参数无返回值

    void (^myblock2)(NSString *string) = ^(NSString *string){ NSLog(@"%@",string);};

    myblock2(@"Hello, World myblock2!");

     //无参数有返回值

    int (^myblocksss)() = ^(int i){return 12;};

    int c = myblocksss();

    NSLog(@"%i",c);

    //有参数有返回值

    int (^myblock3)(int) = ^(int i){ return 12 * i; };

    int i = myblock3(3);

    NSLog(@"%i",i);


    2.作为类的属性

    @property(nonatomic,copy) returnType (^blockName)(parameterTypes)

    returnType是返回值

    blockName是block名称

    parameterTypes是参数

    3.作为方法参数

    - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;

    returnType是返回值

    blockName是block名称

    parameterTypes是参数

    4.调用方法是传入的参数

    [self someMethodThatTakesABlock:^returnType (parameters) {...}];

    returnType是返回值

    blockName是block名称

    parameterTypes是参数

    5.自定义Block类型

    typedef returnType (^TypeName)(parameterTypes);

    TypeName blockName = ^returnType(parameters) {...};

    returnType是返回值

    blockName是block名称

    parameterTypes是参数

    相关文章

      网友评论

          本文标题:iOS Block的三种类型 globalBlock、stack

          本文链接:https://www.haomeiwen.com/subject/efxbvqtx.html