DEV Community

Naveen Ragul B
Naveen Ragul B

Posted on • Updated on

Swift - Properties

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 :

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
Enter fullscreen mode Exit fullscreen mode

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 :

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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 :

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Read-Only Computed Properties - A computed property with a getter but no setter.

example :

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}
Enter fullscreen mode Exit fullscreen mode

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 :

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

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 :

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

example :

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}
Enter fullscreen mode Exit fullscreen mode

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 :

@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()
Enter fullscreen mode Exit fullscreen mode

  • 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.

Oldest comments (0)