मेरे पास APIRequest नाम का एक प्रोटोकॉल है जिसका नाम ResponseType और एक डिकोड फ़ंक्शन है। यह उदाहरण पूर्ण नहीं है, लेकिन मेरा मानना ​​है कि प्रश्न के लिए ये एकमात्र प्रासंगिक भाग हैं।

ArrayResponse नाम की एक संरचना भी होती है जो यह दर्शाती है कि जब नेटवर्क प्रतिक्रिया विभिन्न वस्तुओं के items की एक सरणी के रूप में लौटती है (विशिष्ट APIRequest के ResponseType के आधार पर, साथ ही साथ totalItems

protocol APIRequest {
    associatedtype ResponseType: Codable

    /// Decodes the request result into the ResponseType
    func decode(_: Result<Data, APIError>) throws -> ResponseType
}

struct ArrayResponse<T>: Codable where T: Codable {
    let items: [T]
    let totalItems: Int
}

यहां एक ऐसी संरचना का उदाहरण दिया गया है जो APIRequest प्रोटोकॉल का पालन करती है और इसे ResponseType को Brand के रूप में निर्दिष्ट करती है, जो एक Codable संरचना है जो सर्वर से लौटाए जा रहे ब्रांड डेटा का प्रतिनिधित्व करती है।

struct BrandRequest: APIRequest {
    typealias ResponseType = Brand
}
struct Brand: Codable {
    var brandID: Int
    var brandName: String?
}

BrandRequest का उपयोग सर्वर से एकल Brand लाने के लिए किया जाता है, लेकिन मैं Brand की एक सरणी भी प्राप्त कर सकता हूं (ऊपर ArrayResponse द्वारा दर्शाया गया है, क्योंकि ब्रांड है कई अलग-अलग प्रकारों में से एक, जो सभी एक ही पैटर्न का पालन करते हैं), BrandsRequest का उपयोग करते हुए, जो इसे ResponseType को Brands की एक सरणी के रूप में निर्दिष्ट करता है।

struct BrandsRequest: APIRequest {
    typealias ResponseType = [Brand]
}

APIRequest का पालन करने वाली प्रत्येक संरचना में एक decode फ़ंक्शन प्रदान करने के बजाय, मैंने प्रोटोकॉल एक्सटेंशन में एक डिफ़ॉल्ट कार्यान्वयन करने का निर्णय लिया है, क्योंकि वे सभी एक ही डिकोडिंग का पालन करते हैं।

इस पर निर्भर करते हुए कि ResponseType एक सरणी है (जैसे [Brand], या एक ही आइटम, जैसे ब्रांड, मैं एक भिन्न संस्करण का उपयोग करता हूं decode फ़ंक्शन का। यह एकल आइटम के लिए अच्छी तरह से काम करता है, लेकिन आइटम की सरणी के लिए, मैं ऐरे में देखना चाहता हूं, इसके तत्वों के प्रकार की खोज करना चाहता हूं, और इसका उपयोग यह जांचने के लिए करता हूं कि क्या result.decoded() को उस विशेष प्रकार के ArrayResponse<> के रूप में डिकोड किया जाता है।

इसलिए, उदाहरण के लिए, यदि मैं एक BrandsRequest बनाता हूं, तो मुझे यह शीर्ष decode फ़ंक्शन चाहिए जो को वापस करने के लिए Array को डिकोड करता है (result.decoded() के रूप में प्रयास करें ArrayResponse).items ब्रांड के साथ एक अलग संरचना (जैसे उत्पाद, ग्राहक, आदि) के आधार पर इस फ़ंक्शन को प्राप्त होने वाले सरणी में तत्व का प्रकार। इस उदाहरण में elementType प्राप्त करने के मेरे प्रयास के रूप में कुछ गैर-संकलन कोड है और इसे एक सामान्य तर्क के रूप में उपयोग करें, लेकिन निश्चित रूप से यह काम नहीं करता है। मैं भी सामान्य तर्क के रूप में कोडेबल पास नहीं कर सकता, क्योंकि संकलक मुझे बताता है: प्रोटोकॉल प्रकार का मान 'कोडेबल' (उर्फ 'डिकोडेबल और एनकोडेबल') 'डिकोडेबल' के अनुरूप नहीं हो सकता है; केवल संरचना/enum/वर्ग प्रकार प्रोटोकॉल के अनुरूप हो सकते हैं

तो मेरे प्रश्न हैं:

  1. क्या ArrayResponse<insert type here> में उपयोग करने के लिए ऐरे में तत्व के प्रकार को कैप्चर करने का कोई तरीका है?
  2. क्या decode नेटवर्क प्रतिक्रियाओं के लिए एक बेहतर तरीका है जो ArrayResponse जैसी दिखने वाली वस्तुओं की सरणी देता है बनाम एकल आइटम प्रतिक्रिया जैसे Brand?
extension APIRequest where ResponseType == Array<Codable> {
    func decode(_ result: Result<Data, APIError>) throws -> ResponseType {
        let elementType = type(of: ResponseType.Element.self)
        print(elementType)

        return (try result.decoded() as ArrayResponse<elementType>).items
    }
}

extension APIRequest {
    func decode(_ result: Result<Data, APIError>) throws -> ResponseType {
        return try result.decoded() as ResponseType
    }
}

परिशिष्ट: एक अन्य दृष्टिकोण जिसके बारे में मैंने सोचा था कि तत्व प्रकार के बजाय टी को सरणी प्रकार के रूप में उपयोग करने के लिए ArrayResponse<> को बदलना है:

struct ArrayResponse<T>: Codable where T: Codable {
    let items: T
    let totalItems: Int
}

और उसके बाद सरणी डीकोड को सरल बनाने के लिए:

extension APIRequest where ResponseType == Array<Codable> {
    func decode(_ result: Result<Data, APIError>) throws -> ResponseType {
        return (try result.decoded() as ArrayResponse<ResponseType>).items
    }
}

हालाँकि, संकलक मुझे ये 2 त्रुटियाँ देता है: 'ArrayResponse' requires that 'Decodable & Encodable' conform to 'Encodable' तथा Value of protocol type 'Decodable & Encodable' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols


परिशिष्ट 2: मैं सब कुछ काम कर रहा हूं और संकलित कर सकता हूं, अगर मैं सरणी के भीतर तत्व के प्रकार को परिभाषित करने के लिए APIRequest में एक और संबद्ध प्रकार जोड़ता हूं:

protocol APIRequest {
    associatedtype ResponseType: Codable
    associatedtype ElementType: Codable

    /// Decodes the request result into the ResponseType
    func decode(_: Result<Data, APIError>) throws -> ResponseType
}

और फिर Codable के बजाय ElementType का उपयोग करने के लिए मेरे सरणी decode फ़ंक्शन को बदलें:

extension APIRequest where ResponseType == Array<ElementType> {
    func decode(_ result: Result<Data, APIError>) throws -> ResponseType {
        return (try result.decoded() as ArrayResponse<ResponseType>).items
    }
}

लेकिन फिर मुझे प्रत्येक संरचना में ElementType की आपूर्ति करनी होगी जो APIRequest के अनुरूप है, जिसमें एकल अनुरोध शामिल हैं जहां यह ResponseType के लिए बेमानी है और इसका उपयोग नहीं किया जाता है। सरणी अनुरोधों के लिए, यह केवल सरणी ResponseType के अंदर का मान है, जो दोहराव भी महसूस करता है:

struct BrandRequest: APIRequest {
    typealias ResponseType = Brand
    typealias ElementType = Brand
}

struct BrandsRequest: APIRequest {
    typealias ResponseType = [Brand]
    typealias ElementType = Brand
}

मेरी समस्या की जड़ यह है कि मैं Brand प्रकार को [Brand] सरणी में खोजना चाहता हूं, और इसे ArrayResponse डिकोडिंग के लिए उपयोग करना चाहता हूं।

1
mpatzer 22 मई 2020, 04:21

2 जवाब

यह एक ArrayRequest बनाने और फिर ElementType का डिफ़ॉल्ट कार्यान्वयन प्रदान करने लायक हो सकता है जैसे:

protocol ArrayRequest: APIRequest where ResponseType: Collection {
    associatedtype ElementType = ResponseType.Element
}

Collection अतिरिक्त बाधाओं से बचने में मदद कर सकता है।

1
Jennifer Starratt 26 मई 2020, 21:06

मुझे संदेह है कि यह प्रोटोकॉल का दुरुपयोग है। PAT (संबद्ध प्रकार वाले प्रोटोकॉल) सभी मौजूदा प्रकारों में अधिक सुविधाएँ जोड़ने के बारे में हैं, और यह स्पष्ट नहीं है कि यह ऐसा करता है। इसके बजाय, मेरा मानना ​​है कि आपको जेनरिक समस्या है।

पहले की तरह, आपके पास एक ArrayResponse है, क्योंकि यह आपके API में एक खास बात है:

struct ArrayResponse<Element: Codable>: Codable {
    let items: [Element]
    let totalItems: Int
}

अब, प्रोटोकॉल के बजाय, आपको एक सामान्य संरचना की आवश्यकता है:

struct Request<Response: Codable> {
    // You need some way to fetch this, so I'm going to assume there's an URLRequest
    // hiding in here.
    let urlRequest: URLRequest

    // Decode single values
    func decode(_ result: Result<Data, APIError>) throws -> Response {
        return try JSONDecoder().decode(Response.self, from: result.get())
    }

    // Decode Arrays. This would be nice to put in a constrained extension instead of here,
    // but that's not currently possible in Swift
    func decode(_ result: Result<Data, APIError>) throws -> ArrayResponse<Response> {
        return try JSONDecoder().decode(ArrayResponse<Response>.self, from: result.get())
    }
}

और अंत में, आपको "BrandRequest" बनाने का एक तरीका चाहिए (लेकिन वास्तव में Request<Brand>):

struct Brand: Codable {
    var brandID: Int
    var brandName: String?
}

// You want "BrandRequest", but that's just a particular URLRequest for Request<Brand>.
// I'm going to make something up for the API:
extension Request where Response == Brand {
    init(brandName: String) {
        self.urlRequest = URLRequest(url: URL(string: "https://example.com/api/v1/brands/(\brandName)")!)
    }
}

उस ने कहा, मैं शायद इसे समायोजित कर दूंगा और अलग-अलग Request एक्सटेंशन बनाउंगा जो अनुरोध के आधार पर सही डिकोडर (तत्व बनाम सरणी) संलग्न करता है। आपके प्रोटोकॉल के आधार पर वर्तमान डिज़ाइन, कॉल करने वाले को डिकोड समय पर यह तय करने के लिए मजबूर करता है कि क्या एक या अधिक तत्व हैं, लेकिन यह तब पता चलता है जब अनुरोध बनाया जाता है। तो मैं शायद इन पंक्तियों के साथ और अधिक अनुरोध बनाउंगा, और Response स्पष्ट रूप से ArrayResponse बनाउंगा:

struct Request<Response: Codable> {
    // You need some way to fetch this, so I'm going to assume there's an URLRequest
    // hiding in here.
    let urlRequest: URLRequest
    let decoder: (Result<Data, APIError>) throws -> Response
}

(और फिर init में उपयुक्त डिकोडर असाइन करें)


आपके द्वारा लिंक किए गए कोड को देखते हुए, हाँ, यह वर्ग विरासत को फिर से बनाने के लिए प्रोटोकॉल का उपयोग करने का एक बहुत अच्छा उदाहरण है। APIRequest एक्सटेंशन सामान्य एल्गोरिदम को लागू करने के बजाय डिफ़ॉल्ट कार्यान्वयन बनाने के बारे में है, और यह आमतौर पर "विरासत और ओवरराइड" OOP मानसिकता का सुझाव देता है। APIRequest के अनुरूप अलग-अलग structs के समूह के बजाय, मुझे लगता है कि यह एक APIRequest जेनेरिक स्ट्रक्चर (जैसा ऊपर वर्णित है) के रूप में बेहतर काम करेगा।

लेकिन आप अभी भी सभी मूल कोड को फिर से लिखे बिना वहां पहुंच सकते हैं। उदाहरण के लिए, आप एक सामान्य "सरणी" मैपिंग बना सकते हैं:

struct ArrayRequest<Element: Codable>: APIRequest {
    typealias ResponseType = [Element]
    typealias ElementType = Element
}

typealias BrandsRequest = ArrayRequest<Brand>

और निश्चित रूप से आप इसे एक परत ऊपर धकेल सकते हैं:

struct ElementRequest<Element: Codable>: APIRequest {
    typealias ResponseType = Element
    typealias ElementType = Element
}

typealias BrandRequest = ElementRequest<Brand>

और आपके सभी मौजूदा APIRequest सामान अभी भी काम करते हैं, लेकिन आपका सिंटैक्स बहुत आसान हो सकता है (और टाइपियालेस बनाने की कोई वास्तविक आवश्यकता नहीं है; ElementRequest<Brand> शायद अपने आप ठीक है)।

0
Rob Napier 27 मई 2020, 00:06