I have a struct with a fallible initializer, not an instance method, but an initializer. After updating to 1.2, when I try to assign a let
property inside the initializer, I receive the following error Cannot assign to 'aspectRatio' in self
. My code below:
import Foundation
public struct MediaItem
{
public let url: NSURL!
public let aspectRatio: Double
public var description: String { return (url.absoluteString ?? "no url") + " (aspect ratio = \(aspectRatio))" }
// MARK: - Private Implementation
init?(data: NSDictionary?) {
var valid = false
if let urlString = data?.valueForKeyPath(TwitterKey.MediaURL) as? NSString {
if let url = NSURL(string: urlString as String) {
self.url = url
let h = data?.valueForKeyPath(TwitterKey.Height) as? NSNumber
let w = data?.valueForKeyPath(TwitterKey.Width) as? NSNumber
if h != nil && w != nil && h?.doubleValue != 0 {
aspectRatio = w!.doubleValue / h!.doubleValue
valid = true
}
}
}
if !valid {
return nil
}
}
struct TwitterKey {
static let MediaURL = "media_url_https"
static let Width = "sizes.small.w"
static let Height = "sizes.small.h"
}
}
My question is what do I do to fix this?
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.