34

众所周知,创建 NSDateFormatters 是“昂贵的”

甚至 Apple 的数据格式指南(2014-02 更新)也指出:

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

但是该文档似乎并没有真正与 swift 保持同步,而且我在最新的NSDateFormatter 类参考中也找不到任何关于缓存格式化程序的信息,所以我只能假设 swift 和 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")

其中任何一个是一个好主意还是一个可怕的主意?转换格式也很昂贵吗?

更新: 正如Hot LicksLorenzo Rossi指出的那样,切换格式可能不是一个好主意(不是线程安全的,而且与重新创建一样昂贵......)。

4

8 回答 8

49

我会在这里根据经验给出答案。答案是肯定的,在应用程序范围内缓存 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 以查看该格式化程序是否存在。如果它存在,那么它只是返回它。如果没有,它会创建它然后返回它。这增加了一定程度的安全性,因此如果出于某种原因您想要修改格式化程序,您可以这样做,而不必担心格式化程序正在被另一个线程修改。

我们最终使用的是单例,它出售特定的格式化程序(NSDateFormatter 和 NSNumberFormatter),确保每个线程本身都有自己的特定格式化程序的副本(请注意,该应用程序是在 iOS 7 之前创建的,这使得它成为必不可少的事情)。这样做提高了我们的应用程序性能,并消除了由于线程安全和格式化程序而导致的一些令人讨厌的副作用。由于我们有 threadDictionary 部分,我从未测试过它是否在没有它的情况下在 iOS7+ 上存在任何问题(即它们已经真正成为线程安全的)。因此,为什么我在上面添加了“如果你愿意”。

于 2014-12-08T10:47:54.880 回答
6

DateFormatter由于 Swift 使用 dispatch once 方法来创建静态属性,因此以这种方式创建非常快速且安全。

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"])
于 2019-02-25T10:33:57.523 回答
3

在我看来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
}

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

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

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

于 2014-12-15T04:20:09.267 回答
1

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

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

在这里,我们显然不能有 0,虽然 1 听起来不错,但如果你要从多个线程中使用它,那么你不能只有一个没有它挂起。所以,无穷大。

解决这个问题的一个好方法是小心你产生的数量,为你打开的每个线程保留一个,并确保在你完成使用它们后立即清理它们。

为了帮助您,请查看此其他 stackoverflow 链接 - 恐怕我的答案会与他们的一致(将 NSDateformatters 的数量保持在最低限度)。但是,他们可能有一些本线程未涵盖的推理(没有双关语!)

如何最小化分配和初始化 NSDateFormatter 的成本?

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

于 2014-12-08T10:05:13.350 回答
1

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

引入单例(只要您需要格式化程序的新变体)不是一个好的解决方案。创建格式化程序并不昂贵。

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

Instruments 将帮助您确定程序在哪里创建了许多格式化程序,并且您可以考虑如何根据这些数据重用格式化程序。

转换格式也很昂贵吗?

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

于 2014-12-15T07:18:01.123 回答
0

在对象中:

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

来自苹果文档: https ://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

在斯威夫特:

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

于 2014-12-05T18:46:34.973 回答
0

快速示例

基于@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
    }
}
于 2016-02-29T08:57:18.570 回答
0

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

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

    }

  }

}

这是在库中使用的,这就是为什么您可以在整个过程中看到public修饰符的原因。

于 2016-03-11T11:06:17.157 回答