Need Self to Set All Constants of a Swift Class in Init

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

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

Initializing class constants in Swift

The reason let doesn't work on a read-only calculated property is because it's used to state that the property's actual value will never change after being set – not that the property is read-only. As the Apple docs say (emphasis mine):

You must declare computed properties — including read-only computed
properties — as variable properties with the var keyword, because their
value is not fixed.
The let keyword is only used for constant
properties, to indicate that their values cannot be changed once they
are set as part of instance initialization.

You therefore need to use var in order to reflect the fact that a calculated property's value could change at any time, as you're creating it on the fly when accessing it. Although in your code, this can't happen – as your hello and world properties are let constants themselves. However, Swift is unable to infer this, so you still have to use var.

For example:

class Test {
let hello = "hello"
let world = "world"
var phrase: String {
return self.hello + self.world
}
}

(This doesn't change the readability of the property – as because you haven't provided it with a setter, it's still read-only)

However in your case, you might want to consider using a lazy property instead, as your hello and world properties are constants. A lazy property is created when it's first accessed, and keeps its value for the rest of its lifetime – meaning you won't have to keep on concatenating two constants together every time you access it.

For example:

class Test {
let hello = "hello"
let world = "world"
lazy var phrase: String = {
return self.hello + self.world
}()
}

Another characteristic of let properties is that their value should always be known before initialisation. Because the value of a lazy property might not be known before then, you also need to define it as a var.


If you're still adamant on wanting a let property for this, then as far as I can see, you have two options.

The first is the neatest (although you've said you don't want to do it) – you can assign your phrase property in the initialiser. As long as you do this before the super.init call, you don't have to deal with optionals. For example:

class Test {
let hello = "hello"
let world = "world"
let phrase: String

init() {
phrase = hello+world
}
}

You simply cannot do it inline, as self at that scope refers to the static class, not an instance of the class. Therefore you cannot access the instance members, and have to use init() or a lazy/calculated property.

The second option is pretty hacky – you can mirror your hello and world properties at class level, so you can therefore access them inline in your phrase declaration. For example:

class Test {
static let hello = "hello"
static let world = "world"

// for some reason, Swift has trouble inferring the type
// of the static mirrored versions of these properties
let hello:String = Test.hello
let world:String = Test.world

let phrase = hello+world
}

If you don't actually need your hello or world properties as instance properties, then you can just make them static – which will solve your problem.

Why does Swift disallow assignment to self in class init, but not in protocol init?

It seems this currently behaves as expected.

The whole problem has been discussed on the swift forums: Assigning to Self in Protocol Extensions

The last time this quirk came up in internal discussions, the thought some of us had was that it might be worthwhile to prohibit classes from conforming to protocols with mutating requirements altogether. If you think about it, this makes some amount of sense — it seems like it would be quite hard to write code that can operate on both mutable values and mutable references generically, since the latter do not have value semantics:

var x = y
x.mutatingProtocolRequirement()
// did y change too?

However the discussion sort of fizzled out.

Assigning to constant in Swift initializer

You need to declare r with no initial value, like so:

let r: Double

As of Swift 1.2, a constant can only be assigned once. If you give it a default value you cannot give it another value in init.

init let properties with reference to self in subclass in swift

This is a good use case for an implicitly unwrapped optional.

The Apple documentation states:

“Implicitly unwrapped optionals are useful when an optional’s value is
confirmed to exist immediately after the optional is first defined and
can definitely be assumed to exist at every point thereafter. The
primary use of implicitly unwrapped optionals in Swift is during class
initialization.”

This means that you can use an implicitly unwrapped optional when a property depends on some aspect of self (but not the superclass) when it is initialised.

The property will be nil until it is set in the init() method and then must never be nil again.

class NodeView: UIView {

var _nodePlugView: NodePlugView!

init (node: Node) {
super.init()
_nodePlugView = NodePlugView (parentView: self)
}
}

Swift ivars: set all constants in initializer, or have clean initializer and give up constant?

I think any time you can have the compiler assert something for you, that is a good thing. If you don't want a "wild" initializer, you can use a combination of default parameters and convenience initializers.

Default parameters allow you to have a clean initializer without requiring a verbose call to init if you are happy with most of the defaults:

class MyClass {
let att1 : String
let att2 : Int
let att3 : Int
let att4 : Int
let att5 : Int

init(
att1: String = "Hello",
att2: Int = 2,
att3: Int = 3,
att4: Int = 4,
att5: Int = 5
)
{
self.att1 = att1
self.att2 = att2
self.att3 = att3
self.att4 = att4
self.att5 = att5
}
}

MyClass(att1: "World")
MyClass(att4: 19, att3: 20)
MyClass(
att1: "Hello",
att2: 5,
att3: 6,
att4: 7,
att5: 8
)

Otherwise, there is no way in Swift to have the compiler ensure a variable is only set once outside of an initializer. Your only option then is to use a runtime assert in a willSet:

class MyClass {
var onlyOnce : String! {
willSet {
assert(!onlyOnce)
}
}
}

var foo = MyClass()
foo.onlyOnce = "Hello"
foo.onlyOnce = "World" // runtime error

Initializing Swift properties that require self as an argument

You've found the primary use case of the Implicitly Unwrapped Optional.

  • You need to access self from init, before timer is initialized.
  • Otherwise, timer should never be nil, so you shouldn't have to check it outside of init.

So, you should declare let timer: NSTimer!.



Related Topics



Leave a reply



Submit