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

المترجم: إيفجيني زافوزانسكي ، مطور FunCorp.
ملاحظة: في الوقت الذي تم فيه إعداد الترجمة ، فقد جزء من الكود المصدري للمقال الأصلي أهميته بسبب التغييرات في اللغة ، لذلك تم استبدال بعض أمثلة الكود عمداً.
تم تقديم مغلفات الملكية لأول مرة في منتديات Swift في مارس 2019 ، قبل بضعة أشهر من إعلان SwiftUI. في اقتراحه الأصلي ، وصف دوغلاس جريجور ، أحد أعضاء فريق سويفت كور ، هذه البنية (التي كانت تسمى آنذاك مندوبي العقارات) بأنها "تعميم سهل الوصول إليه من قبل المستخدم للوظيفة التي توفرها حاليًا لغة مثل lazy
، على سبيل المثال."
إذا تم الإعلان عن خاصية باستخدام الكلمة الأساسية lazy
، فهذا يعني أنه سيتم تهيئة هذه الخاصية في أول مرة يتم الوصول إليها. على سبيل المثال ، يمكن تنفيذ تهيئة الخاصية المؤجلة باستخدام خاصية خاصة ، يمكن الوصول إليها من خلال خاصية محسوبة. لكن استخدام الكلمة الأساسية lazy
يجعل ذلك أسهل بكثير.
struct Structure {
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
يمكن استخدام 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:
يوفر 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 ، هناك سلسلتان متساويتان إذا كانا متكافئين قانونيًا ، أي تحتوي على نفس الشخصيات. ولكن لنفترض أننا نريد أن تكون خصائص السلسلة متساوية ، وليس حساسة لحالة الأحرف ، والتي تحتويها.
@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
أفكار مماثلة
@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
أفكار مماثلة
@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
ومع ذلك ، يمكن التحايل على هذا القيد باستخدام 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 لمؤلفي المكتبة إمكانية الوصول إلى السلوك الرفيع المستوى الذي تم حجزه مسبقًا لوظائف اللغة. إن إمكاناتها لتحسين قابلية القراءة وتقليل تعقيد الشفرة هائلة ، وقد فحصنا فقط قدرات هذه الأداة بشكل سطحي.
هل تستخدم أغلفة الممتلكات في مشاريعك؟ اكتب في التعليقات!