Swift SCALE 编码器
Swift 对于 Parity Substrate 框架中使用的类型的 SCALE(简单连接聚合小端)数据格式的实现。
SCALE 是一种轻量级格式,可以用于编码(和解码),因此非常适合资源受限的执行环境,如区块链运行时和低功耗、低内存设备。
需要注意的是,编码上下文(关于类型和数据结构外观的知识)需要在编码和解码双方各自单独了解。编码数据不包含此上下文信息。
要更好地了解不同类型的编码方式,请参阅 Substrate 文档网站的低级别数据格式概述页面。
安装
ScaleCodec 部署到 macOS 10.10,iOS 9,watchOS 2,tvOS 9 和 Linux。仅在最新的 OS 版本上进行了测试,但该模块仅使用非常少的平台提供的 API,因此应该很少出现问题。
ScaleCodec 使用了没有特定于 Apple 平台的 API,因此应该很容易将其移植到其他操作系统。
设置说明
-
Swift 包管理器:将以下内容添加到您的
Package.swift
清单的依赖项部分.package(url: "https://github.com/tesseract-one/swift-scale-codec.git", from: "0.3.0")
-
CocoaPods:将此内容放入您的
Podfile
pod 'ScaleCodec', '~> 0.3'
使用示例
以下是展示编码器使用的示例。
简单类型
编码器支持String
、Data
、Bool
、Int[8-64]
和UInt[8-64]
类型。
import ScaleCodec
let data = Data([0xff, 0xff, 0xff, 0xff])
let encoded = try encode(UInt32.max)
assert(encoded == data)
let uint32 = try decode(UInt32.self, from: data)
assert(uint32 == UInt32.max)
紧凑编码
UInt[8-64]
、SUInt[128-512]
和BigUInt
类型可以使用紧凑编码。这允许BigUInt
存储最大到2^536-1
的值。
ScaleCodec有特殊的包装类型SCompact
,用于以这种格式编码和解码值,并提供两种辅助方法。
示例
import ScaleCodec
let data = Data([0x07, 0x00, 0x00, 0x00, 0x00, 0x01])
let encoded = try encode(UInt64(1 << 32), .compact)
assert(encoded == data))
let compact = try decode(UInt64.self, .compact, from: data)
assert(compact == UInt64(1 << 32))
// without custom encoding methods
// let encoded = try encode(Compact(UInt64(1 << 32)))
// let compact = try decode(Compact<UInt64>.self, from: data).value
Int[128-1024]和UInt[128-1024]
Int[128-1024]
和UInt[128-1024]
类型通过苹果的DoubleWidth
Swift类型实现。它在128-256位上表现良好,但对于512-1024位则相对较慢。
import ScaleCodec
let data = Data([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
])
let encoded = try encode(UInt128(UInt256(2)^128 - 1))
assert(encoded == data))
let compact = try decode(UInt128.self, from: data)
assert(compact == UInt128(UInt256(2)^128 - 1))
数据固定编码
数据
类型可以使用固定编码方式进行编码。在此模式下,不会存储数据长度,因此需要手动提供数据长度。
import ScaleCodec
let data = Data([0x07, 0x00, 0x00, 0x00, 0x00, 0x01]
var encoder = encoder()
try encoder.encode(data, .fixed(6))
assert(encoder.output == data))
var decoder = decoder(from: encoder.output)
let decoded = try decoder.decode(Data.self, .fixed(6))
assert(decoded == encoder.output == data)
容器类型
ScaleCodec 能够编码和解码标准容器。支持的容器包括:Optional
、Result
、Array
、Set
、Dictionary
。容器可以嵌套使用,容器元素应该是可编码的。
import ScaleCodec
let array: [UInt32] = [1, 2, 3, 4, 5]
let data = try encode(array)
let decoded: [UInt32] = try decode(from: data)
assert(array == decoded)
固定数组
数组
可以像数据
一样使用固定编码进行编码。长度需要手动提供。
import ScaleCodec
let array: [UInt32] = [1, 2, 3, 4, 5]
let data = try encode(array, .fixed(5))
let decoded: [UInt32] = try decode(.fixed(5), from: data)
assert(array == decoded)
元组
通过Tuple*
包装器的集合支持元组的编码和解码。ScaleCodec 提供了Tuple()
辅助函数,可以创建用于元组的适当的Tuple*
实例。Tuple*
包装器可以嵌套使用以支持更大的元组。同时,ScaleCodec 还提供了一系列用于元组支持的辅助方法。
import ScaleCodec
let tuple = (UInt32.max, "Hello")
let encoded = try encode(tuple)
let decoded: (UInt32, String) = try decode(from: encoded)
assert(tuple == decoded)
// without helper methods
// let encoded = try encode(Tuple(tuple)) // or directly Tuple2(tuple)
// let decoded = try decode(Tuple2<UInt32, String>.self, from: encoded).tuple
枚举
简单枚举
如果枚举支持 CaseIterable
协议,则可以自动编码不含关联值的简单枚举。Swift 为简单枚举提供了对 CaseIterable
协议的自动实现功能。
import ScaleCodec
enum Test: CaseIterable, ScaleCodec.Codable {
case A
case B
}
let data = try encode(Test.A)
let decoded: Test = try decode(from: data)
assert(decoded == Test.A)
复杂枚举
复杂枚举的编码和解码应手动实现。需要实现两个协议:Encodable
和 Decodable
(《Codable 可以作为其常用别名)。
import ScaleCodec
enum Test: ScaleCodec.Codable {
case A(String?)
case B(UInt32, String) // UInt32 will use Compact encoding.
init<D: ScaleCodec.Decoder>(from decoder: inout D) throws {
let opt = try decoder.decode(.enumCaseId)
switch opt {
case 0: self = try .A(decoder.decode())
case 1: self = try .B(decoder.decode(.compact), decoder.decode())
default: throw decoder.enumCaseError(for: opt)
}
}
func encode<E: ScaleCodec.Encoder>(in encoder: inout E) throws {
switch self {
case .A(let str):
try encoder.encode(0, .enumCaseId)
try encoder.encode(str)
case .B(let int, let str):
try encoder.encode(1, .enumCaseId)
try encoder.encode(int, .compact)
try encoder.encode(str)
}
}
}
let val = Test.B(100, "World!")
let data = try encode(val)
let decoded: Test = try decode(from: data)
assert(decoded == val)
类和结构体
类和结构体应实现Encodable
和Decodable
。`Encoder`和`Decoder`有用于标准容器和类型的辅助方法。
import ScaleCodec
struct Test: ScaleCodec.Codable, Equatable {
let var1: String?
let var2: BigUInt // will use Compact encoding.
let var3: [UInt32] // UInt32 will use Compact encoding.
init(_ v1: String?, _ v2: BigUInt, _ v3: [UInt32]) {
var1 = v1; var2 = v2; var3 = v3
}
init<D: ScaleCodec.Decoder>(from decoder: inout D) throws {
var1 = try decoder.decode()
var2 = try decoder.decode(.compact)
var3 = try decoder.decode(Array<Compact<UInt32>>.self).map { $0.value }
}
func encode<E: ScaleCodec.Encoder>(in encoder: inout E) throws {
try encoder.encode(var1)
try encoder.encode(var2, .compact)
try encoder.encode(var3.map { Compact($0) })
}
}
let val = Test(nil, 123456789, [1, 2, 3, 4, 5])
let data = try encode(val)
let decoded: Test = try decode(from: data)
assert(decoded == val)
固定类和结构
可以从固定编码的 Array
和 Data
对象创建类和结构。为了方便,ScaleCodec 有两组协议:(FixedEncodable
,FixedDecodable
) 和 (FixedDataEncodable
,FixedDataDecodable
)。
示例
import ScaleCodec
struct StringArray4: Equatable, FixedCodable {
typealias Element = String // Fixed Array element type
static var fixedElementCount: Int = 4 // amount of elements in Fixed Array
var array: [String]
init(_ array: [String]) {
self.array = array
}
init(values: [String]) throws { // decoding from Fixed Array
self.init(values)
}
func values() throws -> [String] { // encoding to Fixed Array
return self.array
}
}
private struct Data4: Equatable, FixedDataCodable {
var data: Data
static var fixedBytesCount: Int = 4 // amount of bytes in Fixed Data
init(_ data: Data) {
self.data = data
}
init(decoding data: Data) throws { // decoding from Fixed Data
self.init(data)
}
func serialize() throws -> Data { // encoding to Fixed Data
return self.data
}
}
let string4 = StringArray4(["1", "2", "3", "4"])
let dataS4 = try encode(string4)
let decoded: StringArray4 = try decode(from: dataS4)
assert(decoded == string4)
let data4 = Data4(Data([1, 2, 3, 4]))
let dataE4 = try encode(data4)
let decoded: Data4 = try decode(from: dataE4)
assert(decoded == data4)
大小计算而不是完整解析
在某些情况下,最好计算编码类型的大小,而不是完整解析。在大多数情况下,这将更快。为此,ScaleCodec 有 SizeCalculable
协议。它适用于所有基本类型和容器。
import ScaleCodec
let data = Data([0x10, 0x04, 0x41, 0x04, 0x42, 0x04, 0x43, 0x0c, 0x44, 0x44, 0x44])
var decoder = decoder(from: data)
var skipDecoder = decoder.skippable() // special decoder which can skip data
let size = try Array<String>.calculateSize(in: &skipDecoder)
assert(size == 11)
let decoded = try decoder.decode(Array<String>.self)
assert(decoded == ["A", "B", "C", "DDD"])
许可证
ScaleCodec 可以在 Apache 2.0 许可证 下使用、分发和修改。