Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I run code after the return statement in a function in Swift?

Consider the following code:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let validator:NSPredicate = NSPredicate(format:"SELF MATCHES %@","[A-Za-z0-9- ]+")
    if(validator.evaluateWithObject(string) || string == "" /* i.e. backspace */) {
        self.process(textField)
        return true
    }
    else {
        return false
    }
}

I want to actually run self.process(textField) AFTER the return statement, because before it, the text in the textField has not actually changed yet. This led me to wonder, why can't I just execute some code after the return statement? Why do functions always stop when the return statement happens?

I realize that's traditionally what return means, but is there an alternative? Like, is there a way to return a value from a function and then still keep going?

On the one hand this seems like a stupid question, but on the other hand, I feel like I can't be the first person to ever want to do this. It would be good enough if I could fire off something to run on the next cycle of the run loop, so maybe there's something in GCD that would help me.

like image 946
CommaToast Avatar asked Mar 20 '16 17:03

CommaToast


2 Answers

Since Swift 2.0 we have the keyword called "defer". A keyword that allows us to specify a block of code, a segment inside our function that will be executed just before the program control is transferred outside of the scope. Maybe for cleanup or other needs, actions that need to happen even if an error is thrown.

The code execution inside a defer block is deferred until the penultimate statement is executed, assuming the case where the last one is a return statement.

Here's how you can use it:

func anyFunction(someParameter: Int) -> Int {

    // Some code

    defer {

        // Code to be deferred.

    }

    return someValue

} // anyFunction

The position of the defer block should be placed anywhere within the curly braces and always before the return statement, for logical reasons and also to avoid the warning: "Code after 'return' will never be executed".

Some examples:

enter image description here

enter image description here

like image 103
WeiseRatel Avatar answered Oct 14 '22 12:10

WeiseRatel


The answer to your question is no. But the solution to your problem is simple.

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let validator:NSPredicate = NSPredicate(format:"SELF MATCHES %@","[A-Za-z0-9- ]+")
    if(validator.evaluateWithObject(string) || string == "" /* i.e. backspace */) {
        DispatchQueue.main.async {
            print("about to process textField, timestamp: \(Date().timeIntervalSince1970)")
            self.process(textField)
        }
        print("about to return true, timestamp: \(Date().timeIntervalSince1970)")
        return true
    }
    else {
        return false
    }
}

DispatchQueue.main.async will defer execution to the next iteration of the run loop. With those print() statements you'll see this in the console. Your timestamps will be different but you'll see the small difference (about 15/1000 of a second in this case).

about to return true, timestamp: 1612578965.103658

about to process textField, timestamp: 1612578965.1188931

If you require a specific delay use DispatchQueue.main.asyncAfter

The best explanation I have found on this is Matt Neuburg's book "iOS 14 Programming Fundamentals with Swift". See the Delayed Performance section.

like image 24
Murray Sagal Avatar answered Oct 14 '22 11:10

Murray Sagal