【问题标题】:Mutate inout function paramter inside async block在异步块内改变输入函数参数
【发布时间】:2016-05-04 07:01:09
【问题描述】:

我有以下操场代码:

import UIKit
import XCPlayground

class A {

    var arr : [UIImage] = []

    func addItem(inout localArr: [UIImage]) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
            localArr.append(UIImage())
            print("from inside function localArr: \(localArr)")
            print("form inside function: \(self.arr)")
        }
    }
}

let a = A()
a.addItem(&a.arr)
print("instant print :\(a.arr)")

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(3 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
    print("print after delay: \(a.arr)")
}

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

输出是:

instant print :[]
from inside function localArr: [<UIImage: 0x7f99e8706f10>, {0, 0}]
form inside function: []
print after delay: []

我的问题是,为什么localArraddItem 内部的self.arr 不同,而与外部的a.arr 不同?我的期望是,当我将参数作为inout 传递时,我应该能够对实际对象进行操作,而不是对副本进行操作,但显然这不是发生的情况。

编辑:感谢 dfri 的回答,我知道为什么这不起作用。 inout 真的是call-by-copy-restore,检查另一个答案here。现在,关于如何实际使闭包捕获对原始对象的引用的任何建议?或者也许我应该使用其他技术来实现我想要的?

【问题讨论】:

  • 您的代码和输出不匹配..请编辑它们
  • 已编辑,addItem 函数内部缺少一条打印语句。

标签: ios swift closures swift-playground


【解决方案1】:

有关inout 关键字的一些理论,请参阅以下答案:

不要依赖copy-in-copy-out之间的行为差​​异 并通过引用调用。

...

当函数返回时,你对原始的更改是 被副本的值覆盖。不要依赖于 尝试保持引用调用优化的实现 被覆盖的更改。

现在,您的 addItem 函数将立即完成其调用,因此在函数延迟调度之前完成 inout 复制输入/复制输出任务。这使得在inout 参数的方法使用延迟调度本质上是不好的,至少如果延迟是尝试改变inout 参数的方法。

要看到这一点,让我们跟踪一些数组的引用(而不是跟踪值),以及它们如何在我们的示例运行时显示数组的突变。

func foo(inout bar: [Int]) {
    var pBar : UnsafePointer<Int> = UnsafePointer(bar)
    print("2: \(pBar)")
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
        pBar = UnsafePointer(bar)
        print("3: \(pBar)")
        bar[0] = 2
        pBar = UnsafePointer(bar)
        print("4: \(pBar)")
    }
}

var a : [Int] = [1]
var pA : UnsafePointer<Int> = UnsafePointer(a)
print("1: \(pA)")
foo(&a)
print("foo call to a finished, a = \(a)")

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(5 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
    print("value of a naturally not changed here = \(a)")
    pA = UnsafePointer(a)
    print("5: \(pA)")
}

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

输出是不言自明的:

1: 0x00007fe19271e930
2: 0x00007fe19271e930
foo call to a finished, a = [1] <-- call to foo finished, 'inout' procedure complete
3: 0x00007fe19271e930           <-- dispatch in `foo` starts
4: 0x00007fe1927085e0           <-- mutates 'bar': 'bar' copied (and never 
                                    "returned" as this step is already finished)
value of a naturally not changed here = [1]
5: 0x00007fe19271e930           <-- naturally 'a' wont see the effect of the
                                    delayed mutation in foo

【讨论】:

  • 感谢您的解释,这确实有助于理解inout。您对如何在延迟块内实际改变数组(或任何其他传递的对象)有什么建议吗?
  • @lawicko 很高兴为您提供帮助。我得离开几个小时,但我回来后可以看看,因为那时没有其他人帮助过你。
  • @lawicko 使用引用类型。我相信 NSArray 会做
【解决方案2】:

Swift 数组是值类型。总是在分配时复制。 如果要引用原始数组,请使用 NSMutableArray。 那么也不需要inout

class A {

    var arr : NSMutableArray = []

    func addItem(localArr: NSMutableArray) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
            localArr.addObject(UIImage())
            print("from inside function localArr: \(localArr)")
            print("form inside function: \(self.arr)")
        }
    }
}

let a = A()
a.addItem(a.arr)
print("instant print :\(a.arr)")

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(3 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
    print("print after delay: \(a.arr)")
}

打印:

instant print :(
)
from inside function localArr: (
    "<UIImage: 0x7ffe1872b5f0>, {0, 0}"
)
form inside function: (
    "<UIImage: 0x7ffe1872b5f0>, {0, 0}"
)
print after delay: (
    "<UIImage: 0x7ffe1872b5f0>, {0, 0}"
)

【讨论】:

  • 谢谢,我赞成您的评论,因为 NSMutableArray 在这种情况下确实有效。然而 dfri answer 实际上解释了原始问题,这就是原始代码不起作用的原因。
  • @lawicko 我知道 ;) 我只是想在 dfri 工作时帮助你。
猜你喜欢
  • 1970-01-01
  • 2013-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
相关资源
最近更新 更多