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


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

查看所有标签

猜你喜欢:

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

博客秘诀:超人气博客是怎样炼成的

博客秘诀:超人气博客是怎样炼成的

Darren Rowse、Chris Garrett / 向怡宁 / 人民邮电出版社 / 201005 / 39.00元

作为Web 2.0的新生事物的博客,如今已蓬勃发展,呈燎原之势,业已成为许多人的一种生活方式。中国从事博客写作的人数已达千万级,各类博客网站不可胜数。 然而,为什么有的博客人气鼎盛,拥趸众多,有的博客却门前冷落,少人问津呢?究竟应该怎样写好自己的博客,才能让它吸引更多访客的关注呢?博客网站还能为我做什么呢? 本书的两位作者长期主持知名博客站点ProBlogger.net,指导了成千上万......一起来看看 《博客秘诀:超人气博客是怎样炼成的》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换