OC 中的 block

OC 中的 block

[toc]

块对象(block)不是 OC 的特性而是 C 语言的实现,用一句话来概括 block:带有自动变量(局部变量)的匿名函数,在其他编程语言中,它与闭包(closure)的功能相同。

代码块的定义:

void (^myBlock)(void);

使用 typedef 关键字简化 block 的定义

//定义
typedef int (^myBlock)(int a, int b);
//使用
myBlock fun1 = ^(int a, int b){
  return a * b;
};

代码块的几种使用形式

// 无参数无返回值
void (^BlockOne)(void) = ^(void){
    NSLog(@"无参数,无返回值");  
};  
BlockOne();//block的调用

// 有参数无返回值
void (^BlockTwo)(int a) = ^(int a){
    NSLog(@"有参数,无返回值, 参数 = %d,",a);
};  
BlockTwo(100);

// 有参数有返回值
int (^BlockThree)(int,int) = ^(int a,int b){    
    NSLog(@"有参数,有返回值");
    return a + b; 
};  
BlockThree(1, 5);

// 无参数有返回值
int(^BlockFour)(void) = ^{
    NSLog(@"无参数,有返回值");
    return 100;
};
BlockFour();

代码块的几种使用方式

  1. 可以像函数一样使用
int (^square_block)(int number) = ^(int number){ return (number * number);};
int result = square_block(5);
  1. 作为参数传递给函数
void q_sort(void *base, size_t nel, size_t width, int ^(compare)(const void *, const void *));

探究 block 的本质

看看下面的例子分别打印出什么:

#import <Foundation/Foundation.h>

void blockFunc1()
{
    int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

void blockFunc2()
{
    __block int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

int num = 100;
void blockFunc3()
{
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

void blockFunc4()
{
    static int num = 100;
    void (^block)() = ^{
        NSLog(@"num equal %d", num);
    };
    num = 200;
    block();
}

int main() {
    blockFunc1();
    blockFunc2();
    blockFunc3();
    blockFunc4();
    return 0;
}

将自己的答案和打印出来的值对比一下,然后带着疑问继续下去

将 main.m 文件在命令行打开,然后执行以下语句

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

就会看到 main.m 文件所在目录有一个 C++ 的文件出现,打开,然后拖到最底下

可以看到 blockFunc1 的 C 语言实现

void blockFunc1()
{
    int num = 100;
    void (*block)() = ((void (*)())&__blockFunc1_block_impl_0((void *)__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

去掉类型转换可以看到

void blockFunc1()
{
    int num = 100;
    void (*block)() = &__blockFunc1_block_impl_0(__blockFunc1_block_func_0, &__blockFunc1_block_desc_0_DATA, num));
    num = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

这里可以看到 block 其实就是一个指向结构体的指针

该结构体为:

struct __blockFunc1_block_impl_0 {
  struct __block_impl impl;
  struct __blockFunc1_block_desc_0* Desc;
  int num;
  __blockFunc1_block_impl_0(void *fp, struct __blockFunc1_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

再看一下 blockFunc2 的 C 语言实现

void blockFunc2()
{
    __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 100};
    void (*block)() = ((void (*)())&__blockFunc2_block_impl_0((void *)__blockFunc2_block_func_0, &__blockFunc2_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
    (num.__forwarding->num) = 200;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

发现 block 指向的结构体传入的是存储 num 的地址,所以 num 的值改变,传入函数的值也随之改变

------------- 本文结束 感谢阅读 -------------