Il est bien connu que créer NSDateFormatters est ' coûteux '
Même dans le Guide de formatage de données (mise à jour 2014-02) d'Apple:
Créer un formateur de date n’est pas une opération peu coûteuse. Si vous êtes susceptible d'utiliser fréquemment un formateur, il est généralement plus efficace de mettre en cache une seule instance que de créer et de supprimer plusieurs instances. Une approche consiste à utiliser une variable statique.
Mais cette documentation ne semble pas vraiment à jour avec Swift et je ne trouve rien à ce sujet dans le dernier NSDateFormatter Class Reference à propos de la mise en cache du formateur, donc je ne peux que supposer que c’est aussi cher pour Swift que c’est pour objectif-c.
De nombreuses sources suggèrent caching le formateur de la classe qui l'utilise, par exemple un contrôleur ou une vue.
Je me demandais s'il serait pratique, voire «moins cher», d'ajouter une classe singleton au projet pour stocker le sélecteur de date afin que vous soyez assuré qu'il ne sera plus jamais nécessaire de le créer à nouveau. Cela pourrait être utilisé partout dans l'application. Vous pouvez également créer plusieurs instances partagées contenant plusieurs datepickers. Par exemple, un sélecteur de date pour afficher les dates et un pour une indication de temps:
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")
Une autre approche similaire consisterait à créer un seul singleton et à changer de format en fonction des besoins:
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")
Est-ce que l'un ou l'autre serait une bonne ou une idée horrible? Et le changement de format est-il également coûteux?
Mise à jour: Comme Hot Licks et Lorenzo Rossi , soulignez, changer de format n'est probablement pas une si bonne idée (Pas de fil de discussion et aussi coûteux que de recréer ..) .
Je vais vous donner une réponse basée sur l'expérience. La réponse est oui, la mise en cache de NSDateFormatter à l’échelle de l’application est une bonne idée. Toutefois, pour plus de sécurité, vous souhaitez franchir une étape.
Pourquoi est-il bon? Performance. Il s'avère que la création de NSDateFormatters est en réalité lente. J'ai travaillé sur une application hautement localisée et qui utilisait beaucoup de NSDateFormatters et de NSNumberFormatters. Il y avait des moments où nous les créions de manière dynamique avec avidité au sein de méthodes, ainsi que des classes qui avaient leur propre copie des formateurs dont elles avaient besoin. En outre, nous avons eu le fardeau supplémentaire de pouvoir afficher des chaînes localisées pour différents paramètres régionaux sur le même écran. Nous remarquions que notre application fonctionnait lentement dans certains cas, et après avoir utilisé Instruments, nous avons réalisé que c'était la création du formateur. Par exemple, nous avons constaté une baisse des performances lors du défilement des vues de tableau avec un grand nombre de cellules. Nous avons donc fini par les mettre en cache en créant un objet singleton qui vendait le formateur approprié.
Un appel ressemblerait à quelque chose comme:
NSDateFormatter *dateFormatter = [[FormatterVender sharedInstance] shortDate];
Notez que ceci est Obj-C, mais l'équivalent peut être créé avec Swift. Il vient de se passer que le nôtre était à Obj-C.
Depuis iOS 7, NSDateFormatters et NSNumberFormatters sont "thread-safe", mais comme Hot Licks l'a mentionné, vous ne voudrez probablement pas modifier le format si un autre thread l'utilise. Un autre +1 pour les mettre en cache.
Un autre avantage auquel je viens de penser est la maintenabilité du code. Surtout si vous avez une grande équipe comme la nôtre. Comme tous les développeurs savent qu’il existe un objet centralisé qui vend les formateurs, ils peuvent simplement voir si le formateur dont ils ont besoin existe déjà. Si ce n'est pas le cas, il est ajouté. Ceci est généralement lié aux fonctionnalités et signifie donc qu'un nouveau formateur sera également nécessaire ailleurs. Cela aide également à réduire les bogues, car s’il ya un bogue dans le formateur, vous le corrigez à un endroit. Mais nous l’avons généralement compris lors des tests unitaires du nouveau formateur.
Vous pouvez ajouter un élément supplémentaire pour la sécurité, si vous le souhaitez. Vous pouvez utiliser le threadDictionary de NSThread pour stocker le formateur. En d'autres termes, lorsque vous appelez le singleton qui vendra le formateur, cette classe vérifie le threadDictionary du thread en cours pour voir si ce formateur existe ou non. S'il existe, il le retourne simplement. Sinon, il le crée et le renvoie ensuite. Cela ajoute un niveau de sécurité, donc si, pour une raison quelconque, vous souhaitez modifier votre formateur, vous pouvez le faire sans avoir à vous soucier du fait que le formateur est en train d'être modifié par un autre thread.
Ce que nous avons utilisé à la fin de la journée était singleton qui vendait des formateurs spécifiques (NSDateFormatter et NSNumberFormatter), en veillant à ce que chaque fil lui-même ait sa propre copie de ce formateur spécifique (notez que l’application avait été créée avant iOS 7, chose essentielle à faire). Ce faisant, nous avons amélioré les performances de notre application et éliminé certains effets secondaires désagréables dus à la sécurité des threads et aux formateurs. Puisque nous avons la partie threadDictionary en place, je ne l’ai jamais testée pour voir si nous avions des problèmes sur iOS7 + sans elle (c’est-à-dire qu’ils sont vraiment devenus thread-safe). C'est pourquoi j'ai ajouté le "si vous voulez" ci-dessus.
À mon avis, la mise en cache NSDateFormatter
est une bonne idée si votre application utilise cette application largement ou par le biais de votre application, cela augmentera les performances de votre application. Si vous en avez besoin à 1 ou 2 endroits, ce ne sera pas une bonne idée. Cependant, changer le format de la date n’est pas une bonne idée, cela peut vous conduire à des situations indésirables. (Vous devez suivre le format actuel à chaque fois avant de l'utiliser)
Dans l'une de mes applications, j'ai utilisé un singleton avec trois objets au format de date (tous trois contenant trois formats différents) en tant que propriétés. Et des getters personnalisés pour chaque 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
}
Comme ceci, j'ai également implémenté des accesseurs personnalisés pour les deux autres.
Pourquoi j'ai implémenté des getters personnalisés? Pourquoi je n'ai pas alloué la NSDateFormatter
lors de l'initialisation de singleton?
C'est parce que je ne veux pas les allouer au début même. Je dois l'affecter lorsque c'est nécessaire pour la première fois (à la demande). Dans mon application, les trois variables NSDateFormatters
ne sont pas largement utilisées, c'est pourquoi j'ai choisi un tel modèle pour le mettre en œuvre. (Mon application est en Objective C, c'est pourquoi j'ai utilisé le code Objective C ici)
Est-ce que l'un ou l'autre serait une bonne ou une idée horrible?
L'introduction de singletons (à chaque fois que vous avez besoin d'une nouvelle variante d'un formateur) est pas une bonne solution. Créer des formateurs n’est pas que coûteux.
Au lieu de cela, imaginez des moyens de réutiliser et de partager des instances de formateur et de les transmettre dans votre programme, comme des variables normales. Cette approche est assez simple à introduire dans un programme existant.
Les instruments vous aideront à identifier les endroits où votre programme crée de nombreux formateurs et vous pouvez réfléchir à la façon de les réutiliser en fonction de ces données.
Et le changement de format est-il également coûteux?
Ne dérangez pas les formateurs en mutation que vous partagez, sauf s'ils sont utilisés dans des contextes très spécifiques/locaux (tels qu'une collection de vues particulière). Il sera beaucoup plus facile d’atteindre les résultats attendus. Au lieu de cela, copie puis mute si vous avez besoin d'une variante d'un formateur partagé.
Au lieu d'utiliser un singleton, utilisez l'injection de dépendance. N'oubliez pas de suivre la règle 0, 1, infinie.
http://en.wikipedia.org/wiki/Zero_one_infinity_rule
Ici, nous ne pouvons clairement pas avoir 0, et alors que 1 sonne bien, si vous voulez l’utiliser à partir de plusieurs threads, vous ne pouvez pas en avoir un seul sans le suspendre. Donc, l'infini.
Une bonne façon de procéder consiste simplement à faire attention au nombre que vous en créez, n'en gardez qu'un pour chaque thread que vous ouvrez et assurez-vous qu'ils soient nettoyés dès que vous avez fini de les utiliser.
Pour vous aider, consultez cet autre lien stackoverflow - je crains que ma réponse ne soit alignée sur la leur (réduisez au minimum le nombre de NSDateformatters). Cependant, ils peuvent avoir un raisonnement qui n'était pas couvert dans ce fil (sans jeu de mots!)
Comment minimiser les coûts d'allocation et d'initialisation d'un NSDateFormatter?
Aussi, si je peux me permettre - si vous êtes confronté à cette question, peut-être y a-t-il quelque part dans votre programme où un flux peut être amélioré pour éviter même d'avoir besoin de tant de formateurs?
En Objc:
Considérer que NSDateFormatter est considéré comme dangereux pour les threads, l'utilisation d'un seul outil de formatage dans votre application n'est peut-être pas la meilleure idée. Avoir un formateur par thread peut être une solution. Sinon, vous pouvez envisager de placer le formateur dans une classe thread-safe.
D'après Apple Docs: https://developer.Apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
En rapide:
Si Swift offre la sécurité du fil de la classe, il ne devrait y avoir aucun problème avec une instance.
J'aimerais également développer la réponse de Mobile Ben en fournissant un exemple:
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
}
}
}
Ceci est utilisé dans une bibliothèque, c'est pourquoi vous pouvez voir le modificateur public partout.
Puisque Swift utilise des méthodes de répartition uniques pour la création de propriétés statiques, il est très rapide et sûr de créer DateFormatter
de cette manière.
extension DateFormatter {
static let shortFormatDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}
Que d'écrire
date = DateFormatter.shortFormatDateFormatter.string(from: json["date"])
Exemple rapide
Basé sur la réponse de @Mobile Ben: ceci est un exemple d'un simple singleton 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
}
}