我将项目从Objective-C转换为Swift,并且我使用压缩结构来输入通过套接字发送的转换二进制消息:
typedef struct {
uint16_t version; // Message format version, currently 0x0100.
uint32_t length; // Length of data in bytes.
uint16_t reserved; // Reserved for future use.
uint8_t data[]; // Binary encoded plist.
} __attribute__((packed)) mma_msg_t;
我不确定Swift中最好的方法是什么,我能得到的最接近的是:
struct mma_msg {
var version: CUnsignedShort // Message format version, currently 0x0100.
var length: CUnsignedInt // Length of data in bytes.
var reserved: CUnsignedShort // Reserved for future use.
var data: CUnsignedChar[] // Binary encoded plist.
}
翻译中丢失了两个重要的细节:没有保证整数类型的比特,并且没有结构打包。我不认为这可以在Swift中表达,但如果是这样,怎么样?
我对替代方法的建议持开放态度,例如:类似于Python的struct
模块。
答案 0 :(得分:9)
我开始编写一个以Python的struct模块为模型的Swift类,它可以在github上找到MVPCStruct。
第一个原型的代码如下:
enum Endianness {
case littleEndian
case bigEndian
}
// Split a large integer into bytes.
extension Int {
func splitBytes(endianness: Endianness, size: Int) -> UInt8[] {
var bytes = UInt8[]()
var shift: Int
var step: Int
if endianness == .littleEndian {
shift = 0
step = 8
} else {
shift = (size - 1) * 8
step = -8
}
for count in 0..size {
bytes.append(UInt8((self >> shift) & 0xff))
shift += step
}
return bytes
}
}
extension UInt {
func splitBytes(endianness: Endianness, size: Int) -> UInt8[] {
var bytes = UInt8[]()
var shift: Int
var step: Int
if endianness == .littleEndian {
shift = 0
step = 8
} else {
shift = Int((size - 1) * 8)
step = -8
}
for count in 0..size {
bytes.append(UInt8((self >> UInt(shift)) & 0xff))
shift = shift + step
}
return bytes
}
}
class Struct: NSObject {
//class let PAD_BYTE = UInt8(0x00) // error: class variables not yet supported
//class let ERROR_PACKING = -1
class func platformEndianness() -> Endianness {
return .littleEndian
}
// Pack an array of data according to the format string. Return NSData
// or nil if there's an error.
class func pack(format: String, data: AnyObject[], error: NSErrorPointer) -> NSData? {
let PAD_BYTE = UInt8(0x00)
let ERROR_PACKING = -1
var bytes = UInt8[]()
var index = 0
var repeat = 0
var alignment = true
var endianness = Struct.platformEndianness()
// Set error message and return nil.
func failure(message: String) -> NSData? {
if error {
error.memory = NSError(domain: "se.gu.it.GUStructPacker",
code: ERROR_PACKING,
userInfo: [NSLocalizedDescriptionKey: message])
}
return nil
}
// If alignment is requested, emit pad bytes until alignment is
// satisfied.
func padAlignment(size: Int) {
if alignment {
let mask = size - 1
while (bytes.count & mask) != 0 {
bytes.append(PAD_BYTE)
}
}
}
for c in format {
// Integers are repeat counters. Consume and continue.
if let value = String(c).toInt() {
repeat = repeat * 10 + value
continue
}
// Process repeat count values, minimum of 1.
for i in 0..(repeat > 0 ? repeat : 1) {
switch c {
case "@":
endianness = Struct.platformEndianness()
alignment = true
case "=":
endianness = Struct.platformEndianness()
alignment = false
case "<":
endianness = Endianness.littleEndian
alignment = false
case ">":
endianness = Endianness.bigEndian
alignment = false
case "!":
endianness = Endianness.bigEndian
alignment = false
case "x":
bytes.append(PAD_BYTE)
default:
if index >= data.count {
return failure("expected at least \(index) items for packing, got \(data.count)")
}
let rawValue: AnyObject = data[index++]
switch c {
case "c":
if let str = rawValue as? String {
let codePoint = str.utf16[0]
if codePoint < 128 {
bytes.append(UInt8(codePoint))
} else {
return failure("char format requires String of length 1")
}
} else {
return failure("char format requires String of length 1")
}
case "b":
if let value = rawValue as? Int {
if value >= -0x80 && value <= 0x7f {
bytes.append(UInt8(value & 0xff))
} else {
return failure("value outside valid range of Int8")
}
} else {
return failure("cannot convert argument to Int")
}
case "B":
if let value = rawValue as? UInt {
if value > 0xff {
return failure("value outside valid range of UInt8")
} else {
bytes.append(UInt8(value))
}
} else {
return failure("cannot convert argument to UInt")
}
case "?":
if let value = rawValue as? Bool {
if value {
bytes.append(UInt8(1))
} else {
bytes.append(UInt8(0))
}
} else {
return failure("cannot convert argument to Bool")
}
case "h":
if let value = rawValue as? Int {
if value >= -0x8000 && value <= 0x7fff {
padAlignment(2)
bytes.extend(value.splitBytes(endianness, size: 2))
} else {
return failure("value outside valid range of Int16")
}
} else {
return failure("cannot convert argument to Int")
}
case "H":
if let value = rawValue as? UInt {
if value > 0xffff {
return failure("value outside valid range of UInt16")
} else {
padAlignment(2)
bytes.extend(value.splitBytes(endianness, size: 2))
}
} else {
return failure("cannot convert argument to UInt")
}
case "i", "l":
if let value = rawValue as? Int {
if value >= -0x80000000 && value <= 0x7fffffff {
padAlignment(4)
bytes.extend(value.splitBytes(endianness, size: 4))
} else {
return failure("value outside valid range of Int32")
}
} else {
return failure("cannot convert argument to Int")
}
case "I", "L":
if let value = rawValue as? UInt {
if value > 0xffffffff {
return failure("value outside valid range of UInt32")
} else {
padAlignment(4)
bytes.extend(value.splitBytes(endianness, size: 4))
}
} else {
return failure("cannot convert argument to UInt")
}
case "q":
if let value = rawValue as? Int {
padAlignment(8)
bytes.extend(value.splitBytes(endianness, size: 8))
} else {
return failure("cannot convert argument to Int")
}
case "Q":
if let value = rawValue as? UInt {
padAlignment(8)
bytes.extend(value.splitBytes(endianness, size: 8))
} else {
return failure("cannot convert argument to UInt")
}
case "f", "d":
assert(false, "float/double unimplemented")
case "s", "p":
assert(false, "cstring/pstring unimplemented")
case "P":
assert(false, "pointer unimplemented")
default:
return failure("bad character in format")
}
}
}
// Reset the repeat counter.
repeat = 0
}
if index != data.count {
return failure("expected \(index) items for packing, got \(data.count)")
}
return NSData(bytes: bytes, length: bytes.count)
}
}
答案 1 :(得分:6)
我决定尝试这个,因为它看起来很有趣所以我想出了struct.pack(...)
的解决方案。
您还可以对结构中的整数强制执行位大小:
struct MyMessage
{
var version : UInt16
var length : UInt32
var reserved : UInt16
var data : UInt8[]
}
现在进入打包结构...
extension Int
{
func loByte() -> UInt8 { return UInt8(self & 0xFF) }
func hiByte() -> UInt8 { return UInt8((self >> 8) & 0xFF) }
func loWord() -> Int16 { return Int16(self & 0xFFFF) }
func hiWord() -> Int16 { return Int16((self >> 16) & 0xFFFF) }
}
extension Int16
{
func loByte() -> UInt8 { return UInt8(self & 0xFF) }
func hiByte() -> UInt8 { return UInt8((self >> 8) & 0xFF) }
}
extension UInt
{
func loByte() -> UInt8 { return UInt8(self & 0xFF) }
func hiByte() -> UInt8 { return UInt8((self >> 8) & 0xFF) }
func loWord() -> UInt16 { return UInt16(self & 0xFFFF) }
func hiWord() -> UInt16 { return UInt16((self >> 16) & 0xFFFF) }
}
extension UInt16
{
func loByte() -> UInt8 { return UInt8(self & 0xFF) }
func hiByte() -> UInt8 { return UInt8((self >> 8) & 0xFF) }
}
class DataPacker
{
class func pack(format: String, values: AnyObject...) -> String?
{
var bytes = UInt8[]()
var index = 0
for char in format
{
let value : AnyObject! = values[index++]
switch(char)
{
case "h":
bytes.append((value as Int).loByte())
bytes.append((value as Int).hiByte())
case "H":
bytes.append((value as UInt).loByte())
bytes.append((value as UInt).hiByte())
case "i":
bytes.append((value as Int).loWord().loByte())
bytes.append((value as Int).loWord().hiByte())
bytes.append((value as Int).hiWord().loByte())
bytes.append((value as Int).hiWord().hiByte())
case "I":
bytes.append((value as UInt).loWord().loByte())
bytes.append((value as UInt).loWord().hiByte())
bytes.append((value as UInt).hiWord().loByte())
bytes.append((value as UInt).hiWord().hiByte())
default:
println("Unrecognized character: \(char)")
}
}
return String.stringWithBytes(bytes, length: bytes.count, encoding: NSASCIIStringEncoding)
}
}
let packedString = DataPacker.pack("HHI", values: 0x100, 0x0, 512)
println(packedString)
此示例非常简单,没有真正的错误或类型检查。此外,它并没有真正强制执行任何系统字节顺序(字节顺序),因此可能会出现问题。希望这对有兴趣的人来说有点起点。
对于解压缩,我注意到Swift允许返回一个可变大小的元组。例如:func unpack(format: String) -> (AnyObject...)
没有给出编译警告。但是,我不知道你是怎么回事的。
答案 2 :(得分:2)
https://github.com/nst/BinUtils/
类似于Python的struct.pack()
let d = pack("<h2I3sf", [1, 2, 3, "asd", 0.5])
assert(d == unhexlify("0100 02000000 03000000 617364 0000003f"))
类似于Python的struct.unpack()
let a = unpack(">hBsf", unhexlify("0500 01 41 3fc00000")!)
assert(a[0] as? Int == 1280)
assert(a[1] as? Int == 1)
assert(a[2] as? String == "A")
assert(a[3] as? Double == 1.5)