Programming in Swift: Functions & Types

Jan 4 2022 · Swift 5.5, iOS 15, Xcode 13

Part 4: Properties & Methods

33. Methods

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 32. Challenge: Properties Next episode: 34. Challenge: Methods

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 33. Methods

Update Notes: This course was originally recorded in 2019. It has been reviewed and all content and materials updated as of October 2021.

Transcript: 33. Methods

You’ve already learned how to write your own methods. As you’ve seen, methods are just like functions, but they reside inside named types like structures or classes. Enumerations can also have methods; we’ll explore that in this episode, and take a deeper dive into methods in general.

To start, create a Weekday enumeration, and make it CaseIterable, like this:

32 enum Weekday: CaseIterable {

Then add a case for each day of the week

case monday, tuesday, wednesday, thursday, friday, saturday, sunday

Remember, that CaseIterable protocols can provide you with an array of all your type’s cases. You access it with a type property named allCases.

36 Weekday.allCases

Let’s make a weekday variable, and then we’ll use the allCases property in a Weekday method.

37 var weekday: Weekday = .tuesday

The method we’ll write will advance the weekday by a dayCount.

35 func advance(by dayCount: UInt) {

}

Make sure you’ve got that UInt there, instead of Int. The “U” stands for “unsigned”. We’re not going to be working with negative numbers in this episode, so, an unsigned integer will be easier for us to work with!

First, let’s gets the index of the day we’re working with, in the allCases array. You can do this with the firstIndex method.

36 let indexOfToday = Weekday.allCases.firstIndex(of: self)!

You also could use the lastIndex method; it would give the same result because there’s only one of each day of the week in the array.

Previously, we showed you that you could have enumeration cases use raw values. Maybe you’re wondering why we’re iterating through cases, to find an index, instead of using an integer for a raw value, and using that property.

Well, aside from just wanting to show you a new technique, Catie and I don’t think that integers map that well to weekdays. What day does a week start on? That’s not a universally consistent concept. And if we could settle on a day, would that be considered “day 1”, or “day 0”?

CaseIterable makes it easy to eliminate those arbitrary decisions. But, if you want to, you can have enumerations that do use raw values, but are still CaseIterable. Sometimes it’s just handy to have the cases as an array.

Next, to get the index of the day we’re advancing to, we’ll add dayCount to indexOfToday.

let indexOfAdvancedDay = indexOfToday + dayCount

But indexOfToday is a signed integer, and dayCount is unsigned, so you can’t add them, unless you convert one to the type of the other. We’ll need an Int in the next step, so convert dayCount.

Int(dayCount)

indexOfAdvancedDay might be greater than 6, and that won’t work. To get it into the range that we need, we can take the remainder of dividing by how many days there are, which is Weekday.allCases.count

39 indexOfAdvancedDay % Weekday.allCases.count

And we use the result, as an index into Weekday.allCases.

39 Weekday.allCases[indexOfAdvancedDay % Weekday.allCases.count]

But we’re not actually changing the day yet. In order for this method to change the weekday, we have to mark it as mutating.

35 mutating func …

And, then–this is cool–you can reassign self to be the value you calculated.

39 self = Weekday…

Now, as long as you’ve got a mutable–that is, variable–weekday, you can use your new mutating method!

weekday.advance(by: 6)

Now, let’s define a structure that uses your Weekday type. It’ll be a very simple representation of a time.

struct Time {
  var day: Weekday
  var hour: UInt
}

The memberwise initializer that gets generated for you will allow you to represent Monday, at midnight, like this:

var time = Time(day: .monday, hour: 0)

But it you wanted to make it so that midnight was considered the default, then you could write your own initializer, with a default argument.

51 init(day: Weekday, hour: UInt = 0) {
    self.day = day
    self.hour = hour
  }

And then, you could leave off the hour argument, when initializing.

var time = Time(day: .monday)

If you want to advance a time by a number of hours, with a method, it will need to be mutating, like the last one you wrote.

56 mutating func advance(byHours hourCount: UInt) {
    
  }

The way we’ll do this starts off with getting the sum of the time’s hour, and the hourCount parameter.

hour + hourCount

We can put parentheses around that, in order to be able to call a method on it: quotientAndRemainder.

(hour + hourCount).quotientAndRemainder

If we divide by 24, the quotient will be how many days to advance by, and the remainder will be the hour of that day.

…quotientAndRemainder(dividingBy: 24)

The result is a tuple. We’ll the quotient dayCount, and remainder, hour. And we’ll have to add a self-dot to disambiguate the two values names hour.

let (dayCount, hour) = (self.hour…

All that’s left is to advance the day, and assign the new hour.

58 day.advance(by: dayCount)
    self.hour = hour

Now, if you advance Monday at midnight, by 3 24-hour days, and 5 more hours…

var time = Time(day: .monday)
😺time.advance(byHours: 24 * 3 + 5)🛑

…you’ll get Thursday at 5 AM. Oftentimes in Swift, there are mutating and nonmutating versions of the same sort sort of method. The naming convention for a nonmutating variation of what you just wrote, is advanced, instead of advance.

62 func advanced(byHours hourCount: UInt) {

}

Aside from not being marked with mutating, it will also have to return a new Time.

…) -> Time {

Start by making a mutable copy of the time instance.

63 var time = self

Then, use the mutating method you wrote, on the copy, forwarding the parameter.

63 time.advance(byHours: hourCount)

And then, return the copy!

65 return time

Now, you can make your time instance immutable, and still get an advanced version of it.

67 😺let🛑 time = Time(day: .monday)
😺var advancedTime = time.advanced🛑(byHours: 24 * 3 + 5)

Now, let’s switch those two methods up. Instead of doing the real calculation in the mutating method, do it in the nonmutating method. Start off by copying the first line over.

func advanced(byHours hourCount: UInt) -> Time {
  😺let (dayCount, hour) = (self.hour + hourCount).quotientAndRemainder(dividingBy: 24)🛑

Then, instead of calling advance on your copy, advance its day, instead, and reassign its hour. Like you did above, for the instance itself.

let (dayCount, hour) = (self.hour + hourCount).quotientAndRemainder(dividingBy: 24)

😺var time = self
time.day.advance(by: dayCount)
time.hour = hour
return time🛑

And now, for the mutating method, just like with an enumeration, you can reassign what self is.

56 mutating func advance(byHours hourCount: UInt) {
    self = self.advanced(byHours: hourCount)
  }

So if we advance a Time variable by six hours…

71 advancedTime.advance(byHours: 6)

…what we’re really saying, is: “Make a copy of our Time structure, mutate the copy, and assign that copy back to our variable”. That’s how value types work!

Is that feeling natural to you yet? If you have an instance of a structure, and you mutate it, you’ll always be working with an entirely new structure, that’s just based somehow on the original.

When you use the “mutating” keyword, you’re not just saying that you’re allowed to mutate something about the struct. You’re saying that you’re going to make a whole new struct with that method. The same goes for enumerations.

So when it comes to pairs of mutating and nonmutating methods, as you’ve seen, the bulk of your code can be in either method. Then, you can use language features like making mutable copies, and reassigning to self, to avoid code duplication.

We introduced you to type properties in a previous video. But! There are also type methods! For example, we could make a structure, called Mathematics, and give it a method called getLength, that would operate on X and Y Double values.

struct Mathematics {
  static func getLength(x: Double, y: Double) -> Double {
  
  }
}

Thanks to Pythagoras, we know that we square X and Y, add the results together, and take the square root.

75 return (x * x + y * y).squareRoot()

And we can call that by using the name of the type.

Mathematics.getLength(x: 3, y: 4)

As it is, though, this could be a little clearer. Because we defined it as a struct, we can make a mathematics instance.

80 let mathematics = Mathematics()

And that doesn’t make any sense! To avoid that, we can make it an enumeration, instead.

73 enum Mathematics {

And that last line won’t compile. Which is perfect! We’ll never need an instance.

Because it doesn’t have any cases, you’ll hear sometimes see this kind of type called a “caseless enumeration”. They’re great for organization.

Before I throw another challenge at you, I want to point out that Apple’s Foundation library contains a robust, production-ready Date class that correctly handles all of the subtle intricacies of dealing with dates and times. You really shouldn’t try to write your own, if you’re planning to deal with real-world time.