Jam*_*ord 7 closures types casting tuples swift
我注意到,我可以将具有常规参数的闭包转换为将其参数包装在元组中的闭包。但是,只有当我使用一种特殊的铸造方法时才可以!
let myClosure = { (a: Int, b: Float) -> Void in
print(a, b)
}
// I want to convert the closure to be of this type (I won't go into why).
var myClosureWithTupleArgVar: (((Int, Float)) -> Void)? = nil
// This cast is possible.
myClosureWithTupleArgVar = (((Int, Float)) -> Void)?(myClosure)
myClosureWithTupleArgVar?((1, 2))
// This cast will always fail and return nil (as warned by the compiler).
myClosureWithTupleArgVar = myClosure as? (((Int, Float)) -> Void)
myClosureWithTupleArgVar?((3, 4))
Run Code Online (Sandbox Code Playgroud)
输出:
1 2.0
Run Code Online (Sandbox Code Playgroud)
为什么会这样呢?使用as强制转换和C样式函数调用强制转换有什么区别?
(我对as,as?和as!之间的区别不感兴趣!)
多余的括号是面对此警告的原因(尽管如此,它在操场上显示“ 1 2.0 3 4.0”的结果)。
首先,让我确认一下:
应该myClosureWithTupleArgVar?((1, 2))代替myClosureWithTupleArgVar?(1, 2)
以及myClosureWithTupleArgVar?((3, 4))代替myClosureWithTupleArgVar?(3, 4)
那是因为myClosureWithTupleArgVartype是(((Int, Float)) -> Void)?。
是有原因的,编译器识别出(Int, Float) -> Void的类型(myClosure)是不相同((Int, Float)) -> Void的类型(myClosureWithTupleArgVar)。此时,如果您尝试将代码编辑为:
let myClosure = { (a: Int, b: Float) -> Void in
print(a, b)
}
var myClosureWithTupleArgVar: ((Int, Float) -> Void)? = nil
// This cast is possible.
myClosureWithTupleArgVar = ((Int, Float) -> Void)?(myClosure)
myClosureWithTupleArgVar?(1, 2)
myClosureWithTupleArgVar = myClosure as? ((Int, Float) -> Void)
myClosureWithTupleArgVar?(3, 4)
Run Code Online (Sandbox Code Playgroud)
通过删除多余的括号(((Int, Float) -> Void)?而不是(((Int, Float)) -> Void)),您应该看到相反的警告!这是:
从'(Int,Float)-> Void'到'(Int,Float)-> Void'的条件转换始终成功
这意味着您甚至不必再提及as转换(它们现在具有完全相同的类型):
myClosureWithTupleArgVar = myClosure
Run Code Online (Sandbox Code Playgroud)
代替
myClosureWithTupleArgVar = myClosure as? ((Int, Float) -> Void)
Run Code Online (Sandbox Code Playgroud)
也:
myClosureWithTupleArgVar = myClosure
Run Code Online (Sandbox Code Playgroud)
代替:
myClosureWithTupleArgVar = ((Int, Float) -> Void)?(myClosure)
Run Code Online (Sandbox Code Playgroud)
请记住,这种情况不仅适用于铸造闭包。例:
let int1 = 100
var int2: Int? = nil
// unnecessary castings:
int2 = Int(int1) // nothing shown here, because of Int init: init(_ value: Int)
int2 = int1 as? Int // Conditional cast from 'Int' to 'Int' always succeeds
Run Code Online (Sandbox Code Playgroud)
C 风格强制转换基本上意味着 Swift 编译器将强制调用闭包,就好像它采用元(Int, Float)组作为参数一样,而 as / as? / 作为!cast 将首先对你的cast 进行一些健全性检查,以确保类型兼容等等。
由于编译器认为(在某些版本中,如其他答案的评论中所示) 和(Int, Float) -> ()相距((Int, Float)) -> ()太远而无法兼容,因此健全性检查将仅返回 nil,因此会阻止您的调用。
它的工作原理是,采用(Int, Float)元组的函数/闭包的行为与采用Int和Float参数的函数/闭包完全相同(在当前版本的 Swift 中)。
我将一段代码编译成程序集,从现在开始我将引用它。该片段可以在这里找到: https: //swift.godbolt.org/z/CaOb0s
出于可读性的目的,我在这里使用函数而不是实际的闭包。
我创建了两个与我们拥有的两种情况相对应的函数:
func twoParamFunc(a: Int, b: Float)-> Void {
print(a, b)
}
func singleParamFunc(tuple: (a: Int, b: Float))-> Void {
print(tuple.a, tuple.b)
}
Run Code Online (Sandbox Code Playgroud)
然后我尝试使用两种不同的方法来投射它们:
let cCastFunction = ((((Int, Float)) -> Void)?(twoParamFunc))!
let asCastFunction = (twoParamFunc as? (((Int, Float)) -> Void))!
Run Code Online (Sandbox Code Playgroud)
而当查看swift编译的汇编代码时,我们可以看到两者之间有很多差异。
当查看 C 风格的转换时,我们可以看到大部分代码基本上只是调用 alloc/retain/release 并移动指针和值。对外部代码的唯一调用是通过失败情况(!取消引用空引用),调用$ss18_fatalErrorMessage__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF
而在 swift 风格的演员中,有很多额外的调用(我之前讨论过的健全性检查)。我们有例如
call (type metadata accessor for (Swift.Int, Swift.Float) -> ())
...
call (type metadata accessor for ((Swift.Int, Swift.Float)) -> ())
...
call swift_dynamicCast@PLT
Run Code Online (Sandbox Code Playgroud)
它清楚地表明 Swift 编译器正在对正在转换的类型的兼容性进行一些检查,并且在 c 样式转换中找不到。
现在已经找到了 C 风格强制转换/Swift 风格强制转换的差异,我们可以尝试理解为什么调用 C 风格强制转换函数有效。
当查看我在示例中对函数进行的两个简单调用生成的汇编代码时:
call (type metadata accessor for (Swift.Int, Swift.Float) -> ())
...
call (type metadata accessor for ((Swift.Int, Swift.Float)) -> ())
...
call swift_dynamicCast@PLT
Run Code Online (Sandbox Code Playgroud)
我们可以看到这些函数实际上被编译为相同的调用方式:
singleParamFunc:
mov rdi, qword ptr [rip + (output.a : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.a : (Swift.Int, Swift.Float))+8]
call (output.singleParamFunc(tuple: (a: Swift.Int, b: Swift.Float)) -> ())
Run Code Online (Sandbox Code Playgroud)
这里我们看到元组第一个值对应的值被放入 register rdi,第二个值被放入xmm0,然后调用该函数
twoParamFunc:
mov rax, qword ptr [rip + (output.a : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.a : (Swift.Int, Swift.Float))+8]
...
mov rdi, rax
...
call (output.twoParamFunc(a: Swift.Int, b: Swift.Float) -> ())
Run Code Online (Sandbox Code Playgroud)
在这个函数中,它并不那么简单,但现在值 1 进入rax寄存器,它本身被复制到rdi寄存器中,并且值 2 仍然进入xmm0,并且调用该函数。
但在这个示例中,由于我们正在做其他事情,汇编代码有点混乱,我制作了另一个示例来干净地测试它: https: //swift.godbolt.org/z/vDCZZV
在此示例中(我在其中添加了另一个带有结构的测试),我们可以看到创建的用于调用 3 个函数的汇编代码完全相同:
mov rdi, qword ptr [rip + (output.structValue : output.struct_test)]
movss xmm0, dword ptr [rip + (output.structValue : output.struct_test)+8]
call (output.test(value: output.struct_test) -> ())
Run Code Online (Sandbox Code Playgroud)
mov rdi, qword ptr [rip + (output.tupleValue : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.tupleValue : (Swift.Int, Swift.Float))+8]
call (output.test2(tuple: (Swift.Int, Swift.Float)) -> ())
Run Code Online (Sandbox Code Playgroud)
mov ecx, 1
mov edi, ecx
movss xmm0, dword ptr [rip + .LCPI0_0]
call (output.test3(a: Swift.Int, b: Swift.Float) -> ())
Run Code Online (Sandbox Code Playgroud)
再说一遍,在当前版本的 swift 中,这三个函数中的任何一个都可以被转换为任何其他函数并且仍然可以工作。
这最终比最初计划的时间要长得多,但我认为这个问题值得解决。
| 归档时间: |
|
| 查看次数: |
339 次 |
| 最近记录: |