block本质探寻七之内存管理
说明:
<1>阅读本问,请参照block前述文章加以理解;
<2>环境:ARC;
<3>变量类型:基本数据类型或者对象类型的auto局部变量;
一、三种情形
//代码
void test1()
{
int num = ;
__block int age = ;
Person *per = [[Person alloc] init];
void(^block)(void) = ^{
NSLog(@"%d %d %p", num, age, per);
};
block();
}
//打印
-- ::38.974947+ MJ_TEST[:] 0x100654220
-- ::38.975258+ MJ_TEST[:] -[Person dealloc]
Program ended with exit code:
//clang:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
}; struct __test1_block_impl_0 {
struct __block_impl impl;
struct __test1_block_desc_0* Desc;
int num;
Person *__strong per;
__Block_byref_age_0 *age; // by ref
__test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _num, Person *__strong _per, __Block_byref_age_0 *_age, int flags=) : num(_num), per(_per), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; static void __test1_block_copy_0(struct __test1_block_impl_0*dst, struct __test1_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, /*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->per, (void*)src->per, /*BLOCK_FIELD_IS_OBJECT*/);} static void __test1_block_dispose_0(struct __test1_block_impl_0*src) {_Block_object_dispose((void*)src->age, /*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->per, /*BLOCK_FIELD_IS_OBJECT*/);} static struct __test1_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test1_block_impl_0*, struct __test1_block_impl_0*);
void (*dispose)(struct __test1_block_impl_0*);
} void test1()
{
int num = ;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*),(__Block_byref_age_0 *)&age, , sizeof(__Block_byref_age_0), };
Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
void(*block)(void) = ((void (*)())&__test1_block_impl_0((void *)__test1_block_func_0, &__test1_block_desc_0_DATA, num, per, (__Block_byref_age_0 *)&age, ));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
分析:根据前述文章可知
<1>ARC模式下,block对象(等号右边大括号)被强指针(等号左边)变量持有(默认为strong)时,系统会自动将block对象从栈区copy到堆区;
<2>当访问实例对象或者__block修饰的基础数据类型变量时,block对象结构体中自动会增加两个函数指针——copy和dispose,分别指向__test1_block_copy_0函数和__test1_block_dispose_0函数;
<3>__block修饰的基础数据类型的变量,系统会自动生成一个新的对象(__Block_byref_age_0),block对象通过该新的对象指针变量(__Block_byref_age_0 *age)来访问基础数据类型变量(int age);
<4>num:直接被block捕捉到其结构体内部,随block一起copy到堆区;其随block对象本身一起销毁(不管是栈区的block还是堆区的block);
<5>per:类对象的访问,当block对象被copy到堆区时,block对象会通过copy函数指针来调用__test1_block_copy_0函数完成对per对象的拷贝;同时此时堆区的block对象会根据该类对象的类型(strong还是weak)来决定对其强引用还是弱引用;
说明:所谓的强引用,是指指针指向某个对象(实质为,存放指针变量的内存指向对象的内存);所谓的指向,即是对该对象的引用计数加1即retain操作(即此时该指针持有该对象),但是一个对象可能被多个指针持有,因此realease并不必然导致对象销毁(内存回收)而只是被释放即某个指针变量不再指向(持有)该内存区域,只有当对象的引用计数为0时,系统才会自动回收其内存;
注:引用计数为对象的属性,而非指针;
<6>__block修饰的基本数据类型的变量和对象类型变量,block被拷贝时,都会通过调用__test1_block_copy_0函数中_Block_object_assign函数完成对自身的拷贝——其中,_Block_object_assign函数的第三个参数,8表示由__block修饰的基本数据类型变量,3表示访问的事对象类型变量(那么,__block修饰的对象类型是多少呢?往下看);
当block对象销毁时,二者都是通过调用__test1_block_dispose_0函数中的_Block_object_dispose函数来被销毁;
问题:为什么要增加copy和dispose两个函数指针呢?因为block对象要持有上述两种对象(__block修饰的基本数据类型生成的对象和per实例对象),那么自然要对其进行内存管理,达到持有/释放的可控目的;
因此,__block修饰生成的对象(会随着block的copy而一起被copy到堆区,而拷贝后的栈区的结构体依然会存在,只不过其作用域结束后,系统会对其内存自动回收),block对象要对其持有,肯定是强引用,否则弱引用,该对象的内存管理不受控制,那么block内部修改变量的值存在极大风险——这点没问题;
补充:__weak不能修饰基础类型变量
如上,我么知道,__weak仅能修饰对象类型变量和block指针类型——为什么?
以上我们已经分析过,__strong和__weak:前者指针持有对象达到对该对象内存管理可控的目的(只要该对象的引用计数>0,其内存就不可能被回收,指针就可以合法指向该内存),会进行retain操作即对象的retainCount会自动+1;后者不持有,该对象的内存管理不可控(什么时候释放,跟该指针没关系),不会retain,对象的引用计数不会自动+1;
所以,__strong和__weak修饰的目的是对堆区的内存管理是否管控,而只有对象类型的变量(在堆区创建)才会有管控的问题,基础数据类型变量起始是在栈区存储,其内存(创建/回收)由系统自动管理;
二、__forwarding指针
我们在前面的文章提到,block对int类型的age变量的访问,为什么还要通过__forwarding指针而不是直接访问__Block_byref_age_0结构体中的age变量呢?
分析:
<1>__forwarding指针本身是指向__Block_byref_age_0结构体本身;第一个age又是__Block_byref_age_0结构体类型的指针;第二个age是__Block_byref_age_0结构体中int型成员变量;
<2>在栈区:age->__forwarding->age <=> age->age 没有任何问题;但是在堆区:因为block对象结构体会被copy到堆区,而原先留在栈区的block中的__forwarding指针会自动指向堆区的__Block_byref_age_0结构体;
<3>从上述分析,我们很清楚地知道,将__Block_byref_age_0结构体一并copy到堆区的目的就是堆区的block对象强引用该结构体,所以指向堆区的block对象的各类指针(包括对象本身)都可以通过该block对象达到对__Block_byref_age_0结构体中age变量值的改变等操作的目的,而不必担心int型age变量内存随时会被系统回收的风险
————问题来了:如果栈区的指针或者block对象本身要对age变量的值进行修改,是要面临该风险的,那如何规避呢?
就是通过__forwarding指针,因为此时栈区的_Block_byref_age_0结构体中的__forwarding指针变量是指向堆区的_Block_byref_age_0结构体,除非堆区_Block_byref_age_0结构体内存被手动销毁,否则会一直存在;
//图解
三、__block修饰对象类型变量
//代码
void test2()
{
Person *per = [[Person alloc] init];
per.age = ;
__block Person *blockPer = per;
void(^block)(void) = ^{
blockPer.age = ;
NSLog(@"%d", blockPer.age);
};
block();
}
//打印
-- ::12.020409+ MJ_TEST[:]
-- ::12.020869+ MJ_TEST[:] -[Person dealloc]
Program ended with exit code:
//clang
struct __Block_byref_blockPer_1 {
void *__isa;
__Block_byref_blockPer_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *blockPer;
}; struct __test2_block_impl_0 {
struct __block_impl impl;
struct __test2_block_desc_0* Desc;
__Block_byref_blockPer_1 *blockPer; // by ref
__test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, __Block_byref_blockPer_1 *_blockPer, int flags=) : blockPer(_blockPer->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; static void __test2_block_copy_0(struct __test2_block_impl_0*dst, struct __test2_block_impl_0*src) {_Block_object_assign((void*)&dst->blockPer, (void*)src->blockPer, /*BLOCK_FIELD_IS_BYREF*/);} static void __test2_block_dispose_0(struct __test2_block_impl_0*src) {_Block_object_dispose((void*)src->blockPer, /*BLOCK_FIELD_IS_BYREF*/);} static struct __test2_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*);
void (*dispose)(struct __test2_block_impl_0*);
}
分析:
<1>我们发现,__block修饰的blockPerl实例对象,系统也会自动生成一个新的对象__Block_byref_blockPer_1;被引用的类对象以指针的形式存在于该结构体中(Person *blockPer),该指针指向[[Person alloc] init]这个实例对象(位于堆区);
<2>在__Block_byref_blockPer_1结构体中,还存在__Block_byref_id_object_copy和__Block_byref_id_object_dispose两个函数指针,分别指向__Block_byref_id_object_copy_131函数和__Block_byref_id_object_dispose_131函数(作用同上),如下:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + , *(void * *) ((char*)src + ), );
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + ), );
}
很显然,这两个函数的作用也是针对某个对象的内存管理,那是哪个对象呢?
首先,_Block_object_assign和_Block_object_dispose的第三个参数为131,是指__block修饰实例对象的情形;
其次,dst是__Block_byref_blockPer_1对象的地址,加40是什么?我们算出Person *blockPer指针的地址偏移量正好为40(8+8+4+4+8+8,指针变量占8个字节),那么可以肯定,上述两个函数就是对Person *blockPer指向的实例对象[[Person alloc] init]的内存管理;
所以,我们可以推测出以下结构:block对象__test2_block_impl_0通过其内部成员变量blockPer持有__Block_byref_blockPer_1对象,而__Block_byref_blockPer_1对象又通过其内部成员变量blockPer持有[[Person alloc] init]实例对象;
我们知道,前者通过_test2_block_copy_0函数和__test2_block_dispose_0函数进行内存管理,其持有必定是强引用,,这点没问题;而后者的持有是通过__Block_byref_id_object_copy_131函数和__Block_byref_id_object_dispose_131函数进行内存管理,但其持有是强引用还是弱引用呢?往下看;
//__weak修饰
__block __weak Person *blockPer = per;
//clang:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m(注:要设置runtime,否则编译会报错)
struct __Block_byref_blockPer_1 {
void *__isa;
__Block_byref_blockPer_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak blockPer;
};
//打印
-- ::20.776779+ MJ_TEST[:]
-- ::20.777113+ MJ_TEST[:] -[Person dealloc]
Program ended with exit code:
分析:
<1>我们发现,如果没有__weak修饰,blockPer格式为Person *blockPer,默认为strong类型;__weak修饰后,则会变成weak类型;
<2>根据之前的分析,其实__Block_byref_blockPer_1对象对[[Person alloc] init]实例对象的引用,取决于指向该实例对象的指针类型(因为对象引用是指针传递,前面已讲过);
------!!!但是,该规则仅限于ARC模式的情形,MRC模式下,如果指针是strong类型,系统并不会执行retain操作!!!
这里有个问题,为什么__weak修饰后,Person实例对象打印前没有被销毁呢?因为该实例对象的作用域在test2()函数体内,而block的回调也在函数体内,因此回调时,该实例对象并没有被销毁;
接下来,我们可以验证下:
分析:此时,block回调前,Person实例对象就被销毁了,说明block对象对实例对象的引用取决于Person对象指针的引用类型;
我们再切换到MRC模式下看看:
//代码
void test4()
{
Person *per = [[Person alloc] init]; MyBlock block = [^{
NSLog(@"%p", per);
} copy]; [per release]; block(); [block release];
}
//打印
-- ::05.667590+ MJ_TEST[:] 0x10061add0
-- ::05.668200+ MJ_TEST[:] -[Person dealloc]
Program ended with exit code:
分析:当per指针象release时,[[Person alloc] init]实例对象并没有被释放,而当block指针release时,[[Person alloc] init]实例对象才被释放(block对象不再持有该实例对象),这也印证了上述的分析;
四、注意
针对第三点(__block修饰对象类型的auto局部变量),系统生成的__block对象(__Block_byref_blockPer_1),其通过内部成员指针变量(Person *__weak blockPer)来持有实例对象([[Person alloc] init]),持有的类型取决于成员指针变量blockPer的类型(强指针则强引用,弱指针则弱引用)——该规则,在第三点情形下并且在MRC模式下,如果是强指针类型,则系统不会进行retain操作(除此,其他情况不受影响)!————所以,在MRC模式下,__block修饰对象类型的auto局部指针变量,新生成的__block对象永远不可能强引用该指针指向的实例对象!
block本质探寻七之内存管理的更多相关文章
- block本质探寻一之内存结构
一.代码——命令行模式 //main.m #import <Foundation/Foundation.h> struct __block_impl { void *isa; int Fl ...
- block本质探寻二之变量捕获
一.代码 说明:本文章须结合文章<block本质探寻一之内存结构>和<class和object_getClass方法区别>加以理解: //main.m #import < ...
- block本质探寻八之循环引用
说明:阅读本文,请参照之前的block文章加以理解: 一.循环引用的本质 //代码——ARC环境 void test1() { Person *per = [[Person alloc] init]; ...
- block本质探寻六之修改变量
说明: <1>阅读本文章,请参照前面的block文章加以理解: <2>本文的变量指的是auto类型的局部变量(包括实例对象): <3>ARC和MRC两种模式均适用: ...
- Block介绍(二)内存管理与其他特性
我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情 ...
- iOS中Block介绍(二)内存管理与其他特性
我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情 ...
- block本质探寻五之atuto类型局部实例对象
说明:阅读本文章,请参考之前的block文章加以理解: 一.栈区block分析 //代码 //ARC void test1() { { Person *per = [[Person alloc] in ...
- 第七章 内存管理单元MMU介绍
7.1 内存管理单元MMU介绍 7.1.1 S3C2410/S3C2440 MMU特性 负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查 特性: 与ARM V4兼容的映射长度.域.访问权 ...
- OC第七节——内存管理
戏言: iOS开发已经到了一个ARC时代,一般不需要我们过多的去关注内存是怎么分配,怎么管理的.很长一段时间,我也不知道内存管理是什么鬼,但如果遇到这方面的问题,却找不到解决办法确实很头疼的.So,还 ...
随机推荐
- 微信小程序为什么不被看好?
我自认为对新技术还是比较有热情的,可对于小程序这个“新技术”,我却完全是被动的.去年9月份的时候,微信小程序开始内测,瞬间引爆朋友圈.知乎等一众分享平台.当时我大概了解了一下,觉得从技术角度上来说没啥 ...
- 【转】Twitter Storm如何保证消息不丢失
Twitter Storm如何保证消息不丢失 发表于 2011 年 09 月 30 日 由 xumingming 作者: xumingming | 可以转载, 但必须以超链接形式标明文章原始出处和作者 ...
- centos7 部署 汉化版 gitlab 10.0.2
更新说明: 20171009:增加3.5的内容 20171008:整理出gitlab部署手册 =============================================== gitla ...
- 对象的数据属性(Object)
value: 对象属性的默认值,默认值为undefined configurable: 能否使用delete.能否需改属性特性.或能否修改访问器属性.,false为不可重新定义,默认值为true en ...
- git常用命令简集
基础操作: 初始化git仓库: git init 提交到暂存区: git add “filename” 提交到分支: git commit -m "注释" 工作区状态: git s ...
- 软工读书笔记 week 6 ——《疯狂的程序员》Part 1
这本小说以主人公绝影上大学后初次接触编程开始讲起,这周主要看的就是绝影还在大学的那段经历,虽然故事背景很多年前,但很多地方仍然会引发我的共鸣. 第一个梦想 在“第一个梦想”一节讲了作业布置做一个通讯录 ...
- 手动配置wnmp环境
wamp 是什么? windows,nginx,mysql,php(当然也可以是PYTHON等) 只所以使用nginx,是因为我等下要配置ZendGuardLoader ZendGuardLoader ...
- Django之模型注册
接着上一篇:Django之创建项目 目的:一个空项目创建好了,我们在models.py中新增3张表并在admin界面中显示,并能操作它们. 示例models 编辑models.py # -*- cod ...
- Linux查看磁盘读写
---------- 查看磁盘读写---------iostat -k 1 SQL> ho iostatLinux 2.6.32-279.el6.x86_64 (server-92) 08/1 ...
- 转:C# 线程同步技术 Monitor 和Lock
原文地址:http://www.cnblogs.com/lxblog/archive/2013/03/07/2947182.html 今天我们总结一下 C#线程同步 中的 Monitor 类 和 Lo ...