Capturing a struct reference in a closure doesn

2019-02-19 03:34发布

问题:

I am trying to see if I can use structs for my model and was trying this. When I call vm.testClosure(), it does not change the value of x and I am not sure why.

struct Model
{
    var x = 10.0
}


var m = Model()

class ViewModel
{
    let testClosure:() -> ()

    init(inout model: Model)
    {
        testClosure =
        {
            () -> () in
            model.x = 30.5
        }
    }
}



var vm = ViewModel(model:&m)

m.x

vm.testClosure()

m.x

回答1:

An inout argument isn't a reference to a value type – it's simply a shadow copy of that value type, that is written back to the caller's value when the function returns.

What's happening in your code is that your inout variable is escaping the lifetime of the function (by being captured in a closure that is then stored) – meaning that any changes to the inout variable after the function has returned will never be reflected outside that closure.

Due to this common misconception about inout arguments, there has been a Swift Evolution proposal for only allowing inout arguments to be captured by @noescape closures. As of Swift 3, your current code will no longer compile.

If you really need to be passing around references in your code – then you should be using reference types (make your Model a class). Although I suspect that you'll probably be able to refactor your logic to avoid passing around references in the first place (however without seeing your actual code, it's impossible to advise).

(Edit: Since posting this answer, inout parameters can now be compiled as a pass-by-reference, which can be seen by looking at the SIL or IR emitted. However you are unable to treat them as such due to the fact that there's no guarantee whatsoever that the caller's value will remain valid after the function call.)



回答2:

Instances of the closure will get their own, independent copy of the captured value that it, and only it, can alter. The value is captured in the time of executing the closure. Let see your slightly modified code

struct Model
{
    var x = 10.0

    mutating func modifyX(newValue: Double) {
        let this = self
        let model = m
        x = newValue
// put breakpoint here
//(lldb) po model
//▿ Model
//  - x : 30.0
//
//(lldb) po self
//▿ Model
//  - x : 301.0
//
//(lldb) po this
//▿ Model
//  - x : 30.0            
    }
}

var m = Model()

class ViewModel
{

    let testClosure:() -> ()

    init(inout model: Model)
    {
        model.x = 50
        testClosure =
            { () -> () in
                model.modifyX(301)
        }
        model.x = 30
    }
}

let mx = m.x

vm.testClosure()

let mx2 = m.x


回答3:

Here is what Apple says about that.

Classes and Structures

A value type is a type that is copied when it is assigned to a variable or constant, or when it is passed to a function. [...] All structures and enumerations are value types in Swift

Methods

Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.

However, if you need to modify the properties of your structure or enumeration within a particular method, you can opt in to mutating behaviour for that method. The method can then mutate (that is, change) its properties from within the method, and any changes that it makes are written back to the original structure when the method ends. The method can also assign a completely new instance to its implicit self property, and this new instance will replace the existing one when the method ends.

Taken from here