Is there a way to tell if a lazy var has been initialized?
Asked Answered
B

3

21

I don't want to initialize a view controller until I need to display its view., so I have it in a lazy var like:

lazy var foo: NSViewController! = {
    let foo = NSViewController()
    foo.representedObject = self.representedObject
    return foo
}()

// ...

override var representedObject: Any? {
    didSet {
        if foo != nil {
            foo.representedObject = representedObject
        }
    }
}

self.representedObject is set before foo is ever referenced, but every time I call if foo != nil, it initializes foo :c

Is there any way I can test if foo has already been set?

Bettinabettine answered 28/11, 2016 at 15:39 Comment(0)
B
10

lazy is just a convenience wrapper around one specific lazy-instantiation pattern (and one that is only moderately useful). If you want your own pattern, don't use lazy; just build it yourself.

private var _foo: NSViewController? = nil
var foo: NSViewController {
    if let foo = _foo {
        return foo
    }

    let foo = NSViewController()
    foo.representedObject = self.representedObject
    _foo = foo
    return foo
}

// This can be private or public, as you like (or you don't technically need it)
var isFooLoaded: Bool {
    return _foo != nil
}

override var representedObject: Any? {
    didSet {
        if !isFooLoaded {
            foo.representedObject = representedObject
        }
    }
}

This is designed to follow the isViewLoaded pattern, which addresses the same basic problem.

Bohon answered 28/11, 2016 at 15:53 Comment(4)
So there's no way to tell if a lazy var has been referenced before?Bettinabettine
I'm sure there is some way if you unpack your own data structure as Unsafe memory. It would be fantastically fragile, but everything ultimately is just bytes so it must be knowable somewhere. But in terms of "can this be done safely and more simply than the above?" I don't believe so. lazy is a very simple, hard-coded pattern and it doesn't expose its internals.Bohon
The challenge is, to make this thread safe. IMO, there's no reliable way to ask "isAlreadyInitialized", because - in a multi threaded environment - the result is immediately stale, that is, it may return false - but actually the underlying variable has completed to be initialised "at the same time".Pren
@Pren I'm not sure what you mean here. Is there something preventing standard locking solutions? It'd be nice to have an OSCompareAndSwap in Swift, but even without that it should be straightforward to make this thread safe using os_unfair_lock (or at the extreme, DispatchQueue, though that would be a bit overkill).Bohon
R
17

A shorter version that uses Swift's built-in lazy semantics:

struct Foo {
    lazy var bar: Int = {
        hasBar = true
        return 123
    }()
    private(set) var hasBar = false
}

Just check for hasBar instead.

Reunionist answered 15/1, 2019 at 15:26 Comment(0)
B
10

lazy is just a convenience wrapper around one specific lazy-instantiation pattern (and one that is only moderately useful). If you want your own pattern, don't use lazy; just build it yourself.

private var _foo: NSViewController? = nil
var foo: NSViewController {
    if let foo = _foo {
        return foo
    }

    let foo = NSViewController()
    foo.representedObject = self.representedObject
    _foo = foo
    return foo
}

// This can be private or public, as you like (or you don't technically need it)
var isFooLoaded: Bool {
    return _foo != nil
}

override var representedObject: Any? {
    didSet {
        if !isFooLoaded {
            foo.representedObject = representedObject
        }
    }
}

This is designed to follow the isViewLoaded pattern, which addresses the same basic problem.

Bohon answered 28/11, 2016 at 15:53 Comment(4)
So there's no way to tell if a lazy var has been referenced before?Bettinabettine
I'm sure there is some way if you unpack your own data structure as Unsafe memory. It would be fantastically fragile, but everything ultimately is just bytes so it must be knowable somewhere. But in terms of "can this be done safely and more simply than the above?" I don't believe so. lazy is a very simple, hard-coded pattern and it doesn't expose its internals.Bohon
The challenge is, to make this thread safe. IMO, there's no reliable way to ask "isAlreadyInitialized", because - in a multi threaded environment - the result is immediately stale, that is, it may return false - but actually the underlying variable has completed to be initialised "at the same time".Pren
@Pren I'm not sure what you mean here. Is there something preventing standard locking solutions? It'd be nice to have an OSCompareAndSwap in Swift, but even without that it should be straightforward to make this thread safe using os_unfair_lock (or at the extreme, DispatchQueue, though that would be a bit overkill).Bohon
B
-1

The actual solution I've gone with in my projects is to use the Lazy Containers package that I created, in which I included an isInitialized field:

import LazyContainers



@Lazy
var foo: NSViewController = {
    let foo = NSViewController()
    foo.representedObject = self.representedObject
    return foo
}()

// ...

override var representedObject: Any? {
    didSet {
        if _foo.isInitialized {
            foo.representedObject = representedObject
        }
    }
}
Bettinabettine answered 11/9, 2019 at 19:14 Comment(1)
If you think this answer is not useful, I would love to hear your reason so I can make better answers in the future :)Bettinabettine

© 2022 - 2024 — McMap. All rights reserved.