Les mesures et unités en Swift

Aujourd’hui nous allons vous présenter une API du framework Foundation permettant de manipuler les mesures et les unités de manière simple et élégante.

Les mesures

Avant de commencer, qu’est-ce qu’une mesure ? Wikipedia dit :

Mesurer une grandeur, c’est la comparer à une autre grandeur de même espèce prise comme unité.

https://fr.wikipedia.org/wiki/Mesure_physique

En Swift, c’est simplement un couple entre une valeur (un nombre) et une unité (un objet ou un évènement) :

public struct Measurement<UnitType>: ReferenceConvertible, Comparable, Equatable where UnitType: Unit {
  public init(value: Double, unit: UnitType)
}

public class Unit : NSObject, NSCopying, NSSecureCoding {
  public init(symbol: String)
}

Une mesure (Measurement) est la classe racine que vous aller utiliser afin de manipuler une quantité d’unités comme par exemple « 1 km », « 24 degrés » ou « 56 kilos ». Voici un exemple avec le type d’unité de longueur UnitLength :

// Définition des distances
let distanceParcouru   = Measurement(value: 5, unit: UnitLength.feet)
let distanceAParcourir = Measurement(value: 6, unit: UnitLength.feet)

La librairie de mesure fournit tous les opérateurs basiques pour les manipuler de manière naturelle :

// Maintenant jouons avec les distances
let distanceTotal  = distanceParcouru + distanceAParcourir
let distanceTriple = 3 * distanceAParcourir
let distanceMoitié = distanceAParcourir / 2

Les dimensions

Une unité (Unit) en Swift, est une abstraction et non une classe concrete. Pour créer une unité vous devrez hériter de l’objet Dimension. Une dimension contient un « symbole » et il gère toute la logique pour faire les conversions entre chaque unité de même type (par exemple km en ft, m en milles, etc.). Voici sa définition :

public class Dimension : Unit, NSSecureCoding {
  public init(symbol: String, converter: UnitConverter)
  open class func baseUnit() -> Self
}

Quand vous créer une unité (Unit) vous devez définir un symbole (par exemple « kilometers », « pounds », « radians », etc.), un convertisseur d’unité et une unité de base. A convertisseur d’unité permet de faire les conversions nécessaires entre différentes unités de base. Par exemple, 1000 mètres = 1 kilomètre. L’unité de base de UnitLength est le mètre, et donc les conversions se font relativement par rapport à l’unité de base :

let mètre = UnitLength.baseUnit()

Voici maintenant un exemple d’implémentation d’une Unit, le UnitLength :

public class UnitLength: Dimension, NSSecureCoding {
  /*
    Base unit - meters
  */
  @NSCopying open class var kilometers: UnitLength { get }
  @NSCopying open class var meters: UnitLength { get }
  @NSCopying open class var feet: UnitLength { get }
  @NSCopying open class var miles: UnitLength { get }
  @NSCopying open class var astronomicalUnits: UnitLength { get }
  // ... 
}

Chaque Unit contient ses propres échelles de valeur et d’unité.

Voici une liste d’unités concrète fournit nativement par la librairie d’Apple (ce sont des singletons correspondant à la plupart des unités du Système International d’unités) :

  • UnitAcceleration: metersPerSecondSquared, gravity.
  • UnitAngle: degrees, radians, gradians, etc.
  • UnitArea: squareMeters, squareInches, ares, etc.
  • UnitConcentrationMass: gramsPerLiter, milligramsPerDeciliter, etc.
  • UnitDispersion: partsPerMillion.
  • UnitDuration: seconds, minutes, hours.
  • UnitElectricCharge: coulombs, ampereHours, etc.
  • UnitElectricCurrent: amperes, etc.
  • UnitElectricPotentialDifference:
  • UnitElectricResistance: volts, etc.
  • UnitEnergy: joules, calories, etc.
  • UnitFrequency: hertz, etc.
  • UnitFuelEfficiency: milesPerGallon, litersPer100Kilometers, etc.
  • UnitLength: meters, feet, lightyears, etc.
  • UnitIlluminance: lux.
  • UnitMass: kilograms, pounds, carats, etc.
  • UnitPower: watts, horsepower, etc.
  • UnitPressure: newtonsPerMeterSquared, kilopascals, bars, etc.
  • UnitSpeed: metersPerSecond, knots, etc.
  • UnitTemperature: kelvin, celcius, fahrenheit, etc.
  • UnitVolume: liters, cubicMeters, gallons, etc.

La librairie gère de manière implicite les conversions et les comparaison entre les différentes mesures :

let distanceParcouru = Measurement(value: 3500, unit: UnitLength.feet)
let distanceRestante = Measurement(value: 5, unit: UnitLength.kilometers)

// Implicit Conversion
let totalDistance = distanceParcouru + distanceRestante
print(totalDistance) // 6066,8 m

var marqueur: String = ""

// Comparison Operators
if (distanceParcouru > distanceRestante) {
  marqueur = "Presque arrivé !"
}
else if (distanceParcouru < distanceRestante) {
  marqueur = "À peine commencé !"
}
else {
  marqueur = "À mi-chemin !"
}

print(marqueur) // affiche "À peine commencé !"

Pour plus d'information vous pouvez aller voir la documentation officielle.

Définition des unités

Dans cette section nous allons voir comment créer nos propres unités (Unit). Pour l'illustrer nous allons comparer les animaux en fonction de leur taille.

Commençons par créer une unité UnitAnimalSize :

public class UnitAnimalSize: Dimension {
  static let rat = UnitAnimalSize(symbol: "🐀", converter: UnitConverterLinear(coefficient: 1))
  static let lapin = UnitAnimalSize(symbol: "🐇", converter: UnitConverterLinear(coefficient: 2))
  static let chèvre = UnitAnimalSize(symbol: "🐐", converter: UnitConverterLinear(coefficient: 3))
  static let éléphant = UnitAnimalSize(symbol: "🐘", converter: UnitConverterLinear(coefficient: 4))
  
  public override class func baseUnit() -> UnitAnimalSize {
    return UnitAnimalSize.rat
  }
}

La conversion est gérer implicitement grâce à UnitConverterLinear. Ici, par exemple, 1 🐘 = 4 🐀 or 1 🐘 = 2 🐇. À noter qu'il faut bien préciser l'unité de base, ici c'est le rat 🐀.

let lapin = Measurement(value: 1, unit: UnitAnimalSize.lapin)
let éléphant = Measurement(value: 1, unit: UnitAnimalSize.éléphant)

if 2 * lapin == éléphant {
  print("1 élépant est équivalent à 2 lappins")
}

Comme vous pouvez le constater c'est plutôt simple à utiliser.

Formatter les mesures

Maintenant une des fonctionnalités les plus interessante apporté par cette librairie c'est la localisation ! En effet dans la plupart des cas vous aurez besoin d'afficher les informations aux utilisateurs. Mais si votre application est multi-langue et que vos utilisateurs vivent aux 4 coins du monde il faudra afficher les informations dans leur langue pour que les informations soient bien comprise. Il faudra donc prendre en considération leur langue et leurs conventions. Par exemple pour afficher 5 kilomètres, au Canada nous devrons afficher "5 km", en Égypte "٥ كم" et aux États-unis "3.1 mi".

Voici comment utiliser MeasurementFormatter pour réaliser automatiquement ces conversions :

let formatter = MeasurementFormatter()
let distance  = Measurement(value: 5, unit: UnitLength.kilometers) as Measurement
let result    = formatter.string(from: distance)

// résultat : 3.1 mi - aux États-Unis
// résultat : 5 km - en France

Bien sûr vous pouvez aussi configurer les .providedUnit et les .naturalScale ainsi que le .locale comme les autres formateurs pour afficher des chaines de caractères personnalisés. Pour plus d'information allez vois la documentation officielle.

Conclusion

Comme vous pouvez le constater, Apple nous fournit un framework vraiment simple pour travailler avec les mesures et les unités ! Vous pouvez utiliser les unités déjà fournies et / ou créer les vôtres. Pour les unités déjà fournies, la localisation et le style de formatage sont déjà gérés pour nous, ce qui rend sont utilisation en plus simple dans un contexte multi-langue.

Amusez-vous bien !

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (1 votes, average: 5,00 out of 5)
Loading...

Aucun commentaire

Time limit is exhausted. Please reload CAPTCHA.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.