Object Mapping !

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

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

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

/posts

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

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

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

ولكن ما المميز من استخدام هذه المكتبات .. ؟ ربما ذكرت سابقا أن 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 Marshal  
public struct Post: Unmarshaling {

    let userId : Int
    let id: Int
    let title : String
    let body : String

    public 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(of: Post.self)
                print("mapped to post: \(mappedPosts)")
            } catch {
                print("Error mapping posts: \(error)")
            }
        }   
 }

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

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

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

extension Post {  
    public 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 = userId
        post.id = id

        if let title = item["title"] as? String {
            post.title = title
        }
        if let body = item["body"] as? String {
            post.body = body
        }
        return post
    }

    public 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  
public struct Post: Unmarshaling {

    let userId : Int
    let id: Int
    let title : String
    let body : String

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

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

وبس ,