Erhöhung der Parser-LoyalitÀt von Serverdaten unter iOS

Bei der Entwicklung mobiler Anwendungen auf die eine oder andere Weise mĂŒssen wir Serverdaten in interne Anwendungsmodelle zerlegen. In den allermeisten FĂ€llen liegen diese Daten im JSON-Format vor. Ab Swift 4 besteht das Hauptwerkzeug zum Parsen von JSON darin, ein Protokoll Decodableund ein Objekt zu verwenden JSONDecoder.


Dieser Ansatz vereinfachte das Parsen von Daten erheblich und reduzierte die Anzahl der Boilerplate-Codes. In den meisten FĂ€llen reicht es aus, einfach Modelle mit Eigenschaften zu erstellen, die genau wie die Felder im JSON-Objekt benannt sind, und JSONDecoderden Rest fĂŒr Sie zu erledigen. Minimaler Code, maximaler Nutzen. Dieser Ansatz hat jedoch einen Nachteil, nĂ€mlich die extrem geringe Parser-LoyalitĂ€t. Ich werde erklĂ€ren. Wenn es eine Diskrepanz zwischen dem internen Datenmodell (decodierbare Objekte) und dem, was in JSON geliefert wurde, gibt,JSONDecoderwirft einen Fehler und wir verlieren das ganze Objekt. In einigen Situationen ist dieses Verhaltensmodell möglicherweise vorzuziehen, insbesondere wenn es beispielsweise um Finanztransaktionen geht. In vielen FĂ€llen wĂ€re es jedoch nĂŒtzlich, den Analyseprozess loyaler zu gestalten. In diesem Artikel möchte ich meine Erfahrungen teilen und ĂŒber die wichtigsten Möglichkeiten sprechen, um diese LoyalitĂ€t zu erhöhen.


UngĂŒltige Objekte filtern


Nun, das erste Element ist natĂŒrlich das Filtern ungĂŒltiger Objekte. In vielen Situationen möchten wir nicht das gesamte Objekt verlieren, wenn eines der verschachtelten Objekte ungĂŒltig ist. Dies gilt sowohl fĂŒr einzelne Objekte als auch fĂŒr Arrays von Objekten. Ich werde ein Beispiel geben. Angenommen, wir beantragen den Verkauf von Waren und erhalten auf einem der Bildschirme eine Warenliste in ungefĂ€hr dieser Form.


{
    "products": [
       {...},
       {...},
       ....
    ]
}

, . . , , - . , JSONDecoder “ ” .


, :


{
    "id": 1,
    "title": "Awesome product",
    "price": 12.2,
    "image": {
        "id": 1,
        "url": "http://image.png",
        "thumbnail_url": "http://thumbnail.png"
    }
}

, , image. , , image , image = nil. , , JSONDecoder .


, JSONDecoder 2 : decode decodeIfPresent. optional , nil, , null. .


, , . , init(decoder) try? nil. , boilerplate . .


struct FailableDecodable<Value: Decodable>: Decodable {

    var wrappedValue: Value?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try? container.decode(Value.self)
    }
}

struct FailableDecodableArray<Value: Decodable>: Decodable {

    var wrappedValue: [Value]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        var elements: [Value] = []
        while !container.isAtEnd {
            if let element = try? container.decode(Value.self) {
                elements.append(element)
            }
        }
        wrappedValue = elements
    }
}

.


struct ProductList: Decodable {
    var products:FailableDecodableArray<Product>
}

struct Product: Decodable {
    let id: Int
    let title: String
    let price: Double
    let image: FailableDecodable<Image>?
}

struct Image: Decodable {
    let id: Int
    let url: String
    let thumbnailUrl: String
}

. , .


let products = productsList.products.wrappedValue
let image = products.first?.image.wrappedValue

FailableDecodableArray . , RandomAccessCollection MutableCollection , wrappedValue. FailableDecodable . , , computed property , , . , .


@propertyWrapper


Swift 5.1 — @propertyWrapper.


@propertyWrapper
struct FailableDecodable<Value: Decodable>: Decodable {
    ...
}

@propertyWrapper
struct FailableDecodableArray<Value: Decodable>: Decodable {
    ...
}


struct ProductList: Decodable {
    @FailableDecodableArray
    var products:[Product]
}

struct Product: Decodable {
    let id: Int
    let title: String
    let price: Double
    @FailableDecodable
    let image:Image?
}

, wrappedValue . , , , , :)


, , optional.


@FailableDecodable
let image:Image?


let image: FailableDecodable<Image>

, optional Image? , wrappedValue optional , .
Swift


@FailableDecodable?
let image:Image?

, , JSON nil . @propertyWrapper , 100% JSON.


@dynamicMemberLookup


dynamicMemberLookup.


@dynamicMemberLookup
struct FailableDecodable<Value: Decodable>: Decodable {
    var wrappedValue: Value?

    subscript<Prop>(dynamicMember kp: KeyPath<Value, Prop>) -> Prop {
        wrappedValue[keyPath: kp]
    }

    subscript<Prop>(dynamicMember kp: WritableKeyPath<Value, Prop>) -> Prop {
            get {
                wrappedValue[keyPath: kp]
            }

            set {
                wrappedValue[keyPath: kp] = newValue
            }
    }
}

2 subscript, readonly , read/write . .


struct ProductList: Decodable {
    var products:FailableDecodableArray<Product>
}

struct Product: Decodable {
    let id: Int
    let title: String
    let price: Double
    let image: FailableDecodable<Image>?
}

@propertyWrapper wrappedValue, .


let imageURL = products.first?.image.url

, optional . , , wrappedValue, , .


products.first?.image.load() // Compilation error
products.first?.image.wrappedValue.load() // Success

( ), .



— . - , ( JSON) Swift , “1” 1 — . JSONDecoder , , . . , Product .


{
    "id": 1,
    "title": "Awesome product",
    "price": "12.2",
    "image": {
        "id": 1,
        "url": "http://image.png",
        "thumbnail_url": "http://thumbnail.png"
    }
}

, , , — , , , , , . , .


struct Convertible<Value: Decodable & LosslessStringConvertible>: Decodable {
    var wrappedValue: Value
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        guard let stringValue = try? container.decode(String.self),
            let value = Value(stringValue) else {
                wrappedValue = try container.decode(Value.self)
                return
        }
        wrappedValue = value
    }
}

struct Product: Decodable {
    let id: Int
    let title: String
    let price: Convertible<Double>
    let image: FailableDecodable<Image>?
}

, . FailableDecdable @propertyWrapper @dynamicMemberLookup .


( ) . , , API - , , - . , , , , -, , , -, , . , , .


struct StringConvertible: Decodable {
    var wrappedValue: String
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        guard let number = try? container.decode(Double.self) else {
            wrappedValue = try container.decode(String.self)
            return
        }
        wrappedValue = "\(number)"
    }
}

Zusammenfassend möchte ich darauf hinweisen, dass die Entstehung des Protokolls Decodablein Verbindung mit der Klasse JSONDecoderunser Leben in Bezug auf das Parsen von Serverdaten erheblich vereinfacht hat. Es ist jedoch erwĂ€hnenswert, dass es JSONDecoderim Moment eine extrem geringe LoyalitĂ€t aufweist. Um es zu verbessern (falls erforderlich), mĂŒssen Sie ein wenig arbeiten und ein paar Wrapper schreiben. Ich denke, dass all diese Funktionen in Zukunft im Objekt selbst realisiert werden JSONDecoder, da er vor relativ kurzer Zeit nicht einmal wusste, wie man SchlĂŒssel von Schlangenkoffer zu Kamelkoffer und Schnur konvertiert, und jetzt ist all dies "out of the box" verfĂŒgbar.


All Articles