Sometimes, we want to have more control over the values that are set to properties and retrieved from them, and we can take advantage of getters and setters to do so. In fact, we can combine a getter and a setter, which generate a computed property and a related property that stores the computed value, and access protection mechanisms to prevent the user from making changes to the related property and force him to always use the computed property.
The superhero's sneakers might change over time. However, we always have to make sure that the sneakers' name is an uppercase string. We can define a sneakers
property with a getter method that always converts the string value to an uppercase string and stores it in a private sneakersField
property.
Whenever we assign a value to the sneakers
property, the setter method is called under the hood with the value to be assigned as an argument. Whenever we specify the sneakers
property in any expression, the getter method is called under the hood to retrieve the actual value. The following lines show a new version of the SuperHero
class that adds a sneakers
calculated property.
The code file for the sample is included in the swift_3_oop_chapter_03_08
folder:
import Foundation public class SuperHero { public let name: String public var birthYear: Int private var sneakersField = "NOT SPECIFIED" public var sneakers: String { get { return sneakersField } set { sneakersField = newValue.localizedUppercase } } public var age: Int { get { return currentYear - birthYear } set { birthYear = currentYear - newValue } } public var currentYear: Int { let date = Date() let calendar = Calendar.current let year = calendar.component(.year, from: date) return year } init(name: String, birthYear: Int, sneakers: String) { self.name = name self.birthYear = birthYear self.sneakers = sneakers } }
The new version of the class is declared as public class
; therefore, we declared name
, birthYear
, and sneakers
as public
properties. We also declared both the age
and currentYear
properties as public
. This way, when someone creates instances of the SuperHero
class outside the source file that declares it, he will be able to access the public members, that is, the public
properties we have declared. However, the code declares the sneakersField
property as a private property; therefore, only the code included in the SuperHero
class will be able to access this property. This way, the sneakersField
property will be hidden for those who create instances of the SuperHero
class.
In Swift 3, a class declared with the public
access modifier is accessible outside the defining module. However, a class declared as public
can only be subclassed in the same module where it is defined. If we want to be able to access and subclass a class outside a module, we must use the new open
access modifier. A class declared with the open
access modifier in Swift 3 is equivalent to a class declared with the public
access modifier in the earlier Swift versions.
When we declare a member of a class with the public
access level, the member will be accessible but not overridable outside the defining module. If we want the member to be both accessible and overridable outside of the defining module, we must use the new open
access modifier. A member declared with the open
access modifier in Swift 3 is equivalent to a member declared with the public
access modifier in the earlier Swift versions.
When we declare a member of a class with the private
access level, the member will be accessible only within the enclosing declaration, that is, within the class. If we want the member to be accessible only in the defining module, we must use the new fileprivate
access modifier. A member declared with the fileprivate
access modifier in Swift 3 is equivalent to a member declared with the private
access modifier in the previous Swift versions. The private
access modifier in Swift 3 is more private than in the previous versions and allows us to declare members that we only want to use within the code of the class that declares them.
When we declare the sneakersField
private property, we will specify its initial value as "NOT SPECIFIED"
and not declare its type because the type-inference mechanism determines that it is of type String
, based on the initial value. The following line of code is equivalent to the second line of code. We used the first line for the declaration to simplify our code and avoid redundancy whenever possible:
private var sneakersField = "NOT SPECIFIED" private var sneakersField: String = "NOT SPECIFIED"
The initializer for the class added a new argument that provides an initial value for the new sneakers
property. The next lines create two instances of the SuperHero
class, assign a value to the sneakers
computed property, and then use the print
function to display the value of the property in the Playground. In both cases, we will initialize sneakers
with a string that the setter method converts to an uppercase string. Thus, when we print the values returned by the getter method, the Playground will print the uppercase string that is stored in the sneakerField
private property. 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_08
folder:
var superBoy = SuperHero(name: "Super-Boy", birthYear: 2008, sneakers: "Running with Swift 3") print(superBoy.sneakers) var superGirl = SuperHero(name: "Super-Girl", birthYear: 2009, sneakers: "Jumping Super Girl") print(superGirl.sneakers)
Note the number of times each property's getter and setter methods are executed in the Playground. In this case, the sneakers
getter method is executed two times, as shown in the following screenshot:
18.224.57.16