gpt4 book ai didi

swift - 为什么在强制解包的 nil 可选值类型上使用 dynamicType 有效?

转载 作者:IT王子 更新时间:2023-10-29 05:37:58 32 4
gpt4 key购买 nike

我真的很困惑地发现下面的代码只是拒绝与典型的“Unexpectedly found nil while unwrapping an Optional value”异常一起崩溃,这是你期望从强制展开 bar

struct Foo {
var bar: Bar?
}
struct Bar {}

var foo = Foo()

debugPrint(foo.bar) // nil
debugPrint(foo.bar!.dynamicType) // _dynamicType.Bar

dynamicType 似乎以某种方式 能够回退到 bar 的定义类型——而不会崩溃。

请注意,这似乎仅在 Bar 被定义为 value 类型(如 @dfri says )时发生,Foostructfinal class(如 pointed out by @MartinR )& foo 是可变的。

我最初确实认为它可能只是编译器优化,因为 bar 的类型在编译时是已知的,因此强制展开可以被优化掉——但它当 Bar 被定义为 final class 时也会崩溃。此外,如果我将“优化级别”设置为 -Onone,它仍然有效。

我倾向于认为这是一个奇怪的错误,但需要一些确认。

这是 dynamicType 的错误或功能,还是我只是在这里遗漏了什么?

(使用带有 Swift 2.2 的 Xcode 7.3)


Swift 4.0.3 更新

这在 Swift 4.0.3 中仍然可以重现(用一个更小的例子):

var foo: String?
print(type(of: foo!)) // String

这里我们使用 dynamicType 的继承者 type(of:) 来获取动态类型;和前面的例子一样,它不会崩溃。

最佳答案

这是 indeed a bug ,已修复by this pull request ,它应该会进入 Swift 4.2 版本,一切顺利。


如果有人对复制它的看似奇怪的要求感兴趣,这里有一个(不是真的)简要概述发生了什么......

调用标准库函数 type(of:) 被类型检查器解析为特殊情况;它们在 AST 中被一个特殊的“动态类型表达式”所取代。我没有调查它的前身 dynamicType 是如何处理的,但我怀疑它做了类似的事情。

当为这样的表达式发出中间表示(具体为 SIL)时,the compiler checks to see如果生成的元类型是“厚”的(对于类和协议(protocol)类型的实例),如果是,则发出子表达式(即传递的参数)并获取其动态类型。

但是,如果生成的元类型是“瘦”的(对于结构和枚举),编译器会在编译时知道元类型值。因此,仅当子表达式有副作用时才需要对其求值。这样的表达式作为“忽略的表达式”发出。

问题出在发出被忽略的表达式的逻辑上,这些表达式也是左值(一个可以分配给并作为 inout 传递的表达式)。

Swift 左值可以由多个组件组成(例如,访问属性、执行强制解包等)。一些组件是“物理的”,这意味着它们会产生一个要使用的地址,而其他组件是“逻辑的”,这意味着它们由 getter 和 setter 组成(就像计算变量一样)。

问题是物理组件被错误地认为是无副作用的;然而,强制展开是一个物理组件,不是没有副作用(键路径表达式也是一个非纯物理组件)。

因此,如果表达式左值仅由物理组件组成,则带有强制展开组件的忽略表达式左值将错误地不评估强制展开。

让我们看一下当前崩溃的几个案例(在 Swift 4.0.3 中),并解释为什么该错误被回避并且正确评估了强制解包:

let foo: String? = nil
print(type(of: foo!)) // crash!

这里,foo 不是左值(因为它被声明为 let),所以我们只是获取它的值并强制解包。

class C {} // also crashes if 'C' is 'final', the metatype is still "thick"
var foo: C? = nil
let x = type(of: foo!) // crash!

这里,foo 是一个左值,但编译器认为生成的元类型是“厚”的,因此取决于 foo 的值!,因此加载了左值,因此计算了强制解包。

我们也来看看这个有趣的案例:

class Foo {
var bar: Bar?
}
struct Bar {}

var foo = Foo()
print(type(of: foo.bar!)) // crash!

它会崩溃,但如果 Foo 被标记为 final 则不会。无论哪种方式,生成的元类型都是“瘦”的,那么 Foofinal 有什么区别呢?

嗯,当 Foo 是非 final 时,编译器不能仅通过地址引用属性 bar,因为它可能被子类覆盖,这很可能将其重新实现为计算属性。因此,左值将包含一个逻辑组件(对bar 的getter 的调用),因此编译器将执行加载以确保此getter 调用的潜在副作用被评估(力展开也将在负载中评估)。

然而,当 Foofinal 时,对 bar 的属性访问可以建模为物理组件,即它可以被引用地址。因此编译器错误地假设因为所有左值的组件都是物理的,所以它可以跳过对它的评估。

无论如何,这个问题现在已经解决了。希望有人觉得上面的漫谈有用和/或有趣:)

关于swift - 为什么在强制解包的 nil 可选值类型上使用 dynamicType 有效?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36824048/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com