3

I was trying to modify a parameter of function from within a escaping closure as below:

var completion = [()->Void]()

func addCompletion(closure: @escaping ()->Void) {
    completion.append(closure)
}

func testAdd(v: inout Int) {
    addCompletion{
        v = 1  // Compiler tells 'Escaping closure captures 'inout' parameter 'v'
        print("hello1 \(v)")
    }
}

var x = 0
testAdd(v:&x)

for comp in completion {
    comp()
}

I was wondering if there is another way to let escaping closure change surrounding variable (as function's inout parameter), besides making it into reference type as a class object.

Alan Hoo
  • 445
  • 3
  • 12

1 Answers1

4

inout is specified to have a "copy-in copy-out" behaviour. The value of x is copied when you call the function. In the function body, the copy of x can be modified, or whatever. Then, when the function returns, the copy is copied again, and assigned to the original x. "Call-by-reference" could happen as an optimisation.

This explains why you can't modify an inout parameter in an escaping closure. The swift compiler can't possibly know when every escaping closure returns, to copy the modified value back.

You can use an actual pointer:

func testAdd(v: UnsafeMutablePointer<Int>) {
    addCompletion{
        v.pointee = 1 // you just need to change all references to "v" to "v.pointee"
        print("hello1 \(v.pointee)")
    }
}

Alternatively, if you don't like pointers, you can also do this trick (adapted from my other answer):

func testAdd(modifyV: @escaping ((inout Int) -> Void) -> Void) {
    addCompletion{
        modifyV { v in // here you can change v however you like!
            v = 1
            print("hello1 \(v)")
        }
    }
}

You just need to change the caller to:

testAdd { $0(&x) }
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Both work, thanks. Second solution in my opinion wraps modification by another closure which can be reference type, it really takes me quite while to comprehend. – Alan Hoo Jan 03 '21 at 05:09