iOS开发集锦

浅谈OC中Block的本质

2019-11-04  本文已影响0人  iOS亮子

Block简介

定义和使用

block根据有无参数和有无返回值有以下几种简单使用方式

// 无参数无返回值
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();

可是以上四种block底层又是如何实现的呢? 其本质到底如何? 接下来我们一起探讨一下

Block的本质

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

打开main.cpp文件, 找到文件最底部, 可以看到block的相关源码如下

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

// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_11c959_mi_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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        // 定义block变量
        void (* BlockOne)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        // 执行block内部的源码
        ((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);

    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

其中block的声明和调用的对应关系如下

删除其中的强制转换的相关代码后

// 定义block变量
void (* BlockOne)(void) = &__main_block_impl_0(
                                                (void *)__main_block_func_0,
                                                &__main_block_desc_0_DATA
                                            );

// 执行block内部的源码
BlockOne->FuncPtr(BlockOne);

上述代码中__main_block_impl_0函数接受两个参数, 并有一个返回值, 最后把函数的地址返回给BlockOne, 下面找到__main_block_impl_0的定义

// 结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // c++中的构造函数, 类似于OC中的init方法
  // flags: 默认参数, 调用时可不传
  __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 struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

Block变量捕获

局部变量

auto变量捕获

auto局部变量在Block中是值传递

下述代码输出值为多少?

int age = 10;

void (^BlockTwo)(void) = ^(void){
    NSLog(@"age = %d,",age);
};

age = 13;
BlockTwo();
// 输出10

输出值为什么是10而不是13呢? 我们还是生成main.cpp代码看一下吧, 相关核心代码如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 这里多了一个age属性
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age);
        }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;  
        // 定义属性
        int age = 10;

        // block的定义
        void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        
        // 改变属性值
        age = 13;
        // 调用block
        ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

那么下面我们一步步看一下, 吧一些强制转换的代码去掉之后

int age = 10;

void (*BlockTwo)(void) = &__main_block_impl_0(
                                            __main_block_func_0,
                                            &__main_block_desc_0_DATA,
                                            age
                                            );

age = 13;
BlockTwo->FuncPtr(BlockTwo);

在上面的__main_block_impl_0函数里面相比于之前的, 多了一个age参数

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 新的属性age
  int age;
  // 构造函数, 多了_age参数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 及时这里重新对age进行了赋值
age = 13;

// 这里调用BlockTwo的时候, 结构体重的age属性的值并没有被更新
BlockTwo->FuncPtr(BlockTwo);

// 最后在执行block内部逻辑的时候, 
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    // 这里的age, 仍然是block结构体中的age, 值并没有改变, 所以输出结果还是10
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age);
}

static变量捕获

static局部变量在Block中是指针传递, 看一下下面代码的输出情况

auto int age = 10;
static int weight = 20;

void (^BlockTwo)(void) = ^(void){
    NSLog(@"age = %d, weight = %d,",age, weight);
};

age = 13;
weight = 23;
BlockTwo();
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  int *weight;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *weight = __cself->weight; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight));
        }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        auto int age = 10;
        static int weight = 20;

        void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight));

        age = 13;
        weight = 23;
        ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
int age;
int *weight;

// &weight
void (*BlockTwo)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age, &weight);

// 下面构造方法中, 同样(weight(_weight)方法之前讲过)将传过来的weight的地址赋值给了 (int *weight;)
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    int *weight = __cself->weight; // bound by copy

    // (*weight)相当于从weight的内存地址中取值, 在执行操作
    // 然而weight内存中的值已经在后面赋值的时候被更新了, 所以这里取出的值是赋值后的
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight));
}

也就是说, 同样是局部变量
auto修饰的变量在block中存储的是变量的值(值传递)
static修饰的变量在block中存储的是变量的内存地址(地址传递)
全局变量

int age = 10;
static int weight = 20;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^BlockTwo)(void) = ^(void){
            NSLog(@"age = %d, weight = %d,",age, weight);
        };
        
        age = 13;
        weight = 23;
        BlockTwo();
    }
    return 0;
}

上面代码的输出结果, 毫无疑问是13和23, 相关c++代码如下

int age = 10;
static int weight = 20;


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) {

            // 封装了block执行逻辑的函数
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_0ee0bb_mi_0,age, weight);
        }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        // 定义block变量
        void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        age = 13;
        weight = 23;
        ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

Block的类型

Block的三种类型

void (^block)(void) = ^(void){
    NSLog(@"Hello World");
};

NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);

/*
 2019-06-24 15:46:32.506386+0800 Block[3307:499032] __NSGlobalBlock__
 2019-06-24 15:46:32.506578+0800 Block[3307:499032] __NSGlobalBlock
 2019-06-24 15:46:32.506593+0800 Block[3307:499032] NSBlock
 2019-06-24 15:46:32.506605+0800 Block[3307:499032] NSObject
 */

block在内存中的分配

static int age = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int weight = 21;
        void (^block1)(void) = ^(void){
            NSLog(@"Hello World");
        };
        void (^block2)(void) = ^(void){
            NSLog(@"age  =  %d", age);
        };
        void (^block3)(void) = ^(void){
            NSLog(@"age  =  %d", weight);
        };
        
        NSLog(@"block1 = %@", [block1 class]);
        NSLog(@"block2 = %@", [block2 class]);
        NSLog(@"block3 = %@", [block3 class]);
        
        /*
         2019-06-24 21:13:14.555206+0800 Block[30548:1189724] block1 = __NSGlobalBlock__
         2019-06-24 21:13:14.555444+0800 Block[30548:1189724] block2 = __NSGlobalBlock__
         2019-06-24 21:13:14.555465+0800 Block[30548:1189724] block3 = __NSStackBlock__
         */
    }
    return 0;
}

针对各种不同的block总结如下

void (^block3)(void) = ^(void){
    NSLog(@"age  =  %d", weight);
};

NSLog(@"block3 = %@", [block3 class]);
NSLog(@"block3 = %@", [[block3 copy] class]);
/* 输出分别是: 
block3 = __NSStackBlock__
block3 = __NSMallocBlock__
*/
int weight = 21;
void (^block1)(void) = ^(void){
    NSLog(@"Hello World");
};
void (^block3)(void) = ^(void){
    NSLog(@"age  =  %d", weight);
};

NSLog(@"block1 = %@", [block1 class]);
NSLog(@"block1 = %@", [[block1 copy] class]);
NSLog(@"block3 = %@", [block3 class]);
NSLog(@"block3 = %@", [[block3 copy] class]);
NSLog(@"block3 = %@", [[[block3 copy] copy] class]);
/*
 __NSGlobalBlock__
 __NSGlobalBlock__
 __NSStackBlock__
 __NSMallocBlock__
 __NSMallocBlock__
 */

__block修饰符

Question: 定义一个auto修饰的局部变量, 并在block中修改该变量的值, 能否修改成功呢?

auto int width = 10;
static int height = 20;
void (^block)(void) = ^(void){
    // 事实证明, 在Xcode中这行代码是报错的
    width = 22;
    // 但是static修饰的变量, 却是可以赋值, 不会报错
    height = 22;
    NSLog(@"width = %d, height = %d", width, height);
};

block();

// width = 10, height = 22
__block auto int width = 10;

void (^block)(void) = ^(void) {
    // 很明显, 这里就可以修改了
    width = 12;
    NSLog(@"width = %d", width);
};

block();
// width = 12

为什么上面的代码就可以修改变量了呢, 这是为什么呢…请看源码

下面是生成的block的结构体

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // 这里的width被包装成了一个__Block_byref_width_0对象
  __Block_byref_width_0 *width; // by ref
  // 这里可以对比一下之前的未被__block修饰的int变量
  // int width;
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_width_0 *_width, int flags=0) : width(_width->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
struct __Block_byref_width_0 {
  void *__isa;
  // 一个指向自己本身的成员变量
  __Block_byref_width_0 *__forwarding;
  int __flags;
  int __size;
  // 外部定义的auto变量
  int width;
};

下面我们先看一下, auto和block的定义和调用

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        // __block auto int width = 10;
        auto __Block_byref_width_0 width = {
            0,
            &width,
            0,
            sizeof(__Block_byref_width_0),
            10
        };

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

        block->FuncPtr(block);
    }
    return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_width_0 *width = __cself->width; // bound by ref

    (width->__forwarding->width) = 12;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_9241d5_mi_0, (width->__forwarding->width));
}

作为一个开发者,有一个学习的氛围和一个交流圈子特别重要,这是我的交流群,点击进群(123),大家有兴趣可以进群里一起交流学习!

收录:原文地址

上一篇下一篇

猜你喜欢

热点阅读