10--Block原理

2021-07-03  本文已影响0人  修_远

一、Block类型

我们平时在开发中使用的到的Block主要有三种类型:

1.1 全局block:NSGlobalBlock

- (void)testGlobalBlock {
    // __NSGlobalBlock__: 全局block 没有参数 也没有返回值
    void (^globalBlock)(void) = ^{
        NSLog(@"testGlobalBlock");
    };
    
    NSLog(@"globalBlock %@", (globalBlock));
}
image.png

1.2 堆block:NSMallocBlock

- (void)testMallocBlock {
    // __NSMallocBlock__: 堆block 即是函数 也是对象
    // 对外部变量a进行了拷贝, 所以是堆block
    int a = 10;
    void (^mallocBlock1)(void) = ^{
        NSLog(@"mallocBlock1 : %d", a);
    };
    
    NSLog(@"mallocBlock1 %@", mallocBlock1);
    
    NSLog(@"mallocBlock2 %@", ^{
        NSLog(@"mallocBlock2 : %d", a);
    });
}
image.png

1.3 栈block:NSStackBlock

- (void)testStackBlock {
    // __NSStackBlock__: 在a没有拷贝前, 是栈block
    // 也可以显示的使用 __weak 表示 stackBlock1 对右边的block不持有, 这个时候右边的block就还是栈block
    int a = 10;
    void (^__weak stackBlock1)(void) = ^{
        NSLog(@"stackBlock1 : %d", a);
    };
    
    NSLog(@"stackBlock1 %@", stackBlock1);
}
image.png

总结

Block循环引用

循环引用造成的条件就是互相持有,或者是在某条链路上是存在环形持有关系。例如:

self->block->self
self->view->model->block->self

对于解决循环引用也是非常高频的操作,有以下四种方法:

  1. weak-strong-dance
  2. __block修饰对象, 在内部手动将block变量置为nil
  3. 通过将需要使用的对象放到block的参数中传递到block内部
  4. 使用NSProxy传递消息
    前三种都是非常常规的处理手段,这里就不做介绍了。重点介绍下第4种,怎么用NSProxy

1.4 自定义NSProxy

NSProxy

从上面类的定义中可以看出,它并没有继承NSObject,只是实现了<NSObject>的部分协议,那它有什么用处呢?可以实现伪多继承

我们继承这个类来实现一个自定义的类XYProxy

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface XYProxy : NSProxy

- (id)transformObjc:(NSObject *)objc;

+ (instancetype)proxyWithObjc:(NSObject *)objc;

@end

NS_ASSUME_NONNULL_END

#import "XYProxy.h"


@interface XYProxy ()

@property (nonatomic, weak, readonly) NSObject *objc;

@end

@implementation XYProxy

- (id)transformObjc:(NSObject *)objc {
    _objc = objc;
    return self;
}

+ (instancetype)proxyWithObjc:(NSObject *)objc {
    return [[self alloc] transformObjc:objc];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    SEL sel = [invocation selector];
    if ([self.objc respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.objc];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSMethodSignature *signature = [super methodSignatureForSelector:sel];
    if (self.objc) {
        signature = [self.objc methodSignatureForSelector:sel];
    }
    return signature;
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [self.objc respondsToSelector:aSelector];
}

@end

对消息的查找和转发协议比较熟悉的话,对这里的API应该是非常了解的。这里实现的是转发的最后一步——慢速转发流程。为什么是最后一步,不是动态决议也不是快速转发流程呢?NSProxy的作用就是通过消息的转发来实现某种功能,它本身的作用类似于代理,去帮助别人实现某种功能(调某个方法),同时需要转发消息的调用者和方法,只有慢速转发流程才满足。

1.5 NSProxy总结

可以使用 NSProxy 实现伪多继承

NSProxy 的使用场景

  1. 实现多继承的功能. 通过自定义的Proxy类代理target, 进行消息的转发
  2. 解决NSTimer/CADisplayLink创建时对target的强引用问题, 可以参照YYKit中的YYWeakProxy
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[XYProxy proxyWithObjc:self] selector:@selector(countTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

二、Block本质

上面介绍NSProxy只是为解决循环引用提供一个新的思路,本文的核心仍然是Block。上面借助LLDB查看了block的类型,但并不知道block在底层是怎么运行的,分析block,同样可以通过汇编、源码、CPP分析,这里我们先objc的block转成cpp文件,查看block的结构。

void clangBlockFunc() {
    void(^srBlock)(int) = ^(int blockNum){
        printf("clangBlockFunc : %d", blockNum);
    };
    
    printf("------");
    
    srBlock(110);
}
struct __clangBlockFunc_block_impl_0 {
  struct __block_impl impl;
  struct __clangBlockFunc_block_desc_0* Desc;
  __clangBlockFunc_block_impl_0(void *fp, struct __clangBlockFunc_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
 
static void __clangBlockFunc_block_func_0(struct __clangBlockFunc_block_impl_0 *__cself, int blockNum) {
    printf("clangBlockFunc : %d", blockNum);
}

static struct __clangBlockFunc_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __clangBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __clangBlockFunc_block_impl_0)};
void clangBlockFunc() {
    void(*srBlock)(int) = ((void (*)(int))&__clangBlockFunc_block_impl_0((void *)__clangBlockFunc_block_func_0, &__clangBlockFunc_block_desc_0_DATA));

    printf("------");

    ((void (*)(__block_impl *, int))((__block_impl *)srBlock)->FuncPtr)((__block_impl *)srBlock, 110);
}

其他的内容已经省略,将我们写的代码转成的cpp代码拿出来分析。注意这里使用的c文件转的cpp文件,对比objc文件,只是少了个类名的前缀,其他结构都是完全一致。后面会贴上objc转成的cpp文件的代码,这里可以先进行分析。

2.1 block结构体

struct __clangBlockFunc_block_impl_0 {
  struct __block_impl impl;
  struct __clangBlockFunc_block_desc_0* Desc;
  __clangBlockFunc_block_impl_0(void *fp, struct __clangBlockFunc_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  1. block名称的生成:__函数名__block_impl_index
  2. 第一个属性:__block_impl,从源码中找到__block_impl类型的定义
#ifndef BLOCK_IMPL
#define BLOCK_IMPL
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  1. 第二个属性:__clangBlockFunc_block_desc_0
    同样可以从源码中找到相应的定义,我们知道NSObject都有一个- (NSString *)description方法,同样的block也有自己的desc
static struct __clangBlockFunc_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __clangBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __clangBlockFunc_block_impl_0)};
  1. 构造函数:__clangBlockFunc_block_impl_0
__clangBlockFunc_block_impl_0(void *fp, struct __clangBlockFunc_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

最后可以知道block的构造函数只接受两个参数(void *fp, struct __clangBlockFunc_block_desc_0 *desc)

2.2 block的实现

代码中的block定义为:

void(^srBlock)(int) = ^(int blockNum){
    printf("clangBlockFunc : %d", blockNum);
};

在源码中表现为:

static void __clangBlockFunc_block_func_0(struct __clangBlockFunc_block_impl_0 *__cself, int blockNum) {

        printf("clangBlockFunc : %d", blockNum);
    }

对比上面两个命名,第一个是block结构体的声明,用的是...impl...,后面的事block的实现,用的是...func...,所以我们称block的本质是函数指针确实说得过去。

2.3 block的声明

void clangBlockFunc() {
    void(^srBlock)(int) = ^(int blockNum){
        printf("clangBlockFunc : %d", blockNum);
    };
    
    printf("------");
    
    srBlock(110);
}
void clangBlockFunc() {
    void(*srBlock)(int) = ((void (*)(int))&__clangBlockFunc_block_impl_0((void *)__clangBlockFunc_block_func_0, &__clangBlockFunc_block_desc_0_DATA));

    printf("------");

    ((void (*)(__block_impl *, int))((__block_impl *)srBlock)->FuncPtr)((__block_impl *)srBlock, 110);
}
(void (*)(int))
&__clangBlockFunc_block_impl_0(
  (void *)__clangBlockFunc_block_func_0, 
  &__clangBlockFunc_block_desc_0_DATA)
);

2.4 block的调用

(
  (void (*)(__block_impl *, int))
  ((__block_impl *)srBlock)->FuncPtr
)
((__block_impl *)srBlock, 110);

到这里,我们对一个基本block结构体、block类型的定义、block的调用有了一个非常详细的了解。但我们仍然不知道为什么block中使用self会造成循环引用,后面会进行详细的讲解。

2.5 block在objc中的编译

@implementation XYBlockObjc
- (void)clangBlockFunc {
    void(^srBlock)(int) = ^(int blockNum){
        printf("clangBlockFunc : %d", blockNum);
    };
    
    printf("------");
    
    srBlock(110);
}
@end
struct __XYBlockObjc__clangBlockFunc_block_impl_0 {
   struct __block_impl impl;
   struct __XYBlockObjc__clangBlockFunc_block_desc_0* Desc;
   __XYBlockObjc__clangBlockFunc_block_impl_0(void *fp, struct __XYBlockObjc__clangBlockFunc_block_desc_0 *desc, int flags=0) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
   }
 };
 static void __XYBlockObjc__clangBlockFunc_block_func_0(struct __XYBlockObjc__clangBlockFunc_block_impl_0 *__cself, int blockNum) {

         printf("clangBlockFunc : %d", blockNum);
     }

 static struct __XYBlockObjc__clangBlockFunc_block_desc_0 {
   size_t reserved;
   size_t Block_size;
 } __XYBlockObjc__clangBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __XYBlockObjc__clangBlockFunc_block_impl_0)};

 static void _I_XYBlockObjc_clangBlockFunc(XYBlockObjc * self, SEL _cmd) {
     void(*srBlock)(int) = ((void (*)(int))&__XYBlockObjc__clangBlockFunc_block_impl_0((void *)__XYBlockObjc__clangBlockFunc_block_func_0, &__XYBlockObjc__clangBlockFunc_block_desc_0_DATA));

     printf("------");

     ((void (*)(__block_impl *, int))((__block_impl *)srBlock)->FuncPtr)((__block_impl *)srBlock, 110);
 }

我们对比c转成的cpp文件来看,除了在名称前面添加了类名,其他的基本上没有什么差别,但是在编译出来的源文件中会有非常多的objc的类型。如果是单独分析某个关键字的特性,我们完全可以借用c文件,而不用去被其他类型干扰。

三、Block的值捕获

在block的使用中,我们遇到的最多的场景就是对外部变量的捕获,而遇到最多的问题就是block对self的循环引用,上面的分析并不能让我们对这些场景有个深入的了解,我们需要深入去研究block对外部变量的捕获机制。先来看一段代码:

typedef int(^XYTestBlock)(int code);

@interface XYBlockValueCapture ()
@property (nonatomic, assign) int globalNum;
@property (nonatomic, strong) NSObject *globalObj;
@property (nonatomic, copy) NSString *globalStr;
@end

- (void)testBlockFunc {
    
    int localNum = 10;
    NSObject *localObj = [NSObject new];
    NSString *localStr = @"local str";
    
    XYTestBlock testBlock = ^(int code) {
        NSLog(@"local variable >> num : %d >> obj : %@ >> str : %@", localNum, localObj, localStr);
        
        NSLog(@"global variable >> num : %d >> obj : %@ >> str : %@", self.globalNum, self.globalObj, self.globalStr);
        return 0;
    };
    
    NSLog(@"block return : %d", testBlock(110));
}
struct __XYBlockValueCapture__testBlockFunc_block_impl_0 {
  struct __block_impl impl;
  struct __XYBlockValueCapture__testBlockFunc_block_desc_0* Desc;
  int localNum;
  NSObject *localObj;
  NSString *localStr;
  XYBlockValueCapture *self;
  __XYBlockValueCapture__testBlockFunc_block_impl_0(void *fp, struct __XYBlockValueCapture__testBlockFunc_block_desc_0 *desc, int _localNum, NSObject *_localObj, NSString *_localStr, XYBlockValueCapture *_self, int flags=0) : localNum(_localNum), localObj(_localObj), localStr(_localStr), self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __XYBlockValueCapture__testBlockFunc_block_func_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0 *__cself, int code) {
  int localNum = __cself->localNum; // bound by copy
  NSObject *localObj = __cself->localObj; // bound by copy
  NSString *localStr = __cself->localStr; // bound by copy
  XYBlockValueCapture *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_1, localNum, localObj, localStr);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_2, ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("globalNum")), ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("globalObj")), ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("globalStr")));
        return 0;
    }
static void __XYBlockValueCapture__testBlockFunc_block_copy_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*dst, struct __XYBlockValueCapture__testBlockFunc_block_impl_0*src) {_Block_object_assign((void*)&dst->localObj, (void*)src->localObj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->localStr, (void*)src->localStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __XYBlockValueCapture__testBlockFunc_block_dispose_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*src) {_Block_object_dispose((void*)src->localObj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->localStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __XYBlockValueCapture__testBlockFunc_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*, struct __XYBlockValueCapture__testBlockFunc_block_impl_0*);
  void (*dispose)(struct __XYBlockValueCapture__testBlockFunc_block_impl_0*);
} __XYBlockValueCapture__testBlockFunc_block_desc_0_DATA = { 0, sizeof(struct __XYBlockValueCapture__testBlockFunc_block_impl_0), __XYBlockValueCapture__testBlockFunc_block_copy_0, __XYBlockValueCapture__testBlockFunc_block_dispose_0};

static void _I_XYBlockValueCapture_testBlockFunc(XYBlockValueCapture * self, SEL _cmd) {

    int localNum = 10;
    NSObject *localObj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
    NSString *localStr = (NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_0;

    XYTestBlock testBlock = ((int (*)(int))&__XYBlockValueCapture__testBlockFunc_block_impl_0((void *)__XYBlockValueCapture__testBlockFunc_block_func_0, &__XYBlockValueCapture__testBlockFunc_block_desc_0_DATA, localNum, localObj, localStr, self, 570425344));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_30a962_mi_3, ((int (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 110));
}

对于上面的代码,我们同样是通过block的声明block的实现block的调用三个步骤来分析。关于block的基础类型,我们上面做过详细分析,这里就不再分析,这里只分析与变量相关的代码。

3.1 局部变量捕获

block的声明

上面的例子中,我们定义了三个局部变量,它们是怎么被block使用的呢?

int localNum = 10;
NSObject *localObj = [NSObject new];
NSString *localStr = @"local str";

同样的,clang将block结构体定义为:__XYBlockValueCapture__testBlockFunc_block_impl_0,在这个结构体中,除了我们上面讲到的两个属性和一个构造方法之外,还增加了四个属性,其中有三个就是我们定义的三个变量,还有一个是selfself的内容我们放到后面讲,本节只关注这三个局部变量即可。

  int localNum;
  NSObject *localObj;
  NSString *localStr;
  XYBlockValueCapture *self;

构造函数的变更:

__XYBlockValueCapture__testBlockFunc_block_impl_0(
void *fp, struct __XYBlockValueCapture__testBlockFunc_block_desc_0 *desc, 
int _localNum, NSObject *_localObj, NSString *_localStr, XYBlockValueCapture *_self, 
int flags=0) 
: localNum(_localNum), localObj(_localObj), localStr(_localStr), self(_self)

block的实现

block的声明里面介绍了block会持有所有捕获的局部变量,而且是通过copy的方式绑定。那么在使用的时候就比较简单了,第一个参数是结构体本身,第二个参数是block接受的参数列表,要使用block捕获的局部变量,直接从block结构体中取出来即可。

static int __XYBlockValueCapture__testBlockFunc_block_func_0(struct __XYBlockValueCapture__testBlockFunc_block_impl_0 *__cself, int code) {
  int localNum = __cself->localNum; // bound by copy
  NSObject *localObj = __cself->localObj; // bound by copy
  NSString *localStr = __cself->localStr; // bound by copy
  XYBlockValueCapture *self = __cself->self; // bound by copy

bound by copy:通过copy的方式绑定

block的调用

XYTestBlock testBlock = 
((int (*)(int))
&__XYBlockValueCapture__testBlockFunc_block_impl_0(
  (void *)__XYBlockValueCapture__testBlockFunc_block_func_0, 
  &__XYBlockValueCapture__testBlockFunc_block_desc_0_DATA, 
  localNum, localObj, localStr, self, 
  570425344)
);
(
  (int (*)(__block_impl *, int))
  ((__block_impl *)testBlock)->FuncPtr
)
((__block_impl *)testBlock, 110)

3.2 全局变量的捕获

全局变量可不可以修改呢?其实上面已经给出了答案,答案就在这句话里——对于NSObject *,我们传递的是指针,所以我们可以通过指针修改对象内部的属性。所有的全局变量都是指针self的属性,我们都可以通过self来修改对象内部的属性。

也许你会有这么一个疑问,调用self.globalNum=130的方式是通过调用了属性的setter方法,在block中调用方法本身就是没什么问题的。那我们尝试直接对成员变量赋值试试:

image.png

Xcode告诉我们,self将会被隐式持有,建议我们显示调用self,再来看看这行代码在cpp文件中是什么表现形式

(*(int *)((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)) = 120;
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setGlobalNum:"), 130);

第二行完全证明了我们上述的猜测,self.globalNum=130就是调用了属性的setter方法进行的赋值,这行代码也比较简单读懂,就是objc_msgSend消息发送的流程。对oc消息的查找和转发不熟悉的话,可以看这几篇文章:01--方法本质02--objc_msgSend的使用
第一行的话,其实需要了解对象的原理,关于对象的成员变量的存储原理,我们可以看下cpp文件中的相关代码:

extern "C" unsigned long int OBJC_IVAR_$_XYBlockValueCapture$_globalNum __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct XYBlockValueCapture, _globalNum);
extern "C" unsigned long int OBJC_IVAR_$_XYBlockValueCapture$_globalObj __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct XYBlockValueCapture, _globalObj);
extern "C" unsigned long int OBJC_IVAR_$_XYBlockValueCapture$_globalStr __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct XYBlockValueCapture, _globalStr);

static struct /*_ivar_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[3];
} _OBJC_$_INSTANCE_VARIABLES_XYBlockValueCapture __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    3,
    {{(unsigned long int *)&OBJC_IVAR_$_XYBlockValueCapture$_globalNum, "_globalNum", "i", 2, 4},
     {(unsigned long int *)&OBJC_IVAR_$_XYBlockValueCapture$_globalObj, "_globalObj", "@\"NSObject\"", 3, 8},
     {(unsigned long int *)&OBJC_IVAR_$_XYBlockValueCapture$_globalStr, "_globalStr", "@\"NSString\"", 3, 8}}
};

从上面的定义中可以看出OBJC_IVAR_$_XYBlockValueCapture$_globalNum存储的是成员变量_globalNum相对于对象首地址的偏移量,所以有了以下的读取步骤:

  1. 寻找_globalNum指针,((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)
  2. 因为它是 int *的指针类型,所以需要强转一次,(int *)((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)
  3. 拿到_globalNum指针之后,通过指针给变量重新赋值,(*(int *)((char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum)) = 120;。我们可以将这行代码改写成以下形式
char * globalNumP0 = (char *)self + OBJC_IVAR_$_XYBlockValueCapture$_globalNum;
int * globalNumP1  = (int *) globalNumP0
*(globalNumP1) = 120;

到这里,我们就完全读完block对全局变量使用的情况。

3.3 self的捕获

根据上面分析的一些逻辑来看,block是将self通过值捕获的方式进行了引用,block内部会持有self。如果说这个block被self持有,那么很明显会造成循环引用。最常用的手段就是通过 weakself 来解决循环引用。

XYBlockValueCapture *self = __cself->self; // bound by copy

3.4 __block的捕获

在很多场景中,我们是需要在block中修改变量的,我们的常规做法是使用__block来修饰这个变量。为什么这么做就可以了呢?上源码,看原理😄

- (void)testBlockFunc2 {
    
    __block int localNum2 = 10;
    __block NSObject *localObj2 = [NSObject new];
    
    XYTestBlock testBlock2 = ^(int code) {
        localNum2 = 140;
        localObj2 = [NSObject new];
        NSLog(@"local variable 2 >> num : %d >> obj : %@", localNum2, localObj2);
        return 0;
    };
    
    NSLog(@"block return : %d", testBlock2(150));
}
typedef int(*XYTestBlock)(int code);
// @implementation XYBlockValueCapture


struct __Block_byref_localNum2_0 {
  void *__isa;
__Block_byref_localNum2_0 *__forwarding;
 int __flags;
 int __size;
 int localNum2;
};
struct __Block_byref_localObj2_1 {
  void *__isa;
__Block_byref_localObj2_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *localObj2;
};

struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 {
  struct __block_impl impl;
  struct __XYBlockValueCapture__testBlockFunc2_block_desc_0* Desc;
  __Block_byref_localNum2_0 *localNum2; // by ref
  __Block_byref_localObj2_1 *localObj2; // by ref
  __XYBlockValueCapture__testBlockFunc2_block_impl_0(void *fp, struct __XYBlockValueCapture__testBlockFunc2_block_desc_0 *desc, __Block_byref_localNum2_0 *_localNum2, __Block_byref_localObj2_1 *_localObj2, int flags=0) : localNum2(_localNum2->__forwarding), localObj2(_localObj2->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __XYBlockValueCapture__testBlockFunc2_block_func_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *__cself, int code) {
  __Block_byref_localNum2_0 *localNum2 = __cself->localNum2; // bound by ref
  __Block_byref_localObj2_1 *localObj2 = __cself->localObj2; // bound by ref

        (localNum2->__forwarding->localNum2) = 140;
        (localObj2->__forwarding->localObj2) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_0, (localNum2->__forwarding->localNum2), (localObj2->__forwarding->localObj2));
        return 0;
    }
static void __XYBlockValueCapture__testBlockFunc2_block_copy_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*dst, struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {_Block_object_assign((void*)&dst->localNum2, (void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->localObj2, (void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __XYBlockValueCapture__testBlockFunc2_block_dispose_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {_Block_object_dispose((void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __XYBlockValueCapture__testBlockFunc2_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*, struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
  void (*dispose)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
} __XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA = { 0, sizeof(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0), __XYBlockValueCapture__testBlockFunc2_block_copy_0, __XYBlockValueCapture__testBlockFunc2_block_dispose_0};

static void _I_XYBlockValueCapture_testBlockFunc2(XYBlockValueCapture * self, SEL _cmd) {

    __attribute__((__blocks__(byref))) __Block_byref_localNum2_0 localNum2 = {(void*)0,(__Block_byref_localNum2_0 *)&localNum2, 0, sizeof(__Block_byref_localNum2_0), 10};
    __attribute__((__blocks__(byref))) __Block_byref_localObj2_1 localObj2 = {(void*)0,(__Block_byref_localObj2_1 *)&localObj2, 33554432, sizeof(__Block_byref_localObj2_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};

    XYTestBlock testBlock2 = ((int (*)(int))&__XYBlockValueCapture__testBlockFunc2_block_impl_0((void *)__XYBlockValueCapture__testBlockFunc2_block_func_0, &__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA, (__Block_byref_localNum2_0 *)&localNum2, (__Block_byref_localObj2_1 *)&localObj2, 570425344));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_1, ((int (*)(__block_impl *, int))((__block_impl *)testBlock2)->FuncPtr)((__block_impl *)testBlock2, 150));
}
// @end

block的声明

相同部分就不再累述,直接看新增、变化的部分。

我们在外面定义的是一个 int 和一个 NSObject * 的两个变量

__block int localNum2 = 10;
__block NSObject *localObj2 = [NSObject new];

结果在源码中被转成了__Block_byref_localNum2_0 *__Block_byref_localObj2_1 *这样的两个类型。

__Block_byref_localNum2_0 *localNum2; // by ref
__Block_byref_localObj2_1 *localObj2; // by ref

下面再来看看这两个类型的源码。

先看相同之处

  1. 它们都被包装成了一个新的对象的属性;
  2. 它们在这个新的对象中的属性名,和外面定义的变量名相同;
  3. 新的对象中都包含一个只想自己的指针__forwarding。(TIP:为什么不直接用self,还要额外搞一个指向?);
  4. 都有__flags__size属性,这两个属性不在分析范畴内;
struct __Block_byref_localNum2_0 {
  void *__isa;
__Block_byref_localNum2_0 *__forwarding;
 int __flags;
 int __size;
 int localNum2;
};
struct __Block_byref_localObj2_1 {
  void *__isa;
__Block_byref_localObj2_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *localObj2;
};

再看不同之处:包含NSObject *的对象多了两个方法

  1. __Block_byref_id_object_copy,对象拷贝
  2. __Block_byref_id_object_dispose,对象释放

从源码中可以看出这两个方法最终会调用到系统的两个方法。关于这两个方法,会在后面两节中讲解,本节重点是 __block

  1. _Block_object_assign
  2. _Block_object_dispose
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

block的实现

这里使用的对象的__forwarding中的值,forward是先前的,也就是我们捕获时候的值,系统为什么要区分不同时机的值呢?那它本身的值留着又有什么用呢?这些疑问都留到后面来解答。回到最初的问题,为什么使用__block就可以修改变量的值了。我想答案不明而喻。

block对使用__block修饰的变量做了一层包装,构建出一个新的对象,而block中在使用这个变量的时候,其实是作为新的对象的属性在使用,回到我们上面关于全局变量的值捕获的结论,block可以通过指针修改对象内部的属性,这就是__block的玄机。

static int __XYBlockValueCapture__testBlockFunc2_block_func_0(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *__cself, int code) {
  __Block_byref_localNum2_0 *localNum2 = __cself->localNum2; // bound by ref
  __Block_byref_localObj2_1 *localObj2 = __cself->localObj2; // bound by ref

        (localNum2->__forwarding->localNum2) = 140;
        (localObj2->__forwarding->localObj2) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_0, (localNum2->__forwarding->localNum2), (localObj2->__forwarding->localObj2));
        return 0;
    }

block的调用

__block的玄机是搞懂了,但是在阅读调用处的代码,会发现系统又给我们很多新的东西

static void _I_XYBlockValueCapture_testBlockFunc2(XYBlockValueCapture * self, SEL _cmd) {

    __attribute__((__blocks__(byref))) __Block_byref_localNum2_0 localNum2 = {(void*)0,(__Block_byref_localNum2_0 *)&localNum2, 0, sizeof(__Block_byref_localNum2_0), 10};
    __attribute__((__blocks__(byref))) __Block_byref_localObj2_1 localObj2 = {(void*)0,(__Block_byref_localObj2_1 *)&localObj2, 33554432, sizeof(__Block_byref_localObj2_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};

    XYTestBlock testBlock2 = ((int (*)(int))&__XYBlockValueCapture__testBlockFunc2_block_impl_0((void *)__XYBlockValueCapture__testBlockFunc2_block_func_0, &__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA, (__Block_byref_localNum2_0 *)&localNum2, (__Block_byref_localObj2_1 *)&localObj2, 570425344));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_rs_kpv5j2rn5s7b376tm5l_bl_h0000gn_T_XYBlockValueCapture_1ef520_mi_1, ((int (*)(__block_impl *, int))((__block_impl *)testBlock2)->FuncPtr)((__block_impl *)testBlock2, 150));
}

block狠起来连自己都不放过,它除了拷贝了变量,它还把自己也拷贝了一份,上源码😁

static struct __XYBlockValueCapture__testBlockFunc2_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*, struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
  void (*dispose)(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*);
} __XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA = { 
    0, 
    sizeof(struct __XYBlockValueCapture__testBlockFunc2_block_impl_0), 
    __XYBlockValueCapture__testBlockFunc2_block_copy_0, 
    __XYBlockValueCapture__testBlockFunc2_block_dispose_0
};

static void __XYBlockValueCapture__testBlockFunc2_block_copy_0(
    struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *dst, 
    struct __XYBlockValueCapture__testBlockFunc2_block_impl_0 *src) {
        _Block_object_assign((void*)&dst->localNum2, (void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_assign((void*)&dst->localObj2, (void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __XYBlockValueCapture__testBlockFunc2_block_dispose_0(
    struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {
        _Block_object_dispose((void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_dispose((void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);
}

3.5 block值捕获总结

  1. block中不能修改变量本身;
  2. block中可以通过指针修改对象内部属性;

四、block的拷贝流程

在上面的分析中发现了block内部其实还有新的流程,而对于这个流程的分析可以通过汇编和源码的方式。关于汇编的探索,这里就省了,会在逆向的文章中重点介绍汇编相关的知识,这里就直接来分析源码。libclosure-79下载地址,下载最新的源码即可

libclosure-79

4.1 Block_private.h

block的本质

block的本质

从头文件的定义中可以看出block的本质是一个结构体,这里对block结构体的定义的类型,与我们上面编译出来的cpp文件中的结构体是完全一致的。这里的四个基本属性上面都有详细的说明。

// XY注释:block 结构体
struct Block_layout {
    // 指向表明block的类型
    void * __ptrauth_objc_isa_pointer isa;
    // 标识符,类似isa的位域,按bit位表示一些block的附加信息
    volatile int32_t flags; // contains ref count
    // 保留字段,用于存储block内部变量信息
    int32_t reserved;
    // 函数指针,指向具体的block函数的调用地址
    BlockInvokeFunction invoke;
    // block的描述信息
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

block->flags 的本质

在上面的分析中,可以知道每个block都有一个flags属性。对于标识类属性,苹果惯用isa位域这一套,按位标识不同的含义,下面对每一位标识的含义都有清晰的注释,可以查看。

// Values for Block_layout->flags to describe block objects
// XY注释:flags 标识
enum {
    // 释放标记,用于与 BLOCK_BYREF_NEEDS_FREE 按位与,一同传入flags,表示该block可以释放了
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // 存储引用计数的值
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
#endif
    
    BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
    // 低16位是否有效的标识,程序根据它来决定是否增加或者减少引用计数位的值
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    // 标识是否有拷贝和释放函数,决定 Block_descriptor_2
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    // 表示是否有block C++析构函数
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    // 标识是否有垃圾回收,OSX
    BLOCK_IS_GC =             (1 << 27), // runtime
    // 标识是否是全局block
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    // 标识是否有签名,用于runtime时动态调用,与BLOCK_HAS_SIGNATURE相对
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    // 标识是否有签名
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    // 表示是否使用扩展,决定Block_descriptor_3
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

block->Block_descriptor 本质

上面对cpp文件的分析中,已经出现了两种类型的block,而不同类型的block的描述信息也是不一样的。例如:对__block修饰的变量的捕获,就会构建一个新的对象,由这个对象来修改这个变量。同时,还会添加copy、dispose函数,用来对block的拷贝和释放。这三种description如何取值,后面的流程中会给出。

下面的三种 description 对应的是block的三种值捕获的描述

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    // 保留信息
    uintptr_t reserved;
    // block大小
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    // 拷贝block函数
    BlockCopyFunction copy;
    // 释放block函数
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    // 签名 依赖 BLOCK_HAS_SIGNATURE 字段
    const char *signature;
    // 布局 依赖 BLOCK_HAS_EXTENDED_LAYOUT 字段
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

Block_byref的本质

struct Block_byref {
    void * __ptrauth_objc_isa_pointer isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

Block_byref->flags 本质

// Values for Block_byref->flags to describe __block variables
// XY注释:flags 标识
enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    // block引用位域面罩
    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    // block引用扩展
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    // block引用非对象类型
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    // block引用对象类型
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    // block引用weak类型
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    // block引用类型未定义,将来需要扩展字段的开始
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler
    // block引用是否垃圾回收
    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime
    // block引用是否有copy和dispose函数
    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    // block引用释放标识
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
};

到这里,基本上对block中会出现的所有类型都已经分析完了。这里只是静态的过了一遍定义,并不知道如何使用,block是如何拷贝的,接下来需要对cpp文件的分析来看block的拷贝过程。

4.2 汇编调试

  1. 上代码


    测试拷贝
  2. 开汇编


    汇编调试
  3. 现原形


    block本质

通过汇编的方式,我们找到了跟block有关的信息,然后开启断点调试。需要链接真机调试,才可以看到调试信息,有些信息在x86架构里面是没有的。

image.png

从上面的对象信息中可以看到block结构体中的信息都在这里:

signature: "v8@?0"
invoke   : 0x102391d30 
copy     : 0x102391d90 
dispose  : 0x102391dc8 

我们知道第二位是flags标识,可以打印出来看下效果


image.png

查看是否有:Block_descriptor_2p/t flags & 1<<25
查看是否有:Block_descriptor_3p/t flags & 1<<30
从下面的结果中可以看出,都是存在的

image.png

查看签名信息:po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]

签名信息
//无返回值
return value: -------- -------- -------- --------
    type encoding (v) 'v'
    flags {}
    modifiers {}
    frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
    memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
    //encoding = (@),类型是 @?
    type encoding (@) '@?'
    //@是isObject ,?是isBlock,代表 isBlockObject
    flags {isObject, isBlock}
    modifiers {}
    frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
    //所在偏移位置是8字节
    memory {offset = 0, size = 8}

当我们在其他地方输出block时,发现这个时候的block已经变成了堆block:__NSMallocBlock__

image.png

4.3 block的拷贝流程

上面的那么多内容都是值望梅解渴,并没有真正的深入到block的内部,上源码😁

_Block_object_assign

这个方法是block以及block引用对象拷贝的入口方法。

// XY注释:block以及block引用拷贝的入口点
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
        // 局部变量的值捕获
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;
        // block变量的值捕获
      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;
        // __block包装的变量的值捕获
        // __weak在ARC下,由系统直接处理,后面提到的__weak都是针对MRC下的场景
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
      default:
        break;
    }
}

我们可以回到前面的源码中出现的几种情况:

  1. 对于整数类型:block是直接拷贝了整数的值;
  2. 对于对象类型:调用了_Block_object_assign进行拷贝,而且注释中显式标明了类型3/*BLOCK_FIELD_IS_OBJECT*/
static void __XYBlockValueCapture__testBlockFunc_block_copy_0(
    struct __XYBlockValueCapture__testBlockFunc_block_impl_0*dst, 
    struct __XYBlockValueCapture__testBlockFunc_block_impl_0*src) {
        _Block_object_assign((void*)&dst->localObj, (void*)src->localObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
        _Block_object_assign((void*)&dst->localStr, (void*)src->localStr, 3/*BLOCK_FIELD_IS_OBJECT*/);
        _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
  1. 对于block类型:标明了类型7/*BLOCK_FIELD_IS_BLOCK*/
static void __XYBlockValueCapture__testBlockFunc3_block_copy_1(
    struct __XYBlockValueCapture__testBlockFunc3_block_impl_1*dst,
    struct __XYBlockValueCapture__testBlockFunc3_block_impl_1*src) {
        _Block_object_assign((void*)&dst->testBlock1, (void*)src->testBlock1, 7/*BLOCK_FIELD_IS_BLOCK*/);
    }
  1. 对于__block修饰的类型:标明了类型8/*BLOCK_FIELD_IS_BYREF*/
static void __XYBlockValueCapture__testBlockFunc2_block_copy_0(
    struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*dst, 
    struct __XYBlockValueCapture__testBlockFunc2_block_impl_0*src) {
        _Block_object_assign((void*)&dst->localNum2, (void*)src->localNum2, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_assign((void*)&dst->localObj2, (void*)src->localObj2, 8/*BLOCK_FIELD_IS_BYREF*/);
    }

再看看下这几种类型的定义说明

// Values for _Block_object_assign() and _Block_object_dispose() parameters
// XY注释:block捕获的值的类型
enum {
    // see function implementation for a more complete description of these fields and combinations
    // block捕获的值是对象
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    // block捕获的值是block
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    // block捕获的值是__block修饰的变量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    // block捕获的值是weak对象
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

_Block_copy 源码

  1. 如果是全局block,不拷贝,直接返回;
  2. 只有栈block,才会被拷贝到堆block里面;
// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// XY注释:拷贝block
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    // 如果是正准备释放的block,不进行拷贝
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 如果是全局block,则直接返回
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        // 对栈block进行拷贝
        size_t size = Block_size(aBlock);
        // 在堆上开辟一个新的内存
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if (!result) return NULL;
        // 按位拷贝
        memmove(result, aBlock, size); // bitcopy first

        // reset refcount
        // 重设block的引用计数
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 引用计数设置为1
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // 设置block类型为堆block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

_Block_byref_copy 源码

对引用对象的拷贝,只有变量被__block修饰的时候,才会走到这个流程里面。当引用对象被拷贝时,它的引用计数会被设置为2

// XY注释:拷贝引用对象,避免在解除了forwarding指针的引用之后,仍然会出现访问他的问题
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        // 将block引用对象的引用计数设置为2,一个是调用者持有,一个是栈指针持有
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        // 将copy给自己,以供调用者使用
        copy->forwarding = copy; // patch heap copy to point to itself
        // 将copy给栈指针,栈指针原先的指向会被释放
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

拷贝何时发生

__attribute__((__blocks__(byref))) __Block_byref_localObj2_1 localObj2 = {
    (void*)0,(__Block_byref_localObj2_1 *)&localObj2, 33554432, sizeof(__Block_byref_localObj2_1), 
    __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, 
    ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))
};

XYTestBlock testBlock2 = 
((int (*)(int))&__XYBlockValueCapture__testBlockFunc2_block_impl_0(
    (void *)__XYBlockValueCapture__testBlockFunc2_block_func_0, &__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA, 
    (__Block_byref_localNum2_0 *)&localNum2, (__Block_byref_localObj2_1 *)&localObj2, 570425344)
);

回到上面对__block修饰的源码中,我们先不考虑拷贝是怎么发生的,我们知道了被捕获的对象会被包装成一个新的对象,新的对象又会作为这个block的属性存在,所以我们按照这个思路来分析。

  1. NSObject对象拷贝进新的引用对象 __Block_byref_localObj2_1,函数:__Block_byref_id_object_copy_131
  2. localObj2引用对象拷贝进block __XYBlockValueCapture__testBlockFunc2_block_impl_0,函数:__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA
  3. 栈block拷贝进堆,testBlock2 = __XYBlockValueCapture__testBlockFunc2_block_impl_0,函数:__XYBlockValueCapture__testBlockFunc2_block_desc_0_DATA

当拷贝完成之后,我们来分析下各自的持有状态。

当这行代码执行完成之后,栈block(=右边的block)没有被持有,将会立即释放,所以最终的持有关系链,只剩下堆block->block_byref->object,也就是我们测试代码中的testBlock2

4.4 block的释放流程

那剩下的堆block何时释放呢?首先,block也是一个对象,既然是对象,那么也会遵从ARC协议,当它的引用计数为0的时候释放。

_Block_object_dispose 源码

// XY注释:block释放的流程
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

这个流程仅仅是释放block_byref对象,block的本身的释放时机是有ARC管理,也就是引用计数为0的时候释放。

释放block的代码:_Block_release

void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;

    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

释放block_byref的代码:_Block_byref_release

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}

释放流程就是相对拷贝流程反过来看就行了,比较简单。

五、FAQ

5.1 为什么block内部能不修改外部变量

block的函数性
前面提到了很多次,block曾一度被认为是函数指针。既然是函数指针,那block内部可以被理解为函数的作用域。在源码中可以看出来,外部变量是通过函数形参的方式传递进block内部的。众所周知,形参的改变是不会影响到实参的变化。从语法上来看,我们可以在block内部来修改这个值,但实际上我们并没有真正的修改这个值,所以为了避免理解上的歧义,Apple不允许在block内部修改外部变量。

5.2 怎么理解栈block

  1. 函数是要在入栈被调用的,而在函数里面的变量也在栈上;
  2. =右边的block,是在函数里实现的,所以右边的block是栈block;
  3. 栈空间是宝贵的,在函数出栈之后,这里分配的空间都将被回收;
  4. 所以一般情况下,需要使用block的时候,都会对block进行一次拷贝,将其拷贝到堆上,并将=左边的变量指向堆上block;
  5. 在ARC机制下,在函数结尾处,会对这些临时变量发送release消息。
  6. 当收到release消息的=左边的对象的引用计数变为0,开始释放堆block。

=右边,栈block在函数出栈之后被释放;
=左边,引用计数变为0之后被释放;

5.3 为什么设计block

block的值捕获
说了很多次block的函数性,那有了函数指针,为什么还需要block呢?
函数是一个代码块,有参数列表,内部可以定义局部变量。函数在调用的时候会入栈,调用结束后会出栈,所有的局部变量都将会被释放,而形参只能是从外面传递进来的,实参的管理完全依赖外部,在多线程的场景下,极有可能出现调用函数时实参并不是自己所期待的值、或者已经被释放。
block会捕获当前的环境变量,构建新的block类型将捕获的变量进行包装,然后将block对象拷贝到堆上。以确保block在调用的时候,环境变量仍是当初捕获值,不会在因为环境变量在外部的修改而影响block内部的实现逻辑,以确保延时调用时的环境没有变化。

5.4 为什么设计__forwarding

forwarding单词表示的是转发的含义。结合上面对block分析的理解,当发生copy的时候,栈block会被拷贝到堆上,这是会在堆上拷贝一个新的block对象,而block对捕获的值(不考虑__block)也是copy,这时,被捕获的值实际上存在两个副本。假如没有__forwarding这个指针,当栈block执行的时候,修改的栈上捕获的值,当出栈时,所有修改的信息都将会被丢弃,而堆block上的值没有任何变化,这种情况,会造成一些难以预期的错误,所以设计出__forwarding指针。让栈block的__forwarding指针也指向堆block,这样可以保证这次修改和以后将要修改的值都是同一份。

上一篇 下一篇

猜你喜欢

热点阅读