RyukieDev
  • 关于我-AboutMe
  • 我的作品-MyApps
    • 「梦见」账本
      • 「梦见」账本(Umemi):极致的记账体验
      • 隐私协议:Privacy Policy
      • 服务协议:Terms of use
      • 外观预览:Preview
        • 赛博朋克-Cyberpunk
        • 樱-Sakura
        • 初恋-FirstLove
        • 永生-Eternal
        • 菲尼克斯-Phoenix
        • 报丧女妖-Banshee
        • 九霄-NYXL
        • Dream
        • 猕猴桃-Kiwi
        • 蜂蜜-Hachimitsu
        • DC
    • Elic-扫雷无尽天梯
    • 隐私访问记录
      • 03.如何分析iOS15隐私访问记录
      • PrivacyPolicy
      • FrameWorks
    • 醒词
      • PrivacyPolicy
      • TermsOfUse
    • 一色
      • PrivacyPolicy
    • 醒诗
      • PrivacyPolicy
    • 醒词键盘
      • PrivacyPolicy
    • 中文数字键盘
      • PrivacyPolicy
  • 独立开发
    • 产运
      • 01.没钱推广?这忘了这100美元
      • 02.在个人站点配置app-ads
      • 03.应用图标圆角
      • 04.iOS独立开发者注册公司到App备案上架.md
    • iCloud
      • 01.基于iCloud构建用户体系
      • 02.基于iCloud构建游戏内排行榜
  • Swift
    • 01.纯Swift路由方案探究
    • 02.使用Carthage替代CocoaPods
    • 03.逃逸闭包和非逃逸闭包
    • 04.向下向上取整
    • 05.Copy-on-write
    • 06.OC老项目Swift混编的一些坑
    • 07.OC项目中加入Swift混编
    • 08.Optional实质
    • 09.R-Swift-安全的资源组织方案forSwift
    • 10.Struct与Class
    • 11.Swift5新特性
    • 12.Swift性能分析
    • 13.SwiftPackage使用
    • 14.String与Substring
    • 15.Array,Set,Dictionary
    • 16.For-in跳跃遍历
    • 17.Switch元祖
    • 18.循环的标签控制
    • 19.Protocol与静态派发、动态派发
    • 20.Swift位移枚举
    • 21.Swift轻量级网络封装:SwiftyServiceProtocol(适用于混编或纯Swift项目)
    • 22.open与public
    • 23.Swift项目编译速度优化
    • 24.[译]编写高性能Swift代码-Writing High-Performance Swift Code(2022.8.25版)
    • 25.Swift编译流程
    • 26.Swift方法调度
  • SwiftUI
    • 01.Form
    • 02.Navigation
    • 03.ViewBuilder参数10个限制
    • 04.UIKit混编时Dismiss掉HostController
    • 05.如何在SwiftUI中使用ImagePicker?
    • 06.从some看Swift不透明类型
    • 07.TabView使用
    • 08.openURL
    • 09.Search
    • 10.SwifUI中使用WKWebView
  • DeepLearning
    • 基础知识
      • 01.感知机与神经网络
      • 02.线性可分
    • TensorFlow
      • 01.Anaconda
      • 02.JupyterNotebook
      • 03.安装TensorFlow
  • iOS
    • 底层
      • 01.alloc与字节对齐
      • 02.结构体内存对齐
      • 03.对象本质探究与isa
      • 04.ISA与Class
      • 05.深入探索Class的结构
      • 06.WWDC20-runtime优化
      • 07.深入探究属性
      • 08.isKindOfClass的底层实现
      • 09.slowpath和fastpath
      • 10.Class-cache_t
      • 11.源码解读objc_msgSend
      • 12.类的实现与初始化源码解读
      • 13.动态决议与消息转发
      • 14.iOS应用启动(一):dyld与main函数
      • 15.iOS应用启动(二):环境配置与runtime初始化
      • 16.iOS应用启动(三):镜像文件的读取和加载
      • 17.iOS应用启动(四):分类的加载
      • 18.关联对象源码解析
      • 19.MethodSwizzing方法交换的坑
      • 20.详解KVC
      • 21.KVO几个被忽视的细节
      • 22.KVO底层原理
      • 23.多线程原理与atomic
      • 24.任务与队列的几个面试题
      • 25.dispatch_once单例实现原理
      • 26.栅栏函数
      • 27.信号量
      • 28.锁|性能分析
      • 29.锁|@synchronized
      • 30.锁|递归锁
      • 31.锁|NSConditionLock
      • 32.关于Block你所该知道的一切
    • 内存管理
      • 01.从一个面试题看TaggedPointer
      • 02.Retain&Release
      • 03.SideTable和weak底层实现
      • 04.Timer优化
      • 05.自动释放池与Runloop
      • 06.dealloc
    • 编译器
      • 01.LLVM
    • 杂项
      • 01.堆栈的深度问题
      • 02.使用TTF字体
      • 03.为什么选VIPER
      • 04.项目路由方案
      • 05.隐藏导航栏下面的线
      • 06.源代码到IPA
      • 07.iOS重签名调研
      • 08.load与-initialize
      • 09.NSTimer与GCD
      • 10.NSURLConnection-和-NSURLSession
      • 11.Storyboard中UnwindSegue的使用
      • 12.UI调试-UIDebuggingInformationOverlay
      • 13.UIWebView和WKWebView
      • 14.UIWebView自适应高度
      • 15.weak实现原理
    • Runloop
      • 01.RunLoop
      • 02.autoreleasepool
    • Runtime
      • 01.基本操作
      • 02.实现NSCoding的自动归档和自动解档
      • 03.消息机制
      • 04.重写description打印对象信息
      • 05.MethodSwizzling的问题
    • 优化
      • 01.Apple官方资源瘦身方案ODR(一):初见
      • 02.Apple官方资源瘦身方案ODR(二):践行|换肤系统改造
      • 03.二进制重排实践
      • 04.iOS截屏防护方案
      • 05.提高编译速度
      • 06.图片格式-WebP
      • 07.App启动速度优化
      • 08.IDL自动化埋点
      • 09.渲染原理及优化
      • 10.「利用 Metrics 和 Diagnostics 提高性能」网络研讨活动
      • 11.离屏渲染
      • 12.一键搞定iOS16横竖屏切换
    • 多线程
      • 01.合适的线程数量
      • 02.死锁
      • 03.为什么用dispatch-once实现单例
      • 04.iOS多线程方案
      • 05.iOS多线程技术对比
    • Database
      • 01.数据库主键和外键
      • 02.FMDB-死锁问题
      • 03.FMDB与WCDB
      • 04.SQLite数据库修复
    • 架构
      • 01.组件化
  • 逆向
    • 01.寄存器
    • 03.iOS应用签名原理
    • 04.利用Xcode进行重签名与调试
    • 05.dylib注入
    • 06.MachO文件
    • 07.dyld
    • 08.Hook
    • 09.深入理解动态库与静态库
    • 10.通过符号表找到符号
    • 11.fishhook原理
    • 12.去符号与恢复符号
    • 13.反HOOK防护(一):基于Fishhook
    • 14.反HOOK防护(二):Monkey
    • 15.Inlinehook:Dobby
    • 16.LLDB
    • 17.虚拟内存
    • 18.Chisel工具
    • 19.DS.LLDB工具
    • 20.Cycript工具
    • 21.Cycrupt用法
    • 22.Logos
    • 23.应用砸壳
    • 24.实战人人视频破解
    • 25.解密被加密的数据库文件
  • Flutter
    • 01.初见Flutter
    • 02.Layout
    • 03.状态管理
    • 04.BottomNavigationBar
    • 05.MaterialApp
    • 06.android资源配置
    • 07.Positioned与Container嵌套无法充满容器
    • 08.Cell点击跳转
    • 09.代码规范
    • 10.通过联系人Cell看断言
    • 11.有状态Widget初始化重写&链式调用&排序
    • 12.索引条:手势及clamp函数
    • 13.ListView滑动到指定位置
    • 14.悬浮菜单列表
    • 15.Mock数据小技巧
    • 16.第三方库导入与网络数据异步请求与展示
    • 17.请求数据保留
    • 18.异步编程之Future
    • 19.Future&Microtask
    • 20.Dart异步编程:Isolates和事件循环
    • 21.Widget的生命周期
    • 22.Widget树&Render树&Element树
    • 23.Key
    • 24.调用原生相册
    • 25.iOS原生嵌入FlutterModule
  • 网络
    • 01 网络分层的优点
    • 02 网络理解
    • 03 iOS-网络安全之HTTPS
    • 04 POST和GET
    • 05 SSL-TLS四次握手
  • 直播技术
    • 01 直播技术相关
    • Socket-Little-Endian-Big-Endian
  • 知识点梳理
    • 01 面试算法题记录01
    • 02 面试题记录-C语言
    • 08 一套iOS底层试卷
    • 03 知识点梳理:iOS底层
    • 04 知识点梳理:网络
    • 05 知识点梳理:多线程
    • 06 知识点梳理:计算机基础
    • 07 知识点梳理:算法数据结构
    • 09 知识点梳理:HTML和浏览器
    • 10 知识点梳理:JavaSctipt
  • Framework
    • 01 CodeReading-01-YYModel
    • 02 RYImagePicker-iOS图片视频选择器
    • 03 RYImagesScroller-iOS高度自定义的图片轮播器
    • 04 RYPhotosBrowser
  • Issue
    • 01 使用KVC设置UIAlertAction按钮颜色的Crash
    • 02 iOS-常见崩溃分析
    • 03 UICollectionView的一些问题
  • OpenGL ES
    • 01.顶点着色器与片元着色器
  • 数据结构与算法
    • 剑指Offer-Swift
      • 03.找出数组中重复的数字
      • 04.二维数组中的查找
      • 05.替换空格
      • 06.从尾到头打印链表
      • 07.重建二叉树
      • 12.矩阵中的路径(回溯法)
      • 13.机器人的运动范围
      • 14.I.剪绳子
      • 14.II.剪绳子
      • 15.二进制中1的个数(含一个拓展问题)
      • 16.数值的整数次方
      • 18.删除链表的节点
      • 21.调整数组顺序使奇数位于偶数前面
      • 22.链表中倒数第k个节点
      • 24.反转链表
      • 25.合并两个排序的链表
      • 26.树的子结构
      • 27.二叉树的镜像
      • 28.对称的二叉树
      • 29.顺时针打印矩阵
      • 30.包含min函数的栈(容易被误导的一题)
      • 31.栈的压入、弹出序列
      • 32.I.从上到下打印二叉树
      • 32.II.从上到下打印二叉树II
      • 32.III.从上到下打印二叉树III
      • 32.从上到下花式打印二叉树
      • 33.二叉搜索树的后序遍历序列
      • 34.二叉树中和为某一值的路径
      • 35.复杂链表的复制(无Swift用例)
      • 36.二叉搜索树与双向链表
      • 37.序列化二叉树
      • 39.数组中出现次数超过一半的数字
      • 40.最小的k个数
      • 41.数据流中的中位数
      • 42.连续子数组的最大和
      • 43.1~n整数中1出现的次数
      • 44.数字序列中某一位的数字
      • 45.把数组排成最小的数
      • 46.把数字翻译成字符串
      • 47.礼物的最大价值
      • 48.最长不含重复字符的子字符串
      • 50.第一个只出现一次的字符
      • 52.两个链表的第一个公共节点
      • 53-I.在排序数组中查找数字
      • 53-II.0~n-1中缺失的数字
      • 54.二叉搜索树的第k大节点
      • 55-I.二叉树的深度
      • 55-II.平衡二叉树
      • 56-I.数组中数字出现的次数
      • 56-II.数组中数字出现的次数II
      • 57.和为s的两个数字
      • 58-I.翻转单词顺序
      • 58-II.左旋转字符串
      • 59-I.滑动窗口的最大值
      • 59-II.队列的最大值
      • 60.n个骰子的点数
      • 61.扑克牌中的顺子
      • 62.圆圈中最后剩下的数字
      • 63.股票的最大利润
      • 64.求1+2+…+n
      • 65.不用加减乘除做加法
      • 66.构建乘积数组
      • 67.把字符串转换成整数
      • 68-I.二叉搜索树的最近公共祖先
      • 68-II.二叉树的最近公共祖先
    • 技巧
      • 01.前缀和
      • 02.同余性质
      • 03.快速幂
      • 04.快速排序
      • 05.深度优先&广度优先
      • 06.冒泡排序
      • 07.摩尔投票
      • 08.优先队列
    • 数据结构
Powered by GitBook
On this page
  • 一、 block 的分类
  • 二、 堆、栈block的生命周期
  • 三、 捕获变量的底层实现
  • 四、 运行时 block 类型的确定
  • 五、 汇编验证 block 的类型转换
  • 六、 Block_layout 结构分析
  • 七、 block 签名
  • 八、 __block 捕获变量的生命周期
  • 九、 补充:面试题一解析
  • 参考

Was this helpful?

  1. iOS
  2. 底层

32.关于Block你所该知道的一切

Previous31.锁|NSConditionLockNext内存管理

Last updated 3 years ago

Was this helpful?

一、 block 的分类

全局 block 、 堆 block 、 栈 block 。

1.1 NSGlobalBlock 全局 block

  • 位于全局区

  • 未捕获任何变量 或 只捕获全局静态变量

未捕获任何变量

<__NSGlobalBlock__: 0x100004030>

只捕获全局静态变量

1.2 堆 block

  • 位于堆区

  • 在 block 内部使用 局部变量 或者 OC属性 ,并且赋值给 强引用 或者 Copy修饰 的变量

<__NSMallocBlock__: 0x1007049e0>

1.3 栈 block

  • 位于栈区

  • 在 block 内部使用 局部变量 或者 OC属性 ,但 不 赋值给 强引用 或者 Copy修饰 的变量

<__NSStackBlock__: 0x7ffeefbff378>

1.4 面试题(一)

下面输出的结果会是什么样?

NSObject *obj = [NSObject new];
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));

void (^blockA)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();

void (^ __weak blockB)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();

void (^blockC)(void) = [blockB copy];
blockC();

这里涉及到关于 block类型 的底层知识,我们到最后在进行解析。

1.5 面试题(二)

下面输出的结果会是什么样?

int num = 0;
void (^ __weak blockA)(void) = nil;
{
    void (^ __weak blockB)(void) = ^{
        NSLog(@"%d", num);
    };
    blockA = blockB;
    NSLog(@"=");
}
blockA();

1.6 面试题(三)

下面输出的结果会是什么样?

int num = 0;
void (^ __weak blockA)(void) = nil;
{
    void (^ blockB)(void) = ^{
        NSLog(@"%d", num);
    };
    blockA = blockB;
    NSLog(@"=");
}
blockA();

二、 堆、栈block的生命周期

这里结合 二、三面试题 来进行探索。

2.1 面试题二解读

很可能有同学会给出只会打印 = 的答案。这就是对于 堆、栈block的生命周期 理解的不够深入。正确的输出如下

为什么呢?关键是对 blockB 的生命周期理解错误。一般会这么理解:

这里先不讲为什么,我们再看下 面试题三。

2.2 面试题三解读

这里发生了野指针访问崩溃了。

2.3 堆 block 的生命周期

在大括号内部下断点可以看到, 这时 blockB 为 堆block 且有值。

赋值给 blockA 。

出了大括号后 blockA 为空了,说明 blockB 释放掉了。

因为作为 存储在堆上 的 堆block 其生命周期仅存在于大括号内部。

2.4 栈 block 的生命周期

那么 面试题二 中的 blockB 的生命周期又是怎样的呢?

blockB 为 栈block 。

赋值给 blockA 。

出了大括号的作用域了, blockA 的地址依旧没变,说明 blockB 没有被释放。

因为作为 存储在栈中 的 栈block 其生命周期与 调用栈 相同。函数调用栈结束后释放。

三、 捕获变量的底层实现

3.1 普通变量的捕获

int num = 1;
void (^block)(void) = ^{
    NSLog(@"%d", num);
};
block();

我们将上面的代码还原成 C++ :

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • block 的本质是一个结构体 __main_block_impl_0 ,如果有需要捕获的变量,结构体内部会生成一个成员用来存储,例子中是 int num;

  • __main_block_func_0 保存的是 block 的具体需要执行的函数

  • __block_impl 是 block 的具体具体的一些数据,如函数等

  • __main_block_desc_0 是描述 block 特征的一个数据结构

这段代码中做了些什么呢?

一、 创建 block :

1、 调用 __main_block_impl_0 的 构造函数

2、 传入 __main_block_func_0 函数 和 __main_block_desc_0_DATA 的地址 和 变量 num

3、 指定 block 类型:impl.isa = &_NSConcreteStackBlock; (这里的堆block对应的是 _NSConcreteStackBlock )

4、 存储标志位: impl.Flags = flags;

5、 存储函数: impl.FuncPtr = fp;

6、 描述内容赋值: Desc = desc;

二、 执行 block :

1、 获取 __main_block_func_0 函数: ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)

2、 将 block 作为参数传入: ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

三、 执行函数 __main_block_func_0 :

1、 从 __main_block_impl_0 中取出变量 num : int num = __cself->num;

2、 执行 NSLog : NSLog((NSString *)&__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_main_e543c2_mi_1, num);

3.2 __block 普通变量的捕获

__block int num = 0;
void (^block)(void) = ^{
    num ++;
    NSLog(@"%d", num);
};
block();

将上面的代码转为 C++

我们发现多了几种数据类型: __Block_byref_num_0 、 __main_block_copy_0 、 __main_block_dispose_0 。

加了 __block 的变量,这里被用 __Block_byref_num_0 数据结构包装了一下,这里传入的 num 也是通过地址的形式传入的。

3.3 捕获实例变量

不活实例变量和 3.2 没有什么区别。

NSNumber *num = @100;
void (^block)(void) = ^{
    NSLog(@"%@", num);
};
block();

3.4 全局 block

static const NSString *string = @"aaaaaaa";
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"%@", string);
        };
        block();
    }
}

3.5 栈 block

int num = 0;
void (^ __weak block)(void) = ^{
    NSLog(@"%d", num);
};
block();

这里还原 C++ 你可能会遇到包错:

error: cannot create __weak reference because the current deployment target does not support weak references
        void (^ __attribute__((objc_ownership(weak))) block)(void)

加上一些参数即可:

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 main.m -o main.cpp

这里发现 __weak 实际是用 __attribute__((objc_ownership(weak))) 进行了包装,这里还没有什么更有用的线索,我们先继续探究。

3.6 _NSConcreteStackBlock

相信细心的你一定能发现一个问题,不论什么类型的 block ,还原出的 C++ 底层源码中都是 impl.isa = &_NSConcreteStackBlock; 看名字是个 栈 block 。和实际运行打断点运行时看到的不一样啊。下面我们就来分析一下这里发生了什么。

四、 运行时 block 类型的确定

由于这里并没有什么具体线索,我们通过下断点,并打开汇编。这里是一个 全局block 。

4.1 进入汇编断点

4.2 objc_retainBlock

通过 libobjc.A.dylib`objc_retainBlock: 发现它在 libobjc 中。

在 oc 源码中定位到了相关代码,但更进一步调用的 _Block_copy 并没有找到,我们继续回到汇编,继续执行下一步。

4.3 _Block_copy

这例进入了 _Block_copy 的汇编,发现它是 libsystem_blocks 中的,我们来 libclosure 中寻找一下线索,找到了下面的源码。

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 就进行 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
        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.
        // 设置 isa 为 堆block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

五、 汇编验证 block 的类型转换

5.1 全局block

一直来到 _Block_copy 内,读取 x0 寄存器 (ARM64下,x0这时存的是消息的接收者)。

进入 _Block_copy 时,就已经标识为 全局block 了。

我们发现输出了这样数据结构 signature invoke ,后面再具体看他们代表了什么。

<__NSGlobalBlock__: 0x1006f8028>
 signature: "v8@?0"
 invoke   : 0x1006f5edc (/private/var/containers/Bundle/Application/49DA1CBF-A462-4433-80DE-19721201954A/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)

5.2 堆block

进入 _Block_copy 时,显示的是 栈block 。

<__NSStackBlock__: 0x16d6651b8>
 signature: "v8@?0"
 invoke   : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)

和源码所写的一样,这里进行了 malloc :

在 _Block_copy return 之前我们再看一下 x0 :

这里已经变成了 堆block

<__NSMallocBlock__: 0x281030ab0>
 signature: "v8@?0"
 invoke   : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)

_block 捕获变量的变化

进入 _Block_copy 时:

<__NSStackBlock__: 0x16b21d1a0>
 signature: "v8@?0"
 invoke   : 0x104be5da0 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x104be5e04 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/Block探索.app/Block探索`__copy_helper_block_e8_32r)
 dispose  : 0x104be5e3c (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/Block探索.app/Block探索`__destroy_helper_block_e8_32r)

在 _Block_copy return 之前

<__NSMallocBlock__: 0x2823a83f0>
 signature: "v8@?0"
 invoke   : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__copy_helper_block_e8_32r)
 dispose  : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__destroy_helper_block_e8_32r)

这里的数据结构又发生了变化,多了 copy dispose 。

5.3 栈block

通过查看调用栈,我们发现并没有调用 objc_retainBlock 。没有进行 _Block_copy 操作。

六、 Block_layout 结构分析

6.1 isa

block 的类型:全局、堆、栈

6.2 flags

这些标志位都标明了不同的使用时间,有 运行时:runtime 有 编译期:compiler

// Values for Block_layout->flags to describe block objects
enum {
    /// 正在释放
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    /// 引用计数掩码
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    /// 需要释放
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    /// 是否拥有拷贝辅助函数 有的话就有 Block_descriptor_2
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    /// 是否拥有 block 析构函数
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    /// 标志是否有垃圾回收 MAC
    BLOCK_IS_GC =             (1 << 27), // runtime
    /// 是否是 全局block
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    /// 和 BLOCK_HAS_SIGNATURE 对应
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    /// 有签名 有的话就有 Block_descriptor_3
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    /// 有拓展 Layout
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

6.3 reserved

保留字段

6.4 invoke

函数指针,即block中具体需要执行的内容。

6.5 descriptor

这里我们在看下其中的 Block_descriptor

这里就和我们之前输出的内容对应上了:

<__NSMallocBlock__: 0x2823a83f0>
 signature: "v8@?0"
 invoke   : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__copy_helper_block_e8_32r)
 dispose  : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/Block探索.app/Block探索`__destroy_helper_block_e8_32r)

虽然 Block_layout 的内存结构中只有 Block_descriptor_1 ,但是通过解读源码,可以发现, Block_descriptor_2 和 Block_descriptor_3 的读取是通过内存平移实现的,具体有哪些数据可读则是根据 标识位 来判别。

Block_descriptor_1

基础数据,一定有的

Block_descriptor_2

COPY 相关字段,有 BLOCK_HAS_COPY_DISPOSE 标志位时有。

Block_descriptor_3

签名相关数据,有 BLOCK_HAS_SIGNATURE 标志位时有。

七、 block 签名

上面的 Block_descriptor_3 中咱们提到了签名。如上面例子输出的 signature: "v8@?0" 很眼熟,它其实是 Type Encodings 类型编码,我们在 属性 中有遇到过。

  • v8@?0

    • v 表示返回值为空

    • 8 表示参数的总大小

    • @? 表示block,

    • 0 表示从0号字节开始

具体可以对照官方文档

NSMethodSignature

我们也可以通过 NSMethodSignature 来输出详细它的代表信息。

(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]
<NSMethodSignature: 0xac5982543c221e27>
    number of arguments = 1
    frame size = 224
    is special struct return? NO
    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: -------- -------- -------- --------
        type encoding (@) '@?'
        flags {isObject, isBlock} // 是个对象,也是一个 block
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}

八、 __block 捕获变量的生命周期

我们再来研究下捕获变量的原理,我们继续通过汇编探索。

这里会调用到 _Block_object_assign

我们找到相关源码:

8.1 捕获变量类型

// Values for _Block_object_assign() and _Block_object_dispose() parameters
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
    /// 
    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.
};

8.2 _Block_object_assign

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
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 的实现:
        static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

        _Block_retain_object_default 的实现:
        static void _Block_retain_object_default(const void *ptr __unused) { }

        其实什么都没做
        */
        _Block_retain_object(object);

        // 指针拷贝,引用计数 +1
        *dest = object;
        break;

        // 二、 捕获的是 block
      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        /**
        前文有关于 _Block_copy 的讲解么这里就不多说了
        */
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
      // 三、 捕获的是 __block / __weak __block 修饰的变量
        /*******
         // 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;
        
      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;

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

8.3 _Block_byref_copy

__Block 捕获外界变量的操作 内存拷贝 等

static struct Block_byref *_Block_byref_copy(const void *arg) {
    // 使用 Block_byref 结构转换一下
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // 如果是通过引用计数管理的对象,就进行 malloc ,创建一个 copy
        // 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
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;

        /**
        统一 forwarding 指向 copy
        所以 __block 修饰的指向的是同一片内存空间
        */ 
        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
            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;
            }

            /**
            捕获到了外部变量
            调用 byref_keep 
            内存处理
            被捕获变量生命周期的保存
            */
            (*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;
}

这里我们遇到了用来包装捕获变量的 Block_byref 结构体,还有个一个 byref_keep 与生命周期相关的函数调用,但是他们的来源并没有什么线索,我们只能继续从 C++ 中寻找一些线索。

8.4 Block_byref

其中除了 Block_byref 还有 Block_byref_2 , Block_byref_3 。他们和前面的 Block_descriptor_1 一样,也是通过标志位区分,通过内存平移进行读写的

// __Block 修饰的结构体
struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

// __Block 修饰的结构体 byref_keep 和 byref_destroy 函数 - 来处理里面持有对象的保持和销毁
struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

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

8.5 通过 C++ 理解 Block_byref 结构

位了更深入的理解 Block_byref 的结构,我们通过还原 C++ 找一下线索:

__block NSNumber *num = @100;
void (^block)(void) = ^{
    num = @101;
    NSLog(@"%@", num);
};
block();
/**
对应的 Block_byref 类型结构体
*/
struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSNumber *num;
};

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

...

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        // Block_byref 构造函数
        __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {
            /**
            Block_byref 的 
            void *isa;
            */
            (void*)0, 
            /**
            Block_byref 的 
            struct Block_byref *forwarding;
            */
            (__Block_byref_num_0 *)&num, 
            /**
            Block_byref 的 
            volatile int32_t flags;
            */ 
            33554432, 
            /**
            Block_byref 的 
            uint32_t size;
            */
            sizeof(__Block_byref_num_0), 
            /**
            Block_byref_2 的
            BlockByrefKeepFunction byref_keep;
            */ __Block_byref_id_object_copy
            __Block_byref_id_object_copy_131, 
            /**
            Block_byref_2 的
            BlockByrefDestroyFunction byref_destroy;
            */
            __Block_byref_id_object_dispose_131, 
            // *num
            ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 100)
            };
            
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}

byref_keep

再次调用 _Block_object_assign (详见 8.2),这时传入的 flag 为 131 ,二进制为 1000 0011,而 BLOCK_FIELD_IS_OBJECT = 3 ,所以会执行 BLOCK_FIELD_IS_OBJECT 相关的分支,进行 拷贝 与 引用计数 + 1 。

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

byref_destroy

调用 _Block_object_dispose 进行销毁

static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

九、 补充:面试题一解析

这里是 1 应该都没什么疑问

NSObject *obj = [NSObject new];
NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));

这里为什么是 3 呢?

void (^blockA)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();
  • 这是引用了一个局部变量

    • 内部创建了一个结构体对 obj 进行了 引用 ,引用计数+1

  • 这个 block 被 强引用 ,它是一个 堆block

    • 从上面的源码学习中我们得知, block 的类型是运行时确定的

    • 而在这个过程中,进行了 _Block_copy,引用计数 +1

  • 所以这里打印出了 3

为什么是 4

void (^ __weak blockB)(void) = ^{
    NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();
  • 这是引用了一个局部变量

    • 内部创建了一个结构体对 obj 进行了 引用 ,引用计数+1

  • 这个 block 被 弱引用 ,它是一个 栈block,引用计数不变

为什么是5

void (^blockC)(void) = [blockB copy];
blockC();
  • 将 栈blockB 进行了 copy,引用计数 +1

总结

__block 捕获变量进行了三重拷贝:

  • 变量A 被 __block 修饰,就会被 blockB 从 栈 上 拷贝 到 堆 上

    • _Block_copy

  • blockB 捕获 Block_byref C 并 拷贝

    • _Block_object_assign - _Block_byref_copy

  • Block_byref C 对捕获的 变量A 进行拷贝

    • _Block_object_assign - byref_keep

参考

  • libclosure

1
4
2
3
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
35
36
37
38
39
40