3.2 Account Class
We present and discuss the Account class’s code in Figs. 3.1–3.5. In Section 3.3, we present a simple program that creates and uses Account objects to demonstrate Account’s capabilities. The project for this example has two Swift files—Account.swift contains Account’s definition and main.swift (Section 3.3) contains the app that uses class Account. We could have defined class Account in main.swift as well, but classes are generally defined in their own files to make the code easier to reuse and maintain. To add a new Swift file to your project, select File > New > File..., then select Swift File from the Source category.
To run the final project, click the Run button () on the Xcode toolbar or press + R. The results will be displayed in the Debug area at the bottom of the Xcode window (as you saw in Fig. 1.9).
3.2.1 Defining a Class
Line 4 (Fig. 3.1) begins a class definition for class Account. The class keyword introduces a class definition and is immediately followed by the class name (Account).
Fig. 3.1 | Account class definition.
1
// fig03-01-11: Account.swift
2
// Account class with name and balance properties,
3
// an initializer and deposit and withdraw methods
4
{
public class
Account
Class Names Are Identifiers
Class names are identifiers that use the camel-case naming scheme we discussed in Chapter 2, but, by convention, class names begin with an initial uppercase letter.
Class Body
A left brace (at the end of line 4), {, begins the body of every class definition. A corresponding right brace (at line 39 in Fig. 3.5), }, ends each class definition. By convention, the contents of a class’s body are indented.
Access Modifiers public, internal and private
Class Account’s definition begins with the keyword public (line 4), which is one of Swift’s three access modifiers (public, internal and private). A class that’s declared public can be reused in other apps—for example, the features in the Swift Standard Library are declared public so that you can use them in your apps. A class that’s declared internal can be used only by other code in the same project—internal is the default access specifier if you do not provide one. A class that’s declared private can be used only in the file in which its defined. Though we do not reuse class Account outside this chapter’s example, we chose to declare it public so that it can potentially be reused.
3.2.2 Defining a Class Attribute as a Stored Property
Different accounts typically have different names and balances. For this reason, class Account contains a name property (Fig. 3.2) and a balance property (Fig. 3.3). Figure 3.2 defines the stored property name—such properties enable you to store and retrieve values in an object of a class. Each object has its own copy of the class’s stored properties. Swift stored properties are similar to C# properties and to instance variables with corresponding set and get methods in Java and C++.
Fig. 3.2 | Account class stored property name.
5
public var
name: String =""
// properties must be initialized
6
Accessing a Stored Property
A class’s properties are defined like other constants and variables, but inside the class’s body. The property in Fig. 3.2 is a variable (var) stored property of type String that’s initialized with an empty String. A variable property is read/write—it allows you to get the property’s value from an object of the class and store a value in an object of the class.
You use an object’s identifier and a dot (.)—known as dot syntax—to access a property. Consider an Account object named account1. In the statement:
account1.name = "Jane Green"
// uses the setter to set the name
the expression account1.name uses the property’s setter to store the String "Jane Green" in the account1 object. In the statement:
println(account1.name) // uses the getter to get the name
the expression account1.name uses the property’s getter to retrieve the String "Jane Green" from the account1 object so that it can be displayed with println.
You may also define constant properties in a class with let. A constant property is read only—it provides only a getter for retrieving the value and is used for a value that does not change after it’s initialized.
A Class’s public Properties May Be Accessed Throughout the Class and by Clients of the Class
The name property is defined as public (line 5). A class’s public properties (and other public members) are publicly accessible. They can be accessed throughout the class’s definition and by any code that uses objects of the class—the class’s so-called client code (like main.swift, which you’ll see in Section 3.3). A class’s internal members can be accessed via an object of the class anywhere in the app in which the class is defined. A class’s private members can be used only in the file that defines the class.
Computed Properties
Swift also provides computed properties that do not store data—rather, they manipulate other properties. For example, a Circle class could have a stored property radius and computed properties diameter, circumference and area that would calculate the diameter, circumference and area, respectively, using the stored property radius in the calculations. We’ll define a computed property in Section 6.9.1 and discuss them in more detail in Chapter 8.
3.2.3 Defining a public Stored Property with a private Setter
As you saw in Section 3.2.2, a variable property has a getter and a setter. If a variable property is public, the client code can use the getter to get the property’s value and the setter to modify its value. Though the client code should be able to check the balance, the balance should be modifiable only within class Account, so we can ensure the client code does not modify the balance incorrectly. For this scenario, you can declare that the property’s setter is private. Line 8 (Fig. 3.3) defines the public variable stored property named balance of type Double and initializes it with the value 0.0. The notation private(set) between the public and var keywords indicates that balance’s setter is private and thus can be used only by code in the same file as class Account.
Fig. 3.3 | Account class stored properties.
7
// balance is public, but its setter can be used only in class Account
8
public private(set) var
balance: Double =0.0
9
3.2.4 Initializing a Class’s Properties with init
Swift does not provide default values for a class’s properties—you must initialize them before they’re used. Lines 5 and 8 (Figs. 3.2–3.3) specify default values for both of Account’s properties. But what if you want to provide different values for the name and balance when you create an Account object? Each class you define can optionally provide an initializer with parameters that can be used to initialize a new object of a class. In fact, Swift requires an initializer call for every object that’s created, so this is the ideal point to initialize an object’s properties. For a class that does not explicitly define any initializers, the compiler defines a default initializer (with no parameters) that initializes the class’s properties to the default values specified in their definitions. Initializers are like constructors in most other object-oriented programming languages.
Initializer Definition
Lines 11–19 (Fig. 3.4) define class Account’s public initializer—only the public class members are accessible when the class is reused outside the project in which it’s defined. Each initializer’s name is the keyword init, which is followed by a parameter list enclosed in required parentheses, then the initializer’s body enclosed in braces ({ and }). The parameter list optionally contains a comma-separated list of parameters with type annotations. The argument values passed to the initializer’s parameters initialize the properties for a particular object of the class. As you’ll see in Chapter 8, classes can have multiple initializers—this is called overloading and enables objects of a class to be initialized in different ways. The initializer for class Account provides a name parameter of type String and a balance parameter of type Double, representing the account holder’s name and starting balance, respectively.
Fig. 3.4 | Account class initializer.
10
// initializer
11
{
public init
(name: String, balance: Double)12
name = name
self.
13
14
// validate that balance is greater than 0.0; if not,
15
// property balance keeps its initial value of 0.0
16
if
balance >0.0
{17
balance = balance
self.
18
}19
}20
Each parameter must be declared with a type annotation specifying the type of the expected argument. When you create a new Account object (as you’ll see in Section 3.3), you’ll pass as arguments the account holder’s name (a String) and starting balance (a Double)—the initializer will receive those values in the parameters name and balance, respectively. The initializer assigns the parameter name to the property name (line 12) and validates the parameter balance, assigning it to the property balance (line 17) only if the corresponding argument is greater than 0.0—otherwise, property balance retains its default value of 0.0 that was specified in its definition (line 8 of Fig. 3.3).
Parameters Are Local to Their Defining Initializer, Method or Function
Parameters are local to the initializer, method or function in which they’re defined, as are any variables and constants defined in the body of an initializer, method or function. If a local variable or constant has the same name as a property, using the variable or constant in the body refers to the local variable or constant rather than the property—the local identifier shadows the property. You use the keyword self (like this in other popular object-oriented languages) to refer to the shadowed property explicitly, as shown on the left side of the assignments in lines 12 and 17 (Fig. 3.4).
There’s No Default Initializer in a Class That Defines an Initializer
If you define an initializer for a class, the compiler will not create a default initializer for that class. In that case, you will not be able to create an Account object with the expression Account()—unless the custom initializer you define takes no parameters.
3.2.5 Defining a Class’s Behaviors as Methods
Class Account defines two public methods (Fig. 3.5) for manipulating the balance:
- Method deposit (lines 22–27) ensures that the deposit amount is positive and, if so, adds the amount to the balance.
- Method withdraw (lines 30–38) ensures that the withdrawal amount is positive and that subtracting that amount from the balance will not overdraw the account, and, if so, subtracts the amount from the balance.
Fig. 3.5 | Account class methods deposit and withdraw.
21
// deposit (add) a valid amount into the Account,
22
{
public func
deposit(amount: Double)23
// if amount is valid, add it to the balance
24
if
amount >0.0
{25
balance = balance + amount26
}27
}28
29
// withdraw (subtract) a valid amount from the Account
30
{
public func
withdraw(amount: Double)31
// if amount is valid, and the balance will not
32
// become negative, subtract it from the balance
33
if
amount >0.0
{34
if
balance - amount >=0.0
{35
balance = balance - amount36
}37
}38
}39
}
Defining a Method
A method definition begins with the keyword func (lines 22 and 30) followed by the method’s name and parameter list enclosed in required parentheses, then the method’s body enclosed in braces ({ and }). Like an initializer, the parameter list optionally contains a comma-separated list of parameters with type annotations. Methods deposit and withdraw each receive one parameter of type Double representing the amount to deposit or withdraw, respectively.
Return Type of a Method
A method may also specify a return type by following the parameter list with -> and the type of the value the method returns. A method that does not specify a return type does not return a value, as is the case for methods deposit and withdraw in class Account. Methods with return values use return statements to pass results back to their callers. As you’ll see, Swift methods and functions can return more than one value at a time via a tuple, which you’ll learn about in Chapter 5.
Initializers Cannot Return Values
An important difference between initializers and methods is that initializers cannot return values, so they cannot specify a return type.
A Method Defined Outside a Type Definition Is a Function
If a method is defined outside a class (or struct or enum), then it’s a function (sometimes called a free function or global function)—println and print are two of the many functions defined in the Swift Standard Library. We define a function in Section 3.3.3.