我正在将一个项目从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模块的东西.
我开始编写一个以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)
}
}