iOS Block 部分一

2020-06-30  本文已影响0人  飞不越疯人院

主要讲解 Block 的底层实现原理;

Block部分一
Block部分二
Block部分三
Block知识点总结

基础知识 C++中结构体的构造函数是怎么实现的?

很多 OC 对象低层都转化为了C++的结构体, 由于不懂 C++的语法, 所以网上查了下一些C++结构体的基础知识;
结构体的构造函数

 typedef struct Test{
    int id;
    string name;
     // 用以不初始化就构造结构体
    Test(){} ;
    //只初始化name
    Test(string _name) {
      name = _name;
    }
    /*
    同时初始化id,name , 把入参的 id 和 name 为结构体中的 id 和 name 赋值;
    参考下方 OC 类的构造函数
    */
    Test(inr _id,string _name): id(_id),name(_name)}{};
   }; 

类似于我们的构造函数实现;

///.h 文件声明
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject

- (instancetype)initWithName:(NSString *)name
                         age:(NSInteger)age;
@end
NS_ASSUME_NONNULL_END
///.m文件实现
#import "Person.h"

@interface Person ()
@property (nonatomic, strong) NSString  *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        self.name = name;
        self.age = age;
    }
    return  self;
}
@end

1. 什么是Block?

Block是一个对象, 对象中封装了一个函数以及函数执行的上下文;Block的调用本质是函数的调用;


2. Block的底层实现是什么?

block 的底层是一个结构体__XXXX_block_impl_0

测试代码如下, 一个最简单的Block; 通过指令转换为C++文件后得到如下结果;

///viewDidLoad中写一个最简单的block
- (void)viewDidLoad {
    [super viewDidLoad];
    ///case1  block的底层结构
     ^{
        NSLog(@"Block内部内容;");
        NSLog(@"Block内部内容;");
        NSLog(@"Block内部内容;");
        NSLog(@"Block内部内容;");
      };
}

通过指令转化为C++文件, 底层实现为

static void _I_ViewController1_viewDidLoad(ViewController1 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController1"))}, sel_registerName("viewDidLoad"));
  /*
    Block的底层实现如下;  __XXXX_block_impl_0结构体即为block的底层结构
    通过__XXXX_block_impl_0构造函数来初始化这个结构体, 入参两个参数
    __XXXX_block_func_0: 实际block中需要执行的的代码块
    __XXXXX_block_desc_0_DATA: block 的一些信息
  */    
((void (*)())&__ViewController1__viewDidLoad_block_impl_0((void *)__ViewController1__viewDidLoad_block_func_0, &__ViewController1__viewDidLoad_block_desc_0_DATA));
}
===>
block 的底层为一个结构体__XXXX_block_impl_0;
///__XXXX_block_impl_0的结构如下
struct __ViewController1__viewDidLoad_block_impl_0 {
  ///bock 的实现信息
  struct __block_impl impl;
  ///block 的描述信息
  struct __ViewController1__viewDidLoad_block_desc_0* Desc;
  __ViewController1__viewDidLoad_block_impl_0(void *fp, struct 
  /// block 的构造函数, 为结构体里面的变量赋值, 参考 OC 类的构造函数
__ViewController1__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    ///block中需要执行的代码块, 具体放在内存中的代码段;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
===>
///实际block中的代码块(这个 block 中就是那4个 NSLog)
static void __ViewController1__viewDidLoad_block_func_0(struct __ViewController1__viewDidLoad_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_0);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_3);
}

===>
///block 的实现信息
struct __block_impl {
  ///isa 指针
  void *isa;
  ///标志; 通过构造函数可以看到传入的是0; 
  int Flags;
  ///保留字段
  int Reserved;
  /*
    block 中需要执行的代码块封装的函数地址;
   __XXXX_block_impl_0结构体的构造函数会为其赋值
*/
  void *FuncPtr;
};

===>
///block的一些描述信息
static struct __ViewController1__viewDidLoad_block_desc_0 {
  ///保留字段
  size_t reserved;
  ///block 所占的空间(看下面对sizeof(struct __main_block_impl_0)即可得知)
  size_t Block_size;
} __ViewController1__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController1__viewDidLoad_block_impl_0)};

总结底层的几个结构体和函数
XXXX_block_impl_0 : 就是block对象的底层结构体;
__block_impl: 就是封装了block具体实现的结构体;
XXXX_block_func_0: 就是封装了block内部执行代码块的函数;
XXXX_block_desc_0: 就是封装了block的一些基本信息;

注意: 如果访问了auto变量(ARC), 因为对block进行了copy到堆区的操作, 所以通过clang后会出现两套block, 而调用部分会变成 XXXX_block_impl_1, XXXX_block_func_1等, 因为这个是对XXXX_block_impl_0, XXXX_block_func_1的拷贝后的;


3. 如何定义Block并且通过名字调用的?

在弄清这个问题首先印证一个问题;一个结构体嵌套结构体时, 如果第一个变量是结构体, 则可以通过内存地址直接访问内层结构体中的变量;代码如下:

#import "ViewController2.h"
@interface ViewController2 ()
@end
///case2的印证结构体嵌套时使用
struct SubStruct {
    int a;
};
struct SuperStruct {
    struct SubStruct  subs;
    int b;
};
@implementation ViewController2
- (void)viewDidLoad {
    [super viewDidLoad];
    struct SuperStruct supers = {{10}, 2};
    NSLog(@"%p    %p", &(supers.subs), &supers);
    ///(强制转换为int值)(强制转换为SubStruct)&取supers的地址->访问SubStruct中变量
    int subStruct_a = (int)((struct SubStruct *)&supers)->a;
    NSLog(@"通过外层结构体地址强行访问内存结构体的变量 %d", subStruct_a);
 }

两个地址是一样的; 由于substructsuperstruct 的第一个变量, 所以substruct和外层的superstruct地址是一样的;

2020-06-24 17:19:15.005827+0800 BlockMore1[6213:90176] 0x7ffee413cc88    0x7ffee413cc88

可以通过内存地址方式superstruct调用内层substruct变量;

2020-06-24 17:19:15.005918+0800 BlockMore1[6213:90176] 通过外层结构体地址强行访问内存结构体的变量 10

通过下面代码来探究block的调用方式;

    ///case2  定义一个 block 然后调用
    void(^Case2Block)(void) =  ^{
        NSLog(@"Block内部内容;");
        NSLog(@"Block内部内容;");
        NSLog(@"Block内部内容;");
        NSLog(@"Block内部内容;");
    };
    Case2Block();

通过指令转换为C++文件后的代码为

      ///Case2Block的定义过程
     void(*Case2Block)(void) = ((void (*)())&__ViewController2__viewDidLoad_block_impl_0((void *)__ViewController2__viewDidLoad_block_func_0, &__ViewController2__viewDidLoad_block_desc_0_DATA));
      ///Case2Block的实际调用
     ((void (*)(__block_impl *))((__block_impl *)Case2Block)->FuncPtr)((__block_impl *)Case2Block);

直接看不够直观; 我们将一些强制转换的相关代码去掉;


4. 什么是 Block 的变量捕获(capture)?

首先看下方代码, 我们都知道最后的 打印结果为 a = 100, b = 200, c = 3, d = 400; 原因是 a是全局变量, b是全局静态变量, c是局部变量, d是静态局部变量; 但是为什么会这样? 我们从源码角度分析下为什么;


#import "ViewController3.h"
@interface ViewController3 ()
@end

int a = 1;//全局变量, 整个工程内都有效;
static int b = 2;//静态全局变量, 只在定义它的文件内有效;
@implementation ViewController3
- (void)viewDidLoad {
    [super viewDidLoad];
    /******************************************************************/
    ///case3   block 的变量捕获
    int c = 3;//局部变量, 只在定义他的函数内有效;1
    static int d = 4;//静态局部变量, 只在定义他的函数内有效, 且内存只分配一次;
    /*
    block将局部变量捕获到block内部, 主要一个是捕获值, 一个是捕获址;全局变量不捕获;
    */
    void(^Case3Block)(void) =  ^{
        NSLog(@"a = %d, b = %d, c = %d, d = %d", a, b, c, d);
    };
    a = 100;
    b = 200;
    c = 300;
    d = 400;
    Case3Block();
}
@end

2020-06-25 09:50:45.294219+0800 BlockMore1[2775:24440] a = 100, b = 200, c = 3, d = 400

将上方的代码通过指令编译为C++文件后, 我们可以看到Case3Block的相关代码如下

/*
注意: 全局变量a和b底层实现也是如此, 并没有发生变化;
*/
int a = 1;
static int b = 2;
/*
Case3Block的底层实现,已经变化, block内部重新定义两个变量, 一个全新的c变量, 和一个地址变量d; 
*/
struct __ViewController3__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController3__viewDidLoad_block_desc_0* Desc;
  int c;
  int *d;
  __ViewController3__viewDidLoad_block_impl_0(void *fp, struct __ViewController3__viewDidLoad_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

看过上方源码后相信就可以很直观的得知:
全局变量, block内部并不会捕获创建新的变量进行存储, 所以更改全局变量后,仍然是直接调用全局变量;
局部变量: block会进行变量捕获, 局部变量是捕获值, 静态局部变量捕获址;


补充: self是全局变量还是局部变量?block内是否会捕获self?

self是局部变量, block内部会捕获self;
通过底层代码验证捕获self;

@interface ViewController31 () {
    int  _age;
}
@property (nonatomic, assign) int  count;
@end
@implementation ViewController31
- (void)viewDidLoad {
    [super viewDidLoad];
    ///不论是访问成员变量还是访问属性, 最终都是会将self捕获到self内部;
    ^ {
        NSLog(@"age = %d,  count = %d", _age, self.count);
    };
static void _I_ViewController31_viewDidLoad(ViewController31 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController31"))}, sel_registerName("viewDidLoad"));
    ///block的定义
((void (*)())&__ViewController31__viewDidLoad_block_impl_0((void *)__ViewController31__viewDidLoad_block_func_0, &__ViewController31__viewDidLoad_block_desc_0_DATA, self, 570425344));
}

///block的实现
struct __ViewController31__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController31__viewDidLoad_block_desc_0* Desc;
  ///新增变量*self, 用来存储self
  ViewController31 *self;
  __ViewController31__viewDidLoad_block_impl_0(void *fp, struct __ViewController31__viewDidLoad_block_desc_0 *desc, ViewController31 *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

通过底层代码验证self是局部变量;

- (void)test31 {
    NSLog(@"实例方法self指向当前实例对象");
}

+ (void)test32 {
    NSLog(@"类方法self指向当前类对象");
}

所有的实例方法中底层默认传入*self指向此实例对象; 所以可以得知self是局部变量; 类方法中调用self是指向当前类对象;但是无论是实例方法还是类方法转换为C++文件可以看到都是有入参self参数的;

 /*
每个实例方法的底层, 是默认会入参两个参数
*self  指向当前的实例对象
_cmd 指向当前的方法
  */
static void _I_ViewController31_test31(ViewController31 * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController31_ab9aa9_mi_1);
}

 /*
每个类方法的底层, 是默认会入参两个参数
  self 指向当前的类对象
_cmd 指向当前的方法
  */
static void _C_ViewController31_test32(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController31_ab9aa9_mi_2);
}

通过指令将OC文件转换为C++文件
指令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件.m -o 文件-arm64.cpp
如果需要链接其他框架, 使用-framework参数; 例:-framework UIKit


参考文章和下载链接
测试代码
iOS clang指令报错问题总结
Apple 一些源码的下载地址
C++中结构体的构造函数
全局变量、静态全局变量、静态局部变量和普通局部变量的区别

上一篇下一篇

猜你喜欢

热点阅读