Érdekes

Bináris Coder való Swift

Eredeti Mike Ash az https://www.mikeash.com/pyblog/friday-qa-2017-07-28-a-binary-coder-for-swift.html

Az utolsó cikkben tárgyalt az alapokat a Swift új Codable jegyzőkönyv, röviden megvitatták, hogyan hajtsák végre a saját kódoló, dekódoló, ígértem egy másik cikk arról, hogy egy egyedi bináris coder, amin dolgozom. Ma azt fogom bemutatni, hogy a bináris programozó.

Forráskód
Mint mindig, a forráskód elérhető a GitHub:

https://github.com/mikeash/BinaryCoder/tree/887cecd70c070d86f338065f59ed027c13952c83

Koncepció es Megközelítés
Ez a kóder serializes mezők írásban őket egymás után, mint a nyers bájt, nem metaadatok. Például:

    struct S {
        var a: Int16
        var b: Int32
        var c: Int64
    }

Az eredmény a kódolás egy példányát S tizennégy byte hosszú, két bájt egy négy byte b, nyolc byte c. Az eredmény szinte ugyanaz, mint írtam, a nyers alapjául szolgáló memória S, kivéve, hogy nincs padding, a számok byte-cserélték, hogy endian agnosztikus, képes intelligensen chase le hivatkozásokat, valamint egyedi kódolás, ha szükséges.

Ez a fajta egyszerű bináris kódolás egy kis hobbi, pedig már korábban is kísérletezett más megközelítések a Swift, amelyek közül egyik sem volt kielégítő. Amikor a Swift 4 béta elérhetővé vált a Codable, úgy nézett ki, hogy működik ez.

A használata Codable kissé sértő. Azt akarom, hogy kihasználják a fordító által generált Encodable es Decodable megvalósítások, de azok használata húzva, kódolás, mivel az egyenes vonal nem-metaadatok bináris formátum nagyjából az ellentéte a kulcsos kódolás. A megoldás egyszerű: figyelmen kívül hagyja a kulcsot, támaszkodnak a kódolás, dekódolás, annak érdekében, hogy legyen következetes. Ez csúnya, rossz ötlet az általános, de ez nem működik, sőt, még egy tweet egy tagja a Swift csapat, jelezve, hogy ez lehet az OK gombot. Ez a megközelítés nyilvánvalóan nem rugalmas, hogy a változások a területen elrendezés vagy mező, de olyan hosszan, mint ön is tudja megérteni, hogy elfogadható.

Ez azt jelenti, hogy tetszőleges implementáció Codable nem lehet megbízni, hogy működik ez a kóder. Tudjuk, hogy a fordító által generált megvalósítások munka, korlátozásokkal, de lehet, hogy implementációk a standard könyvtár (például, hogy a végrehajtás a Array), amely támaszkodni, szemantika, hogy ez a kóder nem támogatja. Annak érdekében, hogy több fájltípus megnyitásához ne partipate a bináris kódolás nélkül bírál el, én hoztam létre a saját protokollok a bináris kódolás:

    public protocol BinaryEncodable: Encodable {
        func binaryEncode(to encoder: BinaryEncoder) throws
    }

    public protocol BinaryDecodable: Decodable {
        init(fromBinary decoder: BinaryDecoder) throws
    }

    public typealias BinaryCodable = BinaryEncodable & BinaryDecodable

Írtam fájlokat, hogy egyszerűsítse a gyakori eset, amikor csak akarod használni a fordító általi végrehajtásának Codable:

    public extension BinaryEncodable {
        func binaryEncode(to encoder: BinaryEncoder) throws {
            try self.encode(to: encoder)
        }
    }

    public extension BinaryDecodable {
        public init(fromBinary decoder: BinaryDecoder) throws {
            try self.init(from: decoder)
        }
    }

Így, a saját típusok csak megfelel BinaryCodable, akkor egy alapértelmezett végrehajtása mindent, amire szükségük van, mindaddig, amíg megfelelnek a követelményeknek. Ez szükséges, hogy minden mezőt kell Codable, de nem szükséges az összes mezőt, hogy BinaryCodable. Típus ellenőrzés kell tenni futásidőben, ami sajnálatos, de elfogadható.

A kódoló/dekódoló megvalósítás egyszerű: ők kódolás/dekódolás mindent annak érdekében, figyelmen kívül hagyva a kulcsokat. A kódoló termel bájt megfelelő értékek kódolt, valamint a dekódoló állít elő értékeket a byte-tárolt.

BinaryEncoder Alapjai
A kódoló egy nyilvános osztály:

    public class BinaryEncoder {

Van egy mező, amely az adatokat kódolt eddig:

    fileprivate var data: [UInt8] = []

Ez az adat indul ki, hogy üres, bájt vagy terjedelmű, mint az értékek kódolva.

Egy kényelmes módszer befejeződik a folyamat, ami egy kódoló például a kódolás egy tárgyat, majd visszatér a példány adatok:

    static func encode(_ value: BinaryEncodable) throws -> [UInt8] {
        let encoder = BinaryEncoder()
        try value.binaryEncode(to: encoder)
        return encoder.data
    }

A kódolási folyamat lehet dobni futásidejű hibák, így a kódolónak szüksége van egy hiba típusa:

    enum Error: Swift.Error {
        case typeNotConformingToBinaryEncodable(Encodable.Type)
        case typeNotConformingToEncodable(Any.Type)
    }

Menjünk tovább, hogy az alacsony szintű kódolási módszereket. Kezdjük egy általános módszer, amely kódolja a nyers bájt értéke:

    func appendBytes<T>(of: T) {
        var target = of
        withUnsafeBytes(of: &target) {
            data.append(contentsOf: $0)
        }
    }

Ez lesz az alapja a másik kódolási módszereket.

Vessünk egy gyors pillantást a módszerek kódolás Float es Double következő. CoreFoundation van segítő funkciók, amelyek gondoskodni egy byte csere szükséges, így ezek a módszerek hívás azok a funkciók, majd hívja appendBytes az eredmény:

    func encode(_ value: Float) {
        appendBytes(of: CFConvertFloatHostToSwapped(value))
    }

    func encode(_ value: Double) {
        appendBytes(of: CFConvertDoubleHostToSwapped(value))
    }

Ha már ott vagyunk, itt van a módszer, a kódolás egy Bool. Azt jelenti, hogy a Bool egy UInt8, amely egy 0 vagy 1 kódolja, hogy:

    func encode(_ value: Bool) throws {
        try encode(value ? 1 as UInt8 : 0 as UInt8)
    }

BinaryEncoder még egy kódolási módszer, amely gondoskodik a kódolás minden más Encodable típusok:

    func encode(_ encodable: Encodable) throws {

Ezt különleges esetekben különböző, így bekapcsolja a paramétert:

        switch encodable {

Int es UInt kell külön kezelni, mert a méretek nem következetes. Attól függően, hogy a target platform, lehet, hogy 32 bit vagy 64 bit. Megoldani ezt, átalakítani Int64 vagy UInt64 majd kódolni, hogy érték:

        case let v as Int:
            try encode(Int64(v))
        case let v as UInt:
            try encode(UInt64(v))

Minden más integer típusú kezelik a FixedWidthInteger jegyzőkönyv, amely felfedi elég funkció, hogy a szükséges byte csere kódolás értékek. Mert FixedWidthInteger használ Self néhány visszatérési típusú, nem tudtam, hogy ezt a munkát közvetlenül itt. Ehelyett azt kiterjesztett FixedWidthInteger egy binaryEncode módszer, amely kezeli a munka:

        case let v as FixedWidthInteger:
            v.binaryEncode(to: self)

Float, Double es Bool hívja a típus-specifikus módszerek felett:

        case let v as Float:
            encode(v)
        case let v as Double:
            encode(v)
        case let v as Bool:
            try encode(v)

Bármi ami BinaryEncodable által kódolt hívja a binaryEncode módszer múló self:

        case let binary as BinaryEncodable:
            try binary.binaryEncode(to: self)

Van még egy eset kezelni. Minden érték, amit kap, ez messze nem az a típus, tudjuk, hogyan kell kódolni natívan, sem BinaryEncodable. Ebben az esetben, akkor dobj egy hiba a hívónak hogy ez az érték nem felel meg a jegyzőkönyv:

        default:
            throw Error.typeNotConformingToBinaryEncodable(type(of: encodable))
        }
    }

Végül, nézzük meg a FixedWidthInteger kiterjesztését. Ez van, ki kell hívni self.bigEndian, hogy egy hordozható képviselete az integer típusú, majd hívja appendBytes a kódoló kódolni, hogy képviselet:

    private extension FixedWidthInteger {
        func binaryEncode(to encoder: BinaryEncoder) {
            encoder.appendBytes(of: self.bigEndian)
        }
    }

Most az összes fontos részei a bináris kódolás, de még mindig nem Kódoló végrehajtását. Elérni, majd hozzon létre implementáció a tartály protokollok, amelyek hívja vissza a BinaryEncoder, hogy ezt a munkát.

BinaryEncoder Kódoló Végrehajtása
Kezdjük azzal, hogy nézi az implementáció a konténerek. Kezdjük a KeyedEncodingContainerProtocol megvalósítás:

    private struct KeyedContainer<Key: CodingKey>: KeyedEncodingContainerProtocol {

A végrehajtás szüksége van egy hivatkozás, hogy a bináris kódoló, hogy ez működik:

        var encoder: BinaryEncoder

Kódoló szükséges codingPath tulajdonság, amely visszatér egy sor CodingKey jelző értékek az aktuális útvonalon, a kódoló. Mivel ez a kódoló nem igazán támogatja a kulcs az első helyen, mindig vissza egy üres tömb:

        public var codingPath: [CodingKey] { return [] }

Kód amely használja ezt az osztályt meg kell valósítani, hogy nem kéri ezt az értéket, hogy semmi értelme.

A jegyzőkönyv akkor van egy csomó módszerek kódolás, mind a különböző típusú hogy támogatja:

    public mutating func encode(_ value: Bool, forKey key: Self.Key) throws
    public mutating func encode(_ value: Int, forKey key: Self.Key) throws
    public mutating func encode(_ value: Int8, forKey key: Self.Key) throws
    public mutating func encode(_ value: Int16, forKey key: Self.Key) throws
    public mutating func encode(_ value: Int32, forKey key: Self.Key) throws
    public mutating func encode(_ value: Int64, forKey key: Self.Key) throws
    public mutating func encode(_ value: UInt, forKey key: Self.Key) throws
    public mutating func encode(_ value: UInt8, forKey key: Self.Key) throws
    public mutating func encode(_ value: UInt16, forKey key: Self.Key) throws
    public mutating func encode(_ value: UInt32, forKey key: Self.Key) throws
    public mutating func encode(_ value: UInt64, forKey key: Self.Key) throws
    public mutating func encode(_ value: Float, forKey key: Self.Key) throws
    public mutating func encode(_ value: Double, forKey key: Self.Key) throws
    public mutating func encode(_ value: String, forKey key: Self.Key) throws
    public mutating func encode<T>(_ value: T, forKey key: Self.Key) throws where T : Encodable

Mi kell végrehajtani ezeket egyenként. Kezdjük azzal, hogy az utolsó, amely kezeli az általános Kódoló értékek. Csak fel kell, hogy hívja át BinaryEncoder ez a kódolás módja:

        func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
            try encoder.encode(value)
        }

Használhatjuk egy hasonló technika végrehajtása a egyéb módszerek… mi ez? A fordító hibát, arról jegyzőkönyv felel volna el?

Kiderült, hogy ez egy végrehajtását kódol, amely teljesíti az összes kódolási módszerek a jegyzőkönyv, mert az összes többi típus Encodable. Alkalmas általános módszer teljesíti, bármely megfelelő protokoll követelményeknek. Nyilvánvaló, utólag, de akkor még nem tudtam, amíg már félig kész volt ezt a kódot, majd láttam, hogy a hibák nem jelenik meg, ha a törölt típus-specifikus módszerek.

Most már értem, hogy miért nem hajtotta végre BinaryEncoder van kódolási módszer egy nagy switch használata helyett külön implementációk a különböző támogatott típusok. Túlterhelt módszerek oldódik meg, a fordítási időben alapján a statikus típus, ami elérhető a hívás helyén. A fenti hívás kódoló.kódolás(érték) mindig hívás func kódolás(_ encodable: Encodable) még akkor is, ha a tényleges érték telt el, mondjuk, egy ággyal Double vagy egy Bool. Annak érdekében, hogy ez az egyszerű csomagolás, a végrehajtás BinaryEncoder kell dolgozni egy egységes belépési pont, ami azt jelenti, hogy kell lennie egy nagy switch.

KeyedEncodingContainerProtocol szükséges néhány más módszerek. Van egy kódolási nulla, ami mi végre, hogy nem tesz semmit:

        func encodeNil(forKey key: Key) throws {}

Aztán ott vannak négy módszerek vissza beágyazott konténerek, vagy superclass kódolókhoz. Nem teszünk semmit, okos, szóval ez csak küldöttek vissza a jeladó:

        func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
            return encoder.container(keyedBy: keyType)
        }

        func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
            return encoder.unkeyedContainer()
        }

        func superEncoder() -> Encoder {
            return encoder
        }

        func superEncoder(forKey key: Key) -> Encoder {
            return encoder
        }
    }

Mi is kell implementáció UnkeyedEncodingContainer, SingleValueEncodingContainer. Kiderült, hogy ezek a jegyzőkönyvek hasonló ahhoz, hogy használni tudjuk az egységes végrehajtás mind. A tényleges végrehajtás majdnem ugyanaz, mint volt a KeyedEncodingContainerProtocol, azzal a kiegészítéssel, egy bábu count tulajdonság:

    private struct UnkeyedContanier: UnkeyedEncodingContainer, SingleValueEncodingContainer {
        var encoder: BinaryEncoder

        var codingPath: [CodingKey] { return [] }

        var count: Int { return 0 }

        func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
            return encoder.container(keyedBy: keyType)
        }

        func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
            return self
        }

        func superEncoder() -> Encoder {
            return encoder
        }

        func encodeNil() throws {}

        func encode<T>(_ value: T) throws where T : Encodable {
            try encoder.encode(value)
        }
    }

Használja ezeket a konténereket, majd, hogy BinaryEncoder felel meg Encoder.

Encoder szükséges codingPath tulajdon, mint a konténerek:

    public var codingPath: [CodingKey] { return [] }

Azt is megköveteli, hogy egy userInfo ingatlan. Nem támogatjuk, hogy az vagy, tehát visszatér egy üres szótár:

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

Aztán ott vannak a három módszer amely vissza konténerek:

    public func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
        return KeyedEncodingContainer(KeyedContainer<Key>(encoder: self))
    }

    public func unkeyedContainer() -> UnkeyedEncodingContainer {
        return UnkeyedContanier(encoder: self)
    }

    public func singleValueContainer() -> SingleValueEncodingContainer {
        return UnkeyedContanier(encoder: self)
    }

Ez a vége BinaryEncoder.

BinaryDecoder Alapjai
A dekóder egy nyilvános osztály túl:

    public class BinaryDecoder {

Mint a kódoló néhány adat:

    fileprivate let data: [UInt8]

Ellentétben a kódoló, dekódoló adatok betöltése a tárgy, ha létre. A hívó fél úgy rendelkezik, hogy az adatok, hogy a dekóder fogja dekódolni:

    public init(data: [UInt8]) {
        self.data = data
    }

A dekóder is kell nyomon követni, hogy hol van benne az adatok dekódolja. Ez nem, hogy egy kurzor ingatlan, amely indul ki elején az adatokat:

    fileprivate var cursor = 0

Egy kényelmes módszer befejeződik a folyamat, ami egy dekóder es dekódolás érték:

    static func decode<T: BinaryDecodable>(_ type: T.Type, data: [UInt8]) throws -> T {
        return try BinaryDecoder(data: data).decode(T.self)
    }

A dekóder rendelkezik saját hibát dobja során a dekódolási folyamat. A dekódolás nem sikerül a sok, több szempontból kódolás, tehát BinaryDecoder Error típus már sokkal több esetekben:

    enum Error: Swift.Error {
        case prematureEndOfData
        case typeNotConformingToBinaryDecodable(Decodable.Type)
        case typeNotConformingToDecodable(Any.Type)
        case intOutOfRange(Int64)
        case uintOutOfRange(UInt64)
        case boolOutOfRange(UInt8)
        case invalidUTF8([UInt8])
    }

Most jöhet a tényleges dekódolás. A legalacsonyabb szint a módszer olvas egy bizonyos számú bájt ki adatokat a mutató közelít cursor, vagy dobtak prematureEndOfData ha az adatok nem elég byte:

    func read(_ byteCount: Int, into: UnsafeMutableRawPointer) throws {
        if cursor + byteCount > data.count {
            throw Error.prematureEndOfData
        }

        data.withUnsafeBytes({
            let from = $0.baseAddress! + cursor
            memcpy(into, from, byteCount)
        })

        cursor += byteCount
    }

Van még egy kis általános csomagolás, amely vesz egy inout T olvas be, hogy értéke, használata MemoryLayout kitalálni, hogy hány byte olvasni.

    func read<T>(into: inout T) throws {
        try read(MemoryLayout<T>.size, into: &into)
    }

Mint BinaryEncoder, BinaryDecoder van módszerek dekódolás lebegőpontos típusok. Ezek, létrehoz egy üres CFSwappedFloat értéket olvas be, majd meghívja a megfelelő CF funkció átalakítani, hogy a lebegőpontos típus a kérdés:

    func decode(_ type: Float.Type) throws -> Float {
        var swapped = CFSwappedFloat32()
        try read(into: &swapped)
        return CFConvertFloatSwappedToHost(swapped)
    }

    func decode(_ type: Double.Type) throws -> Double {
        var swapped = CFSwappedFloat64()
        try read(into: &swapped)
        return CFConvertDoubleSwappedToHost(swapped)
    }

A módszer a dekódolás Bool dekódolja egy UInt8, akkor false értékkel tér vissza, ha 0, akkor igaz 1, különben dob hiba:

    func decode(_ type: Bool.Type) throws -> Bool {
        switch try decode(UInt8.self) {
        case 0: return false
        case 1: return true
        case let x: throw Error.boolOutOfRange(x)
        }
    }

Az általános dekódolni módszer Dekódolható használ egy nagy switch dekódolni különböző konkrét típusok:

    func decode<T: Decodable>(_ type: T.Type) throws -> T {
        switch type {

Az Int es UInt, fejt meg egy Int64 vagy UInt64, akkor átalakítja egy Int vagy UInt, vagy dob egy hiba:

        case is Int.Type:
            let v = try decode(Int64.self)
            if let v = Int(exactly: v) {
                return v as! T
            } else {
                throw Error.intOutOfRange(v)
            }
        case is UInt.Type:
            let v = try decode(UInt64.self)
            if let v = UInt(exactly: v) {
                return v as! T
            } else {
                throw Error.uintOutOfRange(v)
            }

A fordító nem veszi észre, hogy T írja meg kell egyeznie az értékek termelődik, így as! T meggyőzi, hogy össze ezt a kódot.

Más egész számok kezelése keresztül FixedWidthInteger segítségével kiterjesztése módszer:

        case let intT as FixedWidthInteger.Type:
            return try intT.from(binaryDecoder: self) as! T

Float, Double es Bool minden hívás a típus-specifikus dekódolás módszerek:

        case is Float.Type:
            return try decode(Float.self) as! T
        case is Double.Type:
            return try decode(Double.self) as! T
        case is Bool.Type:
            return try decode(Bool.self) as! T

BinaryDecodable típusok használata a initializer-ben meghatározott jegyzőkönyv, múló self:

        case let binaryT as BinaryDecodable.Type:
            return try binaryT.init(fromBinary: self) as! T

Ha esetek egyike sem áll le, akkor dobj egy hiba:

        default:
            throw Error.typeNotConformingToBinaryDecodable(type)
        }
    }

FixedWidthInteger módszer Self.init() egy értéket, olvassa byte-ba, majd használja a bigEndian: initializer, hogy végre byte csere:

    private extension FixedWidthInteger {
        static func from(binaryDecoder: BinaryDecoder) throws -> Self {
            var v = Self.init()
            try binaryDecoder.read(into: &v)
            return self.init(bigEndian: v)
        }
    }

Hogy gondoskodik az alapítvány. Most, hogy végre Decoder.

BinaryDecoder Dekóder Végrehajtása
Mint korábban, mi végre a három konténer protokollok. Kezdjük a kulcsos konténer:

    private struct KeyedContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {

Ez küldöttek mindent, hogy a dekóder, így szüksége van egy hivatkozás, hogy:

       var decoder: BinaryDecoder

A protokoll előírja, hogy codingPath:

        var codingPath: [CodingKey] { return [] }

Azt is megköveteli, hogy allKeys, amely visszaadja az összes kulcsokat, hogy a tartály tud. Mivel nem igazán támogatja a kulcs az első helyen, ennek eredménye egy üres tömb:

        var allKeys: [Key] { return [] }

Ez is egy módszer, hogy ha a tartályt tartalmaz egy adott gombot. Majd vakon mondani, hogy “igen”, hogy minden ilyen kérdést:

        func contains(_ key: Key) -> Bool {
            return true
        }

Mint korábban, KeyedDecodingContainerProtocol van egy csomó különböző dekódolni módszerek, melyek mind beérniük általános módszer Decodable:

        func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
            return try decoder.decode(T.self)
        }

Van egy decodeNil, amely majd nem tesz semmit, mindig sikerül:

        func decodeNil(forKey key: Key) throws -> Bool {
            return true
        }

Beágyazott konténerek, valamint a superclass dekódolja megbízott vissza a dekóder:

        func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
            return try decoder.container(keyedBy: type)
        }

        func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
            return try decoder.unkeyedContainer()
        }

        func superDecoder() throws -> Decoder {
            return decoder
        }

        func superDecoder(forKey key: Key) throws -> Decoder {
            return decoder
        }
    }

Mint korábban az egyik típus végre mindketten a másik konténer protokollok:

    private struct UnkeyedContainer: UnkeyedDecodingContainer, SingleValueDecodingContainer {
        var decoder: BinaryDecoder

        var codingPath: [CodingKey] { return [] }

        var count: Int? { return nil }

        var currentIndex: Int { return 0 }

        var isAtEnd: Bool { return false }

        func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
            return try decoder.decode(type)
        }

        func decodeNil() -> Bool {
            return true
        }

        func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
            return try decoder.container(keyedBy: type)
        }

        func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
            return self
        }

        func superDecoder() throws -> Decoder {
            return decoder
        }
    }

Most BinaryDecoder maga tud nyújtani dummy implementáció a tulajdonságok által előírt Decoder szükséges módszerek vissza esetben a konténerek:

    public var codingPath: [CodingKey] { return [] }

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

    public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
        return KeyedDecodingContainer(KeyedContainer<Key>(decoder: self))
    }

    public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
        return UnkeyedContainer(decoder: self)
    }

    public func singleValueContainer() throws -> SingleValueDecodingContainer {
        return UnkeyedContainer(decoder: self)
    }

Ez a vége BinaryDecoder.

Tömb es Karakterlánc Kiterjesztés
Annak érdekében, hogy a programozóknak több hasznos, én végrehajtott BinaryCodable a Array es String. Elméletben tudtam telefonálni a Codable végrehajtása, de nem számíthatok, hogy a végrehajtás dolgozni a korlátozások a bináris programozóknak, nem irányítani a sorszámozott képviselet. Ehelyett azt kézzel hajtották végre.

A terv az, hogy a Array kódolni a gróf, majd kódolni elemei. Hogy megfejteni, dekódolni tudja a gróf, majd dekódolni, hogy sok eleme. String konvertálja magát, hogy UTF-8 formájában Array, majd használja a Array végrehajtását a valódi munkát.

Egy nap, amikor a Swift jut feltételes megfelelőség, akkor képes lesz arra, hogy írjon kiterjesztését Tömb: BinaryCodable, ahol Element: BinaryCodable jelzi, hogy a Array  csak codable, ha annak tartalma. Most, Swift nem tudja kifejezni, hogy fogalma. Ehelyett azt kell mondanunk, hogy a Array  mindig BinaryCodable, akkor runtime típusú ellenőrzések biztosítása érdekében, hogy a tartalom a megfelelő.

Kódolás kérdése ellenőrzése a típusú Element, kódolás self.count, akkor a kódolás minden elemét:

    extension Array: BinaryCodable {
        public func binaryEncode(to encoder: BinaryEncoder) throws {
            guard Element.self is Encodable.Type else {
                throw BinaryEncoder.Error.typeNotConformingToEncodable(Element.self)
            }

            try encoder.encode(self.count)
            for element in self {
                try (element as! Encodable).encode(to: encoder)
            }
        }

Dekódolás az ellenkezője. Ellenőrizze a típus, dekódolni a gróf, majd dekódolni, hogy sok elemek:

        public init(fromBinary decoder: BinaryDecoder) throws {
            guard let binaryElement = Element.self as? Decodable.Type else {
                throw BinaryDecoder.Error.typeNotConformingToDecodable(Element.self)
            }

            let count = try decoder.decode(Int.self)
            self.init()
            self.reserveCapacity(count)
            for _ in 0 ..< count {
                let decoded = try binaryElement.init(from: decoder)
                self.append(decoded as! Element)
            }
        }
    }

String majd kódolni magát azáltal, hogy egy Array a utf8 ingatlan kódolás hogy:

    extension String: BinaryCodable {
        public func binaryEncode(to encoder: BinaryEncoder) throws {
            try Array(self.utf8).binaryEncode(to: encoder)
        }

Dekódolás dekódolja az UTF-8-as Array, akkor létrehoz egy String. Ez a sikertelen lesz, ha a dekódolt Array nem érvényes UTF-8, szóval van egy kis extra kódot ide, hogy ellenőrizze, hogy dobja egy hiba:

        public init(fromBinary decoder: BinaryDecoder) throws {
            let utf8: [UInt8] = try Array(fromBinary: decoder)
            if let str = String(bytes: utf8, encoding: .utf8) {
                self = str
            } else {
                throw BinaryDecoder.Error.invalidUTF8(utf8)
            }
        }
    }

Példa Használatra
Ez gondoskodik a bináris kódolás, dekódolás. Használata egyszerű. Állapítsa meg kell tartani az BinaryCodable, akkor használd BinaryEncoder es BinaryDecoder a típusok:

    struct Company: BinaryCodable {
        var name: String
        var employees: [Employee]
    }

    struct Employee: BinaryCodable {
        var name: String
        var jobTitle: String
        var age: Int
    }

    let company = Company(name: "Joe's Discount Airbags", employees: [
        Employee(name: "Joe Johnson", jobTitle: "CEO", age: 27),
        Employee(name: "Stan Lee", jobTitle: "Janitor", age: 87),
        Employee(name: "Dracula", jobTitle: "Dracula", age: 41),
        Employee(name: "Steve Jobs", jobTitle: "Visionary", age: 56),
    ])
    let data = try BinaryEncoder.encode(company)
    let roundtrippedCompany = try BinaryDecoder.decode(Company.self, data: data)
    // roundtrippedCompany contains the same data as company

Következtetés
Swift új Codable protokoll szívesen emellett a nyelvet, hogy megszüntesse sok boilerplate kód. Ez elég rugalmas ahhoz, hogy egyszerű használni/visszaélés a dolgok jóval JSON, ingatlan lista elemzés. Egyszerű bináris formátumok, mint például ez nem gyakran nevezik, de a célra, de érdekes látni, hogy Codable lehet használni, az valami egészen más, mint a beépített lebonyolítására. Encoder es Decoder protokollok nagy, de megfontolt használata generikus vágja le sok az ismétlődő kód, valamint a végrehajtás viszonylag egyszerű a végén.

BinaryCoder volt írva a kísérleti, oktatási célból, de ez valószínűleg nem az, amire te akarod használni a saját programok. Vannak azonban olyan esetek, hol lehet alkalmas addig, amíg érted a cserével érintett.

 

Vissza a főoldalra