Structures - OBJECT-ORIENTED PROGRAMMING - Understanding Swift Programming: Swift 2 (2015)

Understanding Swift Programming: Swift 2 (2015)

PART 2: OBJECT-ORIENTED PROGRAMMING

14. Structures

A structure in Swift combines related data and methods together. As this definition suggests, structures are very similar to classes. Both serve as blueprints for specific instances that each have their own copy of data that is stored in the form of properties. This data can then be manipulated with methods associated with the structure.

When would you use a structure rather than a class? I'll discuss this in more detail later, but the short answer is that structures are safer, and that you would normally use a structure when you don't need inheritance (which structures do not have) and don't have any other reason to use a class. Apple recommends using a structure whenever you don't need a class.

Many of the types that you might imagine would be implemented as primitive data types in Swift, such as integers, floating point numbers, Booleans, strings, dictionaries, arrays, and the like, are actually implemented as structures.

Structures have received more attention with Swift 2 because of the protocol extension capability, which allows structures to obtain methods by conforming to protocols, much like classes obtain methods from superclasses.

Creating a Structure

A structure, like a class, allows you to create a custom data type. For example, a custom data type that defines a Point, a pair of x, y coordinates representing a point on a screen, might be defined as follows:

struct Point {

var xCoordinate: Double

var yCoordinate: Double

}

So far the syntax is identical to that used for a class except that the keyword struct is used rather than the keyword class.

A specific instance of this structure can be created as follows:

var particularPoint = Point(xCoordinate: 2.3, yCoordinate: 3.8)

This creates an instance of the structure Point with an x coordinate of 2.3 and a y coordinate of 3.8.

The data type of the variable particularPoint will be inferred by the compiler to be of type Point. If we wish, we can explicitly declare the type as follows:

var particularPoint: Point = Point(xCoordinate: 2.3, yCoordinate: 3.8)

Representing a point is a pretty typical use for a structure. Having a single value that is a point on a screen is a little less clumsy than worrying about two different x and y coordinates. It uses the structure type quite efficiently. It is lightweight and the code will run nearly as fast as code defining two separate x and y coordinates.

Note that the names of the properties must be used when a new instance (point) is created. The following will not work:

var particularPoint = Point(2.3, 3.8) // Compiler error

As with classes, when a new instance of a structure is created, the properties defined for the structure must be set to initial values, or initialized. The syntax shown above (the line above the one with the compiler error), in which the names of the properties appear as names for the input parameters in the call to Point that creates the new instance, makes use of what is known as a default memberwise initializer that is not found in classes.

With a memberwise initializer, as long as the property names are used along with a value in the statement creating the instance, it is not necessary to provide an initializer, the method-like code within the structure that sometimes performs initialization.

If a variable is used to refer to the instance, it is possible to modify the properties with dot syntax:

var particularPoint = Point(xCoordinate: 2.3, yCoordinate: 3.8)

particularPoint.xCoordinate = 1.2

print(particularPoint.xCoordinate) // Will print 1.2

However, if the instance is defined with a constant, the instance is read-only. (This is true even if the properties are defined with the var keyword):

let particularPoint = Point(xCoordinate: 2.3, yCoordinate: 3.8)

particularPoint.xCoordinate = 1.2 // Compiler error

Other Ways to Initialize a Structure

A second way of initializing a structure is by providing default values for each of the properties when declaring those properties:

struct Point {

var xCoordinate: Double = 0.0

var yCoordinate: Double = 0.0

}

The explicit declaration of type is actually not required; with a value of 0.0, the compiler would infer it to be Double anyway.

If this approach is taken an instance would normally be created with the properties set to initial values of 0.0 and 0.0. These values could then be set again to their proper values with dot syntax:

var particularPoint = Point()

particularPoint.xCoordinate = 2.3

particularPoint.yCoordinate = 3.8

A third way to initialize an instance of a structure is to provide an initializer. Like an initializer for a class, it works like a method, but does not have a func keyword and is called automatically when an instance is created:

struct Point {

var xCoordinate:Double;

var yCoordinate:Double;

init(x: Double, y: Double) {

self.xCoordinate = x

self.yCoordinate = y

}

}

The use of self.xCoordinate and self.yCoordinate is actually optional; referring to the properties just with xCoordinate and yCoordinate would work. However, it is a good coding practice to use self to remove any ambiguity.

This works mostly the same as using a memberwise initializer, except that the parameter names are now different from the names of the properties. An instance would be created with:

var particularPoint = Point(x: 2.3,y: 3.8)

This approach might be especially useful if only some of the properties needed to be initialized by the statement creating the instance. Others would be initialized with default values set when the property is declared.

The approach also allows initialization without the use of parameter names. An underscore character is used in the definition of the structure:

struct Point {

var xCoordinate:Double;

var yCoordinate:Double;

init(_ x: Double, _ y: Double) {

self.xCoordinate = x

self.yCoordinate = y

}

}

Creating a new instance is then done without the use of parameter names:

var particularPoint = Point(2.3, 3.8)

If an initializer is defined within a structure, it is no longer possible to use the default memberwise initializer.

Initializer Delegation

It is often efficient to have multiple initializers in a structure (or class). Frequently, there are many properties, but most get set to some initial value that is always the same. Only a few need to be set to an initial value that is different depending upon the situation when a new instance is being created.

In such cases, initializer delegation is commonly used. One initializer accepts the values for those properties that need to be changed, and then passes them to a second initializer that performs initialization for all the values. Classes often do this, and have two kinds of initializers, designated initializers and convenience initializers, that each handle a different part of the task.

Structures do things very similarly. However, there isn't any distinction between types of initializers. Instead, one initializer that has parameter names and types that match those in the call that creates the instance is executed when the instance is created. That initializer then calls a second initializer that does the actual initialization. In other words, it delegates part of the initialization task to another initializer.

As an example, we have a structure, Fruit, that allows creation of instances that represent particular pieces of fruit that have different values for the properties skinColor, countryGrownIn, fleshColor, and hasSeeds.

struct Fruit {

var skinColor: String

var countryGrownIn: String

var fleshColor: String

var hasSeeds: Bool

init(skinColor: String, countryGrownIn:String, fleshColor:String, hasSeeds: Bool) {

self.skinColor = skinColor

self.countryGrownIn = countryGrownIn

self.fleshColor = fleshColor

self.hasSeeds = hasSeeds

}

init(skinColor: String) {

self.init(skinColor: skinColor, countryGrownIn: "United States", fleshColor: "white", hasSeeds: true)

}

Let's say that for our particular application, most of the time three of the four properties defined in the class are normally the same: "United States" for countryGrownIn, "white" for fleshColor, and true for hasSeeds. The only property that changes a lot is skinColor, which might be "red", "green", or "yellow", and maybe other variations. We create one initializer that initializes all of the properties in the class. But most of the time, when we create a new instance, we won't define the values of all four properties in our statement that creates the new object. Instead, we'll create a new instance with just a single parameter, as shown below:

var aPieceOfFruit = Fruit(skinColor: "green")

This creates a new instance but does not call the initializer with all four parameters because the parameter names and types do not match. Instead, the initializer that matches the parameter names and types is called. That initializer, in turn, calls the other initializer. In doing so, it passes along the value for skinColor ("green"), and then specifies (default) values for the other three properties: countryGrownIn, fleshColor and hasSeeds.

If we actually want to create an instance and specifically set all of these parameters to particular values, we can still do it, by specifying all of the parameters in the call so that the initializer with all of these parameters in its input parameters will be called.

var aPieceOfFruit = Fruit(skinColor: "green", countryGrownIn: "United States", fleshColor: "white", hasSeeds: true)

No Deinitializer for Structures

Although structures have (default or otherwise) initializers, structures do not have deinitializers, which are method-like pieces of code (using the deinit keyword) that are called automatically when an instance of a class goes away. These are used to allow the programmer to delete any resources that may have been created that iOS would not automatically remove. This is assumed to be unnecessary for structures.

Value Versus Reference Types in Memory

Aside from inheritance, the biggest difference between structures and classes is the way that they are stored in memory. Classes are reference types, meaning that they store only a single copy of the data associated with each instance of a class in memory. This goes into the heap memory (See Chapter 19 on "Memory Management" for a discussion of the differences between heap and stack memory.) You can see the implications of storing a single copy of this data as follows. Suppose you have a class named Apple with a property skinColor, and you create an instance of it as follows:

class Apple {

var skinColor = "red"

}

var aFirstApple = Apple()

print(aFirstApple.skinColor) // Prints: "red"

This creates an instance of the class Apple and sets the skinColor property to "red", the default (since most apples are red).

Now suppose we create the instance and assign it to aFirstApple. And then we make a copy of the instance and assign it to another variable aSecondApple:

var aFirstApple = Apple()

var aSecondApple = aFirstApple

We further set the value of the skinColor property of the aSecondApple reference to "green".

aSecondApple.skinColor = "green"

We then print out the values:

print(aFirstApple.skinColor) // Prints: green

print(aSecondApple.skinColor) // Prints: green

In both cases, the value of the property skinColor is "green". What is happening is that there is only one copy of the property, stored in heap memory, and shared by everything that references it. If its value gets changed, it is changed for everything that references it.

We can compare this with what happens when we implement Apple as a structure rather than a class:

struct Apple {

var skinColor = "red"

}

var aFirstApple = Apple()

print(aFirstApple.skinColor) // Prints: red

This creates an instance of the (now structure) Apple and sets the skinColor property to "red".

We again make a copy of the instance and assign it to another variable aSecondApple:

var aFirstApple = Apple()

var aSecondApple = aFirstApple

Now we set the value of the skinColor property of the aSecondApple reference to "green", just as before.

aSecondApple.skinColor = "green"

We then print out the values:

print(aFirstApple.skinColor) // Prints: red

print(aSecondApple.skinColor) // Prints: green

In this case the skinColor property for aFirstApple is red, while the skinColor property for aSecondApple is green. Why? When the instance reference contained in aFirstApple was assigned to the variable aSecondApple, it did not just assign a reference. Instead, it made a copy of the entire structure and stored it in stack memory. So there were then two separate copies of the instance. The value of the property skinColor for one of the copies could be changed to "green" while allowing the value of the property skinColor for the other copy to remain the same, "red".

Choosing Between a Structure and a Class

If you need inheritance, then the decision is simple: you must use a class.

If you don't need inheritance, then the major factor in deciding between a class and a structure is whether a reference type or a value type is better.

You will hear advice from some developers to the effect that making copies is "expensive", and thus reference types are more efficient. This is true in some cases. It depends on what is contained in the data structure that is being copied. If it is something big, like an large image, it may well be true that copies are expensive and a class may be a better choice. If, however, the structure consists of small pieces of information, tightly coupled together, then this is a situation for a structure. If runtime performance is an issue, a structure may be preferred because accessing the stack memory is much faster than accessing the heap memory.

Unless the data in the structure is unwieldy, safety can be a major consideration. Making copies is safer than passing references that can then be used to corrupt the single existing copy of data. This can be particularly risky when you are dealing with multiple threads and you have different pieces of code making changes to that single copy simultaneously. This can make things very difficult to debug.

In cases where the data comes from, or is being passed to, a Cocoa Touch API, you may not have much of a choice. Many Cocoa Touch APIs expect that the types that it deals with will not only be a class, but a subclass of NSObject.

For whatever reason, structures tend to be used much less frequently than classes in apps. (Structures are obviously a very important part of the Swift language itself, since the language’s fundamental types have been implemented with structures.) This may well change with Swift 2 if “protocol oriented programming” and the use of protocol extensions to define methods catches on.

Using Methods With Structures

It is possible to use instance (and also "class" or “type”) methods with structures, which are used in the same way that they are used for classes, with one exception. The exception is that methods are not allowed to modify the values of properties defined with structures unless the keywordmutable is used when the method is defined.

To use our example of an Apple structure, we can include the following method within the structure:

struct Apple {

var skinColor = "red"

func printSkinColor () {

print(self.skinColor)

}

}

We can then create an instance of an apple with it, and call the method to print the color of its skin:

var aParticularApple = Apple()

aParticularApple.printSkinColor() // Prints: red

Note that this did not change the value of the (single) property of the instance.

Suppose, however, we want to call a method that does change the value of that property:

struct Apple {

var skinColor = "red"

func setSkinColor (color: String) {

skinColor = color // Compiler error

}

}

We would try to create a new instance of Apple, then call the setSkinColor method with a parameter that will change the value of the property skinColor:

var aParticularApple = Apple()

aParticularApple.setSkinColor(color: "green")

However, this won't work. The compiler won't allow us to define a method for the structure that writes to a property, unless it has the mutating keyword just before the func:

struct Apple {

var skinColor = "red"

mutating func setSkinColor (color:String) {

skinColor = color // Will now compile and work OK

}

}

Differences Between Classes and Structures

There are far more similarities than differences between classes and structures. The following chart describes the similarities and differences:

In most cases, those capabilities that structures have work in the same way that they do for classes, as follows:

Computable Properties. These are properties that are accessed with dot syntax like ordinary stored properties, but actually result in code being executed to calculate a value rather than simply retrieving it. Reading a property causes a get piece of code to be executed, while writing to a property causes a set piece of code to be executed. The latter can write to other stored properties in the structure. The syntax is exactly the same as for classes.

Lazy Properties. A lazy property is not initialized until it is actually accessed. Properties are marked as lazy when they involve some effort and delay and aren't necessarily always used. If they never get accessed, the effort and delay does not occur. A property is defined as lazy by simply including the keyword lazy before the var keyword. The syntax is exactly the same as for classes.

Property Observers. A property observer is a piece of code that is executed either just before or just after a new value for a property is set. Code marked with the willSet keyword indicates code that will be executed just before a new value is set; code marked with the didSet keyword indicates code that will be executed just after a new value is set. The syntax is exactly the same as for classes.

Class (Type) Property. A class or type property is a property that is common across all instances of a class or structure, and is not associated with a particular instance. The keyword static is used to designate a class or type property for a structure. This comes just before the var or let in the declaration. (In the case of a class the keyword class is expected to be used. As of Swift 1.2 class/type properties do not yet work for classes.)

Class (Type) Method. A class or type method is a method that is performed for the class or structure as a while, rather than for a particular instance. The keyword static is used to designate a class or type method for a structure. This comes just before the func keyword in the declaration. (In the case of a class the keyword class is used.)

Hands-On Exercises

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

For Chapter 14 exercises, go to

understandingswiftprogramming.com/14