Thursday, August 31, 2017

Using Lazy Variables to Work Around Swift Initialization Rules

Chris Adamson:

In all these cases, you have to provide the object with its delegate (or target) at initialization time. And that leads to a terrible head-scratcher. What do you do if you want to use one of these objects as a property, but also use self as the delegate?

Swift won’t let you easily do this.

[…]

The point of the lazy variable is that it won’t be assigned until its needed. Which in turn means that it can’t be accessed until self exists, because otherwise there’s no way to access it. Therefore, it’s always OK to reference self when assigning a lazy variable.

I understand the reasoning behind Swift’s rules for two-phase initialization and where you can access self. But in practice they introduce extra work and code ugliness in order to prevent a class of problems that I don’t think I’ve ever had. lazy variables are the best solution I’ve found for the issue that Adamson mentions. In fact, lazy variables are pretty cool in general, although beware that their initialization is not thread-safe.

The other main issue I run into with initialization is that you can’t call helper methods before calling super.init(). This is not an issue in Objective-C because, there, calling [super init] is the first thing you do. But in Swift you have to initialize all of your properties before calling super.init(). Implicitly-unwrapped optionals are the obvious, easy solution. Otherwise, you would need to move helper code from instance methods to static functions or a separate helper object or, in some cases, completely restructure your code if you need to access state or functionality from the superclass.

Update (2017-08-31): One of the issues that neither lazy nor IUOs solve is that you may end up needing to make the property var instead of let, even though you have no intention of modifying it once set. You can partially mitigate this by marking the property private(set). However, that adds yet more verbosity and doesn’t as obviously protect against concurrent access the way let does.

Regarding lazy and concurrency, Heath Borders writes:

I just use another computed property that wraps the lazy var in a Dispatch.sync over a private queue, then I never touch the lazy var.

Update (2017-09-01): Another issue with IUOs is the way they work with type inference. If you assign an IUO to a temporary variable, the inferred type is an optional. This means that you have to either force unwrap when assigning to the temporary or at each use of the temporary. So the IUO has leaked outside of its class. One way around this is to create two properties in the class. The first is a private var of type T!. The second is a computed property of type T that returns the first property (implicitly) unwrapped. Then clients of the class can use the computed property and not have to deal with unwrapping.

Previously:

2 Comments RSS · Twitter

[…] Using Lazy Variables to Work Around Swift Initialization Rules […]

[…] This will be useful when calling helper functions from initializers. […]

Leave a Comment