Swift   发布时间:2022-03-31  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了当Swift中的函数签名不同时,为什么UnsafeRawPointer会显示不同的结果?大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

概述

下面的代码可以在Swift Playground中运行: import UIKit func aaa(_ key: UnsafeRawPointer!, _ value: Any! = nil) { print(key) } func bbb(_ key: UnsafeRawPointer!) { print(key) } class A { var key = "aaa
下面的代码可以在Swift Playground中运行:
import UIKit

func aaa(_ key: UnsafeRawPointer!,_ value: Any! = nil) {
    print(key)
}
func bbb(_ key: UnsafeRawPointer!) {
    print(key)
}
class A {
    var key = "aaa"
}
let a = A()
aaa(&a.key)
bbb(&a.key)

这是我的mac上打印的结果:

0x00007fff5dce9248
0x00007fff5dce9220

为什么两个打印的结果不同?更有趣的是,当我更改bbb的功能签名以使其与aaa相同时,两次打印的结果是相同的.如果我在这两个函数调用中使用全局var而不是a.key,则两次打印的结果是相同的.有谁知道为什么会发生这种奇怪的行为?

因为对于每个函数调用,Swift is creating a temporary variable初始化为a.key的getter返回的值.使用指向其给定临时变量的指针调用每个函数.因此指针值可能不一样 – 因为它们引用不同的变量.

这里使用临时变量的原因是因为A是非final类,因此可以使其子类的getter和setter重写(可以将其重新实现为计算属性).

因此,在未优化的构建中,编译器不能直接将键的地址直接传递给函数,而是必须依赖于调用getter(尽管在优化的构建中,此行为可以完全改变).

您将注意到,如果将key标记为final,则现在应该在两个函数中获得一致的指针值:

class A {
    final var key = "aaa"
}

var a = A()
aaa(&a.key) // 0x0000000100a0abe0
bbb(&a.key) // 0x0000000100a0abe0

因为现在key的地址可以是directly passed to the functions,完全绕过它的getter.

值得注意的是,一般来说,你不应该依赖这种行为.您在函数中获得的指针值是纯粹的实现细节,并不保证是稳定的.编译器可以根据自己的意愿自由调用函数,只承诺你获得的指针在调用期间有效,并且会将pointees初始化为期望值(如果是可变的,则对你所做的任何更改)调用者将会看到调查员.

此规则的唯一例外是将指针传递给全局和静态存储变量. Swift确保您获得的指针值对于该特定变量是稳定且唯一的.来自Swift团队的blog post on Interacting with C Pointers(强调我的):

因此,如果您将密钥设置为A的静态存储属性或仅仅是全局存储变量,则可以保证在两个函数调用中获得相同的指针值.

更改功能签名

这似乎是一个优化的事情,因为我只能在-O版本和游乐场中重现它.在未优化的构建中,添加删除额外参数无效.

(虽然值得注意的是你不应该在游乐场中测试Swift行为,因为它们不是真正的Swift环境,并且可以对使用swiftc编译的代码展示不同的运行时行为)

这种行为的原因仅仅是巧合 – 第二个临时变量能够驻留在与第一个临时变量相同的地址(在第一个被解除分配之后).当您向aaa添加额外参数时,将在它们之间分配一个新变量以保存要传递的参数值,从而阻止它们共享相同的地址.

由于a的中间负载以便为a.key的值调用getter,因此在未优化的构建中不能观察到相同的地址.作为优化,如果编译器具有带有常量表达式的属性初始化器,则编译器能够将a.key的值内联到调用站点,从而无需使用此中间负载.

因此,如果给a.key一个非确定的值,例如var key = arc4random(),那么你应该再次观察不同的指针值,因为a.key的值不能再内联.

但无论原因如何,这都是如何不依赖变量(不是全局或静态存储变量)的指针值的完美示例 – 因为您获得的值可以根据优化级别等因素完全改变和参数计数.

inout& UnsafeMutable(RAW)指针

关于your comment

编译器以与UnsafeRawPointer参数略有不同的方式处理inout参数.这是因为你可以在函数调用中改变inout参数的值,但是你不能改变UnsafeRawPointer的指针.

为了使调用者看到的inout参数值的任何突变,编译器通常有两个选项:

>将临时变量初始化为变量getter返回的值.使用指向此变量的指针调用函数,并在函数返回后,使用临时变量的(可能已突变的)值调用变量的setter.
>如果它是可寻址的,只需使用指向变量的直接指针调用函数.

如上所述,编译器不能将第二个选项用于未知最终的存储属性(但这可以随着优化而改变).但是,对于大值,始终依赖第一个选项可能会很昂贵,因为它们必须被复制.这对于具有写时复制行为的值类型尤其有害,因为它们依赖于唯一性以便对其底层缓冲区执行直接突变 – 临时副本违反了这一点.

为了解决这个问题,Swift实现了一个特殊的访问器 – 名为materializeForSet.这个访问器允许被调用者为调用者提供指向给定变量的直接指针(如果它是可寻址的),否则将返回指向包含副本的临时缓冲区的指针.变量,在使用后需要写回setter.

前者是你在inout – you’re getting a direct pointer中看到的从materializeForSet返回a.key的行为,因此你在两个函数调用中得到的指针值是相同的.

但是,materializeForSet仅用于需要回写的函数参数,这解释了为什么它不用于UnsafeRawPointer.如果你使用aaa和bbb的函数参数采用UnsafeMutable(Raw)指针(需要回写),你应该再次观察相同的指针值.

func aaa(_ key: UnsafeMutableRawPointer) {
    print(key)
}

func bbb(_ key: UnsafeMutableRawPointer) {
    print(key)
}

class A {
    var key = "aaa"
}

var a = A()

// will use materializeForSet to get a direct pointer to a.key
aaa(&a.key) // 0x0000000100b00580
bbb(&a.key) // 0x0000000100b00580

但同样,如上所述,对于非全局或静态的变量,不应依赖此行为.

大佬总结

以上是大佬教程为你收集整理的当Swift中的函数签名不同时,为什么UnsafeRawPointer会显示不同的结果?全部内容,希望文章能够帮你解决当Swift中的函数签名不同时,为什么UnsafeRawPointer会显示不同的结果?所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签: