Learning Swift
上QQ阅读APP看书,第一时间看更新

Bringing it all together

At this point, you have learned a lot about the basic workings of Swift. Let's take a moment to bring many of these concepts together in a single program. We will also see some new variations on what we learned.

The goal of the program will be to take a list of invitees and television shows and to ask random people to bring a show of each genre and to ask the rest to just bring themselves.

Before we look at the code, there are three small new features that we will use:

  • We will generate a random number
  • We will use a variable to store only true or false
  • We will use a new type of loop called a do-while loop

The most important feature is the ability to generate a random number. To do this, we have to import Foundation Framework. This is the most basic framework made available by Apple. As the name suggests, it forms the foundation for both the frameworks for OS X and iOS.

Foundation includes a function called rand that returns a random number. Computers are not actually capable of generating truly random numbers, and by default, rand will always return the same values in the same order. To make it return different values each time the program is run, we will use a function called srand that stands for "seed random". Seeding random means that we provide a value to help rand generate a unique sequence of numbers each time. Using the same seed each time would cause rand to generate the same sequence every time so a common way to seed it is to use the current time. For that, we will use a method called clock that is also from Foundation.

Lastly, rand returns a number anywhere from 0 to a very large number, but, as you will see, we want to restrict the random number to be between 0 and the number of invitees. To do this, we will use the remainder operator (%) otherwise referred to as the modulus operator. This operator gives you the remainder after piding the first number by the second number. For example, 14 % 4 will return 2 because 4 goes into 14 a total of 3 times with 2 left over. The great feature of this operator is that it forces a number of any size to always be between 0 and 1 less than the number you are piding it by. This is perfect for changing the possible random values.

The full code to generate a random number looks like this:

// Import Foundation so that "rand" can be used
import Foundation

// Seed the random number generator
srand(UInt32(clock()))

// Random number between 0 and 9
var randomNumber = Int(rand()) % 10

You may notice one other thing in particular about this code. We are using the new syntaxes UInt32() and Int(). This is a way of changing one type into another. For example, the clock function returns a value of the type clock_t, but srand takes a parameter of the type UInt32. Remember, just as with variables, you can hold the option key and click on a function to see which types it takes and returns.

The second feature we will use is the ability to have a variable that can store only true or false. This is called bool, which is short for boolean. We have actually used this type many times before, as it is used in all conditionals and loops, but this is the first time we will be storing bool directly in a variable. At its most basic level, a boolean variable is defined and used as:

var someBool = false
if someBool {
    println("Do This")
}

Note that we can use the boolean variable directly in a conditional. This is because a boolean variable is the exact type a conditional expects. All of our other tests such as <= actually result in a bool.

Lastly, the third feature we will use is a variation of the while loop, called a do-while loop. The only difference with the do-while loop is that the condition is checked at the end of the loop instead of the beginning. This is significant because unlike with a while loop, a do-while loop will always be executed at least once, shown as follows:

var inviteeIndex: Int
do {
    inviteeIndex = Int(rand()) % 5
} while(inviteeIndex != 3)

With this loop, we will continue to generate a random number between 0 and 4 until we get a number that does not equal to 3.

Everything else in the code will build up on the concepts we already know. I highly recommend that you read through the code and try to understand it. Try to not only understand it from the perspective of how it works, but why I wrote it that way. I included comments to help explain both what the code is doing and why it is written that way:

// Import Foundation so that "rand" can be used
import Foundation

// Seed the random number generator
srand(UInt32(clock()))

// -----------------------------
// Input Data
// -----------------------------

// invitees
//
// Each element is a tuple which contains a name
// that is a String and a Bool for if they have been
// invited yet. It is a variable because we will be
// tracking if each invitee has been invited yet. 
var invitees = [
    (name: "Sarah", alreadyInvited: false),
    (name: "Jamison", alreadyInvited: false),
    (name: "Marcos", alreadyInvited: false),
    (name: "Roana", alreadyInvited: false),
    (name: "Neena", alreadyInvited: false),
]

// showsByGenre
//
// Constant because we will not need to modify
// the show list at all
let showsByGenre = [
    "Comedy": "Modern Family",
    "Drama": "Breaking Bad",
    "Variety": "The Colbert Report",
]

This first section of code gives us a localized place to put all of our data. We can easily come back to the program and change the data if we want to, and we don't have to go searching through the rest of the program to update it:

// -----------------------------
// Helper functions
// -----------------------------

// markInviteeAsInvitedAtIndex:
//
// The process of marking an invitee as invited is
// slightly complex. The other code will be more
// understandable if we use a function with a
// representative name. We can also then reuse
// this function in multiple places
func markInviteeAsInvitedAtIndex(index: Int)
{
    // We are replacing the old invitee value with
    // a new one using the same name but with
    // alreadyInvited set to true
    let invitee = invitees[index]
    invitees[index] = (
        name: invitee.name,
        alreadyInvited: true
    )
}

// inviteAtIndex:toBringShow:
//
// Another function to help make future code
// more comprehensible and maintainable
func inviteAtIndex
    (
    index: Int,
    toBringShow show: (genre: String, name: String)
    )
{
    let name = invitees[index].name
    println("\(name), bring a \(show.genre) show")
    println("\(show.name) is a great \(show.genre)")

    markInviteeAsInvitedAtIndex(index)
}

// inviteToBringThemselvesAtIndex:
//
// Similar to the previous function but this time for
// the remaining invitees
func inviteToBringThemselvesAtIndex(index: Int) {
    let invitee = invitees[index]
    println("\(invitee.name), just bring yourself")

    markInviteeAsInvitedAtIndex(index)
}

Here, I have provided a number of functions that will simplify more complex code later in the program. Each is given an understandable name, so that when they are used, we do not have to go look at their code to understand what they do:

// -----------------------------
// Now the core logic
// -----------------------------

// First, we want to make sure each genre is assigned 
// to an invitee
for show in showsByGenre {
    // We need to pick a random invitee that has not
    // already been invited. With the following loop
    // we will continue to pick an invitee until we
    // find one that has not already been invited
    var inviteeIndex: Int
    do {
        inviteeIndex = Int(rand()) % invitees.count
    } while(invitees[inviteeIndex].alreadyInvited)

    // Now that we have found an invitee that has not
    // been invited, we will invite them
    inviteAtIndex(inviteeIndex, toBringShow: (show))
}

// Now that we have assigned each genre, we
// will ask the remaining people to just bring
// themselves
for index in 0 ..< invitees.count {
    let invitee = invitees[index]
    if !invitee.alreadyInvited {
        inviteToBringThemselvesAtIndex(index)
    }
}

This last section is where the real logic of the program is, which is commonly referred to as the business logic. The functions from the previous sections are just details and the final section is the logic that really defines what the program does.

This is far from the only way to organize a program. This will become even clearer as you learn more advanced organization techniques. However, this breakdown shows you the general philosophy behind how you should organize your code. You should strive to write every piece of code as if it were going to be published in a book. Many of the comments in this example will become excessive as you get better with Swift, but when in doubt, explain what you are doing using either a comment or a well-named function. Not only will this help others understand your code, but it will also help you understand it when you come back to it in 6 months and you are basically a stranger to the code again. Not only that, if you force yourself to formalize your thoughts as you write the code, you will find yourself creating many fewer bugs.

Let's also discuss an interesting limitation of this implementation. This program is going to run into a major problem if the number of invitees is less than the number of shows. The do-while loop will continue forever, not ever finding an invitee that has not been invited yet. Your program doesn't have to handle every possible input, but you should at least be aware of its limitations.