Swift内存访问冲突

内存的访问,发生在给变量赋值的时候,或者传递值(给函数)的时候,例如

var one = 1//向one的内存区域发起一次写的操作
print("\(one)")//向one的内存区域发起一次读的操作

在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。读和写访问的区别很明显:一个写访问会改变存储地址,而读操作不会。存储地址是指向正在访问的东西(例如一个变量,常量或者属性)的位置的值 。内存访问的时长要么是瞬时的,要么是长期的。如果一个访问不可能在其访问期间被其它代码访问,那么就是一个瞬时访问。正常来说,两个瞬时访问是不可能同时发生的。大多数内存访问都是瞬时的。例如,下面列举的所有读和写访问都是瞬时的:

func oneMore(than number: Int) -> Int {
    return number + 1
}

var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// 打印“2”

然而,有几种被称为长期访问的内存访问方式,会在别的代码执行时持续进行。瞬时访问和长期访问的区别在于别的代码有没有可能在访问期间同时访问,也就是在时间线上的重叠。一个长期访问可以被别的长期访问或瞬时访问重叠。如果发生了重叠访问,就可能会造成内存冲突,因为在同一块内存区域同时发生读和写的操作是肯定不被允许的。

重叠的访问主要出现在使用 in-out 参数的函数和方法或者结构体的 mutating 方法里。

In-Out 参数的访问冲突

一个函数会对它所有的 in-out 参数保持长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。

这种长期保持的写访问带来的问题是,你不能再访问以 in-out 形式传入的原始变量,即使作用域原则和访问权限允许——任何访问原始变量的行为都会造成冲突。例如:

var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize
    print(number)//不会产生读访问
}

increment(&stepSize)
// 错误:stepSize 访问冲突

代码中stepSize是一个全局变量,并且我们以inout的形式将该变量传入了increment函数当中,这里我们得知道的是,一个函数会对它所有的 in-out 参数保持长期写访问,inout参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。这种方法的带来的弊端就是,你不能在访问以inout形式传入的原始变量(我都得到地址了,我还访问原始干嘛,对吧),即使作用域原则和访问权限允许——任何访问原始变量的行为都会造成冲突。上述代码中,由于inout带来的长期写访问,而我们也对stepSize进行了读访问。所以就会造成内存冲突。(当然,同一函数传入两个inout相同的数据,也会造成写访问冲突)但是为什么print(number)不回发生内存冲突呢?实际上,将inout传入进去时,swift发生了以下操作

	1.	写访问发生在调用期间:
	•	变量 stepSize 被作为 inout 参数传递给 increment。
	•	Swift 会暂时暂停对原始变量的所有访问,并将其访问权转移给函数的 inout 参数 number。
	2.	对原变量的隔离:
	•	在函数调用期间,stepSize 被隔离,外部无法访问。
	•	任何对 number 的读写操作都会作用于 stepSize 的值,但此时只通过 number 访问变量。
	3.	生命周期的非交叠:
	•	函数调用开始时,stepSize 的值被写入到 number 的内存。
	•	在函数调用结束时,number 的值被写回到 stepSize。
	•	在调用期间,stepSize 不再直接可用,因此不存在交叠访问。

解决冲突的方式很多,这里举例一种很鸡肋的方式:

// 显式拷贝
var copyOfStepSize = stepSize
increment(&copyOfStepSize)

// 更新原来的值
stepSize = copyOfStepSize
// stepSize 现在的值是 2

方法里self带来访问冲突

废话少说,上图

struct Player {
    var name: String
    var health: Int
    var energy: Int
    
    static let maxHealth = 10
    mutating func restoreHealth() {
        health = Player.maxHealth
    }
}

extension Player {
    mutating func shareHealth(with teammate: inout Player) {
        balance(&teammate.health, &health)
    }
}

var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria)  // 正常
oscar.shareHealth(with: &Oscar)  // 错误:oscar 访问冲突

在上述代码中我们在plyer结构题方法中对restoreHealth使用mutating访问权限,这样就会一直有对self的写访问,而在oscar.shareHealth我们传入了oscar,shareHealth中的inout又对Player进行的写访问,就造成了两个写访问冲突。所以会造成内存

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // 错误

崩溃。有人疑惑为什么上面的restoreHealth()中不会发生内存冲突呢?

我们可以看到,maxHealth我们是是用static静态修饰的,他属于类型本身,而不属于任何实例,他储单独存储在静态区域。所以访问自然与实例无关了。

属性的访问冲突

如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问一整个值。例如,元组元素的写访问重叠会产生冲突:

var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// 错误:playerInformation 的属性访问冲突

在结构体中,下面展示了对于一个存储在全局变量里的结构体属性的写访问重叠了。

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // 错误

但是对于本地变量,编译器就可以,编译器就可以保证重叠访问是安全的(不懂但尊重)。

func someFunction() {
    var oscar = Player(name: "Oscar", health: 10, energy: 10)
    balance(&oscar.health, &oscar.energy)  // 正常
}

以下就是一些规则,它可以保证结构体属性的重叠访问是安全的:

·你访问的是实例的存储属性,而不是计算属性或类的属性

·结构体是本地变量的值,而非全局变量

·结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/922668.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入解析 EasyExcel 组件原理与应用

✨深入解析 EasyExcel 组件原理与应用✨ 官方:EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网 在日常的 Java 开发工作中,处理 Excel 文件的导入导出是极为常见的需求。 今天,咱们就一起来深入了解一款非常实用的操作 Exce…

基于Java Springboot高校教室资源管理系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 数据库:…

k8s1.31版本最新版本集群使用容器镜像仓库Harbor

虚拟机 rocky9.4 linux master node01 node02 已部署k8s集群版本 1.31 方法 一 使用容器部署harbor (1) wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo yum -y install docker-ce systemctl enable docker…

C语言数据结构学习:循环队列

C语言 数据结构学习 汇总入口: C语言数据结构学习:[汇总] 1. 循环队列 队列的博客:C语言数据结构学习:队列 循环队列会预先定义最大队列空间,然后定义一个数组,通过队列头和队列尾指针分别指向开头和结尾&…

Vue——响应式数据,v-on,v-bind,v-if,v-for(内含项目实战)

目录 响应式数据 ref reactive 事件绑定指令 v-on v-on 鼠标监听事件 v-on 键盘监听事件 v-on 简写形式 属性动态化指令 v-bind iuput标签动态属性绑定 img标签动态属性绑定 b标签动态属性绑定 v-bind 简写形式 条件渲染指令 v-if 遍历指令 v-for 遍历对象的值 遍历…

小米note pro一代(leo)线刷、twrp、magisk、TODO: android源码编译

本文主要说android5 整体思路 android 5.1 twrp magisk Zygisk(Riru) Dreamland(xposed) Riru不支持android5.1, 因此只能选择Zygisk : 如果你正在使用 Android 5,你必须使用 Zygisk 因为 Riru 并不支持 Android 5. 基于magisk之上的xposed 其中提到的 作者…

自然语言处理: RAG优化之Embedding模型选型重要依据:mteb/leaderboard榜

本人项目地址大全:Victor94-king/NLP__ManVictor: CSDN of ManVictor git地址:https://github.com/opendatalab/MinerU 写在前面: 笔者更新不易,希望走过路过点个关注和赞,笔芯!!! 写在前面: 笔者更新不易,希望走过路…

Redis 常用数据类型插入性能对比:循环插入 vs. 批量插入

Redis 是一款高性能的键值数据库,其支持多种数据类型(String、Hash、List、Set、ZSet、Geo)。在开发中,经常会遇到需要插入大量数据的场景。如果逐条插入,性能会显得较低,而采用 Pipeline 批量插入 能大幅提…

oneplus6线刷、trwp、magisk(apatch)、LSPosed、Shamiko、Hide My Applist

oneplus6线刷android10.0.1 oneplus6线刷包(官方android10.0.1)下载、线刷教程: OnePlus6-brick-enchilada_22_K_52_210716_repack-HOS-10_0_11-zip 启用开发者模式 设置 / 连续点击6次版本号 : 启用开发者模式设置/开发者模式/{打开 usb调试, 打开 网络adb调试,…

node.js中使用express.static()托管静态资源

express.static()定义 express.static(root, [options])是一个中间件函数,负责为Express应用提供静态资源服务。它允许你指定一个或多个目录作为静态资源的根目录,当客户端请求这些资源时,Express会查找并返回对应的文件。 安装express npm i…

【含开题报告+文档+PPT+源码】基于SSM的社区老人服务系统设计与实现

开题报告 在当前人口老龄化趋势明显以及信息化社会发展背景下,基于 SSM 框架构建的社区老人服务系统具有深远的背景意义。首先,它响应了我国老龄化进程加快所带来的多元化、个性化养老服务需求,利用互联网技术为老年人提供便捷高效的在线申请…

Spring AI 框架使用的核心概念

一、模型(Model) AI 模型是旨在处理和生成信息的算法,通常模仿人类的认知功能。通过从大型数据集中学习模式和见解,这些模型可以做出预测、文本、图像或其他输出,从而增强各个行业的各种应用。 AI 模型有很多种&…

学习与理解LabVIEW中多列列表框项名和项首字符串属性

多列列表框控件在如下的位置: 可以对该控件右击,如下位置,即可设置该控件的显示项: 垂直线和水平线指的是上图中组成单元格的竖线和横线(不包括行首列首) 现在介绍该多列列表框的两个属性,分别…

(Keil)MDK-ARM各种优化选项详细说明、实际应用及拓展内容

参考 MDK-ARM各种优化选项详细说明、实际应用及拓展内容 本文围绕MDK-ARM优化选项,以及相关拓展知识(微库、实际应用、调试)进行讲述,希望对你今后开发项目有所帮助。 1 总述 我们所指的优化,主要两方面: 1.代码大小(Size) 2.代码性能(运行时间) 在MDK-ARM中,优…

实时数据开发 | 怎么通俗理解Flink容错机制,提到的checkpoint、barrier、Savepoint、sink都是什么

今天学Flink的关键技术–容错机制,用一些通俗的比喻来讲这个复杂的过程。参考自《离线和实时大数据开发实战》 需要先回顾昨天发的Flink关键概念 检查点(checkpoint) Flink容错机制的核心是分布式数据流和状态的快照,从而当分布…

[译]Elasticsearch Sequence ID实现思路及用途

原文地址:https://www.elastic.co/blog/elasticsearch-sequence-ids-6-0 如果 几年前,在Elastic,我们问自己一个"如果"问题,我们知道这将带来有趣的见解: "如果我们在Elasticsearch中对索引操作进行全面排序会怎样…

七、SElinux

一、SElinux简介 SELinux是Security-Enhanced Linux的缩写,意思是安全强化的linuxSELinux 主要由美国国家安全局(NSA)开发,当初开发的目的是为了避免资源的误用传统的访问控制在我们开启权限后,系统进程可以直接访问当我们对权限设置不严谨时…

鸿蒙开发-音视频

Media Kit 特点 一般场合的音视频处理,可以直接使用系统集成的Video组件,不过外观和功能自定义程度低Media kit:轻量媒体引擎,系统资源占用低支持音视频播放/录制,pipeline灵活拼装,插件化扩展source/demu…

小程序25- iconfont 字体图标的使用

项目中使用到图标,一般由公司设计进行设计,设计好后上传到阿里巴巴矢量图标库 日常开发过程中,也可以通过 iconfont 图标库下载使用自带的图标 补充:使用 iconfont 图标库报错:Failed to load font 操作步骤&#xff…

vulhub之fastjson

fastjson 1.2.24 反序列化 RCE 漏洞(CVE-2017-18349) 漏洞简介 什么是json json全称是JavaScript object notation。即JavaScript对象标记法,使用键值对进行信息的存储。举个简单的例子如下: {"name":"BossFrank", "age":23, "isDevel…