声明一个指向 属性 的指针并将其作为输入参数传递给 Swift 中的函数?

Declare a pointer to a property and pass it as inout param into func in Swift?

我需要选择一些属性之一并通过引用传递它以在 func 中设置它。大概代码:

var someProperty = [SomeClass]()
var someProperty2 = [SomeClass]()

func someFunc(someObject: inout [SomeClass]) {
    ...
    someObject = ... //
}

//usage code
let obj: [SomeClass]
if someCase {
    obj = self.someProperty
    ...
} else {
    obj = self.someProperty2
    ...
}
someFunc(&obj)

问题是 obj 不能用作 inout 参数,但即使我将其声明为 var 然后 obj 会更改但不会 somePropertysomeProperty2.

我读到我需要以某种方式将 obj 声明为 UnsafeMutablePointer 并且有类似的问题。但我不知道如何将它们应用于上面的代码以仅修复这 3 行(不更改其余代码):

let obj: [SomeClass]
obj = self.someProperty
obj = self.someProperty2

如何解决这个问题?

P.S。换句话说,我需要 let obj: inout [SomeClass] 之类的东西,但 Swift

不允许

显然我最初对这个问题的理解很模糊,所以有了更新的(希望是准确的)理解,我将通过给出我认为 OP 的具体(和可编译的)代码来重新表达这个问题想做,那就给出我的解决方案吧。

我对一些内容进行了重命名,以便更具体并表达它们在代码中的作用。给出这样的代码:

var sourceArray1 = [1, 2, 3, 4]
var sourceArray2 = [5, 6, 7, 8]

func someFunc(_ someArray: inout [Int]) {
    someArray.indices.forEach { someArray[[=10=]] = 0 }
}

let someCase = true // just for the sake of compiling a concrete case

//usage code
var arrayProxy: [Int]
if someCase {
    arrayProxy = sourceArray1
    // Some additional code that may or may not involve arrayProxy
} else {
    arrayProxy = sourceArray2
    // Some additional code that may or may not involve arrayProxy
}
someFunc(&arrayProxy)

print("sourceArray1 = \(sourceArray1)")
print("sourceArray2 = \(sourceArray2)")

实际输出为

sourceArray1 = [1, 2, 3, 4]
sourceArray2 = [5, 6, 7, 8]

但期望的输出是

sourceArray1 = [0, 0, 0, 0]
sourceArray2 = [5, 6, 7, 8]

因此 if 语句选择的源数组就是要更改的数组,而 arrayProxy 尽管可能在以后的代码中使用,但对于以下目的并不重要想要的效果在这里。

我认为指针在这里不是正确的解决方案。我认为稍微修改一下设计会更好。需要的是改变其中一个源阵列,那么为什么不直接这样做呢?我的解决方案是:

var sourceArray1 = [1, 2, 3, 4]
var sourceArray2 = [5, 6, 7, 8]

func someFunc(_ someArray: inout [Int]) {
    someArray.indices.forEach { someArray[[=13=]] = 0 }
}

func caseTrue(_ someArray: inout [Int]) -> [Int]
{
    // The additional code from the if statement's `true` branch
    someFunc(&someArray)
    return someArray
}

func caseFalse(_ someArray: inout [Int]) -> [Int] {
    // The additional code from the if statement's `false` branch
    someFunc(&someArray)
    return someArray
}

let someCase = true // just for the sake of compiling a concrete case

//usage code - assuming arrayProxy is still needed for something in later code
let arrayProxy = someCase ? caseTrue(&sourceArray1) : caseFalse(&sourceArray2)

print("sourceArray1 = \(sourceArray1)")
print("sourceArray2 = \(sourceArray2)")

caseTruecaseFalse 可以是嵌套在包含 if 的方法中的局部函数,因此它们不会污染当前上下文之外的代码。

class AClass
{
    var sourceArray1 = [1, 2, 3, 4]
    var sourceArray2 = [5, 6, 7, 8]
    
    init() { }
    
    func someFunc(_ someArray: inout [Int]) {
        someArray.indices.forEach { someArray[[=14=]] = 0 }
    }

    func aMethod()
    {
        func caseTrue(_ someArray: inout [Int]) -> [Int]
        {
            // The additional code from the if statement's `true` branch
            someFunc(&someArray)
            return someArray
        }

        func caseFalse(_ someArray: inout [Int]) -> [Int] {
            // The additional code from the if statement's `false` branch
            someFunc(&someArray)
            return someArray
        }
        
        let someCase = true // just for the sake of compiling a concrete case

        //usage code - assuming arrayProxy is still needed for something in later code
        let arrayProxy = someCase ? caseTrue(&sourceArray1) : caseFalse(&sourceArray2)

        print("sourceArray1 = \(sourceArray1)")
        print("sourceArray2 = \(sourceArray2)")
    }
}

如果 caseTruecaseFalse 的主体依赖于 aMethod 中的局部变量,这不是问题。局部函数就像闭包一样从周围的上下文中捕获变量(事实上,在 Swift 中它们基本上只是命名为闭包)。所以在你需要调用它们之前声明函数,这意味着 aMethod.

中可能有一些代码在它们上面

已找到解决方案,但看起来很奇怪:

var someProperty = [SomeClass]()
var someProperty2 = [SomeClass]()

func someFunc(someObject: inout [SomeClass]) {
    ...
    someObject = ... //
}

//usage code
let obj: UnsafeMutablePointer<[SomeClass]>
if someCase {
    obj = withUnsafeMutablePointer(to: &self.someProperty) { [=10=] }
    ...
} else {
    obj = withUnsafeMutablePointer(to: &self.someProperty2) { [=10=] }
    ...
}
someFunc(&obj.pointee)

因此仅更改了“使用代码”部分。我有一段时间没有找到合适的解决方案,因为例如以下代码会产生“悬挂指针”警告:

obj = UnsafeMutablePointer<[SomeClass]>(&self.someProperty)

我的解决方案有时也会导致这个问题:

Thread 1: Simultaneous accesses to 0x7f85df805fc0, but modification requires exclusive access

在评论中讨论后,问题的目的是具体如何使用 UnsafeMutablePointer 来实现结果,而不是关于实现结果的最佳方法。

重要的是,从获取指针到使用它的整个代码都在 withUnsafeMutablePointer 范围内。因为它归结为在两个数组之间进行选择,然后通过别名将其中一个传递给 someFunc,你不知道哪个指针必须保持活动状态,所以你必须让它们保持 都在直播。否则,当 Swift 使其无效时,您的程序可能会崩溃。

用指针达到预期效果的正确和安全的方法是这样的:

func pointerSolution()
{
    var sourceArray1 = [1, 2, 3, 4]
    var sourceArray2 = [5, 6]

    func someFunc(_ someArray: inout [Int]) {
        someArray.indices.forEach { someArray[[=10=]] = 0 }
    }
    
    let someCase = true // just for the sake of compiling a concrete case

    //usage code
    withUnsafeMutablePointer(to: &sourceArray1)
    { src1 in
        withUnsafeMutablePointer(to: &sourceArray2)
        { src2 in
            // Substitute appropriate type for `Int`
            let arrayPtr: UnsafeMutablePointer<[Int]>
            if someCase {
                arrayPtr = src1
                // Some additional code that may or may not involve arrayPtr
            } else {
                arrayPtr = src2
                // Some additional code that may or may not involve arrayPtr
            }
            someFunc(&arrayPtr.pointee)
        }
    }

    print("sourceArray1 = \(sourceArray1)")
    print("sourceArray2 = \(sourceArray2)")
}

如果你必须在多个地方这样做,或者只是想清理嵌套 withUnsafeMutablePointer 块的语法膨胀,你可以提供一个辅助函数:

func withUnsafeMutablePointers<T, R>(
    to value1: inout T, 
    and value2: inout T, 
    _ body: (UnsafeMutablePointer<T>, UnsafeMutablePointer<T>) throws -> R) rethrows -> R
{
    try withUnsafeMutablePointer(to: &value1)
    { ptr1 in
        try withUnsafeMutablePointer(to: &value2)
        { ptr2 in
            try body(ptr1, ptr2)
        }
    }
}

那么在你使用它的地方,你有一层嵌套:

func pointerSolution()
{
    var sourceArray1 = [1, 2, 3, 4]
    var sourceArray2 = [5, 6]

    func someFunc(_ someArray: inout [Int]) {
        someArray.indices.forEach { someArray[[=12=]] = 0 }
    }
    
    let someCase = true // just for the sake of compiling a concrete case

    //usage code
    withUnsafeMutablePointers(to: &sourceArray1, and: &sourceArray2)
    { src1, src2 in
        // Substitute appropriate type for `Int`
        let arrayPtr: UnsafeMutablePointer<[Int]>
        if someCase {
            arrayPtr = src1
            // Some additional code that may or may not involve arrayPtr
        } else {
            arrayPtr = src2
            // Some additional code that may or may not involve arrayPtr
        }
        someFunc(&arrayPtr.pointee)
    }

    print("sourceArray1 = \(sourceArray1)")
    print("sourceArray2 = \(sourceArray2)")
}

如果你想危险地生活,你可以这样做:

func dangerousPointerSolution()
{
    var sourceArray1 = [1, 2, 3, 4]
    var sourceArray2 = [5, 6]

    func someFunc(_ someArray: inout [Int]) {
        someArray.indices.forEach { someArray[[=13=]] = 0 }
    }

    let someCase = true // just for the sake of compiling a concrete case

    //usage code
    let address: Int
    if someCase {
        address = withUnsafeMutablePointer(to: &sourceArray1) { Int(bitPattern: [=13=]) }
        // Some additional code that may or may not involve address
    } else {
        address = withUnsafeMutablePointer(to: &sourceArray2) { Int(bitPattern: [=13=]) }
        // Some additional code that may or may not involve address
    }
    someFunc(&(UnsafeMutablePointer<[Int]>(bitPattern: address)!).pointee)

    print("sourceArray1 = \(sourceArray1)")
    print("sourceArray2 = \(sourceArray2)")
}

注意通过Int的指针转换。这是因为当 withUnsafeMutablePointer returns 时,它在内部使指针 [=20=] 无效,如果你只是 return [=20=],指针就是 [=由 withUnsafeMutablePointer 编辑的 64=] 也无效。所以你必须欺骗 Swift 给你一些你可以在 withUnsafeMutablePointer 之外使用的东西。通过将其转换为 Int,您基本上是将有效地址保存为数值。 Swift 无法使其失效。然后在 withUnsafeMutablePointer 之外,你必须将那个 Int 地址转换回一个指针,UnsafeMutablePointer<T> 有一个初始化程序要做(毕竟,你可以想象一个内存映射的嵌入式系统 I/O。你需要 read/write 到特定地址才能执行 I/O。)任何时候你必须欺骗编译器让你做某事,它应该是一个大危险信号,也许你不应该那样做。您可能仍然有充分的理由,但至少应该引起您的质疑,并考虑替代方案。

重要的是不要使用 address 重建此范围之外的另一个指针。在此特定示例中,它在函数范围内仍然是一个有效地址,仅因为它是在使用指针后引用的局部值的地址。当这些值超出范围时,使用任何指向它们的指针都会成为问题。如果它们是 class 的属性,那么当实例被取消初始化时,让地址转义到 class 的范围之外将是一个问题。对于 struct,问题会更快发生,因为它很可能最终会用于 struct 的副本,而不是原始实例。

简而言之,在使用指针时,尽可能将它们保留在本地,并确保它们或任何可用于重建它们的东西没有它们指向的原始 Swift“对象”,不要不要逃到你确信它们有效的上下文之外。这不是 C。您对分配的内存的生命周期没有那么多的控制权。通常在 Swift 中你不必担心它,但是当你使用指针时,实际上比在 C 中更难推断它们的有效性,因为你无法指定何时分配的内存变得无效。例如,在 Swift 中, 而非 保证 class 的本地分配实例将在作用域结束时保持“活动”状态。事实上,它通常在最后一次使用后立即取消初始化,即使在同一范围内可能有更多代码。如果你有一个指向这样一个对象的指针,即使你仍然在同一个范围内,你现在也可能指向未初始化的内存。 Swift 甚至不得不提供 withExtendedLifetime 来处理这种情况。这就是为什么 Swift 试图将它们的使用限制在 withUnsafePointer 函数族内。这是唯一可以保证其有效性的上下文。在其他上下文中它们是有效的,但编译器无法证明它们是有效的。