2015-12-15 4 views
3

Comme nous le savons tous, CGFloat (qui est omniprésent dans CoreGraphics, UIKit etc) peut être un 32 bits ou nombre à virgule flottante 64 bits, en fonction de l'architecture du processeur .Conversion entre CGFloat et NSNumber sans promotion inutile de doubler

En C, CGFloat il est un typealias float ou à double, à Swift est-il défini comme un struct CGFloat avec une propriété native (qui est Float ou Double).

Il a été observé à plusieurs reprises qu'un NSNumber peut être créé à partir et converti en Float et Double, mais qu'il n'y existe pas conversions similaires en provenance et à CGFloat. Le conseil général (par exemple dans Convert CGFloat to NSNumber in Swift) est de converti par Double

CGFloat <--> Double <--> NSNumber 

Exemple:

let c1 = CGFloat(12.3) 
let num = NSNumber(double: Double(c1)) 
let c2 = CGFloat(num.doubleValue) 

et qui est simple et correcte, aucune précision est perdue. De nos jours, la plupart des plateformes sont 64 bits, et la conversion CGFloat/Double est triviale et probablement optimisée par le compilateur.

Cependant, il a suscité ma curiosité si une conversion peut se faire sans promotion CGFloat-Double sur les plates-formes 32 bits.

On pourrait utiliser une instruction de configuration de construction (comme par exemple dans Should conditional compilation be used to cope with difference in CGFloat on different architectures?):

extension NSNumber { 
    convenience init(cgFloatValue value : CGFloat) { 
     #if arch(x86_64) || arch(arm64) 
      self.init(double: value.native) 
     #else 
      self.init(float: value.native) 
     #endif 
    } 
} 

Mais si Swift est porté sur d'autres architectures qui ne sont pas Intel ou ARM? Cela ne semble pas très à l'épreuve du futur.

On pourrait aussi utiliser la CGFLOAT_IS_DOUBLE constante (comme par exemple dans NSNumber from CGFloat):

if CGFLOAT_IS_DOUBLE != 0 { 
     // ... 
    } else { 
     // ... 
    } 

L'inconvénient est que le compilateur émet toujours un « ne sera jamais exécuté » avertissement sur l'un des les cas.

Donc, pour faire la longue histoire courte:

  • Comment peut-on convertir entre CGFloat et NSNumber de manière sûre, sans avertissement du compilateur, et sans promotion inutile de Double?

Veuillez noter qu'il s'agit d'un problème «académique». Comme mentionné ci-dessus (et dans d'autres Q & A) on peut simplement convertir via Double pratiquement.

Je poste une "réponse personnelle" ici dans l'esprit de share your knowledge, Q&A-style. Bien sûr, d'autres réponses sont les bienvenues!

Répondre

8

Mise à jour: On peut associer une valeur CGFloat-NSNumber et retour:

let c1 = CGFloat(12.3) 
let num = c1 as NSNumber 
let c2 = num as CGFloat 

Cela préserve la précision des CGFloat et travaille avec Swift et Swift 2 3.


(Réponse précédente - beaucoup trop compliqué): Il y a deux solutions que j'ai trouvées. Le premier utilise le pont sans frais entre NSNumber et CFNumber (comme dans What is most common and correct practice to get a CGFloat from an NSNumber? pour Objective-C). Il utilise le fait que CFNumbera un mode de conversion dédié pour CGFloat valeurs:

extension NSNumber { 

    // CGFloat -> NSNumber 
    class func numberWithCGFloat(var value: CGFloat) -> NSNumber { 
     return CFNumberCreate(nil , .CGFloatType, &value) 
    } 

    // NSNumber -> CGFloat 
    var cgFloatValue : CGFloat { 
     var value : CGFloat = 0 
     CFNumberGetValue(self, .CGFloatType, &value) 
     return value 
    } 
} 

simple et agréable. Le seul inconvénient: je ne pouvais pas comprendre comment faire du constructeur une méthode init au lieu d'un class method.

La deuxième solution est un peu plus:

extension NSNumber { 

    // CGFloat -> NSNumber 
    private convenience init(doubleOrFloat d : Double) { 
     self.init(double : d) 
    } 
    private convenience init(doubleOrFloat f : Float) { 
     self.init(float : f) 
    } 
    convenience init(cgFloat : CGFloat) { 
     self.init(doubleOrFloat: cgFloat.native) 
    } 

    // NSNumber -> CGFloat 
    private func doubleOrFloatValue() -> Double { 
     return self.doubleValue 
    } 
    private func doubleOrFloatValue() -> Float { 
     return self.floatValue 
    } 
    var cgFloatValue : CGFloat { 
     return CGFloat(floatLiteral: doubleOrFloatValue()) 
    } 
} 

Il y a deux « aide » privée avec des méthodes d'initialisation du même nom de paramètre externe doubleOrFloat mais différents types de paramètres. De type réelle de cgFloat.native le compilateur détermine que l'on appeler dans

convenience init(cgFloat : CGFloat) { 
     self.init(doubleOrFloat: cgFloat.native) 
    } 

Même idée dans la méthode accesseur. Le type de self.native le compilateur détermine lequel des deux doubleOrFloatValue() méthodes pour appeler à

var cgFloatValue : CGFloat { 
     return CGFloat(floatLiteral: doubleOrFloatValue()) 
    } 
+1

Il convient de noter que d'Avril 2017, avec Swift 3.1 publiée, la distribution entre NSNumber et CGFloat peut échouer à l'exécution et deviendra un casting coché dans Swift 4, donc la solution la plus compliquée pourrait être le chemin à parcourir. –