字符串资源格式的自定义Swift编码器/解码器 [英] Custom Swift Encoder/Decoder for the Strings Resource Format

查看:101
本文介绍了字符串资源格式的自定义Swift编码器/解码器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在玩 Codable ,并从文件中读取和写入JSON。现在,我想编写一个自定义的 Coder ,它可以读写iOS .strings 文件。谁能帮我这个?我找到了协议 Encoder Decoder ,但是我不知道在这里应该实现什么:

I have been playing around with Codable and reading and writing JSON from and to a file. Now I would like to write a custom Coder that can read and write iOS .strings files. Can anyone help me with this? I found the protocols Encoder and Decoder, but I have no idea what I should implement here:

class StringsEncoder {}

extension StringsEncoder: Encoder {
    var codingPath: [CodingKey?] {
        return []
    }

    var userInfo: [CodingUserInfoKey : Any] {
        return [:]
    }

    func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {

    }

    func unkeyedContainer() -> UnkeyedEncodingContainer {

    }

    func singleValueContainer() -> SingleValueEncodingContainer {

    }
}

extension StringsEncoder: Decoder {
    func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {

    }

    func unkeyedContainer() throws -> UnkeyedDecodingContainer {

    }

    func singleValueContainer() throws -> SingleValueDecodingContainer {

    }
}


推荐答案

在这里参加聚会有点晚了,但鉴于投票数很高的问题,我认为这对其他人可能是有帮助的/有益的。 (但是我不会在实践中真正了解这种代码的实际用途,请为此检查上面的注释。)

A bit late to the party here but I feel this might be helpful/informative to others given the question high vote count. (But I won't really get into the actual usefulness of such code in practice—please check the comments above for that.)

不幸的是,给出了编码堆栈灵活性和类型安全性,实现新的 encoding decoding 解决方案以替代外部表示形式,并不是一件容易的事。 。

Unfortunately, given the coding stack flexibility and type-safeness, implementing a new encoding and decoding solution, for an alternative external representation, is far from a trivial task...

让我们开始为所需的encoding 部分developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/Strings/Strings.html rel = nofollow noreferrer>字符串文件的外部表示形式。 (必需的类型将以 top-down 方法引入。)

Let's start by implementing the encoding part for the desired strings file external representation. (The necessary types will be introduced in a top-down approach.)

类似于标准的 JSONEncoder 类,我们需要引入一个类来公开/驱动我们的新编码API。我们称之为 StringsEncoder

Like the standard JSONEncoder class we need to introduce a class to expose/drive our new encoding API. Let's call that StringsEncoder:

/// An object that encodes instances of a data type 
/// as strings following the simple strings file format.
public class StringsEncoder {
    
    /// Returns a strings file-encoded representation of the specified value. 
    public func encode<T: Encodable>(_ value: T) throws -> String {
        let stringsEncoding = StringsEncoding()
        try value.encode(to: stringsEncoding)
        return dotStringsFormat(from: stringsEncoding.data.strings)
    }
    
    private func dotStringsFormat(from strings: [String: String]) -> String {
        var dotStrings = strings.map { "\"\($0)\" = \"\($1)\";" }
        dotStrings.sort()
        dotStrings.insert("/* Generated by StringsEncoder */", at: 0)
        return dotStrings.joined(separator: "\n")
    }
}

接下来,我们需要提供一种类型(例如, struct )符合核心编码器协议:

Next, we need to provide a type (e.g., a struct) conforming to the core Encoder protocol:

fileprivate struct StringsEncoding: Encoder {
    
    /// Stores the actual strings file data during encoding.
    fileprivate final class Data {
        private(set) var strings: [String: String] = [:]
        
        func encode(key codingKey: [CodingKey], value: String) {
            let key = codingKey.map { $0.stringValue }.joined(separator: ".")
            strings[key] = value
        }
    }
    
    fileprivate var data: Data
    
    init(to encodedData: Data = Data()) {
        self.data = encodedData
    }

    var codingPath: [CodingKey] = []
    
    let userInfo: [CodingUserInfoKey : Any] = [:]
    
    func container<Key: CodingKey>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
        var container = StringsKeyedEncoding<Key>(to: data)
        container.codingPath = codingPath
        return KeyedEncodingContainer(container)
    }
    
    func unkeyedContainer() -> UnkeyedEncodingContainer {
        var container = StringsUnkeyedEncoding(to: data)
        container.codingPath = codingPath
        return container
   }
    
    func singleValueContainer() -> SingleValueEncodingContainer {
        var container = StringsSingleValueEncoding(to: data)
        container.codingPath = codingPath
        return container
    }
}

最后,我们需要处理所有3种编码容器类型:

Finally, we need to handle all 3 encoding containers types:


  • KeyedEncodingContainer

  • UnkeyedEncodingContainer

  • SingleValueEncodingContainer

  • KeyedEncodingContainer
  • UnkeyedEncodingContainer
  • SingleValueEncodingContainer
fileprivate struct StringsKeyedEncoding<Key: CodingKey>: KeyedEncodingContainerProtocol {

    private let data: StringsEncoding.Data
    
    init(to data: StringsEncoding.Data) {
        self.data = data
    }
    
    var codingPath: [CodingKey] = []
    
    mutating func encodeNil(forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: "nil")
    }
    
    mutating func encode(_ value: Bool, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: String, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value)
    }
    
    mutating func encode(_ value: Double, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: Float, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: Int, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: Int8, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: Int16, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: Int32, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: Int64, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: UInt, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: UInt8, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: UInt16, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: UInt32, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode(_ value: UInt64, forKey key: Key) throws {
        data.encode(key: codingPath + [key], value: value.description)
    }
    
    mutating func encode<T: Encodable>(_ value: T, forKey key: Key) throws {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath.append(key)
        try value.encode(to: stringsEncoding)
    }
    
    mutating func nestedContainer<NestedKey: CodingKey>(
        keyedBy keyType: NestedKey.Type,
        forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
        var container = StringsKeyedEncoding<NestedKey>(to: data)
        container.codingPath = codingPath + [key]
        return KeyedEncodingContainer(container)
    }
    
    mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
        var container = StringsUnkeyedEncoding(to: data)
        container.codingPath = codingPath + [key]
        return container
    }
    
    mutating func superEncoder() -> Encoder {
        let superKey = Key(stringValue: "super")!
        return superEncoder(forKey: superKey)
    }
    
    mutating func superEncoder(forKey key: Key) -> Encoder {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath = codingPath + [key]
        return stringsEncoding
    }
}


fileprivate struct StringsUnkeyedEncoding: UnkeyedEncodingContainer {

    private let data: StringsEncoding.Data
    
    init(to data: StringsEncoding.Data) {
        self.data = data
    }
    
    var codingPath: [CodingKey] = []

    private(set) var count: Int = 0
    
    private mutating func nextIndexedKey() -> CodingKey {
        let nextCodingKey = IndexedCodingKey(intValue: count)!
        count += 1
        return nextCodingKey
    }
    
    private struct IndexedCodingKey: CodingKey {
        let intValue: Int?
        let stringValue: String

        init?(intValue: Int) {
            self.intValue = intValue
            self.stringValue = intValue.description
        }

        init?(stringValue: String) {
            return nil
        }
    }

    mutating func encodeNil() throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: "nil")
    }
    
    mutating func encode(_ value: Bool) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: String) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value)
    }
    
    mutating func encode(_ value: Double) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: Float) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: Int) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: Int8) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: Int16) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: Int32) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: Int64) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: UInt) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: UInt8) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: UInt16) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: UInt32) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode(_ value: UInt64) throws {
        data.encode(key: codingPath + [nextIndexedKey()], value: value.description)
    }
    
    mutating func encode<T: Encodable>(_ value: T) throws {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath = codingPath + [nextIndexedKey()]
        try value.encode(to: stringsEncoding)
    }
    
    mutating func nestedContainer<NestedKey: CodingKey>(
        keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> {
        var container = StringsKeyedEncoding<NestedKey>(to: data)
        container.codingPath = codingPath + [nextIndexedKey()]
        return KeyedEncodingContainer(container)
    }
    
    mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
        var container = StringsUnkeyedEncoding(to: data)
        container.codingPath = codingPath + [nextIndexedKey()]
        return container
    }
    
    mutating func superEncoder() -> Encoder {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath.append(nextIndexedKey())
        return stringsEncoding
    }
}


fileprivate struct StringsSingleValueEncoding: SingleValueEncodingContainer {
    
    private let data: StringsEncoding.Data
    
    init(to data: StringsEncoding.Data) {
        self.data = data
    }

    var codingPath: [CodingKey] = []
    
    mutating func encodeNil() throws {
        data.encode(key: codingPath, value: "nil")
    }
    
    mutating func encode(_ value: Bool) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: String) throws {
        data.encode(key: codingPath, value: value)
    }
    
    mutating func encode(_ value: Double) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: Float) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: Int) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: Int8) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: Int16) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: Int32) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: Int64) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: UInt) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: UInt8) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: UInt16) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: UInt32) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode(_ value: UInt64) throws {
        data.encode(key: codingPath, value: value.description)
    }
    
    mutating func encode<T: Encodable>(_ value: T) throws {
        var stringsEncoding = StringsEncoding(to: data)
        stringsEncoding.codingPath = codingPath
        try value.encode(to: stringsEncoding)
    }
}

很明显,我做出了一些关于如何编码的设计决策使用(非常!)简单的 strings文件格式的嵌套类型。希望我的代码很清楚,如果需要的话,可以很容易地调整编码细节。

Obviously, I made some design decisions regarding how to encode nested types using the (very!) simple strings file format. Hopefully, my code is clear enough that it should be easy to tweak the encoding details if so desired.

一个简单的简单测试可编码类型:

A simple test for a trivial Codable type:

struct Product: Codable {
    var name: String
    var price: Float
    var info: String
}

let iPhone = Product(name: "iPhone X", price: 1_000, info: "Our best iPhone yet!")

let stringsEncoder = StringsEncoder()
do {
    let stringsFile = try stringsEncoder.encode(iPhone)
    print(stringsFile)
} catch {
    print("Encoding failed: \(error)")
}

输出:

/* Generated by StringsEncoder */
"info" = "Our best iPhone yet!";
"name" = "iPhone X";
"price" = "1000.0";

使用嵌套结构数组的更复杂的测试:

A more complex test with nested structs and arrays:

struct Product: Codable {
    var name: String
    var price: Float
    var info: String
}

struct Address: Codable {
    var street: String
    var city: String
    var state: String
}

struct Store: Codable {
    var name: String
    var address: Address // nested struct
    var products: [Product] // array
}

let iPhone = Product(name: "iPhone X", price: 1_000, info: "Our best iPhone yet!")
let macBook = Product(name: "Mac Book Pro", price: 2_000, info: "Early 2019")
let watch = Product(name: "Apple Watch", price: 500, info: "Series 4")

let appleStore = Store(
    name: "Apple Store",
    address: Address(street: "300 Post Street", city: "San Francisco", state: "CA"),
    products: [iPhone, macBook, watch]
)

let stringsEncoder = StringsEncoder()
do {
    let stringsFile = try stringsEncoder.encode(appleStore)
    print(stringsFile)
} catch {
    print("Encoding failed: \(error)")
}

输出:

/* Generated by StringsEncoder */
"address.city" = "San Francisco";
"address.state" = "CA";
"address.street" = "300 Post Street";
"name" = "Apple Store";
"products.0.info" = "Our best iPhone yet!";
"products.0.name" = "iPhone X";
"products.0.price" = "1000.0";
"products.1.info" = "Early 2019";
"products.1.name" = "Mac Book Pro";
"products.1.price" = "2000.0";
"products.2.info" = "Series 4";
"products.2.name" = "Apple Watch";
"products.2.price" = "500.0";


解码


鉴于这个答案已经很大,我要离开 decoding 部分(即,创建 StringsDecoder 类,并符合 Decoder 协议等)作为对读者的练习...请让我知道你们是否需要任何帮助,稍后我会发布完整的解决方案;)

Decoding

Given how big this answer already is, I'm gonna to leave the decoding part (i.e., creating the StringsDecoder class, conforming to the Decoder protocol, etc) as an exercise to the reader... please let me know if you guys need any help with that and I will post a complete solution later ;)

这篇关于字符串资源格式的自定义Swift编码器/解码器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆