cfischer
cfischer

Reputation: 24912

Passing a value type as reference in Swift

I have my model implemented as structs in Swift 3.0. Several of these structs have delegates that should be able to modify the model depending on the user's actions.

However, when I pass the struct to the delegate method, it gets copied.

How do you solve this? Can you force the compiler to pass this struct as a reference, or the only option is to use a class?

Upvotes: 4

Views: 3718

Answers (3)

Alexander
Alexander

Reputation: 63271

The whole point of using struct in the first place is that this is desirable behavior. It preserves the immutability of the data. inout can achieve this, but it's not recommended in the general case.

protocol Delegate {
    func callback(_ oldValue: Int) -> Int
}

struct IncrementerDelegate: Delegate {
    let step: Int

    func callback(_ oldValue: Int) -> Int {
        return oldValue + step
    }
}

struct Model {
    var i = 0
}

class Controller {
    var model = Model()
    var delegate: Delegate

    init(delegate: Delegate) {
        self.delegate = delegate
    }

    // To be called externally, such as by a button
    func doSomething() {
        // Delegate determains new value, but it's still the
        // controller's job to perform the mutation.
        model.i = delegate.callback(model.i)
    }
}

let delegate = IncrementerDelegate(step: 5)
let controller = Controller(delegate: delegate)
print(controller.model.i)
controller.doSomething() // simulate button press
print(controller.model.i)








protocol CrappyDelegate {
    func callback(_ model: inout Model)
}

struct CrappyIncrementerDelegate: CrappyDelegate {
    let step: Int

    func callback(_ model: inout Model) {
        model.i = 9999999
        // Just hijacked the models value,
        // and the controller can't do anything about it.
    }
}

class VulnerableController {
    var model = Model()
    var delegate: CrappyDelegate

    init(delegate: CrappyDelegate) {
        self.delegate = delegate
    }

    // To be called externally, such as by a button
    func doSomething() {
        // Controller leaks entire model, and has no control over what happens to it
        delegate.callback(&model)
    }
}

let crappyDelegate = CrappyIncrementerDelegate(step: 5)
let vulnerableController = VulnerableController(delegate: crappyDelegate)
print(controller.model.i)
controller.doSomething() // simulate button press
print(controller.model.i) // model hijacked

Upvotes: 2

David S.
David S.

Reputation: 6705

If you want to pass by reference, you should generally use a class not a struct.

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html states:

You can use both classes and structures to define custom data types to use as the building blocks of your program’s code.

However, structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure.

Upvotes: 0

Pedro Castilho
Pedro Castilho

Reputation: 10532

structs are always passed by value. The whole point of using a struct is to have it behave as a value type. If you need delegation (which usually implies mutable state), you should be using a class.

If you really really need to, you could force pass-by-reference by using an inout parameter, but that is not recommended in general. You could also use a box type to simulate passing by reference. But, in general, you should just use a class if you need reference behavior.

Upvotes: 4

Related Questions