Problem overview
A class bundles values with the questions you can ask about them. Here the class is Rectangle: it remembers a width and a height, and it can answer three questions: its area(), its perimeter() and whether it isSquare().
For a rectangle 3 wide and 4 high, the area is 3 · 4 = 12, the perimeter is 3 + 4 + 3 + 4 = 14, and it is not a square. For 5 by 5 the area is 25, the perimeter is 20, and it is a square. The lesson's tests print the square answer as True or False; Swift's own spelling of a Bool is true and false.
Approach
Each rectangle object must keep its own two sizes, so they live in properties: variables that belong to one object. A method then reads them without needing any argument, because inside a method width and height mean "the properties of the object this method was called on".
Every method is one expression on two numbers, so each one runs in O(1) time.
Step by step
- Declare the properties.
var width: Intandvar height: Int, one per line, at the top of the class. EveryRectanglegets its own pair. - Store the parameters in
init. The parameters are named like the properties, soself.width = widthmeans "the property gets the parameter". Withoutself., plainwidthis the parameter. areareturnswidth * height.perimeterreturns2 * (width + height): walking around the rectangle you pass each side once, which is twice the sum of the two different sides.isSquarereturnswidth == height. A comparison already produces aBool, so noifis needed.
The solution in Swift
class Rectangle {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
func area() -> Int {
return width * height
}
func perimeter() -> Int {
return 2 * (width + height)
}
func isSquare() -> Bool {
return width == height
}
}
Using it looks like this. Note the argument labels: the initializer is declared as init(width: Int, height: Int), so a call writes width: and height:.
let a = Rectangle(width: 3, height: 4)
print(a.area(), a.perimeter(), a.isSquare())
let b = Rectangle(width: 5, height: 5)
print(b.area(), b.perimeter(), b.isSquare())
let c = Rectangle(width: 2, height: 10)
let d = Rectangle(width: 10, height: 2)
print(c.area(), c.perimeter(), c.isSquare())
print(d.area(), d.perimeter(), d.isSquare())
12 14 false
25 20 true
20 24 false
20 24 false
The last two lines show that each object keeps its own sizes, and that turning a rectangle on its side changes neither its area nor its perimeter.
Swift details worth knowing
- Properties need a value before
initends. Swift refuses to build an object that has an unsetIntproperty, which is why both assignments belong ininit. - A class is a reference type.
let e = Rectangle(width: 1, height: 1)makes the nameeconstant, not the object's properties, soe.width = 6is allowed ande.area()then returns 6. With astructthe same line would be an error. funcmethods have a return type after->.area()andperimeter()returnInt,isSquare()returnsBool.
Class or struct?
Swift would let you write the same type as a struct, and many Swift types are structs. The difference shows when you copy. A struct is a value type: assigning it copies it. A class is a reference type: assigning it gives a second name for the same object. After let b = a and b.width = 10, with a a 2 by 3 rectangle, a struct leaves a.area() at 6 while b.area() is 30. With the class, a.area() and b.area() are both 30, because there is only one object. This lesson asks for a class, so that is the behaviour to expect.
Trying it on your own
Besides the examples above, a few small rectangles are worth checking by hand against the code: a 1 by 1 rectangle (area 1, perimeter 4, a square), a 1 by 10000 rectangle (area 10000, perimeter 20002, not a square) and a pair of objects with the same sizes, which must give the same answers without affecting each other.
Edge cases and common mistakes
- Missing parentheses in the perimeter.
2 * width + heightmultiplies only the width: for 2 by 10 it gives 14 instead of 24. - Writing
width = widthininit. Both sides are the parameter, so the property is never set. Useself.width = width. - Sharing sizes between objects. Keep the sizes in properties of the object, not in values outside the class, so two rectangles never overwrite each other.
- Using
ifforisSquare.if width == height { return true } else { return false }works but only repeats whatreturn width == heightsays. - Forgetting the labels when creating the object:
Rectangle(3, 4)doesn't compile for this initializer.
Testing
The lesson's tests create several rectangles first and then ask each one its three questions, so a bug that shares data between objects shows up quickly. Try these in a file of your own:
Rectangle(width: 3, height: 4)gives area 12, perimeter 14, not a square.Rectangle(width: 5, height: 5)gives area 25, perimeter 20, a square.Rectangle(width: 2, height: 10)andRectangle(width: 10, height: 2)both give area 20 and perimeter 24.
Check each answer with pencil and paper before you run the code. If the numbers disagree, the most likely cause is a missing pair of parentheses in perimeter or a property that was never assigned in init.
Key takeaways
- Properties hold the data of one object; methods answer questions about it.
self.width = widthseparates the property from the parameter of the same name.- A comparison like
width == heightis already aBool. - A class is a reference type: two names can point to the same object.
