زيادة ولاء محلل بيانات الخادم في iOS

عند تطوير تطبيقات الهاتف المحمول ، بطريقة أو بأخرى ، نواجه الحاجة إلى تحليل بيانات الخادم إلى نماذج تطبيقات داخلية. في الغالبية العظمى من الحالات ، تأتي هذه البيانات بتنسيق JSON. بدءًا من Swift 4 ، فإن الأداة الرئيسية لتحليل JSON هي استخدام بروتوكول Decodableوكائن JSONDecoder.


يبسط هذا النهج إلى حد كبير عملية تحليل البيانات ويقلل من عدد الشفرات المعيارية. في معظم الحالات ، يكفي إنشاء نماذج بخصائص تسمى تمامًا مثل الحقول الموجودة في كائن JSON JSONDecoderوستقوم بالباقي لك. الحد الأدنى للرمز ، أقصى فائدة. ومع ذلك ، فإن هذا النهج له عيب واحد ، وهو الولاء المحلل للغاية. سوف أشرح. إذا كان هناك أي اختلاف بين نموذج البيانات الداخلية (كائنات فك التشفير) وما جاء في JSON ،JSONDecoderيلقي خطأ ونفقد الكائن كله. ربما ، في بعض الحالات ، يُفضل نموذج السلوك هذا ، خاصة عندما يتعلق الأمر ، على سبيل المثال ، بالمعاملات المالية. ولكن في كثير من الحالات قد يكون من المفيد جعل عملية التحليل أكثر ولاءً. في هذه المقالة ، أود أن أشارك تجربتي وأتحدث عن الطرق الرئيسية لزيادة هذا الولاء نفسه.


تصفية الكائنات غير الصالحة


حسنًا ، العنصر الأول بالطبع هو تصفية الكائنات غير الصالحة. في العديد من المواقف ، لا نريد أن نفقد الكائن بالكامل إذا كان أحد الكائنات المتداخلة غير صالح. ينطبق هذا على كائنات مفردة ومصفوفات كائنات. سوف أعطي مثالا على ذلك. لنفترض أننا قدمنا ​​طلبًا لبيع السلع ، وفي إحدى الشاشات نحصل على قائمة السلع في هذا النموذج تقريبًا.


{
    "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)"
    }
}

تلخيص كل ما سبق ، أود أن أشير إلى أن ظهور البروتوكول Decodableبالاقتران مع الفئة JSONDecoderيبسط حياتنا بشكل كبير في ما يرتبط بتحليل بيانات الخادم. ومع ذلك ، تجدر الإشارة إلى أنه JSONDecoderفي الوقت الحالي لديه ولاء منخفض للغاية ولتحسينه (إذا لزم الأمر) ، تحتاج إلى العمل قليلاً وكتابة بعض الأغلفة. أعتقد أنه في المستقبل سيتم تحقيق جميع هذه الميزات في الكائن نفسه JSONDecoder، لأنه لم يكن يعرف في الآونة الأخيرة نسبيًا كيفية تحويل المفاتيح من snakecase إلى camelcase و string حتى الآن ، والآن كل هذا متاح "خارج الصندوق".


All Articles