0

我需要选择一些属性之一并通过引用传递它以将其设置在 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参数,但即使我将其声明为varthenobj已更改但不是somePropertyor someProperty2

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

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

如何解决这个问题?

PS 换句话说,我需要类似let obj: inout [SomeClass]的东西,但 Swift 不允许这样做

4

3 回答 3

2

在评论中讨论之后,问题的目的是具体如何使用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[$0] = 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[$0] = 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[$0] = 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: $0) }
        // Some additional code that may or may not involve address
    } else {
        address = withUnsafeMutablePointer(to: &sourceArray2) { Int(bitPattern: $0) }
        // Some additional code that may or may not involve address
    }
    someFunc(&(UnsafeMutablePointer<[Int]>(bitPattern: address)!).pointee)

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

注意指针转换通过Int. 这是因为当withUnsafeMutablePointer返回时,它会在$0内部使指针无效,如果您只是 return $0,则返回的指针withUnsafeMutablePointer也会无效。因此,您必须设法Swift给您一些可以在withUnsafeMutablePointer. 通过将其转换为Int,您基本上将有效地址保存为数值。斯威夫特不能使它无效。然后在withUnsafeMutablePointer你之外必须将该Int地址转换回一个指针,这UnsafeMutablePointer<T>有一个初始化程序要做(毕竟,您可以想象一个具有内存映射 I/O 的嵌入式系统。您需要读取/写入特定地址来执行 I/O。)任何时候您必须欺骗编译器让你做某事,这应该是一个很大的危险信号,也许你不应该这样做。您可能仍然有充分的理由,但至少,它应该让您质疑他们,并考虑替代方案。

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

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

于 2021-03-26T12:10:41.177 回答
0

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

我已经重命名了一些东西,使其更具体并在代码中表达它们的角色。给定这样的代码:

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

func someFunc(_ someArray: inout [Int]) {
    someArray.indices.forEach { someArray[$0] = 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[$0] = 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)")

caseTrue并且caseFalse可以是嵌套在包含 的方法内的本地函数,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[$0] = 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)")
    }
}

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

于 2021-03-25T14:20:38.947 回答
0

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

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) { $0 }
    ...
} else {
    obj = withUnsafeMutablePointer(to: &self.someProperty2) { $0 }
    ...
}
someFunc(&obj.pointee)

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

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

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

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

于 2021-03-26T06:57:18.813 回答