Swift 2.0 'inout' 函数参数和计算属性
Swift 2.0 'inout' function parameters and computed properties
我正在测试 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
Inside 'getter'
Inside 'someFunc()'
Some string
Inside 'setter'
24
我不明白为什么在 someFunc()
中打印 someString
时没有调用 getter,为什么在 someFunc()
通过 someString
.
可以假设我还不理解 inout 参数的复杂性,并且在作为 inout 参数传递后计算 属性 不再被“计算”,但为什么当我们将另一个值设置为 someString
时调用 'setter'?
谢谢!
UPD:我在下面添加了答案。
2015 年 11 月 18 日更新:Apple 更新了他们的 manual,详细说明了 inout 参数的工作原理。
I don't understand why wasn't getter called while printing someString inside someFunc() and why was it when someFunc() got passed with someString.
getter
在 someFunc()
中打印 someString
时没有被调用,因为它 已经 被调用了。我们已经将此字符串作为 someFunc()
内部的 someString
参数;我们不需要再得到它。
您的输出为:
Inside 'getter' //<-- that's the getter being called!
Inside 'someFunc()'
Some string
Inside 'setter'
您的代码运行:
someFunc(&someString) //<-- that calls the getter!
顺便说一下,这与 inout
无关。如果这是一个正常参数,你会看到同样的东西(就 getter
而言)。
您的困惑可能是由于选择 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()
开始 return,设置 someString
(使用
setter 方法)到局部参数的(可能改变的)值
localString
.
更多信息可以在 Apple 开发者论坛的 https://devforums.apple.com/thread/230567 中找到,
例如:
Given the guarantee of a getter and setter, inout follows naturally:
when calling a function with an inout argument, logically it calls the
getter on the var/subscript and copies the value into a stack
temporary which is guaranteed to have physical addressability. The
physical address of the temporary is passed to the inout argument of
the function ... . The callee does
whatever it wants with that memory location (and never knows whether
the thing passed in was computed or not). When the callee returns,
the setter is invoked to copy the value back into place.
和
It also guarantees that the getter/setter of a property passed inout
will have its getter and setter called once regardless of what the
callee does (this is important if the accessors have side effects or
are expensive).
但也说明必要时避免临时拷贝
@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++ 背景的人来说,输出看起来应该是这样的:
Inside `someFunc()'
Inside getter
Some string
Inside setter
These 42 and 24
Leaving `someFunc()'
实际上输出如下:
Inside getter
Inside `someFunc()'
Some string
These 42 and 24
Leaving `someFunc()'
Inside setter
有点好笑吧?
对于具有 C++ 背景的人来说,这种违反直觉的行为是支持 Swift 许多方面的更基本逻辑的结果。从 Chris 的评论中可以看出,Swift 编译器处理 inout 属性的一般方式是在堆栈上创建 temporary 对象来存储那里的原始值(因此,调用 getter)。
所以,当你操作你的 inout 参数时,你处理的是临时对象(你传递给函数的初始值)而不是原始对象(对我个人来说,有点含糊。但是好吧,可以解决这个问题)。最后:
When the callee returns, the setter is invoked to copy the value back into place
因此,如果稍微更改一下代码:
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)
输出将是:
Inside getter
Inside setter
此外,存储属性也是如此:
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()
输出:
Inside `someOtherFunc()'
Before leaving the function
Inside 'willSet' with 'newValue': Second string
Inside 'didSet' with 'oldValue': Some string
克里斯的回复:
It also guarantees that the getter/setter of a property passed inout will have its getter and setter called once regardless of what the callee does (this is important if the accessors have side effects or are expensive)
好的,但是如果某些采用 inout 参数的方法在某些情况下不修改它(不调用 setter 或不修改那个临时变量,你给它命名)?另外,如果我在我的计算变量的 getter 和 setter 和 运行 的背后构思了一些重要的逻辑到特定情况下会怎样(想象一下我在 getter/setter 中打开和关闭文件) ?最好不要调用 getter 和 setter。
我真的很想在 Apple 的最终 Swift 2.0 编程指南中看到 Chris 的某种形式的回应。
我正在测试 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
Inside 'getter'
Inside 'someFunc()'
Some string
Inside 'setter'
24
我不明白为什么在 someFunc()
中打印 someString
时没有调用 getter,为什么在 someFunc()
通过 someString
.
可以假设我还不理解 inout 参数的复杂性,并且在作为 inout 参数传递后计算 属性 不再被“计算”,但为什么当我们将另一个值设置为 someString
时调用 'setter'?
谢谢!
UPD:我在下面添加了答案。
2015 年 11 月 18 日更新:Apple 更新了他们的 manual,详细说明了 inout 参数的工作原理。
I don't understand why wasn't getter called while printing someString inside someFunc() and why was it when someFunc() got passed with someString.
getter
在 someFunc()
中打印 someString
时没有被调用,因为它 已经 被调用了。我们已经将此字符串作为 someFunc()
内部的 someString
参数;我们不需要再得到它。
您的输出为:
Inside 'getter' //<-- that's the getter being called!
Inside 'someFunc()'
Some string
Inside 'setter'
您的代码运行:
someFunc(&someString) //<-- that calls the getter!
顺便说一下,这与 inout
无关。如果这是一个正常参数,你会看到同样的东西(就 getter
而言)。
您的困惑可能是由于选择 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()
开始 return,设置someString
(使用 setter 方法)到局部参数的(可能改变的)值localString
.
更多信息可以在 Apple 开发者论坛的 https://devforums.apple.com/thread/230567 中找到, 例如:
Given the guarantee of a getter and setter, inout follows naturally: when calling a function with an inout argument, logically it calls the getter on the var/subscript and copies the value into a stack temporary which is guaranteed to have physical addressability. The physical address of the temporary is passed to the inout argument of the function ... . The callee does whatever it wants with that memory location (and never knows whether the thing passed in was computed or not). When the callee returns, the setter is invoked to copy the value back into place.
和
It also guarantees that the getter/setter of a property passed inout will have its getter and setter called once regardless of what the callee does (this is important if the accessors have side effects or are expensive).
但也说明必要时避免临时拷贝
@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++ 背景的人来说,输出看起来应该是这样的:
Inside `someFunc()'
Inside getter
Some string
Inside setter
These 42 and 24
Leaving `someFunc()'
实际上输出如下:
Inside getter
Inside `someFunc()'
Some string
These 42 and 24
Leaving `someFunc()'
Inside setter
有点好笑吧?
对于具有 C++ 背景的人来说,这种违反直觉的行为是支持 Swift 许多方面的更基本逻辑的结果。从 Chris 的评论中可以看出,Swift 编译器处理 inout 属性的一般方式是在堆栈上创建 temporary 对象来存储那里的原始值(因此,调用 getter)。
所以,当你操作你的 inout 参数时,你处理的是临时对象(你传递给函数的初始值)而不是原始对象(对我个人来说,有点含糊。但是好吧,可以解决这个问题)。最后:
When the callee returns, the setter is invoked to copy the value back into place
因此,如果稍微更改一下代码:
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)
输出将是:
Inside getter
Inside setter
此外,存储属性也是如此:
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()
输出:
Inside `someOtherFunc()'
Before leaving the function
Inside 'willSet' with 'newValue': Second string
Inside 'didSet' with 'oldValue': Some string
克里斯的回复:
It also guarantees that the getter/setter of a property passed inout will have its getter and setter called once regardless of what the callee does (this is important if the accessors have side effects or are expensive)
好的,但是如果某些采用 inout 参数的方法在某些情况下不修改它(不调用 setter 或不修改那个临时变量,你给它命名)?另外,如果我在我的计算变量的 getter 和 setter 和 运行 的背后构思了一些重要的逻辑到特定情况下会怎样(想象一下我在 getter/setter 中打开和关闭文件) ?最好不要调用 getter 和 setter。
我真的很想在 Apple 的最终 Swift 2.0 编程指南中看到 Chris 的某种形式的回应。