Bytes
旨在成为你首选的 Swift 库,用于将基本类型转换为字节缓冲区,以及从字节缓冲区转换回基本类型,以便进行序列化并写入网络、文件或数据库。
在你的 Package.swift
文件中添加 Bytes
作为依赖项即可开始使用。然后,在你希望使用该库的任何文件中添加 import Bytes
。
请查看发布版本以获取推荐版本。
dependencies: [
.package(url: "https://github.com/mochidev/Bytes.git", .upToNextMinor(from: "0.3.1")),
],
...
targets: [
.target(
name: "MyPackage",
dependencies: [
"Bytes",
]
)
]
Bytes
是 [UInt8]
的类型别名,这意味着你可以在任何需要字节缓冲区的地方使用它。虽然提供了 Int
、String
、UUID
甚至枚举的扩展,但这个库最终允许你将任何固定大小的结构转换为 Bytes
以及从 Bytes
转换回来。
struct Example {
let a: UInt8
let b: UInt8
}
func methodThatTakesExample(_ example: Example) { ... }
let example = Example(a: 1, b: 2)
// Turn a Swift type into Bytes:
let bytes = Bytes(casting: example) // [0x01, 0x02]
// Turn Bytes into a Swift type:
let backToExample = try bytes.casting(to: Example.self) // Type is explicit
methodThatTakesExample(try bytes.casting()) // Type is inferred from context
请注意,以这种方式转换为 Bytes
和从 Bytes
转换回来的对象在程序范围之外是不稳定的,因为它们反映了对象在那个时刻的内存布局。更可取的方法是直接使用 Int
、String
、UUID
等的专用方法来编码项目,因为它们提供了一种稳定且一致的内存中对象编码和解码方案。
当处理整数时,如果可能,最好使用 bigEndianBytes
/ littleEndianBytes
和 init(bigEndianBytes: Bytes)
/init(littleEndianBytes: Bytes)
这些整数特定的属性和初始化器,因为它们可以保证跨平台(类型转换将始终使用系统内存的原生字节序)。整数重载适用于所有整数类型:Int8
、UInt8
、Int16
、UInt16
、Int32
、UInt32
、Int64
和 UInt64
。不建议使用 Int
和 UInt
进行序列化,因为它们在不同平台上的大小不同。
为了使处理字符串和字符变得简单明了,提供了 utf8Bytes
和 init(utf8Bytes: Bytes)
。这些不是空字符结尾的,但你可以很容易地实现这一点:"Hello".utf8Bytes + [0]
。
UUID 支持以紧凑字节形式 (bytes
和 init(bytes: Bytes)
) 和人类可读的字符串形式 (stringBytes
和 init(stringBytes: Bytes)
) 进行序列化。
枚举和符合 RawRepresentable
的其他类型也通过 rawBytes
和 init(rawBytes: Bytes)
开箱即用地支持。RawType
为整数或字符串/字符的类型也可以使用上述 getter 和初始化器。
整数的数组或集合可以使用 bigEndianBytes
/ littleEndianBytes
进行序列化,并使用 init(bigEndianBytes: Bytes)
/init(littleEndianBytes: Bytes)
进行初始化。对于 UUID 和 RawRepresentable
枚举的集合,也可以通过使用特定于其类型的 API 来完成相同的操作。
有时,在读取字节序列时,你只想验证序列中的下一个字节是否为常量。这可以使用迭代器上的 check()
系列方法轻松完成
try iterator.check(0) // Check for 0x00, throw if not found
try iterator.check([0x0d, 0x0a]) // Check for \r\n, throw if not found
try iterator.checkIfPresent([0x0d, 0x0a]) // Check for \r\n, return false if iterator is finished, throw if not finished and not found
try iterator.checkIfPresent(utf8: "\r\n") // Check for \r\n, return false if iterator is finished, throw if not finished and not found
try iterator.checkIfPresent(utf8: Separators.header) // Check for \r\n\r\n, return false if iterator is finished, throw if not finished and not found
由于 Bytes
只是一个数组,因此所有你习惯使用的方法都可用,包括使用切片。如果你正在处理更复杂的序列化,请考虑以下示例
struct Versionstamp {
let transactionCommitVersion: UInt64
let batchNumber: UInt16
var userData: UInt16?
init(transactionCommitVersion: UInt64, batchNumber: UInt16, userData: UInt16? = nil) {
self.transactionCommitVersion = transactionCommitVersion
self.batchNumber = batchNumber
self.userData = userData
}
var bytes: Bytes {
var result = Bytes()
result.reserveCapacity(userData == nil ? 10 : 12)
result.append(contentsOf: transactionCommitVersion.bigEndianBytes)
result.append(contentsOf: batchNumber.bigEndianBytes)
if let userData = userData {
result.append(contentsOf: userData.bigEndianBytes)
}
return result
}
init<Bytes: BytesCollection>(_ bytes: Bytes?) throws {
guard let bytes = bytes, bytes.count == 10 || bytes.count == 12 else {
throw Error.invalidVersionstamp
}
let transactionCommitVersion = try! UInt64(bigEndianBytes: bytes[0..<8])
let batchNumber = try! UInt16(bigEndianBytes: bytes[8..<10])
var userData: UInt16?
if bytes.count == 12 {
userData = try! UInt16(bigEndianBytes: bytes[10..<12])
}
self.init(transactionCommitVersion: transactionCommitVersion, batchNumber: batchNumber, userData: userData)
}
/// Alternatively using the ByteIterator-class of methods, which don't require keeping track of indices since they decode objects in order.
init<Bytes: BytesCollection>(sequence: Bytes?) throws {
var iterator = sequence.makeIterator()
self.init(
transactionCommitVersion: try iterator.next(bigEndian: UInt64.self),
batchNumber: try iterator.next(bigEndian: UInt16.self),
userData: try iterator.nextIfPresent(bigEndian: UInt16.self)
)
/// Verify we are at the end of the stream of bytes.
guard iterator.next() == nil else {
throw Error.invalidVersionstamp
}
}
}
Bytes
也可以用于从 AsyncSequence
迭代器中提取数据。要了解更多信息,请参阅与 AsyncSequenceReader 集成。
例如,改进上述示例
#if compiler(>=5.5) && canImport(_Concurrency)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension AsyncIteratorProtocol where Element == Byte {
@inlinable
mutating func next(_ type: Versionstamp.Type) async throws -> Versionstamp {
Versionstamp(
transactionCommitVersion: try await next(bigEndian: UInt64.self),
batchNumber: try await next(bigEndian: UInt16.self),
userData: try await nextIfPresent(bigEndian: UInt16.self)
)
}
}
#endif
欢迎贡献!请查看已有的 issue,或开始一个新的 issue 来讨论新功能。尽管不能保证满足功能请求,但符合项目目标并且事先讨论过的 PR 非常受欢迎!
请确保所有提交都具有清晰的提交历史记录,文档齐全且经过全面测试。请在提交之前 rebase 你的 PR,而不是合并到 main
分支。需要线性历史记录,因此 PR 中的合并提交将不被接受。