分享

Runtime05Block原理

 印度阿三17 2019-09-14

例子1

main.m文件的代码如下:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 1;
        void (^myBlock)(int, int) = ^(int a, int b) {
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        myBlock(2, 3);
    }
    return 0;
}

执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 后,打开生成的main-arm64.cpp文件,会看到如下源码:


struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr; //block代码块的内容被封装成了一个函数(函数名为__main_block_func_0),该函数的地址就是FuncPtr变量的值。
};

struct __main_block_impl_0 {  //block被封装成了该结构体
  struct __block_impl impl;  //block的描述信息
  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, int a, int b) { //包含了block代码块的内容

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_rl1qfshn6n9dl6p87b_pjdtr0000gp_T_main_faed76_mi_0, a, b);  //打印a和b
        }

static struct __main_block_desc_0 {  //block的描述信息
  size_t reserved;
  size_t Block_size; //block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 1;

        void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));  //myBlock指向一个__main_block_impl_0结构体实例

        ((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 2, 3); //调用__main_block_func_0()函数
    }
    return 0;
}

在这里插入图片描述

例子2

在main函数里面的block中添加age的打印信息,此时main.m文件的代码如下:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 1;
        void (^myBlock)(int, int) = ^(int a, int b) {
            NSLog(@"age = %d", age);  //新增代码
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        myBlock(2, 3);
    }
    return 0;
}

执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 后,打开生成的main-arm64.cpp文件,会看到如下源码:

struct __main_block_impl_0 { //block被封装成该结构体
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;  //block的描述信息
  int age;  //因为block里面使用了main函数的age局部变量,所以main.m被转化成cpp代码时,age被封装成了__main_block_impl_0结构体的成员变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_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 a, int b) { //block里面的内容都被封装到该函数中
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_rl1qfshn6n9dl6p87b_pjdtr0000gp_T_main_432cf3_mi_0, age); //打印age
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_rl1qfshn6n9dl6p87b_pjdtr0000gp_T_main_432cf3_mi_1, a, b); //打印a和b
        }

static struct __main_block_desc_0 {  //block的描述信息
  size_t reserved;
  size_t Block_size;  //block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //保存着你定义的block的大小
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 1;

        void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); //定义myBlock变量并初始化

        ((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 2, 3); //函数调用
    }
    return 0;
}

运行结果如下
在这里插入图片描述

从上面的两个例子可以看出:block本质上也是一个oc对象,它内部也有一个isa指针。block里面的内容被封装成了一个函数。

思考题1:block内部使用block外面的局部变量(也称auto变量)

为什么下图的打印结果是1
在这里插入图片描述
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m ,进而将main.m文件转为main.cpp文件,然后打开main.cpp文件,找到main函数所在位置,然后分析打印结果:在这里插入图片描述
在这里插入图片描述

思考题2:block内部使用block外面的静态局部变量(也称static变量)

为什么下图的打印结果是weight = 4
在这里插入图片描述
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m ,进而将main.m文件转为main.cpp文件,然后打开main.cpp文件,找到main函数所在位置,然后分析打印结果:
age是局部变量,block对应的__main_block_impl_0结构体中就定义了一个age成员变量,该变量存的是局部变量age的值。而weight是静态局部变量,block对应的__main_block_impl_0结构体中就定义了一个指向weight的成员变量,该变量存的是静态局部变量weight的地址。
在这里插入图片描述

思考题3:block内部使用block外面的全局变量和文件内的全局变量(也称global变量)

为什么下图的打印结果是age = 2,weight = 4 ?
在这里插入图片描述
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m ,进而将main.m文件转为main.cpp文件,然后打开main.cpp文件,找到main函数所在位置,然后分析打印结果:block对应的结构体__main_block_impl_0不会生成全局变量的副本,在block内部对全局变量直接使用即可。
在这里插入图片描述

思考题3:在一个类的方法内部的block使用self

demo的代码如下
在这里插入图片描述
在这里插入图片描述
打印结果如下
在这里插入图片描述

答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m ,进而将Person.m文件转为Person.cpp文件,然后打开Person.cpp文件,找到myBlock所在位置。可以看到Person类的test方法在转化成c 后,会变成_I_Person_test()函数,且该函数的第1、2个参数分别是 self 和 _cmd ,既然是函数参数,所以self 和 _cmd 都是局部变量,既然是局部变量,那么block在使用该局部变量时,就会在block对应的结构体里面添加该成员变量。每一个类的方法(比如实例方法或者类方法,即方法开头是 - 号 或者 号的方法),默认都会有两个参数:self 和 _cmd 参数。
在这里插入图片描述

思考题4:在一个类的方法内部的block使用类的成员变量

demo的代码如下
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
打印结果如下:
在这里插入图片描述

答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m ,进而将Person.m文件转为Person.cpp文件,然后打开Person.cpp文件,找到myBlock所在位置。可以看到Person类的test方法在转化成c 后,会变成_I_Person_test()函数,且该函数的第1、2个参数分别是 self 和 _cmd ,既然是函数参数,所以self 和 _cmd 都是局部变量。但是block对应的__Person__test_block_impl_0结构体里面只是添加了self成员变量,并不会添加Person类的name成员变量。在调用block时,block对应的__Person__test_block_func_0函数会通过Person类类型的self找到Person的成员变量name,然后打印。
在这里插入图片描述

思考题5:block本质上也是一个oc对象

在这里插入图片描述

思考题5:block对应的类的类型

block对应的类的类型有3种:

  • NSGlobalBlock :存储在全局区,当block内部没有使用局部变量(auto变量)时,该block的类型就是 NSGlobalBlock 类型。
  • NSStackBlock :存储在栈上,当block内部使用了局部变量(auto变量)时,该block的类型就是 NSStackBlock 类型。当定义该block的函数弹栈时,该block就会被销毁。
  • NSMallocBlock :存储在对上,当类型为__NSStackBlock__ 的block调用copy方法时,即 [stackBlock copy]的返回值就是__NSMallocBlock__ 类型的block。在ARC的编译环境下,类型为__NSStackBlock__ 的block会在编译时自动调用调用[block copy],进而返回__NSMallocBlock__ 类型的block。
  • 下图是打开ARC环境的编译运行结果。
    在这里插入图片描述
  • 下图是关闭ARC环境的编译运行结果,上图和下图的差别就是block2的类型。在关闭了ARC环境时,即此时为MRC环境,对于block2的赋值操作,系统会执行:void (^block2)(void) = ^ { NSLog(@"autoVar = %d", autoVar); };,从而把 NSStackBlock 类型的block赋值给block2。但在开启ARC环境时,对于block2的赋值操作,系统会执行:void (^block2)(void) = [^ { NSLog(@"autoVar = %d", autoVar); } copy];,从而把__NSMallocBlock__类型的block赋值给block2。
    在这里插入图片描述

思考题5:类型为__NSStackBlock__ 的block的潜在问题

  • 下图是在关闭ARC的环境下,即为MRC环境下的运行结果。类型为__NSStackBlock__ 的block会出现野指针错误。
    在这里插入图片描述
  • 下图是在打开ARC的环境下,即为MRC环境下的运行结果。类型为__NSMallocBlock__的myBlock是分配在堆上的,所以test()函数执行完后,myBlock执行的内存也不会被回收,所以此时age的值是能正确访问的(因为age作为myBlock指向的结构体对象的所属类的一个成员变量)。
    在这里插入图片描述
    age作为myBlock指向的结构体对象的所属类的一个成员变量
    图2

思考题5:block内部使用block外部的对象类型的auto变量所指向的对象的销毁问题(ARC和MRC环境下是不同的)

demo的目录结构:
在这里插入图片描述
main.m文件的代码如下:
在这里插入图片描述
Person.h文件:
在这里插入图片描述
Person.m文件
在这里插入图片描述

  • block内部使用强引用指向person对象的运行结果:
    在这里插入图片描述
  • block内部使用弱引用指向person对象的运行结果:
    在这里插入图片描述
    执行命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m ,查看 “block内部使用弱引用指向person对象”的c 实现,发现block对应的__main_block_impl_0结构体里面的weakPerson成员变量是弱引用类型。。。
    在这里插入图片描述

__block的原理

demo及其运行结果如下图
在这里插入图片描述
执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 命令,可以得到对应的main.cpp文件,该cpp文件的关键代码如下:
在这里插入图片描述

来源:https://www./content-4-451901.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多