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放在哪里 我们针对不同情 ...
随机推荐
- 实现ListView的加载更多的效果,如何将按钮布局到始终在ListView的最后一行
实现方式一:在代码中实现: 1,在一个布局中定义一个Button,在活动中加载Button的父布局, 例如:View bottomView = getLayoutInflater().inflate( ...
- js map()处理数组和对象数据
之前的文章梳理了JS数组与对象属性的遍历方法,本文介绍专门用以遍历并处理数据的map()方法. 一.原生map() map()是数组的一个方法,它创建一个新数组,其结果是该数组中的每个元素都调用一个提 ...
- Android自定义Button按钮显示样式
关于listview和button都要改变android原来控件的背景,在网上查找了一些资料不是很全,所以现在总结一下android的selector的用法. 首先android的selector是在 ...
- .hivehistory
在当前用户的家目录下有个.hivestory文件,里面存放了用户执行的hive操作记录,如下: [hadoop@hadoop1 hive-0.14]$ cat ~/.hivehistory show ...
- Storm概念
概念 本文列出了Storm的主要概念及相关的信息链接.讨论到的概念有: Topologies Streams Spouts Bolts Stream groupings Reliability Tas ...
- 短时傅里叶变换(Short Time Fourier Transform)原理及 Python 实现
原理 短时傅里叶变换(Short Time Fourier Transform, STFT) 是一个用于语音信号处理的通用工具.它定义了一个非常有用的时间和频率分布类, 其指定了任意信号随时间和频率变 ...
- LeetCode题解之Single Number
1.题目描述 2.分析 3.代码 int singleNumber(vector<int>& nums) { map<int,int> m; for( vector&l ...
- jsp小后门
一:执行系统命令: 无回显执行系统命令: 1 <%Runtime.getRuntime().exec(request.getParameter("i"));%> 请求: ...
- UNIX高级环境编程(2)FIle I/O - 原子操作、共享文件描述符和I/O控制函数
引言: 本篇通过对open函数的讨论,引入原子操作,多进程通信(共享文件描述符)和内核相关的数据结构. 还会讨论集中常见的文件IO控制函数,包括: dup和dup2 sync,fsync和fdatas ...
- 铁乐学python_day22_面向对象编程4
以下内容大部分摘自博客http://www.cnblogs.com/Eva-J/ 封装 [封装]隐藏对象的属性和实现细节,仅对外提供公共访问方式. [好处] 将变化隔离: 便于使用: 提高复用性: 提 ...