说明:

<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. OpenGL学习--01--打开一个窗口

    // Include standard headers #include <stdio.h> #include <stdlib.h> // Include GLEW #incl ...

  2. 朴素贝叶斯文本分类实现 python cherry分类器

    贝叶斯模型在机器学习以及人工智能中都有出现,cherry分类器使用了朴素贝叶斯模型算法,经过简单的优化,使用1000个训练数据就能得到97.5%的准确率.虽然现在主流的框架都带有朴素贝叶斯模型算法,大 ...

  3. EventBus 3.0源码解析

    现在网上讲解EventBus的文章大多数都是针对2.x版本的,比较老旧,本篇文章希望可以给大家在新版本上面带来帮助. EventBus 是专门为Android设计的用于订阅,发布总线的库,用到这个库的 ...

  4. Pwn with File结构体(二)

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 最新版的 libc 中会对 vtable 检查,所以之前的攻击方式 ...

  5. HTTP请求封装:Ajax与RESTful API

    一.HTTP请求 HTTP即超文本传输协议,用以进行HTML 文件. 图片文件. 查询结果等的网络传输. 一个完整的HTTP请求包括:请求行.请求头.空行和请求数据(请求数据可以为空) HTTP1.1 ...

  6. easyui 笔记

    easyui-datagrid:loadFilter:处理服务器端传递过来的参数. 刷新datagrid:$("#xxx").datagrid('reload'): form 表单 ...

  7. maven问题总结

    1.maven下载jar包速度慢 1.maven下载jar包速度慢(解决办法) 现在maven项目非常流行,因为它对jar实行了一个非常方便的管理,我们可以通过在pom.xml文件中做对应的配置即可将 ...

  8. kvm 启动libvirtd时出现错误

    kvm 启动libvirtd市出现错误: /etc/init.d/libvirtd start启动 libvirtd 守护进程:libvirtd: relocation error: libvirtd ...

  9. [EXCEL] 不能清除剪贴板: We couldn't free up space on the clipboard. Another program might be using it right now

    Excel复制粘贴时出现以下错误,原因是有程序占用了剪切板. We couldn't free up space on the clipboard. Another program might be ...

  10. MVC 实现自定义404错误页

    直接进入正题. 在HomeController中有一个NotFound的Action方法. public ActionResult NotFound() { return View(); } 对应的视 ...