Advertisement
  1. Code
  2. Swift
Code

Swift from Scratch: Function Parameters, Types, and Nesting

by
Difficulty:BeginnerLength:MediumLanguages:
This post is part of a series called Swift from Scratch.
Swift from Scratch: An Introduction to Functions
Swift from Scratch: Closures

In the previous article, we explored the basics of functions in Swift. Functions, however, have a lot more to offer. In this article, we will continue our exploration of functions and look into function parameters, nesting, and types.

1. Local & External Parameter Names

Local Parameter Names

Let's revisit one of the examples of the previous article. The printMessage function defines one parameter, message.

Even though we give the parameter a name, message, we don't use the name when we call the function. We pass in the value for the message parameter.

The name we define in the function's definition is a local parameter name. The value of the parameter can only be referenced by this name in the body of the function. Function parameters, however, are a bit more flexible than that. Let me explain what I mean by that.

Objective-C is known for its long method names. While this may look clunky and inelegant to outsiders, it makes methods easy to understand and, if chosen well, very descriptive. If you think you lost this benefit when switching to Swift, then you're in for a surprise.

External Parameter Names

When a function accepts several parameters, it isn't always obvious which argument corresponds to which parameter. Take a look at the following example to better understand the problem.

The power function raises the value of a by the exponent b. Both parameters are of type Int. While most people will intuitively pass the base value as the first argument and the exponent as the second argument, this isn't clear from the function's type, name, or signature. As we've seen in the previous article, invoking the function is straightforward.

To avoid confusion, we can give the parameters of a function external names. We can then use these external names when the function is called to unambiguously indicate which argument corresponds to which parameter. Take a look at the updated example below.

Note that the function's body hasn't changed since the local names haven't changed. However, when we invoke the updated function, the difference is clear and the result much less confusing.

While the types of both functions are the same, (Int, Int) -> Int, the functions are different. In other words, the second function isn't a redeclaration of the first function. The syntax to invoke the second function may remind some of you of Objective-C. Not only are the arguments clearly described, the combination of function and parameter names describe the purpose of the function.

In some cases, you want to use the same names for local and external parameter names. This is possible and there's no need to type the parameter name twice. In the following example, we use base and exponent as the local and external parameter names.

By prefixing the parameter name with a # symbol, the parameter name serves as the local and external name of the parameter. This also means that we need to update the body of the function.

It's important to note that by providing an external name for a parameter you are required to use that name when invoking the function. This brings us to default values.

Default Values

We covered default parameter values in the previous article, but there is an important default behavior that you need to be aware of. If you define a default value for a parameter, Swift automatically assigns an external parameter name to the parameter. Let's look at an example of the previous article.

Because the second parameter, format, has a default value, Swift automatically sets the external parameter name of format to format. In other words, the result is the same as if we were to prefix format with a # symbol. You can test this by invoking the above function in your playground. What happens if you pass in the format without using the external parameter name of the second parameter? The answer is shown below.

Swift is pretty clear about what we should do. To summarize, when you define a parameter as optional, Swift automatically sets the external parameter name to the local parameter name. The idea behind this behavior is avoiding confusion and ambiguity.

While this behavior is a good best practice, it is possible to disable it. In the updated function definition below, we add an underscore where we would normally add the external parameter name. This tells Swift that we don't want to set an external parameter name for that particular parameter, despite having a default value.

We can now invoke the formatDate function without providing a name for the second argument.

2. Parameters & Mutability

Let's revisit the first example of this tutorial, the printMessage function. What happens if we change the value of the message parameter inside the function's body?

It doesn't take long for Swift to start complaining.

By default, the parameters of a function are constants. In other words, while we can access the values of function parameters, we cannot change their value. To change this default behavior, add the var keyword to the parameter name in the function definition. Swift will then create a variable copy of the parameter's value for you to work with in the function's body.

Note that this doesn't mean that you can pass in a value, modify it in the function, and use the modified value after the function has done its work. Swift creates a copy of the parameter's value that only exists for the lifetime of the function call. This is also illustrated in the following code block in which we pass a constant to the printMessage function.

3. Variadic Parameters

While the term may sound odd at first, variadic parameters are common in programming. A variadic parameter is a parameter that accepts zero or more values. The values need to be of the same type. Using variadic parameters in Swift is trivial as the following example illustrates.

The syntax is easy to understand. To mark a parameter as variadic, you append three dots to the parameter's type. In the function body, the variadic parameter is accessible as an array. In the above example, args is an array of Int values.

Because Swift needs to know which arguments correspond to which parameters, a variadic parameter is required to be the last parameter. It also implies that a function can have at most one variadic parameter.

The above also applies if a function has parameters with default values. The variadic parameter should always be the last parameter.

4. In-Out Parameters

Earlier in this tutorial, you learned how you can define the mutability of a parameter by using the var keyword. In that section, I emphasized that the value of a variable parameter is only accessible from within the function body. If you want to pass a value into a function, modify it in the function, and pass it back out of the function, in-out parameters are what you're looking for.

The following example shows an example of how in-out parameters work in Swift and what the syntax looks like.

We've defined the first parameter as an in-out parameter by adding the inout keyword. The second parameter is a regular parameter with an external name of withString and a local name of b. Let's see how we invoke this function.

We declare a variable, world, of type String and pass it to the perpendString function. The second parameter is a string literal. By invoking the function, the value of the world variable becomes Hello, world. Note that the first argument is prefixed with an ampersand, &, to indicate that it's an in-out parameter.

It goes without saying that constants and literals cannot be passed in as in-out parameters. Swift will thrown an error when you do as illustrated in the following screenshot.

It's evident that in-out parameters cannot have default values, be variadic, or be defined as var or let. If you forget these details, Swift will kindly remind you with an error.

5. Nesting

In C and Objective-C, functions and methods cannot be nested. In Swift, however, nested functions are quite common. The functions we've seen in this and the previous article are examples of global functions, they are defined in the global scope.

When we define a function inside a global function, we refer to that function as a nested function. A nested function has access to the values defined in its enclosing function. Take a look at the following example to better understand this.

While the functions in this example aren't terribly useful, the example illustrates the idea of nested function and capturing values. The printHelloWorld function is only accessible from within the printMessage function. As illustrated in the example, the printHelloWorld function has access to the constant a. The value is captured by the nested function and is therefore accessible from within that function. Swift takes care of capturing values, including managing the memory of those values.

6. Function Types

In the previous article, we briefly touched upon function types. A function has a particular type, composed of the function's parameter types and its return type. The printMessage function, for example, is of type (String) -> (). Remember that () symbolizes Void, which is equivalent to an empty tuple.

Because every function has a type, it's possible to define a function that accepts another function as a parameter. The following example shows how this works.

The printMessageWithFunction function accepts a string as its first parameter and a function of type (String) -> () as its second parameter. In the function's body, the function that we pass in is invoked with the message argument.

The example also illustrates how we can invoke the printMessageWithFunction function. The myMessage constant is passed in as the first argument and the printMessage function, which we defined earlier, as the second argument. How cool is that?

As I mentioned earlier, it's also possible to return a function from a function. The next example is a bit contrived, but it illustrates what the syntax looks like.

The compute function accepts a boolean and returns a function of type (Int, Int) -> Int. The compute function contains two nested functions that are also of type (Int, Int) -> Int, add and subtract.

The compute function returns a reference to either the add or the subtract function, based on the value of the addition parameter. The example also shows how to use the compute function. We store a reference to the function that is returned by the compute function in the computeFunction constant. We then invoke the function stored in computeFunction, passing in 1 and 2, store the result in result, and print the value of result in the standard output. The example may look complex, but it's actually easy to understand if you know what's going on.

Conclusion

You should now have a good understanding of how functions work in Swift and what you can do with them. Functions are a common theme in Swift and you will use them extensively when working with Swift.

In the next article, we dive head first into closures, a powerful construction that is very reminiscent of blocks in C and Objective-C, closures in JavaScript, and lambdas in Ruby.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.