
I was trying to build a URL the other day. Check the following code block:
1 2 3 4 5 6 |
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:
1 2 3 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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.
1 2 |
self.path = "/user/\(Printable(userId))" // "/user/23940" print(Printable(userId)) // "23940" |
Or, in a more complex example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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: "Goksel", lastName: "Koksal" ) let user2 = User( id: "23941", firstName: "Sila", lastName: nil ) print(user1) print(user2) |
Without Printable
struct, these objects would print below;
1 2 |
"23940, Optional("Goksel"), Optional("Koksal")" "23941, Optional("Sila"), nil" |
With Printable
, it prints;
1 2 |
"23940, Goksel, Koksal" "23941, Sila, (null)" |
The latter seems much cleaner.
Moral of the Story
Don’t use optionals directly while treating them as strings, or printing them on the console.
Thanks for dropping by! And, as usual, help spread the word. ❤️👏