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
:
/// 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:
– (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:
/// 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:
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: