4
votes

so I have been trying to create a "Period" class, with the following attributes:

class PTPeriod {

    // MARK: Stored Properties

    var start: Date
    var end: Date
    var place: PTPlace?


//MARK: DESIGNATED NITIALIZER
init(start: Date, end: Date, place: PTPlace? = nil) {
    self.start = start
    self.end = end
    self.place = place

}

And I want to initialize it with a convenience init that accepts a dictionary like the following:

        {
           "close" : {
              "day" : 1,
              "time" : "0000"
           },
           "open" : {
              "day" : 0,
              "time" : "0900"
           }
        }

This is my initializer, originally I put the heavy lifting into a helper method. However, i was getting the same error I have now so I removed the helper method and the error is still occurring. Unsure where it thinks I am calling self.

UPDATE: moved the date formatting into a date formatter extension, but the error still persists! unsure WHY. new init shown:

My new Convenience init:

// MARK: Convenience init for initializing periods pulled from Google Place API
convenience init?(placeData: [String:Any], place: PTPlace? = nil) throws {
    var start: Date? = nil
    var end: Date? = nil
    var formatter = DateFormatter()

    do {
        for (key, value) in placeData {
            let period = value as! [String: Any]
            if (key == "open") {
                start = try formatter.upcoming_date(with: period)
            } else if (key == "close") {
                end = try formatter.upcoming_date(with: period)
            } else {
                break
            }
        }
        if (start != nil && end != nil) { self.init(start: start!, end: end!, place: place) }
        else { print("we f'd up") }

    }

    catch { print(error.localizedDescription) }
}

Here is my DateFormatter.upcoming_date method:

   func upcoming_date(with googlePlacePeriod: [String: Any]) throws -> Date? {
    if let day = googlePlacePeriod["day"] as? Int {
        if (day < 0) || (day > 6) { throw SerializationError.invalid("day", day) } // throw an error if day is not between 0-6
        if let time = googlePlacePeriod["time"] as? String {
            if time.characters.count != 4 { throw SerializationError.invalid("time", time) } // throw an error if time is not 4 char long
            var upcoming_date: Date
            let gregorian = Calendar(identifier: .gregorian)

            let current_day_of_week = Date().getDayOfWeekInt()
            let dayOfPeriod = day + 1

            ///TRUST THAT THIS WORKS... NO JODAS
            var distance = dayOfPeriod - current_day_of_week // inverse = false

            if (dayOfPeriod < current_day_of_week) {
                switch distance {
                case -1: distance = 6
                case -2: distance = 5
                case -3: distance = 4
                case -4: distance = 3
                case -5: distance = 2
                case -6: distance = 1
                default: break
                }
            }
            /** time example: "1535" translates into 3:35PM
             first two characters in string are "hour" (15)
             last two characters in string are "minute(35)
             **/

            let hour = Int(time.substring(to: time.index(time.startIndex, offsetBy: 2)))!
            let minute = Int(time.substring(from: time.index(time.endIndex, offsetBy: -2)))


            upcoming_date = Date().fastForward(amount: distance, unit: "days", inverse: false)!
            var components = gregorian.dateComponents([.year, .month, .day, .hour, .minute, .second], from: upcoming_date)
            components.hour = hour
            components.minute = minute
            upcoming_date = gregorian.date(from: components)!
            return upcoming_date
        }
        else { throw SerializationError.missing("time") }
    }
    else { throw SerializationError.missing("day") }
}
1
I don't know about the error in your title, but your code as listed doesn't call self.init in the else block. Similarly if an error is thrown, you're catching it (and not calling self.init) instead of letting it bubble up to the caller. - Lily Ballard
@Honey- I do have a designated initializer, but it requires "start" and "end" to be initialized. I will add it to the question for clarity - marko calvocruz
@Kevin- thanks for pointing that out, I'm not so sure I'm fully clear on when to catch errors and when to let them "bubble", though I've done a lot of reading on it. I added a "return nil" for when the init fails (as its a failable initializer) - marko calvocruz
Where does the method upcoming_date come from? It's not related to standard DateFormatter(of course not because Swift methods never use snake case variable names). I suspect there is another formatter declared in the class which uses self when being called. You are discouraged from naming local variables and properties with the same name unless you know what you are doing. - vadian
@markocalvocruz I removed my comment, it wasn't 100% correct. Where did you add the return nil? But basically the reason you're getting that error is, you are accessing some property ie you'r doing self.someProperty before you either do self.init(...) or return nil - Honey

1 Answers

2
votes

You have a failable initializer, so if it fails, you must return nil to let it know it failed:

if (start != nil && end != nil) { 
    self.init(start: start!, end: end!, place: place) }
} else { 
    print("we f'd up") 
    return nil
}

Also, in your do-catch, you need to either

  • re-throw the error in the catch block

    catch {
        print(error.localizedDescription)
        throw error
    }
    
  • eliminate the do-catch blocks altogether; or

  • just return nil:

    catch {
        print(error.localizedDescription)
        return nil
    }
    

    If you do this, you'd presumably not define this failable initializer as one that throws because you're no longer throwing.


Given that you're unlikely to do anything meaningful with the thrown error, I'd just make it a failable initializer that doesn't throw:

convenience init?(placeData: [String: Any], place: PTPlace? = nil) {
    var start: Date?
    var end: Date?
    let formatter = DateFormatter()

    for (key, value) in placeData {
        let period = value as! [String: Any]
        if key == "open" {
            start = try? formatter.upcoming_date(with: period)
        } else if key == "close" {
            end = try? formatter.upcoming_date(with: period)
        } else {
            break  // I'm not sure why you're doing this; it seems extremely imprudent (esp since dictionaries are not ordered)
        }
    }

    if let start = start, let end = end {
        self.init(start: start, end: end, place: place)
    } else {
        print("either start or end were not found")
        return nil
    }
}