令人迷惑的Swift行为

2020-09-07  本文已影响0人  行知路

一、引言

        所有的程序员都知道,多线程可能导致程序发生逻辑错误。此类问题的一般原因是,对相同内存的访问导致脏数据。现在,语言、工具、库等的不断改善,大幅降低多线程导致的问题的可能性。如,在OC当中,GCD的出现,大幅降低了多线程操作的难度。但是,多线程问题依然会不时发生。
        虽然,这类问题不时发生,也不一定好排查、修改。但是,从理论角度上来说,我们对此种问题原理、本质是理解比较清楚的。
        但是,在Swift之中存在类似的问题,此类问题令人迷惑的行为。这种行为与上面所说的多线程导致脏数据的问题结果相同,但是又不像多线程问题那样让人明晰其背后的原理。

二、废多看码

// 这是一个函数,其有两个输入输出参数
// 此函数的功能也较简单:把输入参数求和后,把参数修改为和的一半
// 如果不明白inout是什么意思,请自行百度,或者查看笔者的相关swift语法文章
func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}

// 这里定义了一个元组
var playerInformation = (health: 10, energy: 20)

// 期望此行代码执行之后结果是
// playerInformation.health=15
// playerInformation.energy=15
balance(&playerInformation.health, &playerInformation.energy)

        请诸位读者先不要往下看,此时以自己的理解,来看看上面的代码是否有问题。

        以笔者对C、C++、OC等语言的理解,以及对Swift的理解,认为以上代码是没有什么问题的。但是,世事难料,调用balance函数的那一行语句居然导致运行时崩溃!!!


i报错截图

\color{#FF0000}{按照我们的理解,上述代码不会出错,也不应该出错!但,它就是出错了!}
\color{#FF0000}{------是不是让人很迷惑!!!------}

三、追根溯源

        上述代码抛出了一个令人迷惑的Swift行为。此问题是由Swift对于内存的访问控制策略导致的。

3.1 Swift内存访问控制

3.1.1 问题发生的前提

这里的同时,不是指多线程的同时(此种同时已经为大家所熟知),而是指单线程!

3.1.2 问题发生的表现

3.1.2 内存问题出现的场景

        以上方法操作的是值类型(结构体、元组)。


非值类型不会出现问题 值类型出现问题

对于值类型来说,访问其中的一个成员就会导致对整个实例的独占访问。

3.2 “废多看码”章节的解释

func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}

var playerInformation = (health: 10, energy: 20)

// 此语句输出playerInformation的地址
withUnsafePointer(to: &playerInformation) {ptr in print(ptr)}

balance(&playerInformation.health, &playerInformation.energy)

        对于上述代码,对balance函数的调用,我们进行分析。

也许从读者角度来看,两次对playerInformation的独占访问,不应该出问题,但是Swift的编译器不这么看。

3.3 未完待续

        看了以上内容之后,不知道读者是明白了呢,还是更糊涂了,还是有些明白了。笔者看了之后,对这方面有了一些了解,所以就写了这篇文章。虽然笔者知道这是Swift编译器在背后做的一些工作,但是依然不是很理解这么做的原理。
        除了上述例子外,其实还有更令人疑惑的一些内容。

从应用角度来看,以上的理解已经可以了,碰到问题时不以至于十分惊讶。但是对于编译器在背后捣的鬼,还是希望能够有深入理解。如果哪位读者理解了,请不吝赐教!

        如果读者想看更多的资料,可以查看内存安全

上一篇下一篇

猜你喜欢

热点阅读