Swift 1.2 assigning let after initialization
The statement you're referring to is valid for local and global variables, not for properties, for which the old rule applies: they must all be initialized either inline or in an initializer (unless it's a mutable optional property, which is automatically initialized to nil).
Assigning let variable in fallible initializer swift 1.2
Swift 1.2 has closed a loophole having to do with let
properties:
The new rule is that a let constant must be initialized before use (like a var), and that it may only be initialized, not reassigned or mutated after initialization.
That rule is exactly what you are trying to violate. aspectRatio
is a let
property and you have already given it a value in its declaration:
public let aspectRatio: Double = 0
So before we ever get to the initializer, aspectRatio
has its initial value — 0. And that is the only value it can ever have. The new rule means that you can never assign to aspectRatio
ever again, not even in an initializer.
The solution is (and this was always the right way): assign it no value in its declaration:
public let aspectRatio: Double
Now, in the initializer, either assign it 0 or assign it w!.doubleValue / h!.doubleValue
. In other words, take care of every possibility in the initializer, once. That will be the only time, one way or another, that you get to assign aspectRatio
a value.
If you think about it, you'll realize that this is a much more sensible and consistent approach; previously, you were sort of prevaricating on the meaning of let
, and the new rule has stopped you, rightly, from doing that.
In your rewrite of the code, you are failing to initialize all properties in the situation where you intend to bail out and return nil
. I know it may seem counterintuitive, but you cannot do that. You must initialize all properties even if you intend to bail out. I discuss this very clearly in my book:
A failable class initializer cannot say
return nil
until after it has completed all of its own initialization duties. Thus, for example, a failable subclass designated initializer must see to it that all the subclass’s properties are initialized and must callsuper.init(...)
before it can sayreturn nil
. (There is a certain delicious irony here: before it can tear the instance down, the initializer must finish building the instance up.)
EDIT: Please note that starting in Swift 2.2, this requirement will be lifted. It will be legal to return nil
before initializing properties. This will put class initializers on a par with struct initializers, where this was already legal.
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
.
How can I initialize a let variable using a shared init method?
I found this answer and liked it's approach. It fixed the problem, and is much cleaner IMHO. By implementing init(coder aDecoder: NSCoder)
normally we would be keeping up the appearance that this view can be initialized from a nib/storyboard when it can't.
class SuperCoolView : UIView {
let imageView: UIImageView
required init(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
override init(frame: CGRect) {
self.imageView = UIImageView.new()
// Other stuff
super.init(frame: frame)
}
}
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.”
Swift let is mutable in classes why?
The "Modifying Constant Properties During Initialization" heading under the Initialization section of The Swift Programming Language says:
You can modify the value of a constant property at any point during
initialization, as long as it is set to a definite value by the time
initialization finishes.
Reading between the lines, and considering your example, it sounds very much like restrictions on setting the value of a constant don't apply to initialization. Further evidence supporting that idea appears earlier in the same section:
When you assign a default value to a stored property, or set its
initial value within an initializer, the value of that property is set
directly, without calling any property observers.
It's not unlikely that the constancy of a stored property is enforced by the accessors for that property. If those accessors aren't used during initialization, then it makes sense that you can modify even a constant property as many times as you like during initialization.
The fact that you can't modify j
in your example after first setting it is due to the fact that j
is a local constant, not a property. There probably aren't any accessors for j
at all -- instead the compiler probably enforces access rules for local constants/variables.
Accessing self in initializing closure
This quoted example of the Migration Guide is misleading because it's related to a global variable.
The closure of a instance let
constant is called (once) immediately when the class is initialized. That's the reason why it cannot use other variables declared on the same level.
What you can do is to initialize initialize
(the variable name is not the best one ;-) ) lazily. The closure is also called only once but – as the guide describes – only the first time (when) it is used.
class SomeClass {
let other = SomeOtherClass()
lazy var initialize : () = {
let test = self.other
test.doSomething()
}()
func doSomething() {
// initialize will only be called once
_ = initialize
}
}
Related Topics
Urlsession Datatask Method Returns 0 Bytes of Data
Error Domain=Nsosstatuserrordomain Code=-12780 \"(Null)\"
Swift: How to Catch Exception When Parsing a Numeric String
Realitykit - Stored Entities & Load The Scene Aynchronously from The Url
Modify Scpreferences Persistent Storage: Invalid Argument
Merging Two Arrays of Dictionaries Based on a Shared Value
How to Implement Doubly Linked List in Swift
Invalid Redeclaration of Typealias
How to Pass a String Containing Markdown to Custom View
Convert Ble Current Time to Date
How to Delay Animation in Swiftui
Onreceive String.Publisher Lead to Infinite Loop
How to Get a Class with Generic Type Accept an Array of Different by Same Generic Types