Skip to content

Latest commit

 

History

History
298 lines (247 loc) · 9.22 KB

2021-4-11 Block本质.md

File metadata and controls

298 lines (247 loc) · 9.22 KB

Block本质

项目中常用的block,你知道他的原理吗?

Block使用方式

OC:  void (^block)(void) = ^{ };
Swift: var block = (Void -> Void)

Block基本概念

Block的结构体

struct Block_layout {
    // 代表block其实也是一个oc对象
    void * __ptrauth_objc_isa_pointer isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    // 调用方法
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables (输入的参数)
};

typedef void(*BlockInvokeFunction)(void *, ...);

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    // 预留信息位,默认为0;
    uintptr_t reserved;
    // 为编译后的block结构体大小
    uintptr_t size;
};

int32_t flags:

// Values for Block_layout->flags to describe block objects
enum {
    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
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime  判断是否是堆block
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    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: 判断是否在栈上 (函数 _Block_use_stret 的描述)
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler  签名  
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

isa 说明本质block还是一个oc对象

Block的类型

找到 data.c 文件

void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
// 下面三种类型在 GC 环境下才有
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };

意味目前在iOS环境中,只会使用三种类型的blocj,堆、栈、全局三种类型。

编译后的Block

先来看一下测试的源码

#import "BlockTest.h"

void (^globalBlock)(void) = ^{ NSLog(@"1"); };
// globalBlock 运行和编译都为 __NSGlobalBlock__

@implementation BlockTest

- (void)test {
    int number = 0;
    void (^block1)(void) = ^{
        // 未捕捉外部变量
        NSLog(@"1");
    };
    //  [block1 class] __NSGlobalBlock__
    // block1 运行为 __NSGlobalBlock__ 编译后为__NSStackBlock__ (猜测可能为运行时修改为了全局block)

    void (^block2)(void) = ^{
        // 捕捉外部变量
        number;
    };
    // [block1 class] __NSMallocBlock__
    // block2 运行时为 __NSMallocBlock__  编译后为__NSStackBlock__
}

@end

通过 clang -rewrite-objc xxx.m
编译后的cpp文件,只举出关键的信息

每个block会被编译成三部分

  • struct __XXX_XXX_impl_X { imp + desc + imported variables(有几个加几个) + 初始化函数 }
  • function block内部的方法函数
  • struct block的desc

globalBlock部分:


struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  // 初始化
  __globalBlock_block_impl_0(void *fp, struct
     __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// block里面的方法
static void __globalBlock_block_func_0(struct __globalBlock_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_l7y9kxnx6mx5fy6nsslkh5gr0000gn_T_BlockTest_f9c8b2_mi_0);
}

static struct __globalBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __globalBlock_block_desc_0_DATA = { 0, sizeof(struct __globalBlock_block_impl_0)};
static __globalBlock_block_impl_0 __global_globalBlock_block_impl_0((void *)__globalBlock_block_func_0, &__globalBlock_block_desc_0_DATA);
void (*globalBlock)(void) = ((void (*)())&__global_globalBlock_block_impl_0);

block1部分:

struct __BlockTest__test_block_impl_0 {
  struct __block_impl impl;
  struct __BlockTest__test_block_desc_0* Desc;
  __BlockTest__test_block_impl_0(void *fp, struct __BlockTest__test_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __BlockTest__test_block_func_0(struct __BlockTest__test_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_l7y9kxnx6mx5fy6nsslkh5gr0000gn_T_BlockTest_f9c8b2_mi_1);
    }

static struct __BlockTest__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __BlockTest__test_block_desc_0_DATA = { 0, sizeof(struct __BlockTest__test_block_impl_0)};

block2部分:

struct __BlockTest__test_block_impl_1 {
  struct __block_impl impl;
  struct __BlockTest__test_block_desc_1* Desc;
  int number;
  __BlockTest__test_block_impl_1(void *fp, struct __BlockTest__test_block_desc_1 *desc, int _number, int flags=0) : number(_number) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __BlockTest__test_block_func_1(struct __BlockTest__test_block_impl_1 *__cself) {
  int number = __cself->number; // bound by copy
        number;
    }

static struct __BlockTest__test_block_desc_1 {
  size_t reserved;
  size_t Block_size;
} __BlockTest__test_block_desc_1_DATA = { 0, sizeof(struct __BlockTest__test_block_impl_1)};

test方法

static void _I_BlockTest_test(BlockTest * self, SEL _cmd) {
    int number = 0;
    void (*block1)(void) = ((void (*)())&__BlockTest__test_block_impl_0((void *)__BlockTest__test_block_func_0, &__BlockTest__test_block_desc_0_DATA));
    void (*block2)(void) = ((void (*)())&__BlockTest__test_block_impl_1((void *)__BlockTest__test_block_func_1, &__BlockTest__test_block_desc_1_DATA, number));
}

【问】为啥栈block在运行后变成了堆block?

为了解决栈块在其变量作用域结束之后被释放废弃的问题,我们需要把block复制到堆区,以延长其生命周期。

这里讲解一下block存储位置如何从栈变成堆的。
block 在运行时会调用 Block_copy 方法

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))

运行时使用了 _Block_copy 方法。
_Block_copy方法逻辑:
如果是全局block,直接返回全局block, 如果是堆block,引用计数+1,返回block, 如果是栈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;
    // 表明是堆block
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // 引用计数+1
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 全局block
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // 如果是栈则copy
        size_t size = Block_size(aBlock);
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if (!result) return NULL;
        // 栈内存copy到堆上
        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
        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.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}


// 引用计数+1  32位flags最后一位参数是BLOCK_DEALLOCATING 用来判断是否释放的 所有+2 代表引用计数+1
static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

参考:
Block源码