YichenBman
YichenBman

Reputation: 5661

Inout variable set in in UIAlertAction closure not changing value

I have a function with an inout paramter: enabled: Bool The object I'm referencing (I know inout isn't technically a reference...) and setting using this method is a stored property on a UIViewController var enabled = false

I have multiple booleans triggering different things, and I want to use one method to set them.

So I call the method:

self.determineEnabled(&self.enabled)

Below is the code, and I've used comments to explain whats happening

Code:

func determineEnabled(inout enabled: Bool) {

    if enabled == false {
        enabled = true
   //self.enabled equals true now. This works. Its not in a closure...
    } else {

        let delete = UIAlertAction(title: "Disable", style: .Destructive, handler: { (action) -> Void in

            enabled = false
            print(self.enabled)
            //This doesn't work. the inout variable equals FALSE
            //self.enabled equals true
            //If I set self.enabled = false.. Then it works, but I'm using a method because my app could have dozens of these enabled bools on this view controller.

        let alertController = UIAlertController(title: "Change Bool", message: "", preferredStyle: UIAlertControllerStyle.Alert)

        alertController.addAction(delete)
        self.presentViewController(alertController, animated: true, completion: nil)
    }
}

My app is obviously a more complex than this chunk of code, but I can verify this problem exists within this chuck of code.

I'll be honest that I don't thoroughly understand closures as much as I'd like..

But if I can use self.enabled to correctly change the value of enabled, what is stopping swift from changing setting the inout enabled variable?

UPDATE:

Here is a link from the docs that specifically mention my problem: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545

"There is no copy-out at the end of closures or nested functions. This means if a closure is called after the function returns, any changes that closure makes to the in-out parameters do not get copied back to the original."

Swift evolution on the topic: https://github.com/apple/swift-evolution/blob/master/proposals/0035-limit-inout-capture.md

Upvotes: 1

Views: 502

Answers (2)

Paulw11
Paulw11

Reputation: 114975

You said it yourself in your question;

I know inout isn't technically a reference

From the Apple Swift book

An in-out parameter has a value that is passed in to the function, is modified by the function and is passed back out of the function to replace the original value.

You are modifying the value in a closure that executes some time later when the user interacts with the alert. At this point determineEnabled has already returned and stored the value of the inout parameter.

If an inout parameter was a reference, like a C-style pointer, then enabled would be pointing to a chunk of memory that stores self.enabled and when the value was modified in the closure, self.enabled would be modified.

You can see how this works if you create a simple class with a boolean property and then pass an instance of this class to your determineEnabled function (without using inout). Since objects are passed by reference, a subsequent update to the object's property in the closure will be visible anywhere that same object reference is used;

Upvotes: 2

matt
matt

Reputation: 535617

In Swift, functions are closures. Closures capture state. At the time we encounter the anonymous function containing the code print(self.enabled), self.enabled is true. We know that, because if it were not, we wouldn't be here at all (we'd be in the first wing of the condition, if enabled == false). Therefore when print(self.enabled) is later actually executed, it will print true, because that was the state of things when it captured its value.

Upvotes: 3

Related Questions