iOS——Block two

Block 的实质究竟是什么呢?类型?变量?还是什么黑科技?

Blocks 是 带有局部变量的匿名函数

Blocks 由 OC 转 C++ 源码方法

  1. 在项目中添加 blocks.m 文件,并写好 block 的相关代码。
  2. 打开「终端」,执行 cd XXX/XXX 命令,其中 XXX/XXX 为 block.m 所在的目录。
  3. 继续执行clang -rewrite-objc block.m
  4. 执行完命令之后,block.m 所在目录下就会生成一个 block.cpp 文件,这就是我们需要的 block 相关的 C++ 源码。
c++ 复制代码
1. `/* 包含 Block 实际函数指针的结构体 */`
2. `struct __block_impl {`
3. `void *isa;`
4. `int Flags;`               
5. `int Reserved;        // 今后版本升级所需的区域大小`
6. `void *FuncPtr;      // 函数指针`
7. `};`
9. `/* Block 结构体 */`
10. `struct __main_block_impl_0 {`
11. `// impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体`
12.     `struct __block_impl impl;`
13.     `// Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体`
14.     `struct __main_block_desc_0* Desc;`
15.     `// __main_block_impl_0:Block 构造函数`
16.     `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
17. `impl.isa = &_NSConcreteStackBlock;`
18.         `impl.Flags = flags;`
19.         `impl.FuncPtr = fp;`
20.         `Desc = desc;`
21.     `}`
22. `};`

23. `/* Block 主体部分结构体 */`
24. `static void __main_block_func_0(struct __main_block_impl_0 *__cself) {`
25. `printf("myBlock\n");`
26. `}`

27. `/* Block 附加信息结构体:包含今后版本升级所需区域大小,Block 的大小*/`
28. `static struct __main_block_desc_0 {`
29. `size_t reserved;        // 今后版本升级所需区域大小`
30. `size_t Block_size;    // Block 大小`
31. `} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};`

32. `/* main 函数 */`
33. `int main () {`
34.     `void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));`
35.     `((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);`

36.     `return 0;`
37. `}`

Block 结构体

我们先来看看 __main_block_impl_0 结构体( Block 结构体)

c++ 复制代码
1. `/* Block 结构体 */`
2. `struct __main_block_impl_0 {`
3.     `// impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体`
4.     `struct __block_impl impl;`
5.     `// Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体`
6.     `struct __main_block_desc_0* Desc;`
7.     `// __main_block_impl_0:Block 构造函数`
8. `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
9.         `impl.isa = &_NSConcreteStackBlock;`
10.         `impl.Flags = flags;`
11.         `impl.FuncPtr = fp;`
12.         `Desc = desc;`
13.     `}`
14. `};`

从上边我们可以看出,__main_block_impl_0 结构体(Block 结构体)包含了三个部分:

从上述代码可以看出block的本质是个 __main_block_impl_0 的结构体对象,这就是为什么能用 %@ 打印出block的原因了

  1. 成员变量 impl;
  2. 成员变量 Desc 指针;
  3. __main_block_impl_0 构造函数。
  4. 析构函数中所需要的函数:fp传递了具体的block实现__main_block_func_0,然后保存在block结构体的impl

block捕获变量

这就说明了block声明只是将block实现保存起来,具体的函数实现需要自行调用

值得注意的是,当block为堆block时,block的构造函数会多出来一个参数a,并且在block结构体中多出一个属性a

接着把目光转向__main_block_func_0实现

  • __cself__main_block_impl_0的指针,即block本身
  • int a = __cself->aint a = block->a
  • 由于a只是个属性,所以是堆block只是值拷贝(值相同,内存地址不同)
  • 这也是为什么捕获的外界变量不能直接进行操作的原因,如a++会报错

当__block修饰外界变量的时候

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6SlRlhS-1690945388106)(https://raw.githubusercontent.com/ArnoVD97/PhotoBed/master/photo202307281627162.png)]
__block修饰的属性在底层会生成响应的结构体,保存原始变量的指针,并传递一个指针地址给block------因此是指针拷贝

源码中增加了一个名为_Block_byref_a_0的结构体,用来保存我们要capture并且修改的变量i
__main_block_impl_0引用的是_Block_byref_a_0结构体指针,起到修改外部变量的作用

_ Block_byref_a_0里面有isa,也是一个对象

我们需要负责_Block_byref_a_0结构体相关的内存管理,所以_main_block_desc_0中增加了copy和dispose的函数指针,用于在抵用前后修改相应变量的引用计数

struct __block_impl impl 说明

第一部分 impl__block_impl 结构体类型的成员变量。__block_impl 包含了 Block 实际函数指针 FuncPtrFuncPtr 指针指向 Block 的主体部分,也就是 Block 对应 OC 代码中的 ^{ printf("myBlock\n"); }; 部分。还包含了标志位 Flags,今后版本升级所需的区域大小 Reserved__block_impl 结构体的实例指针 isa

c++ 复制代码
1. `/* 包含 Block 实际函数指针的结构体 */`
2. `struct __block_impl {`
3. `void *isa;               // 用于保存 Block 结构体的实例指针`
4.   `int Flags;               // 标志位`
5.   `int Reserved;        // 今后版本升级所需的区域大小`
6.   `void *FuncPtr;      // 函数指针`
7. `};`

struct __main_block_desc_0* Desc 说明

第二部分 Desc 是指向的是 __main_block_desc_0 类型的结构体的指针型成员变量,__main_block_desc_0 结构体用来描述该 Block 的相关附加信息:

  1. 今后版本升级所需区域大小: reserved 变量。
  2. Block 大小:Block_size 变量。
c++ 复制代码
1. `/* Block 附加信息结构体:包含今后版本升级所需区域大小,Block 的大小*/`
2. `static struct __main_block_desc_0 {`
3. `size_t reserved;      // 今后版本升级所需区域大小`
4. `size_t Block_size;  // Block 大小`
5. `} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};`

__main_block_impl_0 构造函数说明

第三部分是 __main_block_impl_0 结构体(Block 结构体) 的构造函数,负责初始化 __main_block_impl_0 结构体(Block 结构体) 的成员变量。

c++ 复制代码
1. `__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {`
2. `impl.isa = &_NSConcreteStackBlock;`
3.     `impl.Flags = flags;`
4.     `impl.FuncPtr = fp;`
5.     `Desc = desc;`
6. `}`

关于结构体构造函数中对各个成员变量的赋值,我们需要先来看看 main() 函数中,对该构造函数的调用。

c++ 复制代码
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

我们可以把上面的代码稍微转换一下,去掉不同类型之间的转换,使之简洁一点:

c++ 复制代码
1. `struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);`
2. `struct __main_block_impl_0 myBlock = &temp;`

这样,就容易看懂了。该代码将通过 __main_block_impl_0 构造函数,生成的 __main_block_impl_0 结构体(Block 结构体)类型实例的指针,赋值给 __main_block_impl_0 结构体(Block 结构体)类型的指针变量 myBlock

可以看到, 调用 __main_block_impl_0 构造函数的时候,传入了两个参数。

  1. 第一个参数:__main_block_func_0
    • 其实就是 Block 对应的主体部分,可以看到下面关于 __main_block_func_0 结构体的定义 ,和 OC 代码中 ^{ printf("myBlock\n"); }; 部分具有相同的表达式。
    • 这里参数中的 __cself 是指向 Block 的值的指针变量,相当于 OC 中的 self

c++ /* Block 主体部分结构体 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("myBlock\n"); }

  1. 第二个参数:__main_block_desc_0_DATA__main_block_desc_0_DATA 包含该 Block 的相关信息。
    我们再来结合之前的 __main_block_impl_0 结构体定义。
    • __main_block_impl_0 结构体(Block 结构体)可以表述为:
c++ 复制代码
1. `struct __main_block_impl_0 {`
2.      `void *isa;               // 用于保存 Block 结构体的实例指针`
3.     `int Flags;               // 标志位`
4.     `int Reserved;        // 今后版本升级所需的区域大小`
5.     `void *FuncPtr;      // 函数指针`
6.     `struct __main_block_desc_0* Desc;      // Desc:Desc 指针`
7. `};`
  • __main_block_impl_0 构造函数可以表述为:
c++ 复制代码
1. `impl.isa = &_NSConcreteStackBlock;    // isa 保存 Block 结构体实例`
2. `impl.Flags = 0;        // 标志位赋值`
3. `impl.FuncPtr = __main_block_func_0;    // FuncPtr 保存 Block 结构体的主体部分`
4. `Desc = &__main_block_desc_0_DATA;    // Desc 保存 Block 结构体的附加信息`

[[Block签名]]
__main_block_impl_0 结构体(Block 结构体)相当于 Objective-C 类对象的结构体,isa 指针保存的是所属类的结构体的实例的指针。_NSConcreteStackBlock 相当于 Block 的结构体实例。对象 impl.isa = &_NSConcreteStackBlock; 语句中,将 Block 结构体的指针赋值给其成员变量 isa,相当于 Block 结构体的成员变量 保存了 Block 结构体的指针,这里和 Objective-C 中的对象处理方式是一致的。

也就是说明: Block 的实质就是对象。

block的copy分析

接下来就来研究下栈block转换成到堆block的过程------_Block_copy

objc 复制代码
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) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#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. 通过flags标识位------存储引用计数的值是否有效

block的引用计数不受runtime处理的,是由自己管理的

objc 复制代码
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;
        }
    }
}

这里可能有个疑问

为什么引用计数是 +2 而不是 +1

因为flags的第一号位置已经存储着释放标记

objc 复制代码
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
    return aBlock;
}
  1. 是否是全局block------
objc 复制代码
else {
    // Its a stack block.  Make a copy.
    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
#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. 栈block -> 堆block的过程
  • 先通过malloc在堆区开辟一片空间
  • 再通过memmove将数据从栈区拷贝到堆区
  • invokeflags同时进行修改
  • block的isa标记成_NSConcreteMallocBlock
    [[__block的深入研究]]
相关推荐
GEEKVIP2 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
逢生博客8 小时前
Mac 搭建仓颉语言开发环境(Cangjie SDK)
macos·华为·鸿蒙
Rverdoser11 小时前
MacOS Catalina 从源码构建Qt6.2开发库之01: 编译Qt6.2源代码
macos
Magnetic_h11 小时前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
归辞...12 小时前
「iOS」——单例模式
ios·单例模式·cocoa
GEEKVIP14 小时前
如何在没有备份的情况下恢复 Mac 上丢失的数据
经验分享·笔记·安全·macos·电脑·笔记本电脑·改行学it
yanling202314 小时前
黑神话悟空mac可以玩吗
macos·ios·crossove·crossove24
归辞...16 小时前
「iOS」viewController的生命周期
ios·cocoa·xcode
Vzhangs16 小时前
MacOS Sonoma(14.x) 大写模式或中文输入法下的英文模式,光标下方永远会出现的CapsLock箭头Icon的去除办法
macos
crasowas20 小时前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode