1、探寻block类型
既然block是对象,可以通过调用class方法或者isa指针查看具体类型,通过superclass获取其父类,最终都是继承自NSBlock类型
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { void(^block)(void) = ^{ NSLog(@"Hello"); }; NSLog(@"----%@",[block class]); NSLog(@"----%@",[[block class] superclass]); NSLog(@"----%@",[[[block class] superclass] superclass]); NSLog(@"----%@",[[[[block class] superclass] superclass] superclass]); block(); } return 0; }
2022-05-07 23:46:46.041962+0800 block-01[68178:2905102] ----__NSGlobalBlock__ 2022-05-07 23:46:46.042535+0800 block-01[68178:2905102] ----NSBlock 2022-05-07 23:46:46.042595+0800 block-01[68178:2905102] ----NSObject 2022-05-07 23:46:46.042635+0800 block-01[68178:2905102] ----(null)
从上面的打印日志可以知道:
block对象类型:NSGlobalBlock_
父类:NSBlock
父类的父类:NSObject
2、获取block具体类型
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { void(^block1)(void) = ^{ NSLog(@"Hello1"); }; int age = 10; void(^block2)(void) = ^{ NSLog(@"Hello2:%d",age); }; NSLog(@"----%@",[block1 class]); NSLog(@"----%@",[block2 class]); NSLog(@"----%@",[[^{ NSLog(@"Hello3:%d",age); } class] class]); } return 0; }
2022-05-07 23:59:21.573913+0800 block-01[68605:2913488] ----__NSGlobalBlock__ 2022-05-07 23:59:21.574601+0800 block-01[68605:2913488] ----__NSMallocBlock__ 2022-05-07 23:59:21.574695+0800 block-01[68605:2913488] ----__NSStackBlock__
由以上可以看到 block 有3种类型:
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
但是编译完毕之后,情况又不一样了:
struct __main_block_impl_0 { struct __block_impl impl; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; } }; struct __main_block_impl_1 { struct __block_impl impl; __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; } }; struct __main_block_impl_2 { __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; } };
1、发现编译完毕之后,block的类型都是 _NSConcreteStackBlock。
2、这里指向的对象类型名称(编译出来)跟打印出来的类型名称(动态运行后)也是有所不一样的。但是他们是一一对应的。
那么为什么打印出来的跟编译出来的类型不一样?
首先要明白我们的程序最终是以动态运行之后才是准确的。
那么从编译完之后,动态运行期间,还可能根据参数动态调整block对象类型。
3、不同对象类型block的内存分配
说明:
1、程序区域(.text区):俗称代码段。特点:一般来说内存地址是比较小的,也就是比较前面,是内存的低地址。
2、数据区域(.data区):数据段。特点:一般放一些全局变量。
3、堆:动态分配内存的,如alloc出来的对象。特点:需要开发者申请内存,也需要程序员自己管理内存,当然ARC环境内部已经自动帮我们管理了。
4、栈:放一写局部变量。特点:系统自动分配内存,也会自动销毁内存,当局部变量离开作用域就会自动销毁。
4、如何实现不同对象类型block?
还是先公布结果:
对于之前在栈上的block,copy到堆上时。当不再使用的时候需要调用 release 释放内存。当然如果我们的编译环境时 ARC 环境,是不需要调用该方法释放,系统会自动管理。
栈空间的 block 不会保持外部变量的引用
代码实现:
注意:这个要在MRC环境下编译。因为ARC环境下编译,它会已经帮我们做了好多事情。
Build Setting 下面,搜索 Automatic Reference Counting 改成NO就可以了。
4.1、NSGlobalBlock 数据区的block
实现条件:block内部没有访问auto就可以。
代码示例:
#import <Foundation/Foundation.h> void(^block)(void); void test() { block = ^{ NSLog(@"Hello1"); }; NSLog(@"block:%@",[block class]); } int main(int argc, const char * argv[]) { @autoreleasepool { test(); block(); } return 0; }
打印日志:
2022-05-08 01:43:02.443356+0800 block-01[72187:2987373] block:__NSGlobalBlock__ 2022-05-08 01:43:02.443825+0800 block-01[72187:2987373] Hello1
4.2、NSStackBlock 栈区的block
实现条件:block内部访问了auto变量
#import <Foundation/Foundation.h> void(^block)(void); void test() { int age = 10; block = ^{ NSLog(@"Hello1:%d",age); }; } int main(int argc, const char * argv[]) { @autoreleasepool { test(); block(); } return 0; }
打印日志:
2022-05-08 01:15:42.458205+0800 block-01[71217:2967549] Hello1:-1074793848
发现运行后的结果,取到的age值是一个脏数据,并不是我们想要的。
原因分析:
1、test函数内部的变量是存放在栈区,待函数运行完毕,内部的对象会全部被销毁。
2、所以在执行block内部函数时,age变量已经销毁,取出来的值一个乱值。
那么怎么处理这个问题呢?
block 方法放到堆上, 即将 __NSStackBlock__ 对象类型升级为 __NSMallocBlock__ 对象类型,实现方式也很简单,直接在后block后面调用一次copy,那么这时返回的block就是在堆上的block。 这样就不会被自动销毁,想要什么时候销毁,由我们程序员自己决定。
具体验证看下一部分。
4.3、NSMallocBlock 堆区的block
实现条件:block内部访问了auto变量,并且执行了一次copy操作
代码实现:
#import <Foundation/Foundation.h> void(^block)(void); void test() { int age = 10; block = [^{ NSLog(@"Hello1:%d",age); } copy]; NSLog(@"block:%@",[block class]); } int main(int argc, const char * argv[]) { @autoreleasepool { test(); // 此时调用的block时堆上的block block(); } return 0; }
打印日志:
2022-05-08 01:34:07.307367+0800 block-01[71852:2980649] block:__NSMallocBlock__ 2022-05-08 01:34:07.308459+0800 block-01[71852:2980649] Hello1:10
结果分析:
从以上知道,block被放到了堆上,age也能正常取值。
4.4、不同对象类型的block 与copy之间的关系
如果数据区的block执行了copy操作呢,会是什么结果?我们可以尝试一下:
代码示例:
#import <Foundation/Foundation.h> void(^block)(void); void test() { block = [^{ NSLog(@"Hello1"); } copy]; NSLog(@"block:%@",[block class]); } int main(int argc, const char * argv[]) { @autoreleasepool { test(); block(); } return 0; }
2022-05-08 01:50:10.096000+0800 block-01[72430:2991254] block:__NSGlobalBlock__ 2022-05-08 01:50:10.103313+0800 block-01[72430:2991254] Hello1
发现block还是 __NSGlobalBlock__ 类型,内存分配在数据区。
总结以上,不同类型的block调用copy后的结果如下所示: