6

I probably missed an important information about swift. I have a map contains a key / swift array pair. I changed the array and the array inside the map was not changed. Could someone explain what is going on? Thanks!

var map = [String:[String]]()
var list = [String]()
map["list"] = list //output: "["list": []]\n"
print(map)

list.append("test")
print(list)  //output: "["test"]\n"
print(map)   //output: "["list": []]\n"
  • 4
    Swift arrays are passed by value, i.e. copied around. You have to set it back into the map, or use some kind of reference wrapper: http://stackoverflow.com/questions/24250938/swift-pass-array-by-reference – Thilo Mar 12 '16 at 00:07
  • I've used wrapper classes to get this functionality. I do wonder if there is a way to use a pointer to the array and then create an array from the pointer some place else though. – Fred Faust Mar 12 '16 at 00:13
  • There is no way to use a pointer for the array in Swift, as it doesn't work that way. – Stefan Salatic Mar 12 '16 at 00:14
  • "There is no way to use a pointer for the array in Swift" That is not entirely true. – matt Mar 12 '16 at 01:05

4 Answers4

6

As you've been told, these (dictionaries and arrays) are value types. So in general the technique you're looking for is simply to take the array out of the dictionary, modify it, and put it back in again:

var map = [String:[String]]()
map["list"] = [String]()

var list = map["list"]!
list.append("test")
map["list"] = list

However, there's another way: you can get a sort of pointer to the array inside the dictionary, but only if you use a function with an inout parameter. For example:

var map = [String:[String]]()
map["list"] = [String]()

func append(s : String, inout to arr : [String]) {
    arr += [s]
}
append("test", to: &(map["list"]!))

That's actually just a notation for the same thing, but if you're going to do a lot of this you might prefer it.

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • And see this section of my book: http://www.apeth.com/swiftBook/ch04.html#SECreferenceTypes – matt Mar 12 '16 at 01:01
2

Class

Let's suppose we have a type that is a class in Swift:

class MyClass {
    var a:Int
    init(i:Int) {
        a = i
    }
}

let aClass = MyClass(i: 10)
var bClass = aClass
bClass.a = 20
aClass.a // 20
aClass.a = 30
bClass.a // 30

Here we see pointer-style behaviour: aClass and bClass are pointing at the same object. If you change a property value in one of them then both change, because they are the same.

Struct

The behaviour changes when a struct is used, as can be seen here:

struct MyStruct {
    var a:Int
    init(i:Int) {
        a = i
    }
}
let aStruct = MyStruct(i: 10)
var bStruct = aStruct
bStruct.a = 20
aStruct.a // 10

With a struct (and an enum) instead of there being a single instance and multiple pointers to that instance, a new instance is created when we assign the value to a new instance. It is a copy of the original and they are independent of one another.

Array type

Since the Swift Array type is a struct it has the behaviour of copying rather than pointing. While NSArray and NSMutableArray remain classes (as in ObjC) and so have the pointing behaviour of a class.

Making a struct behave like a class

You've already seen others post about inout properties, and you can also use computed variables to partially replicate the behaviour of a pointer:

struct MyStruct {
    lazy var map = [String:[String]]()
    var list:[String] {
        mutating get {
            if map["list"] == nil {
                map["list"] = [String]()
            }
            return map["list"]!
        }
        set {
            map["list"] = newValue
        }
        
    }
}

var mS = MyStruct()
mS.list = ["test"]
mS.map // ["list": ["test"]]
mS.list // ["test"]

While outside a type instance you might do the following:

var map = [String:[String]]()
var list = [String]()
var appendToList = { (a:String) in
    list.append(a)
    map["list"] = list
}
appendToList("test") // list and map["list"] are both updated
Community
  • 1
  • 1
sketchyTech
  • 5,746
  • 1
  • 33
  • 56
1

Array in Swift is defined as a struct, i.e. a value type. When you assign another variable to another variable of value type, it creates a copy of that second variable:

map["list"] = list   // store a **copy** of list into map

To see the difference between value and reference type, change your array to its ObjectiveC cousin, NSMutableArray, which is of reference type:

var map = [String: NSMutableArray]()
var list = NSMutableArray()
map["list"] = list

list.addObject("test")

print(map)   // ["list": (test)]
Code Different
  • 90,614
  • 16
  • 144
  • 163
0

As Code Different says, Arrays in Swift are structs, so you are storing a copy of the array.

If you really want to keep using Swift arrays, I recommend to fill the array "list" first and then copy to "map". That should work for you.

Community
  • 1
  • 1
iGongora
  • 23
  • 4