Swift Init Accessors

In Swift 5.9, a Swift proposal 0400-init-accessors was implemented to allow us to use computed properties to initialize stored properties. That means we don’t need to expose local private stored properties to the constructor parameter list for initialization.

1
2
3
4
5
6
7
8
9
10
11
struct AppState {
private var innerData: Int
var data: Int {
set {
innerData = newValue
}
get {
innerData
}
}
}

In the example above, the AppState has a computed property data that we provide for outside accessing. But the actual stored property is the innerData, the Xcode compiler will synthesize a construction initializer with the private property innerData.
If we try to create an instance for the type AppState, we will see a compilation error

1
2
let state = AppState(innerData: 100)
/// 'AppState' initializer is inaccessible due to 'private' protection level

If we don’t want exposing the inner private properties to outside or we cannot modify construction initializers, we can use the init accessors to initialize stored properties with computed properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct AppState {
private var innerData: Int
var data: Int {
init(initValue) initializes(innerData) {
innerData = initValue
}
set {
innerData = newValue
}
get {
innerData
}
}
}

let state = AppState(data: 100)

As you can see above, computed properties have a new attribute init besides setter and getter. The init attributer has one parameter that you can use to specify the initialized parameter’s name. There are extra two attributes initializes and accesses, the initializes can receive multiple stored properties, that means initialize the computed property will also initialize these specified stored properties. In our example above, the Xcode compiler is intelligent enough to generate a construction initializer with the public property since the computed property data will initialize the innerData by the init accessor we defined.

The init method in computed properties only be invoked once when objects are initialized. Then we assign new value to these computed properties will invoke their setters.

There is a more complicated example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct AppState {
var one: Int
var two: Int
var three: Int {
init(initValue) initializes(two) {
two = initValue
}
set { two = newValue }
get { two }
}
var four: Int {
init(initValue) initializes(one) accesses(two) {
one = initValue + two
}
set { one = newValue }
get { one }
}

init() {
three = 3
four = 4
}
}

The computed property three can initialize the stored property two and the computed property four can initialize the stored property one, so we can initialize the instance by only initializing the properties three and four. But we cannot exchange the initialization order of the two properties, the accesses attributer means that the computed property four needs the initialized property to finishing its initialization. Every stored properties specified in the accesses attributer can be accessed in the init block.

Actually, this feature is introduced for SwiftUI. In SwiftUI 5.0, the Swift team introduced a new observation framework called Observation with a new macro attribute @Observable. A value of class marked with @Observable can be observed by SwiftUI views. Distinct to the ObservableObject, a class with the macro @Observable will expand all its properties to be individual observable objects.

The nuisance is that the macro @Observable will synthesize multiple local stored properties into the class, but cannot modify constructors of the class. It will cause a compile error saying the constructor doesn’t initialize all stored properties of the class since all original stored properties were converted to computed properties.

The init accessor is a workaround for us to resolve various cases when we construct our own Macros.