简体   繁体   中英

Swift: How to assign a variable by reference, not by value?

I'm trying to get a reference to an Array and make modifications to it. Because Array s in Swift are value types, instead of reference types, if I assign my array to a variable first, I am getting a copy of the array instead of the actual array:

var odds = ["1", "3", "5"]
var evens = ["2", "4", "6"]

var source = odds
var destination = evens

var one = odds.first!

source.removeFirst() // only removes the first element of the `source` array, not the `odds` array

destination.append(one)

When we look at the odds and evens arrays, they are unaltered because we changed the source and destination arrays.

I know that I can use the inout parameter attribute on a function to pass them by reference, instead of by value:

func move(inout source: [String], inout destination: [String], value:String) {
    source.removeAtIndex(source.indexOf(value)!)
    destination.append(value)
}

move(&odds, destination: &evens, value:one)

Is there a way to assign these arrays to a variable by reference, instead of by value?

Array is a struct, which means it's a value type in Swift. Because of this, arrays always behave according to value and not reference semantics. The problem here is that you're attempting to use mutable, reference based logic to operate on values types.

You don't want to rely on mutations occurring inside the function to propagate back to the caller. As you've found, this is only possible with inout parameters. What you should do instead is return the mutated array from the function back to the caller. The point of value oriented programming is that it shouldn't matter which array you have, but rather that any two equivalent arrays or values types are interchangeable.

It's slightly easier to imagine with another value type. Take an Int for example, and this function that does some math.

func addFive(int: Int) -> Int {
    return int + 5
}

Now consider a similar function, but written in the reference oriented style that you're attempting to use:

func addFive(inout int: Int) {
    int = int + 5
}

You can see it's simply not natural to operate on value types this way. Instead just return the updated value (the modified arrays) from your function and carry on from there.

Here is your function refactored with value semantics.

func move(source: [String], destination: [String], value:String) -> ([String], [String]) {

    var mutableSource = source
    var mutableDestination = destination

    mutableSource.removeAtIndex(source.indexOf(value)!)
    mutableDestination.append(value)

    return (mutableSource, mutableDestination)
}

let (updatedSource, updatedDestination) = move(odds, destination: evens, value:one)

You cannot assign an array to a variable by reference in Swift.

"In Swift, Array, String, and Dictionary are all value types..."

Source: https://developer.apple.com/swift/blog/?id=10

If you need arrays that can be manipulated by reference you can create a class that encapsulates an array and use it for your variables.

here's an example:

 class ArrayRef<Element>:CustomStringConvertible
 {
    var array:[Element]=[]

    init()               {}
    init(Type:Element.Type)    {}
    init(fromArray:[Element])  { array = fromArray }
    init(_ values:Element ...) { array = values }

    var count:Int { return array.count }

    // allow use of subscripts to manipulate elements
    subscript (index:Int) -> Element
    {
       get { return array[index] }
       set { array[index] = newValue }
    }

    // allow short syntax to access array content
    // example:   myArrayRef[].map({ $0 + "*" })  
    subscript () -> [Element]
    {
       get { return array }
       set { array = newValue }
    }

    // allow printing the array example:  print(myArrayRef) 
    var description:String { return "\(array)" }

    // delegate append method to internal array
    func append(newElement: Element)
    { array.append(newElement) }

    // add more delegation to array methods as needed ...
 }

 // being an object, the ArrayRef class is always passed as a reference
 func modifyArray(X:ArrayRef<String>)
 {
    X[2] = "Modified"
 }
 var a = ArrayRef("A","B","C")
 modifyArray(a)
 print(a) // --> a is now ["A", "B", "Modified"]


 // various means of declaration ...
 var b = ArrayRef<String>()
 b[] = ["1","2","3"]

 var c = ArrayRef(fromArray:b[])

 // use .array to modify array content (or implement delegation in the class)
 c.array += a[] + ["X","Y","Z"]

Note that you could also define your arrays as NSMutableArrays which are Obj-C classes and are passed by reference. It does a similar thing and does present differences with a regular array for the methods that are available.

I recommend this the following only for didactic purpose only, I advise against using it in production code.


You can circulate a "reference" to something via an UnsafePointer instance.

class Ref<T> {
    private var ptr: UnsafePointer<T>!
    var value: T { return ptr.pointee }

    init(_ value: inout T) {
        withUnsafePointer(to: &value) { ptr = $0 }
    }
}

var a = ["1"]
var ref = Ref(&a)
print(a, ref.value) // ["1"] ["1"]
a.append("2")
print(a, ref.value) // ["1", "2"] ["1", "2"]
ref.value.removeFirst()
print(a, ref.value) // ["2"] ["2"]

Thus, you can simulate a reference to a variable via the above class, which stores a pointer to the given variable reference.

Please note that this is a simple use case, and will behave as expected only if if the variable doesn't get destroyed before the pointer , as in that case the memory initially occupied by the variable will be replaced by something else, and the unsafe pointer will no longer be valid. Take for example the next code:

var ref: Ref<[String]>!
// adding an inner scope to simulate `a` being destroyed
do {
    var a: [String] = ["a"]
    ref = Ref(&a)
    print(a, ref.value)
    a = ["b"]
    print(a, ref.value)
}
// `a` was destroyed, however it's place on the stack was taken by `b`
var b: [String:Int] = ["a": 1]
// however `b` is not an array, thus the next line will crash
print(ref.value)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM