在Swift中将NSData转换为整数

8
在Objective-C中,代码看起来像这样,并且运行得非常完美:
NSInteger random = arc4random_uniform(99) + 1 
NSData *data = [NSData dataWithBytes:& random length: sizeof(random)];
int value = *(int*)([data bytes]);

这在Swift中怎么实现呢?
7个回答

16

就像这样:

var src: NSInteger = 2525
var out: NSInteger = 0

let data = NSData(bytes: &src, length: sizeof(NSInteger))
data.getBytes(&out, length: sizeof(NSInteger))
println(out) // ==> 2525

绝对完美!谢谢! - dhint4
1
sizeofValue(random) - newacct
在Swift3中:sizeof(NSInteger)变成了MemoryLayout<NSInteger>.size,而println变成了print。 - parleer
请注意,在Swift 3或更高版本中,从类型而不是对象直接获取大小的方法是MemoryLayout.size(ofValue: value),而不是sizeofValue - Leo Dabus

10

在Swift 4中:

Data流中提取整数值时,需要考虑一些问题,如有符号性字节序。因此,我创建了一个函数,在Data的扩展中推断出要提取的整数类型的有符号性,并将字节序索引作为参数传递。可以提取的整数类型都符合FixedWidthInteger协议。

提醒:此函数不会检查索引范围是否在Data缓冲区的边界内,因此根据提取的类型大小与缓冲区末尾的关系,可能会导致崩溃。

extension Data {
    enum Endianness {
        case BigEndian
        case LittleEndian
    }
    func scanValue<T: FixedWidthInteger>(at index: Data.Index, endianess: Endianness) -> T {
        let number: T = self.subdata(in: index..<index + MemoryLayout<T>.size).withUnsafeBytes({ $0.pointee })
        switch endianess {
        case .BigEndian:
            return number.bigEndian
        case .LittleEndian:
            return number.littleEndian
        }
    }
}

示例:

let data = Data(bytes: [0xFF,0x1F,0x1F,0xFF])

let number1 = data.scanValue(at: 0, endianess: .LittleEndian) as UInt16
let number2 = data.scanValue(at: 0, endianess: .BigEndian) as UInt16

let number3: Int16 = data.scanValue(at: 2, endianess: .LittleEndian)
let number4: Int16 = data.scanValue(at: 2, endianess: .BigEndian)

结果:

number1为8191
number2为65311
number3为-225
number4为8191

观察函数调用以了解如何推断要提取的类型。当然,对于Int8UInt8来说,字节序没有意义,但是该函数按预期工作。

如果需要,可以稍后将值转换为Int


嗨 - 你有没有更简单的解决方案,只将数据<02>转换为UInt8 2或Int 2? - richc
1
@richc:获取 UInt8 的最简单方法是,如果您知道要获取的字节的确切索引,只需使用下标符号 data[index] - Gustavo Seidler
Data.Index inside a Data extension is redundant. Use just Index - Leo Dabus
我认为你应该使用 MemoryLayout.stride 而不是 size。参见文档中对 stride 的定义,即“在连续的内存或 Array<T> 中存储时,从一个 T 实例的开始到下一个实例的开始的字节数”,以及“当 UnsafePointer<T> 实例增加时移动的字节数”。 - Bill

7

您可以扩展数据类型,创建通用方法,获取字节并强制转换或根据需要显式设置结果类型:

extension Data {
    func object<T>(at index: Index = 0) -> T {
        subdata(in: index..<self.index(index, offsetBy: MemoryLayout<T>.size))
        .withUnsafeBytes { $0.load(as: T.self) }
    }
}

extension Numeric {
    var data: Data {
        var source = self
        return Data(bytes: &source, count: MemoryLayout<Self>.size)
    }
}

let data = Data([0xFF, 0x1F])   // 2 bytes

let uint16: UInt16 = data.object()      // 8191  littleEndian
let number1 = uint16.littleEndian       // 8191
let number2 = uint16.bigEndian          // 65311

let int16 = data.object() as Int16      // 8191   littleEndian
let number3 = int16.littleEndian        // 8191
let number4 = int16.bigEndian           // -225

print(number1) // 8191
print(number2) // 65311
print(number3) // 8191
print(number4) // -225

使用Int进行测试

let random = Int.random(in: 1...100)    // 15 UInt32
let data = random.data                  // 8 bytes  [15, 0, 0, 0, 0, 0, 0, 0]

使用 UInt32 进行测试

let random = UInt32.random(in: 1...100)  // 90 UInt32
let data = random.data                   // 4 bytes  [90, 0, 0, 0]

使用Double进行测试

let random = Double.random(in: 0...1)  // 0.2463145485351322 Double
let data = random.data                 // 8 bytes  [12, 99, 62, 49, 60, 135, 207, 63]

如果您想提取子数据:
let data = Data([0xFF, 0x1F, 0x1F, 0xFF])      // 4 bytes

let uint16: UInt16 = data.object(at: 2)        //  65311 littleEndian
let number1 = uint16.littleEndian              // 65311
let number2 = uint16.bigEndian                 // 8191

let int16: Int16 = data.object(at: 2)          // -225   littleEndian
let number3 = int16.littleEndian               // -225
let number4 = int16.bigEndian                  // 8191

number1 // 65311
number2 // 8191
number3 // -225
number4 // 8191

6

对于Swift 3,您可以这样做(小端序,但大端序类似):

func getInt(fromData data: Data, start: Int) -> Int32 {
  let intBits = data.withUnsafeBytes({(bytePointer: UnsafePointer<UInt8>) -> Int32 in
    bytePointer.advanced(by: start).withMemoryRebound(to: Int32.self, capacity: 4) { pointer in
      return pointer.pointee
    }
  })
  return Int32(littleEndian: intBits)
}

你可以修改这个代码,添加泛型等来适应其他基本数据类型(并根据数据字节的字节顺序进行变化)。

这确实是让我对Swift感到烦恼的一件事。对于一个从技术上来说非常简单的东西来说,这就变得极其复杂了。我想知道编译器是否会优化所有无用的东西。 - Antwan van Houdt

5

如果您经常这样做,您可能会发现这种方法很有用:

func readInteger<T : IntegerType>(data : NSData, start : Int) -> T {
    var d : T = 0
    data.getBytes(&d, range: NSRange(location: start, length: sizeof(T)))
    return d
}

该函数以数据中要读取的数值类型的起始位置作为参数,并返回根据你要分配的类型推断出的值。

例如:

let i : UInt32 = readInteger(data, 10);

从数据的第10个位置读取4字节的整数。

如果您将UInt32更改为UInt16,则会读取两个字节。


请详细说明如何使用它以及需要传递哪些参数...谢谢。 - Juan Boero

1

我的贡献是使用 Swift 3.1 扩展:

extension NSData{
    var int :  Int{
        var out: Int = 0
        self.getBytes(&out, length: MemoryLayout<Int>.size)
        return out
    }
}

只需调用 .int 方法即可获取您的值,就像这样:

let value = 50
let data = NSData(bytes: &value, length: 4)
print(data.int)

0

数据转整数,感谢 @rghome

// MARK: - Extensions Data

extension Data {

    /// To interger Data by range
    ///
    /// - Parameters:
    ///   - data:       Data
    ///   - startRange: StartRange
    ///   - endRange:   EndRange
    /// - Returns:      Integer Typed
    func toInterger<T : Integer>(withData data: NSData, withStartRange startRange: Int, withSizeRange endRange: Int) -> T {
        var d : T = 0
        (self as NSData).getBytes(&d, range: NSRange(location: startRange, length: endRange))
        return d
    }
}

101010
let value:Int = Data().toInterger(withStartRange: 0, withEndRange: 2)
get 10
get 2 Int

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接