عند تطوير تطبيقات الهاتف المحمول ، بطريقة أو بأخرى ، نواجه الحاجة إلى تحليل بيانات الخادم إلى نماذج تطبيقات داخلية. في الغالبية العظمى من الحالات ، تأتي هذه البيانات بتنسيق 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()
products.first?.image.wrappedValue.load()
( ), .
— . - , ( 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 حتى الآن ، والآن كل هذا متاح "خارج الصندوق".