Struct with weak ref memory issue

Hi everyone,

I have a struct that is referencing (as weak ref) a parent container object and the container itself holds this struct value. If I print the struct to see what it has in it then the container never gets de-inited when it should. I am sorry if this may seem lame but can anyone teach me why this is happening? Please find below a simple code that helps demonstrating my issue.

class Container {
  var foo: Foo!
  deinit {
    print("deinit \(self)")
  }
  init() {
    foo = Foo(name: "Foo!")
    foo.container = self
  }
}

struct Foo {
  weak var container: Container!
  let name: String
  init(name: String) {
    self.name = name
  }
}

do {
  let c = Container()
  print(c)
  print("Should deinit c...") // (1)
}

do {
  let c = Container()
  print(c.foo) // <----- culprit ?
  print("Should deinit c...") // (2)
}

If I run this on my Mac (macOS Mojave 10.14.2) using Xcode 10.1 I see the first deinit message after (1) but not after the (2).
What's wrong?

Thanks for your help.
Thierry

Are you testing this in a Playground, by chance? memory management - Deinit method is never called - Swift playground - Stack Overflow

Nope. This is the result I get in a macOS test target in Xcode 10.1 I just removed the enclosing "func test_...() {"

@AlexanderM I've just read the link you gave me on SO... just to be clear I've decided to make this test because I've seen weird behaviours in an iOS app I am writing where "containers" were not released just because I printed the description of embedded structs (with weak refs). So it's really not linked to the Playgrounds.

If you breakpoint, run it until right before it exits, and then click the Debug Memory Graph button in Xcode, it thinks it leaks as well (note: Reproducible by replacing the print with let _ = String(describing: c.foo) as well)

That's relief to see Xcode gets it as well :)
Note that if you replace "struct Foo" with "class Foo" everything works as expected.

Few things I found:

  • This is fairly new, the code works properly on the Swift 4.1 Snapshot 2018-05-31 toolchain I had lying around on my computer
  • Here's the most minimal version I could get that breaks:
class DeinitDetector {
	deinit {
		print("Deinit!")
	}
}
struct WeakHolder {
	weak var a: DeinitDetector!
}
do {
	let a = DeinitDetector()
	let b = WeakHolder(a: a)
	let _ = String(describing: b)
}

I created an issue on the issue tracker here

@TellowKrinkle ok thanks for the information. So a regression it seems... Hope it gets fixed soon.

I tried it with Swift Development Snapshot 2018-11-13 (a), it works properly. So it seems to be fixed.

1 Like

Great news. However I'll have to wait 'till it gets mainstream since distribution on the App Store is bound to Xcode's swift versions. But thanks for the check! Let's hope they add a regression test for this issue in Swift's tests because it's such a vicious bug...

It looks like @Mike_Ash did indeed add a test. Here's his pull request.

@ole thanks for the follow up. @Mike_Ash thank you as well!

But shouldn't the test be made on StructHasNativeWeakReference rather than NativeSwiftClassHasWeak to test this very problem (or a new test be added) ? I noticed that class objects don't get retained in my case.