Property Wrappers
Property wrappers are a relatively new feature introduced in Swift 5.1 that allows us to define the behaviour of property accessors. They provide a way to add a layer of functionality on top of a property, such as validation or transformation of values, without cluttering up the code with lots of boilerplates.
In Swift, we use the @
symbol followed by a property wrapper name to apply a property wrapper to a property. Property wrappers are structs or classes that define the logic that should be used for the property and should conform to the PropertyWrapper
protocol.
You can see a property wrapper as an extra layer that defines how a property is stored or computed on reading. It’s beneficial for replacing repetitive code found in getters and setters of properties.
Property Wrappers in Swift:
- @State :
@State
is a property wrapper in SwiftUI that allows us to declare a state variable inside a view. A state variable is a value that can change over time, and when it changes, the view is automatically updated to reflect the new value.
Here is an example of how @State
works:
struct ContentView: View {
@State var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
In this example, we have a count
variable that is declared using @State
. This means that whenever the value of count
changes, SwiftUI automatically updates the view to reflect the new value.
We can also modify the value of count
using the +=
operator inside a closure, which is passed to a button's onTapGesture
property.
It’s important to note that @State
is designed to be used only inside a view. If we need to share data between views, we should use other property wrappers such as @ObservedObject
, @EnvironmentObject
, or @Binding
.
2. @Binding :
The @Binding
property wrapper is used to create a two-way binding between a property in a parent view and a property in a child view. This allows changes in the child view to be reflected in the parent view and vice versa.
To use the @Binding
property wrapper, you need to define a property in the child view with the @Binding
attribute, and pass a reference to a property in the parent view as a binding parameter. For example:
struct ChildView: View {
@Binding var name: String
var body: some View {
TextField("Enter name", text: $name)
}
}
struct ParentView: View {
@State var name: String = "John"
var body: some View {
ChildView(name: $name)
}
}
In this example, the ChildView
has a name
property that is marked with the @Binding
attribute. The ParentView
passes a binding reference to its name
property to the ChildView
. When the user enters text into the text field in the ChildView
, the name
property in the ParentView
is automatically updated.
The @Binding
property wrapper is used in combination with other property wrappers, such as @State
, to create more complex user interfaces in SwiftUI.
NOTE: In UIKit, the @Binding
property wrapper is not available, as it is specific to SwiftUI. However, you can achieve similar functionality using protocols and delegates.
Here’s an example:
Suppose you have two view controllers, ViewControllerA
and ViewControllerB
, and you want to pass data between them using a binding-like mechanism. You can define a protocol in ViewControllerB
that allows ViewControllerA
to set a value on it:
protocol ViewControllerBDelegate: AnyObject {
func viewControllerB(_ viewControllerB: ViewControllerB, didUpdateValue value: String)
}
Then, in ViewControllerB
, you can define a property to hold the delegate:
class ViewControllerB: UIViewController {
weak var delegate: ViewControllerBDelegate?
...
}
And in ViewControllerA
, you can implement the protocol and set itself as the delegate of ViewControllerB
:
class ViewControllerA: UIViewController, ViewControllerBDelegate {
...
func showViewControllerB() {
let viewControllerB = ViewControllerB()
viewControllerB.delegate = self
present(viewControllerB, animated: true)
}
func viewControllerB(_ viewControllerB: ViewControllerB, didUpdateValue value: String) {
// Do something with the value
}
}
Now, when ViewControllerB
updates the value, it can call the delegate method to notify ViewControllerA
:
class ViewControllerB: UIViewController {
...
@IBAction func updateValueButtonTapped() {
delegate?.viewControllerB(self, didUpdateValue: textField.text ?? "")
}
}
This is just a basic example, but it shows how you can achieve binding-like behavior using protocols and delegates in UIKit.
3. @NSCopying :
In Swift, @NSCopying
is a property wrapper used to specify that a class property should be copied rather than retained when it is assigned to another variable or passed as a function argument.
When a class instance is assigned to a variable or passed as an argument, the default behavior is to create a reference to the original instance, meaning that changes made to the original instance will be reflected in all references to that instance. This is called reference semantics.
However, in some cases, we might want to create a copy of the original instance instead of just referencing it. This is called value semantics. @NSCopying
property wrapper helps us achieve this behavior.
To use @NSCopying
, we declare a class property and apply the wrapper like this:
class MyClass {
@NSCopying var myProperty: NSString
// ...
}
Here, myProperty
will be automatically copied when it is assigned to another variable or passed as an argument to a function.
Note that the property being copied must conform to the NSCopying
protocol, which requires implementing the copy(with:)
method to create a copy of the instance.
Also, note that @NSCopying
is specific to Foundation classes, so it can only be applied to properties of classes that inherit from NSObject
.
4. @Lazy :
The @Lazy
property wrapper is not a built-in property wrapper in UIKit or Swift. It may refer to a custom implementation of a lazy property wrapper.
In general, a lazy property wrapper allows for delayed initialization of a property until it is accessed for the first time. This can be useful for improving performance by deferring expensive operations until they are actually needed.
A possible implementation of @Lazy
property wrapper for UIKit could be:
@propertyWrapper
struct Lazy<Value> {
private var initializer: () -> Value
private var value: Value?
init(initialValue: @autoclosure @escaping () -> Value) {
self.initializer = initialValue
}
var wrappedValue: Value {
mutating get {
if let value = value {
return value
} else {
let initialValue = initializer()
value = initialValue
return initialValue
}
}
set {
value = newValue
}
}
}
This implementation takes an initial value as a closure and returns the wrapped value when it is accessed for the first time. The wrapped value is then stored for future access.
An example usage of the @Lazy
property wrapper could be:
class MyViewController: UIViewController {
@Lazy var myView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
// myView is not initialized until it is accessed for the first time
view.addSubview(myView)
}
}
In this example, the myView
property is not initialized until it is accessed for the first time in the viewDidLoad()
method. This allows for delayed initialization of the view, which can be useful for improving performance.
5. @NSManaged :
The @NSManaged
property wrapper is used in Swift with Core Data to indicate that a property of a Core Data entity is managed by the Core Data framework.
When using Core Data, you define your data model in Xcode and then generate NSManagedObject
subclasses that represent entities in your data model. You can then define properties on these subclasses that correspond to attributes and relationships in the data model.
When you mark a property with the @NSManaged
property wrapper, you are telling Swift that the implementation of the property is provided at runtime by Core Data. The property is essentially a placeholder that Core Data uses to map to the corresponding attribute or relationship in the data model.
Here’s an example of how @NSManaged
can be used in a Person
entity in Core Data:
import Foundation
import CoreData
@objc(Person)
class Person: NSManagedObject {
@NSManaged var firstName: String
@NSManaged var lastName: String
@NSManaged var age: Int16
}
In this example, Person
is a Core Data entity that has firstName
, lastName
, and age
attributes. Each of these properties is marked with the @NSManaged
property wrapper to indicate that the implementation is provided by Core Data at runtime.
Note that you should only use @NSManaged
with Core Data managed objects. Using it in other contexts will result in a compiler error.
Property Wrappers in UIKit:
Property wrappers are a feature of the Swift language, and are not specific to any particular framework or library like UIKit. However, there are some property wrappers that are commonly used in UIKit development, including:
@IBOutlet
: used to connect views defined in a storyboard or XIB file to their corresponding properties in a view controller class@IBAction
: used to define actions (methods that respond to user interactions) in a view controller class that can be connected to UI elements in a storyboard or XIB file@objc
: used to mark Swift properties and methods that need to be exposed to Objective-C code (for example, when subclassing a UIKit class or implementing a delegate protocol)@escaping
: used to indicate that a closure parameter in a method or function can escape the scope of that method or function (i.e., be called asynchronously after the method or function has returned)@autoclosure
: used to automatically convert an expression into a closure, allowing the expression to be lazily evaluated (useful for optimizing performance in cases where the expression is expensive or time-consuming to compute)