下面的代码可以在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,则两次打印的结果是相同的.有谁知道为什么会发生这种奇怪的行为?

Why the results of two prints differs?

因为对于每个函数调用,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(强调我的):

However,interaction with C pointers is inherently
unsafe compared to your other Swift code,so care must be taken. In
particular:

  • These conversions cannot safely be used if the callee
    saves the pointer value for use after it returns. The pointer that
    results from these conversions is only guaranteed to be valid for the
    duration of a call. Even if you pass the same variable,array,or
    string as multiple pointer arguments,you Could receive a different
    pointer each time. An exception to this is global or static stored
    variables. You can safely use the address of a global variable as a
    persistent unique pointer value
    ,e.g.: as a KVO context parameter.

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

更改功能签名

When I change the function signature of bbb to make it the same with aaa,the result of two prints are the same

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

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

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

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

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

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

inout& UnsafeMutable(RAW)指针

关于your comment:

But since withUnsafePointer(to:_:) always has the correct behavior I want (in fact it should,otherwise this function is of no use),and it also has an inout parameter. So I assume there are implementation difference between these functions with inout parameters.

编译器以与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会显示不同的结果?的更多相关文章

  1. html5利用canvas实现颜色容差抠图功能

    这篇文章主要介绍了html5利用canvas实现颜色容差抠图功能,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  2. ios – 仅在异步函数完成执行后运行代码

    所以,例如:如果问题是你不知道要调用什么函数,你可以配置你周围的函数/对象,这样有人可以给你一个函数,然后你在我上面说“调用函数”的地方调用你的函数.例如:

  3. ios – 如何使用Objective C类中的多个参数调用Swift函数?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  4. ios – 如何从变量访问属性或方法?

    是否可以使用变量作为Swift中方法或属性的名称来访问方法或属性?在PHP中,您可以使用$object->{$variable}.例如编辑:这是我正在使用的实际代码:解决方法你可以做到,但不能使用“纯粹的”Swift.Swift的重点是防止这种危险的动态属性访问.你必须使用Cocoa的Key-ValueCoding功能:非常方便,它完全穿过你要穿过的字符串到属性名称的桥,但要注意:这里是龙.

  5. iOS >>块>>更改块外部的变量值

    我不是在处理一个Object并改变它,就像我的mString一样.我希望’center’属性的行为类似于myInt,因为它是直接访问的C结构,而不是指向对象的指针.我希望’backgroundColor’的行为类似于我的imstring,因为它是一个指向一个新对象的对象的指针,不是吗?

  6. ios – Xcode Bot:如何在post触发器脚本上获得.ipa路径?

    我正在使用机器人来存档iOS应用程序,我需要获取.ipa产品路径才能将其发布到我们的分发系统中.机器人设置:并使用脚本打印所有env变量,其中不包含ipa文件的路径.此外,一些变量指向不存在的目录,即:XCS_OUTPUT_DIR这里的env变量输出:除此之外,我还能够确认.ipa文件是在另一个文件夹中创建的(/IntegrationAssets//

  7. ios – Swift指针算术和解除引用;将一些类似C的地图代码转换为Swift

    我有一点似乎没有工作的Swift代码……解决方法您正在指定locationPointer指向新位置,但仍在下一行中使用ptr,并且ptr的值尚未更改.将您的最后一行更改为:或者你可以改变指向var的指针并推进它:

  8. iOS 7,用于断开调用的私有API CTCallDisconnect不起作用

    谢谢!

  9. ios – 使用附加字符串本地化Info.plist变量

    我正在尝试本地化应用程序的名称,同时仍然能够根据构建配置追加字符串.所以目前它被设置为:该设置定义为:通过这种方式,我们可以为应用程序添加后缀以用于不同的beta版本.问题是,当我们尝试本地化本地化的InfoPlist.strings中的应用程序显示名称时,就像这样我们覆盖存储在Info.plist中的值,并丢失后缀字符.这有什么好办法吗?

  10. iOS – 开始iOS教程 – 变量之前的下划线?

    这是正确的还是我做错了什么?

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部