iOS - Block探究系列一

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

内容简介:一直搞不懂OC的Block和Java的Lambda,特意研究了一下OC的Block。如果有理解不对或者不到位的地方,欢迎指正。 这一篇我们先梳理一下Block从声明到调用的大致流程。我们在开发中,声明变量的形式一般如下:可是蛋疼的Block声明形式却是:

一直搞不懂OC的Block和 Java 的Lambda,特意研究了一下OC的Block。如果有理解不对或者不到位的地方,欢迎指正。 这一篇我们先梳理一下Block从声明到调用的大致流程。

前言

我们在开发中,声明变量的形式一般如下:

NSInteger number;
UIView *view;
复制代码

可是蛋疼的Block声明形式却是:

void (^block)(int number);
复制代码

为什么不是这种形式:

void (^)(int number) block;
复制代码

带着疑问,我们往下看。

一、 C语言 函数指针

首先,我想让大家看一段代码:

int main(int argc, char * argv[]) {
    // funcPtr为指针变量
    int (*funcPtr)(int count);
    // 指针变量指向函数的地址
    funcPtr = &func;
    int count = 10;
    // 调用方法
    (*funcPtr)(count);
}

// 函数实现
int func(int count) {
    int result = count++;
    NSLog(@"count = %d,地址:%p", count, &count);
    printf("result = %d\n", result);
    return result;
}
复制代码

上面代码中的funcPtr并不是函数名,而是一个指针变量,它指向了func函数的地址。

我们再来看一下Block的声明和实现:

- (void)block {
    // 声明block
    void (^block)(int count);
    int count = 10;
    // block的实现
    block = ^void (int count) {
        count++;
        NSLog(@"count = %d,地址:%p", count, &count);
    };
    // block的调用
    block(count);
    NSLog(@"count = %d,地址:%p", count, &count);
}
复制代码

我们可以发现,函数指针的声明和Block的声明格式很相似 。仅有两点不同:

  • Block没有函数名
  • Block带有"^"(插入记号):因为macOS和iOS的APP源代码中大量使用Block,插入记号便于查找 那么我们可以猜测一下,block的底层实现可能是C语言的函数指针?

二、Block声明的底层实现

block的底层实现可能是C语言的函数指针?我们来验证一下。 首先我们新建一个macOS的命令行项目,然后在main.m函数中声明并调用一个block,代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 声明block
        void (^block)(int count);
        int count = 10;
        // block的实现
        block = ^void (int count) {
            count++;
            NSLog(@"count = %d,地址:%p", count, &count);
        };
        // block的调用
        block(count);
        NSLog(@"count = %d,地址:%p", count, &count);
    }
    return 0;
}
复制代码

在终端中cd到main.m所在的目录,利用clang(LLVM编译器),把OC代码转换成C/C++代码:

clang -rewrite-objc main.m
复制代码

在main.m所在的文件夹会创建出main.cpp文件,在文件的最下面,找到了我们需要的代码:

// __block_imp结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// __main_block_desc_0结构体
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_block_impl_0结构体
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;
  }
};

// __main_block_func_0函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int count) {

            count++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_0r_hkkmpct143n4wd3xxk0l1j8c0000gn_T_main_3c0991_mi_0, count, &count);
}

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*block)(int count);
        int count = 10;
        
        // block的实现
        block = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        
        // block的调用
        ((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, count);
        
        // 打印count
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_0r_hkkmpct143n4wd3xxk0l1j8c0000gn_T_main_3c0991_mi_1, count, &count);
    }
    return 0;
}
复制代码

接下来,我们会分析上面代码中的各个结构体。

2.1 __block_impl结构体

// __block_imp结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
复制代码

__block_imp可以理解为block类对象的结构体。下面我会介绍一下该结构体中每一个成员。 isa 指针表示block是由 _NSConcreteStackBlock_NSConcreteGlobalBlock 或者 _NSConcreteMallocBlock 实例化的。所以,block对象有以上三种基类。关于这三种基类,之后的文章我会详细说明,敬请期待。 Flags标识符,默认为0。Reserved为保留字段。FuncPtr指针变量,它是一个函数指针,指向实现block闭包内自定义的代码 __main_block_func_0 函数

2.2 __main_block_desc_0结构体

// __main_block_desc_0结构体
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)};
复制代码

reserved为保留字段,目前用不到,以后可能会用上。Block_size表示该结构体占据了多少空间。

2.3 __main_block_impl_0结构体

// __main_block_impl_0结构体
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;
  }
};
复制代码

__main_block_impl_0 结构体有两个成员,分别是 __block_impl 结构体和 __main_block_desc_0 结构体的指针。 我们来看下 __main_block_impl_0 结构体的创建函数:

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
复制代码

我们可以看到需要传fp指针和desc结构体,fp是一个函数指针,用来赋值给impl成员的FuncPtr变量。fp指向的是 __main_block_func_0 函数

2.4 __main_block_func_0函数

// __main_block_func_0函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int count) {

            count++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_0r_hkkmpct143n4wd3xxk0l1j8c0000gn_T_main_3c0991_mi_0, count, &count);
}
复制代码

__main_block_func_0函数是用户写在block闭包内部的代码。

上面几部分是block的声明和实现转换成c/c++语言。 下面我们看一下main()函数里面block的调用。

三、Block调用的底层

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*block)(int count);
        int count = 10;
        
        // block的赋值
        block = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        
        // block的调用
        ((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, count);
        
        // 打印count
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_0r_hkkmpct143n4wd3xxk0l1j8c0000gn_T_main_3c0991_mi_1, count, &count);
    }
    return 0;
}
复制代码

接下来我们着重看一下上面main()函数。

3.1 Block的赋值

// block的赋值
block = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
复制代码

((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 这句代码就是 __main_block_impl_0 结构体的创建,传入 __main_block_func_0__main_block_desc_0_DATA 两个参数。然后使用取地址符 & 获取结构体的地址,并且进行强转 ((void (*)(int)) ,强转为声明block的数据类型 void (^block)(int count); 。这里就是函数指针的应用。

3.2 Block的调用

// block的调用
((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, count);
复制代码

需要注意的是上面代码中的 block 是个指针变量,这句代码其实就是调用了 __block_impl * 类型的 block 的FuncPtr函数 __main_block_func_0__main_block_func_0 函数需要传入 __block_impl * 类型的block和 void (^block)(int count); 中的 count

以上就是block在main()函数中的调用。

四、梳理流程

接下来我们把block从声明到调用的流程梳理一下。 总结一下,block,其实就是C语言的函数指针,只不过多了一些结构体用来表示OC对象。 整体的流程为: step1: 声明函数指针 void (*block)(int count); step2: 创建 __main_block_impl_0 结构体,其实FuncPtr指向 __main_block_func_0 函数,step1中的block指针变量指向 __main_block_impl_0 的地址 step3: 调用block(__main_block_impl_0)中的FuncPtr(__main_block_func_0)函数。

以上三步对比第一章中C语言的函数指针:

// funcPtr为指针变量
int (*funcPtr)(int count);
// 指针变量指向函数的地址
funcPtr = &func;
int count = 10;
// 调用方法
(*funcPtr)(count);
复制代码

这样block从声明到调用的流程就清晰了。所以block表面上是匿名函数,实际上在底层还是声明了函数 __main_block_func_0 ,是有函数名。


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

查看所有标签

猜你喜欢:

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

Chinese Authoritarianism in the Information Age

Chinese Authoritarianism in the Information Age

Routledge / 2018-2-13 / GBP 115.00

This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!

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

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试