Swift 1.2 Assigning Let After Initialization

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 call super.init(...) before it can say return 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



Leave a reply



Submit