CGFloat和NSNumber之间的转换没有不必要的升级到Double

时间:2015-12-15 23:44:27

标签: swift nsnumber cgfloat

众所周知,CGFloat(在CoreGraphics,UIKit等中无处不在) 可以是32位或64位浮点数,具体取决于 处理器架构。

在C中,CGFloat它是一个类型 至floatdouble,在Swift中定义为struct CGFloat native属性(FloatDouble)。

反复观察到可以创建NSNumber 并转换为FloatDouble,但不存在 来自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 转换很简单,可能由编译器优化。

然而,如果可以进行转换,它会引起我的好奇心 没有在32位平台上宣传CGFloatDouble

可以使用构建配置语句(例如在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移植到其他没有的架构上会怎么样呢 英特尔还是ARM?这看起来不太适合未来。

也可以使用CGFLOAT_IS_DOUBLE常数(例如,在...中) NSNumber from CGFloat):

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

这里的缺点是编译器总会发出一个 &#34;永远不会执行&#34; 警告其中一个案例。

所以长话短说:

  • 我们如何在两者之间进行转换 CGFloatNSNumber以安全的方式,没有编译器警告, 没有不必要的促销Double

请注意,这是一个&#34;学术&#34;问题。如上所述 以上(以及其他Q&amp; A&#39; s)可以通过Double进行转换 实际上。

我发布了一个&#34;自我回答&#34;在这里的精神 share your knowledge, Q&A-style。当然欢迎其他答案!

2 个答案:

答案 0 :(得分:12)

更新:可以将CGFloat值转换为NSNumber并返回:

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

这保留了CGFloat的精度,并与Swift 2一起使用 和斯威夫特3。

(以前的答案 - 太复杂了):我找到了两个解决方案。第一个使用免费桥接 在NSNumberCFNumber之间(如What is most common and correct practice to get a CGFloat from an NSNumber?中所示) for Objective-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())
    }
}

有两个私人&#34;帮手&#34; init方法与外部相同 参数名称doubleOrFloat但参数类型不同。从实际出发 编译器确定调用哪一个cgFloat.native的类型 在

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

访问器方法中的相同想法。来自self.native的类型 编译器确定两个doubleOrFloatValue()中的哪一个 要调用的方法

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

答案 1 :(得分:0)

let num = NSNumber(double: 1.0)
let c: CGFloat? = CGFloat(exactly: num)

这是初始化程序的documentation