3.
Using Swift in the Command Line
Written by Sarah Reichelt
In Chapter 1, “Introducing Xcode”, you installed Xcode and saw how to create a Mac app from the standard template. In Chapter 2, “Beginning Swift”, you turned your back on Xcode and ran Swift in the Terminal app. You learned about data types, variables, constants and collections.
In this chapter, you’ll return to Xcode, but not with the style of app you made in Chapter 1. This time, you’ll create a command line tool. This is an app without a graphical user interface that runs in Terminal.
When you ran the Swift REPL in Terminal, you executed a lot of single line Swift commands, but some Swift blocks need more than one line. It’s easier to edit and follow these in Xcode.
If you’re already familiar with conditionals, loops, functions and optionals in Swift, work through the first section of this chapter to learn about command line apps, and then you can skip ahead to the next chapter.
Creating the App
Start Xcode like you did in Chapter 1. If you see the “Welcome to Xcode” window, click Create a new Xcode project. If you don’t see that window, go to File ▸ New ▸ Project…. Either method gets you to the template chooser.
This time, select macOS ▸ Command Line Tool:
Click Next and give your new project the name swifty. Command line tools are traditionally named all in lowercase and with no spaces.
Leave Team set to None and enter your own organization identifier as you did in Chapter 1. Make sure the language is set to Swift, or this app won’t be very swifty. :]
Then click Next and decide where to save it.
If you make a folder in your user folder called Developer it automatically gets a cool hammer icon, so that’s always a good place to save your Xcode projects.
Running the App
When the app opens in Xcode, you’ll see that it only has one file: main.swift. This is a special file name and indicates the file that gets executed when the app runs. Open main.swift. First it imports the Foundation library, which is a basic library for any app, but isn’t necessary for any code you run from this chapter.
Then, as is traditional, it has a single line of code that prints Hello, World!. But where does it print this to?
To start, press Command-R to build and run the app:
What happened? The Xcode status display showed that it was building and now says “Finished running swifty”. No app opened and no windows appeared.
Look in the debug console at the bottom of the Xcode window. There’s your “Hello, World!”, followed by a report that the program ended with an exit code of 0. In command line apps, an exit code of 0 means that the app successfully completed its task and then quit.
To prove that your app is responsible for this output, change the print line to:
print("Hello Swift")
Press Command-R to run again and confirm that the console shows your new text:
To make the console display larger from now on, click the button at the bottom right that shows the Hide the Variables View tool tip:
Using the Terminal App
Now you know how to run your command line tool from inside Xcode, but command line tools are normally run from Terminal.
Open Terminal by going to Applications ▸ Utilities ▸ Terminal.app or by pressing Command-Space and searching for Terminal. Press Command-K to clear the window so you start with a clean sheet.
Back in Xcode, select Product ▸ Show Build Folder in Finder. This opens a Finder window buried deep in your Library folder. Press Command-3 to get into Columns view and then follow the trail by clicking Products and Debug until you see the swifty executable file:
You could run it by double-clicking, but that’s not the usual way to run a command line tool. Instead, type cd in your Terminal window followed by a space. Then drag the Debug folder from your Finder window into the Terminal window.
The cd command changes directory to whatever you entered and dragging in a folder or file is the same as typing its full path. Press Return to switch Terminal into the Debug folder.
To run your command, type this:
./swifty
And there’s your “Hello Swift” text printed out in Terminal.
Terminal Paths
When you ran cd or swift repl in Terminal, you didn’t have to prefix the command with ./ so why did you have to do that here?
Terminal keeps a list of the directories or folders that it searches for commands. Your app’s Debug directory isn’t in that list, so you have to tell Terminal exactly where your command is. The ./ prefix is a shorthand way of telling Terminal to look in the current directory.
Note: If you’re curious to see where your Terminal searches, type
echo $PATHin the Terminal window. The output lists all the directories, separated by colons.
Now you know how a command line tool works, it’s time to get back to Xcode and learn some more Swift.
Making Decisions
One of the fundamentals of computer programming is having your code make decisions based on supplied information. In Swift, the most common way of doing this is with the if statement.
Add this section of code to your main.swift file:
// 1
if true {
// 2
print("This is true")
}
This looks simple, but there’s a lot of detail here:
- Start with the
ifkeyword and follow it with a condition. The condition must result in eithertrueorfalse. After that, type an opening curly brace. When you press Return, Xcode automatically gives you a closing brace and puts the cursor in between them. - Inside the braces, type the code that runs whenever the condition is true.
Press Command-R or click the Play button to run this and as you’d expect, “This is true” prints out:
Note: You may be wondering about the lines starting with
//. What are they and why doesn’t the app print them out? Whenever you type//, the Swift compiler sees the rest of that line as a comment and ignores it. Comments allow you to document your code.
Now you’ve seen how to construct an if block, but this one isn’t very useful. To make it do more work, you need to supply a different condition instead of true. The condition can be a Boolean variable, or it can be any Swift code that evaluates to either true or false.
Delete everything you have so far in main.swift and type this:
// 1
var userIsLoggedIn = true
// 2
if userIsLoggedIn {
print("Welcome")
}
This block is a bit more interesting:
- You create a Boolean variable. As you learned in the previous chapter, a Boolean variable is one that can only hold one of two values:
trueorfalse. - Now you use that variable as the condition for the
if.
Run the app and see what prints out:
Change the variable to false and run again. What do you see this time? Depending on your Xcode settings, the console area may have disappeared. If this happens, click the icon in the bottom right of the edit area or press Shift-Command-Y to bring it back. There’s nothing to see here because the if condition was false, so the “Welcome” message never printed.
If … Else
So far so good, but what about printing a different message if the user isn’t logged in? For that, you need to add an else.
Replace your if block with this:
if userIsLoggedIn {
print("Welcome")
} else {
print("Please log in")
}
You have the if, the condition and the curly braces exactly as before, but you’ve added an else followed by another set of curly braces. The code in the first set runs if the condition is true, while the second set runs if it’s false.
Run the app again with userIsLoggedIn set to false:
If you want to make sure the original if part still works, change userIsLoggedIn back to true and test it.
Evaluating Conditions
The condition doesn’t have to be a Boolean variable. It can be any code that evaluates to give a Boolean result.
Add this chunk of code to main.swift:
// 1
var name = "Bob"
// 2
if name.count < 4 {
// 3
print("\(name) is a very short name")
} else {
// 4
print("Good name.")
}
What does this do?
- Create a
Stringvariable and give it the value “Bob”. - Set up an
ifbut this time, the condition isname.count < 4. - If
namehas fewer than four characters this condition will betrue, and you use string interpolation to print a message that includes the name. - The
elseprints out a message for names with four or more letters.
Run this code to see what happens:
Now change “Bob” to a longer name and run the app again:
Comparison Operators
To create a Boolean result, you used a comparison operator — in this case, the less than operator. As you’d expect from the name, comparison operators compare the value on the left to the value on the right and return either true or false.
Here’s a list of comparisons you can make:
-
>greater than -
<less than -
>=greater than or equal to -
<=less than or equal to -
==equal to -
!=not equal to
You’re probably wondering why the equal to operator has two equals signs. In Swift, as in many languages, there’s a different operator for assign and for equality. A single equals sign assigns the value on the right to the object on the left. The double equals sign checks if the two are the same.
The ! sign is the not operator. You can use it paired with an equals sign, or before any comparison result, to invert it from true to false or from false to true.
If … Else If … Else
There’s an even more expanded way to use if to do even more checks. Add this block to your code in main.swift:
// 1
var score = 70
// 2
if score >= 100 {
print("You've won")
// 3
} else if score > 80 {
print("Nearly there...")
// 4
} else {
print("Keep going")
}
Going through these lines:
- Set up an integer variable called
score. - Check if
scoreis greater than or equal to 100. If so, print “You’ve won”. - If
scoreis less than 100, the code execution falls through to the nextelse ifwhere you test to see if it’s more than 80. - As neither of the other conditions are
true, the finalelseprints its report.
Run the app:
Enter various values for score and confirm that each one does what you expect.
You can keep adding else if clauses, but it gets difficult to read and cumbersome to maintain. Once you have more than three possibilities, using switch is a better option.
Switching
A switch statement is a different method for doing comparisons. You switch on a particular value and state what happens at each stage.
Enter this code:
// 1
var grade = "B"
// 2
switch grade {
// 3
case "A":
print("Top of the class!")
case "B":
print("Excellent work.")
case "C":
print("Solid effort.")
case "D":
print("Try harder next time.")
// 4
default:
print("More effort needed.")
}
Stepping through this:
- Make a string variable called
gradeand set it to “B”. - Start a switch statement with the keyword
switchfollowed by the variable to test and an opening curly brace. - Use
casestatements to check for various values ofgrade. As soon as the switch finds a match, it prints an appropriate message and exits. - Every switch must be exhaustive — it must account for all possible input values. Adding
defaultat the end catches everything.
Running this gives you:
Now you know the basics of how to code decisions in Swift. There’s a lot more that switch can do, but this is enough to get you going.
Looping
One thing that computers are great at is repeating the same actions over and over. Swift gives us several different ways to loop repetitively.
Open main.swift and delete everything. You can always refer back to the code, but the output is getting a bit cluttered.
The most common type of loop is a for loop:
// 1
for number in 1 ... 5 {
// 2
print(number)
}
There’s a lot of detail in the first line:
- Start with the
forkeyword followed by the name to use as the index variable — in this case,number. You don’t have to declare the index variable before this. Theinkeyword comes next and then the starting value for the loop index. The three dots form the closed range operator and that tells the loop to repeat, incrementing the loop variable by one each time, until the loop variable equals the number after the operator. - Inside curly braces, add the code that executes each time.
Running this code shows you:
The numbers 1, 2, 3, 4 and 5 printed out and then the loop stopped.
Now edit the for line. Change ... to ..< and run the app again:
You changed from using a closed range operator to using a half-open range operator. This time, the loop ran as long as the index value was less than 5 and then it stopped.
If you’re wondering why this would be any use, remember back in the last chapter where you crashed the REPL by trying to access an extra element of an array? This operator is great for avoiding that crash as you can supply the count of the array as the ending value and the loop stops one before that.
Note: You don’t have to leave a space before and after these range operators and you’ll see a lot of code that doesn’t. The spaces make the code easier to read, but you can choose your own style. If you leave a space before, then you must leave a space after.
The for loop is the one you’ll use most, but while loops are also useful.
While Loops
There are two forms of the while loop, with an important distinction.
Delete everything in main.swift and replace it with this:
// 1
var counter = 0
// 2
while counter < 5 {
// 3
print("Counter: \(counter)")
// 4
counter += 1
// 5
}
More verbose than a for loop, but stepping through:
-
A
whileloop needs something to test to see if it should stop. In this case, you’re using an integer variable. -
Start with the
whilekeyword and then — like for anif— add a condition that evaluates totrueorfalse. -
As long as the condition is
true, the code inside the loop executes repeatedly. -
It’s vital to have something inside the loop that changes the condition. If you forget this, you’ll be stuck in an infinite loop and have to force-quit Xcode.
-
The closing curly brace marks the end of the loop.
Running this code gives you:
Change the starting value for counter to 5 and run the code again. There’s nothing to see because the while loop never ran at all.
Repeat While Loops
That covers the first way to use a while loop, but there’s a second form called a repeat … while loop.
Replace your main.swift code with this:
// 1
var counter = 0
// 2
repeat {
print("Counter: \(counter)")
// 3
counter += 1
// 4
} while counter < 5
At first glance this looks similar to the while loop, but there’s one important difference:
- As before, you create a variable.
- This time, the loop starts with the
repeatkeyword. - The body of the loop executes and increments the counter to avoid an infinite loop.
- Finally, after the closing curly brace, the
whileevaluates the condition.
Running this code gives you the same as the first while gave. But the interesting part happens when you change the starting value of counter to 5:
Despite the fact that the condition never equals true, the loop executed once before reaching the while. That’s the crucial difference between while and repeat ... while. The while loop never executes if the conditional is false at the start. The repeat ... while loop always runs at least once.
If you want a funny way to remember this, check out this cartoon. It’s not coded in Swift, but you’ll get the idea. :]
Looping Through Arrays
So far, you’ve looped using numbers, but in the last chapter, you learned about arrays. Swift provides a convenient way to loop through them too.
Clear main.swift and enter this array of strings:
let toys = ["Andy", "Bo-peep", "Buzz", "Jessie", "Rex"]
On the next line, type for to and admire the way auto-complete has worked out exactly what you want:
Press Return to accept the suggestion, and then replace the body placeholder with print(toy) so you end up with this code:
let toys = ["Andy", "Bo-peep", "Buzz", "Jessie", "Rex"]
for toy in toys {
print(toy)
}
Run the app and Swift loops through the elements in the toys array. Each time through the loop, toy holds the next element:
You’ve covered making decisions and looping in various ways. Now, it’s time to start having some fun with functions. :]
Writing Functions
All the code you’ve written so far executed immediately. Functions are blocks of code that only run when you call them. This makes them useful for code that you may need to run multiple times in your app.
Clear your code from main.swift as usual, and enter this:
func showVersion() {
print("swifty - version 1.0")
}
The important stuff is on the first line:
- Every function starts with the
funckeyword. - The next part is the name of the function. Use the same lowerCamelCase style that you use for variables, and give your functions descriptive names.
- The pair of parentheses mark where you can provide data to the function. This function doesn’t receive any input, but you still need the parentheses.
- The curly braces contain the code that executes whenever you call the function.
Don’t run the app yet. You’ve declared a function, but you haven’t called it, so it won’t run.
Add this line below the function:
showVersion()
This uses the name of the function without the func keyword and it actually makes the function happen. Run the app to see it work:
You now have a function and it runs, but it’ll always do the same thing. Next, you’ll send it some information.
Providing Input
You use the space between the parentheses to tell the function what arguments or parameters to expect.
Replace your code with this version:
// 1
func showVersion(versionNumber: Double) {
print("swifty - version \(versionNumber)")
}
// 2
showVersion(versionNumber: 1.2)
What do these changes do?
- Inside the parentheses, you assign a name and a type to the incoming argument.
- When calling the function, you provide a value or a variable with the same label and the same type.
Run the app now to confirm that it uses your provided versionNumber:
Try changing the value you send to the function and checking that the function uses your different value.
Receiving Output
You’ve used two functions now: the first one took no input, the second one received an argument. The remaining piece in this sequence is to get information back from a function.
Delete your code and add this:
// 1
func getVersion() -> Double {
// 2
return 1.3
}
// 3
let versionNumber = getVersion()
print("Version \(versionNumber)")
There are some interesting things here:
- You declare the function as before with no arguments, but after the brackets and before the curly brace, you added an arrow — a hyphen followed by a greater-than sign — and then a Swift type. This says that the function returns a
Double. - The
returnkeyword precedes the value that the function sends back. Since this function only has one line, you can omit thereturn. - When calling the function, you use the result by assigning it to a constant or variable or by using it immediately. In this case, you store it in a constant and then use it in a
print.
Run the app to see that it works:
Combining Inputs and Outputs
The final step is to write a function that does both: takes input and returns output.
Clear main.swift and add this:
// 1
func areaOfCircle(radius: Double) -> Double {
// 2
let area = Double.pi * radius * radius
// 3
return area
}
// 4
let area = areaOfCircle(radius: 6)
print(area)
Things are a bit more complex here:
- This function declaration sets the name of the function, the name and type of the incoming argument and the type of the return value.
- Inside the function, the code calculates the area of a circle using the supplied radius. There’s a property on
Doubleto give a value forpi. - Since there was more than one line here, the
returnkeyword is essential for outputting the calculated value. - The caller supplies a radius, stores the result and prints it out.
Run the app now to perform your calculation:
Test it by running again with different radius values. Does it give the expected results each time?
Optionals
Now to the last topic in this chapter — optionals. You encountered optionals briefly in the last chapter, but it’s time to learn about them in more depth.
Imagine a box with a big sticker saying Int?. You can’t see inside the box to tell if there is anything there, but you know that if there’s anything inside, it must be an Int. This box is an optional and to see what’s inside, you unwrap it.
An example makes this clearer, so empty main.swift and insert this:
// 1
var mightBeNumber: Int?
// 2
print(mightBeNumber)
// 3
mightBeNumber = 3
print(mightBeNumber)
Ignoring the scary yellow warnings:
- Set up a variable with the type of
Int?which means an optionalInteger. - Print out the value. Xcode complains because it can’t be sure what type of value you’re printing.
- Assign an integer value to the variable and print again.
Run this to see what you get:
The first time, it printed nil, which makes sense as there was no value.
The second time, you got Optional(3), which isn’t super helpful. How can you use that anywhere else? Don’t worry — Swift has this covered with if let.
Unwrapping
Replace your code with this new version:
// 1
var mightBeNumber: Int? = 3
// 2
if let mightBeNumber {
// 3
print(mightBeNumber)
} else {
// 4
print("mightBeNumber is nil")
}
All the Xcode warnings have gone, but why?
- As before, you set up your
Int?but this time, you initialized it immediately. -
if let mightBeNumberunwraps the optional, and if it has an integer value, uses that to set a temporary constant, also calledmightBeNumber. The version ofmightBeNumberinside the curly braces is anInt, and not anInt?. - Now you can use the unwrapped
mightBeNumberlike you would use any non-optionalInt. - If the unwrapping found no value,
if letfalls through to theelse.
Run your app:
This prints 3 as expected. Now remove the = 3 so that the starting value for mightBeNumber is unset. Run the app again and confirm that it prints mightBeNumber is nil.
Note: You’ll see old code that looks like
if let number = number {}. Until Xcode 14, this was the only way to useif letbut as a convention developed of using the same name before and after the equals, the Swift team built a shorter way of writing the same thing.
So when would you ever use an optional? You already saw an example of this when accessing a dictionary. If the key exists in the dictionary, you get an optional value. If it doesn’t, you get nil. And there are lots of other instances – like network calls — where you can’t be sure what you’ll get.
Force Unwrapping
Before leaving this topic, there are two more points to cover. The first is force unwrapping.
Try this bad code:
var forcedString: String? = "This really is a string."
print(forcedString!)
You set up an optional string and then you printed it with a following ! symbol. This stopped Xcode giving any warnings because you force unwrapped the variable. You promised Swift there would be a string value in this variable by the time it needed to access it.
In this case it worked, but if you hadn’t assigned a value to the variable, your app would have crashed. Xcode will sometimes insert force unwrapping code, but don’t do it in your own code.
Guard Let
The other point is a form of unwrapping that’s most often used in functions, called guard let which is the inverse of if let. Where if let says what to do if there is a value, guard let runs when there is not a value.
In a function, it’s good practice to exit as soon as possible if there’s any problem and guard let is good at this.
Here’s an example of not doing this:
func handlingOptionals(name: String?, age: Int?) {
if let name {
if let age {
print("All input data is valid: \(name) & \(age)")
} else {
print("age is not an Int")
}
} else {
print("name is not a string")
}
}
This works, but the correct path is deep in nested if clauses making the sequence difficult to follow.
Now look at this version which uses guard let:
func handlingOptionals(name: String?, age: Int?) {
guard let name else {
print("name is not a string")
return
}
guard let age else {
print("age is not an Int")
return
}
print("All input data is valid: \(name) & \(age)")
}
This uses return to exit the function as soon any unwrapping fails. It produces the same result, but the code looks clean and maintainable.
With both these blocks, if you don’t need to check the arguments individually, you can unwrap them both on the same line:
func handlingOptionals(name: String?, age: Int?) {
guard let name, let age else {
print("One of the arguments is not valid")
return
}
print("All input data is valid: \(name) & \(age)")
}
You can only use guard inside a function or loop that you can break out of, because the last command inside a guard block must be an exit of some sort.
Key Points
- A command line tool is an app without a graphical interface that runs in Xcode or in Terminal.
- Swift can make decisions using
iforswitch. If you have more than three possibilities, use aswitch. - Loops use
fororwhileto step through data. - Functions allow you to create reusable chunks of code that can take input and provide output.
- Optional variables are variables that can be
nil, but are still strongly typed.
Where to Go From Here
In the downloaded materials for this chapter, in projects ▸ final there’s an Xcode project containing the code used in this chapter.
You’ve now learned yet another Mac-only way to run Swift code. In the next chapter, you’ll use a method that works on both Macs and iPads: Swift playgrounds.
There you’ll find out how to create your own data objects by combining what you already know about data types and functions.
For the official Swift information and guides, go to Swift.org.
To read about Swift in more depth, check out our Swift Apprentice book.