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

Bringing it all together

At this point, we 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 have learned.

The goal of the program is to take a list of invitees and a list of television shows and ask random people to bring a show from each genre. It should also ask the rest to just bring themselves.

Before we look at the code, I will mention the three small new features that I will use:

  • Generating a random number
  • Using a variable to store only true or false
  • Repeat-while loops

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

Foundation includes a function called rand that returns a random number. Computers are actually not capable of generating truly random numbers and, by default, rand always returns the same values in the same order. To make it return different values each time the program is run, we use a function called srand that stands for seed random. Seeding random means that we provide a value for rand on which to base its first value. A common way of seeding the random number is using the current time. 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 between 0 and the number of invitees. To do this, we use the remainder operator (%). This operator gives you the remainder after piding the first number by the second number. For example, 14 % 4 returns 2 because 4 goes into 14, 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 by. This is perfect for changing all of the possible random values.

The full code for generating 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 about this code. We are using new syntax 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 like with variables, you can hold the option key and click on a function to see what types it takes and returns.

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

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

Note that we can use the Boolean directly in a conditional. This is because a Boolean is the exact type a conditional is expecting. All of our other tests like <= actually result in a Bool.

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

var inviteeIndex: Int
repeat {
    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 3.

Everything else in the code builds off the concepts we already know. I 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 in that way. I included comments to help explain both what the code is doing and why it is written in 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 in which to put all of our data. We can easily come back to the program and change the data if we want and we don't have to go searching through the rest of the program to update it:

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

// 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
    print("\(name), bring a \(show.genre) show")
    print("\(show.name) is a great \(show.genre)")

    invitees[index].alreadyInvited = true
 }

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

    invitees[index].alreadyInvited = true
 }

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

// -----------------------------
// 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
    repeat {
        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 contains the real logic of the program, which is commonly referred to as the business logic. The functions from the previous section 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 we 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 it help others understand your code, it will also help you understand it when you come back to it in six months and you are 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 a lot less bugs.

Let's also look at 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 repeat-while loop will continue forever, never finding an invitee that was not invited. Your program doesn't have to handle every possible input but you should at least be aware of its limitations.