私はCodable
をいじくり回し、JSONをファイルとの間で読み書きしています。次に、iOSを読み書きできるカスタムCoder
を作成したい.strings
ファイル。誰もこれで私を助けることができますか?プロトコルEncoder
およびDecoder
が見つかりましたが、ここで何を実装する必要があるのかわかりません。
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 {
}
}
ここでのパーティーに少し遅れましたが、これは、票数が多いという質問を考えると、他の人にとって有益/有益かもしれないと感じています。 (しかし、私は実際にそのようなコードの実際の有用性に実際に入るwo n'tそれについては上記のコメントをチェックしてください。)
残念ながら、コーディングスタックの柔軟性と型安全性を考えると、新しいencodingおよびdecodingソリューションを実装することは、代替のexternal representationにはほど遠い簡単なタスク...
まず、目的の stringsファイル 外部表現にencoding部分を実装することから始めましょう。 (必要なタイプは、トップダウン方式で導入されます。)
標準のJSONEncoder
クラスと同様に、新しいエンコーディングAPIを公開/駆動するクラスを導入する必要があります。それを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
プロトコルに準拠するタイプ(たとえば、Encoder
)を提供する必要があります。
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つのすべてのエンコードコンテナータイプを処理する必要があります。
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 file形式を使用して、ネストされた型をエンコードする方法に関して、いくつかの設計上の決定を下しました。うまくいけば、私のコードが十分に明確であり、必要に応じてエンコードの詳細を簡単に調整できるはずです。
単純なCodable
型の簡単なテスト:
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";
nested structsおよび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
プロトコルなど)読者への演習として...皆さんがそれに関して何か助けが必要かどうか教えてください;)