1

I have the following playground code:

import UIKit
import XCPlayground

class A {

    var arr : [UIImage] = []

    func addItem(inout localArr: [UIImage]) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
            localArr.append(UIImage())
            print("from inside function localArr: \(localArr)")
            print("form inside function: \(self.arr)")
        }
    }
}

let a = A()
a.addItem(&a.arr)
print("instant print :\(a.arr)")

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(3 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
    print("print after delay: \(a.arr)")
}

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

The output is:

instant print :[]
from inside function localArr: [<UIImage: 0x7f99e8706f10>, {0, 0}]
form inside function: []
print after delay: []

My question is, why is localArr not the same as self.arr inside the addItem and not the same as a.arr outside? My expectation was that when I pass the parameter as inout I should be able to operate on the actual object, not the copy, but clearly this is not what happens.

Edit: So thanks to dfri answer I know why this doesn't work. The inout is really call-by-copy-restore, check another answer here. Now, any suggestion on how to actually make the closure to capture the reference to the original object? Or perhaps I should use other technique to achieve what I want?

Community
  • 1
  • 1
lawicko
  • 7,246
  • 3
  • 37
  • 49

2 Answers2

2

For some theory on the inout keyword, see the following answer:

Do not depend on the behavioral differences between copy-in copy-out and call by reference.

...

When the function returns, your changes to the original are overwritten with the value of the copy. Do not depend on the implementation of the call-by-reference optimization to try to keep the changes from being overwritten.

Now, your addItem function will finish its call immediately and hence complete the inout copy-in/copy-out task prior to the delayed dispatch in the function. This makes it inherently bad to use delayed dispatches within a method of inout parameters, at least if the delay is one that tries to mutate the inout parameter.

To see this, lets track the references (rather than tracking the values) of some array, and how they possibly show mutation of the array during the runtime of our example.

func foo(inout bar: [Int]) {
    var pBar : UnsafePointer<Int> = UnsafePointer(bar)
    print("2: \(pBar)")
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
        pBar = UnsafePointer(bar)
        print("3: \(pBar)")
        bar[0] = 2
        pBar = UnsafePointer(bar)
        print("4: \(pBar)")
    }
}

var a : [Int] = [1]
var pA : UnsafePointer<Int> = UnsafePointer(a)
print("1: \(pA)")
foo(&a)
print("foo call to a finished, a = \(a)")

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(5 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
    print("value of a naturally not changed here = \(a)")
    pA = UnsafePointer(a)
    print("5: \(pA)")
}

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

The output is quite self-explanatory:

1: 0x00007fe19271e930
2: 0x00007fe19271e930
foo call to a finished, a = [1] <-- call to foo finished, 'inout' procedure complete
3: 0x00007fe19271e930           <-- dispatch in `foo` starts
4: 0x00007fe1927085e0           <-- mutates 'bar': 'bar' copied (and never 
                                    "returned" as this step is already finished)
value of a naturally not changed here = [1]
5: 0x00007fe19271e930           <-- naturally 'a' wont see the effect of the
                                    delayed mutation in foo
Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Thanks for the explaination, this really helps to understand the `inout`. Do you have any suggestions on how to actually be able to mutate the array (or any other passed object) inside the delayed block? – lawicko Jan 27 '16 at 12:08
  • @lawicko Happy to help. I have to leave for some hours, but I can look at it when I get back, given that no one else have helped you by then. – dfrib Jan 27 '16 at 12:11
  • @lawicko use a reference type. I believe NSArray will do – R Menke Jan 27 '16 at 13:04
1

Swift Arrays are value types. Which always copy on assign. Use NSMutableArray if you want to reference the original array. Then there is also no need for inout

class A {

    var arr : NSMutableArray = []

    func addItem(localArr: NSMutableArray) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
            localArr.addObject(UIImage())
            print("from inside function localArr: \(localArr)")
            print("form inside function: \(self.arr)")
        }
    }
}

let a = A()
a.addItem(a.arr)
print("instant print :\(a.arr)")

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (Int64)(3 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
    print("print after delay: \(a.arr)")
}

print:

instant print :(
)
from inside function localArr: (
    "<UIImage: 0x7ffe1872b5f0>, {0, 0}"
)
form inside function: (
    "<UIImage: 0x7ffe1872b5f0>, {0, 0}"
)
print after delay: (
    "<UIImage: 0x7ffe1872b5f0>, {0, 0}"
)
R Menke
  • 8,183
  • 4
  • 35
  • 63
  • Thanks, I have upvoted your comment as the NSMutableArray indeed works in this case. However dfri answer actually explained the original question, which was why the the original code didn't work. – lawicko Jan 28 '16 at 10:22
  • @lawicko I know ;) I was just trying to help you out while dfri was at work. – R Menke Jan 28 '16 at 11:26