前面有一篇介绍Block的博客,主要介绍了Block的简单使用技巧。这篇博客主要更加深入地了解一下Block。包括:Block的实现、__Block的原理以及Block的存储域三方面。

Block的实现

首先我们使用Xcode创建一个Project,点击File-->New-->Project,选择macOS中Application的Command Line Tool,然后设置Project Name即可。你好发现这个工程值包含了一个main.m文件,然后我们做如下更改(更改后的代码如下):

  1. #import <stdio.h>
  2. int main(int argc, const char * argv[]) {
  3. printf("Hello World");
  4. return 0;
  5. }

这个是我们最常见的C代码,导入stdio.h,然后打印出来Hello World。接下来我们写一个最简单的block,没有返回值,没有传入参数:

  1. #import <stdio.h>
  2. int main(int argc, const char * argv[]) {
  3. void (^blk)(void) = ^{
  4. printf("Hello Worldddd");
  5. };
  6. blk();
  7. return 0;
  8. }

打印出来的结果相当于调用了blk输出的结果。接下来我们在item中跳转到main.m所在文件夹然后执行如下命令:

  1. clang -rewrite-objc main.m

你会发现在当前文件夹下生成了一个.cpp文件,它是经过clang编译器编译之后的文件,打开之后里面大概有5百多行,其实我们看下面的这些代码就足够了:

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
  5. impl.isa = &_NSConcreteStackBlock;
  6. impl.Flags = flags;
  7. impl.FuncPtr = fp;
  8. Desc = desc;
  9. }
  10. };
  11. #ifndef BLOCK_IMPL
  12. #define BLOCK_IMPL
  13. struct __block_impl {
  14. void *isa;
  15. int Flags;
  16. int Reserved;
  17. void *FuncPtr;
  18. };
  19. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  20. printf("Hello Worldddd");
  21. }
  22. static struct __main_block_desc_0 {
  23. size_t reserved;
  24. size_t Block_size;
  25. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  26. int main(int argc, const char * argv[]) {
  27. void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  28. ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
  29. return 0;
  30. }

其中包含三个结构体:

  1. __main_block_impl_0__block_impl__main_block_desc_0

和两个方法:

  1. __main_block_func_0main

main就是我们写的main函数。

至此,你能知道的就是:Block看上去很特别,其实就是作为及其普通的C语言源代码来处理的。编译器会把Block的源代码转换成一般的C语言编译器能处理的源代码,并作为极为普通的C语言源代码被编译。

接下来对编译的内容来一个分解,首先是

  1. ^{printf("Hello Worldddd")};

变换后的源代码如下:

  1. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  2. printf("Hello Worldddd");
  3. }

也就是现在变成了一个静态方法,其命名方式为:Block所属的函数名(main)和该Block语法在函数出现的顺序值(0)来给经过clang变换的函数命名。该方法的参数相当于我们在OC里面的指向自身的self。我们看一下该参数的声明:

  1. struct __main_block_impl_0 *__cself

你会发现它其实是一个结构体,该结构体的声明如下:

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
  5. impl.isa = &_NSConcreteStackBlock;
  6. impl.Flags = flags;
  7. impl.FuncPtr = fp;
  8. Desc = desc;
  9. }
  10. };

该结构体中你会发现里面有一个构造函数,你忽略构造函数,会发现该结构体就很简单了,只是包含了impl和 Desc两个属性变量。其中impl也是一个结构体,它的结构如下:

  1. struct __block_impl {
  2. void *isa;
  3. int Flags;
  4. int Reserved;
  5. void *FuncPtr;
  6. };

从属性变量的名字我们可以猜测出该结构体各个属性的含义:

  1. isa:isa指针,指向父类的指针。
  2. Flags:一个标记
  3. Reserved:预留区域,用于以后的使用。
  4. FuncPtr:这个很重要,是一个函数指针。后面会详细说明它的作用。

第二个变量是Desc,也是一个结构体:

  1. static struct __main_block_desc_0 {
  2. size_t reserved;
  3. size_t Block_size;
  4. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

这个结构体就比较简单了,一个预留位,一个是指代该Block大小的属性,后面又包含了一个该实例:

  1. __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

预留位为0,大小为传入结构体的大小。接下来就是很重要的构造函数了:

  1. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
  2. impl.isa = &_NSConcreteStackBlock;
  3. impl.Flags = flags;
  4. impl.FuncPtr = fp;
  5. Desc = desc;
  6. }

其中的&_NSConcreteStackBlock用于初始化impl的isa指针;flags为0;FuncPtr是构造函数传过来的fp函数指针;Desc为一个block的描述。到这里三个结构体和一个函数就介绍完了。接下来看一下main函数里面上述构造函数是如何调用的:

  1. void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

感觉好复杂,我们先做一个转换:

  1. struct __main_block_impl_0 tmpeImpl = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)
  2. struct __main_block_impl_0 *blk = &tmpeImpl;

也就是说把结构体的实例的指针赋值给blk。接下来再看一下构造函数的的初始化,其实赋值就变成了这样:

  1. impl.isa = &_NSConcreteStackBLock;
  2. impl.Flags = 0;
  3. impl.FuncPtr = __main_block_func_0;
  4. Desc = &__main_block_desc_0_DATA;

现在在看一下调用block的那句代码:

  1. blk();

转换成了:

  1. ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

这个转换不是太明白,但是知道他的作用就是把blk当做参数传进去,调用的FuncPtr所指向的函数,也就是__ block _ block _ func _ 0。

到这里就大体了解了Block的实现,其实就是C的几个结构体和方法,经过赋值和调用,进而实现了Block。

另外Block其实实质上也是OC的对象。

__Block的原理

先看一个简单的例子:

  1. #import <stdio.h>
  2. int main(int argc, const char * argv[]) {
  3. int i = 3;
  4. void (^blk)(void) = ^{
  5. printf("Hello World,%d",i);
  6. };
  7. blk();
  8. return 0;
  9. }

使用clang编译后是这样的:


  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. int i;
  5. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
  6. impl.isa = &_NSConcreteStackBlock;
  7. impl.Flags = flags;
  8. impl.FuncPtr = fp;
  9. Desc = desc;
  10. }
  11. };
  12. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  13. int i = __cself->i; // bound by copy
  14. printf("Hello World,%d",i);
  15. }
  16. int main(int argc, const char * argv[]) {
  17. int i = 3;
  18. void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
  19. ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
  20. return 0;
  21. }

也就是在main函数调用的时候把i传到了构造函数里,然后通过i(_i)对结构体的属性变量i赋值,i变量现在已经成为了结构体的一个树形变量。在构造函数执行时把i赋值。在 __ main _ block_func _ 0里面通过 __ cself调用,这个变量实际是在声明block时,被复制到了结构体变量i,因此不会影响变量i的值。当我们尝试在Block中去修改时,你会得到如下错误:

  1. Variable is not assignable(missing __block type specifier)

提示我们加上__block,接下来我们将源代码做如下修改:

  1. int main(int argc, const char * argv[]) {
  2. __block int i = 3;
  3. void (^blk)(void) = ^{
  4. i = i + 3;
  5. printf("Hello World,%d",i);
  6. };
  7. blk();
  8. return 0;
  9. }

运行一下你会发现你成功对i的值进行了修改!用clang进行编译,结果如下:

  1. struct __Block_byref_i_0 {
  2. void *__isa;
  3. __Block_byref_i_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. int i;
  7. };
  8. struct __main_block_impl_0 {
  9. struct __block_impl impl;
  10. struct __main_block_desc_0* Desc;
  11. __Block_byref_i_0 *i; // by ref
  12. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
  13. impl.isa = &_NSConcreteStackBlock;
  14. impl.Flags = flags;
  15. impl.FuncPtr = fp;
  16. Desc = desc;
  17. }
  18. };
  19. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  20. __Block_byref_i_0 *i = __cself->i; // bound by ref
  21. (i->__forwarding->i) = (i->__forwarding->i) + 3;
  22. printf("Hello World,%d",(i->__forwarding->i));
  23. }
  24. 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*/);}
  25. static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
  26. static struct __main_block_desc_0 {
  27. size_t reserved;
  28. size_t Block_size;
  29. void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  30. void (*dispose)(struct __main_block_impl_0*);
  31. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  32. int main(int argc, const char * argv[]) {
  33. __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 3};
  34. void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
  35. ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
  36. return 0;
  37. }

你会发现多了一个__ Block _ byref _i _ 0的结构体,然后多了两个copy和dispose函数。

看一下main函数里面的i,此时也不再是一个简单的基本类型int,而是一个初始化的 __ Block _ byref _ i _ 0的结构体,该结构体有个属性变量为i,然后把3赋值给了那个属性变量。该结构体还有一个指向自己的指针 __ forwarding,它被赋值为i的地址。

现在 __ main _ block _ func _ 0在实现中使用了指向该变量的指针,所以达到了修改外部变量的作用。

Block的存储域

Block的存储域有以下几种:

  1. _ NSConcreteStackBlock,该类的对象Block设置在栈上
  2. _ NSConcreteGlobalBlock,该类的Block设置在程序的数据区(.data)域中。
  3. _ NSConcreteMallocBlcok,该类的Block设置在堆上

    下面这张图展示了Block的存储域:

我们前面看到的都是在Stack的Block,但是你可以在OC工程中打印一下你声明的block的isa,你会发现它其实是Malloc的block,也就是在堆上的block。如图:



还有一种情况是Global的block:



在ARC中,只有NSConcreteGlobalBlock和NSConcreteMallockBlock两种类型的block。因为我们最简单的block在工程中打印出来的都是MallocBlock。也许是因为苹果把对象都放到了堆管理,而Block也是对象,所以也放到了堆上。

此时我们也许会有个疑问:Block超出了变量作用域为什么还能存在呢?

对于Global的Block,变量作用域之外也可以通过指针安全使用,但是设置在栈上的就比较尴尬了,作用域结束后,Block也会 被废弃。为了使Block超出变量作用域还可以存在,Block提供了将Block和 __ block变量从栈上复制到堆上的方法来解决这个问题。这样就算栈上的废弃,堆上的Block还可以继续存在。

看一下对Block进行复制,结果如何:



如果对Block进行了copy操作,__ block的变量也会受到影响,当 __ block的变量配置在栈上,复制之后它将从栈复制到堆上并被Blcok持有,如果是堆上的 __ block变量,Blcok复制之后该变量被Block持有。

如果两个block(block1,block2)同时都是用 __ block变量,如果block1被复制到了堆上,那么 __ block变量也会在block1复制到堆的同时复制到堆上,当block2再是用到 __ block变量的时候,只是增加堆上 __ block变量的引用计数,不会再次复制。如果堆上的block1和block2被废弃了,那么它所是用的 __ block变量也就被释放了(如果block1被废弃,而block2没有被废弃,那么 __ block变量的引用计数-1,直到最后使用 __ block变量的block被废弃的同时,堆上的 __ block也会被释放)。

理解了上面刚才说的复制之后,现在回过来思考另一个问题: __ block的时候转换的结构体中的 __ forwarding指针有什么作用呢?(下面代码中的 __ forwarding)

  1. struct __Block_byref_i_0 {
  2. void *__isa;
  3. __Block_byref_i_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. int i;
  7. };

其实是这样的:栈上的 __ block变量用结构体实例在 __ block变量从栈复制到堆上的时候,会将成员变量 __ forwarding的值替换为复制目标堆上的 __ block变量用结构体实例的地址。通过该操作之后,无论是在Block语法中、Block语法外使用 __ block变量,还是 __ block变量配置在栈上或者堆上,都可以顺利地访问同一个 __ block变量。

以上便是对block的进一步介绍,主要参考了《Objective-C高级编程 iOS与OS X多线程和内存管理》一书。

转载请标明来源:http://www.cnblogs.com/zhanggui/p/8135790.html

老生常谈之Block的更多相关文章

  1. 老生常谈combobox和combotree模糊查询

    FIRST /** * combobox和combotree模糊查询 * combotree 结果显示两级父节点(手动设置数量) * 键盘上下键选择叶子节点 * 键盘回车键设置文本的值 */ (fun ...

  2. iOS之block,一点小心得

    作为一个iOS开发程序员,没用过block是不可能的.这次我探讨的是block原理,但是有些更深层次的东西,我也不是很清楚,以后随着更加了解block将会慢慢完善. 第一个问题,什么是block? 我 ...

  3. 老生常谈系列之Aop--AspectJ

    老生常谈系列之Aop--AspectJ 这篇文章的目的是大概讲解AspectJ是什么,所以这个文章会花比较长的篇幅去解释一些概念(这对于日常开发来说没一点卵用,但我就是想写),本文主要参考Aspect ...

  4. Objective-C中block的底层原理

    先出2个考题: 1. 上面打印的是几,captureNum2 出去作用域后是否被销毁?为什么? 同样类型的题目: 问:打印的数字为多少? 有人会回答:mutArray是captureObject方法的 ...

  5. iOS 键盘添加完成按钮,delegate和block回调

    这个是一个比较初级一点的文章,新人可以看看.当然实现这个需求的时候自己也有一点收获,记下来吧. 前两天产品要求在工程的所有数字键盘弹出时,上面带一个小帽子,上面安装一个“完成”按钮,这个完成按钮也没有 ...

  6. python中IndentationError: expected an indented block错误的解决方法

    IndentationError: expected an indented block 翻译为IndentationError:预期的缩进块 解决方法:有冒号的下一行要缩进,该缩进就缩进

  7. JDBC Tutorials: Commit or Rollback transaction in finally block

    http://skeletoncoder.blogspot.com/2006/10/jdbc-tutorials-commit-or-rollback.html JDBC Tutorials: Com ...

  8. 嵌入式&iOS:回调函数(C)与block(OC)传 参/函数 对比

    C的回调函数: callBack.h 1).声明一个doSomeThingCount函数,参数为一个(无返回值,1个int参数的)函数. void DSTCount(void(*CallBack)(i ...

  9. 嵌入式&iOS:回调函数(C)与block(OC)回调对比

    学了OC的block,再写C的回调函数有点别扭,对比下区别,回忆记录下. C的回调函数: callBack.h 1).定义一个回调函数的参数数量.类型. typedef void (*CallBack ...

随机推荐

  1. linux操作系统基础篇(三)

    1.cat命令 cat除了可以用来查看文本文档还可以将两个文本文档纵向合并到另外一个文本文档中 比如 cat /etc/passwd /etc/group > 1.txt 2. 归纳了所有的压缩 ...

  2. Runtime的理解与实践

    Runtime是什么?见名知意,其概念无非就是"因为 Objective-C 是一门动态语言,所以它需要一个运行时系统--这就是 Runtime 系统"云云.对博主这种菜鸟而言,R ...

  3. 解决thymeleaf layout布局不生效

    今天使用thymeleaf layout布局时总是不生效,特此把解决问题的步骤和几个关键点记录下来备忘. 一.检查依赖 1.thymeleaf必备maven依赖: <dependency> ...

  4. ctrl+z 以后怎么恢复挂起的进程

    (1) CTRL+Z挂起进程并放入后台 (2) jobs 显示当前暂停的进程 (3) bg %N 使第N个任务在后台运行(%前有空格) (4) fg %N 使第N个任务在前台运行 默认bg,fg不带% ...

  5. zanphp 初探----安装篇

    安装 zanphp 的安装详细步骤具体在 http://zanphpdoc.zanphp.io/,但是安装的时候,还是踩了一些坑,Mac 和 Ubuntu 我都安装过, 分享大家注意一下. PHP 版 ...

  6. Libevent 事件循环(1)

    // 事件的dispatch int event_base_loop(struct event_base *base, int flags) {    //得到采用的事件模型 epoll/epoll/ ...

  7. python 生成html文件(表格)

    import pandas as pd def convert_to_html(result,title): d = {} index = 0 for t in title: d[] = result ...

  8. 51Nod--1011最大公约数GCD

    1011 最大公约数GCD 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 输入2个正整数A,B,求A与B的最大公约数. Input 2个数A,B,中间用 ...

  9. PHP 字符串转 bigint 型md5

    1 2 3 4 5 6 7 8 /**      * 字符串转bigint      * @return bigint(string)      */     public function md5( ...

  10. Java设计模式之单例模式详解

    在Java开发过程中,很多场景下都会碰到或要用到单例模式,在设计模式里也是经常作为指导学习的热门模式之一,相信每位开发同事都用到过.我们总是沿着前辈的足迹去做设定好的思路,往往没去探究为何这么做,所以 ...