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放在哪里 我们针对不同情 ...
随机推荐
- React Native常用组件样式总结
在react 中,有时要使用 style 指定样式 ,如要跟随放大比例关系,展示图标. const stylebutton = {width:25*scalesize, height:25*scale ...
- 对抗网络GAN的应用实例
https://sigmoidal.io/beginners-review-of-gan-architectures/ 嗨,大家好!像许多追随AI进展的人一样,我无法忽略生成建模的最新进展,尤其是 ...
- java实现Kafka生产者示例
使用java实现Kafka的生产者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 3 ...
- redis介绍(7)高级用法
redis的过期策略以及内存淘汰机制 分析:这个问题其实相当重要,到底redis有没用到家,这个问题就可以看出来.比如你redis只能存5G数据,可是你写了10G,那会删5G的数据.怎么删的,这个问题 ...
- ansible 一键部署
start install: rpm -Uvh https://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm yum in ...
- ubuntu下配置JDK7环境变量
ubuntu下JDK配置本质上和win是一样的: 1.去官网下载JDK7,找jdk-7u21-linux-i586.tar.gz并下载:http://www.oracle.com/technetwor ...
- MVC 实现自定义404错误页
直接进入正题. 在HomeController中有一个NotFound的Action方法. public ActionResult NotFound() { return View(); } 对应的视 ...
- c# 托管和非托管的介绍
在.net 编程环境中,系统的资源分为托管资源和非托管资源. 对于托管的资源的回收工作,是不需要人工干预回收的,而且你也无法干预他们的回收,所能够做的 只是了解.net CLR如何做这些操作.也就是说 ...
- GIT学习---GIT&github的使用
GIT&github入门 版本控制的原理: 根据md5进行文件的校验[MD5的特性就是每次的输入一致则输出也一致],对于每次的修改进行一次快照 版本控制的2个功能: 版本管理 + 协作开 ...
- lsync目录文件实时同步工具
参考文档:https://vastxiao.github.io/article/2017/09/02/Linux/lsyncd_usage/ 防止连接丢失,已保存至百度网络-郑州-XXXXX 建议首先 ...