OC底层原理之Block的底层原理探索

前言

作为一个iOS开发工程师,对block肯定不会陌生,它几乎是我们开发者使用的最多的类型,但是往往停留在怎么使用的层面,而对block的底层实现原理却知之甚少。今天就对block底层原理进行分析。

准备工作

block 基础语法

block声明:

returnType (^ blockName)(params)
复制代码

block赋值:

block = ^returnType(params){
     
};
复制代码
  • 由于编译器可以从代码块的变量判断出block的返回值类型,所以返回值类型一般可以省略

一个返回值为int 参数为一个int的block可以申明如下:

int (^xqBlock)(int) = ^(int a){
    return a++;
};
复制代码

调用也很简单:

xqBlock(10);
复制代码

block 的类型

block未捕获任何外部变量,或只使用了全局变量时,其类型是__NSGlobalBlock__(全局block)

int a = 8;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^xqBlock)(void) = ^{
            NSLog(@"%d",a);
        };
        xqBlock();
        NSLog(@"%@",xqBlock);
    }
    return 0;
}

**********************************打印结果**********************************
2022-02-23 15:08:37.618811+0800 KCBlockBuild[27580:431773] 8
2022-02-23 15:08:37.619485+0800 KCBlockBuild[27580:431773] <__NSGlobalBlock__: 0x100004030>
Program ended with exit code: 0
复制代码
  • 仅使用了全局变量未捕获自动变量,为 __NSGlobalBlock__

block捕获了外部变量未拷贝到堆区时为__NSStackBlock__(栈block)

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        int a = 8;
        void (^__weak xqBlock)(void) = ^{
            NSLog(@"%d",a);
        };
        xqBlock();
        NSLog(@"%@",xqBlock);
    }
    return 0;
}

**********************************打印结果**********************************
2022-02-23 15:10:59.324554+0800 KCBlockBuild[27772:435681] 8
2022-02-23 15:10:59.325195+0800 KCBlockBuild[27772:435681] <__NSStackBlock__: 0x7ffeefbff398>
Program ended with exit code: 0
复制代码
  • 变量axqBlock捕获,但是由于使用 __weak修饰,不会自动拷贝到堆区

block捕获了外部变量同时被拷贝到堆区时为__NSMallocBlock__(堆block)

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        int a = 8;
        void (^ xqBlock)(void) = ^{
            NSLog(@"%d",a);
        };
        xqBlock();
        NSLog(@"%@",xqBlock);
    }
    return 0;
}

**********************************打印结果**********************************
2022-02-23 15:13:27.869170+0800 KCBlockBuild[27977:439050] 8
2022-02-23 15:13:27.869841+0800 KCBlockBuild[27977:439050] <__NSMallocBlock__: 0x109940b90>
Program ended with exit code: 0
复制代码
  • 变量axqBlock捕获, 此时会自动拷贝到堆区

我们已经知道block是有类型的,那么block是不是和我们普通的对象一样,也是一个类的实例呢?接下来,带着这个疑问将block转换成id类型,查看其结构如下图所示:

image.png

  • 由结构可知,堆block实际是__NSMallocBlock__类的实例对象,其继承链依次为 __NSMallocBlock__ -> __NSMallocBlock -> NSBlock -> NSObject
  • 其它类型block与堆block类似,这里就不一一列举了

被block捕获的变量的内存引用计数变化

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject* objc = [[NSObject alloc]init];
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
        void (^ xqBlock)(void) = ^{
            NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
        };
        xqBlock();
        void(^__weak xqBlock1)(void) = ^{
            NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
        };
        xqBlock1();
        void (^ xqBlock2)(void) =  [xqBlock1 copy];
        xqBlock2();
    }
    return 0;
}

**********************************打印结果**********************************
2022-02-23 15:33:30.209144+0800 KCBlockBuild[29465:464529] 1
2022-02-23 15:33:30.209642+0800 KCBlockBuild[29465:464529] 3
2022-02-23 15:33:30.209684+0800 KCBlockBuild[29465:464529] 4
2022-02-23 15:33:30.209708+0800 KCBlockBuild[29465:464529] 5
Program ended with exit code: 0
复制代码
  • 对象objc初始化后引用计数为 1,当objcxqBlock捕获后,引用计数变为2,由于是__NSMallocBlock__会自动拷贝到堆区,所以引用计数再加1变为3
  • xqBlock1捕获了objc引用计数加一变为4,由于是__NSStackBlock__,所以不会拷贝到堆区
  • xqBlock2xqBlock1拷贝到堆区,所以引用计数再加一变为5

通过上面的分析,可以知道被 block捕获的变量被block强引用了,如果一个对象对 block强引用,同时,block 也捕获了这个对象就会造成循环引用

@interface XQPerson : NSObject
@property(nonatomic,copy)void (^xqBlock)(void);
@end
@implementation XQPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XQPerson* person = [[XQPerson alloc]init];
        person.xqBlock = ^{
            NSLog(@"%@",person);
        };
    }
    return 0;
}
复制代码
  • 这个问题很容易解决,在block之前使用__weak声明一个变量,赋值为 weakPerson 指向person,在block内部使用weakPerson即可,为了避免person被提前释放导致block执行的时候person为空,可以在block内部使用一个__strong声明一个局部变量对weakPerson进行一次强引用。如下所示代码:
XQPerson* person = [[XQPerson alloc]init];
__weak typeof(person) weakPerson = person;
person.xqBlock = ^{
    __strong typeof(weakPerson)strongPerson = weakPerson;
    NSLog(@"%@",strongPerson);
};
复制代码

但是在使用 block的时候,我们需要特别注意一个问题,如果在 block内部再次使用 block__strong 修饰的变量进行捕获,会造成该局部变量引用计数增加,依然会造成循环引用:

@interface XQPerson : NSObject
@property(nonatomic,copy)void (^xqBlock1)(void);
@end

@implementation XQPerson

@end

@interface ViewController ()
@property(nonatomic,copy)void (^xqBlock)(void);
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    XQPerson* person = [[XQPerson alloc]init];
    __weak typeof(self) weakSelf = self;
    self.xqBlock = ^{
        __strong typeof(weakSelf)strongSelf = weakSelf;
        person.xqBlock1 = ^{
            NSLog(@"%@",strongSelf);
        };
    };
    self.xqBlock();
    person.xqBlock1();
}
@end
复制代码

personblock 捕获, strongSelfpersonxqBlock1捕获,相当于间接循环引用了,所以我们在使用block的时候要特别注意block的嵌套问题

源码分析

前面分析了一些block的使用和对被捕获对象引用计数的影响,那么其底层原理有是什么呢?接下来,使用libclosure-79的源码和 clang编译成C++代码共同进行分析

首先定义一个堆block并加上断点后打开汇编调试,如下图所示:

截屏2022-02-24 上午11.53.50.png

运行后如下所示,会跳转执行objc_retainBlock

image.png

增加一个符号断点:objc_retainBlock,继续调试

image.png 由此可以确定,在 block被拷贝时会执行_Block_copy函数

打开libclosure-79的源码,搜索_Block_copy函数

_Block_copy函数中可以发现block在底层的定义是Block_layout结构体指针

struct Block_layout {
    void * __ptrauth_objc_isa_pointer isa;          // isa
    volatile int32_t flags; // contains ref count   //标志位
    int32_t reserved;                               //预留参数
    BlockInvokeFunction invoke;                     //指向block执行的函数指针
    struct Block_descriptor_1 *descriptor;          
    // imported variables
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;                             //预留参数
    uintptr_t size;                                 //block的大小
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {                         // 可选参数
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;                         // 指向拷贝函数的函数指针,编译期赋值
    BlockDisposeFunction dispose;                   // 指向释放函数的函数指针,编译期赋值
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {                         // 可选参数
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;                          // block的签名例如 v8@?0
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
复制代码

flags标志位解析

 // Values for Block_layout->flags to describe block objects
 enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime           // 第0位表示是否正在释放
    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
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime           // 是否需要释放
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler          // 是否有copy和dispose函数
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code 
    BLOCK_IS_GC =             (1 << 27), // runtime           // 是否有垃圾回收
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler          // 是否是全局block
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler          // 是否有签名信息
    };

复制代码

_Block_copy函数解析:

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;
    if (aBlock->flags & BLOCK_NEEDS_FREE) { // 判断是否需要释放
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {  // 判断是否全局block
        return aBlock;
    }
    else {// 栈 -> 堆   将栈block拷贝到堆区
        // Its a stack block.  Make a copy.
        //获取block的大小
        size_t size = Block_size(aBlock); 
        // 开辟size大小的内存空间
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if (!result) return NULL;
        //将aBlock拷贝到result开辟的内存空间
        memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;

#if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            uintptr_t oldDesc = ptrauth_blend_discriminator(
                    &aBlock->descriptor,
                    _Block_descriptor_ptrauth_discriminator);
            uintptr_t newDesc = ptrauth_blend_discriminator(
                    &result->descriptor,
                    _Block_descriptor_ptrauth_discriminator);

            result->descriptor =
                    ptrauth_auth_and_resign(aBlock->descriptor,
                                            ptrauth_key_asda, oldDesc,
                                            ptrauth_key_asda, newDesc);
        }
#endif
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        //将引用计数设置为1,同时标记需要释放
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
         // 获取copy函数并执行
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // 修改isa指向 为堆block
        result->isa = _NSConcreteMallocBlock;   
        
        return result;
    }
}
复制代码
  • if (aBlock->flags & BLOCK_NEEDS_FREE)判断是否需要释放
  • if (aBlock->flags & BLOCK_IS_GLOBAL)判断是否为全局block ,如果是则直接返回aBlock
  • size_t size = Block_size(aBlock);获取 block大小
  • struct Block_layout *result = (struct Block_layout *)malloc(size);在堆区开辟 size大小的内存空间并赋值给result
  • memmove(result, aBlock, size);aBlock拷贝到 result的内存空间
  • result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);重置引用计数
  • result->flags |= BLOCK_NEEDS_FREE | 2 将引用计数设置为1(第1位开始表示引用计数) ,同时标记需要释放
  • _Block_call_copy_helper(result, aBlock);获取copy函数指针并执行,copy 是可选参数,当block全局block或捕获的变量是基本数据类型(例如int)时,copy为空
  • result->isa = _NSConcreteMallocBlock; 修改result类型为堆block

_Block_call_dispose_helper函数分析:

static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
    // 获取copy函数
    if (auto *pFn = _Block_get_dispose_function(aBlock))
        pFn(aBlock);// 如果有copy函数就执行
}
复制代码

_Block_get_copy_function函数分析:

static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_function(struct Block_layout *aBlock)
{
    if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))
        return NULL;
    获取block的descriptor1首地址
    void *desc = _Block_get_descriptor(aBlock);
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
        struct Block_descriptor_small *bds =
                (struct Block_descriptor_small *)desc;
        return _Block_get_relative_function_pointer(
                bds->copy, void (*)(void *, const void *));
    }
#endif
    // 内存平移得到descriptor_2
    struct Block_descriptor_2 *bd2 =
            (struct Block_descriptor_2 *)((unsigned char *)desc +
                                          sizeof(struct Block_descriptor_1));
    // 得到copy 
    return _Block_get_copy_fn(bd2);
}

static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_fn(struct Block_descriptor_2 *desc)
{
    return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy);
}
#define _Block_get_function_pointer(field)      \
    (field)
复制代码

lldb调试验证block结构:

void (^ xqBlock)(void) = ^{
            NSLog(@"xq");
};

id globalBlock = xqBlock;

int a = 18;
void (^ xqBlock1)(void) = ^{
    NSLog(@"%d",a);
};
id intBlock = xqBlock1;

NSObject *obj = [[NSObject alloc]init];
void (^ xqBlock2)(void) = ^{
    NSLog(@"%@",obj);
};
id objcBlock = xqBlock2;
        
*****************************lldb调试*****************************
(lldb) po globalBlock
<__NSGlobalBlock__: 0x100004040>
 signature: "v8@?0"
 invoke   : 0x100003de0 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__main_block_invoke)

(lldb) po intBlock
<__NSMallocBlock__: 0x109941570>
 signature: "v8@?0"
 invoke   : 0x100003e10 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__main_block_invoke_2)

(lldb) po objcBlock
<__NSMallocBlock__: 0x1099415a0>
 signature: "v8@?0"
 invoke   : 0x100003e40 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__main_block_invoke_3)
 copy     : 0x100003e70 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__copy_helper_block_e8_32s)
 dispose  : 0x100003eb0 (/Users/18676349856/Library/Developer/Xcode/DerivedData/Blocks-goliqrxqhbsnjodqrdykntxnwymm/Build/Products/Debug/KCBlockBuild`__destroy_helper_block_e8_32s)
复制代码
  • 由调试结果可知,对于未捕获变量或只捕获了基本数据类型的block不存在copydispose,只有引入了对象类型才存在 copydispose

源码调试:

声明如下block:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc]init];
        void (^ xqBlock)(void) = ^{
            NSLog(@"%@",obj);
        };
        
        NSLog(@"block is %@", xqBlock);
    }
    return 0;
}
复制代码

_Block_copy函数打断点调试如下:

(lldb) x/4g aBlock // 输出aBlock内存信息
0x7ffeefbff398: 0x0000000100394060 0x00000000c2000000
0x7ffeefbff3a8: 0x0000000100003e50 0x0000000100004020
(lldb) po 0x0000000100394060 // 输出isa
__NSStackBlock__
// 输出flags 
(lldb) p/t 0xc2000000
// 由flags结果可以看出 包含 1<<25 即有copy和dispose
(unsigned int) $2 = 0b11000010000000000000000000000000
//输出block的执行函数invoke
(lldb) p (BlockInvokeFunction)0x0000000100003e50
(BlockInvokeFunction) $3 = 0x0000000100003e50 (KCBlockBuild`__main_block_invoke at main.m:30)
// 类型强转
(lldb) p (Block_descriptor_1*)0x0000000100004020
(Block_descriptor_1 *) $4 = 0x0000000100004020
//取值 得到 reserved = 0, size = 40
(lldb) p *$4 
(Block_descriptor_1) $5 = (reserved = 0, size = 40)
// 内存平移得到 descriptor_2
(lldb) p (Block_descriptor_2 *)($4 + 1)
(Block_descriptor_2 *) $6 = 0x0000000100004030
(lldb) p *$6
// 取值,得到copy和dispose
(Block_descriptor_2) $7 = {
  copy = 0x0000000100003e80 (KCBlockBuild`__copy_helper_block_e8_32s at main.m)
  dispose = 0x0000000100003ec0 (KCBlockBuild`__destroy_helper_block_e8_32s at main.m)
}
// 内存平移得到 descriptor_3
(lldb) p (Block_descriptor_3 *)($6 + 1)
(Block_descriptor_3 *) $8 = 0x0000000100004040
(lldb) p *$8
取值得到 签名和layout
(Block_descriptor_3) $9 = (signature = "v8@?0", layout = "")
复制代码

图解: image.png

通过上面的分析,已经知道了 block的底层结果,但是 block内部的 copy,dispose等函数什么时候赋值,以及这些函数到底做了什么,我们还是一无所知的,由于copydispose等函数是在编译期决定的,我们可以将block编译成C++代码进行分析。

声明如下一个block:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [NSObject new];
        void (^ xqBlock)(void) = ^{
            NSLog(@"%@",obj);
        };
        xqBlock();
        NSLog(@"block is %@", xqBlock);
    }
    return 0;
}

复制代码

经过clang -rewrite-objc main.m -o main.cpp编译生成c++代码,main函数如下

//删除部分类型转换代码
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSObject *obj = objc_msgSend)(objc_getClass("NSObject"), sel_registerName("new"));
        xqBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
        xqBlock->FuncPtr(xqBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_4d_29qjz7cx0vs3nqw8qm0_ypz0jm96y7_T_main_bff74a_mi_1, xqBlock);
    }
    return 0;
}
复制代码
  • 通过转换出的C++代码,可以分析出xqBlock初始化成__main_block_impl_0的结构体指针,参数依次是__main_block_func_0__main_block_desc_0_DATAobj570425344
  • block的调用实际是调用FuncPtr并将block自身传入

__main_block_impl_0结构体分析:

struct __main_block_impl_0 {
  struct __block_impl impl;//包含isa,,flags, func等信息
  struct __main_block_desc_0* Desc;// des信息,包含
  NSObject *__strong obj;// 捕获的成员
  //构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) {// obj = _obj
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码
  • 包含了block的所有信息,isa(运行时确定实际类型),flagsFuncPtrDesc,以及捕获的变量,通过main函数里调用构造函数进行了初始化

__block_impl结构体分析:

struct __block_impl {
  void *isa;
  int Flags; // 标识
  int Reserved; // 预留参数
  void *FuncPtr; // 执行函数
};
复制代码
  • libclosure-79源码中Block_layout结构几乎一样,只是少了descriptor,编译出的源码中,descriptor信息都被保存在了__main_block_desc_0结构体中
  • FuncPtr被赋值为__main_block_func_0函数

__main_block_func_0函数分析:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *__strong obj = __cself->obj; // bound by copy
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_4d_29qjz7cx0vs3nqw8qm0_ypz0jm96y7_T_main_5ed05e_mi_0,obj);
 }
复制代码
  • block的执行函数,可以看到,在此函数内,仅仅将一个新的指针指向了 obj,并没有对地址进行拷贝,所以在block内部不能对捕获的变量进行赋值。

__main_block_desc_0结构体分析:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
复制代码
  • __main_block_desc_0_DATA对结构体进行初始化,参数依次是0__main_block_impl_0结构体的大小__main_block_copy_0函数,即为 copy函数,__main_block_dispose_0函数即为disopse函数

__main_block_copy_0__main_block_dispose_0函数分析:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src){
    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
复制代码
  • 这两个函数分别调用了libclosure-79源码的_Block_object_assign函数和_Block_object_dispose函数

_Block_object_assign函数分析:

enum {
    // see function implementation for a more complete description of these fields and combinations
    //普通对象
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    // block对象
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    // __block
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    // __weak修饰,只在__block对象使用
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    // __block对象中成员变量copy,dispose时使用
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

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_default = fn (arc)
        _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 __block 修饰的对象
      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;
        // __block 修饰的对象是结构体类型,其中也有 copy函数 会调用此处,后续进行分析
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;
        __block 或__weak __block 修饰的对象是结构体类型,其中也有 copy函数 会调用此处
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}
复制代码
  • BLOCK_FIELD_IS_OBJECT ,普通对象或将block转为id的对象被捕获block后,copy函数会调用此分支,调用_Block_retain_object函数会调用到libobjc.A.dylibobjc_retain,使对象引用计数+1
  • BLOCK_FIELD_IS_BLOCKblock对象被捕获后执行此分支 调用_Block_copy函数
  • BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK或者BLOCK_FIELD_IS_BYREF__block__weak __block修饰的变量被block捕获后copy函数执行此分支,后续分析_Block_byref_copy函数
  • 另外两个分支都是针对__block修饰的变量,在底层生成的结构体中的成员,进行拷贝时会执行此分支,进行指针赋值

_Block_retain_object函数解析:

_Block_release_object是一个全局函数指针,可能在运行起来后进行赋值,所以在程序运行起来后,我们可以将其打印出来,如下图所示:

image.png

因此,我们可以确定,该函数是libobjc.A.dylib框架下的objc_retain函数,源码如下:

id 
objc_retain(id obj)
{
    if (obj->isTaggedPointerOrNil()) return obj;
    return obj->retain();
}
复制代码
  • 由查看源码可以确定_Block_retain_object实际是对捕获的变量进行了一次retain

__block修饰变量的分析:

由以上的分析可以看出,__block修饰的变量和普通的变量会执行不同的分支,为什么会有这样的差异呢,接下来对__block进行分析

定义一个 __block变量并在block中使用此变量:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSObject* objc = [NSObject alloc];
        void(^xqBlock)(void) = ^{
            NSLog(@"%@",objc);
        };
        xqBlock();
    }
    return 0;
}
复制代码

编译成C++文件如下:

// 省略部分类型强转
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
         __Block_byref_objc_0 objc = {0,&objc, 33554432, sizeof(__Block_byref_objc_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"))};
        xqBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &objc, 570425344));
        xqBlock->FuncPtr(xqBlock);
    }
    return 0;
}
复制代码
  • 由编译成的C++代码可以看出,objc被转换成了一个__Block_byref_objc_0结构体,传入的参数依次为0objc的地址__Block_byref_id_object_copy_131函数,__Block_byref_id_object_dispose_131函数。
  • block初始化将objc的地址传入并捕获。

__Block_byref_objc_0结构体分析:

struct __Block_byref_objc_0 {
  void *__isa;                                        // isa
__Block_byref_objc_0 *__forwarding;                   // 指向自身的地址
 int __flags;                                         // 标志位,表示是否有copy,dispose函数等,
 int __size;                                          // 结构体大小
 void (*__Block_byref_id_object_copy)(void*, void*);  // 结构体对objc的copy函数,捕获的变量是基础数据类型时没有
 void (*__Block_byref_id_object_dispose)(void*);      // 结构体对objc的释放函数,捕获的变量是基础数据类型时没有
 NSObject *__strong objc;                             // 捕获的变量
};
复制代码

我们再来看看此时的__main_block_copy_0函数(blockcopy函数):

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
复制代码
  • 此时的参数发生了变化,第三个参数为8即为,所以此时执行_Block_object_assign函数会执行BLOCK_FIELD_IS_BYREF分支,执行_Block_byref_copy函数

_Block_byref_copy函数分析:

分析_Block_byref_copy函数前,先熟悉一下__block修饰的变量在底层的结构 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; //= __Block_byref_id_object_copy_131 ,copy函数
    BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131 dispose函数
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;                          // 捕获的变量
};
复制代码

_Block_byref_copy函数:

static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    // __block修饰时 src->forwarding->flags = BLOCK_BYREF_HAS_COPY_DISPOSE | BLOCK_BYREF_LAYOUT_EXTENDED | 1 << 29
    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
        // 修改flags,下次不再拷贝到堆区
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4; 
        copy->forwarding = copy; // patch heap copy to point to itself
        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
            //内存平移得到Block_byref_2结构体指针 并拷贝
            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) {
                //内存平移得到Block_byref_3结构体指针 并拷贝
                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;
}
复制代码
  • 在堆区开辟一份内存空间copy,并将src的值赋值给copy
  • src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE判断是否有Block_byref_2结构体,如果有将srccopy都平移Block_byref结构体的大小到Block_byref_2,分别得到src2copy2 ,并将src2byref_keepbyref_destroy函数赋值给copy2
  • src->flags & BLOCK_BYREF_LAYOUT_EXTENDED判断是否有layout(即为成员),如果有,将src2copy2都平移Block_byref_2结构体的大小到Block_byref_3,分别得到src3copy3,并将src3layout赋值给copy3
  • (*src2->byref_keep)(copy, src),调用byref_keep函数,对捕获的变量进行保存。

上文已经介绍过 __block修饰的变量clang编译后是一个__Block_byref_objc_0结构体。

再次回顾一下__Block_byref_objc_0结构体

struct __Block_byref_objc_0 {
  void *__isa;
__Block_byref_objc_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *objc;
};
复制代码

此时在_Block_byref_copy函数中用到的byref_keep。即为__Block_byref_id_object_copy,我们再回到clang编译后的代码寻找这个函数,可以找到,__Block_byref_id_object_copy如下:

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
复制代码
  • dstsrc_Block_byref_copy函数中传入的copysrc,此时平移 40个字节,刚好平移到 加入进来的成员变量 objc
  • 131 = 128 | 3 =BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT,当再次执行到_Block_object_assign函数时,进入BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT分支,对objc进行了指针拷贝,所以使用__block修饰的变量,在block内部可以进行修改

小结: __block修饰的变量在底层实际是Block_byref的结构体,在底层会进行三重拷贝,依次为:_Block_copy->_Block_object_assign ->_Block_byref_copy->_Block_object_assign,分别为将block拷贝到堆区,将__block变量拷贝到堆区,指针拷贝。

block的释放:

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_call_dispose_helper执行block的dispose函数
  • _Block_destructInstance析构block

dispose函数分析:

上文分析copy函数时已经提到过dispose函数在编译时被赋值为__main_block_dispose_0,编译成c++代码后如下所示:

//捕获普通对象
static void __main_block_dispose_0(struct __main_block_impl_0*src){
    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

// 捕获block
static void __main_block_dispose_1(struct __main_block_impl_1*src) {
_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);
}

//捕获__block对象
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
复制代码
  • 通过编译后的函数调用,可以看到,都是调用_Block_object_dispose函数,只是flags参数因捕获的变量类型不同而有所差异。

_Block_object_dispose函数分析:

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_object_dispose和上文分析的_Block_object_assign函数非常相似,只是这里处理的是相反的逻辑。
  • __block修饰的变量执行_Block_byref_release函数,调用结构体内的dispose函数,实际也会调用到这里,进入最后一个分支,实际什么也没做,然后对结构体进行释放。
  • 如果捕获的是block对象,再次执行_Block_release_object,对捕获的block对象进行释放。
  • 捕获的是普通对象执行_Block_release_object函数,对捕获的对象进行release

_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);
        }
    }
}
复制代码
  • _Block_byref_release函数逻辑相对简单,仅仅通过内存平移取出byref2,调用byref_destroy函数,最终会调用_Block_object_dispose函数。由于传参为131 = BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT,所以实际这个函数什么也不会做。然后释放byref

_Block_release_object函数分析

_Block_release_object是一个全局函数指针,可能在运行起来后进行赋值,所以在程序运行起来后,我们可以将其打印出来,如下图所示:

image.png

因此,我们可以确定,该函数是libobjc.A.dylib框架下的objc_release函数,源码如下:

void 
objc_release(id obj)
{
    if (obj->isTaggedPointerOrNil()) return;
    return obj->release();
}
复制代码

所以可以得出_Block_release_object函数是对捕获的对象进行了一次release

_Block_destructInstance分析:

和上面的_Block_release_object一样,_Block_destructInstance也是一个全局函数指针,我们可以使用同样的方式将其输出:

image.png

因此,我们可以确定,该函数是libobjc.A.dylib框架下的objc_destructInstance函数,源码如下:

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}
复制代码
  • 查看源码,可以确定,_Block_destructInstance实际是调用objc_destructInstance函数对block进行释放

总结

block在底层实际也是一个对象,按是否捕获变量和是否拷贝到堆区分为__NSGlobalBlock____NSStackBlock____NSMallocBlock__三种类型。又根据捕获变量类型的不同,决定block内部的结构,如当捕获的类型为基本数据类型,则block没有copydispose函数。如果是普通对象,调用copy函数对对象进行一次retain,如果是 block对象,则会调用_Block_copyblock进行拷贝,如果是__block修饰的基本数据类型,则底层生成的结构体不含有byref_keepbyref_destroy函数,如果是__block修饰是对象,则底层生成的结构体含有byref_keepbyref_destroy函数,实际是进行了三重拷贝

猜你喜欢

转载自juejin.im/post/7069690748999827469