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: