Getting to Know Enum, Struct and Class Types in Swift

Learn all about enums, structs, and classes in Swift, including value vs reference semantics, dynamic member lookup, and protocol conformance. By Adam Rush.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Why Even Use a Class?

Given the above downsides, you might wonder why you would ever want to use a class.

For starters, they allow you to adopt mature and battle-tested frameworks like Cocoa and Cocoa Touch.

Additionally, classes do have more important uses. For example, a large memory-hogging, expensive-to-copy object is a good candidate for wrapping in a class. Classes can model an identity well. You may have a situation in which many views are displaying the same object. If that object is modified, all of the views also reflect changes in the model. With a value type, synchronizing updates can become an issue.

In short, classes are helpful anytime reference versus value semantics come into play.

Check out this two-part tutorial on the subject: Reference vs. Value Types in Swift.

Implementing Computed Properties

All named model types let you create custom setters and getters that don’t necessarily correspond to a stored property.

Suppose you want to add a diameter getter and setter to your Circle model. It’s easy to implement it in terms of the existing radius property.

Add the following code to the end of your playground:

extension Circle {
  var diameter: Double {
    get {
      return radius * 2
    }
    set {
      radius = newValue / 2
    }
  }
}

This implements a new computed property that is purely based on the radius. When you get the diameter, it returns the radius doubled. When you set the diameter, it sets the radius to the value divided by two. Simple!

More often than not, you only want to implement a special getter. In this case, you don’t have to include the get {} keyword block and can just specify the body. Perimeter and area are good use cases for this.

Add the following to the Circle extension you just added:

// Example of getter-only computed properties
var area: Double {
  return radius * radius * Double.pi
}
var perimeter: Double {
  return 2 * radius * Double.pi
}

Unlike classes, struct methods are not allowed to modify, or mutate, stored properties by default, but they can if you declare them as mutating.

For example, add the following to the Circle extension:

func shift(x: Double, y: Double) {
  center.x += x
  center.y += y
}

This tries to define a shift() method on circle, which moves the circle in space — i.e., it changes the center point.

But this generates the following error on the two lines, which increment the center.x and center.y properties.

// ERROR: Left side of mutating operator has immutable type ‘Double'

This can be fixed by adding the mutating keyword, like so:

mutating func shift(x: Double, y: Double) {
  center.x += x
  center.y += y
}

This tells Swift that it’s OK that your function mutates the struct.

Retroactive Modeling and Type Constraining

One of the great features of Swift is retroactive modeling. It lets you extend the behavior of a model type even if you don’t have the source code for it.

Here’s a use case: Suppose you’re a user of the SVG code and you want to add an area and perimeter to Rectangle just like Circle.

To see what this all means, add this to your playground:

extension Rectangle {
  var area: Double {
    return size.width * size.height
  }
  var perimeter: Double {
    return 2 * (size.width + size.height)
  }
}

This adds an extension to add area and perimeter to an existing model, and, now, you’ll formalize these methods into a new protocol.

Add this to your playground:

protocol ClosedShape {
  var area: Double { get }
  var perimeter: Double { get }
}

That gives you an official protocol.

Next, you’ll tell Circle and Rectangle to conform to this protocol retroactively by adding the following to your playground:

extension Circle: ClosedShape {}
extension Rectangle: ClosedShape {}

You can also define a function that, for example, computes the total perimeter of an array of models (any mix of structs, enums or classes) that adopt the ClosedShape protocol.

Add the following to the end of the playground:

func totalPerimeter(shapes: [ClosedShape]) -> Double {
  return shapes.reduce(0) { $0 + $1.perimeter }
}

totalPerimeter(shapes: [circle, rectangle])

This uses reduce to calculate the sum of perimeters. You can learn more about how it works in An Introduction to Functional Programming.

Where to Go From Here?

In this tutorial, you learned about enum, struct and class — the named model types of Swift.

All three have key similarities: They provide encapsulation, can have initializer methods, can have computed properties, can adopt protocols, and can be modeled retroactively.

I hope you have enjoyed this whirlwind tour of the named model types in Swift. If you’re looking for a challenge, consider building a more complete version of the SVG rendering library. You’re off to a good start!

As always, if you have questions or insights you would like to share, please use the forums below!