IOS एप्लिकेशन में डेटा की मान्यता

मुझे लगता है कि हम में से प्रत्येक ने अनुप्रयोगों में डेटा को मान्य करने के कार्य का सामना किया है। उदाहरण के लिए, उपयोगकर्ता को पंजीकृत करते समय, आपको यह सुनिश्चित करने की आवश्यकता होती है कि ईमेल में सही प्रारूप है, और पासवर्ड सुरक्षा आवश्यकताओं को पूरा करता है, और इसी तरह। आप बहुत सारे उदाहरण दे सकते हैं, लेकिन यह सब एक काम के लिए उबलता है - फॉर्म जमा करने से पहले डेटा सत्यापन।


अगली परियोजना पर काम करते समय, मैंने अलग-अलग रूप में प्रत्येक स्क्रीन के लिए सत्यापन विधियों को लिखने के बजाय, एक सार्वभौमिक समाधान बनाने के बारे में सोचा। Swift 5.1 ने @propertyWrapper एनोटेशन पेश किया, और मुझे लगा कि निम्नलिखित की तरह सिंटैक्स रखना सुविधाजनक होगा:


 @Validated([validator1, validator2, ...]) var email: String? = nil let errors = $email.errors //   

प्रमाणकों


पहला कदम यह निर्धारित करना था कि सत्यापनकर्ता कैसे दिखेंगे और काम करेंगे। मेरी परियोजना के हिस्से के रूप में, मुझे डेटा की वैधता की जांच करने और उचित त्रुटि संदेश प्रदर्शित करने की आवश्यकता थी। अधिक जटिल त्रुटि से निपटने की कोई आवश्यकता नहीं थी, इसलिए सत्यापनकर्ता का कार्यान्वयन इस प्रकार था:


 struct ValidationError: LocalizedError { var message: String public var errorDescription: String? { message } } protocol Validator { associatedtype ValueType var errorMessage: String { get } func isValid(value: ValueType?) -> Bool } extension Validator { func validate(value: ValueType?) throws { if !isValid(value: value) { throw ValidationError(message: errorMessage) } } } 

यहां सब कुछ सरल है। सत्यापनकर्ता में एक त्रुटि संदेश होता है जो सत्यापन विफल होने पर सत्यापन को विफल करता है। यह त्रुटि हैंडलिंग को सरल करता है, क्योंकि सभी सत्यापनकर्ता एक ही प्रकार की त्रुटि लौटाते हैं, लेकिन विभिन्न संदेशों के साथ। एक उदाहरण एक सत्यापनकर्ता का कोड है जो एक नियमित अभिव्यक्ति के खिलाफ एक स्ट्रिंग की जांच करता है:


 struct RegexValidator: Validator { public var errorMessage: String private var regex: String public init(regex: String, errorMessage: String) { self.regex = regex self.errorMessage = errorMessage } public func isValid(value: String?) -> Bool { guard let v = value else { return false } let predicate = NSPredicate(format: "SELF MATCHES %@", regex) return predicate.evaluate(with: v) } } 

इस कार्यान्वयन में कई समस्या के लिए जाना जाता है। चूंकि Validator प्रोटोकॉल में एक associatedtype , इसलिए हम प्रकार का एक चर नहीं बना सकते हैं


 var validators:[Validator] //Protocol 'Validator' can only be used as a generic constraint because it has Self or associated type requirements 

इस समस्या को हल करने के लिए, हम एक मानक दृष्टिकोण का उपयोग करते हैं, अर्थात्, AnyValidator संरचना का निर्माण।

 private class ValidatorBox<T>: Validator { var errorMessage: String { fatalError() } func isValid(value: T?) -> Bool { fatalError() } } private class ValidatorBoxHelper<T, V:Validator>: ValidatorBox<T> where V.ValueType == T { private let validator: V init(validator: V) { self.validator = validator } override var errorMessage: String { validator.errorMessage } override func isValid(value: T?) -> Bool { validator.isValid(value: value) } } struct AnyValidator<T>: Validator { private let validator: ValidatorBox<T> public init<V: Validator>(validator: V) where V.ValueType == T { self.validator = ValidatorBoxHelper(validator: validator) } public var errorMessage: String { validator.errorMessage } public func isValid(value: T?) -> Bool { validator.isValid(value: value) } } 

मुझे लगता है कि इस पर टिप्पणी करने के लिए कुछ नहीं है। यह ऊपर वर्णित समस्या को हल करने के लिए एक मानक दृष्टिकोण है। यह मान्यकर्ता प्रोटोकॉल के लिए एक एक्सटेंशन जोड़ने के लिए भी उपयोगी होगा, जिससे आप एक AnyValidator ऑब्जेक्ट बना सकते हैं।

 extension Validator { var validator: AnyValidator<ValueType> { AnyValidator(validator: self) } } 

संपत्ति का आवरण


@Validated किए गए सत्यापनकर्ताओं के साथ, आप सीधे @Validated आवरण के कार्यान्वयन पर जा सकते हैं।


 @propertyWrapper class Validated<Value> { private var validators: [AnyValidator<Value>] var wrappedValue: Value? init(wrappedValue value: Value?, _ validators: [AnyValidator<Value>]) { wrappedValue = value self.validators = validators } var projectedValue: Validated<Value> { self } public var errors: [ValidationError] { var errors: [ValidationError] = [] validators.forEach { do { try $0.validate(value: wrappedValue) } catch { errors.append(error as! ValidationError) } } return errors } } 

इस लेख का उद्देश्य यह विश्लेषण करना नहीं है कि propertyWrapper रैपर कैसे काम करते हैं और वे किस वाक्यविन्यास का उपयोग करते हैं। यदि आप अभी तक उनसे नहीं मिल पाए हैं, तो मैं आपको सलाह देता हूं कि आप मेरे अन्य लेख, How to Approach Wrappers for Swift Properties (अंग्रेजी) पढ़ें।


यह कार्यान्वयन हमें उन संपत्तियों की घोषणा करने की अनुमति देता है, जिन्हें निम्नानुसार सत्यापन की आवश्यकता होती है:


 @Validated([ NotEmptyValidator(errorMessage: "Email can't be empty").validator, RegexValidator(regex:"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-za-z]{2,64} ", errorMessage:"Email has wrong format").validator ]) var email: String? = nil 

और निम्नानुसार किसी भी समय सत्यापन त्रुटियों की एक सरणी प्राप्त करें


 let errors = $email.errors 

छवि
ऐसी संभावना है कि सत्यापनकर्ताओं के कुछ संयोजन (उदाहरण के लिए, ईमेल सत्यापन) कई स्क्रीन पर एप्लिकेशन में दिखाई देंगे। कोड की प्रतिलिपि बनाने से बचने के लिए, ऐसे मामलों में Validated से विरासत में मिला एक अलग आवरण बनाना संभव है।


 @propertyWrapper final class Email: Validated<String> { override var wrappedValue: String? { get { super.wrappedValue } set { super.wrappedValue = newValue } } override var projectedValue: Validated<String> { super.projectedValue } init(wrappedValue value: String?) { let notEmptyValidator = NotEmptyValidator(errorMessage: "Email can't be empty") let regexValidator = RegexValidator(regex:"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-za-z]{2,64} ", errorMessage:"Email has wrong format").validator super.init(wrappedValue: value, [notEmptyValidator, regexValidator]) } } @Email var email: String? = nil 

दुर्भाग्य से, फिलहाल, @propertyWrapper एनोटेशन के लिए wrappedValue और projectedValue को ओवरराइड करने की आवश्यकता होती है, अन्यथा हमें एक संकलन त्रुटि मिलेगी। यह एक कार्यान्वयन बग की तरह दिखता है, इसलिए यह संभव है कि स्विफ्ट के भविष्य के संस्करणों में यह तय हो जाएगा।


रिएक्टिव जोड़ें


IOS13 के साथ देशी देशी प्रतिक्रियाशील प्रोग्रामिंग फ्रेमवर्क साथ में आया। और मुझे लगा कि निम्नलिखित सिंटैक्स के लिए यह उपयोगी हो सकता है:


 let cancellable = $email .publisher .map { $0.map { $0.localizedDescription }.joined(separator: ", ") } .receive(on: RunLoop.main) .assign(to: \.text, on: emailErrorLabel) 

यह वास्तविक समय में सत्यापन त्रुटियों के बारे में जानकारी अपडेट करने की अनुमति देगा (प्रत्येक वर्ण दर्ज किए जाने के बाद)। इस विचार का प्रारंभिक कार्यान्वयन इस प्रकार था:


 @propertyWrapper class Validated<Value> { private var _subject: Any! @available(iOS 13.0, *) private var subject: PassthroughSubject<[ValidationError], Never> { return _subject as! PassthroughSubject<[ValidationError], Never> } open var wrappedValue: Value? { didSet { if #available(iOS 13.0, *) { subject.send(errors) } } } public init(wrappedValue value: Value?, _ validators: [AnyValidator<Value>]) { wrappedValue = value self.validators = validators if #available(iOS 13.0, *) { _subject = PassthroughSubject<[ValidationError], Never>() } } @available(iOS 13.0, *) public var publisher: AnyPublisher<[ValidationError], Never> { subject.eraseToAnyPublisher() } // The rest of the code } 

इस तथ्य के कारण कि संग्रहीत संपत्ति को @available एनोटेशन के साथ चिह्नित नहीं किया जा सकता है, मुझे _subject और subject गुणों के साथ काम का उपयोग करना पड़ा। अन्यथा, सब कुछ बहुत स्पष्ट होना चाहिए। एक PassthroughObject बनाया जाता है जो हर बार wrappedValue संदेशों को बदलता है।


परिणामस्वरूप, उपयोगकर्ता द्वारा फ़ॉर्म भरने के बाद सत्यापन त्रुटि संदेश बदल जाते हैं।
छवि
इस समाधान के परीक्षण की प्रक्रिया में, एक बग का पता चला था। इस घटना के लिए ग्राहकों की उपस्थिति की परवाह किए बिना, हर बार संपत्ति में परिवर्तन होता है। एक तरफ, यह परिणाम को प्रभावित नहीं करता है, लेकिन दूसरी तरफ, उस मामले में जब हमें वास्तविक समय में सत्यापन की आवश्यकता नहीं होती है, तो अनावश्यक कार्रवाई की जाएगी। यह कम से कम एक ग्राहक होने पर ही सही तरीके से मान्य और संदेश भेजेगा। नतीजतन, इस आवश्यकता के साथ कोड को फिर से बनाया गया था।


 @propertyWrapper class Validated<Value> { private var _subject: Any! @available(iOS 13.0, *) private var subject: Publishers.HandleEvents<PassthroughSubject<[ValidationError], Never>> { return _subject as! Publishers.HandleEvents<PassthroughSubject<[ValidationError], Never>> } private var subscribed: Bool = false open var wrappedValue: Value? { didSet { if #available(iOS 13.0, *) { if subscribed { subject.upstream.send(errors) } } } } public init(wrappedValue value: Value?, _ validators: [AnyValidator<Value>]) { wrappedValue = value self.validators = validators if #available(iOS 13.0, *) { _subject = PassthroughSubject<[ValidationError], Never>() .handleEvents(receiveSubscription: {[weak self] _ in self?.subscribed = true }) } } // The rest of the code } 

नतीजतन, मुझे आवेदन में डेटा सत्यापन के लिए अपेक्षाकृत सार्वभौमिक समाधान मिला। यह कुछ समस्याओं को हल नहीं कर सकता है, उदाहरण के लिए, सरल संदेश आउटपुट की तुलना में अधिक जटिल त्रुटि से निपटने, लेकिन यह उपयोगकर्ता इनपुट के सरल सत्यापन के लिए उपयुक्त है। आप GitHub पर पूरा समाधान देख सकते हैं।

Source: https://habr.com/ru/post/hi485092/


All Articles