Creating Classes and Objects
The heart of programming in Swift using Xcode is object-oriented programming. The main idea is to divide a large program into separate objects where each object ideally represents a physical entity. For example, if you were creating a program to control a car, one object might represent the car’s engine, a second object might represent the car’s entertainment system, and a third object might represent the car’s heating and cooling system.
Now if you want to update the part of a program that controls the car’s engine, you just have to modify or replace the object that controls that car engine. Objects not only help you better understand how different parts of a program work together, but also isolate code so you can create building blocks to put together larger, more sophisticated programs.
Objects help you visualize your program as separate building blocks that are independent from each other. Before object-oriented programming, programmers divided programs into subprograms, which also acted like miniature building blocks. The main difference between subprograms and objects is that subprograms could access data used by other subprograms. That meant that if you modified one subprogram, you often inadvertently affected other parts of the program. That made fixing errors or bugs difficult and also made modifying programs difficult as well.
With objects, the main idea is to create separate, independent building blocks that work together without affecting each other’s data. To keep data isolated, objects use encapsulation. That means an object has its own variables (called properties) and functions (called methods).
Objects can communicate with each other in two ways:
Ideally, a program should consist of multiple objects that perform tasks completely independent from each other. That makes it easy to modify a program by replacing one object with an improved version of that object.
There are two steps to creating an object. First, you have to create a class. Second, you create an object based on that class.
A class looks and works much like a structure (see Chapter 10) where you define a name. Once you’ve defined a class, you can create one or more objects based on that class. Think of a class as a cookie cutter that defines the shape of cookies, and objects as different type of dough you can define based on the class.
The simplest type of class you can define looks like this:
class className {
}
To create an object from this class, you just declare a variable, but instead of defining a data type such as Int or String, you define the class name such as:
var myObject = className()
Of course, a class that contains no code is useless, so the two types of code you can include in a class are variables (known as properties) and functions for manipulate data (known as methods). Creating properties in a class involves creating one or more variables and defining a data type and an initial value such as:
class className {
var name : String = ""
var ID : Int = 0
var salary : Double = 0
}
Initial values are important because when you create an object based on a class, you don’t want uninitialized values that could cause problems if you try to use them.
To define a property, you declare a variable name, a data type, and an initial value. Since Swift can infer data types, you could omit the data type declaration and just assign initial values like this:
class className {
var name = ""
var ID = 0
var salary = 0.0
}
Whether you define class properties with a data type or not, you can create an object from this class by just creating a variable name, setting it equal to the class name, and following it with an empty set of parentheses like this:
var myObject = className()
Both of the previous methods create the same initial values for the class properties. For more flexibility, you can create an initializer method called init. This lets you define initial values each time you create an object from a class. An initializer in a class might look like this:
class secondClass {
var name : String
var ID : Int
var salary : Double
init (name: String, ID: Int, salary: Double) {
self.name = name
self.ID = ID
self.salary = salary
}
}
This initializer method (init) defines a parameter list that accepts three items: a string, an integer, and a double value. Then it stores this information in the name, ID, and salary properties respectively. When a class has an initializer, you must create an object from that class by including the proper number and data type to match the initializer parameter list.
That means creating an object that includes a string, an integer, and a double value in parentheses like this:
var secondObject = secondClass (name: "Joe", ID: 102, salary: 120000)
Notice that when you include data in the parameter list, you must label each data chunk with the variable name defined in the initializer parameter list. In this case, the three parameter names are name, ID, and salary, so creating an object means identifying the data with those same parameter names.
Accessing Properties in an Object
To access an object’s properties, you must specify the object name followed by a period and the property name such as:
objectName.propertyName = data
You can also assign an object’s property values to a variable like this:
var person = myObject.name
To access properties, you need to specify the object name, a period, and the property name that you want to access such as:
print (person.name)
Note When accessing values stored in properties, make sure you specify the object name, not the class name. If you created a “person” object from the “className” class, then you would access properties by specifying the object name (person) and the property name (name, ID, salary) such as person.name or person.ID. You would not access the property value by using the class name such as className.name or className.ID. If you try this, your code will not work.
Just like ordinary variables, an object’s properties can only hold one chunk of data at a time. The moment you store new data in a property, it wipes out the previous data.
To see how to create a class and work with an object’s properties, create a new playground by following these steps:
import Cocoa
class className {
var name = ""
var ID = 0
var salary = 0
}
var worker = className()
worker.name = "Bob"
worker.ID = 102
worker.salary = 10
var executive = className()
executive.name = "Robert"
executive.ID = 1
executive.salary = worker.salary * 100
class secondClass {
var name : String
var ID : Int
var salary : Double
init (name: String, ID: Int, salary: Double) {
self.name = name
self.ID = ID
self.salary = salary
}
}
var consultant = secondClass (name: "Joe", ID: 17, salary: 50000)
The first class initializes its properties when they’re declared. Then it creates an object (worker) based on the class (className). To store data in the worker object, the code specifies the object name (worker) and property name and assigns it a value.
The second class example uses an initializer to define the property values. The init method accepts three chunks of data, a string, an integer, and a decimal (Double) value in that order.
To create an object (consultant) from this second class (secondClass), the parentheses need to include the right number of data in the right order along with the proper data types in addition to displaying a label to identify what the data means as shown in Figure 11-1.
Figure 11-1. Creating a class file and accessing properties
Computed Properties in an Object
As an alternative to defining fixed values for properties, you can also use computed properties where one property’s initial value depends on the value of another property. The simplest way to create a computed property is to define a property name, a data type, and then in curly brackets write code that calculates a value. Finally, use the “return” keyword to define the value to store in the property like this:
class shape {
var height : Int = 5
var width : Int {
return height * 2
}
}
var rectangle = shape()
print (rectangle.height)
print (rectangle.width)
This computed property simply takes the value stored in the height property, multiples it by 2, and stores that result in the width property. When you create an object based on the shape class, the initial value of 5 gets stored in the height property and the computed property multiples the height value (5) by 2 to get 10, which it stores in the width property as shown in Figure 11-2.
Figure 11-2. Using computed properties to determine a property’s initial value
In this example of a computed property, the value of one property (width) gets the value of another property (height) to determine its own value. In technical terms, this is known as a getter.
Setting Other Properties
Another option is to use what’s called a setter. With a setter, defining one property sets the value of another property. Since getters and setters are so similar, they’re often used within the same property. That way one property uses a getter to calculate its value from another property, then if you set that property with a value, it can change another property. Defining getters and setters looks like this:
class blob {
var property1 : dataType = value
var property2 : dataType {
get {
return valueHere
}
set {
property1 = valueHere
}
}
}
In the getter, you always need a “return” keyword to return a value to the property that has the getter. In the setter, you must assign a property to a value. This value can be a fixed value or a calculation that returns a value.
Note You don’t need both a getter and a setter. You can just have a getter, which you can shorten by eliminating the “get” keyword and just enclose code in curly brackets as in the class shape example in the beginning of this section. You can also have just a setter, but you’ll need to use the “set” keyword.
In the above example, every time property2 gets assigned a new value, it uses its setter to define a new value for property1.
A setter can also accept a value and use that value to calculate a new result for a different property. To accept a value for a setter, you just need to create a variable enclosed in parentheses and use that value to calculate a result for another property. If you don’t create a variable enclosed in parentheses, you can just use the default parameter name of “newValue.” The following example shows how to use a setter:
class blob {
var property1 : dataType = value
var property2 : dataType {
get {
return valueHere
}
set (newValue) {
property1 = valueHere based on newValue
}
}
}
To see how getters and setters work, follow these steps:
import Cocoa
class shape {
var height : Int = 5
var width : Int {
return height * 2
}
}
var rectangle = shape()
print (rectangle.height)
print (rectangle.width)
rectangle.height = 20
print (rectangle.width)
class blob {
var height : Int = 5
var width : Int = 10
var area : Int {
get {
return height * width
}
set {
height = 24
width = 45
}
}
}
var cat = blob()
cat.area
cat.area = 129
class anotherBlob {
var height : Int = 5
var width : Int = 10
var area : Int {
get {
return height * width
}
set (newValue) {
height = newValue + 10
width = newValue - 5
}
}
}
var CEO = anotherBlob()
print (CEO.area)
CEO.area = 247
print (CEO.height)
print (CEO.width)
You can think of a getter and setter as a function that changes other properties. Be careful when using getters and setters, though, since they could cause unexpected behavior if you aren’t aware of their existence or how they work. Figure 11-3 shows the result of the above code so you can see how the getters and setters affect properties.
Figure 11-3. Using getters and setters to modify properties
Swift provides two property observers called willSet and didSet. The willSet property observer runs code before a property receives a value. The didSet property observer runs code after a property receives a value.
Like getters and setters, property observers essentially are functions that run when a property gets a new value. The basic structure for a willSet and didSet property observer is identical to the structure for a getter and setter like this:
var property : dataType = initialValue {
willSet {
}
didSet {
}
}
The willSet property observer runs code before the property gets a new value. The didSet property observer runs code after the property gets a new value. To see how property observers work, follow these steps:
import Cocoa
class animal {
var IQ : Int = 0
var legs : Int = 0 {
willSet {
IQ += 10
}
didSet {
IQ -= 5
}
}
}
var pet = animal()
print (pet.IQ)
pet.legs = 4
print (pet.IQ)
In this example, the IQ property has an initial value of 0. Notice that when you set the legs property to 4, it immediately runs the willSet property observer code, which increases the IQ property by 10. Then it immediately runs the code in the didSet property observer, which subtracts 3 as shown in Figure 11-4.
Figure 11-4. Using property observers
A class that simply stores one or more properties can be convenient for grouping related data together. However, what makes object-oriented programming more useful is when objects can also manipulate their own data. To create mini-programs for objects to run, you need to define functions (called methods) inside a class.
You’ve already seen methods when you’ve linked push buttons from the user interface to create an IBAction method in the Swift code file. The simplest method inside a class performs the exact same function like this:
class countDown {
var counter = 10
func decrement() {
counter--
}
}
This decrement method simply subtracts 1 from the counter property, which has an initial value of 10. To make this method run, you have to specify the object name, a period, and the method name like this:
var counter = countDown ()
counter.decrement()
This decrement method does the exact same thing every time, which subtracts 1 from the counter property. A more interesting and flexible method would accept data (one or more parameters) to modify the code in the method works such as:
class countDown {
var counter = 10
func decrement() {
counter--
}
func decrementByValue (step : Int) {
counter -= step
}
}
The second method, decrementByValue, accepts a single integer that gets stored in a variable called “step.” Then it subtracts the value of “step” from the counter property as shown in Figure 11-5.
Figure 11-5. Running a method that accepts a value
When a method accepts data, it can also return a specific value. To create such a method, you need to identify the data type the method returns (using the -> symbols) along with the “return” keyword that defines the specific value to return.
So if you wanted to create a method that returns a Float data type, you could define a method such as:
class mathBrain {
var tempValue: Float = 0
func average (first : Float, second : Float) -> Float {
return (first + second) / 2
}
}
This method accepts two Float numbers (stored in variables called “first” and “second”) and returns a Float value. To call this method, you would specify an object name, a period, the method name, and two numbers that are Float data types.
Notice that this method has two parameters called “first” and “second.” When passing the first parameter, you do not need to specify the parameter name (“first”) since it doesn’t have a # symbol in front of it. However, when passing any other parameters, you must specify the parameter name such as:
var math = mathBrain()
var temp : Float = math.average(4.0, second: 9.0)
print (temp)
To see how to create a set and add and remove data from it, create a new playground by following these steps:
import Cocoa
class mathBrain {
var tempValue: Float = 0
func average (first : Float, second : Float) -> Float {
return (first + second) / 2
}
}
var math = mathBrain()
var temp : Float = math.average(4.0, second: 9.0)
print (temp)
The “average” method accepts two parameters, adds them together, and divides by 2. Then it uses the “return” keyword to return this value.
When the average method gets past the numbers 4.0 and 9.0, it returns a value of 6.5, which gets stored in a variable called “temp” as shown in Figure 11-6.
Figure 11-6. Creating sets, inserting data in a set, and removing data from a set
Using Objects in an OS X Program
Objects can contain any number of properties and those properties can hold simple data types such as integers or strings, or more complicated data types such as tuples, sets, or arrays. An object can also contain one or more methods that typically manipulate the object’s properties.
In this sample program, you’ll define one class and create two objects based on that class. You’ll also see how to store a class in a separate file. Rather than cram everything into a single file, it’s easier to store code in separate files.
The two objects will run methods stored in the other object. The user interface will also retrieve values from both object’s properties to display in the text field of the user interface.
To learn more about user interfaces, you’ll also see how to connect two buttons to a single IBAction method and determine which button the user clicked. As usual, you’ll see how to display data in a user interface item through an IBOutlet. To create the sample program, follow these steps:
Figure 11-7. The user interface of the ObjectProgram
This user interface will display the number of hit points for two characters: a sheriff and an outlaw. Each time you click either the Sheriff Shoot or the Outlaw Shoot button, an IBAction method will randomly determine if the other character was hit or not. If so, it will also determine how much damage the shot causes, ranging from 1 to 3. Any changes will appear in the text field under the Sheriff or Outlaw label.
The Sheriff Shoot and Outlaw Shoot buttons runs an IBAction method that first determines which button the user clicked: the Sheriff Shoot or the Outlaw Shoot button. To identify which button the user clicked, you’ll need to modify a Tag property on each button so the Sheriff Shoot button will have a Tag value of 0 while the Outlaw Shoot button will have a Tag value of 1.
After determining whether the sheriff or the outlaw is shooting, the IBAction method runs the shoot method that randomly determines if the shot hit and the damage it caused, which gets subtracted from the hitPoints property of each object.
The total number of hit points appears in the text field under the Sheriff and Outlaw label. The moment the total number of hit points for either the sheriff or outlaw drops to 0 or less, an alert dialog appears to let you know whether the sheriff or the outlaw died. To connect your user interface to your Swift code, follow these steps:
Figure 11-8. Connecting the Outlaw Shoot button to an existing IBAction method
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var sheriffHitPoints: NSTextField!
@IBOutlet weak var outlawHitPoints: NSTextField!
At this point we’ve connected the user interface to our Swift code so we can use the IBOutlets to display data on the user interface. We’ve also created a single IBAction method to run when the user clicks on either of the two push buttons. Now we need to change the Tag property of the Outlaw Shoot button.
Figure 11-9. The Tag property at the bottom of the Attributes Inspector pane
Now that we’ve defined the user interface, the next step is to create a separate Swift file to hold our class, which you can do by following these steps:
Figure 11-10. Selecting a file to hold the class definition code
import Foundation
class person {
var hitPoints = 10
func shoot () -> Int {
var odds = 1 + Int(arc4random_uniform(3))
if odds == 3 {
// Hit, randomly determine damage from 1..3
return 1 + Int(arc4random_uniform(3))
} else {
return 0 // Missed
}
}
}
This class defines one property called hitPoints, which it initializes with a value of 10. It also defines one method called shoot, which does not accept any parameters but does return an integer value. Inside the shoot method, it calculates a random number from 1 to 3 and stores this value in the “odds” variable.
Next, it checks if the value in “odds” is exactly equal to 3. If so, then it calculates a second random number from 1 to 3 and returns this value. If the value in “odds” is not 3, then the method returns 0.
Now that you’ve defined a class in a separate Swift file, it’s time to actually use that class to create objects, which you can do by following these steps:
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var sheriffHitPoints: NSTextField!
@IBOutlet weak var outlawHitPoints: NSTextField!
var sheriff = person ()
var outlaw = person ()
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
sheriffHitPoints.integerValue = sheriff.hitPoints
outlawHitPoints.integerValue = outlaw.hitPoints
}
@IBAction func shootButton(sender: NSButton) {
if sender.tag == 0 { // Sheriff shooting
outlaw.hitPoints -= sheriff.shoot()
} else { // Outlaw shooting
sheriff.hitPoints -= outlaw.shoot()
}
sheriffHitPoints.integerValue = sheriff.hitPoints
outlawHitPoints.integerValue = outlaw.hitPoints
if sheriffHitPoints.integerValue <= 0 {
var myAlert = NSAlert()
myAlert.messageText = "The sheriff died."
myAlert.runModal()
} else if outlawHitPoints.integerValue <= 0 {
var myAlert = NSAlert()
myAlert.messageText = "The outlaw died."
myAlert.runModal()
}
}
This code checks the Tag property of the “sender” variable, which identifies which button the user clicked. If the Tag property is 0, then the user clicked on the Sheriff Shoot button so it runs the shoot method in the sheriff object and subtracts the result (a value from 0 to 3) from the outlaw.hitPoints property.
If the user clicked on the Outlaw Shoot button, then the shoot method runs the shoot method in the outlaw object and subtracts the result (a value from 0 to 3) from the sheriff.hitPoints property.
Whatever the result, it then displays the latest values of the hitPoints property from both the sheriff and the outlaw back in the two text fields on the user interface, identified by the sheriffHitPoints and outlawHitPoints IBOutlets.
Finally, if the hitPoints property of either the sheriff or the outlaw falls to 0 or less, then an alert dialog appears to display a message that either the sheriff or the outlaw died. The complete contents of the AppDelegate.swift file should look like this:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var sheriffHitPoints: NSTextField!
@IBOutlet weak var outlawHitPoints: NSTextField!
var sheriff = person ()
var outlaw = person ()
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
sheriffHitPoints.integerValue = sheriff.hitPoints
outlawHitPoints.integerValue = outlaw.hitPoints
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
@IBAction func shootButton(sender: NSButton) {
if sender.tag == 0 { // Sheriff shooting
outlaw.hitPoints -= sheriff.shoot()
} else { // Outlaw shooting
sheriff.hitPoints -= outlaw.shoot()
}
sheriffHitPoints.integerValue = sheriff.hitPoints
outlawHitPoints.integerValue = outlaw.hitPoints
if sheriffHitPoints.integerValue <= 0 {
var myAlert = NSAlert()
myAlert.messageText = "The sheriff died."
myAlert.runModal()
} else if outlawHitPoints.integerValue <= 0 {
var myAlert = NSAlert()
myAlert.messageText = "The outlaw died."
myAlert.runModal()
}
}
}
To see how this program works, follow these steps:
Figure 11-11. When one character’s hit point total drops to 0 or less, an alert dialog appears
Summary
Swift programming depends entirely on objects and the principles of object-oriented programming. To create an object, you must first define a class. A class typically consists of one or more variables (called properties) and one or more functions (called methods). Once you’ve defined a class, you can create an object that represents that class.
Properties in a class always need to be initialized. You can define initial values for every property at the same time you define those properties, or you can create a special initializer method that lets you accept data to define initial values for an object.
Properties can be initialized with a fixed value, or they can be computed based on values of another property. The value of one property can change the value of a different property.
You can create as many objects from a single class as you want. Typically it’s best to store class definitions in separate files to keep your code organized. An object-oriented program works by objects sending data to the properties of other objects, or calling methods stored in other objects. By working together, yet being independent, objects make it easy to create reliable and sophisticated programs much faster than before.