Look, Mom! My First Spike Solution in Swift

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:

  1. Make it work
  2. 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. Actually, there is a 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.

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 almost never need the timestamp logging that NSLog does. print again takes me back to 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 things out. Swift 3 apparently makes this much simpler. (Either that, or the Swift 2 examples I found weren’t very good. Or maybe both.)

        // 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

        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. Something tells me 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]

About the Author Jon Reid

Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He's been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:
20 comments

Comments are closed