【问题标题】:Conversion between CGFloat and NSNumber without unnecessary promotion to DoubleCGFloat 和 NSNumber 之间的转换,无需对 Double 进行不必要的提升
【发布时间】:2016-03-21 23:21:01
【问题描述】:

众所周知,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 位平台上将 CGFloat 提升为 Double

可以使用构建配置语句(例如在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 {
        // ...
    }

这里的缺点是编译器总是会发出一个 “永远不会被执行”警告其中一种情况。

长话短说:

  • 我们如何转换 CGFloatNSNumber 以安全的方式,没有编译器警告, 并且没有对Double的不必要的提升?

请注意,这是一个“学术”问题。如前所述 上面(以及其他问答中)可以通过Double 简单地转换 实际上。

本着 share your knowledge, Q&A-style。当然也欢迎其他答案!

【问题讨论】:

    标签: swift nsnumber cgfloat


    【解决方案1】:

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

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

    这保留了CGFloat 的精度并适用于 Swift 2 和 Swift 3。


    (上一个答案——太复杂了):我找到了两种解决方案。第一个使用免费桥接 在NSNumberCFNumber 之间(如What is most common and correct practice to get a CGFloat from an NSNumber? 对于目标-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())
        }
    }
    

    有两个私有的“helper”初始化方法具有相同的外部 参数名称doubleOrFloat,但参数类型不同。从实际 cgFloat.native 的类型编译器决定调用哪一个 在

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

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

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

    【讨论】:

    • 需要注意的是,截至 2017 年 4 月,随着 Swift 3.1 的发布,NSNumber 和 CGFloat 之间的转换可能会在运行时失败,并且在 Swift 4 中会变成检查转换,因此更复杂的解决方案可能是要走的路。
    【解决方案2】:
    let num = NSNumber(double: 1.0)
    let c: CGFloat? = CGFloat(exactly: num)
    

    这里是初始化器的documentation

    【讨论】:

      猜你喜欢
      • 2020-04-17
      • 2014-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-23
      • 1970-01-01
      • 1970-01-01
      • 2021-11-22
      相关资源
      最近更新 更多