Search icon CANCEL
Subscription
0
Cart icon
Cart
Close icon
You have no products in your basket yet
Save more on your purchases!
Savings automatically calculated. No voucher code required
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Swift 4 Programming Cookbook

You're reading from  Swift 4 Programming Cookbook

Product type Book
Published in Sep 2017
Publisher Packt
ISBN-13 9781786460899
Pages 384 pages
Edition 1st Edition
Languages
Toc

Table of Contents (9) Chapters close

Preface 1. Swift Building Blocks 2. Building on the Building Blocks 3. Data Wrangling with Swift Control Flow 4. Generics, Operators, and Nested Types 5. Beyond the Standard Library 6. Swift Playgrounds 7. Server-Side Swift 8. Performance and Responsiveness in Swift

Object classes

Object-oriented programming is currently the dominant programming paradigm. At the core of this paradigm is the object class. Objects allow us to encapsulate data and functionality, which can then be stored and passed around.

Getting ready

Let's build some class objects. Then, we'll break down the components of the class to understand how it is defined and used.

How to do it...

Let's start by entering the following code into the playground:

class Person { 

let givenName: String
let middleName: String
let familyName: String
var countryOfResidence: String = "UK"

init(givenName: String, middleName: String, familyName: String) {
self.givenName = givenName
self.middleName = middleName
self.familyName = familyName
}

var displayString: String {
return "\(fullName()) - Location: \(countryOfResidence)"
}

func fullName() -> String {
return "\(givenName) \(middleName) \(familyName)"
}
}

final class Friend: Person {
var whereWeMet: String?

override var displayString: String {
return "\(super.displayString) - \(whereWeMet ?? "Don't know where we met")"
}
}

final class Family: Person {
let relationship: String

init(givenName: String, middleName: String, familyName: String = "Moon",
relationship: String) {
self.relationship = relationship
super.init(givenName: givenName, middleName: middleName, familyName:
familyName)
}

override var displayString: String {
return "\(super.displayString) - \(relationship)"
}
}

let steve = Person(givenName: "Steven", middleName: "Paul", familyName: "Jobs")
let dan = Friend(givenName: "Daniel", middleName: "James", familyName: "Woodel")
dan.whereWeMet = "Worked together at BBC News"
let finnley = Family(givenName: "Finnley", middleName: "David", relationship: "Son")
let dave = Family(givenName: "Dave", middleName: "deRidder", familyName: "Jones", relationship: "Father-In-Law")
dave.countryOfResidence = "US"

print(steve.displayString) // Steven Paul Jobs
print(dan.displayString) // Daniel James Woodel - Worked together at BBC News
print(finnley.displayString) // Finnley David Moon - Son

How it works...

Classes are defined with the class keyword, class names start with a capital letter by convention, and the implementation of the class is contained, or "scoped", within curly brackets:

class Person {
//...
}

An object can have property values, which are contained within the object. These properties can have initial values, as countryOfResidence does in the following code, although bear in mind that constants (defined with let) cannot be changed once the initial value has been set:

class Person {
let givenName: String
let middleName: String
let familyName: String
var countryOfResidence: String = "UK"
//...
}

If your class were to just have the preceding property definitions, the compiler would raise a warning, as givenName, middleName, and familyName are defined as non-optional strings, but we have not provided any way to populate those values.

The compiler needs to know how the object will be initialized, so that we can be sure that all the non-optional properties will indeed have values:

class Person { 
let givenName: String
let middleName: String
let familyName: String
var countryOfResidence: String = "UK"

init(givenName: String, middleName: String, familyName: String) {
self.givenName = givenName
self.middleName = middleName
self.familyName = familyName
}
//...
}

The init is a special method (functions defined within objects are called methods) that's called when the object is initialized. In the Person object of the preceding code, we expect givenName, middleName, and familyName to be passed in when the object is initialized, and we assign those provided values to the object's properties. The self. prefix is used to differentiate between the property and the value passed in as they have the same name.

We do not need to pass in a value for countryOfResidence as we defined an initial value when the property was defined. This isn't ideal, though, as when we initialize a Person object, it will always have the countryOfResidence variable set to "UK", and we will have to change that value after initializing. Another way to do this would be to use a default parameter value, as seen in the previous recipe. Amend the Person object initialization to the following:

class Person { 
let givenName: String
let middleName: String
let familyName: String
var countryOfResidence: String

init(givenName: String, middleName: String, familyName: String, countryOfResidence:
String = "UK") {
self.givenName = givenName
self.middleName = middleName
self.familyName = familyName
self.countryOfResidence = countryOfResidence
}
//...
}

Now, you can provide a country of residence in the initialization or omit it to use the default value:

class Person { 
//...
var displayString: String {
return "\(fullName()) - Location: \(countryOfResidence)"
}
//...
}

The property declaration for displayString is different from the others. Rather than having a value assigned to it, it is followed by an expression contained within curly braces. This is a computed property; its value is not static, but is determined by the given expression every time the property is accessed. Any valid expressions can be used to compute the property, but must return a value that matches the property type that is declared. The compiler will enforce this, and you can't omit the variable type for computed properties.

Since the value of the property is determined at the time of access, it follows that computed properties are read-only:

  
class Person {
//...
func fullName() -> String {
return "\(givenName) \(middleName) \(familyName))"
}
//...
}

Objects can do work based on the information they contain, and this work can be defined in methods. Methods are just functions that are contained within classes and have access to all the object's properties. All the abilities of a function are available, which we explored in the last recipe, including optional inputs and outputs, default parameter values, and parameter overloading:

  
final class Friend: Person {
var whereWeMet: String?
//...
}

Having defined a Person object, we want to extend the concept of Person to define a friend. A friend is also a person, so it stands to reason that anything a Person object can do, a Friend object can also do. We model this inherited behavior by defining Friend as a subclass of Person. We define the class that our Friend class inherits from after the class name, separated by :.

By inheriting from Person, our Friend object inherits all the properties and methods from its superclass. We can add any extra functionality we require--in this case, a property holding details of where we met this friend.

The final prefix tells the compiler that we don't intend for this class to be subclassed; it is the final class in the inheritance hierarchy. This allows the compiler to make some optimizations as we know it won't be extended:

final class Friend: Person { 
//...
override var displayString: String {
return "\(super.displayString) - \(whereWeMet ?? "Don't know where we met")"
}
}

In addition to implementing new functionalities, we can override functionalities from the superclass using the override keyword. In the preceding code, we override the displayString computed property from Person as we want to add the "where we met" information. Within the computed property, we want to get the superclass's implementation; we do this by referencing super and ., and then referencing the property. We can do the same to access the superclass's methods:

final class Family: Person {
let relationship: String
init(givenName: String, middleName: String, familyName: String = "Moon", relationship: String) {
self.relationship = relationship
super.init(givenName: givenName, middleName: middleName, familyName: familyName)
}
//...
}

Our Family class also inherits from Person, and we want to add a relationship property, which we want to form part of the initialization, so we can declare a new init that also takes a relationship value.

There's more...

Class objects are reference types that refer to the way they are stored and referenced internally. To see how these reference type semantics work, consider the following code:

class MovieReview { 
let movieTitle: String
var starRating: Int // Rating out of 5
init(movieTitle: String, starRating: Int) {
self.movieTitle = movieTitle
self.starRating = starRating
}
}

// Write a review
let shawshankReviewOnYourWebsite = MovieReview(movieTitle: "Shawshank Redemption", starRating: 3)

// Post it to social media
let reviewLinkOnTwitter = shawshankReviewOnYourWebsite
let reviewLinkOnFacebook = shawshankReviewOnYourWebsite

print(reviewLinkOnTwitter.starRating) // 3
print(reviewLinkOnFacebook.starRating) // 3

// Reconsider my review
shawshankReviewOnYourWebsite.starRating = 5

// The change visible from anywhere with a reference to the object
print(reviewLinkOnTwitter.starRating) // 5
print(reviewLinkOnFacebook.starRating) // 5

We created a review object and assigned that review to two separate constants. As an object is a reference type, it is a reference to the object that is stored in the constant, rather than a new copy of the object. Therefore, when we reconsider our review and rightly give The Shawshank Redemption five stars, we are changing the underlying object, and all references that access that underlying object will see that the starRating property has changed.

See also

  • Further information about classes can be found in Apple's documentation of the Swift language at http://swiftbook.link/docs/classes-and-structures.
  • In Chapter 8, Performance and Responsiveness in Swift, we will examine reference semantics in more detail, and see how it affects performance.
You have been reading a chapter from
Swift 4 Programming Cookbook
Published in: Sep 2017 Publisher: Packt ISBN-13: 9781786460899
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at AU $19.99/month. Cancel anytime}