Подтвердить что ты не робот

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

Он хорошо знал, что создание NSDateFormatters - это expensive '

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

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

Но этот документ, по-видимому, не соответствует современным требованиям, и я также не могу найти ничего об этом в последнем описании класса NSDateFormatter кеширование форматирования, поэтому я могу только предположить, что это так же дорого, как и для objective-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")

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

Update: Как Горячие Лики и Лоренцо Росси указывают, что переключение форматов, вероятно, не такая хорошая идея (Не поток безопасный и такой же дорогой, как воссоздание..).

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

Ответ 2

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

В одном из моих приложений я использовал Singleton с тремя объектами формата даты (все три содержит три разных формата) в качестве свойств. И пользовательские геттеры для каждого 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 во время инициализации синглтона?

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

Ответ 3

В Objc:

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

От Apple Docs: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

В Swift:

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

Ответ 4

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

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

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

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

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

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

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

Ответ 5

Может ли это быть хорошей или ужасной идеей?

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

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

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

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

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

Ответ 6

Пример Swift

На основе ответа @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
    }
}

Ответ 7

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

import Foundation

public class DateFormatter : NSDateFormatter{

  public class func sharedFormatter() -> NSDateFormatter {
    // current thread 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

    }

  }

}

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