السلام عليكم ورحمة الله وبركاته ..

بعد الحديث في موضوع سابق عن عمليةالاتصال مع السيرفر بشكل منظم ومرتب عن طريق استخدام Moya وكما نعلم انها هي بالاساس تستخدم لعملية تنظيم الاتصال بالسيرفر أو الـ API ..

بعد عملية الاتصال مع السيرفر ولنفرض اننا قمنا بالاتصال بـ

/posts
من المتوقع ان يكون لدينا اما Response اما ب success أو failure ! لن أتطرق للتعامل مع حالات الخطأ في الوقت الحالي

يوجد لدينا العديد من المكتبات التي تساعد على تحويل الـ Json -> class, struct, enum وسأقوم بسردها بالأسفل

  • Marshal
  • Mapper
  • Unbox
  • Decodable
  • ObjectMapper
  • Gloss
  • Genome


بإمكانك الدخول على اي من هذه المكتبيات ومشاهدة طريقة استخدامها

ولكن ما المميز من استخدام هذه المكتبات .. ؟ ربما ذكرت سابقا أن Moya مستخدمه و مشهوره بشكل كبير ..

والمميز فيها هو أن كثير من هذه المكتبات التي ذكرت في الأعلى أضافوا دعما خاصاً لـ Moya بشكل مخصصا..

طبعاً ذلك لا يعني انه لا يمكنك استخدمها بشكل منفرد

بناءاً على هذه الصورة وبعد عمل مجموعة من المقارنات على هذه المكتبات سأقوم بتجربة Marshal ربما بسبب الأرقام التي اظهرتها من ناحية السرعة والأداء كما في الصورة في الأسفل !

المصدر

Object Mapping هو عملية تحويل البيانات التي وصلت من السيرفر على شكل JSON إلى Object او struct . أما Deserlization فهو عملية تحويلها إلى Json من جديد .

طريقة التركيب عن طريق Cocoapods

pod 'Moya-Marshal'
طيب نبدأ الشغل .. ؟! لنفرض أنك قمت بـ طلب بيانات معينة من إحدى الـ API المتوفرةوكانت بالشكل التالي

/posts
وكان الـ Response من السيرفر كما هو بالشكل التالي


[{
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae"
    } 
..]


وهو عبارة عن *Array من Json object *

ما سنقوم بفعله لتحويل البيانات التي استقبالها الى class, struc, enum بالشكل الذي تحتاجه ليسهل عملية التعامل معها فيما بعد وبشكل مباشر ..

import Marshalpublic 
struct Post: Unmarshaling {
	let userId : Int
	let id: Int
	let title : String
	let body : String

	init(object: MarshaledObject) throws {
    	userId = try object.value(for:"userId")
	    id = try object.value(for:"id")
    	title = try object.value(for:"title")
	    body = try object.value(for:"body")
	} 
}


وماذا بعد ذلك .. انتهينا .. فعند طلب البيانات من السيرفر عن طريق Moya كما في مثالنا السابق ستحتاج للتعامل معها بالشكل التالي ..

appNetworkProvider.request(.posts) { result in
    if case let .success(response) = result {
        do {
            let mappedPosts :[Post] = try response.mapArray(Post.self)print("mapped to post: (mappedPosts)")
        } catch {print("Error mapping posts: (error)")
        }
    }
}


وبإمكانك بعد ذلك استخدامها بشكل سهل وسلس مع التطبيق ..

الخلاصة : ما قمنا بعمله هو مجرد استخدام للكود الذي استعرضنها سابقا وقمنا باستخدام احدى المكتبات التي لديها دعم لما قمنا بعمله سابقا بحيث تم تحويل البيانات التي قمنا باستقبالها من السيرفر بشكل مباشر ..

للتعامل مع الـ Parsing الكود السابق كان بالشكل هذا

    extension Post {
    static func modelObject(item: Dictionary<String, Any>) -> Post? {
        var post = Post()
        guard let userId = item["userId"] as? Int, let id = item["id"] as? Int else {
            return nil }
        post.userId = userIdpost.id = id
        if let title = item["title"] as? String {
            post.title = title
        }
        if let body = item["body"] as? String {
            post.body = body
        }
        return post
    }
    
    static func modelObjects(objects: [[String: Any]]) -> [Post?] {
        
        return objects.map{ item in
            return Post.modelObject(item: item)
        }
    }
}



وعند استخدام الـ parsing بعد وصول البيانات

appNetworkProvider.request(.posts) { result in
switch result {
case let .success(moyaResponse):
    let data = moyaResponse.data

    do {
        let json = try JSONSerialization.jsonObject(with: data, options: []) as! [[String:Any]]
        let array : [Post?] = Post.modelObjects(objects: json)
        self.tableViewData = array
        self.tableView.reloadData()
    }catch (let error) {
        print(error.localizedDescription)
    }
// do something with the response data or statusCode
case let .failure(error):
    print(error.errorDescription ?? "")
    // this means there was a network failure - either the request
    // wasn't sent (connectivity), or no response was received (server
    // timed out).  If the server responds with a 4xx or 5xx error, that
    // will be sent as a ".success"-ful response.
}


الكود الحالي باستخدام Moya-Marshal أصبح بالشكل التالي :

import Marshal

struct Post: Unmarshaling {
    let userId : Int
    let id: Int
    let title : String
    let body : String
    
    init(object: MarshaledObject) throws {
        userId = try object.value(for:"userId")
        id = try object.value(for:"id")
        title = try object.value(for:"title")
        body = try object.value(for:"body")
    }
}




نقطة مهمة ويجب التنبه لها وهي أن القيم التي يتم استقبالها عن طريق الـ json يجب أن تكتب كما وصلتك من السيرفر او الـ API في

value(for:"userId")
ولكن من حيث اسنادها الى متغير في الـ struct او class فـ بإمكانك تسمية المتغير كما تريد

وعند وصول البيانات قمنا بالتالي

appNetworkProvider.request(.posts) { result in
    if case let .success(response) = result {
        do {
            let mappedPosts :[Post] = try response.mapArray(Post.self)print("mapped to post: (mappedPosts)")
        } catch {print("Error mapping posts: (error)")
        }
    }
}


بإمكانك تحميل المثال من هنا

وبس ,