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

说明:

<1>阅读本文章,请参照前面的block文章加以理解;

<2>本文的变量指的是auto类型的局部变量(包括实例对象);

<3>ARC和MRC两种模式均适用;

一、无法修改的原因

//代码

 

很明显,强行给age赋值会报错;

  1. void test1()
  2. {
  3. int age = 10;
  4. block = ^{
  5. // age = 20;
  6. NSLog(@"%d", age);
  7. };
  8. }

//打印

  1. 2019-01-15 15:00:43.641417+0800 MJ_TEST[3676:199449] 10
  2. Program ended with exit code: 0

分析:为什么在block内部不能改变age的值?往下看

//clang

  1. struct __test1_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __test1_block_desc_0* Desc;
  4. int age;
  5. __test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
  6. impl.isa = &_NSConcreteStackBlock;
  7. impl.Flags = flags;
  8. impl.FuncPtr = fp;
  9. Desc = desc;
  10. }
  11. };
  12. static void __test1_block_func_0(struct __test1_block_impl_0 *__cself) {
  13. int age = __cself->age; // bound by copy
  14. NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_d8e7e4_mi_0, age);
  15. }
  16. void test1()
  17. {
  18. int age = 10;
  19. block = ((void (*)())&__test1_block_impl_0((void *)__test1_block_func_0, &__test1_block_desc_0_DATA, age));
  20. }

分析:

<1>age被捕捉到block结构体中,根据输出的结果很明显是在ARC模式下,因此当被强指针变量block持有时,系统会自动将block对象从栈区拷贝到堆区;而MRC模式下,因为block对象会随着test1()方法结束,其内存地址会被回收,age的值为乱码

  1. 2019-01-15 16:12:43.234301+0800 MJ_TEST[4434:243013] -272632456
  2. Program ended with exit code: 0

<2>block代码块是通过__test1_block_func_0函数来实现,而该函数应用的age就是block对象结构体__test1_block_impl_0中的age,这跟test1()方法中的age是两个不同的age:前者存在于堆区,后者存在于栈区;

<3>想要在__test1_block_impl_0函数中去改变test1()方法中的局部变量,显然是不成立的,根本就拿不到该局部变量;

 结论:block对象本身的代码块是存放在一个新的函数中,而block引用的外部auto类型的局部变量存在于block指针变量所在的函数中——所以,两个不同的函数间彼此不可能改变对方内部的局部变量;

 

二、修改方法

1)static修饰

//代码

  1. void test2()
  2. {
  3. static int age = 10;
  4. block = ^{
  5. age = 20;
  6. NSLog(@"%d", age);
  7. };
  8. }

//打印

  1. 2019-01-15 16:31:56.533685+0800 MJ_TEST[4676:255859] 20
  2. Program ended with exit code: 0

//clang

  1. struct __test2_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __test2_block_desc_0* Desc;
  4. int *age;
  5. __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
  6. impl.isa = &_NSConcreteStackBlock;
  7. impl.Flags = flags;
  8. impl.FuncPtr = fp;
  9. Desc = desc;
  10. }
  11. };
  12. static void __test2_block_func_0(struct __test2_block_impl_0 *__cself) {
  13. int *age = __cself->age; // bound by copy
  14. (*age) = 20;
  15. NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_bf7285_mi_1, (*age));
  16. }

分析:

<1>根据前述文章,此时test2()方法中的整型age是以指针的形式被捕捉到block对象结构体中,该指针变量指向值为10的内存区域;

<2>通过指针当然可以变量该指针指向的内存区域的值(见“__test2_block_func_0”函数),这点没问题——C语言语法基础;

结论:通过static修饰auto类型的局部变量来改变值,其本质是通过指针来改变变量的值;

补充:static修饰的弊端

<1>修改了变量的属性类型——age由整型变量变成整型指针变量;

<2>static修饰的局部变量,是存放在数据区(全局区),直到整个程序结束才会释放内存——不利于内存的有效利用;

 

2)设置为全局变量

此处就不论证,很容易理解,block对象代码块是放在另一个函数中,而该函数是可以访问该全局变量的——这点没问题;

 

3)__block修饰

//代码

  1. void test3()
  2. {
  3. __block int age = 10;
  4. block = ^{
  5. age = 20;
  6. NSLog(@"%d", age);
  7. };
  8. }

//打印

  1. 2019-01-15 16:51:56.337321+0800 MJ_TEST[4909:268533] 20
  2. Program ended with exit code: 0

//clang

  1. struct __Block_byref_age_0 {
  2. void *__isa;
  3. __Block_byref_age_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. int age;
  7. };
  8. struct __test3_block_impl_0 {
  9. struct __block_impl impl;
  10. struct __test3_block_desc_0* Desc;
  11. __Block_byref_age_0 *age; // by ref
  12. __test3_block_impl_0(void *fp, struct __test3_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
  13. impl.isa = &_NSConcreteStackBlock;
  14. impl.Flags = flags;
  15. impl.FuncPtr = fp;
  16. Desc = desc;
  17. }
  18. };
  19. static void __test3_block_func_0(struct __test3_block_impl_0 *__cself) {
  20. __Block_byref_age_0 *age = __cself->age; // bound by ref
  21. (age->__forwarding->age) = 20;
  22. NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3fa1c2_mi_2, (age->__forwarding->age));
  23. }
  24. void test3()
  25. {
  26. __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
  27. block = ((void (*)())&__test3_block_impl_0((void *)__test3_block_func_0, &__test3_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
  28. }

分析:

<1>在test3()方法中,被__block修饰的age变量被转成__Block_byref_age_0类型的变量,而__Block_byref_age_0是一个结构体并且第一个成员变量是isa指针,那么可以肯定__Block_byref_age_0类型age是一个OC对象——即经__block修饰的auto类型的局部变量会被系统生成一个新的OC对象;

<2>__Block_byref_age_0结构体中:__forwarding是一个指向该结构体本身的指针变量;age就是被捕获到block结构体中的test3()方法中的age(ARC会被copy到堆区);

<3>在block对象的代码块函数__test3_block_func_0中,对整型变量age赋值流程:拿到block对象本身结构体中的成员变量age(__Block_byref_age_0类型指针变量)——>拿到新生成的OC对象结构体__Block_byref_age_0中的成员变量__forwarding——>拿到__Block_byref_age_0中的成员变量age;

补充:age->__forwarding->age <=> age->age,但是为什么通过__forwarding(要多一道手续)来拿到最终的整型变量age呢?——该问题后面文章会写到!

 

结论:通过__block修饰auto类型的局部变量来改变值,本质是系统会创建一个临时的OC对象,该对象结构体存储外部变量,而block对象结构体是通过该临时对象来访问外部变量;

补充:

<1> ARC模式强指针持有情况下,该OC临时对象很显然是存放在栈区——否则,test3()方法结束后block回调时,不能正确对age变量赋值(会崩溃)——此处涉及block的内存管理问题,后面文章会写到!

<2>该方法并不会改变局部变量的类型,age其依然是atuo int类型;

<3>__block不能修饰static变量和全局变量

——因为__block就是为了在block代码块中修改外部auto类型的局部变量的值而设计的!

 

三、结论

【1】在block代码块中修改外部auto类型的局部变量的值:用static修饰、设置为全局变量、__block修饰;

【2】static修饰和设置为全局变量弊端:持续占有内存,不利于内存的高效利用;变量的生命周期不可控——__block反之;

【3】__block不能修饰static变量和全局变量;

注:以上对局部实例对象也适用——此处就不再论证了!

 

四、拓展——查找age地址值

//代码

  1. void test4()
  2. {
  3. __block int age = 10;
  4. block = ^{
  5. age = 20;
  6. NSLog(@"%d", age);
  7. };
  8. NSLog(@"%p", &age);
  9. }

//打印

  1. 2019-01-15 17:58:15.930081+0800 MJ_TEST[5583:308759] 0x100701f38
  2. 2019-01-15 17:58:15.930453+0800 MJ_TEST[5583:308759] 20
  3. Program ended with exit code: 0

分析:打印出的age地址,据上述分析,到底是新生成的oc对象本身的地址,还是该对象结构体内成员变量age的值?

//代码

  1. struct __Block_byref_age_0 {
  2. void *__isa;
  3. struct __Block_byref_age_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. int age;
  7. };
  8. struct __block_impl {
  9. void *isa;
  10. int Flags;
  11. int Reserved;
  12. void *FuncPtr;
  13. };
  14. struct __test3_block_desc_0 {
  15. size_t reserved;
  16. size_t Block_size;
  17. void (*copy)(void);
  18. void (*dispose)(void);
  19. };
  20. struct __test4_block_impl_0 {
  21. struct __block_impl impl;
  22. struct __test3_block_desc_0* Desc;
  23. struct __Block_byref_age_0 *age;
  24. };

//打印

 

分析:

<1>上述block的桥接转换和&(strBlock->age->age),前面的文章已经讲过,此处不再赘述;

<2>我们发现,__Block_byref_age_0结构体内的成员变量age的地址和test4()方法中打印出的age的地址是一样的——也就是说,我们在OC代码中对age的操作都是对__Block_byref_age_0结构体内的成员变量age的操作,这样有利于程序员的理解(苹果公司刻意隐藏底层)!

 

 

 GitHub

原文链接:http://www.cnblogs.com/lybSkill/p/10273443.html

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

本站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号