Optionals
Best practices for handling Swift's Optional
and extending them to make them much more approachable
What are Optionals
Optionals are one of the staple features of Swift. They're something that appears very, very often during normal iOS development. Much more often than other features such as enums or generics.
However, if you're new to Swift and come from a different programming language such as Python, Ruby, or even Objective-C, then optionals can be daunting. This guide is split up into a basic introduction into Optionals
for those new to the subject matter and an advanced guide that will showcase more advanced optional handling for more seasoned developers.
Lets begin by briefly explaining why optionals are even needed.
Why Optionals are useful
Optionals aim to solve a problem related to something called "null pointers". We don't want to go too much into the history here, instead, we will showcase a typical situation where optionals are needed.
Imagine that you write your own User Defaults. We want to store a value String
for a key String
. We will ignore almost everything except for the one function get
that allows us to get a String
from the defaults.
struct MyDefaults {
...
/// A function that returns a `String`
func get(key: String) -> String {
}
}
The problem is what do we do when we don't have that value in our defaults? What would we return? Now, we could return an "Empty" string, but that wouldn't be right, would it? Imagine you'd save the username in the defaults. Next time the user starts his app he'd see an empty username.
What you really want to do is express the notifion of "Nothing". We kinda want to write something like the following:
func get(key: String) -> String or Nothing {
}
This either gives us back a string or it gives us back nothing. This is fundamentally what optionals are good for. They explain to the Swift type system and to the user that a value can either be Something or Nothing. The implementation is also really simple. It is just an enum
with two cases: some
and none
. Some, has the actual value. Here's a simplified version:
enum MyOptional {
case some(String)
case none
}
That's it. Now, the actual Swift Optional is not limited to Strings, instead it uses generics
in order to provide support for any kind of type. It kinda looks like this:
enum Optional<Wrapped>
case some(Wrapped)
case none
So what do we do if we call a function that returns an optional? Since optionals are just simple enums, we can just use Swift's normal enum
handling for this:
let optionalValue = functionReturningOptional()
switch optionalValue {
case .some(value): print(value)
case .none: print(\"Nothing\")
}
The types of Optionals
Thankfully, we don't have to spell out the long Optional.some
all the time. Instead, Swift offers us a very brief alternative, which is just adding a questionmark ?
to a type. This will tell Swift that this type is an Optional
. This means that the following two are equal:
let one: Optional<Int> = Optional.some(5)
let two: Int? = 5
This is usually the default Syntax for Optionals
in Swift. You'll hardly see the other Syntax, but it is still good to know as you might stumble upon it from time to time.
Also, if you want to create an empty optional, you can just use nil
as a shorthand for the empty optional. Again, the following two lines are equal:
let one: Int? = Optional.none
let two: Int? = nil
You can only use nil
if Swift can somehow infer the type of the Optional, so the next example does not work because Swift does not know what kind of Optional
this is. Is this a Int?
or a Float?
or a String?
. Swift can't find out:
let doesNotWork = nil
Swift offers many ways of handling Optional
types. Lets look at them next.
How to handle optionals
As we saw in the previous chapter, Optionals are
really just enum
types. However, as they're deeply ingrained into
the language, Swift offers a lot of additional possibilities of
handling Optionals
.
If Let
Certainly the most used one is the so-called if let. You're basically telling
Swift "If this optional value myOptionalValue
actually contains a value, please give me the contained value
into the variable named myUnwrappedValue
":
if let myUnwrappedValue = myOptionalValue {
print(myUnwrappedValue)
}
If myOptionalValue
is actually empty, then the print
statement
would never be executed. As with any typical if
statement, this
can also have an else expression:
if let myUnwrappedValue = myOptionalValue {
print(myUnwrappedValue)
} else {
print(\"No Value\")
}
You can also combine multiple if let
statements if you need to handle
multiple Optional
values:
if let firstValue = firstOptionalValue,
let secondValue = secondOptionalValue,
let thirdValue = thirdOptionalValue {
print(firstValue, secondValue, thirdValue)
}
Finally, you can mix and match the if let
pattern with normal if expressions:
if let firstValue = firstOptionalValue,
firstValue > 10
let secondValue = secondOptionalValue,
secondValue == \"HTTP\",
let thirdValue = thirdOptionalValue {
print(firstValue, secondValue, thirdValue)
}
In this example, we only print the three values if the firstOptionalValue is not empty and has a value > 10, and if the second optional value is not empty and has the value "HTTP" and if the third optional value is not empty.
Guard
Another nice feature of Swift are the guard
statements. They're basically like inverted if
statements. You usually use them at the beginning of a block of code to make sure that all your requirements are held. The main difference compared to if let
is that you're required to leave the current scope (i.e. return
, continue
, or break
) if the guard
does not succeed. Lets look at this nonsensical function that tries to do addition with two Optional
Int
values. For that to work, we need to make sure that
func addOptionals(firstNumber: Int?, secondNumber: Int?) -> Int? {
guard let first = firstNumber, let second = secondNumber
else { return nil }
return first + second
}
So here, we do the guard let
in order to make sure that both firstNumber
and secondNumber
have a value, otherwise we can't really do the addition. So if one of them (or both) don't have a value, we return early in the else { return nil}
block.
Observe how with if let
the code-to-be-executed is within the if
braces, while with guard let
it is not:
if let a = b {
print(b)
}
guard let a = b else { return }
print(b)
This makes it easier to follow the structure of code because your main code is not nested but only at the very left side of the function.
Switch
We already mentioned this in the previous chapter, but you can also use switch
to handle Optionals
:
switch myOptionalValue {
case let value?: print(value)
default: ()
}
We have a whole guide on pattern matching with Swift where this is explained in much more detail.
Forced Unwrap
Sometimes, if you're absolutely sure that your Optional
has a value, you can
also use the forced unwrap
. This tells Swift to handle this Optional
value as if
it was a non-optional value.
This works great, but it means that the optional has to have a value. If you try a forced unwrap on an empty optional (i.e. nil) it will cause a runtime error (meaning, crash).
let oneValue: Int? = 5
let twoValue: Int? = nil
print(oneValue!) // No Crash
print(twoValue!) // Crash
But wait, there's more. The next section in our guides discusses two additional methods of handling optionals that are also really, really useful: Optional Chaining and Map.
Advanced Optionals
We've already seen the basics of handling optionals. However, there's much more you can do. In this section we'll explore optionals even more and have a look at some advanced ways of handling optionals
Optional Chaining
Now imagine your work on a relationship database, and your data are users and their relations. So you'd have a Person and then the person could have an optional child and the child could have an optional sibling and that sibling could have an optional child, and so on.
Since all of these are Person
types, we could model the type like this:
struct Person {
var child: Person?
var sibling: Person?
var father: Person?
var mother: Person?
}
All of our properties are optional because they can all be nil. Now imagine you'd like to find the following relative:
So, how would we do that with if let
in Swift? Let us have a try:
if let child = person.child,
let sibling = person.sibling,
let nextChild = person.child,
let mother = nextChild.mother {
print(mother)
}
This is a lot of code and can quickly become confusing. Thankfully, Swift has another feature which lets us write this in a much simpler fashion.
The idea being that in a chain of operations on optionals (such as Optional.child -> Optional.silbing -> Optional.child) if any of these operations returns nil, we stop executing the chain early.
You represent this behaviour via a ?
before calling a method. Here is the
previous example implemented with the optional chaining
:
if let mother = person.child?.sibling?.child?.mother {
print(mother)
}
We're basically telling Swift "If the value of the child property of person is not optional, then please get me the sibling property from it". And we do the same again for the next propery.
This, also, works great for dictionaries where all return values are always optional.
example[\"a\"]?[\"b\"]?[\"c\"]
By adding ?
in between each call / access, Swift will
automatically unwrap if there is a value, or stop the chain as soon
as any one evaluates to nil.
Map
Consider the following code:
func example() -> Int? {return 10}
if let value = example() {
storeInDatabase(value * 2)
}
If we break down the logic, what we\'re really trying to achieve,
were it not for optionals, is the following:
``` Swift
storeInDatabase(example() * 10)
```
Optionals are still very useful here, as they prevent us from the
danger of multiplying nil with 10 and trying to store that in the
database. But still, we have to admit that the optionals code looks
more convoluted. As you can imagine, there\'s a solution to this, of
course.
Optionals offer an implementation of the `map` function which will
call the value of an optional with a supplied closure if
the optional has a value.
let px: Int? = 5
// This will print \"5\"
px.map { print($0) }
// This will do nothing
let px: Int? = nil
px.map { print($0) }
This lets us rewrite our example from above in terms of map
as follows:
example().map({ number in
storeInDatabase(number * 2)
})
What happens here is: When the return value of example()
is not optional, then the closure
will be called the value as number
and so we can call the storeInDatabase
function with our number multiplied by two. If the return value of example()
is empty, nothing will happen.
With Swift's nice simplified closure syntax we can even simply this example to the following:
example().map { storeInDatabase($0 * 2) }
Optionals
are a staple of Swift. I guess everybody will agree that
they are a huge boon insofar as they force us to properly handle edge
cases. The Optional
language feature alone removes a whole category of
bugs from the development process.
However, the API surface of Swift's optional is rather limited. The
Swift documentation lists just a
couple
of methods / properties on Optional
- if we ignore customMirror
and
debugDescription
:
var unsafelyUnwrapped: Wrapped { get }
func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
The reason why optionals are still very useful even though they have
such a small amount of methods is that the Swift syntax makes up for it
via features such as optional
chaining,
pattern
matching,
if let
or guard let
. In some situations, though, this manifests
itself in unnecessary line noise. Sometimes, a very succinct method will
let you express a concept in one short line of code instead of multiple
lines of combined if let
statements.
I've sifted through Swift Projects on Github as well as the optional
implementations of other languages such as Rust, Scala, or C# in order
to find a couple of useful additions to Optional
. Below are 14 useful
Optional
extensions. I'll describe them by category and then give a
couple of examples per category. Finally, I'll write a more involved
example that uses several extensions at once.
Emptiness
extension Optional {
/// Returns true if the optional is empty
var isNone: Bool {
return self == .none
}
/// Returns true if the optional is not empty
var isSome: Bool {
return self != .none
}
}
Those are the most basic additions to the optional type. The
implementation could also use a switch
pattern match instead, but the
nil
comparison is much shorter. What I like about these additions is
that they move the concept of an empty optional being nil away from your
code. This might just as well be an implementation detail. Using
optional.isSome
feels much cleaner and less noisy than
if optional == nil
:
// Compare
guard leftButton != nil, rightButton != nil else { fatalError(\"Missing Interface Builder connections\") }
// With
guard leftButton.isSome, rightButton.isSome else { fatalError(\"Missing Interface Builder connections\") }
Or
extension Optional {
/// Return the value of the Optional or the `default` parameter
/// - param: The value to return if the optional is empty
func or(_ default: Wrapped) -> Wrapped {
return self ?? `default`
}
/// Returns the unwrapped value of the optional *or*
/// the result of an expression `else`
/// I.e. optional.or(else: print(\"Arrr\"))
func or(else: @autoclosure () -> Wrapped) -> Wrapped {
return self ?? `else`()
}
/// Returns the unwrapped value of the optional *or*
/// the result of calling the closure `else`
/// I.e. optional.or(else: {
/// ... do a lot of stuff
/// })
func or(else: () -> Wrapped) -> Wrapped {
return self ?? `else`()
}
/// Returns the unwrapped contents of the optional if it is not empty
/// If it is empty, throws exception `throw`
func or(throw exception: Error) throws -> Wrapped {
guard let unwrapped = self else { throw exception }
return unwrapped
}
}
extension Optional where Wrapped == Error {
/// Only perform `else` if the optional has a non-empty error value
func or(_ else: (Error) -> Void) {
guard let error = self else { return }
`else`(error)
}
}
Another abstraction on the isNone / isSome
concept is being able to
specify instructions to be performed when the invariant doesn't hold.
This saves us from having to write out if
or guard
branches and
instead codifies the logic into a simple-to-understand method.
This concept is so useful, that it is defined in three distinct functions.
Default Value
The first one returns the wrapped value of the optional or a default value:
let optional: Int? = nil
print(optional.or(10)) // Prints 10
Default Closure
The second one is very similar to the first one, however it allows to return a default value from a closure instead.
let optional: Int? = nil
optional.or(else: secretValue * 32)
Since this uses the @autoclosure
parameter we could actually use just
the second or
implementation. Then, using a just a default value would
automatically be converted into a closure returning the value. However,
I prefer having two separate implementations as that allows users to
also write closures with more complex logic.
let cachedUserCount: Int? = nil
...
return cachedUserCount.or(else: {
let db = database()
db.prefetch()
guard db.failures.isEmpty else { return 0 }
return db.amountOfUsers
})
A really nice use case for or
is code where you only want to set a
value on an optional if it is empty:
if databaseController == nil {
databaseController = DatabaseController(config: config)
}
This can be replaced with the much nicer:
databaseController = databaseController.or(DatabaseController(config: config)
Throw an error
This is a very useful addition as it allows to merge the chasm between
Optionals and Error Handling in Swift. Depending on the code that
you're using, a method or function may express invalid behaviour by
returning an empty optional (imagine accessing a non-existing key in a
Dictionary
) or by throwing an Error
. Combining these two oftentimes
leads to a lot of unnecessary line noise:
func buildCar() throws -> Car {
let tires = try machine1.createTires()
let windows = try machine2.createWindows()
guard let motor = externalMachine.deliverMotor() else {
throw MachineError.motor
}
let trunk = try machine3.createTrunk()
if let car = manufacturer.buildCar(tires, windows, motor, trunk) {
return car
} else {
throw MachineError.manufacturer
}
}
In this example, we're building a car by combining internal and
external code. The external code (external_machine
and manufacturer
)
choose to use optionals instead of error handling. This makes the code
unnecessary complicated. Our or(throw:)
function makes this much more
readable:
func build_car() throws -> Car {
let tires = try machine1.createTires()
let windows = try machine2.createWindows()
let motor = try externalMachine.deliverMotor().or(throw: MachineError.motor)
let trunk = try machine3.createTrunk()
return try manufacturer.buildCar(tires, windows, motor, trunk).or(throw: MachineError.manufacturer)
}
Handling Errors
The code from the Throw an error section above becomes even more useful when you include the following free function that was proposed by Stijn Willems on Github. Thanks for the suggestion!
func should(_ do: () throws -> Void) -> Error? {
do {
try `do`()
return nil
} catch let error {
return error
}
}
This free function (alternatively, you could make it a class method on
optional) will perform a do {} catch {}
block and return an error if
and only if the closure `do` resulted in an error. Take, the following
Swift code as an example:
do {
try throwingFunction()
} catch let error {
print(error)
}
This is one of the basic tennets of error handling in Swift, and it introduces quite a lot of line noise. With the free function above, you can reduce it to this simple on-liner:
should { try throwingFunction) }.or(print($0))
I feel that there're many situations where such a one-liner for error handling would be very beneficient.
Map
As we saw above, map
and flatMap
are the only methods that Swift
offers on Optionals. However, even those can be improved a bit to be
more versatile in many situations. There're two additional variations
on map
that allow defining a default value similar to how the or
variants above are implemented:
extension Optional {
/// Maps the output *or* returns the default value if the optional is nil
/// - parameter fn: The function to map over the value
/// - parameter or: The value to use if the optional is empty
func map<T>(_ fn: (Wrapped) throws -> T, default: T) rethrows -> T {
return try map(fn) ?? `default`
}
/// Maps the output *or* returns the result of calling `else`
/// - parameter fn: The function to map over the value
/// - parameter else: The function to call if the optional is empty
func map<T>(_ fn: (Wrapped) throws -> T, else: () throws -> T) rethrows -> T {
return try map(fn) ?? `else`()
}
}
The first one will allow you to map
the contents of an optional to a
new type T
. If the optional is empty, you can define a default
value
that should be used instead:
let optional1: String? = \"appventure\"
let optional2: String? = nil
// Without
print(optional1.map({ $0.count }) ?? 0)
print(optional2.map({ $0.count }) ?? 0)
// With
print(optional1.map({ $0.count }, default: 0)) // prints 10
print(optional2.map({ $0.count }, default: 0)) // prints 0
The changes are minimal, but we're moving away from having to use the
??
operator and can instead express the operation more clearly with
the default
keyword.
The second variant is very similar. The main difference is that it
accepts (again) a closure returning value T
instead of value T
.
Here's a brief example:
let optional: String? = nil
print(optional.map({ $0.count }, else: { \"default\".count })
Combining Optionals
This category contains four functions that allow you to define relations between multiple optionals.
extension Optional {
/// Tries to unwrap `self` and if that succeeds continues to unwrap the parameter `optional`
/// and returns the result of that.
func and<B>(_ optional: B?) -> B? {
guard self != nil else { return nil }
return optional
}
/// Executes a closure with the unwrapped result of an optional.
/// This allows chaining optionals together.
func and<T>(then: (Wrapped) throws -> T?) rethrows -> T? {
guard let unwrapped = self else { return nil }
return try then(unwrapped)
}
/// Zips the content of this optional with the content of another
/// optional `other` only if both optionals are not empty
func zip2<A>(with other: Optional<A>) -> (Wrapped, A)? {
guard let first = self, let second = other else { return nil }
return (first, second)
}
/// Zips the content of this optional with the content of another
/// optional `other` only if both optionals are not empty
func zip3<A, B>(with other: Optional<A>, another: Optional<B>) -> (Wrapped, A, B)? {
guard let first = self,
let second = other,
let third = another else { return nil }
return (first, second, third)
}
}
These four functions all share that they take an additional optional as a parameter and return another optional value. However, they're all quite different in what they achieve.
Dependencies
and<B>(_ optional)
is useful if the unpacking of an optional is only
required as a invariant for unpacking another optional:
// Compare
if user != nil, let account = userAccount() ...
// With
if let account = user.and(userAccount()) ...
In the example above, we're not interested in the unwrapped contents of
the user
optional. We just need to make sure that there is a valid
user before we call the userAccount
function. While this relationship
is kinda codified in the user != nil
line, I personally feel that the
and
makes it more clear.
Chaining
and<T>(then:)
is another very useful function. It allows to chain
optionals together so that the output of unpacking optional A
becomes
the input of producing optional B
. Lets start with a simple example:
protocol UserDatabase {
func current() -> User?
func spouse(of user: User) -> User?
func father(of user: User) -> User?
func childrenCount(of user: User) -> Int
}
let database: UserDatabase = ...
// Imagine we want to know the children of the following relationship:
// Man -> Spouse -> Father -> Father -> Spouse -> children
// Without
let childrenCount: Int
if let user = database.current(),
let father1 = database.father(user),
let father2 = database.father(father1),
let spouse = database.spouse(father2),
let children = database.childrenCount(father2) {
childrenCount = children
} else {
childrenCount = 0
}
// With
let children = database.current().and(then: { database.spouse($0) })
.and(then: { database.father($0) })
.and(then: { database.spouse($0) })
.and(then: { database.childrenCount($0) })
.or(0)
There're a lot of improvements when using the version with and(then)
.
First of all, you don't have to come up with superfluous temporary
variable names (user, father1, father2, spouse, children). Second, we
clearly have less code. Also, using the or(0)
instead of a complicated
let childrenCount
is so much easier to read.
Finally, the original Swift example can easily lead to logic errors. You may not have noticed, but there's a bug in the example. When writing lines like that, copy paste errors can easily be introduced. Do you see the error?
Yeah, the children
property should be created by calling
database.childrenCount(spouse)
but I wrote
database.childrenCount(father2)
instead. It is difficult to spot
errors like that. The and(then:)
example makes it much easier because
it always relies on the same variable name $0
.
Zipping
This is another variation on an existing Swift concept. The zip
method
on optional will allow us to combine multiple optionals and unwrap them
together or not at all. I've just provided implementations for zip2
and zip3
but nothing prevents you from going up to zip22
(Well,
maybe sanity and compiler speed).
// Lets start again with a normal Swift example
func buildProduct() -> Product? {
if let var1 = machine1.makeSomething(),
let var2 = machine2.makeAnotherThing(),
let var3 = machine3.createThing() {
return finalMachine.produce(var1, var2, var3)
} else {
return nil
}
}
// The alternative using our extensions
func buildProduct() -> Product? {
return machine1.makeSomething()
.zip3(machine2.makeAnotherThing(), machine3.createThing())
.map { finalMachine.produce($0.1, $0.2, $0.3) }
}
Less code, clearer code, more beautiful code. However, as a downside,
this code is also more involved. The reader has to know and understand
zip
in order to easily grasp it.
On
extension Optional {
/// Executes the closure `some` if and only if the optional has a value
func on(some: () throws -> Void) rethrows {
if self != nil { try some() }
}
/// Executes the closure `none` if and only if the optional has no value
func on(none: () throws -> Void) rethrows {
if self == nil { try none() }
}
}
These two short methods will allow you to perform side effects if an
optional is empty or not. In contrast to the already discussed methods,
these ignore the contents of the optional. So on(some:)
will only
execute the closure some
if the optional is not empty but the closure
some
will not get the unwrapped contents of the optional.
/// Logout if there is no user anymore
self.user.on(none: { AppCoordinator.shared.logout() })
/// self.user is not empty when we are connected to the network
self.user.on(some: { AppCoordinator.shared.unlock() })
Various
extension Optional {
/// Returns the unwrapped value of the optional only if
/// - The optional has a value
/// - The value satisfies the predicate `predicate`
func filter(_ predicate: (Wrapped) -> Bool) -> Wrapped? {
guard let unwrapped = self,
predicate(unwrapped) else { return nil }
return self
}
/// Returns the wrapped value or crashes with `fatalError(message)`
func expect(_ message: String) -> Wrapped {
guard let value = self else { fatalError(message) }
return value
}
}
Filter
This is a simple method which works like an additional guard to only unwrap the optional if it satisfies a predictate. Here's an example. Imagine we want to upgrade all our old users to a premium account for sticking with us for a long time:
// Only affect old users with id < 1000
// Normal Swift
if let aUser = user, user.id < 1000 { aUser.upgradeToPremium() }
// Using `filter`
user.filter({ $0.id < 1000 })?.upgradeToPremium()
Here, user.filter
feels like a much more natural implementation. Also,
it only implements what already exists for Swift's collections.
Expect
This is one of my favorites. Also, I shamelessly stole it from Rust. I'm trying very hard to never force unwrap anything in my codebase. Similar for implicitly unwrapped optionals.
However, this is tricky when working with interface builder outlets. A common pattern that I observed can be seen in the following function:
func updateLabel() {
guard let label = valueLabel else {
fatalError(\"valueLabel not connected in IB\")
}
label.text = state.title
}
The alternative solution, obviously, would be to just to force unwrap
the label, as that leads to a crash just like fatalError
. Then, I'd
have to insert !
though, also it wouldn't give me a nice succinct
description of what actually is wrong. The better alternative here is to
use expect
as implemented above:
func updateLabel() {
valueLabel.expect(\"valueLabel not connected in IB\").text = state.title
}
Example
So now that we've seen a couple of (hopefully) useful Optional
extensions, I'll set up an example to better see how some of these
extensions can be combined to simplify optional handling. First, we need
a bit of context. Forgive me for the rather unconventional and
impossible example:
You're working in the 80s at a shareware distributor. A lot of student programmers are working for you and writing new shareware apps and games every month. You need to keep track of how many were sold. For that, you recieve an XML file from accounting and you need to parse it and insert it into the database (isn't it awesome how in this version of the 80s there's Swift to love but also XML to hate?). Your software system has an XML parser and a database (both written in 6502 ASM of course) that implement the following protocols:
protocol XMLImportNode {
func firstChild(with tag: String) -> XMLImportNode?
func children(with tag: String) -> [XMLImportNode]
func attribute(with name: String) -> String?
}
typealias DatabaseUser = String
typealias DatabaseSoftware = String
protocol Database {
func user(for id: String) throws -> DatabaseUser
func software(for id: String) throws -> DatabaseSoftware
func insertSoftware(user: DatabaseUser, name: String, id: String, type: String, amount: Int) throws
func updateSoftware(software: DatabaseSoftware, amount: Int) throws
}
A typical file looks like this (behold the almighty XML):
Our original Swift code to parse the XML looks like this:
enum ParseError: Error {
case msg(String)
}
func parseGamesFromXML(from root: XMLImportNode, into database: Database) throws {
guard let users = root.firstChild(with: \"users\")?.children(with: \"user\") else {
throw ParseError.msg(\"No Users\")
}
for user in users {
guard let software = user.firstChild(with: \"software\")?
.children(with: \"package\"),
let userId = user.attribute(with: \"id\"),
let dbUser = try? database.user(for: userId)
else { throw ParseError.msg(\"Invalid User\") }
for package in software {
guard let type = package.attribute(with: \"type\"),
type == \"game\",
let name = package.attribute(with: \"name\"),
let softwareId = package.attribute(with: \"id\"),
let amountString = package.attribute(with: \"amount\")
else { throw ParseError.msg(\"Invalid Package\") }
if let existing = try? database.software(for: softwareId) {
try database.updateSoftware(software: existing,
amount: Int(amountString) ?? 0)
} else {
try database.insertSoftware(user: dbUser, name: name,
id: softwareId,
type: type,
amount: Int(amountString) ?? 0)
}
}
}
}
Lets apply what we learned above:
func parseGamesFromXML(from root: XMLImportNode, into database: Database) throws {
for user in try root.firstChild(with: \"users\")
.or(throw: ParseError.msg(\"No Users\")).children(with: \"user\") {
let dbUser = try user.attribute(with: \"id\")
.and(then: { try? database.user(for: $0) })
.or(throw: ParseError.msg(\"Invalid User\"))
for package in (user.firstChild(with: \"software\")?
.children(with: \"package\")).or([]) {
guard (package.attribute(with: \"type\")).filter({ $0 == \"game\" }).isSome
else { continue }
try package.attribute(with: \"name\")
.zip3(with: package.attribute(with: \"id\"),
another: package.attribute(with: \"amount\"))
.map({ (tuple) -> Void in
switch try? database.software(for: tuple.1) {
case let e?: try database.updateSoftware(software: e,
amount: Int(tuple.2).or(0))
default: try database.insertSoftware(user: dbUser, name: tuple.0,
id: tuple.1, type: \"game\",
amount: Int(tuple.2).or(0))
}
}, or: { throw ParseError.msg(\"Invalid Package\") })
}
}
}
If we look at this, then there're two things that immediately come to mind:
- Less Code
- More Complicated Looking Code
I deliberately went into overdrive when utilizing the various Optional
extensions. Some of them fit better while others seem to be a bit
misplaced. However, the key is not to solely rely on these extensions
(like I did above) when using optionals but instead to mix and match
where it makes most sense. Compare the two implementations and consider
which from the second example you'd rather implement with Swift's
native features and which feel better when using the Optional
extensions.
Similar Articles |
---|