Home iOS & Swift Books Swift Apprentice

12
Methods Written by Ben Morrow

In the previous chapter, you learned about properties, which are 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]

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

Comparing methods to computed properties

With computed properties, you saw in the last chapter that you could run code from inside a structure. That sounds a lot like a method. What’s the difference? It comes down to a matter of 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.

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 the last chapter 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. The method definition transforms into this:

// 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 the previous chapters, 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()
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

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. It is possible to open an existing structure (even one you do not have the source code for) and add methods, initializers and computed properties to it. This can be useful for code organization and is discussed in greater detail in Chapter 18, “Access Control and Code Organization”. Doing so is as easy as using the keyword, extension.

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 memberwise initializer disappeared. It turns out that you can keep both if you add your init() to an extension to SimpleDate:

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
  }
}

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.

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 a lot like methods that are called init with 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.
  • By adding your own initializers in extensions, you can keep the compiler’s member-wise initializer for a structure.
  • 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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.