In the program structure section in Lesson 1, we mentioned that functions are a key part of Swift's structure, and are units of code that can accept parameters and can return values. In this section, we'll dive into Swift functions, learning how to implement and call them in the course of a Swift application.

Before diving into Swift function syntax, we should summarize some key points about how functions are used in Swift, and in modern software development generally:

  • Functions are units of code that carry out some specific task.
  • In terms of lines of code, functions should be short. How many lines of code is a maximum for a function has been a topic of debate for decades. However, long functions often do not satisfy the specific task definition.
  • All things being equal, it's better to have a complex process broken into smaller functions, rather than combined into a large, complex function.
  • All things being equal, a function that references its parameters—but not global variables—is more maintainable, less error-prone and more testable.

For many developers new to Swift, its function declaration syntax may seem unfamiliar. Swift's function syntax is probably most similar to Pascal, but also has ideas from C++, Objective-C, and others. With some practice, Swift code will begin to feel elegant and familiar.

The basic syntax for a Swift function that accepts parameters is as follows:

The basic syntax for a Swift function that accepts no parameters is as follows:

The basic syntax for a Swift function that accepts no parameters and returns nothing is as follows:

Let's break down the syntax:

The following is a basic Swift function:

This function is defined with the name printArray. It accepts a single parameter—an array of String, which it will iterate and print. Finally, it returns a single Int value, which is the count of String values that it printed to the console.

Returning values from functions is largely consistent with C-inspired programming languages you've probably used in the past. When processing is finished, a function simply uses the return keyword to return a value to the caller. In the previous function example, we concatenated two String variables, and returned the result using the return keyword.

The following are some Swift-specific notes regarding returning values from functions:

Swift can also return complex and custom types from functions. For example, your functions can return instances of structures, instances of classes, and references to other functions. So, while returning a single value may seem limiting, Swift actually provides tremendous flexibility in its function return features.

The Swift compiler will generate a warning if you call a function that returns a result but do not use or assign that result in your code. For example, consider the following function:

Suppose we had called it with this line of code:

The Swift compiler doesn't understand why we would call a function that returns a value but not use that value. While not an error, it will generate a compile-time warning.

There are times when you may implement a function that returns a value which may not be important to the calling program. This is especially true when developing frameworks for use by other applications—where you provide functionality that the consumer of the framework may not feel is important to them.

For example, a log() function may return a Bool indicating how many characters of data were written to the log—even if the callers don't consider this information interesting:

Suppose the caller calls this function without using the Int return value:

The compiler will generate the following warning:

To suppress the warning, simply add the @discardableResult function attribute with the declaration:

Now, knowing that you expect callers might disregard the return value, the Swift compiler will no longer issue a warning at the point of the function call.

In the previous section, we used the function attribute discardableResult to provide additional information to the Swift compiler about the usage of a function we declared. In that case, the discardableResult attribute informs the compiler that we expect callers of a function may ignore the value returned from the function.

You may encounter and use other function attributes in the course of your Swift programming. The following are some of the more common function attributes:

Name

Description

objc

Used to generate Objective-C calling wrappers. Used when a Swift function you write should also be callable from an Objective-C module.

nonobjc

Suppresses the generation of Objective-C compatibility wrappers where it otherwise would be created. Typically used to resolve circular references that occasionally occur between Swift and Objective-C modules.

available

Informs the compiler which OS versions, Swift versions, or platforms are required for a function to be called.

discardableResult

The return value may be ignored by function callers without generating a compiler warning message.

IBAction

Marks a function as a call point that can be connected to an Interface Builder design file.

introduced

The first version of the platform or language where this function was available.

deprecated

Marks a function as deprecated.

For more complete information about language attributes, refer to the Swift documentation at https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html.

In each example so far, when we've written a function that provided values back to the point of function call, we've used function return to do so. Using the return statement to return a new value to a function caller is the most common approach, and the approach you should use by default.

However, using return to send data back to the function's caller returns a new value. In some cases, it may be desirable to modify variables that are owned by the caller—rather than return new values. Swift provides inout parameters as a way to accomplish this.

Consider the following function, which swaps two Int values without inout parameters:

The parameters a and b are read-only within the function, and swapValues cannot change them. Instead, the function allocates a new tuple and returns it with the values in a swapped order. The caller assigns these new values into the tuple (a1, b1). The caller must then reassign the values of a and b to achieve the desired result.

By using inout parameters, we can write a function that can modify the values of the parameter values, and allow it to make the changes on behalf of the code in the calling scope:

In the swapValues2 version, the inout keyword makes the parameters a and b read/write variables, so the code can reassign their values.

When calling inout parameters, an ampersand (&) must be placed before the variable being passed into the function. If you've used C or C++, you may recognize this syntax, which in those languages means the address of. The effect is the same as in those languages—the callee of the function is given permission to change the content of the variable provided as a parameter.

Many languages, including Swift, have the ability to pass in functions by reference, which can then be called from within the called function. In many languages, the function passed as a parameter is referred to as a callback function, since it has the effect of allowing a function to call back to the caller's code to perform some action after the function has done what was asked of it.

In the following example, let's rewrite the makeSentence function with a version that passes in a callback function as a parameter:

The output of this code is identical to makeSentence1 and makeSentence2 that we saw earlier.

In the function as parameter version 3, the makeSentence3 function has no knowledge of how the printing will be done. It simply calls the function it's provided through the thenPrint parameter, and calls it when the sentence is finished.

The function as parameter technique is commonly used in scenarios where there may be more than one predefined alternative ending for a program flow. In the preceding example, we could have one printSentence routine that printed to the console, a second that posted the result to a web service, and a third that displayed a message box.

Functions as parameters are very powerful and flexible, and are commonly used in Swift programming. Next, we'll learn about a similar—and even more commonly used variant of this technique: closures.

In the previous section, you learned how to pass a named function into another function, allowing the latter to call the former at the appropriate time.

Closures are another way to pass code to a function, which it can then call later. In the case of closures, however, we're passing a block of code that can be called from within the function.

The two approaches are very similar—and to some extent, interchangeable. In both cases, the called function will run a block of code using the name specified by its own parameter name. A closure is primarily different in that a function as parameter has a name in the caller's scope, while a closure is an unnamed block of code.

Closures in Swift are the most common approach to providing code to execute after asynchronous processing has completed. The following function uses a closure to download data from the web. You'll fully implement this solution in the following activity:

This ends our look at functions. In this section, we took a deep dive into how Swift implements functions and the importance of functions in developing virtually any application in Swift.

For application developers who use any type of web service, processing the results of asynchronous web service requests will be a daily requirement. Let's apply what you've learned about writing functions to implement real-world web service requests:

  1. Launch Xcode, and open the start project named Functions - Starter.xcodeproj.
  2. Add the following function to the ViewController.swift file before the closing brace of the ViewController class:
    func doWebRequest() -> String {
        var webPageContent = "No data yet!"
    
        let url = URL(string: "https://www.packtpub.com")!
        let urlRequest = URLRequest(url: url)
        let session = URLSession(
             configuration: URLSessionConfiguration.default)
    
        let task = session.dataTask(with: urlRequest) {
            (data, response, error) in
            webPageContent = String(data: data!, encoding: .utf8)!
        }
        task.resume()
    
        return webPageContent
    }
  3. Change the start project's startButtonTapped method to contain the following body:
        @IBAction func startButtonTapped(_ sender: UIButton) {
            self.updateTextView(doWebRequest())
        }
  4. Run the application with a simulator, press the Start Web Request button, and observe the output in the TextView underneath the button.
    • What happened? Why didn't that work?
    • The doWebRequest function, as written, doesn't wait for the web request to complete before returning the webPageContent String variable.
  5. Replace the doWebRequest function with the following implementation:
            func doWebRequest(closure: @escaping (_ webSiteContent: String?) -> Void) {
            let url = URL(string: "https://www.packtpub.com")!
            let urlRequest = URLRequest(url: url)
            let session = URLSession(configuration: URLSessionConfiguration.default)
    
            let task = session.dataTask(with: urlRequest) {
                (data, response, error) in
                let content = String(data: data!, encoding: .utf8)
                closure(content)
            }
            task.resume()
        }
  6. Modify the startButtonTapped function as follows, so that it calls the new doWebRequest version, which accepts a closure parameter:
        @IBAction func startButtonTapped(_ sender: UIButton) {
            doWebRequest { (content) in
                    self.updateTextView(content!)
            }
        }
  7. Run the application on a simulator, press the Start Web Request button, and observe the output in the debug console. You should now see the HTML source for the web page assigned to the url variable.

Assuming you encountered no exceptions or web connectivity problems, the program you coded for the web request activity will have worked just fine. But it lacks any error handling and is not up to scratch to include in a production application!

Open the project in the Functions – Finished with Error Handling folder, and review it. Then, ask yourself what steps have been taken to ensure this code will not crash the application when external data is not returned as expected.