๐ŸŽ“ A1Learner
๐Ÿ”
0 of 27 lessons completed

Swift Building Wrappers

A new property wrapper can be built for a rule that an existing wrapper does not cover. A wrapper that records every value it holds is one such design.

A wrapper that records history

The Tracked wrapper stores a history array beside its value. Two assignments after the start leave three entries in the projected history.

@propertyWrapper
struct Tracked {
    private var value: Int
    private var history: [Int]
    init(wrappedValue: Int) {
        self.value = wrappedValue
        self.history = [wrappedValue]
    }
    var wrappedValue: Int {
        get {
            return value
        }
        set {
            value = newValue
            history.append(newValue)
        }
    }
    var projectedValue: [Int] {
        return history
    }
}
struct Counter {
    @Tracked var count: Int = 0
}
var counter = Counter()
counter.count = 5
counter.count = 9
print(counter.count)
print(counter.$count)

Counting the recorded values

The projected history is a plain array, so its count can be read like any other array count. Three assignments after the start bring the history to four entries.

@propertyWrapper
struct Tracked {
    private var value: Int
    private var history: [Int]
    init(wrappedValue: Int) {
        self.value = wrappedValue
        self.history = [wrappedValue]
    }
    var wrappedValue: Int {
        get {
            return value
        }
        set {
            value = newValue
            history.append(newValue)
        }
    }
    var projectedValue: [Int] {
        return history
    }
}
struct ScoreBoard {
    @Tracked var score: Int = 10
}
var board = ScoreBoard()
board.score = 20
board.score = 35
board.score = 35
print(board.score)
print(board.$score)
print(board.$score.count)

Two wrapper types in one struct

The Player struct combines the Clamped wrapper on health with the Tracked wrapper on score. Each property follows the rule of its own wrapper.

@propertyWrapper
struct Clamped {
    private var value: Int
    private let lower: Int
    private let upper: Int
    init(wrappedValue: Int, lower: Int, upper: Int) {
        self.lower = lower
        self.upper = upper
        self.value = min(max(wrappedValue, lower), upper)
    }
    var wrappedValue: Int {
        get {
            return value
        }
        set {
            value = min(max(newValue, lower), upper)
        }
    }
}
@propertyWrapper
struct Tracked {
    private var value: Int
    private var history: [Int]
    init(wrappedValue: Int) {
        self.value = wrappedValue
        self.history = [wrappedValue]
    }
    var wrappedValue: Int {
        get {
            return value
        }
        set {
            value = newValue
            history.append(newValue)
        }
    }
    var projectedValue: [Int] {
        return history
    }
}
struct Player {
    @Clamped(lower: 0, upper: 100) var health: Int = 90
    @Tracked var score: Int = 0
}
var player = Player()
player.health = 140
player.score = 20
player.score = 35
print(player.health)
print(player.score)
print(player.$score)

Example

@propertyWrapper
struct Tracked {
    private var value: Int
    private var history: [Int]
    init(wrappedValue: Int) {
        self.value = wrappedValue
        self.history = [wrappedValue]
    }
    var wrappedValue: Int {
        get {
            return value
        }
        set {
            value = newValue
            history.append(newValue)
        }
    }
    var projectedValue: [Int] {
        return history
    }
}
struct Sensor {
    @Tracked var reading: Int = 18
}
var sensor = Sensor()
sensor.reading = 20
sensor.reading = 23
print(sensor.reading)
print(sensor.$reading)
print(sensor.$reading.count)

Key points

  • Building a wrapper starts with the @propertyWrapper mark and a wrappedValue property.
  • The wrapper can keep extra stored state beside the wrapped value.
  • The Tracked wrapper keeps a history array that starts with the first value.
  • Each assignment through Tracked appends the new value to that history.
  • Tracked exposes its history as the projected value, so the dollar form reads the whole record.
  • Different wrapper types can work side by side in one struct.

In this example, the Sensor struct wraps reading with the Tracked wrapper, which starts its history with the first value 18. Assigning 20 and then 23 appends each value, so the projected history shows all three readings. The last print shows the history count 3.

Note

The history grows by one entry for every assignment, including repeat values.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What does the projected value of the Tracked wrapper hold?