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