【问题标题】:Swift protocol extension with specific Self type具有特定 Self 类型的 Swift 协议扩展
【发布时间】:2021-09-20 01:41:34
【问题描述】:

我为 UnsignedInteger 协议添加了一个扩展,以添加一个以十六进制格式表示数字的十六进制方法。我还希望特定的符合结构具有参数的默认值。下面是我写的。

extension UnsignedInteger {
    func hex(withFieldWidth fieldWidth: Int, andUseUppercase uppercase: Bool = true) -> String {
        return String(format: "%0\(fieldWidth)\(uppercase ? "X" : "x")", self as! CVarArg)
    }
}

extension UnsignedInteger where Self == UInt8 {
    func hex(withFieldWidth fieldWidth: Int = 2, andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: fieldWidth, andUseUppercase: uppercase)
    }
}

extension UnsignedInteger where Self == UInt16 {
    func hex(withFieldWidth fieldWidth: Int = 4, andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: fieldWidth, andUseUppercase: uppercase)
    }
}

但是,对于 UInt8 和 UInt16 特定的扩展,它似乎在调用自身而不是第一个扩展块中的十六进制,正如我收到的 UInt8 和 UInt16 块的警告消息所解释的那样:All paths through this function will call itself

如果我从 UInt8 和 UInt16 块中删除 fieldWidh,调用十六进制(带有fieldWidth 的硬编码值)似乎编译得很好,我相信这种方式是从第一个扩展块调用十六进制方法。下面是编译好的代码。

extension UnsignedInteger {
    func hex(withFieldWidth fieldWidth: Int, andUseUppercase uppercase: Bool = true) -> String {
        return String(format: "%0\(fieldWidth)\(uppercase ? "X" : "x")", self as! CVarArg)
    }
}

extension UnsignedInteger where Self == UInt8 {
    func hex(andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: 2, andUseUppercase: uppercase)
    }
}

extension UnsignedInteger where Self == UInt16 {
    func hex(andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: 4, andUseUppercase: uppercase)
    }
}

在进行协议扩展时,有没有办法为特定的符合结构的参数指定默认值?

【问题讨论】:

    标签: swift protocols protocol-extension


    【解决方案1】:

    这个问题已经得到解答,但可以改进。无需强制转换为 CVarArg,您可以简单地添加约束 where Self: CVarArg。您还可以将字符数作为字符串初始值设定项的 CVarArg 传递,并在您的字符串格式中使用 *


    extension UnsignedInteger where Self: CVarArg {
        func hex(uppercase: Bool = true) -> String {
            .init(format: "%0*\(uppercase ? "X" : "x")", bitWidth / 4, self)
        }
    }
    

    UInt8(255).hex()   // "FF"
    UInt8(255).hex(uppercase: false)   // "ff"
    UInt16(255).hex()  // "00FF"
    UInt16(255).hex(uppercase: false)  // "00ff"
    UInt32(255).hex()  // "000000FF"
    UInt32(255).hex(uppercase: false)  // "000000ff"
    

    【讨论】:

    • 是的,我没有注意到 UInt 类型符合它,这将让我避免强制转换 self.谢谢@Leo Dabus。
    【解决方案2】:

    在进行协议扩展时,有没有办法为特定的符合结构的参数指定默认值?

    您已经在问题中强调了这种方法的问题。


    我怎样才能以不同的方式解决它?

    UnsignedInteger 继承自 BinaryInteger,可以为您提供 bitWidth 信息(UInt8 => 8UInt16 => 16 等等)。

    extension UnsignedInteger {
        func hex(uppercase: Bool = true) -> String {
            let fieldWidth = self.bitWidth / 4
            return String(format: "%0\(fieldWidth)\(uppercase ? "X" : "x")", self as! CVarArg)
        }
    }
    

    以上使其适用于UIntUInt8UInt16UInt32UInt64


    更进一步,您可以使用FixedWidthInteger 执行此操作,现在它将适用于所有有符号和无符号整数。

    【讨论】:

    • 感谢@Tarun Tyagi,正如您所提到的,我使用 bitWidth 为参数设置了默认值,并且它起作用了。但是如果没有 bitWidth 字段怎么办,我正在尝试的东西有可能吗?
    • @hakunamatata Swift 编译器将正确地选择一个函数的更专业的实现(UInt8UInt16),而不是选择一个基本实现(UnsignedInteger 在你的情况下)。因此,当您使用与基本实现完全相同的签名调用hex(... 时,它将始终选择当前实现而不是基本实现。这将在运行时本身成为一个无限循环 - 所以编译器会提前警告你这个问题。
    • @hakunamatata 唯一的解决方案是以有意义的方式更改函数签名(不仅仅是添加默认值),就像您在第二版中所做的那样 - 您不想这样做。您希望拥有相同的函数签名。
    猜你喜欢
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    相关资源
    最近更新 更多