14

To demonstrate this problem, I made a vanilla Cocoa project. Here is the AppDelegate.swift:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    weak lazy var isGood : NSNumber? = {
        return true
    }()

    func doSomething() {
        let result = isGood!
    }

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        // Insert code here to initialize your application
    }

    func applicationWillTerminate(aNotification: NSNotification) {
        // Insert code here to tear down your application
    }
}

Xcode gives this:

unkown :0: error: cannot convert return expression of type 'NSNumber?' (aka 'Optional') to return type 'NSNumber?'

unkown :0: cannot assign value of type 'NSNumber?' to type 'NSNumber??'

unkown :0: cannot assign value of type 'NSNumber?' to type 'NSNumber??'

In my actual project, it's another object of MyCustomClass (instead of NSNumber). The error is the same except the type is MyCustomClass.

If I remove weak or lazy from the declaration, it's all fine. But I wanted to save the reference count from being +1, since the MyCustomClass is an NSViewController which is sure to always be there.

Any idea of how to use the weak lazy variable?

Community
  • 1
  • 1
LShi
  • 1,500
  • 16
  • 29

4 Answers4

31

Weak and lazy do not mix well. The error message is completely useless at explaining what is going on, but essentially lazy and weak are at odds with each other:

  • lazy tells Swift that you don't want your variable created until the first time you access it, but once it is created, you want to keep it indefinitely for future reference, while
  • weak tells Swift that you don't want your variable to be the last link that keeps your variable from being deallocated, which works against the "keep indefinitely" goal of lazy variables.

You can solve this by emulating lazy, like this:

class Foo {

    weak var isGoodCache : NSNumber?

    private var makeIsGood : NSNumber {
        isGoodCache = true
        return isGoodCache!
    }

    var isGood:NSNumber? {
        return isGoodCache ?? makeIsGood
    }
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 3
    Fantastic! This is exactly what I was looking for. But I started to think if I'm over-engineering. One retain won't hurt, right? The only benefit here may be it prevents future possible reference cycle. – LShi Jul 03 '16 at 20:41
8

The reason why lazy and weak are incompatible is that every use would create a new instance unless you have another place that holds a strong reference. If you have another place to hold a strong reference then you don't need your class member to be the one creating the instance.

Lets assume we have:

weak lazy var myVariable: MyClass? = createMyClassInstance()

When you use it the first time, lets say you just reference it in a function call somewhere ...

myFunction(myVariable)

on the very next line, myVariable is empty again.

Why is that? because, once myFunction is finished, there are no more references to myVariable, and since it is weak, the corresponding object goes out of scope and vanishes.

There would be no difference between that lazy weak variable and any function call result. So, your variable may as well be a function or a computed variable (which would make things clearer for anybody looking at your code).

Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • How do you think of @dasblinkenlight 's approach? The advantage is that the computed result is cached. I do think using a simple computed variable is clear. Performance also won't be an issue. – LShi Jul 09 '16 at 08:21
  • As far as I can tell, the variable will be deallocated and reallocated in every reference to isGood unless the value returned is retained elsewhere. For a simple NSNumber that may not be a problem but, if you're using a more complex class with internal states, you will not be getting the same instance on every reference and internal states will be lost. – Alain T. Jul 15 '16 at 05:05
  • Concise and accurate explanation on why they are not compatiable. – Todanley Apr 12 '18 at 03:57
  • 1
    @AlainT. I have to disagree. Such a feature isn't useless. It of course depends on how you'd use it. It would be useful for, say, a dependency injection container which should be able to lazily create a property when requested, and to keep a reference to it for future requests, but to NOT retain it itself. – solidcell Aug 17 '19 at 14:07
  • @solidcell. Indeed, that's a scenario I hadn't thought of. Not sure I would do such a thing though because the responsibility of ownership would become unclear to me. – Alain T. Aug 17 '19 at 19:08
2

try to use a weak computed property instead ...

import Foundation

class C {
    weak var d : NSDate? {
        return NSDate()
    }
}

let c = C()
print(NSDate())
sleep(1)
print(c.d)

which give you the wanted behaviour (if i understand you question :-))

2016-07-03 16:49:04 +0000
Optional(2016-07-03 16:49:05 +0000)

if d MUST be initialized only once, then

import Foundation

class C {
    weak var d : NSDate? {
        return d0
    }
    lazy var d0: NSDate? = {
        return NSDate()
    }()
}

let c = C()
print(NSDate())
sleep(1)
print(c.d)
sleep(1)
print(c.d)

which gives you exactly what you need

2016-07-03 16:57:24 +0000
Optional(2016-07-03 16:57:25 +0000)
Optional(2016-07-03 16:57:25 +0000)
user3441734
  • 16,722
  • 2
  • 40
  • 59
  • Thanks. I prefer @dasblinkenlight's solution. In your second approach, which is close to what I'm looking for, `d0` still holds a "long-term" reference. In effect it's similar to using `lazy` without `weak`, if I understand it correctly. – LShi Jul 03 '16 at 20:33
0

The creator of a reference need not be the one responsible for retaining it. I have some classes that retain themselves intentionally and decide for themselves when they should be reclaimed by releasing their strong reference to self.

I use another class as a factory like this. Sometimes I have to tweak the arguments to the constructor so this generic class is more of a template than an actual implementation. But while they exist, the factory is designed to return the already existing instance.

class Factory<T> {
private weak var refI: T?
var ref: T {
    if refI != nil {
        return refI
    }
    let r = T()
    refI = r
    return r
}

}

WeakPointer
  • 3,087
  • 27
  • 22