Skip to content

Instantly share code, notes, and snippets.

@JarvisTheAvenger
Last active August 7, 2021 07:52
Show Gist options
  • Save JarvisTheAvenger/570133a1d7bc169595548a729d32aa3f to your computer and use it in GitHub Desktop.
Save JarvisTheAvenger/570133a1d7bc169595548a729d32aa3f to your computer and use it in GitHub Desktop.
import Foundation
// Property Wrapper
// A property wrapper adds a layer of separation between code that manages how a property is stored and
// the code that defines a property. For example, if you have properties that provide thread-safety checks or store
// their underlying data in a database, you have to write that code on every property. When you use a property wrapper,
// you write the management code once when you define the wrapper, and then reuse that management code by applying it to multiple properties.
@propertyWrapper
struct ValidEmail {
var email : String?
var wrappedValue : String? {
get { return email }
set { email = validate(email: newValue) ? newValue : nil }
}
init(wrappedValue value: String?) {
self.email = value
}
private func validate(email: String?) -> 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)
}
}
struct User {
@ValidEmail var email : String?
}
var user = User()
user.email = "d"
print(user.email ?? "invalid email")
user.email = "d@d.com"
print(user.email ?? "invalid email")
// Configurable Wrappers
@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
}
}
}
@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
}
}
}
struct Player {
@Scores var score : Int = 0
@Constrained(0...100) var customScore : Int = 0
}
// Gaining Access to the Wrapper Itself
// If you need access to the wrapper itself (not the wrapped value),
// you need to add an underscore before the property name. For instance, let’s take our Account structure.
struct Account {
var firstName: String
var lastName: String
@ValidEmail
var email: String?
}
let account = Account(firstName: "Test",
lastName: "Test",
email: "test@test.com")
account.email // Wrapped value (String)
//account._email // Wrapper(Email<String>)
// Projected Value
//The @propertyWrapper annotation provides one more syntax sugar - a projected value.
//This property can have any type you want. To access this property,
//you need to add a $ prefix to the property name. To explain how it works,
//we use an example from the Combine framework.
// The @Published property wrapper creates a publisher for the property and returns it as a projected value.
class Message {
@Published
var message: String = ""
func sendMessage() {
print(message) // Print the wrapped value
$message.sink { print($0) } // Subscribe to the publisher
}
}
// Limitations
// 1. Applying multiple wrappers to the property is not allowed
// 2. They can’t participate in error handling. The wrapped value is a property (not a method), and we can’t mark the getter or setter as throws
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment