Structs, What Are They Good For?

There are four scrumptious flavors of named types in Swift: protocols, enumerations, structures and classes – all custom data types that can be created and given a name. Of these, enums, classes and structs can be used to model data, protocols being used to tell the others how to behave.

The choice between using a struct and a class has some interesting nuances. The hope of this post is to clear up these murky waters leaving us in a golden shower of data model clarity!

Hit me with your fact stick…OW!

1) Structs Are Ideal For Simple Data Models

Below shows a model to represent an audio equalizer (at least in principle). It includes a simple range of values for:

  • Frequency(Hz) that the eq band is effecting
  • Q: This describes how wide the effect of the eq band is
  • Gain(db) of how much the band is boosting or cutting
struct EqBandSetting {
    var frequency: Double
    var Q: Double   
    var gain: Double
}

// implementation

class Equalizer {
    
    // properties with default values 
    
    var bass: EqBandSetting = EqBandSetting(frequency: 250.0, Q: 1.0, gain: 0.0)
    var mid: EqBandSetting = EqBandSetting(frequency: 2000.0, Q: 1.0, gain: 0.0)
    var treble: EqBandSetting = EqBandSetting(frequency: 6000.0, Q: 1.0, gain: 0.0)
    
    // initializer with default values 
    
    init(){}
    
    // initializer with custom values
    
    init(bass: EqBandSetting, mid: EqBandSetting, treble: EqBandSetting) {
        self.bass = bass
        self.mid = mid
        self.treble = treble
    }
}

// Declare setting for a guitar eq
let eqBass = EqBandSetting(frequency: 250.0, Q: 0.35, gain: -5.8)
let eqMid = EqBandSetting(frequency: 4000.0, Q: 0.2, gain: -2.1)
let eqTreble = EqBandSetting(frequency: 8000.0, Q: 1.1, gain: -7.0)

let guitarEq = Equalizer(bass: eqBass, mid: eqMid, treble: eqTreble)

Some other common examples of simple structs would be a coordinate system, or the dimensions of a shape.

 

2) Structs Can’t Use Inheritance

Structs are always destined to be lonely little data model sausages. They, unlike classes, cannot be subclassed.  A class can inherit from a superclass but with structs there’s no such thing as a superstruct.

If the data being modeled can be subcategorized classes would be a better approach. In the example above the Equalizer class could potentially inherit from an “Effect” class.

// An Effects superclass might be setup like this

enum Category {
    case Eq
    case Compressor
    case Reverb
    case Modulation
    case Other
}

class Effect {
    var inputs: Int
    var outputs: Int
    var category: Category
    
    init(inputs: Int, outputs: Int, category: Category) {
        self.inputs = inputs
        self.outputs = outputs
        self.category = category
    }
}

 

3) Structs Are Value Types

This means that a copy is made of the values within the struct when it’s assigned to a variable or constant, or when it’s passed into a function. Think of it as a new independent entity in the same way that when a new string variable is created it acts like a new holding tank for the data, independent of other variables or constants.

In example 1 we created 3 instances of the EqBandSetting structure, each with its own set of values. let’s see what happens when a struct is copied:

eqBass.frequency  // 250.0 - the original bass frequency setting

// assign it to a new variable
var eqBassCopy = eqBass

// mutate the copy
eqBassCopy.frequency = 375

eqBass.frequency // 250 - the original frequency is unchanged
eqBassCopy.frequency // 375 - the copy has mutated

Here’s the RUB: the only flavor of named data model that isn’t a value type is a class. Enums, structs and the types derived from these like Int, Double, String, Array and Dictionary are all value types (the values are copied).

Classes on the other hand are reference types. When a class is assigned to a variable or constant, or when it’s passed into a function a reference to the original instance is kept.

// Here's the instance of the guitar eq class created earlier
guitarEq

guitarEq.bass.gain // -5.8


// Assign original instance to new variable
var anotherGuitarEq = guitarEq

// Now change a property within anotherGuitarEq
anotherGuitarEq.bass.gain = -7.5

guitarEq.bass.gain // -7.5 - so the original instance is ALSO mutated


// Let's tweak the Q of the treble band
anotherGuitarEq.treble.Q = 0.2

guitarEq.treble.Q // 0.2 - The original instance is ALSO mutated


// It works BOTH ways. For example, changing property on original instance

guitarEq.mid.frequency = 2500.0

anotherGuitarEq.mid.frequency // 2500.0 - Property also mutated on other instance

 

4) Structs can be Safer Than Classes

Structs, unlike classes don’t hold references to the data, they hold the values themselves. This can be safer if the extra jazz that comes with classes is not required. This is because it’s easier to keep track of what and where the values get mutated. They also don’t have reference counts (like classes) so will avoid potential issues with memory leaks. You don’t have to worry about structs being stuck in memory after they’re needed.

 

5) Structs can be Faster Than Classes

Classes allocate memory on the heap. Structs allocate memory on the stack. And so wrote the Computer Science Gods. Essentially a heap is a more complicated data structure which takes more work to manage than its simpler stack counterpart.

A heap has to be more concerned with the order of the data. Items in a stack have to be added and removed in order, like a tube of pim’s cookies(US) or digestive biscuits(UK). Could someone bring me a tea?….ok.

There are some amazing posts on StackOverflow elaborating on this topic. Cliff note: StackOverflow jedi Khanh Nguyen measured structs to be up to 37000 times faster than classes in certain circumstances! Now that’s ludicrous speed!! #Spaceballs #SpaceballsSequelReally?

 

6) Structs can put the “M” in MVC

A key concept to the Model-View-Controller design pattern is in abstracting the data driving the app away from the other parts of the code. The struct in the example below could be stored in a separate Swift file representing the model layer. For example; this file could be called “PluginModel”.

// Presets for the EQ stored in separate "PluginModel.swift" file
struct EqPreset {
    let highBoost = Equalizer(
        bass: EqBandSetting.init(frequency: 250.0, Q: 1.0, gain: 0.0),
        mid: EqBandSetting.init(frequency: 1000.0, Q: 1.0, gain: 0.0),
        treble: EqBandSetting.init(frequency: 6000.0, Q: 0.6, gain: 4.5))
    
    let highLowCut = Equalizer(
        bass: EqBandSetting.init(frequency: 220.0, Q: 0.6, gain: -24.0),
        mid: EqBandSetting.init(frequency: 1000.0, Q: 1.0, gain: 0.0),
        treble: EqBandSetting.init(frequency: 6000.0, Q: 0.7, gain: -48.0))
    
    let warmer = Equalizer(
        bass: EqBandSetting.init(frequency: 220.0, Q: 1.0, gain: 0.0),
        mid: EqBandSetting.init(frequency: 500.0, Q: 0.9, gain: 3.0),
        treble: EqBandSetting.init(frequency: 5500.0, Q: 0.7, gain: -4.0))
}


// In the view controller file create instance of EqPreset

let eqPreset = EqPreset()

let selectedPreset = eqPreset.warmer  // assigning the "warmer" preset

Caution: if the project has to interact with Objective-C code. Structs in Swift don’t inherit from NSObject so they wont bridge nicely to Objective-C. Another ramification of this is that structs can’t be stored in NSUserDefault. This means that to store persistent app data with structs you have to don the big-boy-pants and delve into CoreData.

 

7) Structs Have a Memberwise Initializer

Notice how in the EqBandSetting struct there is no initialization method, yet it was still possible to initialize an instance of it with the line “let eqBass = EqBandSetting…”.

To save you the trouble of creating an initializer Swift gives you the option of not declaring one. It provides a memberwise initizalizer that contains all properties within the struct – nice!

In Summary

Structs are a useful complex data type for grouping properties and then passing them around as a single value. They don’t have all the sass of a class but they avoid some of the potential pitfalls of a reference type.

Leave a Reply