iOS Block详细介绍(block实现)

时间:2022-09-13 11:02:30

Block的实现

数据结构定义 block的数据结构定义如下图

iOS Block详细介绍(block实现)

对应的结构体定义如下:

struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
}; struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};

通过改图 我们可以知道 一个block实例实际上由6部分组成。

1.isa指针。所有对象都有该指针,用于实现对象相关的功能。

2.flags 用于按bit位表示一些block的附加信息。本文后面介绍 block copy 的实现代码可以看到对该变量的使用。

3.reserved 保留变量

4.invoke 函数指针 指向具体的block实现的函数调用地址

5.descriptor 表示该block的附加描述信息 主要是size 大小 以及 copy  和 dispose函数的指针

6.variables capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

该数据结构和后面的clang分析出来的结构实际上是一样的,不过仅是结构体的潜逃方式不一样,如下 2 个结构体 SampleA 和 SampleB 在内存上是完全一样的,原因是结构体本身并不带有任何额外的附加信息。

struct SampleA {
int a;
int b;
int c;
}; struct SampleB {
int a;
struct Part1 {
int b;
};
struct Part2 {
int c;
};
};

在objective-C语言中,一共有三种类型的block:

1._NSConcreteGlobalBlock 全局的静态Block 不会访问任何外部变量

2._NSConcreteStackBlock 保存在栈中的Block 当函数返回时会被销毁

3._NSContreteMallcoBlock 保存在堆中的block 当引用计数为0的时候会被销毁

下面我们会分别查看他们各自实现方式上的差别

研究工具:Clang

为了研究编译器是如何实现block的  我们需要clang clang提供一个命令 可以将OC的源码改写成C语言的 借此可以研究 block 具体的源码实现方式。该命令是

clang -rewrite-objc block.c

NSConcreteGlobalBlock 类型的 block 的实现

我们先使用这样的方法创建一个工程

iOS Block详细介绍(block实现)

int main(int argc, const char * argv[]) {
@autoreleasepool {
^{NSLog(@"hello world");}();
}
return ;
}

然后在终端输入 clang -rewrite-objc main.m 会生成一个main.cpp文件 打开去掉无关代码 我们就可以看到block的实现原理。

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=) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xm_fwf5hc0d7f3113f_myyxxql40000gn_T_main_9c11bb_mi_0);} static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { , sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
}
return ;
}

下面我们就具体看一下是如何实现的。__main_block_impl_0就是该block的实现,从中我们可以看出

1.一个block实际上是一个对象,它主要由一个isa和一个impl和一个descriptor组成。

2.由于 clang 改写的具体实现方式和 LLVM 不太一样,并且这里没有开启 ARC。所以这里我们看到 isa 指向的还是_NSConcreteStackBlock。但在 LLVM 的实现中,开启 ARC 时,block 应该是 _NSConcreteGlobalBlock 类型

3.impl是实际上的函数指针 ,本例中,它指向 __main_block_func_0。这里的 impl 相当于之前提到的 invoke 变量,只是 clang 编译器对变量的命名不一样而已。

4.descriptor 是用于描述当前这个 block 的附加信息的,包括结构体的大小,需要 capture 和 dispose 的变量列表等。结构体大小需要保存是因为,每个 block 因为会 capture 一些变量,这些变量会加到 __main_block_impl_0 这个结构体中,使其体积变大。在该例子中我们还看不到相关 capture 的代码,后面将会看到。

NSConcreteStackBlock 类型的 block 的实现

int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = ;
void(^block)(void) = ^{
NSLog(@"%d",a);
};
block();
}
return ;
}

用之前提到的 clang 工具,转换后的关键代码如下:

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 a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_xm_fwf5hc0d7f3113f_myyxxql40000gn_T_main_74e24a_mi_0,a);
} static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { , sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 100;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return ;
}

在本例中我们可以看到:

1.本例中 isa指向_NSConcreteStackBlock,说明这是一个分配在栈上的实例。

2.main_block_impl_0 中增加了一个变量 a,在block中引用的变量a实际是在申明block时,被复制到 main_block_impl_0 结构体中的那个变量 a 因为这样,我们就能理解,在 block 内部修改变量 a 的内容,不会影响外部的实际变量 a。

3.main_block_impl_0 中由于增加了一个变量 a,所以结构体的大小变大了,该结构体大小被写在了 main_block_desc_0 中

我们修改上面的源码 在变量前面增加 __block 关键字:

int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = ;
void(^block)(void) = ^{
NSLog(@"%d",a);
};
block();
}
return ;
}

编译后 变成这个样子

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
}; struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
}; struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=) : a(_a->__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_a_0 *a = __cself->a; // bound by ref NSLog((NSString *)&__NSConstantStringImpl__var_folders_xm_fwf5hc0d7f3113f_myyxxql40000gn_T_main_472c9f_mi_0,(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, /*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, /*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 = { , 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_a_0 a = {(void*),(__Block_byref_a_0 *)&a, , sizeof(__Block_byref_a_0), };
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return ;
}

从代码中我们可以看到:

1.源码中增加一个名为 _Block_byref_a_0的结构体,用来保存我们要捕捉并且能修改的变量a

2.main_block_impl_0 中引用的是 Block_byref_i_0 的结构体指针,这样就可以达到修改外部变量的作用。

3.__Block_byref_i_0 结构体中带有 isa,说明它也是一个对象。

4.我们需要负责 Block_byref_i_0 结构体相关的内存管理,所以 main_block_desc_0 中增加了 copy 和 dispose 函数指针,对于在调用前后修改相应变量的引用计数。

NSConcreteMallocBlock 类型的 block 的实现

NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中。

变量的复制

对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的

iOS Block详细介绍(block实现)

对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的

iOS Block详细介绍(block实现)

以上就是block的实现原理了 在这里我只是把唐巧的关于block的实现原理的博客又抄写并验证了一下。 原文地址在这里。感谢唐巧。

http://blog.devtang.com/2013/07/28/a-look-inside-blocks/

iOS Block详细介绍(block实现)的更多相关文章

  1. iOS 自动布局详细介绍

    1. 自动布局的理解 iOS自动布局很有用,可以在不同size的屏幕上运行,原先看的头痛,还是习惯用最蠢的[UIScreen mainScreen].bounds.size.width等来布局,后来实 ...

  2. iOS UIButton详细介绍

    昨天,做了项目中的一点,觉得细节还是很重要的.像一个普通的UIButton,给它调试字体格式,大小什么的,确实是蛮耗时间的. 今天打算详细的归纳归纳.. typedef NS_ENUM(NSInteg ...

  3. iOS 阶段学习第24天笔记(Block的介绍)

    iOS学习(OC语言)知识点整理 一.Block 的介绍 1)概念: block 是一种数据类型,类似于C语言中没有名字的函数,可以接收参数,也可以返回值与C函数一样被调用 封装一段代码 可以在任何地 ...

  4. iOS沙盒机制介绍,Block 的介绍

    一.iOS沙盒机制介绍 (转载) 1)概念:每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用放入文件 系统隔离,ios系统不允许访问 其他应用的应用沙盒,但在ios8中已经开放访 ...

  5. iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)

    1. 前言 Block:带有自动变量(局部变量)的匿名函数.它是C语言的扩充功能.之所以是拓展,是因为C语言不允许存在这样匿名函数. 1.1 匿名函数 匿名函数是指不带函数名称函数.C语言中,函数是怎 ...

  6. ios Block详细用法

    ios Block详细用法 ios4.0系统已开始支持block,在编程过程中,blocks被Obj-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行.Blocks可以作为函数参数或者函数的 ...

  7. Block的详细介绍

    关于block的介绍 ==ios中的内存空间分级== 栈区 存放函数参数值.局部变量.函数返回地址等,函数跳转跳转时现场保护(寄存器),这些系统都会帮我们自动实现,无需我们干预. 所以大量的局部变量, ...

  8. iOS开发Block的介绍以及Block的循环引用问题

    1:block的循环引用问题最主要记住两点: 如果[block内部]使用[外部声明的强引用]访问[对象A], 那么[block内部]会自动产生一个[强引用]指向[对象A] 如果[block内部]使用[ ...

  9. IOS 浅谈闭包block的使用

    前言:对于ios初学者,block通常用于逆向传值,遍历等,会使用,但是可能心虚,会感觉block很神秘,那么下面就一起来揭开它的面纱吧. ps: 下面重点讲叙了闭包的概念,常用的语法,以及访问变量, ...

随机推荐

  1. 自制操作系统(二) 让bootsector开机启动打印一首诗

    qq:992591601 欢迎交流 2016-03-31作 2016-06-01.2016-06-27改 我总结了些基本原理: 1.软盘的第一个扇区为启动区 2.计算机读软盘是以512字节为单位来读写 ...

  2. 干货-iOS、mac开源项目及库,以后我也会持续更新。

    昨晚在网上看的干货,直接分享给大家了,觉得有用的,直接fork吧. https://github.com/Brances/TimLiu-iOS

  3. Linux下使用QQ的几种方式

    Linux下没有官方的QQ聊天应用,对于经常使用QQ与朋友同事沟通交流的小伙伴们来说肯定很不方便,在Linux下可以使用以下几种方法使用QQ:   1.wine qq for linux Ubuntu ...

  4. 【floyed】【HDU1217】【Arbitrage】

    题目大意: 给你几种货币,以及几种汇率关系,问是否存在套利的可能? 思路: 初步想法:图上存在一个环的路径上权值相乘大于1.... 再者:该如何找到图上所有环呢.... 好吧 经过鸟神 和 况神的指点 ...

  5. 学习笔记:JavaScript-入门篇

    1.对话框,输出框,警告框   1. document.write() 可用于直接向 HTML 输出流写内容.简单的说就是直接在网页中输出内容.   2.alert(字符串或变量);   3.conf ...

  6. LINQ to Entities does not recognize the method 'System.DateTime AddDays(Double)' method, and this method cannot be translated into a store expression.

    NormalSubmission=analysis.Count(x=>x.FinishTime<= endTime.AddDays(1))报错linq不能识别 => var endT ...

  7. linux 内核模块makefile通用模板

    ifneq ($(KERNELRELEASE),)# 在 mylist 后面添加需要编译的模块数量 mylist=hello.o a.o# 为每一个模块添加所需的文件 hello-objs := ma ...

  8. Ubuntu SVN安装&amp&semi;使用&amp&semi;命令

    SVN 安装 apt-get install subversion checkout svn checkout svn://192.168.1.110/app 按提示输入相应的用户名和密码. 往版本库 ...

  9. UI设计:掌握这6点,轻松0到1

    非科班出身能成为UI设计师吗? 答案是肯定的.世上无难事,只怕有心人.只要找对方法.坚持不懈,即便是零基础也能学好UI设计. 那么零基础学习UI设计,需要学习哪些知识?我们要从哪些地方学起?怎么系统学 ...

  10. C&plus;&plus;对象数组初始化

    类对象 数组 初始化可以使用构造函数初始化,同时类有不同的构造函数,可以对类对象数组元素使用不同的构造函数;