我真的很困惑,发现下面的代码只是拒绝崩溃与典型的“意外发现nil同时解开一个可选值”异常,你期望从力展开bar
。
struct Foo {
var bar: Bar?
}
struct Bar {}
var foo = Foo()
debugPrint(foo.bar) // nil
debugPrint(foo.bar!.dynamicType) // _dynamicType.Bar
似乎dynamicType
能以某种方式回退到定义类型的bar
- 没有崩溃。
请注意,这只会在Bar
定义为值类型(如@dfri says)时出现,Foo
是struct
或final class
(如pointed out by @MartinR)和foo
是可变的。
我最初认为它可能只是一个编译器优化,因为bar
的类型在编译时是已知的,因此力展开可能已被优化 - 但当Bar
被定义为final class
时它也会崩溃。此外,如果我将“优化级别”设置为-Onone
,它仍然有效。
我倾向于认为这是一个奇怪的错误,但想要一些确认。
这是dynamicType
的错误或功能,还是我在这里遗漏了一些东西?
(使用Xcode 7.3 w / Swift 2.2)
在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特定)时,如果生成的元类型为“thick”(对于类和协议类型的实例),则为the compiler checks to see,如果是,则发出子表达式(即传递的参数)并获得其动态类型。
但是,如果生成的元类型为“thin”(对于结构和枚举),则编译器在编译时知道元类型值。因此,只有具有副作用的子表达式才需要进行评估。这样的表达式作为“被忽略的表达式”发出。
问题在于发出忽略表达式的逻辑,这些表达式也是左值(一个可以分配给inout
并作为let foo: String? = nil
print(type(of: foo!)) // crash!
传递的表达式)。
Swift左值可以由多个组件组成(例如,访问属性,执行强制解包等)。一些组件是“物理的”,这意味着它们产生了一个可以使用的地址,而其他组件是“逻辑的”,这意味着它们包含一个getter和setter(就像计算变量一样)。
问题是物理成分被错误地认为是无副作用的;但是,力展开是一个物理组件,并且不是副作用(键路径表达式也是非纯物理组件)。
因此,如果它们仅由物理组件组成,则使用强制解包组件忽略表达式左值将错误地不评估力展开。
让我们来看看目前崩溃的几个案例(在Swift 4.0.3中),并解释为什么这个bug是侧面步骤并且正确评估了force unwrap:
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
被标记为Foo
则不会崩溃。由此产生的元型都是“薄”,所以final
是Foo
有什么区别?
好吧,当bar
是非final时,编译器不能仅通过地址引用属性bar
,因为它可能被子类覆盖,这可能会将其重新实现为计算属性。因此,左值将包含一个逻辑组件(对Foo
的getter的调用),因此编译器将执行加载以确保评估此getter调用的潜在副作用(并且还将在加载中评估force unwrap)。
然而,当final
是bar
时,对foo.bar.dynamicType == Swift.Optional<Bar>
的属性访问可以被建模为物理组件,即它可以通过地址来引用。因此编译器错误地认为因为所有左值的组件都是物理的,所以它可能会跳过评估它。
无论如何,这个问题现在已经解决了。希望有人发现上面的漫游有用和/或有趣:)
因为dynamicType对类型而不是值进行操作。在运行时......
Bar
很自然地,当你解开可选项时,你会得到qazxswpoi