block本质探寻二之变量捕获
一、代码
说明:本文章须结合文章《block本质探寻一之内存结构》和《class和object_getClass方法区别》加以理解;
//main.m
#import <Foundation/Foundation.h> int a = ;
static int b = ; int main(int argc, const char * argv[]) {
@autoreleasepool { auto int c = ;
static int d = ; void (^block)(void) = ^{
NSLog(@"a=%d, b=%d, c=%d, d=%d", a, b, c, d);
}; a = ;
b = ;
c = ;
d = ; block();
}
return ;
}
//打印
-- ::16.246684+ MJ_TEST[:] a=, b=, c=, d=
Program ended with exit code:
分析:很显然,只有c的值没有改变,其它变量的值都改变了——为什么,看下底层代码实现;
二、main.cpp
int a = ;
static int b = ; struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int c;
int *d;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_d, int flags=) : c(_c), d(_d) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int c = __cself->c; // bound by copy
int *d = __cself->d; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_1f1f41_mi_0, a, b, c, (*d));
} static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { , sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; auto int c = ;
static int d = ; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &d)); a = ;
b = ;
c = ;
d = ; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return ;
}
分析:
1)C语言语法
<1>int c被转换成auto int c:我们知道c、d为局部变量,而a、b为全局变量,C语言中,所有没有修饰符的局部变量默认的修饰符为auto,static修饰的变量为静态变量,还有一个register注册类的在此不再赘述(自己有兴趣上网查下);
<2>auto类型的局部变量的生命周期为离其最近的大括号内,超出该大括号,该变量被自动销毁;
<3>static类型的变量(不论是全局还是局部),其值一直保留在内存中,不受大括号的限制,程序结束时才被销毁;
2)变量捕获概念
我们发现在block结构体中,存在c、d而不存在a、b变量,在此,我们把存在于block结构体中的外部变量称为变量捕获(存在的形式在所不问),不存在的则没有被捕获,所以a、b变量没有被block捕获;
3)变量调用流程
<1>在block结构体中,c保持不变依然为int型变量,而d被转换成int型指针变量,因此在main函数中通过__main_block_impl_0方法传递实参c本身的值和d指向的内存的值&d;
而在block的构造函数中,c(_c), d(_d)为C++语法<=>c = _c,d = _d,那么main函数中的实参c、&d最终传递给了block结构体中的变量c和指针变量d;
<2>最后在__main_block_func_0方法中,对c、d而言,须先获取block内部的成员变量再输出;而对于a、b,因为是全局变量,所以可以直接引用;
综上所述:
auto局部变量因为作用域(或生命周期)有限,随时会销毁,故block在引用时系统会自动将其值保存在block结构体中(即捕获);而全局变量和static修饰的变量(局部或全局),并不会随时被销毁,其值一直会在内存中保持不变,知道整个程序结束时才销毁
1)另外从另一个角度理解,全局变量其作用域为从其定义的地方开始到该文件结束止都是有效的,所以main函数中可以用,__main_block_func_0函数中也可以用,不需要再将其保存到block自身的结构体中;
2)static修饰的局部变量会被转化成指针变量,而保存到block结构体中也是指针,因为指针本身的值为另一个变量的地址,所以block对该指针的操作始终是对另一个变量的地址的操作,而非另一个变量值的本身,当对d重新赋值时,block中的指针变量指向的变量的值也就随之改变,对*d输出当然被改变(*d即取出指向的内存地址存放的值);
3)auto局部变量被捕获,即是在内存中重新开辟了内存来存放该变量的值(即copy),只不过是在block结构体对应的内存中;
三、结论
1.

2.auto修饰的局部变量在block定义后的修改,不影响block内部对该变量的使用;后两者,有影响;
四、扩展——OC对象捕获问题
1)Person.m——注:此处我将.h文件也贴过来了,为了很好的阅读
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject @property (nonatomic, copy) NSString *name; - (instancetype)initWithName:(NSString *)name; @end NS_ASSUME_NONNULL_END #import "Person.h" int weight_ = ; @implementation Person - (void)test
{
void(^block)(void) = ^{
NSLog(@"-----%p", self);
NSLog(@"-----%@", _name);
NSLog(@"-----%@", self.name);
NSLog(@"-----%d", weight_);
};
} - (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) { }
return self;
} @end
2)Person.cpp——注:此处只对.m文件进行转化
问题一:参数
static void _I_Person_test(Person * self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, ));
}
static instancetype _Nonnull _I_Person_initWithName_(Person * self, SEL _cmd, NSString * _Nonnull name) {
self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
if (self) {
}
return self;
}
分析:
<1>在.m文件内部有两个方法test和initWithName,前者不带参数,后者带一个参数name,但是转成C++后,发现,两个方法前面均自动加上了两个参数:Person * self, SEL _cmd;这是每个方法必备的两个参数,前者是调用对象本身self,后者是方法名;
<2>此处的self为实例对象而非类对象(验证方法:在test方法中打印self的地址%p,会发现每次调用的值都不一样,而类对象在内存中只有一份;
问题二:self捕获
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
分析:
<1>self被捕获到block结构体体中,那么可以肯定self是auto类型的局部变量;
<2>从另一个角度理解:self作为实参从main函数中传递到block结构体的构造函数__Person__test_block_impl_0的形参_self,再将_slef赋值于self;而参数本身就是一个auto类型的局部变量,函数结束后就自动被销毁;
问题三:block代码块执行
int weight_ = ;
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_0, self);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_1, (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_name)));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_2, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_3, weight_);
}
分析:
<1>self作为auto类型的局部变量,输出前需先从block结构体中取出该成员变量;
<2>weight作为全局变量,直接引用,无须捕获;
<3>以_name来引用对象属性,其本质是block的成员变量,存放在类对象的结构体内存中,而结构体指针变量须通过"->"来引用该结构体成员变量,但是self作为实例对象与Person类对象不是同一个,问什么能通过"->"来引用?

————原因:self的第一个成员变量为isa,而isa是指向类对象的指针,即类对象的首地址跟实例对象的首地址是同一个地址,而结构体成员变量在内存中的地址是连续的,因此self可以通过"->"形式来找到_name成员变量;
<4>self.name即通过getter方法来访问name值,转换成C++为objc_msgSend方法,即通过消息转发机制来访问name值,而消息转发机制的本质是通过isa来找到类对象,进而访问该类对象中的name成员变量;
3)结论
【1】oc实例对象self会被捕获到block结构体中;
【2】@property声明的属性的引用,须先执行【1】步骤,因此严格上讲也是被捕获到block中;
【3】.m文件中声明的全局变量,不受self影响,依然不会被捕获到block中,;
block本质探寻二之变量捕获的更多相关文章
- block本质探寻八之循环引用
说明:阅读本文,请参照之前的block文章加以理解: 一.循环引用的本质 //代码——ARC环境 void test1() { Person *per = [[Person alloc] init]; ...
- block本质探寻三之block类型
一.oc代码 提示:看本文章之前,最好按顺序来看: //代码 void test1() { ; void(^block1)(void) = ^{ NSLog(@"block1----&quo ...
- block本质探寻一之内存结构
一.代码——命令行模式 //main.m #import <Foundation/Foundation.h> struct __block_impl { void *isa; int Fl ...
- block本质探寻六之修改变量
说明: <1>阅读本文章,请参照前面的block文章加以理解: <2>本文的变量指的是auto类型的局部变量(包括实例对象): <3>ARC和MRC两种模式均适用: ...
- block本质探寻五之atuto类型局部实例对象
说明:阅读本文章,请参考之前的block文章加以理解: 一.栈区block分析 //代码 //ARC void test1() { { Person *per = [[Person alloc] in ...
- block本质探寻四之copy
说明: <1>阅读本文,最好阅读之前的block文章加以理解: <2>本文内容:三种block类型的copy情况(MRC).是否深拷贝.错误copy: 一.MRC模式下,三种b ...
- block本质探寻七之内存管理
说明: <1>阅读本问,请参照block前述文章加以理解: <2>环境:ARC: <3>变量类型:基本数据类型或者对象类型的auto局部变量: 一.三种情形 //代 ...
- iOS开发系列-Block本质篇
概述 在iOS开发中Block使用比较广泛,对于使用以及一些常规的技术点这里不再赘述,主要利用C++角度分析Block内部数据底层实现,解开开发中为什么这样编写代码解决问题. Block底层结构窥探 ...
- Block介绍(二)内存管理与其他特性
我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情 ...
随机推荐
- 如何利用API导出带有页眉页脚的excel
在报表中设置的页眉页脚在页面中是看不到的,如下图: 页面中的效果: 在打印的时候,可以看到页眉页脚的效果: 那么,如果将页眉页脚导入到导出的excel中呢.我们可以通过API来进行设置: < ...
- opengl学习笔记
准备: 1.准备资源:从GLEW1.13.0下载GLEW,并且解压出glew-1.13.0目录.从FreeGLUT官网下载3.0.0版本.直接从这里下的编译后的FreeGLUT,选for MSVC,下 ...
- Java 8方法引用使用指南
[编者按]本文作者为拥有15年 Java 开发经验的资深程序员 Per-Åke Minborg,主要介绍如何灵活地解析 Java 中的方法引用.文章系国内 ITOM 管理平台 OneAPM 编译呈现. ...
- ASP.NET中使用UpdatePanel时用Response输出出现错误的解决方法
asp.net中执行到Response.write("xx");之类语句或Microsoft JScript 运行时错误: Sys.WebForms.PageRequestMana ...
- 附加到SQL2012的数据库就不能再附加到低于SQL2012的数据库版本
附加到SQL2012的数据库就不能再附加到低于SQL2012的数据库版本 昨天我只是将数据库附加到SQL2012,然后各个数据库都做了收缩事务日志的操作 兼容级别这些都没有改 再附加回SQL2005的 ...
- .Net Core+Vue.js+ElementUI 实现前后端分离
.Net Core+Vue.js+ElementUI 实现前后端分离 Tags: Vue 架构 前端采用:Vue.js.Element-UI.axios 后端采用:.Net Core Mvc 本项目是 ...
- 转:java 委托
委托模式是软件设计模式中的一项基本技巧.在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理.委托模式是一项基本技巧,许多其他的模式,如状态模式.策略模式.访问者模式 ...
- Linux ifconfig命令详解
ifconfig(interfaces config).通常需要以root身份登录或使用sudo来使用ifconfig工具 ifconfig 命令用来查看和配置网络设备.当网络环境发生改变时可通过此命 ...
- hibernate连接mysql,查询条件中有中文时,查询结果没有记录,而数据库有符合条件的记录(解决方法)
今天在另一台服务器上重新部署了网站,结果出现了以下问题: ——用hibernate做mysql的数据库连接时,当查询条件中有中文的时候,查询结果没有记录,而数据库中是存在符合条件的记录的. 测试了以下 ...
- 缓存知识整理(包含Redis)
一.缓存知识 1.buffer和cache的区别 Buffer 缓冲 写操作 写缓冲 Cache 缓存 读操作 读缓存 磁盘-->内存-->CPU 2.PHP的缓存方案 官方文档:h ...