26

I recently downloaded Xcode 10 and I noticed an apparent bug when using weak or unowned variables. I managed to create a simple example that showcases the problem so that people can recreate it.

class MainClass {
    weak var weakClass: SomeClass!

    init() {

        // WARNING: Instance will be immediately deallocated because property 'weakClass' is 'weak'

        self.weakClass = SomeClass()
    }
}

class SomeClass {}

As the error says, weakClass immediately deallocates once MainClass is initialized and is always nil.

I have opened up the same playground with Xcode 9.3 and I can confirm that the code works fine with no errors or warnings

Is this a bug in Xcode 10 or am I not getting something. If it is, is there any workarounds?

EDIT: Original Example

class LoginCoordinator {

    var viewModel: LoginViewModel?
    var viewController: LoginViewController?

    init() {
        viewModel = LoginViewModel()
        viewModel?.coordinator = self
        viewController = LoginViewController(viewModel: viewModel!)
    }
}


class LoginViewModel: ViewModelDelegate {
    weak var coordinator: LoginCoordinator?
}

coordinator is always nil in LoginViewModel

AppDelegate.swift

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func setupView() {
        let coordinator = LoginCoordinator()
        let navigationController = UINavigationController(rootViewController: coordinator.create)

        navigationController.isNavigationBarHidden = true
        navigationController.navigationBar.isTranslucent = false

        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = navigationController
        window?.makeKeyAndVisible()
        window?.layer.cornerRadius = 6
        window?.layer.masksToBounds = true
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        setupView()
        return true
    }
Nader
  • 1,120
  • 1
  • 9
  • 22
  • 1
    Its not a bug. Swift Playground may temporarily retain the object while it executes as it keeps track of it whereas there is nothing to retain your object when you run it your coordinator/controller. – ekscrypto Aug 10 '18 at 19:19
  • I ran the code normally outside playgrounds and it is still not working @ekscrypto – Nader Aug 10 '18 at 19:21
  • 1
    Exactly, just remove "weak", code should work. – ekscrypto Aug 10 '18 at 19:26
  • @NaderBesada why don't you create a temp `deinit` method for `LoginCoordinator` and print out a message indicating it's been hit (or set a breakpoint). It would appear from what you describe that `LoginCoordinator` is weakly held. Where is `LoginCoordinator` created? – Mobile Ben Aug 10 '18 at 19:27
  • LoginCoordinator is initally created in the AppDelegate's `didFinishLaunch` – Nader Aug 10 '18 at 19:29
  • Why not include the code for it? Is it created within the scope of `didFinishLaunch` only? Or do you have it assigned to a global variable. If not, that is why it becomes weak. Note, you could remove the weak ni `LoginViewModel` but there is a code smell there and there is indeed a retain cycle there. – Mobile Ben Aug 10 '18 at 22:28
  • If I strongly reference `coordinator` in `LoginViewModel`, a retain cycle occurs and `LoginViewController` would not be able to be deinitialized. If I declare coordinator as a weak variable (like in the updated example), `LoginCoordinator` instantly deinitializes and `coordinator` is always nil @MobileBen – Nader Aug 10 '18 at 22:51
  • @NaderBesada just post your `didFinishLaunch` code (or part of it). I suspect you do not have a global variable which holds your `LoginCoordinator`. This is what you would need to have a strong reference. If in `didFinishLaunch` you are locally declaring `LoginController` it will be released once `didFinishLaunch` is done. If `LoginController` is meant to be long-lived (ie. never released) then you could have a strong ref to it in your model view. These are all code smells. But up to you. You are not quite helping since you are not providing requested info. – Mobile Ben Aug 10 '18 at 22:55
  • @MobileBen updated my question – Nader Aug 10 '18 at 23:13
  • @NaderBesada it is as I indicated. You are declaring the coordinator in the method scope of `setupView`. Have coordinator be a class variable. For example make it `var coordinator: LoginCoordinator?` right under your `var window`. That will give you a strong reference to the coordinator and then it should work with the weak ref. You need to better understand how the lifetime of objects with scope work. – Mobile Ben Aug 10 '18 at 23:24
  • @NaderBesada could you find a good answer of your question? – Yucel Bayram May 23 '19 at 10:40
  • https://stackoverflow.com/questions/47083638/how-to-get-rid-of-retain-cycle-with-collectionview – Sanju Feb 26 '20 at 17:56

4 Answers4

14

To understand this you must know the concept of ARC. ARC concept is automatic reference count means ARC will keep something in memory, as long as an allocated memory is strongly referenced by some variable. If it(ARC) found some allocated memory doesn't have any strong reference it will dealloc it. So the warning weakClass immediately deallocates once MainClass is initialized and is always nil. Because it doesn't have any strong reference.Please comment any doubt.

One example below for retain cycle creation:

class A {
var classBObject: B?

  init() {
     classBObject = B()
     classBObject.classAObject = self // Creates a retain cycle
 }
}

class B {
   var classAObject: A? // Strong(by default all are strong) variable create retain cycle
}

So, in class B if we take weak var classAObject retain cycle will not happen.

vivekDas
  • 1,248
  • 8
  • 12
  • 1
    Makes sense. In my original code, `SomeClass` is a `UIViewController` with a strong reference to `MainClass`. So for `MainClass to have a reference to `SomeClass`, `SomeClass` has to be a weak variable in order to avoid a retain cycle, correct? – Nader Aug 10 '18 at 05:56
  • In your code provided above I can't see SomeClass with a strong reference to MainClass ? – vivekDas Aug 10 '18 at 06:01
  • I was curious if ARC can deallocate something inside a view controller in the time between creating a weak reference and then creating another strong reference to it in the next line? can ARC run garbage collection in between the two statements? – James Joshua Street May 04 '21 at 02:16
8

This is the purpose of weak. Swift uses reference count to manage memory. A strong pointer increases the reference count of the pointed object by 1, a weak pointer does not increase reference count. An object with 0 reference count will be deallocated.

Your instance of SomeClass only pointed by a weak pointer, so its reference count is 0. As a result it is deallocated immediately.

Weak is useful to avoid retain cycles. For example, in escaping closure and in delegation design pattern.

ukim
  • 2,395
  • 15
  • 22
  • 1
    If I add `let mainClass = MainClass()` to `SomeClass` wouldn't that create a retain cycle? I tried that and the warning is still there – Nader Aug 10 '18 at 05:40
  • @NaderBesada A retain cycle is created when two objects have strong pointers pointed to each other. An object is an instance of a class. – ukim Aug 10 '18 at 05:44
  • @NaderBesada based on the way you are doing things, you should make `MainClass` hold a strong ref to `SomeClass` and have `SomeClass` hold a weak to `MainClass`. One way of thinking about it is that in your example code, `MainClass` is allocating `SomeClass`, so it should hold the strong ref. – Mobile Ben Aug 10 '18 at 18:59
  • I edited my answer to illustrate my problem in a better way @MobileBen – Nader Aug 10 '18 at 19:15
  • Define `immediately`. We have this warning on an instance in a view controller and the weak var is not deallocated immediately. The view controller appears, the weak var is instantiated, we wait, click a button that hits a breakpoint and yup, the weak var is still not nil. So, define immediately, because this weak var is not deallocated immediately. – Alex Zavatone Sep 30 '19 at 16:37
3

The question is, "is that reference strongly referenced elsewhere? If so, it will not be deallocated."

I propose that Apple's warning message is misleading. I think that it should state that it will be deallocated immediately when its containing object is deallocated or when other strong references to it are deallocated.

Here's why.

We have this warning on an instance in a view controller and the weak var is not deallocated immediately. The view controller appears, the weak var is instantiated, we wait, click a button that hits a breakpoint and yup, the weak var is still not nil. Yet when the view controller disappears and is deallocated, the weak var is deallocated immediately.

But why? Well, by the time we come to the part of code that has a weak reference to the variable, other code already has caused this variable to have retain count of 3. This means that even though it's weak, it can't be immediately dismissed.

You can check this with po myObject.retainCount. It's not guaranteed to be accurate, but it will give you an idea. If the object's retainCount > 1 and it's strongly linked somewhere else, (please put a comment in your code to indicate where it is strongly referenced), weak will work. To avoid a compiler warning, don't reference the object directly, but the strong reference in the other object.

So, I think that Apple needs to reword this warning because it's surely misleading.

Alex Zavatone
  • 4,106
  • 36
  • 54
0

Further, given that a Swift property doesn’t have a corresponding instance variable, it makes sense that when you give a value to a weak property, it is immediately deallocated. I run into a similar problem here where my SceneDelegate asks for the rootViewController of this AppCoordinator :

class AppCoordinator {

    private weak var navigationController : UINavigationController!

    var rootViewController : UIViewController {
       navigationController = UINavigationController()
       return navigationController    // <-- here the app crashes: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
    }
}
Enrico Cupellini
  • 447
  • 7
  • 14