Generating computed properties with setters and getters

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 
      } 
    } 

Tip

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

Generating computed properties with setters and getters

Tip

A computed property with a getter method and without a setter method is known as 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:

Generating computed properties with setters and getters

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 
      } 
    } 

Tip

Declarations that use the let keyword cannot be computed properties; therefore, we must always use the var keyword when we declare computed properties, even when they are get-only properties.

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:

Generating computed properties with setters and getters

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 
    } 

Tip

We only have to specify the get clause when we provide a set clause for the property.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.97.48