Objective-C中Block的一些探索

Block

中文翻译成块,我的理解是对函数或者方法实现的一个封装,当然也有捕获外部变量的能力,只有当块被调用时,内部的方法实现才会执行。那块的本质是什么呢?对象?那又怎么去证明呢。灵光一闪,想到了下面一张图
在这里插入图片描述
根据上面这张图,我们是不是可以打印Block的SuperClass,看看到底是什么呢?

void(^block)(void) = ^(void){
     NSLog(@"Block");
};
block();
NSLog(@"%@",[block superclass]);
NSLog(@"%@",[[block superclass] superclass]);
NSLog(@"%@",[[[block superclass] superclass] superclass]);

2020-04-08 11:13:24.560052+0800 ObjcStudy[27895:510541] __NSGlobalBlock
2020-04-08 11:13:24.560199+0800 ObjcStudy[27895:510541] NSBlock
2020-04-08 11:13:24.560309+0800 ObjcStudy[27895:510541] NSObject

完美,这也验证了Block就是对象的猜想,在上面的输出中,我们发现了__NSGlobalBlock,这是不是Block的类型呢?有了这个想法,可以继续的往下探索Block的类型,同样,通过NSLog来输出Block到底有什么

void(^block1)(void) = ^(void){};
block1();
    
int a = 100;
void(^block2)(void) = ^(void){
    NSLog(@"%d",a);
};
block2();
NSLog(@"%@",block1);
NSLog(@"%@",block2);
NSLog(@"%@",^(void){NSLog(@"%d",a);});

2020-04-08 11:14:55.371336+0800 ObjcStudy[27895:510541] <__NSGlobalBlock__: 0x106384060>
2020-04-08 11:14:55.371479+0800 ObjcStudy[27895:510541] <__NSMallocBlock__: 0x6000000c01e0>
2020-04-08 11:14:55.371623+0800 ObjcStudy[27895:510541] <__NSStackBlock__: 0x7ffee987c130>

发现有以下三种类型的Block

  • NSGlobalBlock (程序的数据区域(.data区))
  • NSMallocBlock(堆)
  • NSStackBlock(栈)

问题变的有点意思了,在手敲代码的时候,发现Block内部可以捕获a的值,但是无法更改a的值
在这里插入图片描述
提示说需要__block来修饰a变量,为什么呢?想看看Block内部构造,到底做了些什么骚操作,我们知道Objective-C的代码底层都转换成了C++的代码,那我们就通过clang将.m .c文件转换成.cpp文件,看看到底搞了哪些事情,先建个main.c文件,写一个简单block如下:

#include <stdio.h>
int main(){
    void(^block)(void) = ^(void){};
    block();
    return 0;
}

通过clang转换成.cpp文件

clang -rewrite-objc main.c -o main.cpp

转换完成后发现,短短几行的代码,变成了几百行。。。浏览了下发现有个函数与我们写的main函数是一一对应的

int main(){
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

把强转类型都删了,看起来会舒服点

// 对应的代码是 void(^block)(void) = ^(void){};
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
// 对应的代码是 block();
(block)->FuncPtr)(block);

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

// __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}

// __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

通过上面的代码我们可以得出block的工作流程

  1. __main_block_impl_0()生成结构体函数
  2. 参数为__main_block_func_0 也就是block的{}中的内容
  3. __main_block_func_0赋值给了__block_impl类型的FuncPtr
  4. (block)->FuncPtr 也就是__main_block_func_0 也就是运行了block的{}里面的内容

大概知道了流程后,测试下捕获外部变量,看看Block又是怎么操作的

int a = 10;
void(^block)(void) = ^(void){
    NSLog(@"%d",a);
};
a++;
block();

2020-04-08 15:29:06.322437+0800 ObjcStudy[28744:583891] 10

输出结果居然是10!!!而不是11,同样clang下

int main(){
    int a = 10;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    a++;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    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 = __cself->a; // bound by copy
  printf("%d", a);
}

可以看出__main_block_impl_0对变量a是值得获取,捕获的是a的瞬时值,所以在Block之后对a做的任何操作,都不会使Block内部保存的a有所改变,上面Xcode有报错,提示需要在变量前添加__block,那加上又会出现什么效果呢?

#include <stdio.h>
int main(){
    __block int a = 10;
    void(^block)(void) = ^(void){
        printf("%d", a);
    };
    a++;
    block();
    return 0;
}

继续clang,发现__block 对变量a搞事情了,出现了新的结构体__Block_byref_a_0,并将a转换成了此结构体类型的变量!!!

int main(){
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    (a.__forwarding->a)++;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

// __Block_byref_a_0
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

// __main_block_impl_0
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
  printf("%d", (a->__forwarding->a));
}

可以看出被__block 修饰的a,生成的结构体(__Block_byref_a_0)保存了对a的地址引用(__Block_byref_a_0 *)&a,在捕获或者使用的时候,都是通过引用地址来访问变量a的值,当然block内部也可以给外部变量a赋值等一系列操作。这里的__forwarding很有意思,不管是block内部还是外部对变量a的访问或者操作,都是通过__forwarding访问的同一个__block对象。
在日常的Block使用过程中,经常会遇到循环引用,比如下面的代码:

typedef void(^Block)(void);
@interface ViewController ()
@property (nonatomic, copy) Block block;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.block = ^{
        NSLog(@"%@",self);
    };
    self.block();
}

self持有block,block又持有self,这样就导致了循环引用,使得当前ViewController不会被释放掉,引起内存泄露。假如把block或者self置为nil,是不是就能打破这种循环引用呢?block内部是无法直接对self操作了,这就需要用到__block

    __block UIViewController *vc = self;
    self.block = ^{
        NSLog(@"%@",vc);
        vc = nil;
    };

通过实验,__block确实可以打破这种循环引用,但是必须得确保block被调用,否则循环引用依然存在!!!
日常的开发中,经常会用__weak来避免循环引用,当然block内部对外部变量需要长时间占有的话,还会使用__strong,避免外部变量被提前释放

__weak typeof(self)weakSelf = self;
self.block = ^{
    NSLog(@"%@",weakSelf);
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值