Swift Özellikleri için Wrapper'lara Nasıl Yaklaşılır?

Yayınlanan: 2022-03-11

Basit bir ifadeyle, bir özellik sarmalayıcı, özelliğe okuma ve yazma erişimini kapsayan ve buna ek davranış ekleyen genel bir yapıdır. Kullanılabilir özellik değerlerini kısıtlamamız, okuma/yazma erişimine fazladan mantık eklememiz (veritabanlarını veya kullanıcı varsayılanlarını kullanmak gibi) veya bazı ek yöntemler eklememiz gerekiyorsa kullanırız.

Swift 5.1'deki Özellik Sarmalayıcıları

Bu makale, yeni, daha temiz bir sözdizimi tanıtan, sarma özelliklerine yönelik yeni bir Swift 5.1 yaklaşımı hakkındadır.

Eski Yaklaşım

Bir uygulama geliştirdiğinizi ve kullanıcı profili verilerini içeren bir nesneniz olduğunu hayal edin.

 struct Account { var firstName: String var lastName: String var email: String? } let account = Account(firstName: "Test", lastName: "Test", email: "[email protected]") account.email = "[email protected]" print(account.email)

E-posta doğrulaması eklemek istiyorsunuz—kullanıcının e-posta adresi geçerli değilse, email özelliği nil olmalıdır. Bu mantığı kapsüllemek için bir özellik sarmalayıcı kullanmak iyi bir durum olabilir.

 struct Email<Value: StringProtocol> { private var _value: Value? init(initialValue value: Value?) { _value = value } var value: Value? { get { return validate(email: _value) ? _value : nil } set { _value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-za-z]{2,64}" let pred = NSPredicate(format: "SELF MATCHES %@", regex) return pred.evaluate(with: email) } }

Bu sarmalayıcıyı Hesap yapısında kullanabiliriz:

 struct Account { var firstName: String var lastName: String var email: Email<String> }

Artık, e-posta özelliğinin yalnızca geçerli bir e-posta adresi içerebileceğinden eminiz.

Sözdizimi dışında her şey iyi görünüyor.

 let account = Account(firstName: "Test", lastName: "Test", email: Email(initialValue: "[email protected]")) account.email.value = "[email protected]" print(account.email.value)

Özellik sarmalayıcıyla, bu tür özellikleri başlatma, okuma ve yazma sözdizimi daha karmaşık hale gelir. Peki, bu karmaşıklığı önlemek ve özellik sarmalayıcılarını sözdizimi değişiklikleri olmadan kullanmak mümkün müdür? Swift 5.1 ile cevap evet.

Yeni Yol: @propertyWrapper Açıklama

Swift 5.1, @propertyWrapper bir özellik sarmalayıcının işaretlenmesine izin verildiğinde, özellik sarmalayıcıları oluşturmak için daha zarif bir çözüm sunar. Bu tür sarmalayıcılar, geleneksel olanlara kıyasla daha kompakt sözdizimine sahiptir ve bu da daha kompakt ve anlaşılır kod ile sonuçlanır. @propertyWrapper ek açıklamasının yalnızca bir gereksinimi vardır: Sarmalayıcı nesneniz, wrappedValue adlı statik olmayan bir özellik içermelidir.

 @propertyWrapper struct Email<Value: StringProtocol> { var value: Value? var wrappedValue: Value? { get { return validate(email: value) ? value : nil } set { value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx) return emailPred.evaluate(with: email) } }

Kodda bu tür sarılmış özelliği tanımlamak için yeni sözdizimini kullanmamız gerekir.

 @Email var email: String?

Bu nedenle, özelliği @ ek açıklamasıyla işaretledik. . Özellik türü, "wrappedValue" sarmalayıcı türüyle eşleşmelidir. Artık bu özellik ile sıradan bir özellikte olduğu gibi çalışabilirsiniz.

 email = "[email protected]" print(email) // [email protected] email = "invalid" print(email) // nil

Harika, şimdi eski yaklaşımdan daha iyi görünüyor. Ancak sarmalayıcı uygulamamızın bir dezavantajı vardır: Sarılmış değer için bir başlangıç ​​değerine izin vermez.

 @Email var email: String? = "[email protected]" //compilation error.

Bunu çözmek için, sarmalayıcıya aşağıdaki başlatıcıyı eklememiz gerekiyor:

 init(wrappedValue value: Value?) { self.value = value }

Ve bu kadar.

 @Email var email: String? = "[email protected]" print(email) // [email protected] @Email var email: String? = "invalid" print(email) // nil

Sarıcının son kodu aşağıdadır:

 @propertyWrapper struct Email<Value: StringProtocol> { var value: Value? init(wrappedValue value: Value?) { self.value = value } var wrappedValue: Value? { get { return validate(email: value) ? value : nil } set { value = newValue } } private func validate(email: Value?) -> Bool { guard let email = email else { return false } let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx) return emailPred.evaluate(with: email) } }

Yapılandırılabilir Sarmalayıcılar

Başka bir örnek alalım. Bir oyun yazıyorsunuz ve kullanıcı puanlarının saklandığı bir özelliğiniz var. Gereklilik, bu değerin 0'dan büyük veya ona eşit ve 100'den küçük veya ona eşit olmasıdır. Bunu bir özellik sarmalayıcı kullanarak başarabilirsiniz.

 @propertyWrapper struct Scores { private let minValue = 0 private let maxValue = 100 private var value: Int init(wrappedValue value: Int) { self.value = value } var wrappedValue: Int { get { return max(min(value, maxValue), minValue) } set { value = newValue } } } @Scores var scores: Int = 0

Bu kod çalışıyor ancak genel görünmüyor. Farklı kısıtlamalarla yeniden kullanamazsınız (0 ve 100 değil). Ayrıca, yalnızca tamsayı değerlerini sınırlayabilir. Karşılaştırılabilir protokolüne uyan herhangi bir türü sınırlayabilen yapılandırılabilir bir sarmalayıcıya sahip olmak daha iyi olur. Sarmalayıcımızı yapılandırılabilir hale getirmek için, tüm yapılandırma parametrelerini bir başlatıcı aracılığıyla eklememiz gerekir. Başlatıcı bir wrappedValue özniteliği (özelliğimizin başlangıç ​​değeri) içeriyorsa, bu ilk parametre olmalıdır.

 @propertyWrapper struct Constrained<Value: Comparable> { private var range: ClosedRange<Value> private var value: Value init(wrappedValue value: Value, _ range: ClosedRange<Value>) { self.value = value self.range = range } var wrappedValue: Value { get { return max(min(value, range.upperBound), range.lowerBound) } set { value = newValue } } }

Sarılmış bir özelliği başlatmak için, açıklamadan sonra parantez içinde tüm yapılandırma niteliklerini tanımlarız.

 @Constrained(0...100) var scores: Int = 0

Yapılandırma özniteliklerinin sayısı sınırsızdır. Bunları, başlatıcıdakiyle aynı sırada parantez içinde tanımlamanız gerekir.

Sarıcının Kendisine Erişim Kazanma

Sarmalayıcının kendisine (sarılmış değere değil) erişmeniz gerekiyorsa, özellik adından önce bir alt çizgi eklemeniz gerekir. Örneğin, Hesap yapımızı ele alalım.

 struct Account { var firstName: String var lastName: String @Email var email: String? } let account = Account(firstName: "Test", lastName: "Test", email: "[email protected]") account.email // Wrapped value (String) account._email // Wrapper(Email<String>)

Ona eklediğimiz ek işlevleri kullanmak için sarmalayıcının kendisine erişmemiz gerekiyor. Örneğin, Hesap yapısının Eşitlenebilir protokolüne uymasını istiyoruz. E-posta adresleri eşitse iki hesap eşittir ve e-posta adresleri büyük/küçük harfe duyarlı olmamalıdır.

 extension Account: Equatable { static func ==(lhs: Account, rhs: Account) -> Bool { return lhs.email?.lowercased() == rhs.email?.lowercased() } }

Çalışır, ancak en iyi çözüm değildir çünkü e-postaları karşılaştırdığımız her yerde bir küçük harfli() yöntemi eklemeyi unutmamalıyız. E-posta yapısını eşit hale getirmek daha iyi bir yol olabilir:

 extension Email: Equatable { static func ==(lhs: Email, rhs: Email) -> Bool { return lhs.wrappedValue?.lowercased() == rhs.wrappedValue?.lowercased() } }

ve sarılmış değerler yerine sarmalayıcıları karşılaştırın:

 extension Account: Equatable { static func ==(lhs: Account, rhs: Account) -> Bool { return lhs._email == rhs._email } }

Öngörülen Değer

@propertyWrapper ek açıklaması, bir sözdizimi şekeri daha sağlar - tahmini bir değer. Bu özellik istediğiniz herhangi bir türe sahip olabilir. Bu özelliğe erişmek için mülk adına bir $ öneki eklemeniz gerekir. Nasıl çalıştığını açıklamak için Combine çerçevesinden bir örnek kullanıyoruz.

@Published özellik sarmalayıcısı, mülk için bir yayıncı oluşturur ve bunu tahmini bir değer olarak döndürür.

 @Published var message: String print(message) // Print the wrapped value $message.sink { print($0) } // Subscribe to the publisher

Gördüğünüz gibi, sarmalanmış özelliğe erişmek için bir mesaj ve yayıncıya erişmek için bir $message kullanıyoruz. Sarmalayıcınıza tahmini bir değer eklemek için ne yapmalısınız? Özel bir şey yok, sadece ilan et.

 @propertyWrapper struct Published<Value> { private let subject = PassthroughSubject<Value, Never>() var wrappedValue: Value { didSet { subject.send(wrappedValue) } } var projectedValue: AnyPublisher<Value, Never> { subject.eraseToAnyPublisher() } }

Daha önce belirtildiği gibi, projectedValue özelliği, ihtiyaçlarınıza göre herhangi bir türe sahip olabilir.

sınırlamalar

Yeni özellik sarmalayıcıların sözdizimi iyi görünüyor, ancak aynı zamanda birkaç sınırlama içeriyor, başlıcaları:

  1. Hata işlemeye katılamazlar. Sarılmış değer bir özelliktir (yöntem değil) ve alıcı veya ayarlayıcıyı throws olarak işaretleyemeyiz. Örneğin, Email örneğimizde, bir kullanıcı geçersiz bir e-posta oluşturmaya çalışırsa hata atmak mümkün değildir. Bazı durumlarda kabul edilemez olabilen bir fatalError() çağrısıyla nil değerini döndürebilir veya uygulamayı kilitleyebiliriz.
  2. Özelliğe birden çok sarmalayıcı uygulanmasına izin verilmez. Örneğin, @Email sarmalayıcısını büyük/küçük harfe duyarlı hale getirmek yerine ayrı bir @Email sarmalayıcıya sahip olmak ve bunu bir @CaseInsensitive sarmalayıcısı ile birleştirmek daha iyi olur. Ancak bu tür yapılar yasaktır ve derleme hatalarına yol açar.
 @CaseInsensitive @Email var email: String?

Bu özel durum için bir geçici çözüm olarak, Email sarmalayıcısını CaseInsensitive sarmalayıcıdan devralabiliriz. Ancak kalıtımın da sınırlamaları vardır; yalnızca sınıflar kalıtımı destekler ve yalnızca bir temel sınıfa izin verilir.

Çözüm

@propertyWrapper ek açıklamaları, özellik sarmalayıcıların sözdizimini basitleştirir ve sarmalanmış özelliklerle sıradan olanlarla aynı şekilde çalışabiliriz. Bu, Swift Developer olarak kodunuzu daha kompakt ve anlaşılır hale getirir. Aynı zamanda, dikkate almamız gereken birkaç sınırlamaya sahiptir. Bazılarının gelecekteki Swift sürümlerinde düzeltileceğini umuyorum.

Swift özellikleri hakkında daha fazla bilgi edinmek isterseniz resmi belgelere göz atın.