As previously explained, we don't want a user of our superhero class to be able to change a superhero's birth year after an instance is initialized because the superhero won't be born again at a different date. In fact, we want to calculate the superhero's age and make it available to users. We use an approximated age in order to keep the focus on the properties and don't complicate our lives with the manipulation of complete dates and the Date
class.
We can define a property called age
with a getter method but without a setter method; that is, we will create a read-only computed property. This way, it is possible to retrieve the superhero's age, but we cannot change it because there isn't a setter defined for the property. The getter method returns the result of calculating the superhero's age based on the current year and the value of the birthYear
stored property.
The following lines show the new version of the SuperHero
class with the new age
calculated read-only property. It is necessary to import Foundation
to use the Date
and Calendar
classes. Note that the code for the getter method appears after the property declaration with its type and the get
keyword. All the lines enclosed in curly brackets after the get
keyword define the code that will be executed when we request the value for the age
property. The method creates a new instance of the Date
class, date
, and retrieves the current calendar, Calendar.current
. Then, the method retrieves the year component for date
and returns the difference between the current year and the value of the birthYear
property. The code file for the sample is included in the swift_3_oop_chapter_03_04
folder:
import Foundation
class SuperHero {
let name: String
let birthYear: Int
var age: Int {
get {
let date = Date()
let calendar = Calendar.current
let year = calendar.component(.year, from: date)
return year - birthYear
}
}
init(name: String, birthYear: Int) {
self.name = name
self.birthYear = birthYear
}
}
We must use the var
keyword to declare computed properties, such as the previously defined age
computed property.
Swift 3 removed the NS
prefix from many classes and made the APIs simpler. Instead of working with NSDate
, we work with the Date
class. Instead of working with NSCalendar
, we work with the Calendar
class. In addition, the methods and the properties have shorter names that do not repeat unnecessary words.
The next lines create an instance that initializes the values of the two immutable stored properties and then use the print
function to display the value of the age
calculated property in the Playground. Enter the lines after the code that creates the new version of the SuperHero
class. Then, a line of code tries to assign a new value to the age
property and fails to do so because the property doesn't declare a setter method. We will see a similar error message in the Swift REPL and in the Swift Sandbox. The code file for the sample is included in the swift_3_oop_chapter_03_05
folder:
var antMan = SuperHero(name: "Ant-Man", birthYear:
1975)
print(antMan.age)
var ironMan = SuperHero(name: "Iron-Man", birthYear:
1982)
print(ironMan.age)
antMan.age = 32
The Playground displays the following error message for the last line, as shown in the next screenshot:
Cannot assign to property: 'age' is a get-only property
Later, we will decide that it would be nice to allow the user to customize a superhero and allow it to change either its age or birth year. We can add a setter method to the age
property with code that calculates the birth year based on the specified age and assigns this value to the birthYear
property. Of course, the first thing we need to do is replace the let
keyword with var
when we define the birthYear
stored property as we want it to become a mutable property.
The following lines show the new version of the SuperHero
class with the new age
calculated property. Note that the code for the setter method appears after the code for the getter method within the curly brackets that enclose the getter and setter declarations. We can place the setter method before the getter method. All the lines enclosed in curly brackets after the set
keyword define the code that will be executed when we assign a new value to the age
property, and the implicit name for the new value is newValue
. So, the code enclosed in curly brackets after the set
keyword receives the value that will be assigned to the property in the newValue
argument. As we didn't specify a different name for the implicit argument, we can access the value using the newValue
argument. Note that we don't see the argument name in the code; this is the default convention in Swift. The code file for the sample is included in the swift_3_oop_chapter_03_05
folder:
import Foundation class SuperHero { let name: String var birthYear: Int var age: Int { get { let date = Date() let calendar = Calendar.current let year = calendar.component(.year, from: date) return year - birthYear } set { let date = Date() let calendar = Calendar.current let year = calendar.component(.year, from: date) birthYear = year - newValue } } init(name: String, birthYear: Int) { self.name = name self.birthYear = birthYear } }
The setter method creates a new instance of the Date
class, date
, and retrieves the current calendar, calendar
. Then, the method retrieves the year component for date
and assigns the result of the current year, year
, minus the new age value that is specified, newValue
, to the birthYear
property. This way, the birthYear
property will save the year in which the super hero was born based on the received age value.
The next lines create two instances of the SuperHero
class, assign a value to the age
computed property, and then use the print
function to display the value of both the age
calculated property and the birthYear
stored property in the Playground. Enter the lines after the code that creates the new version of the SuperHero
class. The code file for the sample is included in the swift_3_oop_chapter_03_05
folder:
var antMan = SuperHero(name: "Ant-Man", birthYear:
1975)
print(antMan.age)
var ironMan = SuperHero(name: "Iron-Man", birthYear:
1982)
print(ironMan.age)
antMan.age = 32
print(antMan.age)
print(antMan.birthYear)
ironMan.age = 45
print(ironMan.age)
print(ironMan.birthYear)
As a result of assigning a new value to the age
computed property, its setter method changes the value of the birthYear
stored property, as shown in the following screenshot:
Both the getter and setter methods use the same code to retrieve the current year. We can add a get-only property that retrieves the current year and call it from both the getter and setter methods for the age
computed property. We will declare the function as a get-only property for the SuperHero
class. We know that this class isn't the best place for this get-only property as it would be better to have it added to a date-related class, such as the Date
class. We will be able to do so later after you learn additional things.
The following lines show the new version of the SuperHero
class with the new currentYear
calculated property. Note that the code for both the setter and getter methods for the age property is simpler because they use the new currentYear
calculated property instead of repeating the code. The code file for the sample is included in the swift_3_oop_chapter_03_06
folder:
import Foundation
class SuperHero {
let name: String
var birthYear: Int
var age: Int {
get {
return currentYear - birthYear
}
set {
birthYear = currentYear - newValue
}
}
var currentYear: Int {
get {
let date = Date()
let calendar = Calendar.current
let year = calendar.component(.year, from: date)
return year
}
}
init(name: String, birthYear: Int) {
self.name = name
self.birthYear = birthYear
}
}
The next lines create two instances of the SuperHero
class, assign a value to the age computed property, and then use the print
function to display the value of both the age
calculated property and the birthYear
stored property in the Playground. Enter the lines after the code that creates the new version of the SuperHero
class. The code file for the sample is included in the swift_3_oop_chapter_03_06
folder:
var superBoy = SuperHero(name: "Super-Boy", birthYear: 2008) print(superBoy.age) var superGirl = SuperHero(name: "Super-Girl", birthYear: 2009) print(superGirl.age) superBoy.age = 9 print(superBoy.age) print(superBoy.birthYear) superGirl.age = 8 print(superGirl.age) print(superGirl.birthYear) print(superBoy.currentYear) print(superGirl.currentYear)
Note the number of times each property's getter and setter methods are executed in the Playground. In this case, the currentYear
getter method is executed eight times, as shown in the following screenshot:
The recently added currentYear
computed property is get-only; therefore, we won't add a set
clause to it. We can simplify the code that declares this property by omitting the get
clause, as shown in the following lines.
The code file for the sample is included in the swift_3_oop_chapter_03_07
folder:
var currentYear: Int { let date = Date() let calendar = Calendar.current let year = calendar.component(.year, from: date) return year }
18.191.97.48