Developing Seam: Thoughts on Swift

Seam & Swift

I recently released Seam: Relax Your Mind, my first game to the App Store. It's a procedurally generated puzzle game wherein you rotate and snap together puzzle pieces. I have minimal experience writing iOS apps - Seam is a small project, but it's my biggest yet.

Seam is written in Swift and uses Apple's SpriteKit game engine (I'll talk about SpriteKit later). Here are a few things about Swift I think are interesting.

Getting Swifty

The type system: I prefer statically typed languages, especially if there are strict speed requirements (like with a video game). I'd like to specifically highlight the syntax for dealing with types, which is beautifully consistent. This is a subtle thing that I think has a massive impact on a vague feeling ("code feel") that makes reasoning about the typing easier.

// this is an integer
var score: Int = 0

// this is an array of integers
var dailyScores: [Int] = [0, 10, 19]

// this is a function that takes an array of ints and returns an int
func bestDaily(dailyScores: [Int]) -> Int { ... }

// this is a function that takes no args and returns a function
// can you infer the type signature of that function?
func funcMaker() -> (([Int]) -> Int) { ... }

The Swift type system has too many features to discuss in a blog post. It's not perfect though, the type inference fails too frequently. I rely on type inference only in the simplest cases.

Optionals: This might be Stockholm syndrome because there's almost no way to avoid dealing with optionals if you're writing against iOS libraries, but my arc (pun intended) with optionals was "these are annoying" to "these are completely sensible (but still a tiny bit annoying)".

That tiny annoyance is well worth the incredible confidence reasoning against optional values gives you, you check if the value you're interested in manipulating is nil or not, and you're off to the races. It enforces good habits and it gives you a hook to bail out early if something isn't quite set the way you were expecting.

func getUser(_ username: String) -> User? {
    // this function looks for a User by username, it returns
    // an "Optional User" which can either be a valid user or nil
}

func sendLateFeeNotice(username: String) {
    let user = getUser(username)
    if user == nil {
        return
    }
    // now that we've checked if no user was found, we can now
    // assume our user object is safe to use and manipulate
    ...
}

That's a slightly contrived example but once you get comfortable with using optionals (and if you're not yet, just keep at it, you can do it!) they genuinely inspire confidence in your code. Confidence is a great thing in programming because it clears your head, you don't have to hold as much logical spaghetti in your head, freeing you to think about the problem at hand.

Optionals are not magic though, they reduce the need for some types of error handling but not all. Additionally, there is a feature called optional chaining that allows you to safely drill down a deep data structure and get at the underlying optional - this feature is totally necessary but at times you might see something that looks like this:

let vpOperationsEmail = company?.operations?.vicePresident?.user?.email

This is a very useful feature, but sometimes it looks and feels clunky. As a rule of thumb I think you should avoid force unwrapping basically any long optional chain (using the ! operator) and do your nil check (or use guard) right after.

I could wax poetic about the merits and shortcomings of Swift for a long time, but let me wrap up with one more.

Memory management: Swift uses Automatic Reference Counting (ARC) to manage almost all memory for you. There are some technical implications due to that but for most programmers the biggest will be managing reference cycles using the weak keyword. Thinking about cycles can get tricky if your data structures are complex. On your journey to deeper understanding about reference cycles and why they happen: favor using things passed by value, like enums & structs, which will help you avoid reference cycles entirely.