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