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

2024-03-29

我真的很困惑地发现以下代码根本拒绝因典型的“解包可选值时意外发现 nil” 强制展开所期望的异常bar.

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

var foo = Foo()

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

它似乎dynamicType is somehow能够回退到定义的类型bar- 没有崩溃。

请注意,这似乎仅在以下情况下发生:Bar被定义为value类型(如@dfri 说 https://stackoverflow.com/questions/36824048/why-does-using-dynamictype-on-a-force-unwrapped-optional-struct-not-crash#comment61221761_36824048), Foo is a struct or final class (as @MartinR 指出 https://stackoverflow.com/questions/36824048/why-does-using-dynamictype-on-a-force-unwrapped-optional-struct-not-crash#comment61219783_36824048) & foo是可变的。

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

我倾向于认为这是一个奇怪的错误,但希望得到一些确认。

这是一个错误还是一个功能dynamicType,或者我只是在这里遗漏了一些东西?

(Using Xcode 7.3 w/ Swift 2.2)


斯威夫特 4.0.3 更新

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

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

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


This is 确实是一个错误 https://bugs.swift.org/browse/SR-1327,已修复通过这个拉取请求 https://github.com/apple/swift/pull/14410,这应该会进入 Swift 4.2 的版本,一切进展顺利。


如果有人对重现它的看似奇怪的要求感兴趣,这里有一个(不是真正的)简要概述正在发生的事情......

调用标准库函数type(of:)由类型检查器作为特殊情况解决;它们在 AST 中被特殊的“动态类型表达式”替换。我没有研究它的前身如何dynamicType已处理,但我怀疑它做了类似的事情。

当为这样的表达式发出中间表示(具体来说是 SIL)时,编译器检查以查看 https://github.com/apple/swift/blob/34e77c0f258a989fd800d4b471d136fc11151558/lib/SILGen/SILGenExpr.cpp#L2846如果生成的元类型是“厚”(对于类和协议类型实例),如果是,则发出子表达式(即传递的参数)并获取其动态类型。

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

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

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

问题是physical组件被错误地认为是无副作用的;然而,力展开是一个物理组成部分,并且是not无副作用(键路径表达式也是非纯物理组件)。

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

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

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

Here, 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!

Here, foo is左值,但编译器认为生成的元类型是“厚”的,因此取决于foo!,因此加载左值,并因此评估强制展开。

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

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

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

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

那么,当Foo是非最终的,编译器不能只引用该属性bar通过地址,因为它可能被子类覆盖,子类很可能将其重新实现为计算属性。因此,左值将包含一个logical组件(调用bar的 getter),因此编译器将执行加载以确保评估此 getter 调用的潜在副作用(并且强制解包也将在加载中评估)。

然而当Foo is final, 属性访问bar可以建模为物理组件,即可以通过地址引用。因此,编译器错误地认为,由于所有左值的组件都是物理的,因此它可能会跳过对其求值。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么在强制解包的 nil 可选值类型上使用dynamicType 有效? 的相关文章

  • iOS:同时使用 Facebook 和 Google、Google Plus 登录

    早上好 我正在尝试在同一视图中实现 Facebook 登录 工作正常 以及 google plus 登录 我正在遵循官方网站 Google 的指南 但 Facebook 按钮和 google plus 按钮之间存在问题 Facebook 告
  • Swift 2.0 中的 countForFetchRequest

    我正在尝试使用countForFetchRequestSwift 2 0 中托管对象上下文上的方法 我注意到错误处理executeFetchRequest已更改为新的do try catch syntax func executeFetch
  • 是否可以从 NSFetchedResultsController 获取最新快照

    从 iOS 13 开始 保持UITableView与一个同步NSFetchedResultsController好像是有快照的 每当 ManagedObjectContext 报告添加 删除或更新时 NSFetchedResultsCont
  • 当 tableView 向下滑动时显示 UISearchController

    我通过 UISearchController 在我的测试应用程序中实现了搜索栏 当我启动应用程序时 我会在导航控制器下方看到搜索栏 但如何在应用程序启动时隐藏它并仅在下拉表格视图时显示它 并在拉出表格视图时再次隐藏 我在google或you
  • 如何使用 Parse 和 Swift 从 Facebook 注销或撤销登录

    我正在尝试将我的用户完全从我的应用程序中注销 使用时 PFUser logout 我成功地将用户从 Parse 中注销 但是 当我返回应用程序并单击登录按钮时 我被重定向到 Facebook 屏幕 显示 您已经授权 应用程序名称 所以我永远
  • 如何使用 Apple Map Kit 实现地址自动完成

    我想自动填写用户的地址 与 google api 在此链接中提供的地址相同 https developers google com maps documentation javascript places autocomplete hl e
  • textFieldDidChangeSelection:在视图更新期间修改状态,这将导致未定义的行为

    这是我的代码 struct CustomTextField UIViewRepresentable var placeholder String Binding var text String func makeUIView context
  • 在 RealityKit 中更改对象的枢轴点

    我希望立方体仅在 z 轴正方向上缩放 现在 当我缩放它时 它总是围绕其中心缩放 因此 为此我必须更改对象的锚点 我知道在 SceneKit 中有一个可以使用的枢轴属性 在 RealityKit 中也有办法做到这一点吗 我发现了一个相当简单的
  • iOS UITest:如何找到UITableViewCell的AccessoryView?

    你好我正在学习UITests now 我有个问题 如何检测accessoryView的点击tableViewCell 在UI测试中 下面是我的tableViewCell 我想要检测细节闭合配件视图水龙头 像这样 app tables cel
  • Swift 2.0 中的协议扩展方法调度

    我面临有关协议方法调度的问题 我有一个类层次结构 如下所示 protocol E func test extension E func test print jello class A E class B A func test print
  • 为什么 'self.self' 在 swift 中编译并运行?

    昨天我回顾了 Swift 中的一段代码 其中包括这一行 self self someProperty 这让我很惊讶 因为这个词self被保留并用作对当前实例的引用 起初我用其他语言检查了这种现象 但都给出了错误 这并不奇怪 但是 为什么它能
  • AFNetworking 的 setImageWithURLRequest 在滚动后在错误的单元格中设置图像(iOS、Swift)

    我使用表dequeueReusableCellWithIdentifier and afnetworking uiimageview 我的一些细胞有图像 有些则没有 如果我在加载图像之前滚动表格 成功块会将图像放入重复使用的错误单元格中 例
  • 图表无法在 Xcode 14 上编译

    我在图书馆中面临以下错误 Type 图表数据集 不符合协议 范围可替换集合 实例方法不可用 替换Subrange with 用于满足协议的要求 范围可替换集合 将其附加到扩展 ChartDataSet RangeReplaceableCol
  • Swift 将十进制坐标转换为度、分、秒、方向

    我怎样才能将其转换为快速 我最好的猜测是所有 int 都变成了 var 删除所有导致 的 此外 如果有的话可以给我指出一个很好的来源来了解事物如何转换 那就太好了 NSString coordinateString int latSecon
  • Swift 生成器上的链式表达式错误

    迅速回复 zip 1 2 3 7 8 9 generate next repl swift 1 22 error value of type Zip2Generator
  • iOS 8 UITableView 分隔符插入 0 不起作用

    我有一个应用程序 其中UITableView的分隔符插入设置为自定义值 右0 Left 0 这完美地适用于iOS 7 x 但是在iOS 8 0我看到分隔符插入设置为默认值15在右侧 即使在 xib 文件中它设置为0 它仍然显示不正确 我该如
  • 如何将计算值转换为文字以进行枚举初始化

    我遇到了枚举的问题 因为我想将 case 初始化为双精度值PI 180 有没有办法通过常量或一些时髦的魔法获取这个计算值并将其转换为文字 以便我可以初始化枚举 我宁愿不必做3 14 我宁愿使用该值的实际编译器和硬件计算表示 所以我的第一次尝
  • 平滑地将渐变应用于 UIImage

    我正在尝试使用 CoreGraphic 将渐变应用于 UIImage 然而 我得到的结果不是很好 我想在图像底部创建一个黑色到透明的渐变 以便为我放置一些文本创建对比度 然而 我能够实现的渐变与图像不能很好地融合 你可以清楚地看到中心的分离
  • 将对象映射到 TableView 部分的 Swift 二维数组

    我想不出更好的方法来做到这一点 我将学生对象的所有属性映射到二维数组中 所以我的电视有几个部分 我也不能使用静态表视图 如果是这样 这个问题就不会存在 所以我在 TVC 中的代码 let currentUser PFUser current
  • 如何在 Swift 中解析蓝牙设备发送的浮点数?

    在我的 iOS 应用程序上 我需要解码蓝牙接收到的 Float 值 并从不同的设备 不是 iOS 获取 4 个字节 因此我需要一个 便携式 4 字节 Float 格式 目前发件人正在使用以下格式 数据编码 0xCCBBAAAEE 0xEE

随机推荐