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
frominit
, beforetimer
is initialized. - Otherwise,
timer
should never be nil, so you shouldn't have to check it outside ofinit
.
So, you should declare let timer: NSTimer!
.
Related Topics
How to Write an 'If Case' Statement as an Expression
Enable + Disable Auto-Layout Constraints
How to Fix Error: This Class Is Not Key Value Coding-Compliant for the Key Tableview.'
How to Insert an Image Inline Uilabel in iOS 8 Using Swift
Getting Timed Metadata in Swift iOS 8 from M3U8 Streaming Video
How to Share Both Image and Text Together in Swift
Difference Between Packed VS Normal Data Type
How to Call Initializer for Subclass of Generic Type
How to Add Initializers in Extensions to Existing Uikit Classes Such as Uicolor
Select Next Nstextfield with Tab Key in Swift
Terminate Subprocesses of MACos Command Line Tool in Swift
Conforming to Hashable Protocol
Convert Array of Unicodescalar into String in Swift