ScaleCodec 0.3.1

ScaleCodec 0.3.1

Yehor Popovych 维护。



  • Tesseract Systems, Inc.

Swift SCALE 编码器

🐧 linux: ready GitHub license Build Status GitHub release SPM compatible CocoaPods version Platform OS X | iOS | tvOS | watchOS | Linux

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'

使用示例

以下是展示编码器使用的示例。

简单类型

编码器支持StringDataBoolInt[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 能够编码和解码标准容器。支持的容器包括:OptionalResultArraySetDictionary。容器可以嵌套使用,容器元素应该是可编码的。

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)

复杂枚举

复杂枚举的编码和解码应手动实现。需要实现两个协议:EncodableDecodable(《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)

类和结构体

类和结构体应实现EncodableDecodable。`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)

固定类和结构

可以从固定编码的 ArrayData 对象创建类和结构。为了方便,ScaleCodec 有两组协议:(FixedEncodableFixedDecodable) 和 (FixedDataEncodableFixedDataDecodable)。

示例

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 许可证 下使用、分发和修改。