Swift optional chaining and method argument evaluation

21 Apr, 2015
Xebia Background Header Wave
Everyone that has been programming in Swift knows that you can call a method on an optional object using a question mark (?). This is called optional chaining. But what if that method takes any arguments whose value you need to get from the same optional? Can you safely force unwrap those values? A common use case of this is a UIViewController that runs some code within a closure after some delay or after a network call. We want to keep a weak reference to self within that closure because we want to be sure that we don't create reference cycles in case the closure would be retained. Besides, we (usually) don't need to run that piece of code within the closure in case the view controller got dismissed before that closure got executed. Here is a simplified example: [objc] class ViewController: UIViewController { let finishedMessage = "Network call has finished" let messageLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() someNetworkCall { [weak self] in self?.finished(self?.finishedMessage) } } func finished(message: String) { messageLabel.text = message } } [/objc] Here we call the someNetworkCall function that takes a () -> () closure as argument. Once the network call is finished it will call that closure. Inside the closure, we would like to change the text of our label to a finished message. Unfortunately, the code above will not compile. That's because the finished method takes a non-optional String as parameter and not an optional, which is returned by self?.finishedMessage. I used to fix such problem by wrapping the code in a if let statement: [objc] if let this = self { this.finished(this.finishedMessage) } [/objc] This works quite well, especially when there are multiple lines of code that you want to skip if self became nil (e.g. the view controller got dismissed and deallocated). But I always wondered if it was safe to force unwrap the method arguments even when self would be nil: [objc] self?.finished(self!.finishedMessage) [/objc] The question here is: does Swift evaluate method argument even if it does not call the method? I went through the Swift Programming Guide to find any information on this but couldn't find an answer. Luckily it's not hard to find out. Let's add a method that will return the finishedMessage and print a message and then call the finished method on an object that we know for sure is nil. [objc] override func viewDidLoad() { super.viewDidLoad() let vc: ViewController? = nil vc?.finished(printAndGetFinishedMessage()) } func printAndGetFinishedMessage() -> String { println("Getting message") return finishedMessage } [/objc] When we run this, we see that nothing gets printed to the console. So now we know that Swift will not evaluate the method arguments when the method is not invoked. Therefore we can change our original code to the following: [objc] someNetworkCall { [weak self] in self?.finished(self!.finishedMessage) } [/objc]

Explore related posts