Scripting in Swift is Pretty Awesome



Since Apple came out with Swift, most of us have focused on using it to write our iOS/Mac OS X applications; it was the first instinct we had upon learning the language. Hell, it was the only instinct I had when learning the language the day it first came out. It wasn't until 7 months later  that I realized it was even more powerful than it seemed, and that scripting in swift was not only possible but quite easy!

The Story...

The move from Swift 1.1 to Swift 1.2 was a big one if you recall. The update changed the syntax of the language and populated our projects with what seemed like a thousand errors when running our projects in the new Xcode Beta at the time. What made this worth it was the enhancements to the Swift compiler. In particular, I'm talking about the enhancement that changed the recompilation of every swift file everytime you started a build to the new fancy-schmancy incremental compilations. We had a huge, mature project at this point so the compile times were killing us. We were seeing compile times of 15 minutes on average even if your only change was a comment! In comparison, the new swift compiler in Swift 1.2 gave us an average of 1-3 minutes for each compilation.

Long story short, we needed to use the beta to develop (because, compile times) but couldn't release to the App Store if we had code written in Swift 1.2 due to their beta release restrictions. What was the solution, you ask? Well, we decided to write Swift 1.2 code AND it's Swift 1.1 counterparts at the same time. It looked like this:

//====================Swift 1.2 Code===================="        
//let kraken = mythicalBeast as! Kraken        
//====================Old Swift Code====================" //       
let kraken = mythicalBeast as Kraken
//==========================End========================="

For release time, we needed to write a script that would comment out the Swift 1.2 code and uncommented the old Swift 1.1 code and vice versa for dev cycles. The only scripting language I knew about was Python. A quick Google search gave me this. If you pay attention to what's on that page, you'll notice it mentions an interactive mode and creating a Python file with a line of Shebang at the top. That interactive mode launches what's called a Python REPL (Read-Eval-Print-Loop). Wait, what's that?! A REPL?!?!

Swift has one of those!!!

I turns out, you can use the Swift REPL to do the same thing! So let's start:

Actually Scripting in Swift

First and foremost, we need an environment to easily code in. Creating a swift file is an easy task but when you're writing your script, you're going to need syntax highlighting, quick class lookup, documentation, and so on and so forth. You could always just touch MyScript.swift in the Terminal and use Vim to go from there but this next part will be geared to someone who isn't as much of a badass (but soon will be).

So first, you need to start with a new Xcode OS X Command Line Tool Application:

Screen Shot 2015-06-29 at 10.47.01 AM

Screen Shot 2015-06-29 at 10.47.01 AM

Call it whatever you want but before you start bandying about renaming files let me save you a little time: Xcode does not recognize Shebang in any other file but the main.swift file. Throwing it at the top of any other file that isn't named "main.swift" will give you this warning:

For now, let's just save the renaming of your main.swift file for the end when your script is ready to go. So in your main.swift file, remove any generated code and comments and throw this line of code on the first line of your swift file:

#!/usr/bin/swift

This line essentially launches the swift REPL first so that rest of your file actually compiles in a swift environment.

Next step is to make your main.swift file executable. Open up Terminal, navigate to your main.swift file's directory and execute this command:

$ chmod +x main.swift

From here go back to Xcode and do whatever you want! The rest of the file can be treated like any regular swift file. The cool part here is that you can even import frameworks like Foundation as well! Anything you can do with Foundation, you can put into a script. This includes File I/O, string manipulation, and more. The only gotcha to remember is that scripts in swift follow the same principle as writing a program in C or writing things in an Xcode Playground - any functions, classes & declarations need to be above their usages like so:

#!/usr/bin/swift 

import Foundation 
class MythicalBeast { 
    func whatsMyName() { 
        print("I don't know what I am, but I'm the stuff of legends.") 
    } 
} 

class Kraken: MythicalBeast { 
    override func whatsMyName() { 
        print("I'm the Kraken, yo!") 
    } 
} 

//can't use the Kraken class until after the declaration 
let kraken = Kraken()
kraken.whatsMyName()

Once you have your script ready to go, then running it is a simple matter of navigating to your swift script & executing it in the Terminal like so:

$./main.swift 
I'm the Kraken, yo!

Your script can even accept arguments! just append whatever you want after your execution command to add your arguments like a regular script:

$ ./main.swift firstArgument secondArgument thirdArgument

To read these arguments in your script, you can use the Process enum in the Swift Standard Library like so:

dump(Process.arguments)

Using the terminal command we just wrote, the above code will print out each argument to the Terminal on new lines like so:

$ ./main.swift firstArgument secondArgument thirdArgument 
▿ 4 elements
    - [0]: ./main.swift   
    - [1]: firstArgument   
    - [2]: secondArgument   
    - [3]: thirdArgument

There you go! That should be everything you need to know. Now be careful -

NOTE: If you'd like to see a non updated version of my first ever Swift script, check this repo out. That repo contains the script I wrote in the story at the top of this post.

Using swiftc to Make Things a Little Easier (Thanks to @eneko)

I just recently got a tweet from @eneko stating that you could also use the swift compiler in Terminal, swiftc (only as of Xcode 6.1 and on Yosemite), to compile your swift files into an executable binary. This skips the chmod +x call and gets right to using the tools we know and love:

# The parameter after -o is the name you really want your script 
# to be called other than "main".
$ swiftc main.swift -o kraken
The only issue with using swiftc is that, depending on your environment, you may need to run `xcrun -sdk macosx swiftc` before your Terminal command if you try to import frameworks like `Foundation` in your swift script like so:
$ xcrun -sdk macosx swiftc kraken.swift -o kraken

This is only needed if you get an error that looks like this in your console:

<unknown>:0: error: cannot load underlying module for 'CoreGraphics'
<unknown>:0: note: did you forget to set an SDK using -sdk or SDKROOT?
<unknown>:0: note: use "xcrun -sdk macosx swift" to select the default OS X SDK installed with Xcode

If that looks ugly and hard to remember, then you can always alias the xcrun line by putting this line in your .bash_profile:

alias swiftc='xcrun -sdk macosx swiftc'

Save your .bash_profile, start a new Terminal window and voila! This beautiful command should now work like God intended:

$ swiftc main.swift -o kraken

After a successful compilation, you should be able to execute your executable binary like so:

$ ./kraken

##Pro-Tip #1 Using this command should also enable you to compile more than one swift file together. You may need this if your swift script gets to be a little too long and you want to split functionality amongst several files. Just add the files you want to compile together before the -o argument and you should be set!

$ swiftc one.swift two.swift three.swift -o combined.swift

##Pro-Tip #2 If you want to get even fancier and you want to use your script throughout your system, try moving your binary to your /usr/local/bin folder. Doing so can allow your script to be used in any directory in Terminal assuming you have your $PATH set correctly. It also removes the need to call your script with ./. You can do this by moving your executable like so:

$ cp kraken /usr/local/bin

This simplifies the execution of your script even further by making your future commands to this script look like this:

$ kraken firstArgument secondArgument etc

Cato - A Neat Library by @NeoNacho

I'm also going to encourage you to check out this great ruby gem by Boris from Cocoapods. It's a neat way of enabling your scripts in Swift to be versioned by giving you power to specify which version of Swift your script is written in and more!

Conclusion

Scripting is a powerful asset and a useful tool in any programmer's tool belt. For many iOS Devs, Swift or Objective-C are the only languages they know. If they know Swift, then there is no need to learn Python or another scripting language when writing simple scripts for any automation process. This even includes Continuous Integration! We even use it with Jenkins when automating deployment of our applications. Hopefully with this guide, you can travel a little further on the automation train.

Happy coding fellow nerds!