iOS Block源码分析系列(一)————2分钟明白Block究竟是什么?

Block其实就是C语言的扩充功能,实现了对C的闭包实现,一个带有局部变量的匿名函数。Block的语法,类型介绍我这里就不BB了,网上太多了,全是介绍怎么写的,这里开几篇博客来看看block的源码和内部实现结构,网上写的很乱很杂,而且都不全,自己买了本书,开搞!!!!!!

入门嘛,咱们先来一段最最最简单的代码

#include "stdio.h"
int main()
{
	void (^block)(void) = ^(void)
    {printf("aaa\n");};
	block();
	return 0;
}

楼主你sb么,这那么简单你看个球啊

别急,我们来看看Clang(LLVM)

通过clang命令行工具中的-rewrite-objc参数,我们可以把OC代码转化为C++的实现。帮助文档中的说明是这样的:Rewrite Objective-C source to C++

clang-rewrite-objc文件名

以上命令会生成一个a.cpp文件,其中就是a.c文件的C++实现。越过开头的冗余代码,我们关心的代码部分往往在最下端


不会用vim编写代码的看看这个,两分钟搞定点击打开链接

终端里面clang -rewrite-objc mkj.c,然后就会生成cpp,转换的源代码就出现了

这个文件打开就会出现几百行代码,这里的代码很多都是声明来的,总结下来就是如下代码

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("aaa\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()
{
 void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
 return 0;
}

那两行代码竟然增加到了那么多,可以从里面看出来,最终block匿名函数会用C语言的函数指针来处理


源码结构分析部分

1.block实际的结构体部分(本体)

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;
  }
};
首先impl和Desc也是两个结构体,而__main_block_imp_0就是该结构体的构造函数,用来初始化,后面细细介绍


2.我们先来看看第一个成员变量impl

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
在objc中, id 代表了一个对象。根据上面的声明, 凡是首地址是*isa的struct指针,都可以被认为是objc中的对象

isa指针,这个很熟悉吧,这就能看出其实block其实就是对象

他的第一个属性也是一个结构__block_impl,而第一个参数也是一个isa的指针。
在运行时,NSObject和block的isa指针都是指向对象的一个8字节。
NSObject及派生类对象的isa指向Class的prototype,而block的isa指向了_NSConcreteStackBlock这个指针。就是说当一个block被声明的时候他都是一个_NSConcreteStackBlock类的对象。

flags和reserved这两个基本就是某些标记(暂时没那么重要)
FuncPtr这个就是函数指针,也就是block所需要执行的代码段,真正存的地址


3.第二个成员变量Desc

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
顾名思义reserve和Block_size分别代表了版本升级所需的区域和Block的大小


4.第三个就是这个结构体的构造函数(可以理解为对象的初始化方法)

__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;
  }
可以通过这里看出,初始化的时候会右fp指针(这里就是函数指针),以及desc结构体的指针传进来初始化,void *fp的指针赋值给了FuncPtr指针,以上的结构和初始化函数就是基本的Block生成的源代码


源码调用部分分析

5.现在来看看Main函数中调用的基本转换(初始化转换)

 void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

看起来这个函数好吓人啊,没事,咱们把类型都去了,而且分开两步来写

/* 调用结构体函数初始化 
struct __main_block_impl_0 impBlock = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);

/* 赋值给该结构体类型指针变量
struct __main_block_impl_0 *block = &impBlock;

这非常容易理解了吧,就是把栈上生成的__main_block_impl_0的结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量block

__main_block_func_0就是转换成的函数指针,这样void (*block)(void) = ^{}这句简单的代码最终就是上面的结构体初始化函数的内部实现逻辑


6.再来细看下初始化函数内部的实现__block_main_impl_0

__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
第一个参数是由Block块语法转换的正真内部函数指针,第二个参数就是作为静态全局变量初始化__main_block_desc_0的结构体实例指针,初始化如下

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

最终,初始化内部进行赋值

isa = &_NSConcreteStackBlock

Flags = 0

Reversed = 0

FuncPtr = __main_blcok_func_0(就是Block块代码转换成的C语言函数指针)

Desc = &__ main_block_desc_0_DATA


7.最终block()调用的内部实现

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
老规矩 ,去掉类型就是

(*block->imp.FuncPtr)(block);

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("aaa\n");}
没错,最后就是直接调用函数指针进行最终的调用,由上面所描述的,FuncPtr就是由__main_block_func_0的函数指针所赋值的指针,而且可以看出,这个函数的参数正是指向block本身结构体实例,block作为参数进行了传递


扩充下:

还记得初始化的时候内部出现这句话了么

isa = &_NSConcreteStackBlock

看过runtime源码的应该能明白,objc_object结构体有个Class isa,这个isa指向下面这个结构体

struct objc_class {

    Class isa  OBJC_ISA_AVAILABILITY;



#if !__OBJC2__

    Class super_class                                        OBJC2_UNAVAILABLE;

    constchar *name                                         OBJC2_UNAVAILABLE;

    long version                                             OBJC2_UNAVAILABLE;

    long info                                                OBJC2_UNAVAILABLE;

    long instance_size                                       OBJC2_UNAVAILABLE;

    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;

    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;

    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;

    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;

#endif



}


简单来说这个isa就是指向其类或者类的父类的指针,包含了父类各种方法和属性,感觉说不明白啊,我擦




这么理解吧,isa指针__NSConcreteStackBlock相当于class的结构体实例,关于该类的所有信息都包含在这个地址里面,跑起来的时候就可以通过该指针去父类找需要的东西了,那么Block最简单的实质应该有个初步了解了吧,明白了Block其实就是OC里面的对象了

概括下:

1.__main_block_impl_0这个就是Block内部的结构体(该结构体成员有impl,Desc 和一个该结构体的初始化函数)

2.Block块内的代码转换成了__main_block_func_0的c语言函数(参数就是__main_block_impl_0结构体)

3.Block语法的初始化实际就是将__main_blcok_impl_0的结构体实例化,重点是Block的代码块{}通过转换成C的函数指针进行结构体的成员变量FuncPtr指针赋值

4.Block调用就是通过实例化的结构体里面的FuncPtr指针就行函数调用,而且参数就是该结构体本身(暂时没用到这个传出去的结构体,另外写一个介绍带参数是如何完成回调的,如何截获变量等等)




收工~~~~~~~~睡觉~~~~~~~~~有不同理解告诉我,看我不打死你~~~~~~


看第二集的同学可以点击点击看第二集,继续听我叨逼叨,反正瞎BB的,对了,别看C的代码很长,其实分析下很简单的呦......


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值