Not sure about it as written, but if you were to call anything on the "inner" wouldn't there be the corresponding "retain, call, release" sequence? (with both retain/release being @_noLocks unsafe).
I suspect that's indeed what the compiler's unhappy about, although in principle none of that should be necessary since it's a let property of self and therefore cannot (validly) be deallocated while asdf is executing.
So, maybe an implementation limitation or oversight?
Can it do it though? Imagine you do have retain/release in debug and not in release. When you compile with release – there are no @_noLocks errors. Then you switch to Debug – and start getting those errors... Will that be an acceptable workflow? Or should "no locks" checker be pessimistic and always assume debug? Similar to how #if condition in Swift always checks both branches. I don't know, thinking out loud.
Not sure about it as written, but if you were to call anything on the "inner" wouldn't there be the corresponding "retain, call, release" sequence?
Yeah, if I was getting error specific to reference counting, that would make more sense.
E.g. this code will generate the following error:
"This code performs reference counting operations which can cause locking"
final class Test {
@_noLocks
func asdf() {
let closure = { self.test() }
}
func test() {
}
}
But with the error that I am getting, I can't do anything with inner (e.g. pass it to Unmanaged)
Also, I am not sure if referencing inner needs to increase retain count necessarily? We are accessing existing variable that is already at +1 retain count, we are not assigning this to another variable. But I might be missing something here?
That is good point! But this is somewhat different example since here Inner.foo would fail to compile if it was annotated with @_noLocks since Outer.asdf would require it to be.
I think in my original example, it was not possible for it to happen? But maybe compiler can't have visibility into that?
Right. But the original case was for a class member variable, not a global…?
In principle the compiler is free to omit any retains and releases that it can prove are unnecessary. Hypothetically it could do some pretty awesome optimisations in this regard, like not bothering to retain or release within an entire complicated object graph when it knows that the whole thing will go away at a specific point in program control flow (e.g. imagine building a big but temporary object graph during a processing task, and having it boil down to malloc/realloc and a single free).
To date, however, I'm not aware of any such advanced smarts (in Swift or any other notable language).
Also, I suspect this is hampered by the technical requirement to preserve even undesirable behaviour, such as retain loops.
Note that retain loop does not automatically imply "leak":
class C {
deinit {
print("deinit")
}
init(link: C? = nil) {
self.link = link
something()
}
var link: C?
}
var a: C! = C()
var b: C! = C()
a.link = b
b.link = a
a = nil
b = nil
RunLoop.current.run(until: .distantFuture)
Here object "a" points to object "b" and object "b" points back to object "a" and we got rid of our external pointers to "a" and "b" (by setting "a" and "b" variables to nil) – there seems to be a "leak", but that's only a temporary one: