Chapter 13: Classes

大綱

Creating classes

  • Unlike a struct, a class doesn’t provide a memberwise initializer automatically — which means you must provide it yourself if you need it.

class Person {
  var firstName: String
  var lastName: String

  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }

  var fullName: String {
    return "\(firstName) \(lastName)"
  }
}

let john = Person(firstName: "Johnny", lastName: "Appleseed")

Reference types

  • Classes are reference types, so a variable of a class type doesn’t store an actual instance — it stores a reference to a location in memory that stores the instance.

The heap vs. the stack

  • When you create a reference type such as class, the system stores the actual instance in a region of memory known as the heap. Instances of a value type such as a struct resides in a region of memory called the stack。

  • Stack: The system uses the stack to store anything on the immediate thread of execution; it is tightly managed and optimized by the CPU.

    • it’s very efficient, and thus quite fast

  • Heap: The system uses the heap to store instances of reference types. The heap is generally a large pool of memory from which the system can request and dynamically allocate blocks of memory.

    • The heap doesn’t automatically destroy its data like the stack does; additional work is required to do that

Working with references

  • sharing among class instances results in a new way of thinking when passing things around

var homeOwner = john
john.firstName = "John" // John wants to use his short name!
john.firstName // "John"
// homeOwner的firstName會跟著john的firstName變動
homeOwner.firstName // "John"

Object identity

  • In Swift, the === operator lets you check if the identity of one object is equal to the identity of another

john === homeOwner // true

let imposterJohn = Person(firstName: "Johnny", lastName: "Appleseed")

john === homeOwner // true
john === imposterJohn // false
imposterJohn === homeOwner // false
// Create fake, imposter Johns. Use === to see if any of these imposters are our real John.
var imposters = (0...100).map { _ in
  Person(firstName: "John", lastName: "Appleseed")
}

// Equality (==) is not effective when John cannot be identified by his name alone
imposters.contains {
  $0.firstName == john.firstName && $0.lastName == john.lastName
} // true

// Check to ensure the real John is not found among the imposters.
imposters.contains {
  $0 === john
} // false

Methods and mutability

struct Grade {
  let letter: String
  let points: Double
  let credits: Double
}

class Student {
  var firstName: String
  var lastName: String
  var grades: [Grade] = []
  var credits = 0.0

  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  // 可以直接變更grades的內容,不用像strunt需要加mutating
  func recordGrade(_ grade: Grade) {
    grades.append(grade)
    credits += grade.credits
  }
}

let jane = Student(firstName: "Jane", lastName: "Appleseed")
let history = Grade(letter: "B", points: 9.0, credits: 3.0)
var math = Grade(letter: "A", points: 16.0, credits: 4.0)

jane.recordGrade(history)
jane.recordGrade(math)

Mutability and constants

  • 透過let, var來進行class的mutation限制。

Any individual member of a class can be protected from modification through the use of constants, but because reference types are not themselves treated as values, they are not protected as a whole from mutation.

// Error: jane is a `let` constant
jane = Student(firstName: "John", lastName: "Appleseed")

var jane = Student(firstName: "Jane", lastName: "Appleseed")
jane = Student(firstName: "John", lastName: "Appleseed")

Understanding state and side effects

  • If you update a class instance with a new value every reference to that instance will also see the new value

  • class instances are mutable, you need to be careful about unexpected behavior around shared references.

Extending a class using an extension

  • classes can be re-opened using the extension keyword to add methods and computed properties”

    Excerpt From: By Ray Fix. “Swift Apprentice.” Apple Books.

Values vs. objects

  • An object is an instance of a reference type, and such instances have identity meaning that every object is unique. No two objects are considered equal simply because they hold the same state.

    • use === to see if objects are truly equal and not just containing the same state.

    • instances of value types, which are values, are considered equal if they are the same value.

    • For example: A delivery range is a value, so you implement it as a struct. A student is an object so you implement it as a class. In non-technical terms, no two students are considered equal, even if they have the same name!

Speed

  • structs rely on the faster stack while classes rely on the slower heap.

Minimalist approach

  • If your data will never change or you need a simple data store, then use structures.

  • If you need to update your data and you need it to contain logic to update its own state, then use classes.

  • it’s best to begin with a struct. If you need the added capabilities of a class sometime later, then you just convert the struct to a class.

Structures vs. classes recap

  • Structures

    • Useful for representing values.

    • Implicit copying of values.

    • Becomes completely immutable when declared with let.

      Fast memory allocation (stack)

  • Classes

    • Useful for representing objects with an identity.

    • Implicit sharing of objects.

    • Internals can remain mutable even when declared with let.

    • Slower memory allocation (heap).

Key points

  • Like structures, classes are a named type that can have properties and methods.

  • Classes use references that are shared on assignment.

  • Class instances are called objects.

  • Objects are mutable.

  • Mutability introduces state, which adds complexity when managing your objects.

  • Use classes when you want reference semantics; structures for value semantics.

Last updated

Was this helpful?