Capturing a struct reference in a closure doesn't allow mutations to occur
Asked Answered
G

3

3

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
Goaltender answered 5/5, 2016 at 5:29 Comment(1)
struct can't be pass by refernce #31495931Gimmick
I
4

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.)

Inject answered 5/5, 2016 at 9:54 Comment(2)
OK thanks so much that makes sense. It seems like its impossible to use structs for the model as there is no way for the view model to interact with the model??Goaltender
@Goaltender Depends on what the relationship between the ViewModel and Model is. Wouldn't it make more sense for the ViewModel to hold the value of your Model (with a property)? That way you can mutate the model from within your ViewModel – and access the changes from outside the ViewModel via the property. What's the actual use case of of your code?Inject
B
0

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
Bertold answered 5/5, 2016 at 8:57 Comment(0)
Y
0

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

Yseulte answered 5/5, 2016 at 9:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.