经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
block本质探寻三之block类型
来源:cnblogs  作者:春天里的花骨朵  时间:2019/1/11 9:50:33  对本文有异议

一、oc代码

提示:看本文章之前,最好按顺序来看;

//代码

  1. void test1()
  2. {
  3. int age = 10;
  4. void(^block1)(void) = ^{
  5. NSLog(@"block1----");
  6. };
  7. void(^block2)(void) = ^{
  8. NSLog(@"block2----%d", age);
  9. };
  10. NSLog(@"block1-----\n%@ %@ %@ %@", [block1 class], [[block1 class] superclass], [[[block1 class] superclass] superclass], [[[[block1 class] superclass] superclass] superclass]);
  11. NSLog(@"block2-----\n%@ %@ %@ %@", [block2 class], [[block2 class] superclass], [[[block2 class] superclass] superclass], [[[[block2 class] superclass] superclass] superclass]);
  12. NSLog(@"block-----\n%@ %@ %@ %@", [^{
  13. NSLog(@"block----%d", age);
  14. } class], [[^{
  15. NSLog(@"block----%d", age);
  16. } class] superclass], [[[^{
  17. NSLog(@"block----%d", age);
  18. } class] superclass] superclass], [[[[^{
  19. NSLog(@"block----%d", age);
  20. } class] superclass] superclass] superclass]);
  21. }

//打印

  1. 2019-01-10 14:36:04.290317+0800 MJ_TEST[3446:174827] block1-----
  2. __NSGlobalBlock__ __NSGlobalBlock NSBlock NSObject
  3. 2019-01-10 14:36:04.290608+0800 MJ_TEST[3446:174827] block2-----
  4. __NSMallocBlock__ __NSMallocBlock NSBlock NSObject
  5. 2019-01-10 14:36:04.290652+0800 MJ_TEST[3446:174827] block-----
  6. __NSStackBlock__ __NSStackBlock NSBlock NSObject
  7. Program ended with exit code: 0

分析:

1)三个block的类型分别为:__NSGlobalBlock__、__NSMallocBlock__、__NSStackBlock__,什么原因,往下看;

2)上述三种类型最终都是继承自NSBlock,而NSBlock又是继承自NSObject:此处又进一步说明block其实就是一个OC对象(前面的文章已经证明过);

说明:上述结果是在ARC模式下打印的结果,现在我们看看MRC的打印情况

//设置

//打印

  1. 2019-01-10 15:05:50.667948+0800 MJ_TEST[3576:189745] block1-----
  2. __NSGlobalBlock__ __NSGlobalBlock NSBlock NSObject
  3. 2019-01-10 15:05:50.668257+0800 MJ_TEST[3576:189745] block2-----
  4. __NSStackBlock__ __NSStackBlock NSBlock NSObject
  5. 2019-01-10 15:05:50.668279+0800 MJ_TEST[3576:189745] block-----
  6. __NSStackBlock__ __NSStackBlock NSBlock NSObject
  7. Program ended with exit code: 0

分析:发现MRC模式下,三种block类型:__NSGlobalBlock__、__NSStackBlock__、__NSStackBlock__,为什么中间的类型由malloc变成了stack?这是因为ARC系统做了很多工作,导致不能正确的反应block类型(具体哪些工作,我就不清楚了);

补充一下:clang成C++代码,我们看下

  1. struct __test1_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __test1_block_desc_0* Desc;
  4. __test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int flags=0) {
  5. impl.isa = &_NSConcreteStackBlock;
  6. impl.Flags = flags;
  7. impl.FuncPtr = fp;
  8. Desc = desc;
  9. }
  10. };
  11. struct __test1_block_impl_1 {
  12. struct __block_impl impl;
  13. struct __test1_block_desc_1* Desc;
  14. int age;
  15. __test1_block_impl_1(void *fp, struct __test1_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
  16. impl.isa = &_NSConcreteStackBlock;
  17. impl.Flags = flags;
  18. impl.FuncPtr = fp;
  19. Desc = desc;
  20. }
  21. };
  22. struct __test1_block_impl_2 {
  23. struct __block_impl impl;
  24. struct __test1_block_desc_2* Desc;
  25. int age;
  26. __test1_block_impl_2(void *fp, struct __test1_block_desc_2 *desc, int _age, int flags=0) : age(_age) {
  27. impl.isa = &_NSConcreteStackBlock;
  28. impl.Flags = flags;
  29. impl.FuncPtr = fp;
  30. Desc = desc;
  31. }
  32. };

分析:发现都是_NSConcreteStackBlock类型,也不能正确反应block的实质类型,也是有问题的(据说是LLVM编译器版本的问题,而clang又是LLVM的一部分);

 

二、原因分析

1)程序内存结构

<1>首先,程序的内存结构分为:程序区(代码区)、数据区(全局区)、堆区、栈区;

<2>全局变量、static类型的局部变量存放在数据区,内存直到程序结束才自动释放;auto类型的局部变量、参数(形/实参)存放在栈区,离其最近的大阔结束时自动内存自动被释放;通过malloc\alloc\copy等手动开辟内存的,则存放在堆区,需要程序员手动予以释放;程序区可以看成非变量区,除变量外,程序的一些其他代码即常量存放在该区;

  

说明:

<1>除了堆区需要程序员手动管理内存外,其他区都由系统自动管理;

<2>等号左右:

  1. {
  2. //等号左边:auto形局部变量,存放在栈区;等号右边:常量,存放在数据区;
  3. int age = 10;
  4. //等号左边:auto形局部变量(指针),存放在栈区;等号右边:auto形局部变量地址,存放在栈区;
  5. int *agePtr = &age;
  6. //等号左边:auto形局部变量(指针),存放在栈区;等号右边:alloc开辟的对象,存放在堆区;
  7. NSObject *objc = [[NSObject alloc] init];
  8. }

 

2)block类型

<1>三种block类型(global、malloc、stack),从字面理解,可以推断依次存放在数据区、堆区、栈区;

<2>我们发现,blcok1没有访问任何变量,后两个block都访问量变量age,而age是一个auto类型的局部变量;似乎block的类型跟访问的变量有关系?往下看;

//代码

  1. int weight = 20;
  2. void test2()
  3. {
  4. static int age = 10;
  5. void(^block1)(void) = ^{
  6. NSLog(@"-----%d", age);
  7. };
  8. void(^block2)(void) = ^{
  9. NSLog(@"-----%d", weight);
  10. };
  11. NSLog(@"%@ %@", [block1 class], [block2 class]);
  12. }

//打印

  1. 2019-01-10 15:52:56.366509+0800 MJ_TEST[3852:215571] __NSGlobalBlock__ __NSGlobalBlock__
  2. Program ended with exit code: 0

分析:

<1>如果age是static修饰的局部变量,或者访问全局变量,则block的类型都是__NSGlobalBlock__,那么我们基本上可以肯定,block的类型取决于其访问的变量的属性;

<2>这里带来了一个新的问题:

因为auto类型的局部变量是存放在栈区的,而block要访问该变量,经前述文章分析,block会讲该变量捕获到block结构体内部,即重新开辟内存来存放该局部变量(相当于copy操作,但不是copy),那么此时的block自己是存放在哪个区呢?

前面说了,auto类型的局部变量一定是存放在栈区的,这点毋庸置疑,而block虽然新开辟内存来存放该变量,但改变不了该变量是一个auto类型的局部变量的属性,因此此时的block也只能存放在栈区;

既然存放在栈区,则访问的变量作用域仅限于离其最近的大括号范围内,超出则被自动释放,我们来验证下

//代码

  1. void(^block)(void);
  2. void test3()
  3. {
  4. int age = 10;
  5. block = ^{
  6. NSLog(@"----%d", age);
  7. };
  8. }
  9. int main(int argc, const char * argv[]) {
  10. @autoreleasepool {
  11. // test1();
  12. // test2();
  13. test3();
  14. block();
  15. }
  16. return 0;
  17. }

//打印

  1. 2019-01-10 16:09:19.489782+0800 MJ_TEST[3939:224126] -----272632456
  2. Program ended with exit code: 0

分析:

<1>age是一个auto类型的局部变量,作用域仅限于test3()函数,该函数一旦调用完毕,age则被自动释放(变成垃圾内存,值不确定);

<2>根据打印结果,age的值不是10而是一堆乱码,说明age已经被自动释放,block再次调用时,访问的是被废弃的内存;

那么如何才能不被自动释放?往下看

//代码

  1. void test4()
  2. {
  3. int age = 10;
  4. block = [^{
  5. NSLog(@"----%d", age);
  6. } copy];
  7. //错误写法
  8. // [block copy];
  9. }
  10. int main(int argc, const char * argv[]) {
  11. @autoreleasepool {
  12. // test1();
  13. // test2();
  14. // test3();
  15. test4();
  16. block();
  17. }
  18. return 0;
  19. }

//打印

  1. 2019-01-10 16:18:54.102162+0800 MJ_TEST[4004:229566] ----10
  2. Program ended with exit code: 0

分析:

<1>通过copy操作能达到auto类型的局部变量的值正确,为什么?因为copy是把age的值直接拷贝到了一块新的内存区域,而我们知道copy操作开辟的内存必定是在堆区;

<2>因此,防止一个auto类型的局部变量自动释放的方法,就是将其copy到堆区进行手动管理,达到对其生命周期可控的目的(所以记得要释放block);

说明:block此处的copy是深拷贝还是浅拷贝,以及深拷贝和浅拷贝的区别——后面文章会写到!

 

三、结论

1)blcok的类型取决于其访问的变量的类型:

【1】global:没有访问auto类型局部变量——包括:没有访问任何变量、访问了static类型的局部变量、访问了全局变量(包括static和auto类型);

【2】stack:访问了auto类型的局部变量;

【3】malloc:对block进行了copy操作;

2)存储位置:

 

 

 

GitHub

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号