Much fanfare accompanied the “playground” feature of Xcode 6 that allows developers to write some code and see the effect in realtime. If you have any experience with scripting languages such as Ruby or Python, you recognize this as nothing more than running the interactive language interpreter. With Ruby you type irb
at the command line; for Python try python
.
$ irb
1.9.3-p448 :001 > fruit = ['apples', 'pears', 'bananas']
=> ["apples", "pears", "bananas"]
$ python
Python 2.7.6 (default, May 30 2014, 22:21:15)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.34.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> fruit = ['apples', 'pears', 'bananas']
For some reason Swift employs the more technical name of an interpreter, the REPL, or Read-Eval-Print-Loop. The interpreter reads what you entered, evaluates it, prints it, and then loops back to read. The REPL isn’t magic, and its not anything new, it’s just new to those whose only experience with iOS and Mac development is Objective-C. Objective-C (and it’s C and C++ brethren) use the ECLR paradigm: Edit-Compile-Link-Run.
It stands to reason that if you can run a Swift interpreter in Xcode, you can run it from the commandline. And sure enough you can you just have to dig down into the Xcode 6 package contents and find the binary to run. So open up a Terminal or iTerm 2 and type into your shell:
export PATH="/Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/":$PATH
Now, this assumes that the Xcode6-Beta application was installed into /Applications
. If you are running it off of your desktop or some other location, adjust the path accordingly. For those unfamiliar with the Unix PATH
environment variable, check out Wikipedia. Let’s fire up the interactive Swift interpreter!
$ swift -repl
Welcome to Swift! Type :help for assistance.
1>
Create an array similar to the examples above:
1> fruit = ["apples", "pears", "bananas"]
fruit = ["apples", "pears", "bananas"]
^
Okay, so we know that we still have to use the Swift syntax for declaring variables. We can cope.
1> var fruit = ["apples", "pears", "bananas"]
fruit: String[] = size=3 {
[0] = "apples"
[1] = "pears"
[2] = "bananas"
}
Note that similar to Ruby’s interpreter, Swift echoed back what we declared. That’s the Print part of the REPL.
Now iterate over the fruit
:
2> for f in fruit
}
^
Oops… we didn’t end the line with an opening brace. Swift clearly prefers that the opening brace be on the same line as the for
keyword, and we happen to prefer that style as well.
5> for f in fruit {
6. println("One type of fruit is \(f)")
7. }
One type of fruit is apples
One type of fruit is pears
One type of fruit is bananas
To quit out of the interpreter you can type CTRL-D or just type :q
and hit Return.
Cool indeed. But what we’d really like is to treat Swift as scripting language, not just another language we can play with in an interpreter. We’ll give that a try by writing a simple Swift script that accepts a list of filenames and then performs the Unix wc
function. Those that have dabbled in system administration or report generation know that wc
prints the number of lines, words, and characters in each of its input files.
With the use of the -i
flag to the swift
executable we can do exactly that. Along the way we’ll continue to drive home the point that adopting Swift does not mean you have to abandon everything you know about iOS and OS X application development.
To make a point, we won’t be using the Xcode editor to edit the file wc.swift
, but rather our favorite Mac version of Emacs known as Aquamacs. If you are familiar with Emacs editing modes, there’s even one for swift that we like to use.
The first line of wc.swift
should be
[objc]
import Foundation
[/objc]
This is the real reason why developing in Swift already feels so robust; it doesn’t require abandonment of the core iOS and Mac OS X frameworks. Bringing in the Foundation Framework links in all manner of classes ranging from NSCalendar
to NSXMLParser
. You will certainly almost always want to start your Swift files with import Foundation
, and it’s so fundamental that Xcode puts the line in for you when creating a new Swift file.
The next line of code sets up the array of filenames that were (or will be) passed into our application.
[objc]
let filenames = Process.arguments
[/objc]
Now that we have our filenames we’ll iterate on them and use the fact that Swift String
gives us access to NSString
methods to load a string with the contents of that file.
[objc]
for filename in filenames {
var fileContents:String? = String.stringWithContentsOfFile(filename)
[/objc]
Before we continue let’s again look at why we had to declare fileContents
as a String?
. The reason is straightforward: imagine if the stringWithContentsOfFile
method failed for some reason, then what would fileContents
hold? Nothing. That’s why the variable is declared as an optional: it can either be a String
or it can be nothing at all.
Since fileContents
is an optional, any further methods called on it would have to accompanied with an exclamation mark or we would have to assign the contents to an unwrapped variable or constant. Trying to access methods using the optional results in:
wc.swift:16:23: error: 'String?' does not have a member named 'componentsSeparatedByCharactersInSet'
var lines:Array = fileContents.componentsSeparatedByCharactersInSet(newline)
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
So, we have to use exclamation mark:
var lines:Array = fileContents!.componentsSeparatedByCharactersInSet(newline)
This is, however, poor form. If reading the file contents of the string did fail (for example, there was no file there to begin with), accessing it in this manner will crash with a likely fatal error: Can't unwrap Optional.None
. Instead we should use optional binding to encapsulate our wc
routine:
[objc]
if let contents = fileContents {
[/objc]
This line basically says, “If fileContents
is a String
and not the None
type then assign that string to the constant contents
and provide that constant to the true branch of the if
statement.” That’s a mouthful. What this does is allows use to use contents
without the exclamation mark, because it is already unwrapped and we know that it is a String
.
The remaining code is straightforward and illustrates once more that Swift provides access to the Foundation Framework. Using NSCharacterSet
and the NSString componentsSeparatedByCharactersInSet
method we are able to quickly pull apart how many lines and words are in our string. Tying it all together we get:
[objc]
import Foundation
let filenames = Process.arguments
for filename in filenames {
var fileContents:String? = String.stringWithContentsOfFile(filename)
if let contents = fileContents {
var whitespace = NSCharacterSet.whitespaceAndNewlineCharacterSet() as NSCharacterSet
var newline = NSCharacterSet.newlineCharacterSet() as NSCharacterSet
var lines:Array = contents.componentsSeparatedByCharactersInSet(newline)
var words:Array = contents.componentsSeparatedByCharactersInSet(whitespace)
var numChars = countElements(contents)
var numLines = lines.count – 1
var numWords = words.count – 1
println("\t\(numLines)\t\(numWords)\t\(numChars)\t\(filename)")
} else {
println("wc.swift: \(filename): No such file")
}
}
[/objc]
Finally, how to run this! The swift
binary provides the -i
switch for running in immediate mode, which is what we want. So we type swift -i wc.swift
to run our code and…
$ swift -i wc.swift
wc.swift:5:8: error: cannot load underlying module for 'Foundation'
import Foundation
^
Oops. Remember that your Xcode development environment is capable of compiling applications for all manner of iOS versions as well as Mac OS X. There are differences between the operating systems and versions, so you need to tell swift
which SDK you’re going to use (similar to setting your iOS SDK in Xcode). Thankfully swift
gives a nice little hint: "use -sdk $(xcrun --show-sdk-path --sdk macosx)" to select the default OS X SDK
. We’ll try that.
$ swift -i -sdk $(xcrun --show-sdk-path --sdk macosx) wc.swift
wc.swift: -i: No such file
31 184 831 wc.swift
wc.swift: -enable-objc-attr-requires-objc-module: No such file
wc.swift: -target: No such file
wc.swift: x86_64-apple-darwin14.0.0: No such file
wc.swift: -module-name: No such file
wc.swift: wc: No such file
What the… What’s happening here is the default behavior of Process.arguments
. If there are no options presented to the application Process.arguments
will contain the commandline for swift
, which is not what we want (obviously). In our example, Process.arguments
contained [-i, wc.swift, -enable-objc-attr-requires-objc-module, -target, x86_64-apple-darwin14.0.0, -module-name, wc, -sdk, /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk, -color-diagnostics]
.
To invoke the script in the intended manner we use the “dash-dash” option and then provide the filename. $ swift -i -sdk $(xcrun --show-sdk-path --sdk macosx) wc.swift -- declaration.txt
:
1 |
6 35 210 declaration.txt |
declaration.txt
contains:
1 2 3 4 5 6 |
We hold these truths to be self-evident, that all men are created equal, that they are endowed by their Creator with certain unalienable Rights, that among these are Life, Liberty and the pursuit of Happiness. |
Was writing and executing this as straightforward as Ruby or Python? No. But it does illustrate you can use Swift as a scripting language on your Mac and iteratively run bits of code without going through the Edit-Compile-Link-Run cycle.
This is some of what I’ve been looking for, finally a little bit of content.
I got the standard library reference as
well as the swift iBook, and I’ve never had a worse introduction to a language, and I’ve written executable (or interpretable code) in Fortran II, FAP, Fortran IV, Fortran 68, Basic, VisualBasic, C, C++, prolog, python, Haskell, OCaml. And, oh, Objective C. Maybe others.
Where can I find out more of the kind of information you have here?
John – thanks for the compliment. As Swift is new there’s an obvious lack of “cookbook” training material. You might find http://www.sososwift.com/ to be a good resource for finding more articles (I’ve posted my posts there), and I can guarantee you within the next few months an O’Reilly book will hit the shelves. If there’s any topic you’d like covered here let me know, I’d be happy to research it.
Thanks. With the reference to sososwift and the other material on this website I have more than enough to keep me busy for a while!