Swift - Properties
Published: 2022-04-04 06:14:39

Properties can be of different types based on

- How they are stored :
  1. Stored property (can be constant and variable)
  2. Computed property (can be only variable )

 
- Where they are associated with :
  1. Instance property - belongs to instance of type
  2. Type property - belongs to type itself 

---
### Stored Property

- Stored Properties of Constant Structure Instances can't be modified, even if the property inside the structure is declared as a variable.

**_This behavior is due to structures being value types. When an instance of a value type is marked as a constant, so are all of its properties._**

**_The same isn’t true for classes, which are reference types. If you assign an instance of a reference type to a constant, you can still change that instance’s variable properties._**

- A lazy stored property is a property whose initial value isn’t calculated until the first time it’s used.

You must always declare a lazy property as a variable (with the var keyword), because its initial value might not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore can’t be declared as lazy.

- A Lazy stored property with a **default value** can refer to **self**, because lazy property will not be accessed until after initialization has been completed(by the time self will be available).

example :

``` swift
class SomeClass{
    let prop1 : Int
    let prop2 : Int
    
    lazy var lazyProp : () -> Int = { return self.prop1 + self.prop2 }
    
    //var notLazyProp : () -> Int = { return self.prop1 + self.prop2 }  //Cannot find 'self' in scope : Error
    
    init(prop1 : Int, prop2 : Int){
        self.prop1 = prop1
        self.prop2 = prop2
    }
}

let someObject = SomeClass(prop1: 3, prop2: 7)
print( someObject.lazyProp() ) // 10
```

---

### Computed Property

Classes, structures, and enumerations can define computed properties, which don’t actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.

example :

``` swift
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
```

**Shorthand Getter and Setter Declaration**
- If a getter has a single expression, the getter implicitly returns that expression.
- If setter doesn’t define a name for the new value to be set, a default name of newValue is used.

example :

``` swift
struct CompactRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            Point(x: origin.x + (size.width / 2),
                  y: origin.y + (size.height / 2))
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}
```

- **Read-Only Computed Properties** - A computed property with a getter but no setter.

example :

``` swift
struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}
```

---
### Property Observers
Property observers observe and respond to changes in a property’s value. Property observers can be added to stored properties you define yourself, and also to properties that a subclass inherits from its superclass.


You can add property observers in the following places:

1. Stored properties that you define
2. Stored properties that you inherit
3. Computed properties that you inherit  

**_For a computed property that you define, use the property’s setter to observe and respond to value changes, instead of trying to create an observer_** 

The two property observers are :

1. **willSet** is called just before the value is stored.
2. **didSet** is called immediately after the new value is stored.

- if you implement a willSet and didSet observer, it’s passed the new property and old property value as a constant parameter respectively. If you don’t write the parameter name and parentheses within your implementation, the parameter is made available with a default parameter name of newValue and oldValue respectively.

example :

``` swift
class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}
```

**_If you pass a property that has observers to a function as an in-out parameter, the willSet and didSet observers are always called._**

---

### Property Wrappers
A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property. You can also use a property wrapper to reuse code in the getter and setter of multiple properties.

To define a property wrapper, you make a structure, enumeration, or class that defines a **wrappedValue** property.

example :

``` swift
@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}
```

You apply a wrapper to a property by writing the wrapper’s name before the property as an attribute.

example :

``` swift
struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}
```

Use initializer for setting initial values for Wrapped Properties

- Projected value can be used to keep track of whether the property wrapper adjusted the new value for the property before storing that new value. The name of the projected value is the same as the wrapped value prefixed with a dollar sign ($)

example :

``` swift
@propertyWrapper
struct SmallNumber {
    private var number: Int
    private(set) var projectedValue: Bool

    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }

    init() {
        self.number = 0
        self.projectedValue = false
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
```
---

- We can define computed variables and define observers for stored variables, in either a global or local scope. 

- You can apply a property wrapper to a local stored variable, but not to a global variable or a computed variable

- Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself doesn’t have an initializer that can assign a value to a stored type property at initialization time.

- You define type properties with the static keyword. For computed type properties for class types, you can use the class keyword instead to allow subclasses to override the superclass’s implementation.