10

I want to pass an array to an object and store a reference to this array. I want to be able to modify this array within this object and make sure that it's modified everywhere else.

Here is what I am trying to accomplish (how the code doesn't work)

class Foo {
   var foo : Array<Int>

   init(foo: Array<Int>) {
      self.foo = foo
   }

   func modify() {
      foo.append(5)
   }
}

var a = [1,2,3,4]
let bar = Foo(a)
bar.modify()
print(a) // My goal is that it will print 1,2,3,4,5

My findings so far

A) The array (by default) are passed strange way. It's a reference until you modify an array length. As soon as you modify a length it will be copied and modified. As result, if I append or delete anything from it in the object it won't be seen outside

B) I can use inout on a function parameter. This will allow me to modify it within this function. However, as soon as I will try to assign it to some object member I am again struck by A)

C) I can wrap an array in some Container class. This probably is the cleanest way. However, I serialize/deserialize these objects and I would rather not put it in Container (because I will have to work around some things for serialization and deserialization and sending it to the server).

Are there anything else? Am I missing some Swift construct which allows me to do that?

5
  • How would you expect to change "a" declaring it as a constant? Also you would need to pass it using inout parameter and pass it to your method with "&" prefix Commented Dec 28, 2015 at 0:00
  • I can change "a" to be a var. Using & and inout parameter will work only if I modify the array just in the method to which it was passed. As soon as I do "self.foo = foo" it will stop working. Meaning modification of self.foo won't affect "a" Commented Dec 28, 2015 at 0:06
  • declare it as var, define var foo:[Int] = [] { willSet(newArray) { a = newArray } } Commented Dec 28, 2015 at 0:16
  • The problem is even worse than your question implies. You say that things go horribly wrong if you append or delete anything to/from the array. But in fact, even if you stick to using a fixed-size array and just change the value of one of the array elements then the changed array is not available to the calling program. In my case I'm working with a 100 KB byte array, and the Swift idea that it should be considered a value object is totally foreign to my 40 years of experience with programming. Oh, well, "the world has moved on". Just curious, what did you eventually do in this situation? Commented Dec 29, 2016 at 10:35
  • @RenniePet I ended up using a wrapper. However, it was kind-of complex for me, because of these serialization/deserialization problems which I mentioned. Commented Dec 29, 2016 at 18:15

5 Answers 5

6

You'll have to use an NSArray or NSMutableArray for this because Swift Arrays are value types so any assignment will make a copy.

Sign up to request clarification or add additional context in comments.

Comments

2

You could make use of Swifts (very un-swifty) UnsafeMutablePointer.

Since (from your post) the behaviour references to arrays can't really seem be trusted, instead keep an UnsafeMutablePointer companion to the class inner array foo as well as any "external" arrays that you want to be binded to foo, in the sense that they are both just pointers to same address in memory.

class Foo {
    var foo : [Int]
    var pInner: UnsafeMutablePointer<Int>

    init(foo: [Int]) {
        pInner = UnsafeMutablePointer(foo)
        self.foo = Array(UnsafeBufferPointer(start: pInner, count: foo.count))
    }

    func modify(inout pOuter: UnsafeMutablePointer<Int>) {
        foo.append(5) // <-- foo gets new memory adress
        pInner = UnsafeMutablePointer(foo)
        pOuter = pInner
    }
}

var a = [1,2,3,4] // first alloc in memory
var pOuter: UnsafeMutablePointer<Int> = UnsafeMutablePointer(a)
var bar = Foo(foo: a) // 'bar.foo' now at same address as 'a'
print(bar.foo) // [1,2,3,4]
bar.modify(&pOuter) // -> [1,2,3,4,5]
a = Array(UnsafeBufferPointer(start: pOuter, count: bar.foo.count))

/* Same pointer adress, OK! */
print(bar.pInner)
print(pOuter)

/* Naturally same value (same address in memory) */
print(bar.foo)
print(a)

Pointers can be dangerous though (hence the fitting type name), and, again, very un-swifty. Anyway...

/* When you're done: clear pointers. Usually when using
   pointers like these you should take care to .destroy
   and .dealloc, but here your pointers are just companions
   to an Array property (which has a pointer an reference
   counter itself), and the latter will take care of the 
   objects in memory when it goes out of scope.            */
bar.pInner = nil
pOuter = nil

Now, what happens when either a or foo goes out of scope, will it break the variable that are not out of scope, or does Swift contain some clever reference counting that realises a memory address is still in use? I haven't investigated this, but feel free to indulge yourself in that.

2 Comments

Interesting. You will have to pass pOuter in each method which modifies inner array though.
Yeah it's not the Swiftiest or prettiest solution, but you'll know for sure that your arrays is not only "modified everywhere else", but are, in fact, the same arrays in memory. Also, note that for point A) in your answer: swift allocates exactly the space you need when initializing an array with values. If you append a value, it needs to allocate space for full array+new element elsewhere, then copy "old" array + new entry to new space, and finally release old space. This is why it seems as if it's randomly using reference/value copy.
0

From the Swift Programming Language,

Structures are always copied when they are passed around in your code, and do not use reference counting.

If you examine the contents of the array variable, you will see that indeed the append works:

class Foo {
  var foo : Array

  init(_ foo: Array) {
    self.foo = foo
  }

  func modify() {
    foo.append(5)
  }

  func printFoo() {
    print("self.foo: \(foo)")
  }
}

let a = [1,2,3,4]
let bar = Foo(a)
bar.modify()
bar.printFoo()
print("a: \(a)")

produces

self.foo: [1, 2, 3, 4, 5]
a: [1, 2, 3, 4]

You have taken a copy of a, not a reference to a.

4 Comments

Yes. I know that it will append to an array inside of the object. My goal is to make sure that an array (in this case "a") which was passed to the object is also modified. So, I am looking for a way for it to print out [1,2,3,4,5]
I would have a very careful think about what you are trying to do here. By wanting to duplicate an external array's contents every time you make a change to an internal array variable, you are subverting the concept of encapsulation. Would it not be better to manipulate the internal array variable and then expose it when required? I think that Leo's answer is clever by using the power of property observers but it copies the whole array every time an array element is changed.
I disagree regarding encapsulation. Let say we were passing and object to Foo and Foo was calling some method which will modify this object content. This won't break encapsulation. Generally speaking Array<T> is the object. We are calling a method (append) on it. So, it shouldn't be treated any different.
Your problem is this: "Let say we were passing and object to Foo and Foo was calling some method which will modify this object content." You should understand that you're not passing a reference, but the copy of a value type.
-1

a is declared a constant hence cannot be modified. If you are planning to modify the contents of a, declare it as a variable. i.e.,

var a = [1,2,3,4]

1 Comment

You are right. a should be declared as "var". However, the problem is more deeper than that.
-1

I haven't tested this but, as you are using a class to wrap the array, I see no reason why the following would not work.

class Foo {
   var foo : Array<Int>

   init(foo: inout Array<Int>) {
      self.foo = foo
   }

   func modify() {
      foo.append(5)
   }
}

let a = [1,2,3,4]
let bar = Foo(&a)
bar.modify()
print("a: \(a)") // a: [1,2,3,4,5]

1 Comment

Please justify your negative vote.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.