Cereal 4.0.0

Cereal 4.0.0

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2017 年 10 月
SwiftSwift 版本3.0
SPM支持 SPM

James Richard 维护。



Cereal 4.0.0

Cereal 是一个适用于 Swift 的序列化框架。它旨在替代 NSCoding,以允许高级 Swift 特性。使用 NSCoding,您无法将 Swift 结构体、枚举或泛型类编码或解码。Cereal 通过泛型延迟解码和不受 NSObjectProtocol 依赖的协议解决这个问题。

请注意,Cereal 2.0 中存储的数据与 Cereal 1.3 不同,尽管 API 相同,它们并不兼容。不要期望 1.3 版本编写的数据能被 2.0 版本读取。

特性

  • 编码和解码 String、Bool、Int、Int64、Float、Double、NSDate 和 NSURL
  • 编码和解码数组、字典数组、字典以及数组字典
  • 编码和解码您自己的 enum 类型
  • 编码和解码您自己的 struct 类型
  • 编码和解码您自己的 class 类型,即使它们没有继承自 NSObject
  • 作为 protocol 进行编码和解码
  • 对泛型类型进行编码和解码
  • 编码和解码 RawRepresentable 类型,其原始值的编码/解码
  • 强制解码与编码相同的类型
  • 全面的测试套件

要求

  • iOS 8.0+ / Mac OS X 10.9 / watchOS 2
  • Xcode 7.0+ (7.1 用于支持 watchOS 2 的 CocoaPods)

安装

基本集成

对于基本集成,将以下内容添加到您的 Podfile 中

pod 'Cereal', '~> 2.0'

多重目标

如果您想将 Cereal 集成到您的 watchOS 目标和 iOS 目标中,您需要如下配置

def shared_pods 
  pod 'Cereal', '~> 2.0'
end

target :"iOS App Target" do
    platform :ios, '8.0'
    shared_pods
end

target :"watchOS Extension Target" do
    platform :watchos, '2.0'
    shared_pods
end

手动

如果您想手动添加 Cereal 到您的项目中,您可以复制 Cereal 目录中包含的 .swift 文件到您项目中,并在所有必要的目标上进行编译。

用法

谷物存储(Cereal)的功能类似于NSKeyedArchiver。在编码时,您会创建一个CerealEncoder,并编码您想要存储在结果数据中的对象。以下是一个编码各种原始类型的示例

var encoder = CerealEncoder()
try encoder.encode(5, forKey: "myInt")
try encoder.encode([1,2,3], forKey: "myIntArray")
try encoder.encode(["Hello,", "World!"], forKey: "myStringArray")
try encoder.encode(["Foo": "Bar"], forKey: "myStringToStringDictionary")
let data = encoder.toData()

然后您可以将数据存储在您希望的地方;无论是状态恢复、用户默认值、文件,还是通过WatchConnectivity与用户的Apple Watch进行通信。为了解码另一端的对象

let decoder = CerealDecoder(data: data)
let myInt: Int? = try decoder.decode("myInt")
let myIntArray: [Int]? = try decoder.decode("myIntArray")
let myStringArray: [String]? = try decoder.decode("myStringArray")
let myStringToStringDictionary: [String: String]? = try decoder.decode("myStringToStringDictionary")

由于与字典和数组一起使用的泛型代码的复杂性,您不能将字典/数组深度嵌套到自己内部。但是,支持字典数组数组和数组合字典。我强烈建议您尽可能将自定义类型序列化,而不是字典。以下是我们iOS项目示例中的示例

// employees is a [Employee]; Employee is a Swift struct.
let data = try CerealEncoder.data(withRoot: employees) // Returns an Data object

并且解码该数据

do {
    employees = try CerealDecoder.rootCerealItems(with: storedEmployeeData)
} catch let error {
    NSLog("Couldn't decode employees due to error: \(error)")
}

上面的示例使用了简化根级别的编码和解码的方法。通常,您将项目编码为特定的键

序列化您的具体类型

您的自定义类型应该采用CerealType(或其子协议,IdentifyingCerealType)以进行编码和解码。以下是一个Employee结构示例

struct Employee {
    var name = ""
    var age = 18
    var gender: Gender = .Female

    init() { }
}

extension Employee: CerealType {
    private struct Keys {
        static let name = "name"
        static let age = "age"
        static let gender = "gender"
    }

    init(cereal: CerealDecoder) throws {
        name = try cereal.decode(Keys.name) ?? ""
        age = try cereal.decode(Keys.age) ?? 0
        gender = try cereal.decodeCereal(Keys.gender) ?? .Female
    }

    func encodeWithCereal(inout cereal: CerealEncoder) throws {
        try cereal.encode(name, forKey: Keys.name)
        try cereal.encode(age, forKey: Keys.age)
        try cereal.encode(gender, forKey: Keys.gender)
    }
}

您可能会注意到,上面有两种解码方法;decode和decodeCereal。这是因为编译器只能从返回类型(由于原始类型和自定义类型必须符合CerealRepresentable,这是我们内部的协议,不应该在任何代码中符合)来确定使用哪个版本解码,因此有几种不同的解码方法。

序列化您的协议

Cereal需要支持原语导向编程的实战概念。虽然协议可以为我们的泛型解码器提供可实例化的类型数据,但我们需要一种方法来识别协议抽象下的具体类型。

为了让您的类型能够支持协议后的编码,我们使用Cereal.register注册具体类型,该方法接收您类型的.Type。以下是一个原语导向类型示例

protocol Vehicle: IdentifyingCerealType {
    var make: String { get }
    var model: String { get }
    var description: String { get }
}

// Used for other implementers of Vehicle
private struct SharedKeys {
    static let make = "make"
    static let model = "model"
}

enum VehicleError: ErrorType {
    case MissingData
}

struct Car: Vehicle {
    private struct Keys {
        static let cylinders = "cylinders"
    }

    let make: String
    let model: String
    let cylinders: Int

    var description: String {
        return "\(model) by \(make) w/ \(cylinders) cylinders"
    }

    static let initializationIdentifier = "car"

    init(cereal: CerealDecoder) throws {
        guard let make: String = try cereal.decode(SharedKeys.make) else { throw VehicleError.MissingData }
        self.make = make

        guard let model: String = try cereal.decode(SharedKeys.model) else { throw VehicleError.MissingData }
        self.model = model

        guard let cylinders: Int = try cereal.decode(Keys.cylinders) else { throw VehicleError.MissingData }
        self.cylinders = cylinders
    }

    init(make: String, model: String, cylinders: Int) {
        self.make = make
        self.model = model
        self.cylinders = cylinders
    }

    func encodeWithCereal(inout cereal: CerealEncoder) throws {
        try cereal.encode(make, forKey: SharedKeys.make)
        try cereal.encode(model, forKey: SharedKeys.model)
        try cereal.encode(cylinders, forKey: Keys.cylinders)
    }
}

// This is where the magic happens!
Cereal.register(Car.self)

哇!这样我们就可以创建一个用于协议引用解码的对象了,但如何在实际中进行解码和编码呢?

let vehicle: Vehicle = Car(make: "Foo", model: "Bar", cylinders: 8)

// Encoding
let encoder = CerealEncoder()
try encoder.encode(vehicle, forKey: "vehicle")

// Decoding
let decoder = CerealDecoder(data: encoder.toData())
let decodedVehicle: Vehicle = try decoder.decodeCereal("vehicle") as! Vehicle

使用IdentifyingCerealType进行安全的解码

除了允许您编码和解码协议外,您还可以使用IdentifyingCerealType作为确保编码和解码相同的类型的方法。

使用协议类型处理字典和数组

当使用协议集合,如[Vehicle]时,编译器在编码和解码类型时会出现问题。为了支持这些类型,Array和Dictionary有一个扩展CER_casted(),它会将类型转换为期望的类型

let decoder = try CerealDecoder(data: storedVehicleData)
let vehicles: [Vehicles] = try decoder.decodeIdentifyingCerealArray("vehicles")?.CER_casted() ?? []

编码需要类似处理

var encoder = CerealEncoder()
try encoder.encodeIdentifyingItems(vehicles.CER_casted(), forKey: "vehicles")

还有deepCast(_: [[KeyType: ValueType]])deepArrayCast(_: [KeyType: [ValueType]])来处理数组字典和字典数组。

序列化泛型

您的泛型类型的工作方式与其他可序列化类型相同。但是,您的泛型类型应该约束到CerealRepresentable。

struct Stack<ItemType: CerealRepresentable>: CerealType {
    private items: [ItemType]
    // ... rest of the implementation ...
}

然后您可以使用相同的方法进行编码和解码对象。

注册泛型类型

如果您的泛型类型正在使用IdentifyingCerealType,您需要在Cereal中注册特殊化,让它了解如何正确初始化您的类型。例如,如果Stack符合IdentifyingCerealType

Cereal.register(Stack<Int>.self)

当您在泛型类型中实现初始化标识符时,它也应该是基于泛型的

struct Stack<ItemType: CerealRepresentable>: IdentifyingCerealType {
    static var initializationIdentifier: String {
        return "Stack-\(ItemType.self)"
    }
    // ... rest of the implementation ...
}

原生表示性支持

RawRepresentable 类型添加序列化从未如此简单,因为它们可以用它们的原始值表示。所以,如果您有一个枚举,或者具有支持 CerealRepresentable 类型(如 StringBoolInt)的原始值的选项集,那么您所需要做的就是添加 CerealRepresentable 符合性到您的 RawRepresentable 类型,这样您就完成了。例如,这些 RawRepresentable 类型

enum Gender: Int {
    case Female
    case Male
}

struct EmployeeResponsibilites : OptionSetType {
    let rawValue: Int

    static let None           = OptionSet(rawValue: 0)
    static let CleanWorkplace = OptionSet(rawValue: 1 << 0)
    static let OpenWindow     = OptionSet(rawValue: 1 << 1)
    static let BringCofee     = OptionSet(rawValue: 1 << 2)
}

可以很容易地进行编码和解码,只需添加 CerealRepresentable 符合性

extension Gender: CerealRepresentable {}
extension EmployeeResponsibilites: CerealRepresentable {}

编码/解码可能看起来像这样

struct Employee {
    var gender: Gender = .Female
    var responsibilities: EmployeeResponsibilites = [.CleanWorkplace, .OpenWindow]
    init() { }
}

extension Employee: CerealType {
    private struct Keys {
        static let gender = "gender"
        static let responsibility = "responsibility"
    }

    init(decoder: CerealDecoder) throws {
        gender = try decoder.decode(Keys.gender) ?? .Female
        responsibility = try decoder.decode(Keys.responsibility) ?? .None
    }

    func encodeWithCereal(inout cereal: CerealEncoder) throws {
        try cereal.encode(gender, forKey: Keys.gender)
        try cereal.encode(responsibility, forKey: Keys.responsibility)
    }
}

进一步阅读

Cereal 有很多排列方式。我强烈建议您阅读 API,如果您在使用过程中有任何问题,请查看我们的综合性测试套件。它涵盖了所有内容。

许可证

版权所有 © 2015,Weebly。保留所有权利。

以下条件满足时,允许在原始和二进制形式中重新分发和使用,无论是否修改:

源代码的重新分发必须保留上述版权声明、本许可证和各种免责声明。以二进制形式重新分发必须在每个文档中重新生成上述版权声明,本许可证和免责声明,以及分配提供的文档和其他材料。未经事先书面许可,不得使用 Weebly 或其贡献者的名称认可或推广由此软件派生出的产品。本软件按“原样”提供,并明确或暗示地放弃了任何简称、适销性和适用于特定目的的隐含保证。在任何情况下,Weebly,Inc 不承担任何直接的、间接的、偶然的、特殊的、示范性的或后果性的损害赔偿(包括但不限于因替代货物或服务的采购、使用、数据或利润损失;或业务中断)责任,无论源于何种原因,以及基于何种侵权理论,即使已告知本软件存在损害的可能性。