我的应用程序开始出现问题,我追踪到一些非常简单的代码,我已经隔离并确认这些代码即使在最简单的情况下也会使我的应用程序崩溃:
var dictionary: [String: String]? = [:]
override func viewDidLoad() {
super.viewDidLoad()
dictionary?["key"] = dictionary?["key"] ?? "test"
}
看起来很无辜,但是如果您在没有其他代码的独立应用程序中运行此代码,您会在运行时收到此错误:
线程1:同时访问0x10ce01360,但修改需要独占访问
我已经确认,如果你将它分成两行,它就可以正常工作:
let currentValue = dictionary?["key"]
dictionary?["key"] = currentValue ?? "test"
但是链接似乎足够基本,我很惊讶如此简单的代码会导致运行时崩溃。这是一个错误,还是运行时无法处理这个问题的真正原因?
这与内存安全有关。基本原则是读和写相互重叠是不安全的,因此 Swift 通过崩溃来阻止你做不安全的事情。从内存位置读取数据的同时向内存位置写入数据是不安全的,而这正是您的代码所做的。
dictionary?["key"] = ...
是对 dictionary
的写访问权限。访问首先检查 dictionary
是否为 nil,如果不为 nil,则计算右侧的表达式,并调用下标 setter。只有在那之后写访问才完成。恰好“右边的表达式”也包含对 dictionary
的读取访问,所以 Swift 崩溃了。
相反,如果字典不是可选的,则执行
dictionary["key"] = dictionary["key"] ?? "value"
很好,因为在计算右侧表达式后可以开始写访问。
您还可以选择始终通过将右侧的表达式复制到局部变量(这是不同的内存位置)来求值,因此访问它不会与写入
dictionary
重叠。
您可以在 Swift 指南的内存安全部分看到更多重叠访问的示例。
很多时候这些重叠访问可以在编译时检测到,但在这种特殊情况下编译器似乎不够智能。运行时还通过在每次访问之前和之后插入
swift_beginAccess
和 swift_endAccess
调用来检查重叠访问。你可以在Exclusivity.cpp中看到这个系统是如何实现的。
简而言之,这是设计使然 - 这是一项安全功能,可防止您以未定义的方式写入内存。