博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
<<iOS 与OS X多线程和内存管理>>笔记:Blocks实现(二)
阅读量:5906 次
发布时间:2019-06-19

本文共 13752 字,大约阅读时间需要 45 分钟。

前言


在中我写的都是我们日常开发过程中所用到的Blocks.这里我们深层次的看一下Blocks的相关实现.

把OC代码转换为C++结构体代码


为了使我们更方便看清Block内部的运行,我们需要把OC代码代码转化为带有结构体的C++代码.这里我们就需要使用到clang -rewrite-objc指令.步骤有如下两步.

  • 打开终端,使用cd指令进入需要转化的文件目录下,比如我要对桌面上的Test工程下的main.m文件进行转化.终端指令类似于下图所示.

  • 然后执行如下的终端命令 clang -rewrite-objc main.m,如下所示.

然后在当前文件夹下就会出现后缀为.cpp的C++执行文件.如下所示.

Block的实现

首先,我们在main函数中写一个简单block匿名函数并且进行调用,如下所示.

int main(int argc, const char * argv[]) {    @autoreleasepool {        void (^blk)(void) = ^{
printf("Block\n");}; blk(); } return 0;}复制代码

然后,我们通过 clang -rewrite-objc main.m指令把mian.m转变为C++文件.里面代码较多,我们下拉到文件的最底部.

struct __block_impl {  void *isa;  int Flags;  int Reserved;  void *FuncPtr;};struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}static struct __main_block_desc_0 {  size_t reserved;  size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, const char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;         void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);    }    return 0;}复制代码

我们可以看到,我们写的block已经被转化为一个C++语言的函数,如下所示.

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}复制代码

概念函数的参数**__cself相当于C++实例方法中指向实例自身的变量this,或是Objective-C实例方法中指向对象自身的变量self,也就是说参数____cself为指向Block值的变量.可是我们发现____cself并没有在这里使用,这里我们先不做研究,我们先看一下参数____cself**的本质.

struct __main_block_impl_0 *__cself复制代码
  • Block的结构体

我们看到参数**____cself**是__main_block_impl_0 结构体的指针,该结构体如下所示.

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }   };复制代码

通过<<iOS 与OS X多线程和内存管理>>我们可以了解到两个成员变量各包含什么信息.

  • Block结构体的成员变量

我们先看一下成员变量impl的结构体(在.cpp文件的顶部位置).如下所示.

struct __block_impl {  void *isa;  int Flags;  int Reserved;//今后版本升级所需的区域  void *FuncPtr;//函数指针};复制代码

第二个成员变量Desc主要是存储今后版本升级所需的区域和Block大小.具体如下所示.

static struct __main_block_desc_0 {  size_t reserved; //今后版本升级所需的区域  size_t Block_size; //Block大小}复制代码
  • Block的构造

接下来我们就看一下**__main_block_impl_0**的构造函数是如何构造的.在main函数中调用的源码如图所示.

书中为了方便大家理解这句代码调用,进行了如下的转换.也就是说blk其实上是指向类型为__main_block_impl_0的tmp结构体指针.

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);        struct __main_block_impl_0 *blk = &tmp;复制代码

接下来我们看一下结构体的构造函数的参数.首先是**__main_block_desc_0_DATA**这个参数.我们在代码中找到了它的赋值过程.如下所示.

static struct __main_block_desc_0  __main_block_desc_0_DATA = {                              0,                              sizeof(struct __main_block_impl_0)};复制代码

通过上面的构造函数,__main_block_impl_0的值就会如下所示.

impl.isa = &_NSConcreteStackBlock;    impl.Flags = 0;    impl.Reserved = 0;    impl.FuncPtr = ___main_block_func_0;    Desc = &__main_block_desc_0_DATA;复制代码
  • Block的调用过程

接下来我们看一下使用block的代码是如何实现的.

blk();复制代码

找到.cpp文件对应的代码如下所示.

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);复制代码

我们去掉转化部分.简化代码之后如下所示.这句代码是什么意思呢?这就是使用函数的指针调用函数.正如我们刚刚所示的一样.正如上一个模块所说的那样,___main_block_func_0的函数指针被赋值到了结构体的FuncPtr中了.另外___main_block_func_0的所需参数是__main_block_impl_0的类型,也就是blk.所以有以下的函数调用.

(*blk->FuncPtr)(blk);复制代码
  • Block的实质

这时候我们需要回过头来说明__main_block_impl_0结构体成员变量 impl中的isa指针.

我们知道isa指针在构造函数中被赋值为**&_NSConcreteStackBlock**.如下图所示.

其实Block就是Objective-C对象.为什么这么说呢?首先我们看一下什么叫做Objective-C对象.

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。

假定我们创建一个如下的对象.

@interface MyObject : NSObject{    int val0;    int val1;}@end复制代码

那么基于Objective-C对象的结构体就应该如下所示.

struct MyObject{    Class isa;    int val0;    int val1;}复制代码

其中的isa指针指向如下所示.具体可查看书中的98页.

通过比较我们知道Block的结构体中有isa指针._NSConcreteStackBlock就相当于上图的class_t结构体实例.也就是说Block即为Objective-C的对象.

Block截获自动变量值的实现


对于Block截获自动变量值,在中我们已经说过了,现在我们列举一下例子.来看一下是如何实现截获自动变量值这一过程的.

int number = 1;                void (^blk)(void) = ^{            printf("value:%d\n",number);        };        number = 3;        blk();复制代码

运行程序.打印结果如下所示.

通过clang -rewrite-objc main.m指令编译成C++文件.其中核心代码如下所示.

struct __block_impl {  void *isa;  int Flags;  int Reserved;  void *FuncPtr;};struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int number;//新增成员变量  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : number(_number) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  int number = __cself->number; // bound by copy            printf("value:%d\n",number);}static struct __main_block_desc_0 {  size_t reserved;  size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, const char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;         int number = 1;        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number));        number = 3;        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);    }    return 0;}复制代码

这时候我们把Block的结构体拿出来看一下.我们发现新增了一个成员变量number以及构造方法发生新增了对number的赋值.

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int number;//新增成员变量  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : number(_number) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};复制代码

然后看一下main函数中__main_block_impl_0构造函数的构造过程.

int number = 1;        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number));复制代码

这一步我们就知道在__main_block_impl_0结构体构造的时候已经把number的值存储到了自身成员变量number中了,所以后面number如何改变,那么Block在构造完成之后打印的number值就不会发生改变了.

通过上面的表述,我们可以就了解为什么在不能Block中直接修改变量的值?(面试题).例如下图所示.

这是为什么呢?我们看一下**__main_block_func_0函数的实现,如下所示.我们可以知道传递的是__main_block_impl_0**结构体的成员变量的值.而不是指针(其实就算是指针也没有任何的关系),跟原来的number变量无任何关系.所以我们不能在函数中直接修改number变量变量.

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {            int number = __cself->number; // bound by copy            printf("value:%d\n",number);}复制代码

__block说明符的实现


上面一个木块最后我们说到如果直接在block中给变量赋值会报错,我们发现根本原因就是Block结构体中传递的是变量值,而不是指针,那么如何解决这一问题呢?这时候**__block说明符**就出现了.我们看一下C语言代码,如下所示.

__block int number = 1;                void (^blk)(void) = ^{            printf("value:%d\n",number);            number = 6;        };        blk();复制代码

但是通过clang -rewrite-objc main.m指令转变的C++代码去发生了很大的变化.核心代码如下所示.

//numbr变量已经通过__block的修饰变成了结构体struct __Block_byref_number_0 {  void *__isa;__Block_byref_number_0 *__forwarding; int __flags; int __size; int number;};struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  __Block_byref_number_0 *number; // by ref  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_number_0 *_number, int flags=0) : number(_number->__forwarding) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  __Block_byref_number_0 *number = __cself->number; // bound by ref            printf("value:%d\n",(number->__forwarding->number));            (number->__forwarding->number) = 6;        }static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->number, (void*)src->number, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->number, 8/*BLOCK_FIELD_IS_BYREF*/);}static struct __main_block_desc_0 {  size_t reserved;  size_t Block_size;  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);  void (*dispose)(struct __main_block_impl_0*);} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};int main(int argc, const char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;         __attribute__((__blocks__(byref))) __Block_byref_number_0 number = {(void*)0,(__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 1};        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_number_0 *)&number, 570425344));        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);    }    return 0;}复制代码

我们看一下主要改变的部分.int number = 1;变成__block int number = 1;之后,C++代码如下所示.代码量提升了不是一倍两倍呀~

struct __Block_byref_number_0 {  void *__isa;__Block_byref_number_0 *__forwarding;//指向自身的指针 int __flags; int __size; int number;};复制代码

然后我们看一下在main函数中的构造代码.如下所示.

__attribute__((__blocks__(byref)))  __Block_byref_number_0 number = {(void*)0,(__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 1};复制代码

简化代码之后,如下所示.

__Block_byref_number_0 number = {0,&number,0, sizeof(__Block_byref_number_0), 1};复制代码

这时候Block结构体的构造函数和新增成员变量也发生了改变.成员变量变成了指向**__Block_byref_number_0**类型的结构体.

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  __Block_byref_number_0 *number; //新增成员变量  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_number_0 *_number, int flags=0) : number(_number->__forwarding) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};复制代码

那么在block中进行赋值的时候是如何操作的呢?这主要是通过**__Block_byref_number_0的成员变量__forwarding来完成的.__forwarding是指向本身的指针.我们可以通过__forwarding来找到成员变量number的值.所以在__main_block_func_0**函数实现中有如下的代码.

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {            __Block_byref_number_0 *number = __cself->number; // bound by ref            printf("value:%d\n",(number->__forwarding->number));            (number->__forwarding->number) = 6;}复制代码

对于__Block_byref_number_0结构体中的__forwarding指针,我们可以看下面的示意图.

Block存储域


通过下面一张表我们了解到Block和__block变量时存储在栈区的结构体类型自动变量(一般情况下).

名称 实质
Block 栈上Block的结构体实例
__block 栈上__block变量的结构体实例

接下来我们还是来研究Block结构体的isa指针,在前面的例子中,isa指针是指向_NSConcreteStackBlock的.其实还有很多类似的类.我们先用一张表格来说明每个类的不同点

设置对象的存储域 副本源的配置存储域 复制效果
_NSConcreteStackBlock 从栈区复制到堆区
_NSConcreteMallocBlock 引用计数增加
_NSConcreteGlobaBlock 全局区 全局区 什么也不做

通过上面的表格,我们就可以知道两个面试题的答案,

问: Block的类一共有几种? 答: 三种,分别是 _NSConcreteStackBlock 、_NSConcreteMallocBlock、_NSConcreteGlobaBlock

问: Block为什么用copy修饰? 答: block在定义成属性的时候应该使用copy修饰,平常我们使用的block主要是存放在栈区的(有的也会存放在全局区).栈区的block出了作用域之后就会被释放掉,如果我们在block释放掉之后还继续调用,那么就会出现crash.理论上,在全局区的block我们是不需要进行copy的.但是大部分的block是存储在栈区的,为了统一规范管理,所以我们都使用copy对block属性进行修饰.

__block变量存储域


上一个模块是对Block进行了说明,那么对于使用__block变量的Block从栈上复制到堆上是,__block变量会有什么影响呢?

__block变量的配置存储域 Block从栈区复制到堆时的影响
从栈复制到堆并被Block持有
被Block持有

上面这张表是表达了什么意思呢? 也就是说:

  1. 如果有一个Block使用某个__block变量,那么__block变量会从栈复制到堆并被Block持有.
  2. 如果有多个Block使用某个__block变量,那么在第一个Block中__block变量会从栈复制到堆并被第一个Block持有.从第二个Block时是持有__block变量,也就是只会增加__block变量的引用计数.

对于**__forwarding指针**(指向自身的指针),我们曾经说过,"不管__block变量配置在栈上还是堆上,都能正确访问该变量."我们可以通过下面的例子来说明一下情况.

__block int val = 0;void (^blk)(void) = [^{ ++val; } copy];++val;blk();NSLog(@"%d",val);复制代码

通过blk这个Block的copy操作, 被__block修饰的val变量成功的从栈上复制到了堆上了.

所以^{ ++val; }++val;都可以被转化为以下的形式.

++(val.__forwarding->val);复制代码

我们可以通过下面的示意图来表示上面的转变过程.

截获对象的实现


我们曾经说过截获变量值,现在我们说一下截获对象的实现.演示源码如下所示.

void (^blk)(id obj);        {//array的作用域        id array = [[NSMutableArray alloc] init];        blk = [^(id obj){                        [array addObject:obj];            NSLog(@"array count = %ld",[array count]);        } copy];        }//array的作用域已经结束        blk([NSObject new]);        blk([NSObject new]);        blk([NSObject new]);复制代码

我们知道array的作用域已经结束了(到达注释位置时候),可以我们调用block仍然可以访问到array.如下所示,这是为什么呢?

实际上在blk的实现过程中.已经持有了array对象.<<iOS 与OS X多线程和内存管理>>是有以下代码的.

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  id  __strong array; //强引用的array成员变量};复制代码

在Objective-C中,C语言结构体并不能含有__strong修饰符的变量.因为编译器不知道应该何时进行C语言结构体的初始化和废弃操作.不能很好的管理内存.Objective-C的运行时库可以很好的把握Block从栈上复制到堆以及堆上的Block被废弃的时机.从而有效管理成员变量的持有和释放.为此,在__main_block_desc_0就增建了两个成员变量copy和dispose,已经对应的函数.用于成员变量的持有和释放.如下图所示.

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}static struct __main_block_desc_0 {  size_t reserved;  size_t Block_size;  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);  void (*dispose)(struct __main_block_impl_0*);} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};复制代码

可是我在实际过程中并没有**__strong**修饰词.个人猜想是已经进行了缺省操作了.省略了__strong的修饰符.源码截图如下所示.大家可以自行试验操作.

循环引用的本质


上一个模块我们说了.Block可以持有对象.如果一个对象中含有某个Block的成员属性(strong修饰).在Block中直接使用self,会造成循环引用,原因就出现**__main_block_impl_0结构体中的obj.__main_block_impl_0**对obj是强引用,self对Block变量是强引用,两者相互引用,最终造成循环引用.

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  id  __strong obj; //强引用的obj成员变量};复制代码

示意图如下所示.

结束


这一篇Block的实现总共写了三天,加上自己验证,售货良多,希望这一篇博客对大家有所帮助.还是希望大家来看一下<<iOS 与OS X多线程和内存管理>>原书,自己敲一遍实现源码,这样帮助很大,会加深印象.最后感谢各位看官查看本篇文章.如果有任何问题,欢迎联系骚栋.欢迎指导批斗.

转载地址:http://lecpx.baihongyu.com/

你可能感兴趣的文章
Android 四大组件之一(Activity)
查看>>
扫描(一)
查看>>
Centos7安装rabbitmq server 3.6.0
查看>>
iostat命令学习
查看>>
html video的url更新,自动清缓存
查看>>
【11】ajax请求后台接口数据与返回值处理js写法
查看>>
Python菜鸟之路:Jquery Ajax的使用
查看>>
LeetCode算法题-Maximum Depth of Binary Tree
查看>>
Cox 教学视频5
查看>>
使用ffmpeg实现对h264视频解码 -- (实现了一个易于使用的c++封装库)
查看>>
flink watermark介绍
查看>>
Android Xutils 框架
查看>>
Sysbench 0.5版安装配置
查看>>
书摘—你不可不知的心理策略
查看>>
【博客话题】毕业——开始人生的艰苦历程
查看>>
Linux安装telnet
查看>>
sap scriptfom 多语言翻译
查看>>
黄聪:3分钟学会sessionStorage用法
查看>>
Entity Framework 全面教程详解(转)
查看>>
Windows上Python2.7安装Scrapy过程
查看>>