Block的底层数据结构和变量捕获

block的本质上也是一个OC对象,它内部也有一个isa指针

block是封装了函数调用以及函数调用环境的OC对象

结构示意图

在这里插入图片描述

变量捕获示意图

为了保证block内部能够正常访问外部的变量,block有变量捕获机制

在这里插入图片描述

示例代码

============================== 原始代码 ==============================
@interface TBPerson : NSObject

@property (nonatomic, strong) NSString *name;

@end

@implementation TBPerson

@end

@interface ViewController ()

@end

@implementation ViewController

//  全局变量
static const NSInteger height = 10;
static const TBPerson *person;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    person = [[TBPerson alloc] init];
    person.name = @"111";
    //  auto类型局部变量
    int age = 20;
    TBPerson *person1 = [[TBPerson alloc] init];
    person1.name = @"222";
    //  static类型局部变量
    static const int number = 30;
    static const TBPerson *person2;
    person2 = [[TBPerson alloc] init];
    person2.name = @"333";
    //  weak修饰的类型
    __weak TBPerson *person3 = person1;
    person3.name = @"444";
    
    void (^block) (void) = ^{
        //  对外部变量进行访问
        NSLog(@"%ld",(long)height);
        NSLog(@"%@",person.name);
        
        NSLog(@"%ld",(long)age);
        NSLog(@"%@",person1.name);
        
        NSLog(@"%ld",(long)number);
        NSLog(@"%@",person2.name);
        
        NSLog(@"%@",person3.name);
    };
    block();
}

============================== 使用clang转换为C++猜测底层实现 ==============================

/*
	__weak问题解决
    	再使用clang转换OC为C++代码时,可能会遇到以下问题
    	cannot create __weak reference in file using manual reference
	解决方法:支持ARC、指定运行时系统版本,比如
		xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 *** -o *** 
*/

/*
	将__ViewController__viewDidLoad_block_impl_0函数的地址赋值给block(block就是指向一个结构体对象)
        __ViewController__viewDidLoad_block_func_0          --- block要执行的函数
        __ViewController__viewDidLoad_block_desc_0_DATA     --- block的描述信息和捕获的对象内存管理方法
        后面的参数为block内部需要访问的外部变量
 */
void (*block) (void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0,
                                                                                &__ViewController__viewDidLoad_block_desc_0_DATA,
                                                                                age,person1, &number, &person2, person3, 570425344));
/*
   __block_impl为block结构体的第一个元素,所以__block_impl的地址就是block结构体的地址
   block的执行过程 --- block找到FuncPtr(block需要执行的函数地址)进行执行
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);


//  全局变量
static const NSInteger height = 10;
static const TBPerson *person;
/*
    可以看到全局变量height、person并没有捕获到block内部,直接访问
    只是捕获了局部变量
        auto    类型的局部变量进行的是值传递
        static  类型的局部变量进行的是指针传递
    (为什么进行变量捕获 --- 作用域的问题)

	每个方法里面默认传递2个参数 self,_cmd,所有方法传递的参数都是局部变量
 */
struct __ViewController__viewDidLoad_block_impl_0 {
    struct __block_impl impl;
    struct __ViewController__viewDidLoad_block_desc_0* Desc;
    //  auto类型的局部变量
    int age;
    TBPerson *__strong person1;
    //  static类型的局部变量
    const int *number;
    const TBPerson *__strong *person2;
    //  __weak修饰
    TBPerson *__weak person3;
    //	构造函数(类似于OC的init方法),返回结构体对象
    __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _age, TBPerson *__strong _person1, const int *_number, const TBPerson *__strong *_person2, TBPerson *__weak _person3, int flags=0) : age(_age), person1(_person1), number(_number), person2(_person2), person3(_person3) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

/*
   void *isa
       存储着block的地址
       struct __main_block_impl_0 的地址和 struct __block_impl 的地址是一样的
   void *FuncPtr
       存储着block执行逻辑函数的地址
*/
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

//  block的描述信息和对象类型内存管理方法
static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = {
    0,                                                          //  目前此参数未用到
    sizeof(struct __ViewController__viewDidLoad_block_impl_0),  //  block结构体的大小
    __ViewController__viewDidLoad_block_copy_0,                 //  如果栈上的block被拷贝到堆上时调用此方法(目前均使用ARC,如果有访问外部变量时系统会自动调用copy方法)
    __ViewController__viewDidLoad_block_dispose_0               //  如果block从堆上移除(废弃)时调用此方法
};

/*
    如果栈上的block被拷贝到堆上时调用此方法
        copy函数内部会调用_Block_object_assign函数
        _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,类似于retain(形成强引用、弱引用)
 */
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person1, (void*)src->person1, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->person2, (void*)src->person2, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->person3, (void*)src->person3, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

/*
    如果block从堆上移除(废弃)时
        dispose函数内部会调用_Block_object_dispose函数
        _Block_object_dispose函数会自动释放引用的auto变量,类似于release
 */
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {
    _Block_object_dispose((void*)src->person1, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->person2, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->person3, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

//  封装了block执行逻辑的函数
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    TBPerson *__strong person1 = __cself->person1; // bound by copy
    const int *number = __cself->number; // bound by copy
    const TBPerson *__strong *person2 = __cself->person2; // bound by copy
    TBPerson *__weak person3 = __cself->person3; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_47fdc6_mi_4,(long)height);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_47fdc6_mi_5,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_47fdc6_mi_6,(long)age);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_47fdc6_mi_7,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person1, sel_registerName("name")));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_47fdc6_mi_8,(long)(*number));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_47fdc6_mi_9,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person2, sel_registerName("name")));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_47fdc6_mi_10,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person3, sel_registerName("name")));
}

============================== block内部访问了对象类型的auto变量时 总结==============================
/*
	如果block是在栈上:
		将不会对auto变量产生强引用
	如果block是在堆上:
		1、会调用block内部的copy函数
		2、copy函数内部会调用_Block_object_assign函数
		3、_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,类似于retain(形成强引用、弱引用)
	如果block从堆上移除:
		1、会调用block内部的dispose函数
		2、dispose函数内部会调用_Block_object_dispose函数
        3、_Block_object_dispose函数会自动释放引用的auto变量,类似于release
*/

@end
PS 此文为学习 李明杰 老师的 iOS底层原理课程 所写笔记
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值