Properties and Class or Type Methods - OBJECT-ORIENTED PROGRAMMING - Understanding Swift Programming: Swift 2 (2015)

Understanding Swift Programming: Swift 2 (2015)

PART 2: OBJECT-ORIENTED PROGRAMMING

16. Properties and Class or Type Methods

An earlier chapter (Chapter 12 on "Classes, Objects, and Inheritance") described basic properties and how they are used with classes and objects. Two other earlier chapters (Chapters 14 and 15) described structures and enumerations.

There are a number of different kinds of properties—stored properties, computed properties, and lazy properties. Some can have property observers. There are instance and class or type properties. Properties can be used not only with classes, but also with structures and enumerations. But the situation is a little complex—different kinds of properties can be used with different kinds of these types.

The figure below shows what kinds of properties can be used by different types:

Thus, stored instance properties can be used by classes and structures, but not enumerations. Stored class (type) properties can be used by structures and enumerations, but not classes. (They are expected to be implemented for classes at some future point.) Computed properties, lazy properties, and property observers are supported by all three types.

In this chapter I will describe how these various properties are used—class (or type) stored properties, computed properties, lazy properties, and property observers.

I will also describe how class or type methods are used, which can be used by all of these types.

I refer to properties or methods that apply to an entire class, structure, or enumeration by calling them "class or type" properties or methods. I'm trying to avoid confusion here. Apple just calls them type properties or type methods, but I think this is confusing because the word type refers to a much broader set of things than just classes, structures, or enumerations. Also, the keywords used are class or static.

Class or Type Properties

Class (type) properties don't actually work yet for classes. Class/type properties do work for structures and enumerations, but if you try to use them with classes, you get an error message "class variables not yet supported". Apple's documentation doesn't say what the correct syntax will be be for class properties when used for classes. Therefore I will describe (guess) how I expect that they will work here for classes, and how they do work for the other types.

CLASSES

A class property, also known as a type property, stores a value for the class as a whole. Such a property will, in the case of a class, presumably be defined with the class keyword in front of the var or let keyword. The following shows an example of a class (type) property as used in a class:

class Apple {

var weightOfApple = 0.0

class var weightHeaviestApple = 0.0

// Warning:

// 1. Does not yet work for classes

// 2. Use of class keyword is a guess

}

The class keyword in the third line makes weightHeaviestApple a type or class property. This may look a bit like a class nested within another class, but it is not. The idea here is that this property stores something for the class as a whole. This property stores the weight of the heaviest apple in the class. The class also has an instance property that stores the weight of particular apples. (And presumably lots of other instance properties not included here.)

STRUCTURES

The example below defines a structure that works exactly like the class example above. The difference is that the struct keyword is used instead of class when defining the structure, and the static keyword used in front of the variable declaration in the third line.

struct Apple {

var weightOfApple = 0.0

static var weightHeaviestApple = 0.0

}

ENUMERATIONS

Enumerations also use the keyword static for class or type properties.

Computed Properties

The properties that I have described so far store values associated with a particular instance or for the class as a while and later retrieve them. Such properties are known as stored properties. A class can also have what are known as computed properties. A computed property is accessed for retrieval just like a stored property, but it neither stores nor retrieves the value of the property from memory. Instead, it computes the values when accessed. The code below shows a class with a single computed property, weightKg:

class Fruit {

var weightInLbs: Float = 0.0

var weightInKg: Float {

get {

return weightInLbs*2.20462

}

set (newValue) {

weightInLbs = newValue/2.20462

}

}

}

The computed property weightKg provides the weight of a piece of fruit in kilograms. That weight is actually stored in pounds, in the property weightLbs.

We can create an instance of Fruit as follows:

var pieceOfFruit = Fruit()

We can then set its weight to be half a pound:

pieceOfFruit.weightInLbs = 0.5

If we access the weightInKg property, Swift will compute the weight in kilograms by executing the code associated with the get keyword, returning the value that was computed.

print(pieceOfFruit.weightInKg) // prints: 1.10231

We can also write a value to the computed property:

pieceOfFruit.weightInKg = 0.8

This will cause the code associated with the set keyword to be executed. The parentheses just after the set serve to define the name of an input parameter which is set to the value that was written to the computed property. By convention, we have called this newValue, although we could have used any legitimate identifier name. The code converts the value in newValue to pounds and stores it in the stored property weightInLbs.

Computed properties must have a getter, using the keyword get. A setter, using the keyword set, is optional.

This is an instance computed property, and can be used in classes, structures, and enumerations.

Class or type computed properties also exist, and can be used in classes, structures, and enumerations, but are rarely used.

Lazy Properties

A lazy property is a property the value of which is stored only when it is accessed, rather than being stored when a new instance is initialized.

In cases where it takes some effort to initialize a property, it may be desirable to delay initializing it. The property may have a value that is downloaded over the Internet, or otherwise obtained in an expensive manner. In many cases the property may never be accessed and in such a case this saves the effort (and potential delay) of obtaining the value. To defer initialization until a property is first accessed, put the keyword lazy just before the var or let defining whether the property is a variable or a constant.

The example below shows a property that contains an image, which is obtained by the method getFruitImage:

lazy let imageOfFruit: UIImage = getFruitImage("fruitImage.gif")

func getFruitImage(imageFilename String) -> UIImage {

// Add code here to

// go to server and get an image for imageCode,

// given the name of the file

// then return the image from the function

}

Property Observers

A property observer has syntax similar to a method. For a particular property, there are two possible pieces of code. One is executed just before a value is stored to the property in question; the other is executed just after a value is stored to the property.

A property observer is a piece of code that is executed when the value of a property is about to be, or has just, changed. Property observers obviously only work with properties that are variables, not constants, since the latter never change.

A property observer is included just after the definition of a property in a class (or structure or enumeration) definition. If the code is to be executed just before the value of a property is set, the keyword willSet is used. If the code is to be executed just after a property is set, the keyworddidSet is used.

If willSet is used, a constant named newValue is defined by the compiler that contains the value that the property will be set to.

If didSet is used, a constant named oldValue is defined by the compiler that contains the value that the property was previously set to.

For an observer to execute code only after a value has changed, code like the following goes in the definition of the property:

var color: String = "red" {

didSet {

print("The property color was set to a new value; the old value was \(oldValue)")

}

}

This prints out the old value of the property, using the oldValue constant provided automatically.

If code is to be executed both just before and just after a value has changed, both the willSet and didSet keywords are used.

var color: String = "red"{

willSet {

print("The property color will be set to a new value of\(newValue)")

}

didSet {

print("The property color was set to a new value; the old value was \(oldValue)")

}

}

You can make up a custom name to replace oldValue or newValue, if desired, by placing the custom name within parentheses just after the willSet or didSet keyword:

var color: String = "red" {

willSet (willBeColor){

print("The property color will be set to a new value of\(willBeColor)")

}

didSet (wasColor) {

print("The property color was set to a new value; the old value was \(wasColor)")

}

}

Property observers are not called when a property is initialized, either when it is provided with an initial value or such a value is set with a default, convenience, or memberwise initializer.

Property observers cannot be used with properties that are initialized lazily (with the lazy keyword).

Property observers will only work with stored properties, not computed properties. (You can put any code you want to execute when a computed property is accessed in the getter or setter of the computed property.) If a computed property has been inherited, you can add a property observer to it (since you might not have access to its getter or setter).

Observers as described above can also be used for global and local variables, even though these are not properties. (Global variables are those that are defined outside of any definition of a class, structure, enumeration, function, method, or closure. Local variables are those that are defined within a function, method, or closure.)

Class (Type) Methods

Most methods that you will be using with Swift are instance methods. This means that they are invoked from a particular instance of a class, and if reference is made to an instance property, the particular instance they are invoked from determines which copy of the property is used. Instance methods can also refer to class or type properties.

Class methods, like class properties, are different. A class method, also known as a type method, is invoked from the name of the class, structure, or enumeration. Because it has no concept of what an instance is, it cannot access instance properties, but only class (type) properties.

Because classes do not yet have class properties, I'll use an example of a structure with a class (type) property and a class (type) method, is shown below:

struct Apple {

var weightOfApple = 0.0

static var weightHeaviestApple = 0.0

static func printWeightOfHeaviestApple() {

print("Weight of the heaviest apple is \(weightHeaviestApple)")

}

}

Apple.weightHeaviestApple = 1.2

Apple.printWeightOfHeaviestApple()

// Prints: Weight of the heaviest apple is 1.2

This sets the class/type property and then executes the method that prints its value.

Hands-On Exercises

Go to the following web address with a Macintosh or Windows PC to do the Hands-On Exercises.

For Chapter 16 exercises, go to

understandingswiftprogramming.com/16