Unit 15: Defining Structures Flashcards
Method
a function defined inside a type; it can use the data stored in the type’s properties to do work
Property
a piece of data held by a struc, class or enum; e.g., each Array instance possesses a count property that is different depending on the characteristics of the particular array
Struct
can hold values as properties, define behaviour as methods, and define initialisers to set up its initial state; every instance of a struct has its own separate identity
Modeling Data
When building an app, one of the most important things to think about is how your app is going to represent the information that it needs.
Some apps are software models of the real world. For example, a shopping app has products, shopping carts, customers, and orders similar to a physical store.
In general, the types of data that an app deals with are known collectively as its model, or sometimes more verbosely, its data model.
A model is specific to a particular app:
A music player app might work with tracks, artists, albums, and playlists.
A drawing app might work with pens, brushes, a canvas, and shapes the user has drawn.
In this playground, you’ll learn some ways of defining the model of an app — starting with a song.
Modeling a Song
You know about some types: String, Int, Double, and Bool. But how can you use them to represent a song? Or a list of songs?
You could try something like this:
let songTitles = [“Ooh yeah”, “Maybe”, “No, no, no”, “Makin’ up your mind”]
let artists = [“Brenda and the Del-chords”, “Brenda and the Del-chords”, “Fizz”, “Boom!”]
let durations = [90, 200, 150, 440]
let song3 = “(songTitles[2]) by (artists[2]), duration (durations[2])s”
/*:
To access the information about a single song, you’d need to access three different arrays using the same index.
This approach raises some questions and issues.
How would you loop through an array of songs? What if you wanted to pass a song in as an argument to a function? What if you decided that you also wanted to include a star rating with each song. Would you have to rewrite everything?
It would be much better to have a Song
instead of a String
, a String
and an Int
.
Next, see how to define a Song
.
*/
To access the information about a single song, you’d need to access three different arrays using the same index.
This approach raises some questions and issues.
How would you loop through an array of songs? What if you wanted to pass a song in as an argument to a function? What if you decided that you also wanted to include a star rating with each song. Would you have to rewrite everything?
It would be much better to have a Song instead of a String, a String and an Int.
Next, see how to define a Song.
Custom Types
You aren’t limited to the types that come built-in with Swift. You can use existing types as building blocks to define your own types.
One way to create a new type in Swift is to define a structure, often called a struct. A struct is a simple way of grouping values of other types together.
To represent a song, you could declare a Song struct like this:
struct Song { let title: String let artist: String let duration: Int } /*: This creates a new type, called `Song`.
You learned about properties in the Instances lesson. Song
has three properties: title
, artist
, and duration
, each declared with let
and a type annotation. You can think of a Song
as a group of three constants.
> Your struct is a new type, and like all other types (String
, Int
…) its name should begin with a capital letter. Property names should begin with lower case letters. This makes it easy to tell the difference between types and values when reading code.
Once you’ve declared a new type, you can create an instance like this: */ let song = Song(title: "No, no, no", artist: "Fizz", duration: 150) //: Remember from the Instances lesson that every type has at least one initializer. When you declare a struct, an initializer is automatically created for you. Because this initializer has a parameter for each member property in the struct, it is called a _memberwise initializer_. //: - experiment: Try creating a new `Song`. Notice that the autocompletion pop-up menu will include the memberwise initializer.
//: Next, learn more about the properties of a struct. //:
Struct Properties
In the Instances lesson, you learned how to access the values of an instance’s properties. For instances of your own custom types, you work with properties the same way.
Here is the Song struct declaration again, and a new Song value created using the memberwise initializer:
struct Song { let title: String let artist: String let duration: Int } let song = Song(title: "No, no, no", artist: "Fizz", duration: 150) /*: In the example above, `song` is an instance of `Song`, and `Song` is the type. Each property can be accessed like this: */ song.title song.artist song.duration /*: The properties are on the instance you’ve created, called `song`. The instance `song` has a specific title, the type `Song` does not. The type `Song` just defines what each instance contains.
Think of it this way. A description of a cat, such as “A cat has four legs, whiskers, and a tail,” cannot be stroked. But you can stroke an actual cat (if it’s in the right mood).
- Experiment: Uncomment the line below and look at the error.\ The error is “Instance member 'title' cannot be used on type 'Song'.” It is saying that `title` can only be used on instances of the type `Song`, and not on the type itself. */ //Song.title /*: Learn how to make types with mutable properties next.
Mutability
Recall that an array declared with let is immutable, whereas one declared with var is mutable.
An array is a kind of struct and you can make your own structs mutable or immutable.
To make the properties of your custom structs mutable there are two things you need to do:
In the definition of the struct, declare any changeable properties using var.
Assign the struct to a variable, not a constant.
In this updated version of Song there is a mutable rating property:
struct Song {
let title: String
let artist: String
let duration: Int
var rating: Int
}
//: This step alone isn’t enough to make the rating
property on every Song
instance mutable. The struct must also be assigned to a variable:
var song = Song(title: “No, no, no”, artist: “Fizz”, duration: 150, rating: 0)
song.rating
song.rating = 4
song.rating
//: - Experiment: Change the variable song
to be a constant. What error does that produce?
//:
//: Note that the programmer who defines the type gets to choose which properties can possibly be changed. But any programmer that uses the type can decide whether a particular instance is mutable or immutable.
//:
//: Next, see what happens if you want to use the value of one property to figure out the value of another.
Calculated Properties
A Song has a duration property, measured in seconds. But it would also be useful to ask a song for its duration as a string formatted in minutes and seconds.
To solve this, you could have two properties, minutes and seconds, but then you would need to perform a calculation to find out the total duration. Alternatively, you could have three properties — minutes, seconds, and duration — but it would be easy to create a struct with inconsistent data, where the duration value didn’t add up to the right number of minutes and seconds.
A better approach to the problem would be to calculate the formatted string from the duration value.
For properties that can be calculated on demand, you can add a calculated property to the struct like this:
struct Song {
let title: String
let artist: String
let duration: Int
var formattedDuration: String { let minutes = duration / 60 // The modulus (%) operator gives the remainder let seconds = duration % 60 return "\(minutes)m \(seconds)s" } } let song = Song(title: "No, no, no", artist: "Fizz", duration: 150) song.formattedDuration /*: You have already encountered a calculated property: the `count` of an `Array`.
A calculated property is declared as a var
, since it could change depending on the rest of the struct. The rest of the declaration consists of a name, a type annotation, and then some code in braces, which has to return a value of the correct type. You can access the calculated property just like any other.
Note that inside the definition of formattedDuration
, the property duration
is accessed without dot notation. Within the code of a struct, you can access its own properties directly by their names, without using the dot.
- callout(Exercise): Add another calculated property to
Song
namedformattedTitle
, which gives you aString
. For the song above, the formatted title would be “No, no, no by Fizz”.
Next see how the types you defined can be used in functions.
[Previous](@previous) | page 5 of 9 | [Next: Functions](@next) */
Functions
Your own types can be passed into or out of functions, just like built-in types. (In fact, you can use your own types any place you use built-in types.)
This struct defines a rectangular area:
struct Rectangle { let width: Int let height: Int } //: If you wanted to find out if one rectangle is larger than another, you could define a function like this: func isRectangle(_ rectangle: Rectangle, biggerThan rectangle2: Rectangle) -> Bool { let areaOne = rectangle.height * rectangle.width let areaTwo = rectangle2.height * rectangle2.width return areaOne > areaTwo } //: Then you could use the function to compare two rectangles: let rectangle = Rectangle(width: 10, height: 10) let anotherRectangle = Rectangle(width: 10, height: 30)
isRectangle(rectangle, biggerThan: anotherRectangle)
//: This works, but there are a couple of issues: //: //: - The two arguments to the function are a lot of code to read in one line, which makes it harder to understand. //: - The function is available everywhere in a program, but you only need it when dealing with rectangles. //: - If you don't know there is an `isRectangle()` function, it is difficult to find using autocompletion. //: //: Next, see how to make this kind of functionality part of the `Rectangle` type. //:
Instance Methods
You learned how to use instance methods in the Instances lesson. When you define your own type, you can also define instance methods.
Instance methods are declared like functions, but you put them inside the body of the type definition:
struct Rectangle {
let width: Int
let height: Int
func isBiggerThan(_ rectangle: Rectangle) -> Bool { let areaOne = width * height let areaTwo = rectangle.width * rectangle.height return areaOne > areaTwo } }
//: Notice that within the body of the method definition, you can access the values of `height` and `width` of the struct without using a dot. The instance method is written as part of the struct definition, and so it can directly access the properties within the instance. //: //: Just like the methods on built-in types, the methods you define are called using the instance name, then a dot, then the name and arguments of the method: let rectangle = Rectangle(width: 10, height: 10) let otherRectangle = Rectangle(width: 10, height: 20)
rectangle.isBiggerThan(otherRectangle)
otherRectangle.isBiggerThan(rectangle)
//: - callout(Exercise): Simplify the `biggerThan` method by creating a calculated property named `area` for the rectangle struct and then using the calculated property inside the `isBiggerThan()` method. //:
Wrap Up
Every app has a model that represents the data it uses.
You can define your own types in Swift to organize data in a way that makes sense for your app.
One way to create custom types is by defining structs.
A struct is a type that can group together properties and methods.
Some properties hold data, like variables or constants. Others return a calculated value.
You create and use instances of your custom types the same way as any other type in Swift.
You use your custom types like any other types, including as function parameters and return types.
Next, design and build some structs of your own.
Exercise: Your Own Structure
When you worked through the Types playground, you had a chance to think about some real-world examples of types and the associated types they might depend on. A TrainingShoe type, for example, might have a size property that’s an Int, a brandName that’s a String, and a releaseDate that’s a Date.
Think of another real-world object and its properties. Make up some actions or behaviors that the object might be able to perform. Write them all in plain English first in a comment:
Using the struct syntax from this lesson, create a type for your real-world object with the properties and methods you thought of. Remembering to mark each property with let or var depending on whether or not it will be allowed to change. If you’re not sure how to implement the body of one of the methods, describe what the method should do in a comment.
Hint: If you made any properties with custom types, you can create placeholder types that have empty implementations. (See the TrainingShoe code at the bottom of this page for an example.) The placeholder type below will make sure your playground can run without errors.
// Add your own struct here:
/*: - callout(Exercise): Use the struct you created to make a new instance of your type.
*/
/*: - note: Here's an example of a placeholder type used for making a TrainingShoe: */ // Placeholder type struct Shoelaces {
}
struct TrainingShoe {
let size: Int
var isTied: Bool
var laces: Shoelaces
func squeak() { // Make a loud noise like rubber squealing on a gym floor }
func warnAboutLaces() { // If laces are untied, print a reminder to tie them } }
// Create an instance of the placeholder type let newLaces = Shoelaces()
// Use the instance of the placeholder type to create an instance of your new type let newShoe = TrainingShoe(size: 39, isTied: true, laces: newLaces)
/*:
*/