【问题标题】:Swift 2.0 'inout' function parameters and computed propertiesSwift 2.0 'inout' 函数参数和计算属性
【发布时间】:2015-07-05 15:55:57
【问题描述】:

我现在正在测试 Swift 2.0 beta 并且发现了奇怪的行为。这是一个示例代码:

private func someFunc(inout someString: String) {
    print("Inside \'someFunc()\'")

    print(someString)
    someString = "Some another string"
}

private var someAncillaryInt = 42

print(someAncillaryInt)

private var someString: String {
    get {
        print("Inside \'getter\'")
    
        return "Some string"
    }
    set {
        print("Inside \'setter\'")
        someAncillaryInt = 24
    }
}

someFunc(&someString)
print(someAncillaryInt)

输出:

42

在“吸气剂”内部

在'someFunc()'内部

一些字符串

在“二传手”内部

24

我不明白为什么在someFunc() 中打印someString 时没有调用getter,为什么当someFunc()someString 一起传递时会调用。

可以假设我还不了解 inout 参数的复杂性,并且在作为 inout 参数传递之后,计算属性不再是,em,“计算”,但是为什么当我们将另一个值设置为 someString 时会调用“setter”?

谢谢!

UPD:我在下面添加了答案。

更新 18/11/2015:Apple 更新了他们的 manual,详细解释了 inout 参数的工作原理。

【问题讨论】:

    标签: swift swift2


    【解决方案1】:

    您的困惑可能是因为同时选择了someString 全局变量的名称,并作为参数的名称 someFunc() 函数。

    print(someString) inside someFunc() 打印 (局部)函数参数的值,完全不相关 (并隐藏)全局 someString 变量。

    改名函数参数会更容易理解

    private func someFunc(inout localString: String) {
        print("Inside \'someFunc()\'")
        print(localString)
        localString = "Some another string"
    }
    

    语义相同(因此产生相同的输出)。

    你可以想到

    someFunc(&someString)
    

    如下:

    • 检索someString 的值(使用getter 方法)。
    • 执行someFunc(),带有本地参数localString 设置为 someString 的值。
    • someFunc() 返回时,设置someString(使用 setter 方法)到本地参数的(可能已更改)值 localString

    更多信息可以在 Apple 开发者论坛的https://devforums.apple.com/thread/230567 中找到, 例如:

    鉴于 getter 和 setter 的保证,inout 自然地遵循: 当调用带有 inout 参数的函数时,逻辑上它调用 var/subscript 上的 getter 并将值复制到堆栈中 临时的,保证具有物理寻址能力。这 临时的物理地址被传递给 inout 参数 功能 ... 。被调用者做 任何它想要的内存位置(并且永远不知道是否 传入的东西是否已计算)。当被调用者返回时, 调用 setter 将值复制回原位。

    它也保证了一个属性的getter/setter传入out 无论是什么,都会调用一次它的 getter 和 setter 被调用者(如果访问器有副作用或 很贵)。

    但也说明必要时避免临时复制。

    【讨论】:

    • 我不应该将“inout”参数视为类似于 C++ 中的引用。这可能是我困惑的根源。
    • @mesmerizingsnow:我对 C++ 中的引用不太熟悉。但是 inout 参数绝对不同于 C 或 Objective-C 中的“传递变量的地址”,因此如果 someString 被声明为属性,则 someFunc(&someString) 在 Objective-C 中是不可能的(比较 stackoverflow.com/questions/23605298/… )。
    • 我的前提是 'inout' 参数将充当 C++ 中的引用。因此,如果您将某个变量作为“inout”参数传递,则无论何时访问它,您都会得到它的实际状态。这意味着,例如,如果某个单独的线程试图同时修改变量(假设同步问题已解决),您将始终获得变量的实际状态。但是我现在看到的是只有在传递变量时才会保留状态。这就是为什么不叫“getter”的原因。不过,这个逻辑与“inout”的定义并不矛盾。
    • @mesmerizingsnow:我在答案中添加了一些来自 Apple 开发者论坛的摘录,这是否澄清了您的疑问?
    • 好的,谢谢。但是,我不太确定那些人一直在谈论什么。你知道,他们讨论了关于 'inout' 说明符的 Swift 编译器未来实现,我个人不想知道。但我很高兴我的想法正在开发者论坛上进行讨论,并且可以跟踪讨论的进度。
    【解决方案2】:

    我不明白为什么在 someFunc() 中打印 someString 时没有调用 getter,为什么当 someFunc() 与 someString 一起传递时会调用。

    gettersomeFunc() 内打印someString 时未被调用,因为它已经被调用了。我们已经将此字符串作为someString 内部的someFunc() 参数;我们不需要再次获得它。

    您的输出内容为:

    Inside 'getter' //<-- that's the getter being called!
    Inside 'someFunc()'
    Some string
    Inside 'setter'
    

    您的代码运行:

    someFunc(&someString) //<-- that calls the getter!
    

    顺便说一句,这与inout 无关。如果这是一个普通参数,你会看到同样的事情(就getter 而言)。

    【讨论】:

      【解决方案3】:

      @Martin R 在上述thread 上指出了我,特别是 Chris Lattner(Swift 的策划者)发表的第 16 条评论,它帮助我理解了“inout”行为。谢谢大佬!

      考虑这段代码:

      private var someString: String {
          get {
              print("Inside getter")
      
              return "Some string"
          }
          set {
              print("Inside setter")
          }
      }
      
      private func someFunc(inout stringArg: String) {
          let funcName = "`someFunc()\'"
      
          print("Inside " + funcName)
      
          let someDontMatter0 = 42, someDontMatter1 = 24
      
          print(stringArg)
      
          // sets temporary, not the original one. Hence, no calls to setter
          stringArg = "Some other string"
          stringArg = "No matter what string"
      
          print("These \(someDontMatter0) and \(someDontMatter1)")
      
          print("Leaving " + funcName)
      
          // when callee returns, calls the setter with "No matter what
          // string" as a `newValue'
      }
      
      // implicitly creates temporary initialised with `someString' value
      // getting from `someString's getter.
      someFunc(&someString)
      

      对于有 C++ 背景的人来说,输出看起来应该是这样的:

      在`someFunc()'里面

      内部吸气剂

      一些字符串

      内部设置器

      这些 42 和 24

      离开`someFunc()'

      实际上输出如下:

      内部吸气剂

      在`someFunc()'里面

      一些字符串

      这些 42 和 24

      离开`someFunc()'

      内部设置器

      有点搞笑,对吧?

      对于具有 C++ 背景的人来说,这种违反直觉的行为是更基本的逻辑的结果,它支撑着 Swift 的许多方面。从 Chris 的评论中可以看出,Swift 编译器处理 inout 属性的一般方式是在堆栈上创建 temporary 对象以将原始值存储在那里(因此,调用 getter )。

      因此,当您操作 inout 参数时,您处理的是临时对象(具有传递给函数的初始值),而不是原始对象(对我个人而言,这有点模糊。但是好的,可以处理)。最后:

      当被调用者返回时,调用setter将值复制回原位

      所以,如果对代码稍作改动:

      private var someString: String {
          get {
              print("Inside getter")
      
              return "Some string"
          }
          set {
              print("Inside setter")
          }
      }
      
      private func someFunc(inout stringArg: String) {
          // before returning calls `someString's setter
      }
      
      // calls `someString's getter
      someFunc(&someString)
      

      输出将是:

      内部吸气剂

      内部设置器

      此外,存储属性也是如此:

      private func someFunc() {
          var someString = "Some string" {
              willSet {
                  print("Inside \'willSet\' with \'newValue\': \(newValue)")
              }
              didSet {
                  print("Inside \'didSet\' with \'oldValue\': \(oldValue)")
              }
          }
      
          func someOtherFunc(inout stringArg: String) {
              print("Inside `someOtherFunc()\'")
          
              stringArg = "First string"
              stringArg = "Second string"
          
              print("Before leaving the function")
          }
      
          someOtherFunc(&someString)
      }
      
      someFunc()
      

      输出:

      在`someOtherFunc()'中

      离开函数之前

      在带有“newValue”的“willSet”内:第二个字符串

      在带有“oldValue”的“didSet”中:一些字符串

      来自 Chris 的回复:

      它还保证传入输出的属性的 getter/setter 将调用其 getter 和 setter 一次,而不管被调用者做什么(如果访问器有副作用或昂贵,这很重要)

      好的,但是如果在某些情况下采用 inout 参数的某些方法不修改它(不调用 setter 或不修改该临时变量,你命名它)怎么办?另外,如果我在计算变量的 getter 和 setter 背后构思了一些重要的逻辑并遇到了特定的情况(想象一下我在 getter/setter 中打开和关闭文件)怎么办?最好不要调用 getter 和 setter。

      我真的很想在 Apple 的最终 Swift 2.0 编程指南中以某种形式看到 Chris 的回应。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-29
        • 2014-07-24
        • 1970-01-01
        • 1970-01-01
        • 2018-06-11
        • 2018-07-28
        • 2016-05-20
        相关资源
        最近更新 更多