ios - block原理解读(一)

栏目: C++ · 发布时间: 5年前

内容简介:block在网络上的文章也比较多,本文将开发中block使用细节和block实现原理结合起来,

前言

block在网络上的文章也比较多,

本文将开发中block使用细节和block实现原理结合起来,

加上个人的理解,

帮助大家更好地理解block和使用block。

问题

block的意义

block为什么不能修改外部变量?这里的外部变量又指的是什么?

block为什么要用copy

被block引用的对象,引用计数为何+=2?

__block 又是什么原理?

循环引用究竟是为何引起的?

等等

block产生的意义

程序始终都要遵循逐行执行的原则,

而block 可以理解为 逻辑触发执行

定时器 可以理解为 时间触发执行

举个有点意思的例子:

背景:我现在身处异世界,我有很多酷炫的技能

我现在有这样一个技能,发动这个技能,我可以在当前这个地方留一个分身并安排好任务,然后我可以继续做我自己的事情了,等我再次使用这个技能时,我的分身将开始处理这项任务,处理完成后,我的分身将会消失。

隐含的问题:给分身安排具体任务的时候,这个任务在未来是否能够完成是未知的,因为我们不知道未来会发生什么

代码亦是如此。

进一步理解:程序是严密而又真实的,所以并不存在什么高科技呀,黑魔法呀~

block其实就是用程序实现了代码缓存和对象缓存,相应的对象缓存在block对象中,

执行block,就是把缓存的代码执行一遍,而相应的对象的状态,可能会因为执行完缓存下来的代码而发生变化。

到此,希望你对block会产生了那么一点点的兴趣~

依旧还是要从源码说起

强调:以下讲的变量a 默认指的是 自动变量auto,不是对象类型

#import 
  
    

int main(int argc, char * argv[]) {
@autoreleasepool {

int a = 0;
void (^block)(void) = ^{
NSLog(@"%d",a);
};

block();

return 0;
}
}

将其翻译成底层c++文件, 一点一点看

main函数里的代码

int a = 10;
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);

这是一大串什么?

不要着急,我们从上到下,从左往右进行说明。

首先,block初始化这一行

// 原代码
void (^block)(void) = ^{
       NSLog(@"%d",a);
 };
// 翻译成c++代码
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
// 简化后
block =  &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,a);
  • void (*block)(void) 函数指针,参数void,返回值void

  • ((void (*)()) 上面函数指针的类型,作用是强转

  • &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)) 分解来看

分解第一步:__main_block_impl_0 组成

__main_block_impl_0是一个结构体,包含一个构造函数和三个变量

一个普通的结构体类型,一个结构体指针类型,

还有一个和外部的变量a,名称一样,类型一样。

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=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

分解第二步:__main_block_impl_0 的 构造函数

第一个参数void *fp,传入的是 &__main_block_func_0

__main_block_func_0是一个静态函数,

它的参数又是__main_block_impl_0这个结构体指针

即 FuncPtr 存储的是 __main_block_func_0 静态函数地址

impl.FuncPtr = fp;

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_9bc6d9_mi_0,a);
}

是不是快要绕晕了呢?

在静态函数__main_block_func_0内,

先是获取__main_block_impl_0结构体内的变量a,然后打印出来。

从这一点可以看出 静态函数的作用就是写在block内部的代码的容器和入口。

而__main_block_impl_0结构体内的变量a的值来自外部,是在结构体的构造函数内进行了赋值操作。

得到如下结论:

获取到的基础变量的值在block初始化的时候已经确定了,

block外部的变量a在后续无论做什么操作,都不会影响block内部保存的变量a

这就是在block内部直接修改自动变量会报错的原因,

如果这里直接允许修改了,在目前条件下,也仅仅能做到block内部的变量a进行重新赋值操作,和外部变量a没有关系,产生歧义。

ios - block原理解读(一)

当然,使用__block修饰的自动变量可以进行修改,那么又是什么原理呢?我们先继续解读当前的源码

第二个参数 &__main_block_desc_0

__main_block_desc_0静态结构体存储的是block的基础信息

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)};

第三个参数为我们用到的外部的变量a,赋值给结构体内的变量a

第四个参数没有传,默认为0 (辅助变量,可以忽略,不会影响block的理解)

分解第三步:__main_block_impl_0 中的 __block_impl

存储的block的信息,相应的参数在构造函数内进行了赋值操作

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

最后 block初始化代码和执行代码放在一起看

void (^block)(void) = ^{
       NSLog(@"%d",a);
 };
// c++代码
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
// 简化
block =  &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)

block();
// c++代码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// 简化
block->FuncPtr(block);

block->FuncPtr 指的就是 __main_block_func_0这个函数的地址,参数为block本身

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_19f603_mi_0,a);
 }

串起来再看block构造

block初始化:block 通过 __main_block_impl_0结构体构造函数进行初始化,同时生成__main_block_func_0静态函数,并将其地址以及其他相关信息储存在__block_impl这个结构体成员变量中。

其中,__block_impl这个结构体成员变量是__main_block_impl_0的首地址。

block调用:block指针指向的是__main_block_impl_0 的首地址,即__block_impl的地址,所以可以强转为(__block_impl *)类型,并访问其成员FuncPtr,指向的是静态函数地址,并传入参数__main_block_impl_0,也就是block自己。

名称 类型 是否随block内容改变 生成顺序 说明
__block_impl 结构体 NO 1 底层结构体,属于__main_block_impl_0成员
__main_block_impl_0 结构体 YES 2 缓存变量/对象,主结构体
__main_block_func_0 静态函数 YES 2 缓存代码,地址存放在__block_impl中

该缓存代码指的是:

ios - block原理解读(一)

总结:

将外部变量/对象的信息缓存在__main_block_impl_0中,

将代码缓存在静态函数中,

静态函数在缓存代码的时候需要用到外部变量/对象的信息

执行block就是执行了该静态函数

如果到此有不理解的地方可能c++基础较薄弱,百度一下辅助查看

最后

本文说明了block的用意,揭开了黑魔法的初级面纱,细讲了block基础源码,解释了基础类型的变量为何不能在block内部直接修改

后续会借此基础之上,继续解读目录中的问题

作者:tigerAndBull
链接:https://www.jianshu.com/p/1e8855a1b47d


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Code

Code

Charles Petzold / Microsoft Press / 2000-10-21 / USD 29.99

Paperback Edition What do flashlights, the British invasion, black cats, and seesaws have to do with computers? In CODE, they show us the ingenious ways we manipulate language and invent new means of ......一起来看看 《Code》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器