Swift, here I come!
It’s time to start another version of the MarvelBrowser project. As I did with the Objective-C version, I begin the Swift version with a spike solution. But the first time was to see if I could satisfy Marvel’s authentication requirements. Basically I needed to get the incantation correct. This time, I know the steps to take, but I will repeat them in Swift.
I have two goals:
- Make it work
- Make it Swifty
Could you give me feedback on the Swiftiness of my code?
[This post is part of the series TDD Sample App: The Complete Collection …So Far]
Hide Those API Keys
In Objective-C, I put the definitions of my public and private API keys into NSStrings:
static NSString *const MarvelPublicKey = @"my-public-key";
static NSString *const MarvelPrivateKey = @"my-private-key";
A big drawback of Objective-C is its lack of namespaces. There is one namespace: the single, global namespace. The real problem is that we can’t create more. To avoid clashes, we use long, verbose names.
So here’s how I decided to do it in Swift:
struct MarvelKeys {
static let publicKey = "my-public-key"
static let privateKey = "my-private-key"
}
This struct is never instantiated. Its sole purpose is to add semantic organization. (These days, I use an enum instead because then no one can instantiate it.)
First Steps
In the viewDidLoad() method of ViewController, I begin by concatenating a timestamp, the private key, and the public key:
override func viewDidLoad() {
super.viewDidLoad()
// Concatenate keys per https://developer.marvel.com/documentation/authorization
let timeStamp = "1" // Hard-coded for spike
let keys = timeStamp + MarvelKeys.privateKey + MarvelKeys.publicKey
// Confirm manually:
print(keys)
}
Here are some things that strike me about Swift:
- The need to declare a method as an override method provides important feedback. It asks the question, “Don’t you need to call super?”
- Type inference is cool. I know, it’s so 2 years ago. But not having to declare the types… yeah. That’s nice.
- The super-easy string concatenation takes me back to my BASIC days as a kid. (I do hear the voice of Alex Stepanov in my head. He’s complaining that it violates the commutative property of +. Ah well, purity sometimes gives way to pragmatism.)
- I rarely need the timestamp logging that NSLog provides. print takes me back to coding in BASIC.
Create MD5 Hash
This is where Swift first got hard for me. How do I call the plain-C function CC_MD5? Because Objective-C is a strict superset of C, everything in C is available. This is a strength of Objective-C that kept it going for 30 years. It’s also a disadvantage to have a language that is a two-headed beast, bringing along all of C’s lack of safety.
To access CC_MD5 from Swift, I had to create a bridging header MarvelBrowser-Swift-Bridging-Header.h:
#import <CommonCrypto/CommonCrypto.h>
How do I convert the concatenated keys to a UTF8 string, pass it in, and get data back? This was frustrating, but eventually, I figured out something that works. (It can be made cleaner. But in a spike solution, clean isn’t the goal. Quick learning is the goal, so the code can be dirty.)
// Create MD5 hash:
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5(keys, CC_LONG(keys.utf8.count), &digest)
var hash = ""
for (_, byte) in digest.enumerated() {
hash += String(format: "%02x", byte)
}
// Manually confirm that it's 32 hex digits:
print(hash)
Question: Is the first argument to CC_MD5 automatically converted to UTF8? Or am I just getting lucky, because my string happens to have nothing but ASCII-expressible characters?
To convert each byte to hex, the original Objective-C code used a classic for-loop. I thought about doing
for i in 0 ..< CC_MD5_DIGEST_LENGTH
But the only purpose of the index is to access elements of the hash array. Using for-in over an enumeration seems like a more Swifty approach.
Create URL String
I see that stringWithFormat gets a lot less use in Swift.
// Manually confirm URL string:
let urlString = "https://gateway.marvel.com/v1/public/characters?nameStartsWith=Spider&ts=\(timeStamp)&apikey=\(MarvelKeys.publicKey)&hash=\(hash)"
print(urlString)
Swift’s string interpolation makes it so you no longer have to worry about variable order. Again, this is so 2 years ago. Still, I pause to appreciate it.
I did find one thing to complain about: Isn’t there a way in Swift to wrap a long string literal across multiple lines?
Create and Fire the Data Task
Creating the data task, and logging the results, was pretty straightforward. In Swift 3, the hardest part was continuing to type "NS" by habit, and finding the right way to express UTF8 encoding.
// Create data task:
let session = URLSession.shared
let url = URL(string: urlString)!
let dataTask = session.dataTask(with: url) { data, response, error in
print("error: ", error);
print("response: ", response);
let str = String(data: data!, encoding: String.Encoding.utf8)
print("data: ", str);
}
dataTask.resume()
I’m aware of the two exclamation marks in the code above. Force-unwrapping is generally something to avoid. But in this case, it’s just a spike solution. The code will be kept off to the side in the spike-networking branch. I’ll exercise more care in the master branch.
Ahh, that closure. There are two things I like about it. First is the name “closure” over the more generic “block.” It better expresses what happens already happens to variables in Objective-C blocks, so I like the more precise name.
Finally, the ability to express a closure argument as a trailing closure. Again, let me pause to appreciate this language.
How’d I Do So Far?
I have to say, so far Swift is pretty cool. My appreciation may diminish when I get into stubbing and mocking. But for today, I will sit back and smile. And I was glad when I saw JSON results in the console.
How did I do as far as making my code Swifty? Got any tips for unwrapping nullables? Leave a comment below to add your observations!
[This post is part of the series TDD Sample App: The Complete Collection …So Far]
Quality Coding repeats the network call spike solution, this time in Swift
I would change it
struct MarvelKeys {
static let publicKey = "my-public-key"
static let privateKey = "my-private-key"
}
to
enum MarvelKeys {
static let publicKey = "my-public-key"
static let privateKey = "my-private-key"
}
to avoid the chance of trying to instantiate the MarvelKeys struct:
let keys = MarvelKeys()
Ahh, clever. Thanks to everyone who chimed in.
Hey Jon, great post, glad your coming to the Swift community ;)
Did you consider using an enum with no cases for your ‘struct MarvelKeys’, this way it is impossible to instantiated it ;)
I was thinking enum at first glance too…
Great job! I’m very inspired by all your posts and have learned so much from you.
Regarding your questions and request for feedback, here are two points:
1. Wrapping a long string literal across multiple lines? Yes. Use the + operator. For example:
let five = 5
let myString = “this is the beginning of a long string ” +
“that i will make across a few different lines ” +
“with some string interpolation with the number \(five)”
print(myString)
2. Unwrapping optionals: I wrote a post on this for my publisher, hopefully it will help. It was about a year ago, but it all still applies (there are some nuances in Swift 3 with Implicitly Unwrapped Optionals, but if I understand correctly, behavior is still for most intents and purposes the same): http://www.informit.com/articles/article.aspx?p=2359760
Basically, you can unwrap forcefully (not recommended in most cases) with !, chain with ? (which will provide an optional result), unwrap or use a default with ??, or bind using if-let/guard-let syntax, or any other binding combination. Since an optional is just an enumeration, you can also switch on it, but the other methods are much easier to work with and understand.
BJ, thanks for the info on optionals. There’s much more there than I realized! I’ll study your article.
As for wrapping a long string literal, see my response to Paul Williamson below.
The closure-in parameters `(data, response, error)` only need to be parenthesized if you add their types. If you can do it with type inference, you might as well drop the parens.
Nice. Thanks!
Regarding force unwrapping… first one is a good candidate for a `guard` and second one perhaps an `if let data = data {…}`
I see — thanks for the specific examples.
After watching your talks on youtube, I’m really inspired to start developing in TDD.. So thank you for that.
About the code, I like using NSURLComponents to build the URL, specially the queryItems property…
Hey Jon! Great post!
Curious as to what your thoughts are on this approach in terms of achieving “swifty”. ;) To avoid possibly force unwrapping nil `data` one can use an `if let`.
if let data = data {
print(“data: “, String(data: data, encoding: String.Encoding.utf8));
}
If we wanted to do more things with `data`, we could avoid the extra `if` indentation by using `guard`.
guard let data = data else { return }
print(“data: “, String(data: data, encoding: String.Encoding.utf8));
Yup immediatley wanted that struct to be an enum!
Creating the hash from the digest could be simplified by not enumerating if you don’t need the index.
for byte in digest { //do work }
This could be “simplified” even a bit more by using reduce.
let hash = digest.reduce("", combine: { $0 + String(format: "%02x", $1)})
Ahh now that’s exactly the kind of “next step” I was hoping for! Takes a little getting used to, but then I could read it okay.
Nice! I just found your blog and am finding some useful stuff in here :)
Not bad for making things “Swifty” on a first pass, but you can do better! In addition to John’s comment, above, the suggestion to change to an enum for the keys is a good one (you’d only go the struct route if you planned on allowing extensions to add further keys). But I’d also suggest dropping the plural on “MarvelKeys” and. It would be nice to also remove the redundant “Key” part of each case, but this results in a clash of name with reserved keyword (“public” and “private”), which you can either get around by capitalising the first letter (Public/Private) and breaking the case naming convention (start with lowercase), or just keep publicKey/privateKey names (and breaking the naming convention of not repeating redundant info). If you go with the former, you get:
enum MarvelKey {
case Public = "my-public-key"
case Private = "my-private-key"
}
And usage:
let keys = "\(timeStamp)\(MarvelKey.Private)\(MarvelKey.Public)"
Yes, as you can tell, I was struggling to find a way to work around the keywords. Bending the rules seems like a decent approach.
Changing “MarvelKeys” to “MarvelKey” is subtle, but improves legibility at the point-of-call.
I like it.
Question: Why do you prefer string interpolation over plain concatenation? I find concatenation easier to read because it introduces blank spaces. …Ohh, because you changed the keys from statics to cases. Curiously, doing so broke the spike, so I reverted that bit.
I recently switched from Public to `public`
Sure! Just terminate the string and add a + operator. E.g.
let urlString = "https://gateway.marvel.com/v1/public/characters" +
"?nameStartsWith=Spider" +
"&ts=\(timeStamp)" +
"&apikey=\(MarvelKeys.publicKey)" +
"&hash=\(hash)"
How can I tell whether this does what I want (create a single literal) rather than perform operations on several literals?