web-dev-qa-db-ja.com

アプリケーション全体でNSDateformatterをキャッシュするのは良い考えですか?

well NSDateFormattersの作成は ' 高価 'であることがわかっています

Appleの Data Formatting Guide (2014-02更新)でさえこう述べています:

日付フォーマッターの作成は、安価な操作ではありません。フォーマッターを頻繁に使用する可能性がある場合、通常、複数のインスタンスを作成して破棄するよりも、単一のインスタンスをキャッシュする方が効率的です。 1つの方法は、静的変数を使用することです。

しかし、そのドキュメントはSwiftで実際に最新の状態ではないようで、最新の NSDateFormatter Class Reference でフォーマッタのキャッシュについて何も見つからないので、 Swiftの場合は、objective-cの場合と同じくらい高額であると想定してください。

多くのソースは caching を使用するクラス内のフォーマッター、たとえばコントローラーやビューを示唆しています。

日付ピッカーを保存するためにプロジェクトにシングルトンクラスを追加して、再度作成する必要がないことが確実になるので、便利か、さらには「安く」なるかと思いました。これはアプリのどこでも使用できます。複数の日付ピッカーを含む複数の共有インスタンスを作成することもできます。たとえば、日付を表示するための1つの日付ピッカーと時間表記のための1つ:

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")

別の同様のアプローチは、シングルトンを1つだけ作成し、必要に応じてフォーマットを切り替えることです。

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 が指摘しているように、形式の切り替えはおそらくそうではありません良いアイデアです(スレッドセーフではなく、再作成と同じくらい高価です...)。

33
Tieme

ここでは、経験に基づいた答えでチャイムを鳴らします。答えは「はい」です。アプリ全体にNSDateFormatterをキャッシュすることをお勧めしますが、安全性を高めるために、このために実行したいステップがあります。

なぜそれが良いのですか?パフォーマンス。 NSDateFormattersの作成は実際には遅いことがわかりました。私は高度にローカライズされ、NSNumberFormattersだけでなく多くのNSDateFormattersを使用するアプリを開発しました。メソッド内で動的に貪欲にそれらを作成したり、必要なフォーマッターの独自のコピーを持つクラスを作成したりすることもありました。さらに、異なるロケールにローカライズされた文字列を同じ画面に表示できる場合があるという追加の負担がありました。特定の状況でアプリの動作が遅いことに気づき、Instrumentsを実行した後、それがフォーマッターの作成であることに気付きました。たとえば、多数のセルを含むテーブルビューをスクロールすると、パフォーマンスが低下することがわかりました。したがって、適切なフォーマッタを提供するシングルトンオブジェクトを作成して、それらをキャッシュすることになりました。

呼び出しは次のようになります。

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

これはObj-Cですが、Swiftでも同等のことができます。それはちょうど私たちがObj-Cにいたときに起こりました。

IOS 7以降、NSDateFormattersとNSNumberFormattersは「スレッドセーフ」ですが、ホットリックスで述べたように、別のスレッドがフォーマットを使用している場合は、フォーマットの変更を避けた方がよいでしょう。それらをキャッシュするためのもう1つの+1。

そして、私が考えたもう1つの利点は、コードの保守性でした。特に、私たちのように大規模なチームがある場合。すべての開発者は、フォーマッターを販売する集中オブジェクトがあることを知っているので、必要なフォーマッターが既に存在するかどうかを簡単に確認できます。ない場合は追加されます。これは通常、機能に関連しているため、通常、新しいフォーマッターが他の場所でも必要になることを意味します。これにより、フォーマッタにバグがあった場合でも1箇所で修正できるため、バグの削減にも役立ちます。しかし、通常、新しいフォーマッターの単体テスト中にそれを見つけます。

必要に応じて、安全のために追加できる要素がもう1つあります。 NSThreadのthreadDictionaryを使用してフォーマッタを保存できます。つまり、フォーマッタを提供するシングルトンを呼び出すと、そのクラスは現在のスレッドのthreadDictionaryをチェックして、フォーマッタが存在するかどうかを確認します。存在する場合は、単にそれを返します。そうでない場合は、それを作成してから返します。これにより、ある程度の安全性が追加されるため、何らかの理由でフォーマッタを変更したい場合は、それを実行でき、フォーマッタが別のスレッドによって変更されていることを心配する必要はありません。

1日の終わりに使用したのは、特定のフォーマッタ(NSDateFormatterとNSNumberFormatterの両方)を販売するシングルトンでした。これにより、各スレッド自体がその特定のフォーマッタの独自のコピーを持つようになります(アプリはiOS 7より前に作成され、これにより、行うために不可欠なこと)。これにより、アプリのパフォーマンスが向上し、スレッドの安全性とフォーマッターが原因で発生したいくつかの厄介な副作用がなくなりました。私たちはthreadDictionaryの部分を配置しているので、iOS7以降でそれがないと問題がないかどうか(つまり、本当にスレッドセーフになったかどうか)をテストしていません。したがって、なぜ「上記の場合」を追加したのか。

42
Mobile Ben

私の意見では、キャッシングNSDateFormatterは、アプリがそれを広く、またはアプリ全体で使用している場合に適しています。これにより、アプリのパフォーマンスが向上します。 1つか2つの場所でそれが必要な場合、それは良い考えではありません。ただし、日付形式を変更することはお勧めできません。望ましくない状況につながる可能性があります。 (現在のフォーマットを使用する前に毎回追跡する必要があります)

私のアプリケーションの1つでは、プロパティとして3つの日付形式オブジェクト(3つすべてに3つの異なる形式が含まれています)を持つシングルトンを使用しました。各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
}

このように、他の2つにもカスタムゲッターを実装しました。

なぜカスタムゲッターを実装したのですか?シングルトン初期化中にNSDateFormatterを割り当てなかったのはなぜですか?

それは私が最初にそれらを割り当てたくないからです。初めて必要になったときに(オンデマンドベースで)割り当てる必要があります。私のアプリでは、3つすべてのNSDateFormattersは広く使用されていません。そのため、実装するためにそのようなパターンを選択しました。 (私のアプリはObjective Cにあるので、ここでObjective Cコードを使用しました)

3
Midhun MP

それらのどちらが良いアイデアか、恐ろしいアイデアでしょうか?

シングルトンの導入(フォーマッターの新しいバリアントが必要なときはいつでも)notは良い解決策です。フォーマッタを作成することはthatコストがかかりません。

代わりに、フォーマッタのインスタンスを再利用して共有し、通常の変数のようにプログラム内で渡す方法を考案してください。このアプローチは、既存のプログラムに導入するのが非常に簡単です。

Instrumentsは、プログラムが多くのフォーマッターを作成する場所を特定するのに役立ち、そのデータに基づいてフォーマッターを再利用する方法を検討できます。

そして、フォーマットの切り替えも高価ですか?

非常に具体的/ローカルなコンテキスト(ビューの特定のコレクションなど)で使用されない限り、共有するミュータを変更しないでください。これは期待される結果を達成するためにはるかに簡単になります。代わりに、共有フォーマッタのバリアントが必要な場合はcopyを変更します。

1
justin

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"])
1

シングルトンを使用する代わりに、依存性注入を使用します。 0、1、無限ルールに従うことを忘れないでください。

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

ここでは、明らかに0にすることはできません。1はいい音に聞こえますが、複数のスレッドから使​​用する場合は、ハングしない状態で1つだけにすることはできません。だから、無限。

これにアプローチする良い方法は、スポーンする数​​に注意し、開いたスレッドごとに1つだけ保持し、それらを使い終わったらすぐにクリーンアップすることです。

あなたを助けるために、この他のstackoverflowリンクをチェックしてください-私の答えは彼らのものと一致するのではないかと思います(NSDateformattersの数を最小限に抑えてください)。ただし、これらのスレッドには、このスレッドではカバーされていなかった何らかの理由がある場合があります(しゃれた意図はありません!)

NSDateFormatterの割り当てと初期化のコストを最小限に抑える方法

また、私が尋ねる場合-この質問に直面している場合、おそらく多くのフォーマッターを必要としないようにフローを改善できる場所がプログラムのどこかにあるでしょうか?

1

Objcで:

アプリケーションでNSDateFormatterを単一のフォーマッターを使用してスレッドセーフでないと見なすことは、最善のアイデアではない可能性があります。スレッドごとに1つのフォーマッターを用意するのがよいでしょう。または、フォーマッタをスレッドセーフクラスでラップすることを検討することもできます。

からApple Docs: https://developer.Apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

Swiftの場合:

Swiftの場合、クラスのスレッドセーフが提供されるので、インスタンスが1つでも問題はありません。

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修飾子を表示できます。

0
the_critic

迅速な例

@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
    }
}
0
user3378170