Является ли кэширование NSDateformatter для всей приложения хорошей идеей?

Хорошо известно, что создание NSDateFormatters " дорого"

Даже в Руководстве по форматированию данных Apple (обновлено 2014-02) говорится:

Создание средства форматирования даты не является дешевой операцией. Если вы, вероятно, будете часто использовать средство форматирования, обычно более эффективно кэшировать один экземпляр, чем создавать и распоряжаться несколькими экземплярами. Одним из подходов является использование статической переменной.

Но этот документ, кажется, не совсем в курсе Swift, и я также не могу найти что-либо об этом в последнем Справочнике по классам NSDateFormatter о кэшировании форматера, так что я могу только предположить, что это так же дорого для swift, как и для target-c,

Многие источники предлагают кэшировать форматер внутри класса, используя его, например, контроллер или представление.

Мне было интересно, будет ли удобно или даже "дешевле" добавить в проект одноэлементный класс для хранения средства выбора даты, чтобы вы были уверены, что его создавать больше не нужно. Это может быть использовано везде в приложении. Вы также можете создать несколько общих экземпляров, содержащих несколько сборщиков дат. Например, один указатель даты для отображения дат и один для обозначения времени:

class DateformatterManager {
    var formatter = NSDateFormatter()

    class var dateFormatManager : DateformatterManager {
        struct Static {
            static let instance : DateformatterManager = DateformatterManager()
        }
        // date shown as date in some tableviews
        Static.instance.formatter.dateFormat = "yyyy-MM-dd"
        return Static.instance
    }

    class var timeFormatManager : DateformatterManager {
        struct Static {
            static let instance : DateformatterManager = DateformatterManager()
        }
        // date shown as time in some tableviews
        Static.instance.formatter.dateFormat = "HH:mm"
        return Static.instance
    }

    // MARK: - Helpers
    func stringFromDate(date: NSDate) -> String {
        return self.formatter.stringFromDate(date)
    }
    func dateFromString(date: String) -> NSDate? {
        return self.formatter.dateFromString(date)!
    }
}

// Usage would be something like: 
DateformatterManager.dateFormatManager.dateFromString("2014-12-05")

Другим аналогичным подходом будет создание только одного синглтона и переключение формата в зависимости от необходимости:

class DateformatterManager {
    var formatter = NSDateFormatter()

    var dateFormatter : NSDateFormatter{
        get {
            // date shown as date in some tableviews
            formatter.dateFormat = "yyyy-MM-dd"
            return formatter
        }
    }

    var timeFormatter : NSDateFormatter{
        get {
            // date shown as time in some tableviews
            formatter.dateFormat = "HH:mm"
            return formatter
        }
    }

    class var sharedManager : DateformatterManager {
        struct Static {
            static let instance : DateformatterManager = DateformatterManager()
        }
        return Static.instance
    }

    // MARK: - Helpers
    func dateStringFromDate(date: NSDate) -> String {
        return self.dateFormatter.stringFromDate(date)
    }
    func dateFromDateString(date: String) -> NSDate? {
        return self.dateFormatter.dateFromString(date)!
    }
    func timeStringFromDate(date: NSDate) -> String {
        return self.timeFormatter.stringFromDate(date)
    }
    func dateFromTimeString(date: String) -> NSDate? {
        return self.timeFormatter.dateFromString(date)!
    }
}

// Usage would be something like: 
var DateformatterManager.sharedManager.dateFromDateString("2014-12-05")

Будет ли любая из них хорошей или ужасной идеей? И переключение формата также дорого?

Обновление: как отмечают Hot Licks и Lorenzo Rossi, переключение форматов, вероятно, не очень хорошая идея (не многопоточная и такая же дорогая, как пересоздание...).

7 ответов

Решение

Я перезвоню здесь с ответом, основанным на опыте. Ответ - да, кэширование всего приложения в NSDateFormatter - хорошая идея, однако для дополнительной безопасности есть шаг, который вы хотите предпринять для этого.

Почему это хорошо? Спектакль. Оказывается, что создание NSDateFormatters на самом деле медленно. Я работал над приложением, которое было сильно локализовано и использовало много NSDateFormatters, а также NSNumberFormatters. Были времена, когда мы динамически создавали их жадно в методах, а также имели классы, которые имели свою собственную копию нужных им средств форматирования. Кроме того, у нас было дополнительное бремя, что были случаи, когда мы могли также отображать строки, локализованные для разных локалей, на одном экране. Мы заметили, что в некоторых случаях наше приложение работало медленно, и после запуска Instruments мы поняли, что это создание форматера. Например, мы видели снижение производительности при прокрутке табличных представлений с большим количеством ячеек. Таким образом, мы закончили кэшировать их, создав одноэлементный объект, который поместил соответствующий форматер.

Звонок будет выглядеть примерно так:

NSDateFormatter *dateFormatter = [[FormatterVender sharedInstance] shortDate];

Обратите внимание, что это Obj-C, но эквивалент можно сделать в Swift. Просто так случилось, что наш был в Obj-C.

Начиная с iOS 7, NSDateFormatters и NSNumberFormatters являются "поточно-ориентированными", однако, как уже упоминалось в "Hot Licks", вы, вероятно, не захотите менять формат, если его использует другой поток. Еще +1 за их кеширование.

И еще одно преимущество, о котором я только что подумал, - это удобство сопровождения кода. Особенно, если у вас большая команда, как у нас. Поскольку все разработчики знают, что существует централизованный объект, который реализует средства форматирования, они могут просто увидеть, существует ли уже необходимый им модуль форматирования. Если это не так, он добавляется. Это, как правило, связано с особенностями, и, следовательно, обычно означает, что новый форматер понадобится и в других местах. Это также помогает уменьшить количество ошибок, потому что, если в программе форматирования есть ошибка, вы исправляете ее в одном месте. Но мы обычно ловим это во время модульных тестов для нового форматера.

Есть еще один элемент, который вы можете добавить для безопасности, если хотите. То есть вы можете использовать threadDictionary NSThread для хранения средства форматирования. Другими словами, когда вы вызываете синглтон, который будет продавать средство форматирования, этот класс проверяет threadDictionary текущего потока, чтобы увидеть, существует ли этот модуль форматирования. Если он существует, он просто возвращает его. Если нет, он создает его и затем возвращает. Это добавляет уровень безопасности, поэтому, если по какой-то причине вы захотите изменить свой форматтер, вы можете сделать это, и вам не нужно беспокоиться о том, что форматер изменяется другим потоком.

В конце дня мы использовали синглтон, который продавал определенные форматеры (как NSDateFormatter и NSNumberFormatter), гарантируя, что у каждого потока была своя собственная копия этого конкретного форматера (обратите внимание, что приложение было создано до iOS 7, что делало это что нужно сделать). Это улучшило производительность нашего приложения, а также избавило от некоторых неприятных побочных эффектов, которые мы испытывали из-за безопасности потоков и форматировщиков. Так как у нас есть часть threadDictionary, я никогда не тестировал ее, чтобы увидеть, есть ли у нас какие-либо проблемы с iOS7+ без нее (т.е. они действительно стали поточно-ориентированными). Вот почему я добавил "если хочешь" выше.

Поскольку Swift использует методы dispatch Once для создания статических свойств, его создание действительно быстрое и безопасное. DateFormatter таким образом.

extension DateFormatter {
    static let shortFormatDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

Чем просто написать

date = DateFormatter.shortFormatDateFormatter.string(from: json["date"])

На мой взгляд, кеширование NSDateFormatter Хорошая идея, если ваше приложение использует это приложение широко или повсеместно, это повысит производительность вашего приложения. Если вам это нужно в 1 или 2 местах, это не будет хорошей идеей. Однако изменение формата даты не является хорошей идеей, оно может привести вас к нежелательным ситуациям. (Вы должны отслеживать текущий формат каждый раз, прежде чем использовать его)

В одном из моих приложений в качестве свойств я использовал синглтон с тремя объектами формата даты (все три содержат три разных формата). И пользовательские геттеры для каждого NSDateFormatter

+ (instancetype)defaultDateManager
{
    static DateManager *dateManager = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        dateManager                = [[DateManager alloc] init];
    });

    return dateManager;
}

// Custom Getter for short date
- (NSDateFormatter *)shortDate
{
    if (!_shortDateFormatter)
    {
        _shortDateFormatter = [[NSDateFormatter alloc] init];
        [_shortDateFormatter setDateFormat:@"yyyy-MM-dd"]
    }
    return _shortDateFormatter
}

Таким образом, я реализовал собственные геттеры и для двух других.

Почему я реализовал пользовательские геттеры? Почему я не выделил NSDateFormatter во время инициализации синглтона?

Это потому, что я не хочу выделять их в самом начале. Мне нужно выделить его, когда он понадобится в первый раз (по требованию). В моем приложении все три NSDateFormatters широко не используются, поэтому я выбрал такой шаблон для его реализации. (Мое приложение в Objective C, поэтому я использовал код Objective C здесь)

Будет ли любая из них хорошей или ужасной идеей?

Введение синглетонов (когда вам нужен новый вариант форматера) не является хорошим решением. Создание форматеров не так уж и дорого.

Вместо этого разработайте способы повторного использования и обмена экземплярами форматеров и передачи их в вашу программу, как обычные переменные. Этот подход довольно просто внедрить в существующую программу.

Инструменты помогут вам определить, где ваша программа создает много форматеров, и вы можете подумать, как повторно использовать форматеры на основе этих данных.

И переключение формата также дорого?

Не беспокойтесь о мутирующих форматерах, которыми вы делитесь, если только они не используются в очень специфических / локальных контекстах (например, в определенной коллекции представлений). Это будет намного проще для достижения ожидаемых результатов. Вместо этого скопируйте, а затем измените, если вам нужен вариант общего форматера.

Вместо использования синглтона используйте внедрение зависимостей. Не забудьте следовать 0, 1, бесконечному правилу.

http://en.wikipedia.org/wiki/Zero_one_infinity_rule

Здесь у нас явно не может быть 0, и хотя 1 звучит замечательно, если вы собираетесь использовать его из нескольких потоков, у вас не будет только одного без зависания. Итак, бесконечность.

Хороший способ подойти к этому - просто быть осторожным с тем, сколько вы порождаете, держать по одному для каждой открываемой нити и быть уверенным, что они будут очищены, как только вы закончите их использовать.

Чтобы помочь вам, проверьте эту другую ссылку на стек-поток - я боюсь, что мой ответ будет в соответствии с их ответом (свести количество NSDateformatters к минимуму). Тем не менее, они могут иметь некоторые аргументы, которые не были рассмотрены в этой теме (не каламбур!)

Как минимизировать затраты на выделение и инициализацию NSDateFormatter?

Кроме того, если я могу спросить - если вы столкнулись с этим вопросом, возможно, есть где-то в вашей программе, где поток может быть улучшен, чтобы избежать даже необходимости в таком количестве форматеров?

В объекте:

Учитывая, что NSDateFormatter считается поточно-небезопасным, использование одного форматера в вашем приложении может оказаться не самой лучшей идеей. Один способ форматирования на поток может быть подходящим вариантом. В качестве альтернативы вы можете рассмотреть обертку форматера в поточно-безопасный класс.

Из документов Apple: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

В Свифте:

Если swift предлагает потокобезопасность класса, тогда не должно быть проблем с одним экземпляром.

Я также хотел бы расширить ответ Мобил Бена, приведя пример:

import Foundation

public class DateFormatter : NSDateFormatter{

  public class func sharedFormatter() -> NSDateFormatter {
    // current thread's hash
    let threadHash = NSThread.currentThread().hash
    // check if a date formatter has already been created for this thread
    if let existingFormatter = NSThread.currentThread().threadDictionary[threadHash] as? NSDateFormatter{
      // a date formatter has already been created, return that
      return existingFormatter
    }else{
      // otherwise, create a new date formatter 
      let dateFormatter = NSDateFormatter()
      // and store it in the threadDictionary (so that we can access it later on in the current thread)
      NSThread.currentThread().threadDictionary[threadHash] = dateFormatter
      return dateFormatter

    }

  }

}

Это используется в библиотеке, поэтому вы можете видеть общедоступный модификатор повсюду.

Свифт Пример

Основано на ответе @Mobile Ben: это пример простого синглтона Swift.

class YourDateFormatter {

    // MARK: - Properties

    static let sharedFormatter = YourDateFormatter()
    /// only date format
    private let dateFormatter: NSDateFormatter
    /// only time format
    private let timeFormatter: NSDateFormatter

    // MARK: - private init

    private init() {

        // init the formatters

        dateFormatter = NSDateFormatter()
        timeFormatter = NSDateFormatter()

        // config the format

        dateFormatter.dateStyle = .MediumStyle
        dateFormatter.timeStyle = .NoStyle
        dateFormatter.doesRelativeDateFormatting = true

        timeFormatter.dateStyle = .NoStyle
        timeFormatter.timeStyle = .MediumStyle

    }

    // MARK: - Public 

    func dateFormat(date: NSDate) -> String {
        return dateFormatter.stringFromDate(date)
    }

    func timeFormat(date: NSDate) -> String {
        return timeFormatter.stringFromDate(date)
    }

    func dateTimeFormat(date: NSDate) -> String {
        let dateFormat = self.dateFormat(date)
        let timeFormat = self.timeFormat(date)

        return dateFormat + " - " + timeFormat
    }
}
Другие вопросы по тегам