Search code examples
swiftswift5swift-optionals

Swift optionals in class methods


I am still relatively new to swift, so I am having some problems with the proper syntax. Here is my code for the class Date, which has the isLeapYear and daysInMonth methods. I am having problems with the optionals for these methods:

class Date {
    var day, month, year : Int

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

    func isLeapYear(y : Int? = self.year) -> Bool {  
        var x = false
        if y % 4 == 0 {x = true}
        return x
    }

    //Returns the amount of days in a given month
    func daysInMonth(month : Int? = self.month, year : Int? = self.year) -> Int? {
        let _31_day_months = [1, 3, 5, 7, 8, 10, 12]
        let _30_day_months = [4, 6, 9, 11]
        if month == 2 {
            if self.isLeapYear(y : year) {return 29}  else {return 28}
        }
        else if _31_day_months.contains(month) {return 31}
        else if _30_day_months.contains(month) {return 30}
        else {return nil}
    }
}

What I want to do with func isLeapYear(y : Int? = self.year) -> Bool is that when I call isLeapYear and y isn't specified, that it is automatically set to self.year. However I get the following error:

use of unresolved identifier 'self'

I also get the error

value of optional type 'Int?' must be unwrapped to a value of type 'Int'

I know I have to use !, but I don't know exactly how and where, I have tried doing if y! % 4 == 0, but that just seemed to make it worse.

I would also like to do the same thing for the method daysInMonth


Solution

  • Default values need to be constant at compile-time. You can't define them in terms of some other property. You need to check their values at runtime. In your example this would be:

    func isLeapYear(y : Int? = nil) -> Bool {
        var x = false
        if (y ?? year) % 4 == 0 {x = true}  // <== "?? year" meaning "y, or year if y is nil"
        return x
    }
    

    Note that this is a very confusing API. You'd have to create a random Date instance in order to check something unrelated to that instance. Instead what I believe you really mean here is two methods; one static and one on the instance:

    // Static method, called as Year.isLeapYear(xxxx)
    static func isLeapYear(_ y: Int) -> Bool {
        // Note that this is not the correct Gregorian Leap Year rule
        return y % 4 == 0
    }
    
    // Instance method, called as year.isLeapYear()
    func isLeapYear() -> Bool { Date.isLeapYear(year) }
    

    Since you're new to Swift, it's worth noting: This should be a struct, not a class (it is a pure value that has no identity, and any two Dates with the same properties should be considered the same Date, which is what structs are for). And you should be careful calling it "Date" since that collides with the Foundation type of the same name.