Getters and Setters Described Using Letters

Values are associated with classes, structs and enums using properties. Properties can come in two forms, the simplest being a stored property. Thankfully Swift makes life a little simpler than in the Objective-C world by doing away with instance variables. In Swift the functionality of Objective-C iVars and properties are brought together into a single property declaration.  This comes as either a constant (let) or variable (var) and can be assigned a default value where they’re declared, or thair value can be set or modified at initialization.

Note: Only classes and structs can contain stored properties. Sorry Enums, no stored properties for you!

Computed Properties

This focus of this post is on the second form of properties: computed properties. These are like holding tanks but instead of containing a specific value they contain a set of instructions for what to do when a new value is assigned, and another set of instructions for when the value is used. As the name would suggest what goes into and out of the holding tank is computed using other variables or constants.

class AudioFileData {
    
    var fileLength: Int = 0   // length in seconds
    var sampleRate: Int = 0   // sample rate in Hertz
    var wordLength: Int = 0   // word length in bits
    var channels: Int = 0     // number of audio channels
    
    init (fileLength: Int, sampleRate: Int, wordLength: Int, channels: Int) {
        self.fileLength = fileLength
        self.sampleRate = sampleRate
        self.wordLength = wordLength
        self.channels = channels
    }
    
    var fileSize: Double {
        get {
            return Double(fileLength * sampleRate * wordLength * channels) / 8
        }
        set {
            self.fileLength = (Int)(newValue * 8) / (sampleRate * wordLength * channels)
        }
    }
}

let audioFile = AudioFileData(fileLength: 60, sampleRate: 44100, wordLength: 24, channels: 2)

audioFile.fileSize  // 15876000 bytes

// set the file length based on the file size

audioFile.fileSize = 31752000
audioFile.fileLength   // 120 seconds

The above example shows a class that represents data for an audio file. The class has 4 stored properties that are initialized when an audio file object is created. fileSize is a computed property that uses the stored properties to determine the size of the file. The is done with the calculation provided in the getter. (Note, the value is divided by 8 because there are 8 bits to 1 byte). Whenever a new value is assigned to fileSize the setter code block is executed which computes a new value for the file length (+ updates the fileLength). Pretty cool.

In the setter the value for the new file size is accessed through the default keyword newValue. However, it’s possible to assign a custom name by adding it in parentheses after the keyword set:

set (newFileSize) {
    self.fileLength = (Int)(newFileSize * 8) / (sampleRate * wordLength * channels)
}

Note: a getter must be provided, however it’s not mandatory to provide a setter. Computed properties with a getter only are known as read only computed properties.

Using computed properties can be a powerful way to make your code more concise and declarative in nature. Instead of writing a lot of similar code every time a value is changed or accessed, these tasks can be grouped inside getters and setters.

Property Observers

Now that we’ve got a handle on getters and setters in computed properties let’s take a quick look at a useful feature of stored properties. Stored properties have the ability to execute blocks of code similar to get and set in computed properties. With stored properties the keywords willSet and didSet can be used to fire off a block of code whenever the property’s value is changed, these are known as property observers.

  • willSet: this is called before the new value is stored. Within a willSet statement by default the new value of the property can be accessed using the keyword newValue (just like within a computed property setter)
  • didSet: this is called immediately after the new value is stored. Within a didSet statement by default the old value (that’s being replaced) can be accessed using the keyword oldValue . As with computed properties a custom name for the old value can be provided in parentheses after the didSet keyword
let minimumBarNumber = -2

class BarCounter {
    
    var barNumber: Int = 0 {
        willSet(newBarNumber) {
            print("Playhead is about to move to bar \(newBarNumber)")
        }
        didSet {
            if barNumber < minimumBarNumber {
                print("Playhead moved to bar number: \(barNumber - oldValue)")
            } else {
                print("Playhead out of range")
                // handle error with code here
            }
            
        }
    }
}

// implementation

let barCounter = BarCounter()

barCounter.barNumber = 5

// Playhead is about to move to bar 5
// Playhead moved to bar number: 5

barCounter.barNumber = 21

// Playhead is about to move to bar 21
// Playhead moved to bar number: 16

Note: because property observers allow for custom code for setters they can only be used on variables, not constants.

It tends to be more common to see didSet in Swift code, as opposed to willSet. It can be particularly useful for triggering other function, for example when downloading an image inside a view controller:

var imageURL: NSURL? {
    didSet {
        downloadImage()
    }
}

func downloadImage() {
    guard let url = imageURL else { return }
    
    if let imageData = NSData(contentsOfURL: url) {
        image = UIImage(data: imageData)
    }  
}

Now whenever the imageURL is assigned a value it calls the downloadImage function through the didSet statement. Inside the downloadImage function a new value for image is assigned, where it says image = UIImage(data: imageData).  By using a setter for image we could automatically update the imageViewImage property.

var imageView = UIImageView()

var image: UIImage? {
    get {
        return imageView.image
    }
    set (newImage) {
        imageView.image = newImage
    }
}

So by using property observers and computed properties it's possible to trigger a series of events whenever a value is assigned or modified. In the example above this chain is started when a new value is assigned to the NSURL for the image.

Find a playground with these examples on GitHub here.

If you've found the post helpful please share with the links below!

For the latest updates + blog posts follow me on twitter here.

Leave a Reply