Lightning Read #2: Optional String Pitfalls
I was trying to build a URL the other day. Check the following code block:
init?(userId: String?) {
guard userId != nil else { return nil }
self.path = "/user/\(userId)"
self.url.appendPathComponent(self.path)
}
Seems legit, right?
Assume that we’re passing 23940
as userId
to this initializer. Depending on the Swift version, path value would be:
Swift 2.x | path = "/user/23940"
Swift 3.x | path = "/user/Optional("23940")"
Swift 4.x | path = "/user/Optional("23940")"
Subtle, but heart-breaking.
Firstly, beware of this issue if you’re working on a super-old project which still uses Swift 2.x for some reason, and planning to migrate.
Secondly, we could use a guard-let instead of a guard-check statement here to fix this issue. However, it’s not convenient to have guard statements everywhere, especially when you simply want to print stuff.
I created the following struct to overcome this problem:
public struct Printable<T>: CustomStringConvertible {
public let value: T?
public init(_ value: T?) {
self.value = value
}
public var description: String {
if let value = value {
return String(describing: value)
} else {
return "(null)"
}
}
}
Note: Yes, we could also print “nil” instead of “(null)” but I think the latter is more expressive. You can always choose this one or the other.
Whenever you have optionals, you simply wrap them in a Printable
struct as below.
self.path = "/user/\(Printable(userId))" // "/user/23940"
print(Printable(userId)) // "23940"
Or, in a more complex example:
struct User: CustomStringConvertible {
let id: String
let firstName: String?
let lastName: String?
var description: String {
var string = id
string += ", \(Printable(firstName))"
string += ", \(Printable(lastName))"
return string
}
}
let user1 = User(
id: "23940",
firstName: "Göksel",
lastName: "Köksal"
)
let user2 = User(
id: "23941",
firstName: "Sıla",
lastName: nil
)
print(user1)
print(user2)
Without Printable
struct, these objects would print below;
"23940, Optional("Göksel"), Optional("Köksal")"
"23941, Optional("Sıla"), nil"
With Printable
, it prints;
"23940, Göksel, Köksal"
"23941, Sıla, (null)"
The latter seems much cleaner.
Alternatives
Global “describe” Function
func describe<T>(_ value: Optional<T>) -> String {
switch value {
case .some(let wrapped):
return String(describing: wrapped)
case .none:
return "(null)"
}
}
Optional Extension
extension Optional {
var stringValue: String {
switch self {
case .some(let wrapped):
return String(describing: wrapped)
case .none:
return "(null)"
}
}
}
Credit: Alp Avanoğlu
Comparison
var url1: URL? = URL(string: "www.google.com")
var url2: URL? = nil
// `Printable` struct:
print(Printable(url1)) // Prints "www.google.com"
print(Printable(url2)) // Prints "(null)"
// `describe` global function:
print(describe(url1)) // Prints "www.google.com"
print(describe(url2)) // Prints "(null)"
// `stringValue` extension on `Optional`:
print(url1.stringValue) // Prints "www.google.com"
print(url2.stringValue) // Prints "(null)"
Moral of the Story
If you are annoyed by logs like Optional(www.google.com)
on your console, you can start using one of the alternatives.
Thanks for dropping by! And, as usual, help spread the word. ❤️👏
Image Credit: Photo by Max Bender on Unsplash