Skip to main content
Clarify why I didn’t use the computed property approach
Source Link
Rob
  • 2.7k
  • 17
  • 27

I might suggest that we want to use stored propertiesA few observations:

  • I might suggest that we want to use stored properties, like the original Objective-C code. I would be wary of using computed properties that return collections, as that can introduce non-obvious performance hits if you reference this computed property repeatedly, causing the whole array to be re-retrieved multiple times. Admittedly, this collection is small enough, it’s unlikely to be material, but it is something to be sensitive to when dealing with computed properties and collections.

  • I see no reason to store the DateFormatter. If you are using it for other purposes, then go ahead and do that, but there is nothing in this example that suggests that is the case.

  • I’d personally go towards a singleton, too

Thus, perhaps something like the Objective-C code. And I’d personally go towards a singleton, too:

public class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}
public class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
            NotificationCenter.default.post(name: .dateInfoChanged, object: nil)
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}

I might suggest that we want to use stored properties, like the Objective-C code. And I’d personally go towards a singleton, too:

public class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}
public class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
            NotificationCenter.default.post(name: .dateInfoChanged, object: nil)
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}

A few observations:

  • I might suggest that we want to use stored properties, like the original Objective-C code. I would be wary of using computed properties that return collections, as that can introduce non-obvious performance hits if you reference this computed property repeatedly, causing the whole array to be re-retrieved multiple times. Admittedly, this collection is small enough, it’s unlikely to be material, but it is something to be sensitive to when dealing with computed properties and collections.

  • I see no reason to store the DateFormatter. If you are using it for other purposes, then go ahead and do that, but there is nothing in this example that suggests that is the case.

  • I’d personally go towards a singleton, too

Thus, perhaps something like:

class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}
class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
            NotificationCenter.default.post(name: .dateInfoChanged, object: nil)
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}
Source Link
Rob
  • 2.7k
  • 17
  • 27

I might suggest that we want to use stored properties, like the Objective-C code. And I’d personally go towards a singleton, too:

public class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}

And, if you have view controllers that are also observing .currentLocaleDidChangeNotification, you might want to eliminate any race conditions by introducing your own notification, e.g. .dateInfoChanged:

extension Notification.Name {
    static let dateInfoChanged = Notification.Name(rawValue: Bundle.main.bundleIdentifier! + ".dateInfoChanged")
}

And then:

public class DateInfo {
    static let shared = DateInfo()

    private(set) var shortMonthNames: [String] = []
    private(set) var longMonthNames: [String] = []
    private(set) var numberOfMonths: Int = 0

    private init() {
        NotificationCenter.default.addObserver(forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: nil) { [weak self] _ in
            self?.update()
            NotificationCenter.default.post(name: .dateInfoChanged, object: nil)
        }
        update()
    }

    private func update() {
        let formatter = DateFormatter()
        shortMonthNames = formatter.shortStandaloneMonthSymbols
        longMonthNames = formatter.standaloneMonthSymbols
        numberOfMonths = shortMonthNames.count
    }
}

Then view controllers can observe .dateInfoChanged, and you’ll be confident that they’ll be getting this month info after it was updated.