Projects
If we want to move away from developing with a single file, we need to move away from playgrounds and create our first project. In order to not complicate the project too much, we will create a command-line tool project. This is a program that does not have a graphical interface. As an exercise, we will redevelop our example program from Chapter 2, Building Blocks – Variables, Collections, and Flow Control, which manages invites to a party. We will develop an app with a graphical interface in Chapter 10, A Whole New World – Developing an App.
Setting up a command-line Xcode project
To create a new command-line tool project, open Xcode and from the menu bar along the top, navigate to File | New | Project. A window will appear that allows you to select a template for the project. Choose Command Line Tool from the OS X | Application menu, as shown in the following figure:
From there, click on Next and then give the project a name such as Learning Swift Command Line
. Any organization name and identifier is fine. Finally, make sure that Swift is selected from the Language drop-down menu and click on Next again. Now, save the project somewhere you can find it later and click on Create.
Xcode will then present you with the project development window. Select the main.swift
file to the left of the window and you should see the Hello, World!
code that Xcode generates for you, as shown in the following figure:
This should feel pretty similar to a playground, except that we can no longer see the output of the code to the right. In a regular project like this, the code is not run automatically for you. The code will still be analyzed for errors as you write it, but you must run it yourself whenever you want to test it. To run the code, you can click on the Run button in the toolbar, which looks like a play button.
The program will then build and run. Once it does, Xcode will show the console at the bottom of the window, where you will see the text Hello, World!
, which is the result of running this program. This is the same console that we see in playgrounds.
Unlike a playground, we have the Project Navigator pane to the left. This is where we organize all the source files that go into making the application work.
Creating and using an external file
Now that we have successfully created our command-line project, let's create our first new file. It is pretty common to create a separate file for each type that you create. Let's start by creating a file for an Invitee
class. We want to add the file to the same file group as the main.swift
file, so click on that group. You can then click on the plus sign (+) icon at the lower left-hand corner of the window and select New File. From that window, navigate to OS X | Source | Swift File and click on Next:
The default location for the file to be saved should be in the same place as main.swift
, which is great. Name your new file Invitee.swift
and click on Create. Let's add a simple Invitee
structure to this file. We want an invitee to have a name and to have the ability to ask them to the party with or without a show:
// Invitee.swift struct Invitee { let name: String func askToBringShowFromGenre(genre: ShowGenre) { println("\(self.name), bring a \(genre.name) show") println("\(genre.example) is a great \(genre.name)") } func askToBringThemselves() { println("\(self.name), just bring yourself") } }
This is a very simple type and does not require any inheritance, so there is no reason to use a class. Note that inheritance is not the only reason to use a class, as we will see in later chapters, but for now, a structure will work great for us. This code provides simple, well-named methods to print out the two types of invites.
We are already making use of a structure that we have not yet created called ShowGenre
. We expect it to have a name
and example
property. Let's implement this structure now. Create another file called ShowGenre.swift
and add the following code to it:
// ShowGenre.swift struct ShowGenre { let name: String let example: String }
This is an even simpler structure. This is just a small improvement over the use of a tuple because it is given a name instead of just properties. It may seem like a waste to have an entire file just for this, but this is great for maintainability in future. It is easier to find the structure because it is in a well-named file, which can be used if we want to add more code to it later.
An important principle in code design is called the separation of concerns. The idea is that every file and every type should have a clear and well-defined concern. You should avoid having two files or types responsible for the same thing and it should be obvious why each file and type exists.
Interfacing with code from other files
Now that we have our basic data structures, we can use a smarter container for our list of invitees. This list can contain the logic for how we assigned a random invitee a genre. Let's start by defining the structure with some properties:
// InviteList.swift struct InviteList { var invited: [Invitee] = [] var pendingInvitees: [Invitee] init(invitees: [Invitee]) { srand(UInt32(clock())) self.pendingInvitees = invitees } }
Instead of storing a single list of both the invited and pending invitees, we can store them in two separate arrays. This makes the selection of a pending invitee much easier. This code also provides a custom initializer so that from other classes, all we need to provide is an invitee list without worrying about the fact that it is a list of pending invitees. We could have just used the default initializer, but the parameter would have been named pendingInvitees
. Also, we seed the random number generator for later use.
Note that we did not need to provide a value for the invited in our initializer because we gave it a default value of an empty array.
Note also that we freely use our Invitee
structure in this code. Swift automatically finds and allows you to use any code from other files in the same project. Interfacing with code from other files is as simple as that.
Now, let's add a helper function to move an invitee from the pendingInvitee
list to the invited list:
// InviteList.swift struct InviteList { // ... // Move invitee from pendingInvitees to invited // // Must be mutating because we are changing the contents of // our array properties mutating func invitedPendingInviteeAtIndex(index: Int) { // Removing an item from an array returns that item let invitee = self.pendingInvitees.removeAtIndex(index) self.invited.append(invitee) } }
This will make our other methods cleaner and easier to understand. The first thing we want to allow is sending invites to a random invitee and asking them to bring a show from a specific genre:
// InviteList.swift struct InviteList { // ... // Must be mutating because it calls another mutating method mutating func askRandomInviteeToBringGenre(genre: ShowGenre) { if self.pendingInvitees.count > 0 { let randomIndex = Int(rand()) % self.pendingInvitees.count let invitee = self.pendingInvitees[randomIndex] invitee.askToBringShowFromGenre(genre) self.invitedPendingInviteeAtIndex(randomIndex) } } }
The selection of a random invitee is much cleaner than our previous implementation. We can create a random number between 0
and the number of pending invitees, instead of trying to select a random invitee until we find the one that hasn't been invited yet. However, before we pick that random number, we have to make sure that the number of pending invitees is greater than zero. If there are no remaining invitees, we would be piding the random number by 0
in Int(rand()) % self.pendingInvitees.count
. This would cause a crash. It also has the extra benefit that we can now handle the scenarios where there are more genres than invitees.
Lastly, we want to be able to invite everyone else, so that they just bring themselves:
// InviteList.swift struct InviteList { // ... // Must be mutating because it calls another mutating method mutating func inviteeRemainingInvitees() { while self.pendingInvitees.count > 0 { let invitee = self.pendingInvitees[0] invitee.askToBringThemselves() self.invitedPendingInviteeAtIndex(0) } } }
Here, we repeatedly invited and removed the first pending invitee from the pendingInvitees
array until there were none left.
We now have all of our custom types and we can return to the main.swift
file to finish the logic of the program. To switch back, you can just click on the file again in the Project Navigator (the list of files to the left). Here, all we want to do is create our invitee list and a list of genres with example shows. Then, we can loop through our genres and ask our invitee list to do the inviting:
var inviteeList = InviteList(invitees: [ Invitee(name: "Sarah"), Invitee(name: "Jamison"), Invitee(name: "Marcos"), Invitee(name: "Roana"), Invitee(name: "Neena"), ]) let genres = [ ShowGenre(name: "Comedy", example: "Modern Family"), ShowGenre(name: "Drama", example: "Breaking Bad"), ShowGenre(name: "Variety", example: "The Colbert Report"), ] for genre in genres { inviteeList.askRandomInviteeToBringGenre(genre) } inviteeList.inviteeRemainingInvitees()
That is our complete program. You can now run the program by clicking on the Run button again and see the output. You just completed your first real Swift project!
File organization and navigation
As your project gets larger, it can be cumbersome to have just a single list of files. It can help you organize your files into folders to help differentiate what role they play in your app. In the Project Navigator, folders are called groups. You can create a new group by selecting the group you would like to add the new group to by navigating to File | New | Group. It isn't terribly important as to exactly how you group your files; the important thing is that you should be able to come up with a relatively simple system that makes sense. If you have trouble doing that, you should consider how you could improve the way you break up your code. If you have trouble categorizing your files, then your code is most likely not being broken up in a maintainable way.
I definitely recommend that you use lots of files and groups to better separate your code. However, the drawback of this is that the Project Navigator can fill up pretty quickly and become hard to navigate. A great trick in Xcode to navigate to files more quickly is to use the keyboard shortcut command + Shift + O. This will display the Open Quickly search. Here, you can start typing the name of the file you want to open and Xcode will show you all the matching files. Use the arrow keys to navigate up and down and press Enter to open the file you want.