Variadic parameters make it possible to pass zero or more values of a specific type into a function. It can be a clean alternative for methods that often work with one element, and you don’t want to create an array of components for just a single value on the implementation level.
The great thing about Variadic Parameters is that Swift handles converting a single value into a collection of values. We can keep our APIs more readable and clean while Swift takes care of the conversion. Let’s see how this works.
What is a Variadic Parameter?
A Variadic Parameter is defined using a value type followed by 3 dots, for example:
mutating func add(_ newContent: Content...) {
content.append(contentsOf: newContent)
}
This add(_:)
method can now take only a single value or multiple values at once:
struct Content {
let title: String
}
struct Bucket {
private(set) var content: [Content] = []
mutating func add(_ newContent: Content...) {
content.append(contentsOf: newContent)
}
}
var bucket = Bucket()
/// Add only a single content item:
bucket.add(Content(title: "Blog post 1"))
/// Add multiple items:
bucket.add(Content(title: "Blog post 2"), Content(title: "Blog post 3"))
Since Swift 5.4 shipped with Xcode 12.5, we can now also define multiple Variadic Arguments in a method definition:
mutating func add(_ newContent: Content..., newUsers: User...) {
content.append(contentsOf: newContent)
users.append(contentsOf: newUsers)
}
This is great if you want multiple parameters to have the flexibility of receiving zero or more values.
Understanding the limitations of Variadic Parameters
A Variadic Argument can be a great fit in many cases but can also not be the right solution. This is due to its limitations that are important to know to decide whether or not it’s useful to use a Variadic Parameter.
A Variadic Argument can also be empty
If an argument is defined implicitly as an array, it would be kind of logical to understand it’s possible to pass in an empty array:
struct Bucket {
private(set) var content: [Content] = []
mutating func add(_ newContent: [Content]) {
content.append(contentsOf: newContent)
}
}
var bucket = Bucket()
bucket.add([])
The underlying value of a Variadic Parameter is also an array which means we can do exactly the same:
struct Bucket {
private(set) var content: [Content] = []
mutating func add(_ newContent: Content...) {
content.append(contentsOf: newContent)
}
}
var bucket = Bucket()
bucket.add()
In most cases, this shouldn’t be a problem, as your method’s implementation needs to handle an array of elements, either way. However, you might have built-in some logging, which can be executed in those cases too. Just be aware!
Passing an array of values as a variadic argument doesn’t work
Although a proposal arrived in 2019 to make this work, it’s still not possible to pass in an array as a Variadic Parameter:
Although it might be expected for this to work, you will run into the following error:
Cannot pass array of type ‘[Content]’ as variadic arguments of type ‘Content’
The only way to solve this, for now, is to add another method overload accepting an array of content:
struct Bucket {
private(set) var content: [Content] = []
private(set) var users: [User] = []
mutating func add(_ newContent: Content..., newUsers: User...) {
content.append(contentsOf: newContent)
users.append(contentsOf: newUsers)
}
mutating func add(_ newContent: [Content]) {
content.append(contentsOf: newContent)
}
}
Or by creating a protocol that makes both single and multiple items equal. An example of this can be found in my blog post on result builders.
Conclusion
Variadic Parameters are a great way to keep your code clean on the implementation level when a method often receives one or multiple values. However, Variadic Arguments come with certain limitations that need to be considered before deciding to use them in your scenario.
If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!