The Big Nerd Ranch Guide to Structures and Classes in Swift
At this point you should be somewhat familiar with using Swift’s standard types: strings, arrays, enums, etc. It is time to move on to bigger and better things: defining your own types. In this chapter, you will build a simple 2D physics simulation. You will create your own structure and a few classes, and you will learn about the differences between them.
Structures
In Cocoa, structures are typically used to represent groupings of data. For example, there is NSPoint, which represents a point in 2D space with an X and a Y value. As your first structure you will create a 2D vector structure.
Create a new playground. From Xcode’s File menu, select New... → Playground. Name the playground Physics and save it with the rest of your projects.
Start by defining the Vector structure:
import Cocoa struct Vector { var x: Double var y: Double }
Much like C structures, Swift structures are composite data types. They are composed of one or more fields, or properties, each of which has a specified type. A few lines down, create an instance of Vector and access its properties:
let gravity = Vector(x: 0.0, y: -9.8) // {x 0, y -9.800000000000001} gravity.x // 0 gravity.y // -9.800000000000001
You just used Swift’s automatic initializer to create an instance of this structure. The automatic initializer has a parameter for each property in the structure. If you were to add a z field, this code would cause a compiler error because it lacks a z parameter. (Do not worry about the zeros; that is just typical floating point fun.)
You can provide your own initializers, but when you do, the automatic initializer is no longer provided. Go back to Vector and add an initializer that takes no parameters and initializes x and y to 0.
struct Vector { var x: Double var y: Double init() { x = 0 y = 0 } }
Initializers in Swift use the init keyword, followed by the parameter list, and then the body of the initializer. Within the body, the x and y properties are assigned directly.
An initializer must initialize all of the properties of its structure.
As we warned, defining this initializer has caused the automatic one to vanish, causing an error in the playground. You can easily define it manually, however:
struct Vector { var x: Double var y: Double init() { x = 0 y = 0 } init(x: Double, y: Double) { self.x = x self.y = y } }
A Swift programmer would say that this initializer takes two parameters, x and y, both of type Double.
What is self? It represents the instance of the type that is being initialized. Using self.propertyName is usually unnecessary (you did not use it in init()), but because the initializer’s parameter names match the names of the properties you must use self to tell the compiler that you mean the property and not the parameter.
Before continuing, let’s make an improvement. As the Vector structure stands, its two initializers have independent code paths. It would be better to have them use one code path by having the parameterless initializer call the initializer which takes both x and y.
struct Vector { var x: Double var y: Double init() {x = 0y = 0self.init(x: 0, y: 0) } init(x: Double, y: Double) { self.x = x self.y = y } }
A single code path for initialization is not required for structures, but it is a good habit to get into as you will use it when working with classes.
Instance methods
Methods allow you to add functionality to your data types. In Swift, you can add methods to structures as well as classes (and enums!). Instance methods operate within the context of a single instance of the type. Add an instance method for multiplying a vector by a scalar:
struct Vector { ... init(x: Double, y: Double) { self.x = x self.y = y } func vectorByAddingVector(vector: Vector) -> Vector { return Vector(x: self.x + vector.x, y: self.y + vector.y) } }
The func keyword in the context of a structure indicates that this is a method. It takes a single parameter of type Double and returns an instance of Vector.
Try this new method out:
let gravity = Vector(x: 0.0, y: -9.8) // {x 0, y -9.800000000000001}gravity.xgravity.ylet twoGs = gravity.vectorByAddingVector(gravity) // {x 0, y -19.6}
What is the name of this method? In conversation you would call it vectorByAddingVector, but in this text we include parameters, like this: vectorByAddingVector(_:). By default, the first parameter of a method is not named – thus the underscore.
Why not name the first parameter? Because the convention – inherited from Objective-C and Cocoa – is that the base name of the method includes the name of the first parameter, in this case Vector. Suppose you added another parameter to that method. What would it look like?
func vectorByAddingVector(vector: Vector, numberOfTimes: Int) -> Vector { var result = self for _ in 0..<numberOfTimes { ...
This method would be called vectorByAddingVector(_:numberOfTimes:). Note that there is a colon for each parameter.
This can lead to verbose method names, but the code actually becomes very readable. No guessing or relying on the IDE to tell you what the third parameter is!
By default, each parameter’s internal name is the same as its external name (except the first parameter, that is). In vectorByAddingVector(_:numberOfTimes:), the second parameter is named numberOfTimes. That is certainly very descriptive, but you might prefer to use a shorter name (like times) within the method. In that case you would explicitly set the internal parameter name like this:
func vectorByAddingVector(vector: Vector, numberOfTimes times: Int) -> Vector { var result = self for _ in 0..<times { ...
The method’s signature has not changed. For those calling it, its name is still vectorByAddingVector(_:numberOfTimes:), but internally you have the satisfaction of using the name you want.
Using self in instance methods
As in initializers, self represents the instance that the method is being called on. As long as there is no conflict with named parameters or local variables, however, it is entirely optional, so we prefer to leave it off. Make this change to vectorByAddingVector(_:).
struct Vector { ... func vectorByAddingVector(vector: Vector) -> Vector {return Vector(x: self.x + vector.x,y: self.y + vector.y)return Vector(x: x + vector.x, y: y + vector.y) } }