Initialize Lazy Instance Variable with Value That Depends on Other Instance Variables

Initialize lazy instance variable with value that depends on other instance variables

You can use a once-only executed closure which captures properties of self and use these at execution (= first use of the lazy property). E.g.

class Foo {
var foo: Int
var bar: Int
lazy var lazyFoobarSum: Int = { return self.foo + self.bar }()

init(foo: Int, bar: Int) {
self.foo = foo
self.bar = bar
}
}

let foo = Foo(foo: 2, bar: 3)
foo.foo = 7
print(foo.lazyFoobarSum) // 10

W.r.t. to your own attempt: you may, in the same way, make use of help (instance) functions of self in this once-only executed closure.

class Foo {
var foo: Int
var bar: Int
lazy var lazyFoobarSum: Int = { return self.getFooBarSum() }()

init(foo: Int, bar: Int) {
self.foo = foo
self.bar = bar
}

func getFooBarSum() -> Int { return foo + bar }
}

let foo = Foo(foo: 2, bar: 3)
foo.foo = 7
print(foo.lazyFoobarSum) // 10

Instance variable that relies on other instance variables

Since you need it in almost every method of your controller, that is natural dependency of your class.

In that case, it may be calculated inside controller's constructor. However, you don't want to query your database before you actually need User or UserIsSysAdmin properties.

So, you can use Lazy<T> to solve that issue:

public class CompanyController : Controller 
{
private readonly Lazy<ApplicationUser> user;

private readonly Lazy<bool> userIsSysAdmin;

//Not sure why you need parameterless this constructor?
public CompanyController()
{
this.user = new Lazy<ApplicationUser>(() => UserManager.FindById(System.Web.HttpContext.Current.User.Identity.GetUserId()));
this.userIsSysAdmin = new Lazy<bool>(() => UserManager.GetRoles(User.Id).Any(u => u == "Sys Admin"));
}

public CompanyController(ApplicationUserManager userManager, ApplicationRoleManager roleManager)
{
UserManager = userManager;
RoleManager = roleManager;
this.user = new Lazy<ApplicationUser>(() => UserManager.FindById(System.Web.HttpContext.Current.User.Identity.GetUserId()));
this.userIsSysAdmin = new Lazy<bool>(() => UserManager.GetRoles(User.Id).Any(u => u == "Sys Admin"));
}

public ApplicationUser User
{
get { return this.user.Value; }
}

public bool UserIsSysAdmin
{
get { return this.userIsSysAdmin.Value; }
}

private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
}

Access modifiers of User and UserIsSysAdmin properties should be set according to your specific logic and use-case.

How to initialize properties that depend on each other

@MartinR has pointed out the major issue here:

var corX = 0
var corY = 0
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40))

The problem is that a Swift default initializer cannot refer to the value of another property, because at the time of initialization, the property doesn't exist yet (because the instance itself doesn't exist yet). Basically, in panzer's default initializer you are implicitly referring to self.corX and self.corY - but there is no self because self is exactly what we are in the middle of creating.

One workaround is to make the initializer lazy:

class ViewController: UIViewController {
var corX : CGFloat = 0
var corY : CGFloat = 0
lazy var panzer : UIImageView = UIImageView(frame: CGRectMake(self.corX, self.corY, 30, 40))
// ...
}

That's legal because panzer doesn't get initialized until later, when it is first referred to by your actual code. By that time, self and its properties exist.

Property initialization using by lazy vs. lateinit

Here are the significant differences between lateinit var and by lazy { ... } delegated property:

  • lazy { ... } delegate can only be used for val properties, whereas lateinit can only be applied to vars, because it can't be compiled to a final field, thus no immutability can be guaranteed;

  • lateinit var has a backing field which stores the value, and by lazy { ... } creates a delegate object in which the value is stored once calculated, stores the reference to the delegate instance in the class object and generates the getter for the property that works with the delegate instance. So if you need the backing field present in the class, use lateinit;

  • In addition to vals, lateinit cannot be used for nullable properties or Java primitive types (this is because of null used for uninitialized value);

  • lateinit var can be initialized from anywhere the object is seen from, e.g. from inside a framework code, and multiple initialization scenarios are possible for different objects of a single class. by lazy { ... }, in turn, defines the only initializer for the property, which can be altered only by overriding the property in a subclass. If you want your property to be initialized from outside in a way probably unknown beforehand, use lateinit.

  • Initialization by lazy { ... } is thread-safe by default and guarantees that the initializer is invoked at most once (but this can be altered by using another lazy overload). In the case of lateinit var, it's up to the user's code to initialize the property correctly in multi-threaded environments.

  • A Lazy instance can be saved, passed around and even used for multiple properties. On contrary, lateinit vars do not store any additional runtime state (only null in the field for uninitialized value).

  • If you hold a reference to an instance of Lazy, isInitialized() allows you to check whether it has already been initialized (and you can obtain such instance with reflection from a delegated property). To check whether a lateinit property has been initialized, you can use property::isInitialized since Kotlin 1.2.

  • A lambda passed to by lazy { ... } may capture references from the context where it is used into its closure.. It will then store the references and release them only once the property has been initialized. This may lead to object hierarchies, such as Android activities, not being released for too long (or ever, if the property remains accessible and is never accessed), so you should be careful about what you use inside the initializer lambda.

Also, there's another way not mentioned in the question: Delegates.notNull(), which is suitable for deferred initialization of non-null properties, including those of Java primitive types.

Swift lazy instantiating using self

For some reason, a lazy property needs an explicit type annotation if its
initial value refers to self. This is mentioned on the swift-evolution mailing list, however I cannot explain why that is
necessary.

With

lazy var first: FirstClass = FirstClass(second: self)
// ^^^^^^^^^^^^

your code compiles and runs as expected.

Here is another example which demonstrates that the problem occurs
also with structs, i.e. it is unrelated to subclassing:

func foo(x: Int) -> Int { return x + 1 }

struct MyClass {
let x = 1

lazy var y = foo(0) // No compiler error
lazy var z1 = foo(self.x) // error: use of unresolved identifier 'self'
lazy var z2: Int = foo(self.x) // No compiler error
}

The initial value of y does not depend on self and does not need a
type annotation. The initial values of z1/z2 depend on self,
and it compiles only with an explicit type annotation.

Update: This has been fixed in Swift 4/Xcode 9 beta 3,
lazy property initializers can now reference instance members without explicit self, and without explicit type annotation. (Thanks to @hamish for the update.)

Lazy property initialization in Swift

It seems that this question has largely been answered, but to circle back to the original post, here is (IMHO) a relatively succinct translation in Swift. The key is that you can chain lazy properties. Note that I used both a class function and a closure - either is fine.

import Swift

println("begin")

class ClassWithLazyProperties {

lazy var entries:[String] = ClassWithLazyProperties.loadStuff()
lazy var entriesByNumber:Dictionary<Int, String> = {

var d = Dictionary<Int, String>()
for i in 0..<self.entries.count {
d[i] = self.entries[i]
}
return d
}()

private class func loadStuff() -> [String] {
return ["Acai", "Apples", "Apricots", "Avocado", "Ackee", "Bananas", "Bilberries"]
}

}

let c = ClassWithLazyProperties()
c.entriesByNumber
// 0: "Acai", 1: "Apples", 2: "Apricots", 3: "Avocado", 4: "Ackee", 5: "Bananas", 6: "Bilberries"]


println("end")


Related Topics



Leave a reply



Submit