iOS--Block

Block都继承自NSBlock,并最终继承与NSObject,所以Block有 isa 指针,本质是一个封装了函数调用以及函数调用环境的OC对象。
Block调用实际上是函数调用。

block底层结构

Block分为

  1. _NSConcreteStackBlock 栈Block
  2. _NSConcreteGlobalBlock 全局Block
  3. _NSConcreteMallocBlock 堆Block


    block类型
//没有访问 auto 变量, Global block
    void (^block1)(void) = ^{
        NSLog(@"----------block1");
    };
    
    //访问 auto 变量, Stack block (关闭ARC)
    int number = 12;
    void (^block2)(void) = ^{
        NSLog(@"----------block2, %d", number);
    };
    
    // 栈 block 调用了 copy
    [block2 copy];
block在栈上不定时会被销毁,为了保持栈,调用copy或者对栈属性设置copy关键字,将栈block移到堆内存保存,注意MRC环境下堆内存的release。

Block分类与内存位置

Block分类与内存位置

在 ARC 环境下,编译器会根据情況自动将栈上的 block 复制到堆上,比如以下情况:

  • block 作为函数返回值时
  • 将 block 赋值给 __strong 指针时(block 的 property 设置为 copy, 对应的是这一条,对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。)
  • block 作为 Cocoa API 中方法名含有 using Block 的方法参数时
  • block 作为 GCD API 的方法参数时

Block 的 copy 操作

Block 的 copy 操作

Block变量捕获(capture)

为了保证 Block内部能够正常访问外部的变量,block 有个变量捕获机制

1. 对于 基本数据类型 的 局部变量 截获变量的 值,block增加一个变量存储这个局部变量的 值。

局部变量定义 默认是 auto :自动变量 ,如 int number 6; 实际是 auto int number = 6;,因为默认是 auto,所以一般都是省略的。auto只存在于局部变量,自动变量离开作用域就销毁,为了保证block在执行时依然能获取到值,block只能先捕获自动变量的值。

//基本类型的局部变量截获
- (void)methond0{
    int number = 6;
    int (^Blockt)(int) = ^int(int num) {
        return num * number;
    };
    number = 1;
    NSLog(@"基本类型的局部变量截获:%d", Blockt(2));
}
输出结果:基本类型的局部变量截获:12
2. 对于 静态局部变量 以 指针形式 截获。

因为静态变量常驻内存的,block在使用静态局部变量时只要保存一个指向静态局部变量地址的指针,在需要使用这个变量的时候,通过指针指向的地址,获取最新的值。

//基本类型的静态局部变量截获
- (void)methond1{
    static int number = 6;
    int (^Blockt)(int) = ^int(int num) {
        return num * number;
    };
    number = 1;
    NSLog(@"基本类型的静态局部变量截获:%d", Blockt(2));
}
输出结果:基本类型的静态局部变量截获:2
3. 对于对象类型的局部变量(auto变量) 连同所有权修饰 一起截获。
对象类型的局部变量(auto变量)

特别的对于 self,self 是对象在调用函数的时候传入的参数,不同对象调用同一个函数,self参数指代不同的对象。

void (^block)(void) = ^{
        NSLog(@"--------%p", self);
    };
self 是局部变量,会被捕获

对于类的属性,block会捕获这个类,再去访问这个类的属性,如self有一个 name 属性,下面block依然会捕获self,然后通过self去访问name属性:

void (^block)(void) = ^{
        NSLog(@"--------%p", _name);
    };
4. 不截获全局变量(包括静态全局变量)。

因为block在任何位置任何时间都可以直接访问到全局变量。

总结:局部变量会捕获,全局变量不会捕获

----------------------------------------------------------------------------------

__block 修饰符

一般情况下,对被截获变量进行 赋值 操作需要添加 __block 修饰符

{
    NSMutableArray *array = [NSMutableArray array];   
    void (^Block)(void) = ^ {
        //对数组添加元素,使用数组,不需要__block 修饰符
        [array addObject:@1];    
    };
    Block();
}
    __block NSMutableArray *array = nil;
    void (^Block)(void) = ^ {
      //对array初始化赋值,需要__block 修饰符
        array = [NSMutableArray array];   
    };
    Block();
  • 需要__block 修饰符:对局部变量(包括基本数据类型和对象类型)进行赋值时。
- (void)methond0{
    __block int number = 6;
    int (^Blockt)(int) = ^int(int num) {
        return num * number;
    };
    number = 1;
    NSLog(@"__block修饰的基本类型的局部变量截获:%d", Blockt(2));
}
输出结果:__block修饰的基本类型的局部变量截获:2

__block 修饰的变量最终变成了对象。

  • 不需要__block 修饰符:对静态局部变量,静态全局变量,全局变量进行赋值。


    image.png
image.png

__block的内存管理

__block的内存管理

block从堆中移除

栈上用__block修饰符修饰的变量在copy之后,在堆上分配了一个与原来变量一样的内存,并且栈上变量的forwarding指针指向堆上block变量的地址;堆上变量forwarding指针也指向自己block变量的地址;
也就是说,在__block变量进行copy之后会在堆上产生一个相同的变量,forwarding指针都指向堆上__block变量。


image.png

__forwarding存在的意义:
不论在内存的任何位置,都可以顺利的访问同一个__block 变量。
如果对__block 变量不copy,操作的就是栈上的__block 变量;
如果发生了copy,无论操作的是栈上、还是堆上__block 变量,都是使用的堆上__block 变量。


image.png

栈上的Block在变量的作用域结束之后,或者说栈上的函数退出之后销毁。

在MRC环境下,对Block进行copy之后,是否会引起内存泄漏?是的。
随着栈上block和__Block变量作用域的结束,该block和__Block变量会销毁,但是堆上的block或者__block变量没有其它成员变量去指向它,引起内存泄漏。

Block笔试题:

@property(nonatomic, copy)int(^blk)(int);

{
    __block int m = 10;
    _blk = ^int(int num){
       return num * m;
    };
    
    m = 6;
    
    [self executeBlock];
}

- (void)executeBlock
{
    int result = _blk(4);
    NSLog(@"result is %d", res);
}
> result is 24

由于multiplier被__block修饰,在clang编译之后是一个结构体,multiplier是 该结构体的一个成员变量:
在multiplier = 6时,是结构体中forwarding指向的multiplier成员变量赋值为6;
(multiplier.__forwarding->multiplier) = 6;
如果_blk没有进行copy操作,修改的就是栈上的__block变量;反之修改的是堆上__block变量;

__block说明符在MRC和ARC下的区别:
__block说明符的作用是 修饰变量后 可以让变量成为对象,MRC下,__block修饰的变量成为对象后,被block使用后没有强引用的关系,而ARC下有block对__block修饰的变量有强引用的关系;

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前面我们说了weak和strong的一些作用,自然我们接下来就要聊一下block。Block在我们项目中绝对会用到...
    Harry__Li阅读 3,950评论 0 0
  • 假设我们熟悉代理递值的话,对代理我们可能又爱有恨!我们先建立模型A页面 push B页面,如果把A页面的值传递到B...
    郭伟_技术与产品阅读 1,579评论 0 0
  • Block在开发中常用的,要想解决Block在开发中遇到的问题,我们需要了解Block的本质、截获变量的特性、__...
    字节码阅读 3,610评论 0 2
  • Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功...
    iOSunRain阅读 1,183评论 0 0
  • 1. Block是啥? 答:Block是将函数及其执行上下文封装起来的对象。 使用终端编译.m内容: 编译之后会生...
    DoBetter1阅读 2,047评论 0 1