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?

0

3 Answers 3

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.

0
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.

4
  • 1
    So there's no way to tell if a lazy var has been referenced before?
    – Ky -
    Commented Nov 28, 2016 at 23:06
  • 2
    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.
    – Rob Napier
    Commented Nov 28, 2016 at 23:35
  • 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". Commented Sep 4, 2020 at 7:35
  • 2
    @CouchDeveloper 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).
    – Rob Napier
    Commented Sep 4, 2020 at 16:19
-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
        }
    }
}
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 :)
    – Ky -
    Commented Jul 14 at 18:19

Not the answer you're looking for? Browse other questions tagged or ask your own question.