《Objective-C 高级编程》第二篇:Block源代码解析

本系列文章主要是对《Objective-C 高级编程》这本书做的读书笔记总结,除了这本书中的内容以外,也加上了自己对开发技术的理解和一些个人的经验分享。

一、Objective-C源代码 转 C++源代码的的方法

通过 clang(LLVM编译器)命令转换:

clang -rewrite-objc 源代码的文件名

先来看一个最简单的block

int main() {
    
    void (^blk)(void) = ^{
        printf("我是block\n");
    };
    
    blk();
    
    return 0;
}

将以上源代码转换成C++源代码

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

//block结构体
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;

  //Block构造函数
    __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;
    }
};

// 将来被调用的block内部的代码:block值被转换为C的函数代码
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)};

//main 函数
int main() {
    
    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;
}

看下面的代码

^ {
    printf("我是block\n");
};

可以看到,变换后的源代码中也含有相同的表达式

static void __main_block_func_0(struct __main_block_impl_0 * __cself) {
    
    printf("我是block\n");
}

通过block使用的匿名函数实际上被作为简单的C语言函数来处理。根据block语法所属的函数名(此处为main)和该block语法在该函数出现的顺序值(此处为0)来给函数命名 __main_block_func_0

该函数的参数 __cself 相当于C++实例方法中指向实例自身的变量this,或是Object-C实例方法中指向对象自身的变量self,即参数 __cself 为指向block值的变量。

这里的 __main_block_func_0 函数并没有使用到 __cself。使用 __cself 的例子将在后面介绍,我们先来看看该参数的声明,参数 __cself__main_block_func_0 结构体的指针。

struct __main_block_impl_0 * __cself

该结构体的声明如下:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 * Desc;
}

第一个成员变量是 impl,我们先来看一下 __block_impl 结构体的声明。

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

接下来我们看一下第二个成员变量 Desc指针

struct __main_block_desc_0 {
    unsigned long reserved; // 版本升级所需的区域
    unsigned long Block_size; // block的大小
}

那么,我们再来看一下初始化这些结构体的 _main_block_impl_0 结构体的构造函数

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

NSConcreteStackBlock用于初始化 __block_impl结构体 的isa成员,后面将会讲解。我们先来看一下该构造函数的调用。

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

因为转换较多,所以看起来比较不清楚,我们来去掉转换的部分,再看一下

struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

struct __main_block_impl_0 *blk = &temp;

这样就容易理解了。该源代码将 __main_block_impl_0结构体 类型的自动变量,赋值给 __main_block_impl_0结构体指针类型的变量blk

下面就来看看 __main_block_impl_0结构体实例构造参数。

__main_block_impl_0 (__main_block_func_0, &__main_block_desc_0_DATA);

第一个参数是由block语法转换为C语言函数指针。第二个参数是作为静态全局变量初始化的 __main_block_desc_0 结构体的实例指针。

以下为 __main_block_desc_0 结构体实例的初始化部分代码,可以看到源代码使用 __main_block_impl_0 结构体的实例大小,进行初始化。

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

下面看看栈上的 __main_block_impl_0结构体 实例是如何根本这些参数进行初始化的。

struct __main_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0* Desc;
};

该结构体根据构造函数会像下面这样进行初始化

isa = &_NSConcreteStackBlock;
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;

__main_block_func_0 函数指针赋给成员变量 FuncPtr

blk();

转换后的源代码如下

((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);

去掉转换部分:

(*blk -> impl.FuncPtr)(blk);

这就是简单地使用函数指针调用函数。

相关推荐