【问题标题】:swift 3: How to extend [UInt8] to add getUInt32BE functionswift 3:如何扩展 [UInt8] 以添加 getUInt32BE 功能
【发布时间】:2016-04-26 08:06:07
【问题描述】:

我正在尝试使用getUInt32BE() 函数扩展[UInt8],如下代码所示。

这是我收到的错误:

"不能用类型索引为 'Self' 类型的值下标 '范围'"

有人可以帮我纠正这个错误吗?

extension Collection where Iterator.Element == UInt8 {
    public func getUInt32BE(at: Int = 0) -> UInt32 {
        return self[at..<at+4].reduce(0) {
          $0 << 8 + UInt32($1)
        }
    }
}

提前致谢:)

【问题讨论】:

    标签: swift bit-manipulation swift3


    【解决方案1】:

    备选方案#1:使用UnsafePointer 的.pointee 属性

    如以下问答中所述

    您可以在 Swift 2.2 中使用,例如

    UnsafePointer<UInt16>(bytes).memory
    

    将 2 字节 UInt8 数组转换为 UInt16 值(主机字节顺序)。

    现在,在 Swift 3.0-dev 中,.memory 已被 .pointee 取代。应用上述以及此更改,我们同样可以使用 UnsafePointer&lt;UInt32&gt;(bytes).pointee 访问 4 字节 UInt8 数组的 UInt32 表示,但请注意该表示使用主机字节顺序进行转换。为了可能(如有必要)将其转换为整数的大端表示,我们可以使用每个整数类型可用的 .bigEndian 属性,如下面的问答中所述:

    这在 Swift 3.0-dev 中仍然有效,因此,我们可以如下构造您的 getUInt32BE(...) 方法

    extension Collection where Iterator.Element == UInt8 {
        public func getUInt32BE(at: Index.Distance) -> UInt32? {
            let from = startIndex.advanced(by: at, limit: endIndex)
            let to = from.advanced(by: 4, limit: endIndex)
            
            guard case let bytes = Array(self[from..<to]) 
                where bytes.count == 4 else { return nil }
            
            return UnsafePointer<UInt32>(bytes).pointee.bigEndian
        }
    }
    

    备选方案 #2:使用位移

    或者,使用带有位移的上述修改版本(类似于您的问题的尝试)而不是使用UnsafePointer&lt;..&gt;:

    extension Collection where Iterator.Element == UInt8 {
    
        public func getUInt32BE(at: Index.Distance) -> UInt32? {
            let from = startIndex.advanced(by: at, limit: endIndex)
            let to = from.advanced(by: 4, limit: endIndex)
            
            guard case let bytesSlice = self[from..<to] 
                where from.distance(to: to) == 4 else { return nil }
            
            return bytesSlice.reduce(0) { (tot, val) -> UInt32 in
                tot << 8 + UInt32(val as! UInt8)
            }
        }
    }
    

    请注意,我们需要通过将 val 从 Iterator.Element 强制转换为 UInt8 来帮助编译器(由于扩展的 where 子句,保证成功;Iterator.Element == UInt8)。


    示例用法

    上述两种替代方法的示例用法:

    /* example usage */
    let bytes: [UInt8] = [
        0,   // 0b 0000 0000
        255, // 0b 1111 1111 
        0,   // 0b 0000 0000
        104, // 0b 0110 1000
        76]  // 0b 0100 1100
        
    /*  byteArr[1..<5], big-endian:
            0b 1111 1111 0000 0000 0110 1000 0100 1100 */
    let u32verify = 0b11111111000000000110100001001100 
    print(u32verify) // 4278216780
        
    if let u32val = bytes.getUInt32BE(1) {
        print(u32val) // 4278216780, OK
    }
    

    有关Collection协议的详细信息(您上面错误的原因是没有使用该协议的关联类型Index.Distance来构造您的子数组),请参见例如

    【讨论】:

    • @yielding 乐于助人。请注意,我已经稍微更新了答案,现在使用.bigEndian 属性(而不是反转bytes 数组),而之前的答案假设主机字节顺序是小端(不是很通用,但不是固定的)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    相关资源
    最近更新 更多