Chapters

Hide chapters

Swift Apprentice: Fundamentals

First Edition · iOS 16 · Swift 5.7 · Xcode 14.2

Section III: Building Your Own Types

Section 3: 9 chapters
Show chapters Hide chapters

13. Methods
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In Chapter 12, “Properties”, you learned about properties, constants and variables that are part of structures. Methods, as you’ve already seen, are functions that reside inside a structure.

In this chapter, you’ll take a closer look at methods and initializers. As with properties, you’ll begin to design more complex structures. The things you learn in this chapter will apply to methods across all named types, including classes and enumerations, which you’ll see in later chapters.

Method Refresher

Remember Array.removeLast()? It pops the last item off an instance of an array:

var numbers = [1, 2, 3]
numbers.removeLast()
numbers // [1, 2]

1 2 3

Methods like removeLast() help you control the data in the structure.

Comparing Methods to Computed Properties

With computed properties, you saw in Chapter 12, “Properties”, that you could run code from inside a structure. That sounds a lot like a method. What’s the difference? It comes down to style, but there are a few helpful thoughts to help you decide. Properties hold values that you can get and set while methods perform work. Sometimes this distinction gets fuzzy when a method’s sole purpose is to return a single value.

Lauy a sekxol? Wut Qentelul Mqurujlz. Upcickuxu yemkiheyiem oc SX udvunq? Ti Woj Razdag. Ku Seqqapev Xrufesxz. Jcoows I imhqociwn rqex lipua roxrib or o gimzak aw ad a jojpukaz zmovutxc?

Turning a Function Into a Method

To explore methods and initializers, you will create a simple model for dates called SimpleDate. Be aware that Apple’s Foundation framework contains a robust, production-ready Date class that correctly handles all of the subtle intricacies of dealing with dates and times. For learning purposes, though, we’ll explore how you might construct SimpleDate to be useful in many contexts.

let months = ["January", "February", "March",
              "April", "May", "June",
              "July", "August", "September",
              "October", "November", "December"]

struct SimpleDate {
  var month: String
}

func monthsUntilWinterBreak(from date: SimpleDate) -> Int {
  months.firstIndex(of: "December")! -
  months.firstIndex(of: date.month)!
}
struct SimpleDate {
  var month: String

  func monthsUntilWinterBreak(from date: SimpleDate) -> Int {
    months.firstIndex(of: "December")! -
    months.firstIndex(of: date.month)!
  }
}
let date = SimpleDate(month: "October")
date.monthsUntilWinterBreak(from: date) // 2

date.monthsUntilWinterBreak() // Error!

Introducing self

You already saw Self (spelled with an uppercase S) in Chapter 12, “Properties”, as a way to access static properties from inside a struct. Now we look at lowercase self. A structure definition (uppercase first letter) is like a blueprint, whereas an instance (lowercase first letter) is a real object. To access the value of an instance, you use the keyword self inside the structure. The Swift compiler passes it into your method as a secret parameter.

// 1
func monthsUntilWinterBreak() -> Int {
  // 2
  months.firstIndex(of: "December")! -
    months.firstIndex(of: self.month)!
}
date.monthsUntilWinterBreak() // 2
months.firstIndex(of: "December")! -
  months.firstIndex(of: month)!

Mini-Exercise

Since monthsUntilWinterBreak() returns a single value and there’s not much calculation involved, transform the method into a computed property with a getter component.

Introducing Initializers

You learned about initializers in Chapter 11, “Structures”, and Chapter 12, “Properties”, but let’s look at them again with your newfound knowledge of methods.

let date = SimpleDate(month: "October")
let date = SimpleDate() // Error!
struct SimpleDate {
  var month: String

  init() {
    month = "January"
  }

  func monthsUntilWinterBreak() -> Int {
    months.firstIndex(of: "December")! -
      months.firstIndex(of: month)!
  }
}
let date = SimpleDate(month: "October") // Error!
let date = SimpleDate()
date.month // January
date.monthsUntilWinterBreak() // 11
init() {
  month = "March"
}
let date = SimpleDate()
date.month // March
date.monthsUntilWinterBreak() // 9

Initializers in Structures

Add a day property to SimpleDate:

struct SimpleDate {
  var month: String
  var day: Int

  init() {
    month = "January"
    day = 1
  }

  func monthsUntilWinterBreak() -> Int {
    months.firstIndex(of: "December")! -
    months.firstIndex(of: month)!
  }
}
let valentinesDay = SimpleDate(month: "February",
                               day: 14) // Error!
init(month: String, day: Int) {
  self.month = month
  self.day = day
}
init() {
  month = "January"
  day = 1
}
let valentinesDay = SimpleDate(month: "February", day: 14)
valentinesDay.month // February
valentinesDay.day // 14

Default Values and Initializers

There is a more straightforward way to make a no-argument initializer.

struct SimpleDate {
  // 1
  var month = "January"
  var day = 1

  //2

  func monthsUntilWinterBreak() -> Int {
    months.firstIndex(of: "December")! -
    months.firstIndex(of: month)!
  }
}
let newYearsDay = SimpleDate()
newYearsDay.month // January
newYearsDay.day // 1

let valentinesDay = SimpleDate(month: "February", day: 14)
valentinesDay.month // February
valentinesDay.day // 14
let octoberFirst = SimpleDate(month: "October")
octoberFirst.month // October
octoberFirst.day // 1

let januaryTwentySecond = SimpleDate(day: 22)
januaryTwentySecond.month // January
januaryTwentySecond.day // 22

Introducing Mutating Methods

Methods in structures cannot change the values of the instance without being marked as mutating. You can imagine a method in the SimpleDate structure that advances to the next day:

mutating func advance() {
  day += 1
}

Type Methods

Like type properties, you can use type methods to access data across all instances. You call type methods on the type itself instead of on an instance. To define a type method, you prefix it with the static modifier.

struct Math {
  // 1
  static func factorial(of number: Int) -> Int {
    // 2
    (1...number).reduce(1, *)
  }
}
// 3
Math.factorial(of: 6) // 720
7! 9 x 2 r 2 p 4 t 7 z 6 }

Mini-Exercise

Add a type method to the Math structure that calculates the n-th triangle number. It will be very similar to the factorial formula, except instead of multiplying the numbers, you add them.

Adding to an Existing Structure With Extensions

Sometimes you want to add functionality to a structure but don’t want to muddy up the original definition. And sometimes, you can’t add the functionality because you don’t have access to the source code.

extension Math {
  static func primeFactors(of value: Int) -> [Int] {
    // 1
    var remainingValue = value
    // 2
    var testFactor = 2
    var primes: [Int] = []
    // 3
    while testFactor * testFactor <= remainingValue {
      if remainingValue % testFactor == 0 {
        primes.append(testFactor)
        remainingValue /= testFactor
      }
      else {
        testFactor += 1
      }
    }
    if remainingValue > 1 {
      primes.append(remainingValue)
    }
    return primes
  }
}
Math.primeFactors(of: 81) // [3, 3, 3, 3]

Keeping the Compiler-generated Initializer Using Extensions

With the SimpleDate structure, you saw that once you added your own init(), the compiler-generated one disappeared. You can keep both if you add your init() to an extension to SimpleDate. The code looks like this:

struct SimpleDate {
  var month = "January"
  var day = 1

  func monthsUntilWinterBreak() -> Int {
    months.firstIndex(of: "December")! -
    months.firstIndex(of: month)!
  }

  mutating func advance() {
    day += 1
  }
}

extension SimpleDate {
  init(month: Int, day: Int) {
    self.month = months[month-1]
    self.day = day
  }
}
let halloween = SimpleDate(month: 10, day: 31)
halloween.month // October
halloween.day // 31

Challenges

Before moving on, here are some challenges to test your knowledge of methods. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.

Challenge 1: Grow a Circle

Given the Circle structure below:

struct Circle {

  var radius = 0.0

  var area: Double {
    .pi * radius * radius
  }

}

Challenge 2: A More Advanced advance()

Here is a naïve way of writing advance() for the SimpleDate structure you saw earlier in the chapter:

let months = ["January", "February", "March",
             "April", "May", "June",
             "July", "August", "September",
             "October", "November", "December"]

struct SimpleDate {
 var month: String
 var day: Int

 mutating func advance() {
   day += 1
 }
}

var date = SimpleDate(month: "December", day: 31)
date.advance()
date.month // December; should be January!
date.day // 32; should be 1!

Challenge 3: Odd and Even Math

Add type methods named isEven and isOdd to your Math namespace that return true if a number is even or odd, respectively.

Challenge 4: Odd and Even Int

It turns out that Int is simply a struct. Add the computed properties isEven and isOdd to Int using an extension.

Challenge 5: Prime Factors

Add the method primeFactors() to Int. Since this is an expensive operation, this is best left as an actual method and not a computed property.

Key Points

  • Methods are functions associated with a type.
  • Methods are the behaviors that define the functionality of a type.
  • A method can access the data of an instance by using the keyword self.
  • Initializers create new instances of a type. They look like functions called init without the func keyword and no return value.
  • A type method adds behavior to a type instead of the instances of that type. To define a type method, you prefix it with the static modifier.
  • You can open an existing structure and add methods, initializers and computed properties to it by using an extension.
  • Adding custom initializers as extensions allows you to keep the compiler-generated memberwise initializer.
  • Methods can exist in all the named types — structures, classes and enumerations.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now