4

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") }
}
degenPenguin
  • 725
  • 1
  • 8
  • 23
  • 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 Sep 05 '17 at 20:22
  • @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 – degenPenguin Sep 05 '17 at 20:27
  • @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) – degenPenguin Sep 05 '17 at 20:28
  • 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 Sep 05 '17 at 20:30
  • @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` – mfaani Sep 05 '17 at 20:34
  • @vadian, apologies, upcoming_date was originally inside the convenience init but I cut it out and added it to DateFormatter, I will include it to the original question for complete transparency and full information – degenPenguin Sep 05 '17 at 20:35
  • @honey, Thank you. I understand that I need to call self.init early but how can I if I need to convert a dictionary into a date to get the required attributes to initialize? "start", "end" are not optionals. I don't see where I am calling self inside the initializer at all – degenPenguin Sep 05 '17 at 20:39

1 Answers1

2

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
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • It worked, you made the problem disappear!! I don't quite understand (yet) why this gave me problems but thank you. I would not have suspected this to be the issue – degenPenguin Sep 05 '17 at 20:47
  • 1
    The trick is that in failable initializer, every path of execution must either call a designated initializer or must `return nil` (or, if the initializer `throws`, either must `throw` an actual error). You had two paths of execution (the `else` clause and the `catch` block, that failed to do so). The compiler error message is just misleading. – Rob Sep 05 '17 at 20:56