block(四)揭开神秘面纱(下)
看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开):
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/Block_private.h
内存管理的真面目
objc层面如何区分不同内存区的block
Block_private.h中有这样一组值:
/* the raw data space for runtime classes for blocks */ /* class+meta used for stack, malloc, and collectable based blocks */ BLOCK_EXPORT void * _NSConcreteStackBlock[32]; BLOCK_EXPORT void * _NSConcreteMallocBlock[32]; BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]; BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其用于对block的isa指针赋值
1.栈
1
2
3
4
5
6
7
8
9
10
11
|
struct __OBJ1__of2_block_impl_0 { struct __block_impl impl; struct __OBJ1__of2_block_desc_0* Desc; OBJ1 * self ; __OBJ1__of2_block_impl_0( void *fp, struct __OBJ1__of2_block_desc_0 *desc, OBJ1 *_self, int flags= 0 ) : self (_self) { impl.isa = & _NSConcreteStackBlock ; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; |
在栈上创建的block,其isa指针是_NSConcreteStackBlock。
2.全局区
在全局区创建的block,其比较类似,其构造函数会将isa指针赋值为_NSConcreteGlobalBlock。
3.堆
我们无法直接创建堆上的block,堆上的block需要从stack block拷贝得来,在runtime.c中的_Block_copy_internal函数中,有这样几行:
1
2
3
4
5
6
7
8
|
// Its a stack block. Make a copy. if (!isGC) { struct Block_layout *result = malloc(aBlock->descriptor->size); ... result->isa = _NSConcreteMallocBlock ; ... return result; } |
可以看到,栈block复制得来的新block,其isa指针会被赋值为_NSConcreteMallocBlock
4.其余的isa类型
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]; BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]; BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其他三种类型是用于gc和arc,我们暂不讨论
复制block
对block调用Block_copy方法,或者向其发送objc copy消息,最终都会调用runtime.c中的_Block_copy_internal函数,其内部实现会检查block的flag,从而进行不同的操作:
1
2
3
4
5
|
static void *_Block_copy_internal( const void *arg, const int flags) { ... aBlock = ( struct Block_layout *)arg; ... } |
1.栈block的复制
1
2
3
4
5
6
7
8
|
// reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1 ; result->isa = _NSConcreteMallocBlock ; if (result->flags & BLOCK_HAS_COPY_DISPOSE) { //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock); (*aBlock->descriptor-> copy )(result, aBlock); // do fixup } |
除了修改isa指针的值之外,拷贝过程中,还会将BLOCK_NEEDS_FREE置入,大家记住这个值,后面会用到。
最后,如果block有辅助copy/dispose函数,那么辅助的copy函数会被调用。
2.全局block的复制
1
2
3
|
else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } |
全局block进行copy是直接返回了原block,没有任何的其他操作。
3.堆block的复制
1
2
3
4
5
|
if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } |
栈block复制时,置入的BLOCK_NEEDS_FREE标记此时起作用,_Block_copy_internal函数识别当前block是一个堆block,则仅仅增加引用计数,然后返回原block。
辅助copy/dispose函数
1.普通变量的复制
辅助copy函数用于拷贝block所引用的可修改变量,我们这里以 __block int i = 1024为例:
先看看Block_private.h中的定义:
1
2
3
4
5
6
7
8
9
|
struct Block_byref { void *isa; struct Block_byref *forwarding; int flags; /* refcount; */ int size; void (*byref_keep)( struct Block_byref *dst, struct Block_byref *src); void (*byref_destroy)( struct Block_byref *); /* long shared[0]; */ }; |
而我们的__block int i = 1024的转码:
1
2
3
4
5
6
7
8
9
|
struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i; }; //所以我们知道,当此结构体被类型强转为Block_byref时,前四个成员是一致的,访问flags就相当于访问__flags,而内部实现就是这样使用的 ... __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {( void *) 0 ,(__Block_byref_i_0 *)&i, 0 , sizeof (__Block_byref_i_0), 1024 }; //i初始化时__flags为0 |
1
|
static void __main_block_copy_0( struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign(( void *)&dst->i, ( void *)src->i, 8 /*BLOCK_FIELD_IS_BYREF*/ );} |
此时,复制时调用的辅助函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
void _Block_object_assign( void *destAddr, const void *object, const int flags) { //此处flags为8,即BLOCK_FIELD_IS_BYREF ... if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) { // copying a __block reference from the stack Block to the heap // flags will indicate if it holds a __weak reference and needs a special isa _Block_byref_assign_copy(destAddr, object, flags); } ... } static void _Block_byref_assign_copy( void *dest, const void *arg, const int flags) { //此处flags为8,即BLOCK_FIELD_IS_BYREF struct Block_byref **destp = ( struct Block_byref **)dest; struct Block_byref *src = ( struct Block_byref *)arg; ... else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0 ) { //当初次拷贝i时,flags为0,进入此分支会进行复制操作并改变flags值,置入BLOCK_NEEDS_FREE和初始的引用计数 ... } // already copied to heap else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) { //当再次拷贝i时,则仅仅增加其引用计数 latching_incr_int(&src->forwarding->flags); } // assign byref data block pointer into new Block _Block_assign(src->forwarding, ( void **)destp); //这句仅仅是直接赋值,其函数实现只有一行赋值语句,查阅runtime.c可知 } |
所以,我们知道,当我们多次copy一个block时,其引用的__block变量只会被拷贝一次。
2.objc变量的复制
当objc变量没有__block修饰时:
1
|
static void __OBJ1__of2_block_copy_0( struct __OBJ1__of2_block_impl_0*dst, struct __OBJ1__of2_block_impl_0*src) {_Block_object_assign(( void *)&dst-> self , ( void *)src-> self , 3 /*BLOCK_FIELD_IS_OBJECT*/ );} |
1
2
3
4
5
6
7
8
9
10
|
void _Block_object_assign( void *destAddr, const void *object, const int flags) { ... else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) { //printf("retaining object at %p\n", object); _Block_retain_object(object); //当我们没有开启arc时,这个函数会retian此object //printf("done retaining object at %p\n", object); _Block_assign(( void *)object, destAddr); } .... } |
当objc变量有__block修饰时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
struct __Block_byref_bSelf_0 { void *__isa; __Block_byref_bSelf_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)( void *, void *); void (*__Block_byref_id_object_dispose)( void *); OBJ1 *bSelf; }; static void __Block_byref_id_object_copy_131( void *dst, void *src) { _Block_object_assign(( char *)dst + 40 , *( void * *) (( char *)src + 40 ), 131 ); //131即为BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER } static void __Block_byref_id_object_dispose_131( void *src) { _Block_object_dispose(*( void * *) (( char *)src + 40 ), 131 ); } ... //33554432即为BLOCK_HAS_COPY_DISPOSE __block __Block_byref_bSelf_0 bSelf = {( void *) 0 ,(__Block_byref_bSelf_0 *)&bSelf, 33554432 , sizeof (__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self }; |
BLOCK_HAS_COPY_DISPOSE告诉内部实现,这个变量结构体具有自己的copy/dispose辅助函数,而此时我们的内部实现不会进行默认的复制操作:
1
2
3
4
5
6
7
8
9
10
11
|
void _Block_object_assign( void *destAddr, const void *object, const int flags) { //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags); if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) { if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) { _Block_assign_weak(object, destAddr); } else { // do *not* retain or *copy* __block variables whatever they are _Block_assign(( void *)object, destAddr); } } |
当我们没有开启arc,且flags中具有BLOCK_BYREF_CALLER时,会进入_Block_assign函数,而此函数仅仅是赋值
所以,如果要避免objc实例中的block引起的循环引用,我们需要让block间接使用self:
__block bSelf = self;
其他
对于dipose辅助函数,其行为与copy是类似的,我们不再重复同样的东西,如果大家要了解,自行查阅runtime.c和Block_private.h即可。
我们已经理解了非arc非gc情况下的block的内存管理内部实现,对arc和gc的情况,其行为也是类似的,只是一些函数的指针指向的真正函数会改变,比如_Block_use_GC函数,会将一些函数指向其他的实现,使其适用于gc开启的情况。
小结
block实际上是一些执行语句和语句需要的上下文的组合,而runtime给予的内部实现决定了它不会浪费一比特的内存。
我们知道cocoa中的容器类class有mutable和immutable之分,实际上我们可以将block看做一个immutable的容器,其盛放的是执行的代码和执行此代码需要的变量,而一个immutable变量的无法改变的特质,也决定了block在复制时,的确没有必要不断分配新的内存。故而其复制的行为会是增加引用计数。
最后,参考资料列表如下
http://thirdcog.eu/pwcblocks/#cblocks-memory http://blog.csdn.net/jasonblog/article/details/7756763 http://clang.llvm.org/docs/Block-ABI-Apple.html http://www.tanhao.me/pieces/310.html http://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/runtime.c http://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/Block_private.h
感谢大神分享
最新文章
- ABP文档 - 目录
- 一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?
- SQL --Chapter02 查询基础
- 更新新网卡驱动,修复win7雷凌网卡Ralink RT3290在电脑睡眠时和启动网卡时出现蓝屏netr28x.sys驱动文件错误
- java的servlet初步学习
- Swift 实现iOS Animation动画教程
- C#多线程(上) 分类: C# 线程 2015-03-09 10:35 174人阅读 评论(0) 收藏
- 分布式传输协调程序MSDTC配置
- uva 10069 Distinct Subsequences(高精度 + DP求解子串个数)
- 一个好的函数(gcd)求最小公约数
- pycharm+selenium搭建环境之no module named 'selenium'异常解决
- 从PRISM开始学WPF(六)MVVM(三)事件聚合器EventAggregator?
- Django Form组件 学生管理系统
- 设计模式之Singleton模式和Strategy模式是什么
- Visible Trees HDU - 2841
- BZOJ 1042: [HAOI2008]硬币购物 (详解)(背包&;容斥原理)
- JavaScript之小工具之日志log()[兼容]
- 类似No module named 'bs4'等错误的解决方法
- Linux(CentOS)下的apache服务器配置与管理
- mysql区间范围查询问题
热门文章
- Itext 中的文本信息绝对定位
- cf B. Maximum Absurdity
- #if defined 的意思?
- Row Cache Objects
- 鼠标键盘无法进入:(EE) config/hal: couldn’t initialise context: (null)
- 转:ASP.Net MVC:校验、AJAX与过滤器
- HDU4453--Looploop (Splay伸展树)
- Raid1源代码分析--读流程
- 法爱格2014 春夏新款欧美纯色修身高腰无袖吊带V领 拼接性感 连衣裙 黑色 M【图片 价格 品牌 报价】-京东
- c语言指针与结构体之内存动态分配