Swift 初阶 —— inout 参数 & 数据独占问题

一、前言

我们都知道, 在 swift 里 inout 参数是用来修改实参的. 但其实 inout 参数的使用是要遵循"数据独占性"的, 要不然编译器会报错或警告.

inout 参数实际上只把对象的地址传了进来.

二、数据独占性

1、什么是数据独占

简单来说, "数据独占" 就是在单线程环境下, 当你往一块连续的内存写东西的同时, 不能读这块内存且不能同时往这块内存写其他东西.

2、为什么只有值类型才会有数据独占, 引用类型没有

这就涉及到 Swift 的设计哲学了. 因为一个引用类型的对象是一个指针而已, 且这个指针指向的实例是在堆上创建的, 所以这个堆上的实例是允许同时被多个指针指着的, 所以这个堆上的实例天生被设计出来就是为了共享的, 因此就引用类型没有数据独占这一说了.

但一个值类型的对象就是一块在栈上的连续内存, 不是指针. 所以值类型对象天生的设计就是不被共享的, 所以 Swift 才会在传 inout 参数时对值类型的对象实施数据独占.

3、访问重叠

3.1. 什么是访问重叠

访问重叠就是给多个 inout 参数传同一块连续内存. 这样做会导致 Swift 认为你在这个函数里同时对这块连续内存多次写入, 或边写边读, 于是 Swift 就认为你在破坏数据独占.

3.2. 访问重叠经典例子

3.2.1. 向多个 inout 参数传同一块连续内存

a. 同一个变量

b. 同一个数组(因为数组是内存连续的)

c. 同一个 struct 对象
图1 图 1 报错原因:

因为 state 传的是整块 appState 的内存, 而 max 传的是 appState 里的 limit 那块内存, 这两块内存是重叠了的, 所以 Swift 会怀疑你会对重叠的那部份内存边读边写, 于是就会报访问重叠 (overlapping accesses) 的错误.
图2 图 2 没报错原因:

这其实是 Swift6 的优化. appState 的 counter 和 appState 的 limit 这两块内存是不重叠的, 因此把这两块内存传到 value 和 max, 不会引起访问重叠的错误. 但这只是对 struct 的优化, 并不是对所有值类型对象的优化.

3.2.2. mutating 方法里使用逃逸闭包, 并在逃逸闭包里向 inout 参数写入

图 3 图 3 报错原因:

  1. inout 参数实际上只把对象的地址传了进来.
  2. mutating 方法实际会把 self 以 inout 的形式传进来, 因此 mutating 方法会对 self 要有数据独占性. 而如果如图 3 所示在逃逸闭包里修改 self, Swift 会认为你会在调用其他 mutating 方法时执行这个闭包, 那么对其他 mutating 方法而言就做不到 "数据独占" 了 (因为其他 mutating 函数里可能会出现同时向同一块内存写入的情况).
    图 4 图 4 为正确解决方案:

当我们把 self 声明在捕获列表里后, swift 实际上会把 self 拷贝出一个只读的副本, 然后再让闭包捕获这个不可变的副本. 所以闭包读到的 self 和 mutating 捕获到的 self 就不是同一块内存了, 就保证了 mutating 函数的 self 的数据独占.

三、总结

所以综上所述, swift 对 inout 参数的规定就是: inout 参数只能在当前函数的作用域内访问, 同时不能访问重叠. 除此之外, mutating 函数、setter、didSet & willSet 实际上都会把 self 以 inout 的方式传进来, 因此不能再这些函数里建一个逃逸闭包, 然后再在这个逃逸闭包里向 self 写值.

相关推荐
꧁Q༒ོγ꧂12 分钟前
算法详解(三)--递归与分治
开发语言·c++·算法·排序算法
桃子叔叔15 分钟前
基于SWIFT框架的预训练微调和推理实战指南之完整实战项目
大模型·swift
ganshenml18 分钟前
【Android】 开发四角版本全解析:AS、AGP、Gradle 与 JDK 的配套关系
android·java·开发语言
我命由我1234518 分钟前
Kotlin 运算符 - == 运算符与 === 运算符
android·java·开发语言·java-ee·kotlin·android studio·android-studio
少云清20 分钟前
【接口测试】3_Dubbo接口 _Telnet或python远程调用Dubbo接口
开发语言·python·dubbo·接口测试
菜的不敢吱声20 分钟前
swift学习第5天
学习·ssh·swift
盒子691023 分钟前
【golang】替换 ioutil.ReadAll 为 io.ReadAll 性能会下降吗
开发语言·后端·golang
alonewolf_9928 分钟前
Java类加载机制深度解析:从双亲委派到热加载实战
java·开发语言
无限进步_1 小时前
【数据结构&C语言】对称二叉树的递归之美:镜像世界的探索
c语言·开发语言·数据结构·c++·算法·github·visual studio
CSDN_RTKLIB1 小时前
C++取模与取余
开发语言·c++