need self to set all constants of a swift class in init
Asked Answered
A

2

30

I have a Swift class that has a constant ivar (are they called instance constants now?). To set the value to this constant, I need to call an initializer of the desired object and pass itself. However, I am not allowed to as I need to initialize all values first, then call super.init() and after that I am allowed to access self. So what to do in this case?

class Broadcaster: NSObject, CBPeripheralManagerDelegate {

    let broadcastID: NSUUID
    let bluetoothManager: CBPeripheralManager

    init(broadcastID: NSUUID) {
        self.broadcastID = broadcastID

        let options: Dictionary<NSString, AnyObject> = [ CBPeripheralManagerOptionShowPowerAlertKey: true ]
        self.bluetoothManager = CBPeripheralManager(delegate: self, queue: nil, options: options) // error: 'self' used before super.init call

        super.init()
    }
}
Alvera answered 14/6, 2014 at 9:38 Comment(0)
Z
34

UPDATE for Swift 1.2 and later

Unfortunately, it doesn’t seem to be possible any more to have bluetoothManager as a constant. Starting from Swift 1.2, in an initializer, constant properties can only assign a value once. This doesn’t allow us to start with a nil value by declaring it as an optional and change it later in the initialization process. Here’s the updated version with bluetoothManager as a variable.

class Broadcaster: NSObject, CBPeripheralManagerDelegate {

    let broadcastID: NSUUID
    var bluetoothManager: CBPeripheralManager!

    init(broadcastID: NSUUID) {
        self.broadcastID = broadcastID
        super.init()
        let options: Dictionary<String, AnyObject> = [ CBPeripheralManagerOptionShowPowerAlertKey: true ]
        self.bluetoothManager = CBPeripheralManager(delegate: self, queue: nil, options: options)
    }
}

Original answer

You could use implicitly unwrapped optional here (for bluetoothManager) and assign the value to it after super.init():

class Broadcaster: NSObject, CBPeripheralManagerDelegate {

    let broadcastID: NSUUID
    let bluetoothManager: CBPeripheralManager!

    init(broadcastID: NSUUID) {
        self.broadcastID = broadcastID
        super.init()
        let options: Dictionary<NSString, AnyObject> = [ CBPeripheralManagerOptionShowPowerAlertKey: true ]
        self.bluetoothManager = CBPeripheralManager(delegate: self, queue: nil, options: options)
    }
}

Because bluetoothManager is an optional, by the time super.init() is called, all properties are initialized (bluetoothManager is implicitly initialized with nil). But because we know that bluetoothManager will definitely have the value after the class is initialized, we declare it as explicitly unwrapped to avoid checks when using it.

UPDATE

A property can be declared as constant and still be changed in the initializer. One just has to make sure it has a definite value by the time initialization finishes. This is documented in chapter “Modifying Constant Properties During Initialization” of Swift book.

The situation when a property needs to be initialized with a call where self must be passed from not yet fully initialized object is described in chapter “Unowned References and Implicitly Unwrapped Optional Properties.”

Zetta answered 14/6, 2014 at 14:54 Comment(7)
But then it needs to be a var, instead of a let, doesn't it?Alvera
No, because this all is happening in the initializer, it can still be let. But after the class initialization it won’t be possible to change it.Zetta
See sections called “Modifying Constant Properties During Initialization” and “Unowned References and Implicitly Unwrapped Optional Properties” in Swift book.Zetta
Honestly, it does not seem to make much sense that own instance variables need to be initialized before super is being initialized. super does not know anything about my instance vars. So what's the rationale behind it? Vice versa it would make more sense to me.Pennebaker
@eofster, both parts of your answer not working in the current version.Messidor
Agree with @kelin, this answer no longer works in Swift 2.0 and Xcode 7 (beta 4). Shame, as it was my preferred way of handling this problem. I'm going to go with lazy stored properties.Gawen
@Messidor Thanks for the heads-up! Updated the answer to reflect the latest Swift versions.Zetta
T
1

How about setting up your bluetoothManager as a @lazy property and accessing it later on e.g. to startAdvertising?

@lazy var bluetoothManager: CBPeripheralManager = CBPeripheralManager(delegate: self, queue: nil)

init() { ... }

func start() {

    self.bluetoothManager.startAdvertising([ "foo" : "bar" ])

}
Tertiary answered 14/6, 2014 at 10:6 Comment(1)
That's something I already tried. And it works. But that gives away the benefit of having a let. :/Alvera

© 2022 - 2024 — McMap. All rights reserved.