Chapters

Hide chapters

Swift Apprentice: Fundamentals

Second Edition · iOS 18 · Swift 6 · Xcode 16.2

Section III: Building Your Own Types

Section 3: 9 chapters
Show chapters Hide chapters

13. Methods
Written by Eli Ganim

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.

Need a setter? Yes Computed Property. Extensive computation or DB access? No Yes Method. No Computed Property. Should I implement this value getter as a method or as a computed property?

Ask yourself whether you want to be able to set a value as well as get the value. A computed property can have a setter component inside to write values. Another question to consider is whether the calculation requires extensive computation or reads from a database. Even for a simple value, a method helps you indicate to future developers that the call is expensive in time and computational resources. If the call is cheap (as in constant time O(1)), stick with a computed property.

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.

In the code below, how could you convert monthsUntilWinterBreak(date:) into a method?

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

Note: This example is fragile because it force unwraps an index that might not be valid. You would not want to do this in production code. Also, if you live in the southern hemisphere, you might be disappointed with the result since the winter season starts in June. Dealing with time is hard. :]

Making a method is as easy as moving the function inside the structure definition:

struct SimpleDate {
  var month: String

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

There’s no identifying keyword for a method; it is just a function inside a named type. You call methods on an instance using dot syntax just as you do for properties:

let date = SimpleDate(month: "October")
date.monthsUntilWinterBreak(from: date) // 2

And just like properties, as soon as you start typing a method name, Xcode will provide suggestions. You can select one with the Up and Down arrow keys on your keyboard, and you can autocomplete the call by pressing Tab:

If you think about this code for a minute, you’ll realize that the method’s definition is awkward. There must be an alternative for accessing content stored by the instance instead of passing the instance itself as a parameter to the method. It would be so much nicer to call this:

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.

The method definition transforms into this:

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

Here’s what changed:

  1. Now, there’s no parameter in the method definition.
  2. In the implementation, self replaces the old parameter name.

You can now call the method without passing a parameter:

date.monthsUntilWinterBreak() // 2

That’s looking a lot cleaner! One more thing you can do to simplify the code is to remove self.. …and you’re saying to yourself, “But you just told me to add it!”

While you can always use self to access the properties and methods of the current instance, most of the time, you don’t need to. In monthsUntilWinterBreak(), you can say month instead of self.month:

months.firstIndex(of: "December")! -
  months.firstIndex(of: month)!

Most programmers use self only when required, such as disambiguating between an input parameter and a property with the same name. You’ll get more practice using self a little later.

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.

Initializers are special methods you call to create a new instance. They omit the func keyword and even a name; instead, they use init. An initializer can have parameters, but it doesn’t have to.

Right now, when you create a new instance of the SimpleDate structure, you have to specify a value for the month property:

let date = SimpleDate(month: "October")

It is often convenient to have a no-parameter initializer. An empty initializer would create a new SimpleDate instance with a reasonable default value:

let date = SimpleDate() // Error!

While the compiler gives you an error now, you can fix it by making a no-parameter initializer like this:

struct SimpleDate {
  var month: String

  init() {
    month = "January"
  }

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

Here’s what’s happening in that code:

  1. The init() definition requires neither the func keyword nor a name. You use the name of the type to call an initializer.
  2. Like a function, an initializer must have a parameter list, even if it is empty.
  3. In the initializer, you assign values for all the stored properties of a structure.
  4. An initializer never returns a value. Its task is solely to initialize a new instance.

When you write your own custom initializer, the automatic memberwise initializer is no longer generated. So this code doesn’t work right now:

let date = SimpleDate(month: "October") // Error!

For now, comment out that code. Click somewhere in that line and press Command-/ to comment it out. You’ll add the memberwise initializer back in soon.

For now, use your new simple, empty initializer to create an instance:

let date = SimpleDate()
date.month // January
date.monthsUntilWinterBreak() // 11

You can test a change to the value in the initializer:

init() {
  month = "March"
}

The value of monthsUntilWinterBreak() will change accordingly:

let date = SimpleDate()
date.month // March
date.monthsUntilWinterBreak() // 9

As you think about the implementation, a good user experience optimization would have the initializer use a default value based on today’s date.

In the future, you’ll be capable of retrieving the current date. Eventually, you’ll use the Date class from the Foundation framework to work with dates.

Before you get carried away with all the power that these frameworks provide, let’s continue implementing your own SimpleDate type from the ground up.

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

Since initializers ensure all properties are set before the instance is ready to use, you must set day inside init(). The compiler would complain if you tried to create an initializer without setting the day property.

Again, recall that the auto-generated memberwise initializer takes all stored properties as parameters. For the SimpleDate structure, that is init(month: String, day: Int). However, when you add a custom initializer, the compiler scraps it.

So this code won’t work right now:

let valentinesDay = SimpleDate(month: "February",
                               day: 14) // Error!

To make it work again, you’ll have to define your own like so:

init(month: String, day: Int) {
  self.month = month
  self.day = day
}

In this code, you assign the incoming parameters to the properties of the structure. Notice how self tells the compiler that you’re referring to the property rather than the local parameter.

self wasn’t necessary in the simple initializer:

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

There aren’t any parameters with the same name as the properties in this code. Therefore, self isn’t necessary for the compiler to understand you’re referring to properties.

You can now use the initializer the same way you used to use the automatically generated one:

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.

When you set default values for parameters, the automatic memberwise initializer will use them.

In your structure, remove both initializers and then add default values for month and day:

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

  //2

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

Here’s what’s happening in this code:

  1. You assign a reasonable default to each declared property: January 1st.
  2. Both initializers, init() and init(month:day:) have been removed. …Look ma’, no initializers!

Even though both custom initializers are gone, you can still use both initializer styles:

let newYearsDay = SimpleDate()
newYearsDay.month // January
newYearsDay.day // 1

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

Once again, the automatic memberwise initializer is available since you didn’t declare any custom ones. The compiler provides init(month:day:) for you since those parameters are the properties.

However, it is also smart enough to realize that the properties have default values when declared and therefore do not need to be passed into the initializer. So that is how you get init() as well. What’s cool is that you can also mix and match, passing only the properties that you care to set:

let octoberFirst = SimpleDate(month: "October")
octoberFirst.month // October
octoberFirst.day // 1

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

In that code, you only passed the month into the first instance and only the day into the second instance. Pretty slick, eh!

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
}

Note: The implementation above is a naive way of writing advance() because it doesn’t account for what happens at the end of a month. In a challenge at the end of this chapter, you’ll create a more robust version.

The mutating keyword marks any method that may change one or more of the structure’s values. By marking a method as mutating, you’re telling the Swift compiler this method must not be called on constant instances declared with let. If you call a mutating method on a constant instance of a structure, the compiler will flag it as an error.

Swift secretly passes in self to mutating methods, just like non-mutating methods. But for mutating methods, the secret self gets marked as an inout parameter. Whatever happens inside the mutating method will impact everything that relies on the type externally.

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.

Type methods are useful for things that are about a type in general rather than something about specific instances.

For example, you could use type methods to group similar methods into a structure:

struct Math {
  // 1
  static func factorial(of number: Int) -> Int {
    // 2
    (1...number).reduce(1, *)
  }
}
// 3
Math.factorial(of: 6) // 720

You might have custom calculations for things such as factorial. Instead of having many free-standing functions, you can group related functions as type methods in a structure. The structure is said to act as a namespace.

Here’s what’s happening:

  1. You use static to declare the type method, which accepts an integer and returns an integer.
  2. The implementation uses a higher-order function called reduce(_:_:). It effectively follows the formula for calculating a factorial: “The product of all the whole numbers from 1 to n”. You could write this using a for loop, but the higher-order function expresses your intent in a cleaner way.
  3. You call the type method on Math rather than on an instance of the type.

6! 1 x 2 x 3 x 4 x 5 x 6 }

Type methods gathered into a structure will code complete in Xcode. In this example, you can see all the math utility methods available by typing Math..

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 feature is useful for code organization. Doing so is as easy as typing the keyword extension.

At the bottom of your playground, outside the definition of Math, add this type method named primeFactors(of:) using an 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
  }
}

This method finds the prime factors for a given number. For example, 81 returns [3, 3, 3, 3]. Here’s what’s happening in the code:

  1. The value passed in as a parameter is assigned to the mutable variable, remainingValue, so that it can be changed as the calculation runs.
  2. The testFactor starts as two and will be divided into remainingValue.
  3. The logic runs a loop until the remainingValue is exhausted. If it evenly divides, meaning there’s no remainder, that value of the testFactor is set aside as a prime factor. If it doesn’t evenly divide, testFactor is incremented for the next loop.

This algorithm is a brute force one but does contain one optimization: the square of the testFactor should never be larger than the remainingValue. If it is, the remainingValue itself must be prime and added to the primes list.

You’ve now added a method to Math without changing its original definition. Verify that the extension works with this code:

Math.primeFactors(of: 81) // [3, 3, 3, 3]

Pretty slick! You’re about to see how that can be powerful in practice.

Note: In an extension, you cannot add stored properties to an existing structure because that would change the size and memory layout of the structure and break existing code.

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

init(month:day:) gets added to SimpleDate without sacrificing the automatically generated memberwise initializer. You can create an instance using the month index Int instead of the month name String:

let halloween = SimpleDate(month: 10, day: 31)
halloween.month // October
halloween.day // 31

Hooray!

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
  }

}

Write a method that can change an instance’s area by a growth factor. For example, if you call circle.grow(byFactor: 3), the area of the instance will triple.

Hint: Add a setter to area.

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!

What happens when the function should go from the end of one month to the start of the next? Rewrite advance() to account for advancing from December 31st to January 1st.

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.

Note: Generally, you want to be careful about what functionality you add to standard library types as it can confuse readers.

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.
© 2026 Kodeco Inc.