看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开):

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

感谢分享

block(四)揭开神秘面纱(下)-b的更多相关文章

  1. block(四)揭开神秘面纱(下)

    看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开): http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntim ...

  2. block(三)揭开神秘面纱(上)

    block到底是什么 我们使用clang的rewrite-objc命令来获取转码后的代码. 1.block的底层实现 我们来看看最简单的一个block: [caption id="attac ...

  3. block(三)揭开神秘面纱(上)-b

    block到底是什么 我们使用clang的rewrite-objc命令来获取转码后的代码. 1.block的底层实现 我们来看看最简单的一个block: [caption id="attac ...

  4. ASP.NET Identity系列01,揭开神秘面纱

    早在2005年的时候,微软随着ASP.NET 推出了membership机制,十年磨一剑,如今的ASP.NET Identity是否足够强大,一起来体会. 在VS2013下新建项目,选择"A ...

  5. 第二波分析:德国是2018世界杯夺冠最大热门? Python数据分析来揭开神秘面纱… (附源代码)

    2018年,世界杯小组赛已经在如火如荼的进行中.在上篇文章的基础上[2018世界杯:用Python分析热门夺冠球队],我们继续分析世界杯32强的实力情况,以期能够更进一步分析本次世界杯的夺冠热门球队. ...

  6. 揭开自然拼读法(Phonics)的神秘面纱

    揭开自然拼读法(Phonics)的神秘面纱 自然拼读法  (Phonics),是指看到一个单词,就可以根据英文字母在单词里的发音规律把这个单词读出来的一种方法.即从“字母发音-字母组合发音-单词-简单 ...

  7. 揭开Sass和Compass的神秘面纱

    揭开Sass和Compass的神秘面纱 可能之前你像我一样,对Sass和Compass毫无所知,好一点儿的可能知道它们是用来作为CSS预处理的.那么,今天请跟我一起学习下Sass和Compass的一些 ...

  8. 带你揭开ATM的神秘面纱

    相信大家都用过ATM取过money吧,但是有多少人真正是了解ATM的呢?相信除了ATM从业者外了解的人寥寥无几吧,鄙人作为一个从事ATM软件开发的伪专业人士就站在我的角度为大家揭开ATM的神秘面纱吧. ...

  9. 揭开GrowingIO无埋点的神秘面纱

    揭开GrowingIO无埋点的神秘面纱   早在研究用户行为分析的时候,就发现国内的GrowingIO在宣传无埋点技术,最近正好抽出时间来研究一下所谓的无埋点到底是什么样的. 我分六部分来分析一下无埋 ...

随机推荐

  1. 红米除线刷的另外一种救砖方法fastboot

    原文来自:https://jingyan.baidu.com/article/48a42057e945bca9242504d7.html , 按照它操做了一下,虽然没有救活我的红米1,但是让我更好的了 ...

  2. 杂谈:大容量(T级容量)的网盘的意义

    这两天,大容量的网盘的消息不断的推出.有百度的网盘推1T容量的:有腾讯的推10T容量的:有的还推不限容量的等等不一而足. 先看看大容量网盘的历史 早先是没有网盘这个概念的.能提供免费空间是电子邮箱 早 ...

  3. [Canvas]新版箴言钟表

    动态效果点此下载用浏览器打开观看. 本作Github地址:https://github.com/horn19782016/12MaximClock 图例: 代码: <!DOCTYPE html& ...

  4. 【Python】用geopy查两经纬度间的距离

    代码: from geopy.distance import vincenty from geopy.distance import great_circle 天安门 = (39.90733345, ...

  5. Android 多语言支持

    本文内容 字符串本地化原理 环境 创建项目 测试其他语言 Android 本地化语言 ISO 编码 参考资料 使用 Android 的人越来越多,每天都在增加.因此,当你想把你的应用成功地全球化时,通 ...

  6. JAVA Eclipse如何导入已有的项目

    File-Import,然后在弹出的窗口中输入exit,会自动提示下面的选项(已存在的项目)   把项目源代码放到Eclipse的工作目录,然后找到   导入完成    

  7. Mac 流程图

    https://www.lucidchart.com/pages/signup?utm_expid=39895073-174.qKyHpBEbQS26y86OArD-rQ.1 https://www. ...

  8. sqlAlchemy学习 001

    研究学习主题 sqlAlchemy架构图 测试练习代码编写 连接数据库 看代码 db_config = { 'host': 'xxx.xxx.xxx.xx', 'user': 'root', 'pas ...

  9. Hive表的建立和导入导出数据

    Hive是Hadoop的常用工具之一,Hive查询语言(HiveQL)的语法和SQL类似,基本实现了SQL-92标准. 1. 表的建立 编写以下的文件: USE test; DROP TABLE IF ...

  10. Percona-XtraBackup系列三:增量备份恢复

    1:创建测试表和测试库如果需要快速建立测试表和库的话,参考之前写的这篇博客:http://www.cnblogs.com/xiaoit/p/3376685.html create database b ...