How to filter nil values out of the collection types— Swift
Defensive coding is good coding.
Handling nil (null)
value is something that every developer has to experience. No matter what language you are using or what platform you are developing, you have take care it very seriously. Otherwise it can and will cause problems to your application.
This article will talk about how to filter nil
values out of collection types by Control-flow
, Functional programming
, and creating a classExtension
for reusable code. I will summarise pros and cons for each methods at the end of this article.
1. Control flow
Control flow statements are provided by Swift. For examlple, for-loops
to perform a task multiple times; for-in
to iterate over arrays, dictionaries, and other sequences; if-else
statements to execute different branches of code based on certain conditions.
Array
For example, let’s take a look at the following arrays: First one is nullable, and the second one is non-nullable
let optionalElements: [String?] = ["Ant", "Bird", nil, "Dog", nil]
var filteredElements: [String] = []
Our goal is to filter non-nil
elements from optionalElements array and append it to filteredElements.
To achieve this, we will iterate through optionalElements for each element. There are two ways for gather a value. Let’s talk about the first way, force unwrapping
i.e. if an element is not nil
, then we will force it to non-optional type and append it to filteredElements. Else, continue.
for optionalElement in optionalElements {
if optionalElement != nil {
filteredElements.append(optionalElement!)
}
}
Another way is called optional binding
. It is more safe than using force unwrapping. It uses if-else
statements to bind an optional type. See the following code below.
for optionalElement in optionalElements {
if let unwrappedElement = optionalElement {
filteredElements.append(unwrappedElement)
}
}
You can learn more about optional binding and force unwrapping from my Optional type article
Dictionary
Let’s assume that, we are going to build a dictionary from user payment data which is contain non-nil
value and use it as a service parameters
for service request body — Here is a struct where we store user payment data
struct UserPaymentData {
var paymentID: String?
var username: String?
var fullName: String?
var email: String?
var address: String?
var mobileNumber: String?
var gender: String?
}
Let assume that we displayed view to retrieve user payment data from an application. And this is data we got from a user.
let paymentData = UserPaymentData()
paymentData.paymentID = "xxxxxxxxxxxxxxxxxxxxxxxx"
paymentData.username = "Khemmachart"
paymentData.fullName = "Khemmachart Chutapetch"
paymentData.email = "khemmachart@myemail.com"
As you can compare object properties to its class’s variables. Some of them are still contain no value (nil)
, such as, address, mobileNumber, gender. In this case, we should not send that nil
value to a service. So we need to filter those properties when building a dictionary object — First idea to to assign only exist properties.
var parameters: [String: String] = [:]if let unwrappedPaymentID = paymentData.paymentID {
parameters["payment_id"] = unwrappedPaymentID
}
if let unwrappedUsername = paymentData.username {
parameters["username"] = unwrappedUsername
}
if let unwrappedFullName = paymentData.fullName {
parameters["full_name"] = unwrappedFullName
}
if let unwrappedEmail = paymentData.email {
parameters["email"] = unwrappedEmail
}
if let unwrappedAddress = paymentData.address {
parameters["address"] = unwrappedAddress
}
if let unwrappedMobileNumber = paymentData.mobileNumber {
parameters["mobile_number"] = unwrappedMobileNumber
}
What do you think about this code? In my view, it not look pretty good. In term of code quality we call it duplicated code
or hardcode
. I am going to tell you how to prevent this kind of code.
2. Functional programming
Functional programming functions are provided by Apple. We can use these functions to reduce number of lines of code. It also easy to maintain and scale. We have no need to edit anything except making our struct to support one a new property, and store data to it.
Array
For an array collection, we can use both compactMap and filter. Depends on what result type are you expecting.
compactMap
— is a generic instance Method which is returns an array containing the non-nil
results of calling the given transformation with each element of this sequence. — Apple
let optionalElements = ["Ant", "Bird", nil, "Dog", nil]
let filteredElements = optionalElements.compactMap ({ $0 })
In case you still want an array of optionals
, you can use filter
to remove nil
elements. filter
— is an instance method which is returns a new collection of the same type containing, in order, the elements of the original collection that satisfy the given predicate.
let optionalElements = ["Ant", "Bird", nil, "Dog", nil]
let filteredElements = optionalElements.filter({ $0 != nil })
Dictionary
As we discuss above, we want to build a dictionary object to contain an key
and non-nil
values for service parameters. By that reasons, we have to unwrap all data before assign to dictionary to prevent nil value.
We can improve this flow of code by assign all data to dictionary either it is exist
or nil
. You can see the code below.
var optionalParameters: [String: String?] = [:]
optionalParameters["payment_id"] = paymentData.paymentID
optionalParameters["username"] = paymentData.username
optionalParameters["full_name"] = paymentData.fullName
optionalParameters["email"] = paymentData.email
optionalParameters["address"] = paymentData.address
optionalParameters["mobile_number"] = paymentData.mobileNumber
Now we have a dictionary which is contain optional string as a value, as you can see from its type[String: String?]
. Then what we have to do is to remove nil
out of it self. There are two ways to remove nil
out of this dictionary, which arecontrol-flow
and functional functions
. Both case return non-nil
.
Let start with the control-flow
. We can use for-loop to iterate over dictionaries’ keys and then use if-else to help us unwrapping its value.
var unwrappedParameters: [String: String] = [:]
for key in optionalParameters.keys {
if let unwrappedValue = optionalParameters[key] {
unwrappedParameters[key] = unwrappedValue
}
}
Or using functional programming
function to return a non-nil
value
let unwrappedParameters = optionalParameters.filter({ $0.value != nil }).mapValues({ $0! })
This case is a combination of filter
and mapValue.
Now it look better in term for performance, we can reduce lines of code from control flow by using functional programming but I think this code look a little bit hard to understand. Our teammate might take a long to to understand how it works.
3. Collection Extension
A swift Extensions allows you to add functionality to an existing class, structure, enumeration, or protocol type. Extensions are similar to categories in Objective-C — Rather than using a global function or a method, we can create an extension to reduce duplicated code and make it more readable.
First problem with using this method is a generic type
. Every times we call an array element or a dictionary value, it will return a generic type
. We need to turn it to an optional type to check nil
by create a new optional protocol and let optional conform that protocol.
protocol OptionalType {
associatedtype Wrapped
func toOptional() -> Wrapped?
}extension Optional : OptionalType {
func toOptional() -> Wrapped? {
return self
}
}
Array
For an array type, we will add an extension to sequence collection which is contains elements as an optional type as you can see form where
clauses. Then we write a methods to turn generic type to be optional type to check nil
by using functional programing functions. If it is, then unwrap it
extension Sequence where Iterator.Element: OptionalType {
func unwrappedElements() -> [Iterator.Element.Wrapped] {
return compactMap({ $0.toOptional() })
} func filteredElements() -> [Iterator.Element] {
return filter({ $0.toOptional() != nil })
}
}
Example:
let unwrappedElements = optionalElements.unwrappedElements()
Dictionary
Same as array. We will add an extension to dictionary collection where its value is an optional type. Then we use the functional function to filter nil
out of it values.
extension Dictionary where Value: OptionalType {
func unwrappedValues() -> [Key: Value.Wrapped] {
return filter({ $0.value.toOptional() != nil })
.mapValues({ $0.toOptional()! })
}
}
Example:
let unwrappedParameters = optionalParameters.unwrappedValues()
Conclusions
In the last session of this article, I will summarize the advantages and disadvantages for the usage of three methods we can use to filter nil values out of the collection types above.
Control-Flow
Using control-flow is easy to write as it is a basic pattern that every programmer should be familiar but it lead to duplicated code in your project. Let assume that you have a large scale project, how many control-flow you have to write to filter nil
values? And it hard for maintenance and scale, if your data properties changes, it might affect to many places where are using that instance.
Functional programming
We can take the advantages of the functional programming concepts, such as, pure functions or immutable objects to make our to be more effective. It it can reduce our duplicated code. The only thing I concern is that, it might a little bit difficult to understand when someone review our code because the methods name does not implies how it works.
Collection extension
Extension is a very useful feature from Swift. We can reduce duplicated code by group it into a function. In additional, we can named the function to implies how it work to make our code to be more readable. There is no need to implement function body by using functional programming same as my example. As it might not suitable to your application behavior. But there is no doubt about using the extension!
Please share your idea or what do you think about filtering nil values out of a collection? However, this is my first English Technical article, please let me know if there are something wrong or not clear. I will be appreciated if your comment can improve my English and technical skill :)
References
Special thank to songtham tung to help me revise this article. He is a Full Stack Software Engineer who came from Silicon Valley. You can check out his awesome articles !!