说明:

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

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

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

一、无法修改的原因

//代码

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

void test1()
{
int age = ;
block = ^{
// age = 20;
NSLog(@"%d", age);
};
}

//打印

-- ::43.641417+ MJ_TEST[:]
Program ended with exit code:

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

//clang

struct __test1_block_impl_0 {
struct __block_impl impl;
struct __test1_block_desc_0* Desc;
int age;
__test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _age, int flags=) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; static void __test1_block_func_0(struct __test1_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_d8e7e4_mi_0, age);
} void test1()
{
int age = ;
block = ((void (*)())&__test1_block_impl_0((void *)__test1_block_func_0, &__test1_block_desc_0_DATA, age));
}

分析:

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

-- ::43.234301+ MJ_TEST[:] -
Program ended with exit code:

<2>block代码块是通过__test1_block_func_0函数来实现,而该函数应用的age就是block对象结构体__test1_block_impl_0中的age,这跟test1()方法中的age是两个不同的age:可以通过打印两个age地址发现,他们的地址确实不一样,如果是ARC,前者存在于堆区,后者存在于栈区;而MRC,都在栈区,但内存地址不一样;

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

但为什么修改内部age会报错?

苹果设计的初衷就是要保持内外部变量的一致性即同一个变量(有利于程序员的理解),__block修饰就是实现这个一致性,后面查找age地址验证会提到!

二、修改方法

1)static修饰

//代码

void test2()
{
static int age = ;
block = ^{
age = ;
NSLog(@"%d", age);
};
}

//打印

-- ::56.533685+ MJ_TEST[:]
Program ended with exit code:

//clang

struct __test2_block_impl_0 {
struct __block_impl impl;
struct __test2_block_desc_0* Desc;
int *age;
__test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, int *_age, int flags=) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; static void __test2_block_func_0(struct __test2_block_impl_0 *__cself) {
int *age = __cself->age; // bound by copy (*age) = ;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_bf7285_mi_1, (*age));
}

分析:

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

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

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

补充:static修饰的弊端

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

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

2)设置为全局变量

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

3)__block修饰

//代码

void test3()
{
__block int age = ;
block = ^{
age = ;
NSLog(@"%d", age);
};
}

//打印

-- ::56.337321+ MJ_TEST[:]
Program ended with exit code:

//clang

struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
}; struct __test3_block_impl_0 {
struct __block_impl impl;
struct __test3_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__test3_block_impl_0(void *fp, struct __test3_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; static void __test3_block_func_0(struct __test3_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref (age->__forwarding->age) = ;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3fa1c2_mi_2, (age->__forwarding->age));
} void test3()
{
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*),(__Block_byref_age_0 *)&age, , sizeof(__Block_byref_age_0), };
block = ((void (*)())&__test3_block_impl_0((void *)__test3_block_func_0, &__test3_block_desc_0_DATA, (__Block_byref_age_0 *)&age, ));
}

分析:

<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变量和全局变量;

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

注:如果在block代码块中对可变数组执行添加数组元素的操作,不需要用__block修饰数组指针,因为添加元素并不是改变数组的值(具体指数组指针的地址);

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

//代码

void test4()
{
__block int age = ;
block = ^{
age = ;
NSLog(@"%d", age);
}; NSLog(@"%p", &age);
}

//打印

-- ::15.930081+ MJ_TEST[:] 0x100701f38
-- ::15.930453+ MJ_TEST[:]
Program ended with exit code:

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

//代码

struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
}; struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
}; struct __test3_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
}; struct __test4_block_impl_0 {
struct __block_impl impl;
struct __test3_block_desc_0* Desc;
struct __Block_byref_age_0 *age;
};

//打印

分析:

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

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

GitHub

block本质探寻六之修改变量的更多相关文章

  1. block本质探寻二之变量捕获

    一.代码 说明:本文章须结合文章<block本质探寻一之内存结构>和<class和object_getClass方法区别>加以理解: //main.m #import < ...

  2. block本质探寻八之循环引用

    说明:阅读本文,请参照之前的block文章加以理解: 一.循环引用的本质 //代码——ARC环境 void test1() { Person *per = [[Person alloc] init]; ...

  3. block本质探寻七之内存管理

    说明: <1>阅读本问,请参照block前述文章加以理解: <2>环境:ARC: <3>变量类型:基本数据类型或者对象类型的auto局部变量: 一.三种情形 //代 ...

  4. block本质探寻一之内存结构

    一.代码——命令行模式 //main.m #import <Foundation/Foundation.h> struct __block_impl { void *isa; int Fl ...

  5. block本质探寻三之block类型

    一.oc代码 提示:看本文章之前,最好按顺序来看: //代码 void test1() { ; void(^block1)(void) = ^{ NSLog(@"block1----&quo ...

  6. block本质探寻五之atuto类型局部实例对象

    说明:阅读本文章,请参考之前的block文章加以理解: 一.栈区block分析 //代码 //ARC void test1() { { Person *per = [[Person alloc] in ...

  7. block本质探寻四之copy

    说明: <1>阅读本文,最好阅读之前的block文章加以理解: <2>本文内容:三种block类型的copy情况(MRC).是否深拷贝.错误copy: 一.MRC模式下,三种b ...

  8. iOS开发系列-Block本质篇

    概述 在iOS开发中Block使用比较广泛,对于使用以及一些常规的技术点这里不再赘述,主要利用C++角度分析Block内部数据底层实现,解开开发中为什么这样编写代码解决问题. Block底层结构窥探 ...

  9. Idea批量修改变量名

    Idea批量修改变量名.在变量名上进行rename操作,所有的同名变量都会自动更改. 快捷键:ALT+SHIFT+R

随机推荐

  1. CSS 0.5px 细线边框的原理和实现方式

    细线边框的具体实现方法有:伪元素缩放或渐变,box-shadow模拟,svg画线,border-image裁剪等.要实现小于1px的线条,有个先决条件:屏幕的分辨率要足够高,设备像素比要大于1,即cs ...

  2. requestURI的组成部分

    使用 java EE HttpServletRequest对象获取的 request.getRequestURL(); request.getRequestURI(); request.getCont ...

  3. java 内存分析之堆栈空间

    package Demo; public class Demo { public static void main(String[] args) { Demo demo = new Demo(); ; ...

  4. Django 自定义分页

    1.路由 urls.py url(r'^index2.html/', views.index2), 2.views.py def index2(request): # 数据总条数 当前页 每页显示条数 ...

  5. springboot学习入门之五---开发Web应用之JSP篇

    转载:http://tengj.top/2017/03/13/springboot5/ 1整体结构 整体的框架结构,跟前面介绍Thymeleaf的时候差不多,只是多了webapp这个用来存放jsp的目 ...

  6. leetCode题解之Number of Lines To Write String

    1.题目描述 2.分析 使用一个map将字母和数字对应起来,方便后续使用. 3.代码 vector<int> numberOfLines(vector<int>& wi ...

  7. 网站源IP暴露使用高防之后还行不行如何解决?

    如题:使用高防后源站IP暴露的解决办法 在购买高防IP后,如果还存在攻击绕过高防直接打到源站IP的情况,就需要更换下源站IP了.但在这之前,请务必排查确认没有其他可能暴露源站IP的因素后,再去更换源站 ...

  8. Oracle EBS 附件功能

    SELECT fde.table_name, fde.data_object_code, fdet.user_entity_name, fdet.user_entity_prompt, fat.app ...

  9. java:通过Calendar类正确计算两日期之间的间隔

    在开发Android应用时偶然需要用到一个提示用户已用天数的功能,从实现上来看无非就是持久化存入用户第一次使用应用的时间firstTime(通过SharedPreferences .xml.sqlit ...

  10. C# 获取两个时间段之间的所有时间与获取当前时间所在的季度开始和结束时间

    一:C# 获取两个时间段之间的所有时间 public List<string> GetTimeList(string rq1, string rq2) { List<string&g ...