Swift: as easy as 1, 2, 3…?

Apple shook the developer community a few years ago with the release of Swift, a language that is “concise, expressive and lightning fast”. As a developer mainly working on Apple platforms, I decided to jump on it right away, trying to keep in mind there was going to be bumps in the road like there has been with any new language.

Versions 1 and 2 came out one year apart and 3 followed the same schedule. Here’s a little breakdown on these three advertised aspects of the language.

Concise

Swift is very concise. That’s, if you’re comparing it to Objective-C. As most macOS / iOS apps were written in Objective-C, that probably was the goal. In some cases, you’ll end up writing less characters in C, even C++, than Swift. Not to mention interpreted languages such as Python or Ruby. But that’s not the point – when writing C/C++, you can keep it small, but make it unreadable for others.

There are definite situations where Swift takes a common problem and just solves it for you. One example is the chaining of optionals, Swift’s non-pointer pointers:

unsure?.method()?.memberpointer?.method()

The non-pointer pointer (optional) “unsure” is possibly pointing to a object of a class that has the “method” method that has unsure outcome – if it returns something, it’s going to be an object that has a member non-pointer pointer “memberpointer” that might or might not contain a object of a class that has a “method” method. If it does, it’s called.

Awesome. Version 1.

But wait – what if you want to know if it failed? What if the returned object didn’t have a pointer stored in the member pointer non-pointer pointer… “optional” variable?

Version 2 introduced guards. You could now guard the statement:

guard unsure?.method()?.memberpointer?.method() else {
  if unsure == nil {
    NSLog("It was unsure.")
  }
  break
}

Fine. But now you must exit the current stack, since something went wrong. This is not your usual try / catch where you can still continue – guard takes your current position and runs with it. So when a non-pointer pointer returns a nil, you must exit, using break, continue, return or throw.

It’s a big mind shift. Good thing is they also provide a defer keyword, with which you can create a block that is always executed when popping out of the current stack.

Also, it looks like Swift really makes an effort in trying to make us not think about pointers.

Expressive

Comparing to Objective-C, this is a definite yes. Almost to the point of being BASIC. Headers are gone, as they should in the year 2017, brackets are being used for creating arrays, and Swift has all the other things that exist in modern languages. I don’t believe there is another Syntax Guide as lengthy as the one for Swift 3 – it’s definitely packed with features.

Swift also lets you extend existing base types, like Objective-C. Sometimes this is very necessary. Example:

Cross-platform development using SceneKit

SceneKit is nice and pretty feature-rich and starts to be exhaustively documented as well. There are some caveats, though. If you want the same source to translate to iOS, tvOS and OS X (since it’s available on all three), you’ll pretty quickly run into a major problem with base data types.

The usual floating-point data type used in most libraries for Swift is Float. SceneKit on iOS and tvOS uses Float for numeric values on such things as colors, transformations, animations and such. SceneKit on macOS uses CGFloat for everything.

Swift doesn’t let you automatically cast CGFloats to Floats. Or Floats to CGFloats.

Such basic types as SCNVector3 consist of three values of non-castable types that vary depending on the platform. This leads to having to choose between two options:

  1. Write all code dealing with SCNVectors twice, once for iOS and tvOS and once for macOS
  2. Introduce intermediate accessors for Float and/or Double to get the right kind of values when accessing SCNVectors and implement that twice

This is where extending base types such as Float come in handy. With the following two source files, working with SceneKit becomes less of a pain in a multi-platform scenario.

// Floats-osx.swift
import Foundation

extension Float {
 func f() -> Float {
 return self
 }

 func scf() -> CGFloat {
 return CGFloat(self)
 }
 
 func cgf() -> CGFloat {
 return CGFloat(self)
 }
}

extension CGFloat {
 func scf() -> CGFloat {
 return CGFloat(self)
 }
 
 func f() -> Float {
 return Float(self)
 }
}

And the counterpart for iOS/tvOS:

// Floats-tvOS.swift
import Foundation
import SceneKit

extension Float {
 func f() -> Float {
 return self
 }
 func scf() -> Float {
 return self
 }

 func cgf() -> CGFloat {
 return CGFloat(self)
 }
}

extension CGFloat {
 func scf() -> Float {
 return Float(self)
 }
 
 func f() -> Float {
 return Float(self)
 }
}

Now, on either platform, a float variable will have a function .scf() that will always return the right type for SceneKit. Also, no matter which one it is, a .f() will give a Float type. In addition to these, there’s a .cgf() to get the proper type for functions that require a CGFloat.

Lightning fast

Swift could be categorized as a language that compiles into machine language while maintaining readability. This means it can produce very well performing apps – but there still are things better left implemented in pure C.

In CARL, the user interface and all higher-level functions (such as the sequencer engine) are written in Swift, yet the audio synthesis engine had to be written in C to make it work fast enough so that it wouldn’t burn through the CPU while playing. You may notice that even with several tracks and tens of notes playing simultaneously, it’s barely reaching 20-30% CPU use on a modern MacBook. And most of that usage is UI-related, the parts that were written in Swift. When Swift 1 was released, I rewrote the synth engine in Swift using its internal types and barely got a single note out before hitting 100% on a core.

The reason there’s still a massive difference in some cases is that in C, you can get away with things like in-memory lookup tables and locked in-memory array buffers, bringing the overhead in dealing with audio samples down to almost nothing.

But more than the actual run speed of an application, think about how much Swift can save you time when writing an app. With Objective-C, you would spend a lot of time keeping .h files in sync with .m files, changing all the hundreds of @implementation and @interface blocks to match, basically consuming the square bracket keys on your keyboard and counting them on the screen.

Also, being able to bring in a “module” into your app just by bringing a .swift file into your project saves a ton of time in learning how to point to include folders and library folders and having to instruct the compiler and linker separately every step of the way.

Conclusion

Yes, Swift can be considered concise, expressive and lightning fast. For many things today, especially since it’s expanding beyond macOS and iOS, it will be a good choice for a fresh project.

But even more importantly, Swift is finally a good language for new and learning programmers. Don’t learn Java. Java is for schools. Net PCs never happened. Learn Swift. But also learn Python. And C, if possible.

Leave a Reply


*