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:
func functionName(parm1: Type1, parm2: Type2) -> ReturnType {
The basic syntax for a Swift function that accepts no parameters is as follows:
func functionName() -> ReturnType {
The basic syntax for a Swift function that accepts no parameters and returns nothing is as follows:
func functionName() {
Let's break down the syntax:
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:
func printArray(array: [String]) -> Int { var count = 0 for string in array { print(string) count += 1 } return count }
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:
printArray(array: strings)
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:
func concatenatedNames(n1: String, n2: String) -> String { return "\(n1) \(n2)" }
While using short variable names within the function is convenient, calling the function may seem unintuitive from the point of view of the programmer calling the function:
let fullName = concatenatedNames(n1: "John", n2: "Smith")
Argument labels allow us to create a function that allows the caller of our function to refer to the function's parameters by different names than we use within the function.
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:
let fullName = concatenatedNames(firstName: "John", lastName: "Smith")
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:
func addTwoInts(_ a: Int, _ b: Int) -> Int { return x + y }
By specifying the underscore (_) character for the argument label associated with each parameter, the caller need not specify a parameter name. The compiler will simply match each passed parameter to the function's passed parameter in the same order in which they are declared:
let c = addTwoInts(4, 5) // c will be 9
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.
The following function prints the temperature. It assumes the provided value is in Centigrade units, if units are not specified:
enum TemperatureUnits : String { case celcius = "\u{00B0}C" case fahrenheit = "\u{00B0}F" } func printTemperature(value: Double, units: TempUnits = .celcius) { print("The temperature is \(value)\(units.rawValue)") }
Because a default value is provided for units, we can omit the units when calling the function:
printTemperature(value: 17.5) // The temperature is 17.5°C
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.
Use an Xcode playground to implement a function that uses a variety of parameter techniques covered until now.
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))
An example output is given here:
John Doe 5 Covington Square Birmingham 01234 USA ===== John Doe 5 Covington Square Birmingham 01234
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:
func addTwoInts(_ a: Int, _ b: Int) -> Int { return x + y }
Suppose we had called it with this line of code:
addTwoInts(4, 5) // return is "discarded"
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:
func log(_ message: String) -> Int
Suppose the caller calls this function without using the Int return value:
log("app started!")
The compiler will generate the following warning:
Result of 'log(message:) is unused
To suppress the warning, simply add the @discardableResult
function attribute with the declaration:
@disdcardableResult func log(_ message: String) -> Int
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 |
---|---|
|
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:
import Foundation func makeSentence1(_ words: String...) -> String { var sentence = "" for word in words { sentence += "\(word) " } return "\(sentence.trimmingCharacters(in: [" "]))." } let sentence1 = makeSentence1("Hello", "World", "And", "Universe")
In this example, the makeSentence1
function will accept any number of words as input, and then uses the for…in
loop to combine them into a sentence.
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:
func makeSentence2(_ words: [String]) -> String { var sentence = "" for word in words { sentence += "\(word) " } return "\(sentence.trimmingCharacters(in: [" "]))." } let sentence2 = makeSentence2(["Hello", "World", "And", "Universe"])
The output of both makeSentence1
and makeSentence2
is the same:
Hello World And Universe.
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:
func swapValues1(_ a: Int, _ b: Int) -> (Int, Int) { return (b, a) } var a = 3 var b = 2 let (a1,b1) = swapValues1(a, b) a = a1 b = b1 print("\(a), \(b)") // 2, 3
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:
var a = 3 var b = 2 func swapValues2(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp } swapValues2(&a, &b) print("\(a), \(b)") // 2, 3
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.
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:
func factorialWithRecursion(n: Int) -> Int { return n == 0 ? 1 : n * factorialWithRecursion(n: n-1) }
The following line calls the recursive function, assigning the result to a variable named factorial2
:
let factorial2 = factorialWithRecursion(n: 6) // 720
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:
import Foundation func makeSentence3(_ words: [String], thenPrint: (String) -> Void) { var sentence = "" for word in words { sentence += "\(word) " } thenPrint("\(sentence.trimmingCharacters(in: [" "])).") } func printSentence(_ sentence: String) { print(sentence) } makeSentence3(["Hello", "World", "and", "Universe"], thenPrint: printSentence(_:))
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:
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() }
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!
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.