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:
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:
func
signals that what follows is a function declaration. In Swift, there's no distinction between functions (that return a value) and procedures (which do not)—both begin with func.func
is the name of the function. The naming rules for functions are the same as for Swift variables, and like variables, it's conventional to begin a function name with a lowercase letter.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.
In the previous section, we created a function with a parameter named array, which is the parameter label we used when calling the function:
Swift supports optional argument labels for parameters, which will be familiar to Objective-C programmers, and likely unfamiliar to others.
Consider the following function, which returns the concatenation of two strings:
For example, we might add argument labels to the function as follows:
func concatenatedNames(firstName n1: String, lastName n2: String) -> String { return "\(n1) \(n2)" }
Adding the argument labels doesn't change the implementation of the function at all—we still use the variable names n1
and n2
within the function. But the caller of the function may now use the more intuitive argument labels to refer to the parameter names:
In addition to changing the calling reference for a function's parameters, argument labels can be used to remove names for input parameters. Doing so can make functions feel more like calling C or Objective-C functions.
For example, consider the following function:
While excluding parameter names is a powerful feature, it should be used appropriately. Use this technique when the parameters passed to a function are obvious. For example: addTwoInts(a,b)
, or logMessage("Opened file")
. Don't use optional parameter names to make Swift feel more like you're using a programming language you've used in the past. The default Swift behavior—explicitly specifying parameter names—is intentional, and makes code easier to read, understand and maintain.
Like many other C-inspired languages, you can provide parameter default values for any parameter. When a default value is specified in the function definition, the function caller can omit the parameter—and the default value will be substituted instead.
Because a default value is provided for units, we can omit the units when calling the function:
In any programming language, functions are a core language element used to make programs modular, readable, and maintainable, and virtually every program you write will use functions extensively. Let's practice what you've learned about Swift functions.
Function.playground
.func buildAddress(_ name: String, address: String, city: String, zipCode postalCode: String, country: String? = "USA") -> String { return """ \(name) \(address) \(city) \(postalCode) \(country ?? "") """ }
print(buildAddress("John Doe", address: "5 Covington Square", city: "Birmingham", zipCode: "01234")) print("=====") print(buildAddress("John Doe", address: "5 Covington Square", city: "Birmingham", zipCode: "01234", country: nil))
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:
return
keyword anywhere in the function. You can return from more than one place in the function, when appropriate (such as in a guard statement, which we'll cover shortly).return
keyword by itself.return
statement before the function's closing brace is optional.return (2, 4, 6)
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.
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:
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 |
---|---|
|
Used to generate Objective-C calling wrappers. Used when a Swift function you write should also be callable from an Objective-C module. |
|
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. |
|
Informs the compiler which OS versions, Swift versions, or platforms are required for a function to be called. |
|
The return value may be ignored by function callers without generating a compiler warning message. |
|
Marks a function as a call point that can be connected to an Interface Builder design file. |
|
The first version of the platform or language where this function was available. |
|
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.
Swift supports functions with variadic parameters—these are named parameters that accept more than one value of the same type.
For example, we could write a function to make a sentence containing a variable number of words:
Because Swift's array features are quite powerful, and declaring an ad hoc array of values of the same type is quite easy, you might also approach variadic parameters in the following way:
The output of both makeSentence1
and makeSentence2
is the same:
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.
Consider the following function, which swaps two Int values without inout
parameters:
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.
Like many modern programming languages, Swift supports recursive function calls. Recursion is simply the ability for a function to call itself from within its own body. Most canonical use cases for recursion come from computer science, for example, sorting algorithms. However, even if you're an end user app developer, there may be times when recursion will make your code more concise and efficient.
The following function uses recursion to calculate the mathematical factorial:
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.
The output of this code is identical to makeSentence1
and makeSentence2
that we saw earlier.
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 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:
Functions - Starter.xcodeproj
.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 }
@IBAction func startButtonTapped(_ sender: UIButton) { self.updateTextView(doWebRequest()) }
webPageContent
String variable.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() }
closure
). In this implementation, the function doWebRequest
has no return value. Instead, it waits until the web request has completed, and then returns the HTML response by calling the closure
function, passing the HTML to the closure as a parameter value.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!) } }
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!