Update 12 Oct ’16: I’ve updated the code in this post and the Xcode Playground blog post version to Swift 3! Thank you for the wait 😁
As an iOS developer, handling empty value cases in Objective-C is never easy. Let’s suppose we’re making a function that return NSString
instance out of a NSDictionary
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Will return @p NSString representation of user's token from passed @p payload, or @p nil if there's no user token in it. | |
– (NSString *)userTokenFromJSON:(NSDictionary *)json | |
{ | |
return json["user_token"]; | |
} |
Everything seems fine, isn’t it? The method’s logic is pretty clear – it returns the value in user_token
key of the JSON. If the key exists, it will return the string. If not, it will return a nil
value… dead simple, right?
No.
I left out a sanity check there, but let’s continue our example for now.
Suppose that the returned string will be encrypted and stored by C++ library. And for that, we need to cast our NSString
to C string:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
– (void)storeUserToken:(NSString *)userToken | |
{ | |
if (nil == userToken) { | |
return; | |
} | |
const char * rawString = [userToken UTF8String]; | |
// Further code that uses rawString here… | |
} |
Where’s the problem, Do? Everything looks fine…
Right. The method above looks good – it stopped the process early if passed userToken
is nil. Both of them will work correctly, until somebody from the server side single-handedly pass null value in response JSON’s user_token key, instead of omitting it.
Let’s run through the code once again. If the passed JSON is made from NSJSONSerialization
process, the user_token
key will store a NSNull
instance. Thus, the result from userTokenFromJSON:
will be a NSNull
instead of a nil
or NSString
– which will allow it to pass through storeUserToken:
‘s early sanity check code (since it’s not a nil), and break the whole app, since NSNull
doesn’t have UTF8String
method.
Let’s hope this case will never happen in production servers. And yes – I’m looking at you, cowboys.
Due to this issue, nil
-checking alone in Objective-C isn’t sufficient. We also need to ensure whether an instance is the right class using isKindOfClass:
method. It doesn’t always work well either – for example, if the server on the example above returns a number for user_token
value, there’s a chance that it’ll read as _NSCFString
(Apple’s private API) instead of a NSNumber
.
That’s why after a few month working with Swift, I grew appreciating the Swift Team’s decision to include Optionals. I believe they made this as an answer to Objective-C’s tedious sanity check. The documentation clearly says that:
You use optionals in situations where a value may be absent. An optional says:
There is a value, and it equals x
or
There isn’t a value at all.
If I declare a variable to be a String?
(read: String Optional), it would either be a String
value or a nil
. Not a NSNull
, not other class type, and not another Apple’s private API. So, userTokenFromJSON:
above could be rewritten in Swift into this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Returns `String` optional representation of user's token from passed `json`. | |
func getUserToken(json: [String: AnyObject]) -> String? { | |
return json["user_token"] as? String | |
} |
And yes, this method will an Optional – either String
or a nil.
🙂 But the process isn’t ended here – we need to take the available String
value out of the Optional. The term is usually called as unwrapping in Swift development – and there are several ways to do it!
Wait, it seems I had enough rant above… this post started to feel edgy. Let’s change the mood, shall we?
In this post, I’ll list the ways for unwrapping Swift’s Optionals that I have found so far. For the sake of the post, let’s assume we got a new function that needs a String
input and an Optional variable:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func createGreetings(sailorName: String) -> String { | |
return "👮 Ahoy, \(sailorName)! Welcome to S.S. Salty Sailor, arrr!" | |
} | |
var name: String? |
Now, we need to unwrap the name
(since it’s a String
optional) to pass it to the createGreetings(sailorName:)
. There are several ways to do this:
1. Force unwrap (!
)
Ah, the ol’ forceful way. Adding bang / exclamation (!) mark after the variable is a sure way to keep the compiler from whining:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name = "Ol' Man Jenkins" | |
// This call will work, since it has a valid value. | |
print(createGreetings(sailorName: name!)) | |
name = nil | |
// This call will cause an error, since `name` has no value at this point. | |
print(createGreetings(sailorName: name!)) |
Sure, the whining stops – but at what cost? Force-unwrapping a nil
-valued Optional will raise an exception, which will happen on the second createGreetings
call of the code above.
Of course, we could do some nil
checking before force unwrapping. Still, it will make our code twice as forceful as the code above:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if name != nil { | |
print(createGreetings(sailorName: name!)) | |
} |
Thankfully, Swift provide a better way to do this.
2. if let
An if let
statement is just like the nil
-checking statement above, but less bangs 😉 we pass the non-nil
value to a new variable, and execute the code inside the if let
statement:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name = "Donald Duck" | |
if let validName = name { | |
print(createGreetings(sailorName: validName)) | |
} |
Since it only passes non-nil
values, it won’t execute the code inside the statement if the variable is a nil
. The code in if let
statement below won’t be executed:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name = nil | |
if let anotherName = name { | |
// This code won't be executed, since name is a nil | |
print(createGreetings(sailorName: anotherName)) | |
} |
And, since naming things is the second hardest thing in Computer Science, we could reuse the variable name for the if let
statement. The compiler will use the unwrapped version for the code inside it:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name = "Daffy Duck" | |
if let name = name { | |
// We can use `name` variable without bangs here, since it will treat it as a valid String value | |
// if it passed the `if let` statement above. | |
print(createGreetings(sailorName: name)) | |
} |
So, we knew that if let
is more beneficial than force unwrap. While it’s convenient, we could end up big or nested if let statement for complicated logic:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if let name = name { | |
let uppercasedName = name.uppercased() | |
let lowercasedName = name.lowercased() | |
// And whole other code here… just imagine it yourself 😁 | |
let newName = "Sgt. " + name | |
print(createGreetings(sailorName: newName)) | |
} |
Assume the if let
bracket above got several dozen lines of code – it would be cumbersome, no? It would be even worse if there’s another if
bracket inside it. Of course, we could use return early method, but we’ll be forced to force-unwrap:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func sampleEarlyReturnFunction(sampleName: String?) { | |
if sampleName == nil { | |
return | |
} | |
let anotherName = sampleName! | |
print(createGreetings(sailorName: anotherName)) | |
} | |
// This code will do just fine, even the name has nil value. | |
sampleEarlyReturnFunction(name) |
Thankfully, Swift 2.0 provides a solution that allows us to return early cleanly:
3. guard let
Swift’s guard
syntax forces a code block to return early when its condition is not met. It also works with let
for unwrapping optionals:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func guardLetEarlyReturnFunction(sampleName: String?) { | |
guard let validName = sampleName else { | |
print("👺 \(#function): Invalid name provided!") | |
return | |
} | |
print(createGreetings(sailorName: validName)) | |
// Add whole other code here 🙂 | |
} | |
guardLetEarlyReturnFunction(nil) | |
guardLetEarlyReturnFunction(name) |
Neat, right? With guard
, we could handle the outlier cases on the top of the code block, and proceed with the normal case below it. Still, there are times that we only need if let
or guard let
only to return a value, such as below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func getValidString(string: String?) -> String { | |
if let validString = string { | |
return validString | |
} else { | |
return "" | |
} | |
} | |
func anotherGetValidString(string: String?) -> String { | |
guard let validString = string else { | |
return "" | |
} | |
return validString | |
} |
For this specific use case, Swift provides a simple shortcut:
4. nil
-coalescing operator (??
)
Based on the documentation, nil
-coalescing operator (??
) unwraps an optional if it isn’t a nil
, and returns the other value otherwise. Simply put, it’s a shortcut of a != nil ? a! : b
.
This allows us to implement the code above with less code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func nilCoalescingGetValidString(string: String?) -> String { | |
return string ?? "" | |
} | |
print(nilCoalescingGetValidString(nil)) | |
print(nilCoalescingGetValidString(name)) | |
let validName = nilCoalescingGetValidString(name) | |
print(createGreetings(sailorName: validName)) |
Besides the common operators above, there’s another way to unwrap optionals – which is based by the implentation of the optionals itself.
5. switch
Why switch
statement, you ask? Long story short, I found Benedict Terhecte’s blog post about advanced enum usage a few months ago. There’s a simplified implementation of Swift’s optional there, that turned out to be an (somewhat like) enum with associated values:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Simplified implementation of Swift's Optional | |
enum MyOptional<T> { | |
case some(T) | |
case none | |
} |
Knowing this, we could use switch
‘s pattern matching to unwrap its values:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func printSailorName(sailorName sailorName: String?) { | |
switch sailorName { | |
case .some(let validName): | |
print(createGreetings(sailorName: validName)) | |
case .none: | |
print("👺 The sailor name input is invalid!") | |
} | |
} | |
printSailorName(sailorName: nil) | |
printSailorName(sailorName: name) |
This is beneficial if we got two optionals and different conditions according to their values (or absence of it). On my latest project, I created a view model to cater date selection in a calendar. This is the super simplified version:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class CalendarViewModel { | |
var selectedCheckInDate: NSDate? | |
var selectedCheckOutDate: NSDate? | |
func update(selectedDate date: NSDate) { | |
switch (selectedCheckInDate, selectedCheckOutDate) { | |
case (.none, .none): | |
selectedCheckInDate = date | |
case (.some(_), .none): | |
selectedCheckOutDate = date | |
case (.some(_), .some(_)): | |
selectedCheckOutDate = nil | |
selectedCheckInDate = date | |
default: | |
break | |
} | |
} | |
} |
Though we could implement the update(selectedDate:)
method above using “equal-nil
” checking, but IMO, it’s more self-describing with switch
‘s pattern matching.
Bonus: flatMap
for Arrays
There’s a flatMap
built-in method for Swift Array
that can be used to filter-out nil
values. Here’s an example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let sailorNames: [String?] = [ | |
nil, | |
"Daffy Duck", | |
"Donald Duck", | |
nil, | |
"Darkwing Duck", | |
"Howard The Duck", | |
] | |
let unwrappedSailorNames = sailorNames.flatMap({ $0 }) | |
for sailorName in unwrappedSailorNames { | |
print(createGreetings(sailorName: sailorName)) | |
} |
It will only work if we return Optional
element on the flatMap
block, though. Here’s a sample to test it:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let unwrapFlatMapCount = sailorNames.flatMap { name -> String? in | |
return name | |
}.count | |
let otherOptionalFlatMapCount = sailorNames.flatMap { name -> Int? in | |
return name?.hashValue | |
}.count | |
let otherNormalFlatMapCount = sailorNames.flatMap { name -> Int in | |
return name?.hashValue ?? 0 | |
}.count | |
print("Unwrap flatMap count: \(unwrapFlatMapCount)") // will print 4 | |
print("Other optional flatMap count: \(unwrapFlatMapCount)") // will print 4, too | |
print("Other `normal` flatMap count: \(otherNormalFlatMapCount)") // will print 6 |
CMIIW, but from what I know, flatMap is meant to take a nested value inside an array and put it to the surface level (hence the flatten), and map it as needed. At first, I only think this will be useful to flatten a nested array:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let duckSailors = ["Daffy", "Donald", "Howard"] | |
let sealSailors = ["Manatee", "Moby"] | |
let otherSailors = [duckSailors, sealSailors] | |
let flattenedSailors = otherSailors | |
.flatMap { nameArray -> [String] in | |
return nameArray | |
} | |
print("Flatten Sailor names: \(flattenedSailors)") | |
flattenedSailors.forEach { name in | |
print(createGreetings(sailorName: name)) | |
} |
The duckSailors
and sealSailors
above were String
s that nested inside a container – which is an array. Returning the exact array in otherSailors
‘ flatMap
block will flatten out the values inside it.
If we revisit the simplified Optional
implementation above, we could see that it’s just another container – that may contain something (.Some(T)
), or none (.None
). That’s why the flatMap
operation filters out nils – because those Optional contains nothing! 😉
I hope you find this post useful! See you later on future posts! 😄
P.S: I’ll try to post an updated version of this for Swift 3 soon! 😉