2015-12-15 3 views
3

Как мы все знаем, CGFloat (который повсеместно в CoreGraphics, UIKit и т.д.) может быть 32-битное или 64-битное число с плавающей запятой, в зависимости от архитектуры процессора ,Преобразование между CGFloat и NSNumber без ненужного продвижения Удвоить

В C, CGFloat это typealias к float или double, в Свифта он определен как struct CGFloat с native собственности (который является Float или Double).

Было замечено неоднократно, что NSNumber может быть создан из и преобразован в Float и Double, но существует не подобных преобразований от и до CGFloat. Общие рекомендации (например, в Convert CGFloat to NSNumber in Swift) является обращенного через Double

CGFloat <--> Double <--> NSNumber 

Пример:

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

и что является простым и правильным, нет точного не теряется. Также большинство платформ сегодня 64-бит, а затем преобразование CGFloat/Double тривиально и, вероятно, оптимизировано компилятором.

Однако это вызвало мое любопытство, если преобразование может быть сделано без продвижения CGFloat к Double на 32-битных платформах.

Можно было бы использовать заявление в сборки конфигурации (как, например, в 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 
    } 
} 

Но что, если Swift портирован на другие архитектуры, которые не являются Intel или ARM? Это не выглядит очень перспективным.

Можно также использовать константу CGFLOAT_IS_DOUBLE (как, например, в NSNumber from CGFloat):

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

Недостатком здесь является то, что компилятор всегда будет излучать «никогда не будет выполнена» предупреждение на одном из случаев.

Итак, чтобы сделать длинную историю короткой:

  • Как мы можем конвертировать между CGFloat и NSNumber безопасным способом, без предупреждений компилятора, и без ненужного продвижения к Double?

Обратите внимание, что это означает «академическую» проблему. Как упоминалось выше, (и в других Q & A) можно просто преобразовать через Double практически.

Я размещаю здесь «self-answer» в духе share your knowledge, Q&A-style. Конечно, другие ответы приветствуются!

ответ

8

Update: Можно привести значение CGFloat к NSNumber и обратно:

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

Это сохраняет точность CGFloat и работает с Swift 2 и Swift 3.


(Предыдущий ответ - слишком сложный): Есть два решения, которые я нашел. В первом случае используется бесплатное мостовое соединение между NSNumber и CFNumber (как в What is most common and correct practice to get a CGFloat from an NSNumber? 10 для объекта Цель-C). Он использует тот факт, что CFNumberимеет выделенный режим преобразования для CGFloat значений:

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 
    } 
} 

Это является простым и приятным. Единственный недостаток: я не мог вычислить , как сделать конструктор init вместо class method.

Второе возможное решение немного дольше:

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()) 
    } 
} 

Есть два частных «хелперы» INIT методы с тем же внешним именем параметра doubleOrFloat, но с разными типами параметров. От фактического типа cgFloat.native компилятор определяет один вызвать в

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

ту же идею в методе аксессора. От типа self.native компилятор определяет, какой из двух doubleOrFloatValue() методов для вызова в

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

Следует отметить, что в апреле 2017 года, с Swift 3.1 выпущен, бросок между NSNumber и CGFloat может произойти сбой во время выполнения и станет проверенным актером в Swift 4, поэтому более запутанным решением может стать путь. –

 Смежные вопросы

  • Нет связанных вопросов^_^