简括
Block的声明与赋值只是保存一段代码块,必须调用才能执行内部代码。
Block 底层就是一个struct结构体,所以它就是一个对象,作为结构体,里面会添加属性,导致block会有自动捕获变量的特性。
正是因为block有自动捕获变量特性,导致他的三种类型变化。我们正常声明一个block的时候,这个block其实是存在global区内存中,当这个对象进行自动捕获后,这个对象block会变成到stack区中,如果将这个block对象进行了赋值操作,也就是执行 赋值运算符=号操作(相当于copy一份进heap堆区),则会将这个带有捕获对象的stack区的block变为Heap区的block。下面我们会用代码 同打印 进行一一验证。
Block变量的赋值格式
Block变量的赋值格式是:myBlock变量 = ^返回值类型(参数列表){函数体};不过通常情况下都将返回值类型省略,因为编译器可以从存储代码块的变量中确定返回值的类型
int(^myBlock)(int) = ^(int num){
return num * 7;
};
Block分为三类
按照内存分布,block分为三种。
Block类型 | 存在内存区域 | 内存指针开头 |
---|---|---|
NSGlobalBlock | 静态区 | 0x1 |
NSStackBlock | stack区(栈区) | 0x7 |
NSMallocBlock | heap区 (堆区) | 0x6 |
Block类型分别在什么情况下生成?
1. NSGlobalBlock生成
void (^myBlcok)(void) = ^{
NSLog(@"全局block");
};
NSLog(@"myBlock: %@", myBlcok);
NSLog(@"只开辟生成的block: %@",^{
NSLog(@"这个也是全局block");
});
如上代码所示,我们自定义一个block,或者只是开辟一个block空间,没有对他进行运算符=号 的操作。我们尝试打印一下其内存地址。
2019-12-16 16:34:49.047725+0800 BlockTest[1713:160228] myBlock: <__NSGlobalBlock__: 0x103e22040>
2019-12-16 16:34:49.048129+0800 BlockTest[1713:160228] 只开辟生成的block: <__NSGlobalBlock__: 0x103e22060>
打印结果内存为0x1,类型NSGlobalBlock。
2. NSStackBlock 生成
int a = 10;
//block有捕获自动变量
NSLog(@"只开辟生成的block: %@",^{
NSLog(@"block捕获变量: %d",a);
});
如上代码所示:我们开辟一个block,如果不捕获变量时候,这个blokc是存在静态区的NSGlobalBlock类型。那如果我们用此block自动捕获了对象,那么会变成什么样子呢?看下面打印。
2019-12-16 16:58:07.115068+0800 BlockTest[2062:239378] 只开辟生成的block: <__NSStackBlock__: 0x7ffee1df60e0>
通过打印,我们可以看出,具有捕获变量的NSGlobalBlock对象,变成了NSStackBlock类型对象。内存地址0x7
3. NSMallocBlock 生成
int a = 10;
//block有捕获自动变量
void (^myBlcok)(void) = ^{
NSLog(@"myblock: %d",a);
};
NSLog(@"myBlock: %@", myBlcok);
如上代码所示:我们开辟一个block,并给其命名myBlock,myBlock通过block特性自动捕获了变量a,那么现在的myBlock还同没有捕获变量a之前那样还是属于 NSGlobalBlock类型吗?我们看一下打印
2019-12-16 16:58:07.114845+0800 BlockTest[2062:239378] myBlock: <__NSMallocBlock__: 0x600000dcbd20>
通过打印可以看出,现在myBlock已经变成存在heap区的NSMallocBlock类型了。想想上一步,未初始的对象存在global区的NSGlobalBlock类型,经过自动捕获对象的存在stack区的NSStackBlock类型,如果我们在执行赋值运算符=号之后,现在存在的是heap区的NSMallocBlock类型了。
在这个过程中,发现了什么问题没有?
我们是在通过赋值运算符=号之后,发现stack区的block变成了heap区的block,这期间发生了什么呢?这样做的目的是什么呢?
初步思考,由于赋值后,我们这个会通过对象调用这块内存区域,如果我们这块内存还在栈中,万一我们在使用这个对象的途中,系统给我们释放了这块内存,那就会造成crash,这是万万不能允许的。所以从这方面考虑我们也应该需要把这个对象拷贝到heap区,让程序员来控制它何时释放。
下一片文章我们一起通过源码进行探索解析。
知识点补充:
-
_NSConcreteStackBlock:
只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。 -
_NSConcreteMallocBlock:
有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制 -
_NSConcreteGlobalBlock:
没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。
没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。
网友评论