缓存NSDateformatter应用程序范围的好主意吗?

时间:2014-12-05 18:02:20

标签: ios cocoa caching swift nsdatepicker

well知道创建NSDateFormatters是' expensive'

即便是Apple的Data Formatting Guide(2014-02更新)声明:

  

创建日期格式化程序并不是一种廉价的操作。如果您可能经常使用格式化程序,则缓存单个实例通常比创建和处理多个实例更有效。一种方法是使用静态变量。

但是那个doc似乎并不是最新的swift,我也无法在最新的NSDateFormatter Class Reference中找到关于缓存格式化程序的任何内容,所以我只能假设它只是对于客观的c来说,它的价格很快。

许多消息来源建议caching使用它的类中的格式化程序,例如控制器或视图。

我想知道它是否方便,甚至更便宜'将单例类添加到项目中以存储日期选择器,以便您确保无需再次创建它。这可以在应用程序的任何地方使用。您还可以创建包含多个日期选择器的多个共享实例。例如,一个用于显示日期的日期选择器和一个用于时间表示法的日期选择器:

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 LicksLorenzo Rossi指出的那样,切换格式可能不是一个好主意(不是线程安全的,而且与重新创建一样昂贵。)。

8 个答案:

答案 0 :(得分:40)

我会根据经验回答这里的答案。答案是肯定的,缓存NSDateFormatter应用程序范围是一个好主意,但是,为了增加安全性,您需要采取一个步骤。

为什么好?性能。事实证明,创建NSDateFormatters实际上很慢。我开发了一个高度本地化的应用程序,并使用了很多NSDateFormatters和NSNumberFormatters。有时我们会在方法中贪婪地动态创建它们,并且拥有拥有自己所需格式化程序副本的类。此外,我们还有一个额外的负担,即我们还可以在同一屏幕上显示为不同语言环境本地化的字符串。我们注意到我们的应用程序在某些情况下运行缓慢,并且在运行了Instruments之后,我们意识到它是格式化程序创建。例如,当滚动具有大量单元格的表视图时,我们看到性能受到影响。所以我们最终通过创建一个单独的对象来缓存它们,这个对象出现了相应的格式化程序。

电话会看起来像:

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

注意,这是Obj-C,但可以在Swift中进行等效。刚刚发生在我们的Obj-C。

从iOS 7开始,NSDateFormatters和NSNumberFormatters是“线程安全的”,但正如Hot Licks所提到的,如果另一个线程正在使用它,你可能不想修改格式。另外+1用于缓存它们。

我刚才想到的另一个好处是代码可维护性。特别是如果你有像我们这样的大型团队。因为所有开发人员都知道有一个集中式对象可以查看格式化程序,所以他们只需查看它们所需的格式化程序是否已经存在。如果没有,则会添加。这通常与功能相关,因此通常意味着其他地方也需要新的格式化程序。这也有助于减少错误,因为如果格式化程序中出现了错误,您可以将其修复一个位置。但是我们通常会在新格式化程序的单元测试中发现它。

如果您愿意,还可以添加一个元素以确保安全。您可以使用NSThread的threadDictionary来存储格式化程序。换句话说,当你调用将销售格式化程序的单例时,该类检查当前线程的threadDictionary以查看该格式化程序是否存在。如果它存在,那么它只是返回它。如果没有,它会创建它然后返回它。这增加了安全级别,因此,如果由于某种原因您想要修改格式化程序,则可以执行此操作,而不必担心格式化程序正在被另一个线程修改。

我们在一天结束时使用的是singleton,它出售了特定的格式化程序(NSDateFormatter和NSNumberFormatter),确保每个线程本身都拥有该特定格式化程序的副本(注意应用程序是在iOS 7之前创建的,做了一件必不可少的事情)。这样做可以改善我们的应用程序性能,并消除由于线程安全和格式化程序而遇到的一些令人讨厌的副作用。由于我们有threadDictionary部分,我从来没有测试它,看看我们是否在没有它的情况下在iOS7 +上有任何问题(即它们真正成为线程安全的)。因此,为什么我在上面添加了“如果你想要”。

答案 1 :(得分:3)

在我看来缓存NSDateFormatter是一个好主意,如果你的应用广泛使用或整个应用程序,它会提高你的应用程序的性能。如果你需要在1或2个地方,这不是一个好主意。但是,更改日期格式不是一个好主意,它可能会导致您进入不受欢迎的情况。 (您需要在每次使用之前跟踪当前格式)

在我的一个应用程序中,我使用了一个带有三个日期格式对象的单例(所有三个包含三种不同的格式)作为属性。每个NSDateFormatter

的自定义getter
+ (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
}

像这样我也为其他两个实现了自定义getter。

为什么我实施了自定义getter?为什么我在单例初始化期间没有分配NSDateFormatter

这是因为我不想在开头本身分配它们。我需要在第一次需要时分配它(按需)。在我的应用程序中,所有三个NSDateFormatters都没有被广泛使用,这就是为什么我选择这样一个模式来实现它。 (我的应用程序是在Objective C中,这就是我在这里使用Objective C代码的原因)

答案 2 :(得分:1)

不使用单例,而是使用依赖注入。请记住遵循0,1,无限规则。

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

在这里,我们显然不能有0,而1听起来不错,如果你打算从多个线程使用它,那么你不能只有一个没有它悬挂。所以,无限。

解决这个问题的一个好方法就是要小心你产生的数量,为你打开的每个线程保持一个,并确保一旦你使用它们就清理它们。

为了帮助您,请查看其他stackoverflow链接 - 我担心我的答案将与他们的答案一致(将NSDateformatters的数量降至最低)。但是,他们可能有一些在这个帖子中没有涉及的推理(没有双关语!)

How to minimize the costs for allocating and initializing an NSDateFormatter?

另外,如果我可能会问 - 如果您遇到这个问题,可能在您的程序中某处可以改进流程以避免需要这么多格式化程序?

答案 3 :(得分:1)

  

其中任何一个都是好主意还是可怕主意?

介绍单例(任何时候你需要一个格式化程序的新变种)是一个好的解决方案。创建格式化程序 非常昂贵。

相反,设计重用和共享格式化程序实例的方法,并在程序中传递它们,如常规变量。这种方法很容易引入现有程序。

仪器将帮助您确定程序创建多个格式化程序的位置,您可以考虑如何根据该数据重用格式化程序。

  

并且转换格式也很贵吗?

除非在非常特定/本地的上下文(例如特定的视图集合)中使用,否则不要打扰您共享的变体格式化程序。这将更容易实现预期的结果。相反,如果您需要共享格式化程序的变体, copy 则会发生变异。

答案 4 :(得分:1)

由于Swift使用调度一次方法来创建静态属性,因此以这种方式创建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"])

答案 5 :(得分:0)

在Objc:

考虑到NSDateFormatter在您的应用程序中使用单个格式化程序被认为是线程不安全可能不是最好的主意。每个线程有一个格式化程序可能是一种方法。或者,您可以考虑将格式化程序包装在线程安全类中。

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

在斯威夫特:

如果swift提供了类的线程安全性,那么只有一个实例就没有问题。

答案 6 :(得分:0)

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 :(得分:0)

我还希望通过提供一个示例来扩展 Mobile Ben 的答案:

    @Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    try {
        super.onRestoreInstanceState(savedInstanceState);
    }catch (Exception e){
        e.printStackTrace();
        Intent intent=new Intent(this,MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

这是在库中使用的,这就是为什么你可以看到 public 修饰符。