مغلفة خاصية سويفت

إذا كنت تستخدم SwiftUI ، فربما تكون قد أوليت اهتمامًا للكلمات الرئيسية مثلObservedObject وEnvironmentObject و @ FetchRequest وما إلى ذلك. خاصية Wrappers (المشار إليها فيما يلي باسم "wrappers property") هي ميزة جديدة في Swift 5.1. ستساعدك هذه المقالة على فهم من أين تأتي جميع الإنشاءات من @ ، وكيفية استخدامها في SwiftUI وفي مشاريعك.



المترجم: إيفجيني زافوزانسكي ، مطور FunCorp.


ملاحظة: في الوقت الذي تم فيه إعداد الترجمة ، فقد جزء من الكود المصدري للمقال الأصلي أهميته بسبب التغييرات في اللغة ، لذلك تم استبدال بعض أمثلة الكود عمداً.


تم تقديم مغلفات الملكية لأول مرة في منتديات Swift في مارس 2019 ، قبل بضعة أشهر من إعلان SwiftUI. في اقتراحه الأصلي ، وصف دوغلاس جريجور ، أحد أعضاء فريق سويفت كور ، هذه البنية (التي كانت تسمى آنذاك مندوبي العقارات) بأنها "تعميم سهل الوصول إليه من قبل المستخدم للوظيفة التي توفرها حاليًا لغة مثل lazy ، على سبيل المثال."


إذا تم الإعلان عن خاصية باستخدام الكلمة الأساسية lazy ، فهذا يعني أنه سيتم تهيئة هذه الخاصية في أول مرة يتم الوصول إليها. على سبيل المثال ، يمكن تنفيذ تهيئة الخاصية المؤجلة باستخدام خاصية خاصة ، يمكن الوصول إليها من خلال خاصية محسوبة. لكن استخدام الكلمة الأساسية lazy يجعل ذلك أسهل بكثير.


 struct Structure {    //      lazy    lazy var deferred = …    //            private var _deferred: Type?    var deferred: Type {        get {            if let value = _deferred { return value }            let initialValue = …            _deferred = initialValue            return initialValue        }        set {            _deferred = newValue        }    } } 

SE-0258: تشرح خاصية التفاف الملكية تمامًا تصميم أغلفة الممتلكات وتنفيذها. لذلك ، بدلاً من محاولة تحسين الوصف في الوثائق الرسمية ، خذ بعين الاعتبار بعض الأمثلة التي يمكن تنفيذها باستخدام wrappers wrappers:


  • تقييد قيم الممتلكات ؛
  • تحويل القيم عند تغيير الخصائص ؛
  • تغيير دلالات المساواة ومقارنة الممتلكات ؛
  • تسجيل الدخول الملكية.

الحد من قيم الممتلكات


SE-0258: يوفر Property Wrapper العديد من الأمثلة العملية ، بما في ذلك @Clamping ، @Copying ، @Atomic ، @ThreadSpecific ، @Box ، @UserDefault . النظر في المجمع @Clamping ، والذي يسمح لك للحد من الحد الأقصى أو الحد الأدنى لقيمة الممتلكات.


 @propertyWrapper struct Clamping<Value: Comparable> {    var value: Value    let range: ClosedRange<Value>    init(initialValue value: Value, _ range: ClosedRange<Value>) {        precondition(range.contains(value))        self.value = value        self.range = range    }    var wrappedValue: Value {        get { value }        set { value = min(max(range.lowerBound, newValue), range.upperBound) }    } } 

يمكن استخدام @Clamping ، على سبيل المثال ، لنمذجة حموضة الحل ، والتي يمكن أن تأخذ قيمتها قيمة من 0 إلى 14.


 struct Solution {    @Clamping(0...14) var pH: Double = 7.0 } let carbonicAcid = Solution(pH: 4.68) 

ستؤدي محاولة تعيين قيمة الرقم الهيدروجيني خارج النطاق من (0...14) إلى جعل الخاصية تأخذ القيمة الأقرب إلى الحد الأدنى أو الحد الأقصى للفاصل الزمني.


 let superDuperAcid = Solution(pH: -1) superDuperAcid.pH // 0 

يمكن استخدام wrappers الخاصية لتنفيذ wrappers الأخرى. على سبيل المثال ، يحد البرنامج المجمع @UnitInterval قيمة خاصية للفاصل الزمني (0...1) باستخدام @Clamping(0...1) :


 @propertyWrapper struct UnitInterval<Value: FloatingPoint> {    @Clamping(0...1)    var wrappedValue: Value = .zero    init(initialValue value: Value) {        self.wrappedValue = value    } } 

أفكار مماثلة


  • تشير @NonNegative / @NonNegative إلى أن القيمة يمكن أن تكون رقمًا موجبًا أو سالبًا.
  • @NonZero يشير إلى أن قيمة الخاصية لا يمكن أن تكون 0.
  • @Validated أو @Whitelisted / @Blacklisted يقيد قيمة الخاصية إلى قيم معينة.

تحويل القيم عند تغيير الخصائص


التحقق من صحة قيم حقل النص هو صداع مستمر لمطوري التطبيقات. هناك العديد من الأشياء التي يجب عليك تتبعها: بدءًا من الترميزات مثل الترميز إلى محاولات ضارة لإدخال رمز من خلال حقل نص. ضع في اعتبارك استخدام برنامج التفاف الخاصية لإزالة المسافات التي أدخلها المستخدم في بداية ونهاية السطر.


 import Foundation let url = URL(string: " https://habrahabr.ru") // nil let date = ISO8601DateFormatter().date(from: " 2019-06-24") // nil let words = " Hello, world!".components(separatedBy: .whitespaces) words.count // 3 

يوفر Foundation trimmingCharacters(in:) ، والتي يمكنك من خلالها إزالة المسافات في بداية ونهاية السطر. يمكنك الاتصال بهذه الطريقة كلما احتجت إلى ضمان صحة المدخلات ، ولكنها ليست مريحة للغاية. يمكنك استخدام المجمع الملكية لهذا الغرض.


 import Foundation @propertyWrapper struct Trimmed {    private(set) var value: String = ""    var wrappedValue: String {        get { return value }        set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }    }    init(initialValue: String) {        self.wrappedValue = initialValue    } } 

 struct Post {    @Trimmed var title: String    @Trimmed var body: String } let quine = Post(title: " Swift Property Wrappers ", body: "…") quine.title // "Swift Property Wrappers" —        quine.title = "   @propertyWrapper " // "@propertyWrapper" 

أفكار مماثلة



تغيير دلالات المساواة والملكية مقارنة


في Swift ، هناك سلسلتان متساويتان إذا كانا متكافئين قانونيًا ، أي تحتوي على نفس الشخصيات. ولكن لنفترض أننا نريد أن تكون خصائص السلسلة متساوية ، وليس حساسة لحالة الأحرف ، والتي تحتويها.


@CaseInsensitive بتنفيذ برنامج @CaseInsensitive لخصائص نوع String أو String SubString .


 import Foundation @propertyWrapper struct CaseInsensitive<Value: StringProtocol> {    var wrappedValue: Value } extension CaseInsensitive: Comparable {    private func compare(_ other: CaseInsensitive) -> ComparisonResult {        wrappedValue.caseInsensitiveCompare(other.wrappedValue)    }    static func == (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool {        lhs.compare(rhs) == .orderedSame    }    static func < (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool {        lhs.compare(rhs) == .orderedAscending    }    static func > (lhs: CaseInsensitive, rhs: CaseInsensitive) -> Bool {        lhs.compare(rhs) == .orderedDescending    } } 

 let hello: String = "hello" let HELLO: String = "HELLO" hello == HELLO // false CaseInsensitive(wrappedValue: hello) == CaseInsensitive(wrappedValue: HELLO) // true 

أفكار مماثلة


  • @Approximate لمقارنة تقريبية لخصائص النوع Double أو Float.
  • @Ranked للخصائص التي يتم ترتيب قيمها (على سبيل المثال ، رتبة أوراق اللعب).

تسجيل الدخول الملكية


سيسمح لك @Versioned باعتراض القيم المعينة وتذكر متى تم تعيينها.


 import Foundation @propertyWrapper struct Versioned<Value> {    private var value: Value    private(set) var timestampedValues: [(Date, Value)] = []    var wrappedValue: Value {        get { value }        set {            defer { timestampedValues.append((Date(), value)) }            value = newValue        }    }    init(initialValue value: Value) {        self.wrappedValue = value    } } 

تتيح ExpenseReport فئة ExpenseReport حفظ الطوابع الزمنية ExpenseReport معالجة تقرير المصاريف.


 class ExpenseReport {    enum State { case submitted, received, approved, denied }    @Versioned var state: State = .submitted } 

لكن المثال أعلاه يوضح وجود قيود خطيرة في التنفيذ الحالي لمغلفات الملكية ، والذي يتبع من تقييد Swift: لا يمكن للخصائص رمي استثناءات. إذا أردنا إضافة تقييد إلى @Versioned لمنع القيمة من التغيير إلى .approved بعد أن أخذت القيمة .denied ، فإن الخيار الأفضل هو fatalError() ، وهو غير مناسب للتطبيقات الحقيقية.


 class ExpenseReport {    @Versioned var state: State = .submitted {        willSet {            if newValue == .approved,                $state.timestampedValues.map { $0.1 }.contains(.denied)            {                fatalError("")            }        }    } } var tripExpenses = ExpenseReport() tripExpenses.state = .denied tripExpenses.state = .approved // Fatal error: «»   . 

أفكار مماثلة


  • @Audited إلى الممتلكات.
  • @UserDefault آلية قراءة وحفظ البيانات في UserDefaults .

قيود


خصائص لا يمكن رمي استثناءات


كما ذكرنا سابقًا ، يمكن أن تستخدم مغلفات الخصائص بضعة طرق فقط لمعالجة القيم غير الصالحة:


  • تجاهلهم
  • إنهاء التطبيق باستخدام fatalError ().

لا يمكن تمييز الخصائص الملتفة بسمة `typealias`


لا يمكن @UnitInterval مثال @UnitInterval أعلاه ، الذي تقتصر ملكيته بواسطة الفاصل الزمني (0...1) على أنه


 typealias UnitInterval = Clamping(0...1) 

القيود المفروضة على استخدام تكوين العديد من الأغلفة الملكية


تكوين أغلفة الخصائص ليس عملية تبديلية: سيؤثر ترتيب الإعلان على السلوك. ضع في اعتبارك مثالًا يتم فيه تطبيع خاصية slug ، وهي عنوان url لنشر المدونة. في هذه الحالة ، ستختلف نتيجة التسوية حسب وقت استبدال المسافات بشرطة ، قبل أو بعد إزالة المسافات. لذلك ، في الوقت الحالي ، لا يتم دعم تكوين العديد من أغلفة الخصائص.


 @propertyWrapper struct Dasherized {    private(set) var value: String = ""    var wrappedValue: String {        get { value }        set { value = newValue.replacingOccurrences(of: " ", with: "-") }    }    init(initialValue: String) {        self.wrappedValue = initialValue    } } struct Post {    …    @Dasherized @Trimmed var slug: String // error: multiple property wrappers are not supported } 

ومع ذلك ، يمكن التحايل على هذا القيد باستخدام wrappers الخاصية المتداخلة.


 @propertyWrapper struct TrimmedAndDasherized {    @Dasherized    private(set) var value: String = ""    var wrappedValue: String {        get { value }        set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }    }    init(initialValue: String) {        self.wrappedValue = initialValue    } } struct Post {    …    @TrimmedAndDasherized var slug: String } 

قيود المجمع الأخرى الممتلكات


  • لا يمكن استخدامها داخل البروتوكول.
  • لا يمكن التصريح عن مثيل خاصية المجمع في enum .
  • لا يمكن إبطال خاصية ملفوفة تم إعلانها داخل الفصل بواسطة خاصية أخرى.
  • لا يمكن أن تكون خاصية ملفوفة lazy ، أو @NSManaged ، أو @NSManaged ، أو weak أو unowned @NSManaged .
  • يجب أن تكون الخاصية المُلتفة هي الوحيدة ضمن تعريفها (على سبيل المثال ، @Lazy var (x, y) = /* ... */ ).
  • لا يمكن أن تحتوي الخاصية getter على getter و setter .
  • يجب أن يكون لأنواع خاصية wrappedValue ومتغير wrappedValue في init(wrappedValue:) مستوى وصول مماثل لنوع برنامج التفاف الخاصية.
  • يجب أن يكون للخاصية type لـ projectedValue نفس مستوى الوصول مثل نوع المجمع للخاصية.
  • يجب أن يكون لدى init() نفس مستوى الوصول مثل نوع مجمّع الخصائص.

لنلخص. تتيح مغلفات الخصائص في Swift لمؤلفي المكتبة إمكانية الوصول إلى السلوك الرفيع المستوى الذي تم حجزه مسبقًا لوظائف اللغة. إن إمكاناتها لتحسين قابلية القراءة وتقليل تعقيد الشفرة هائلة ، وقد فحصنا فقط قدرات هذه الأداة بشكل سطحي.


هل تستخدم أغلفة الممتلكات في مشاريعك؟ اكتب في التعليقات!

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


All Articles