[Context]
I have an iOS project that has a few internal actors. These actors are persisted across the entire lifetime of the application. This is a possible source that could cause a retain cycle.
[Currently]
Every time when a view gets a hold of an actor. The system would schedule a print out of the retain count of the actor in 1 second. The thinking is, if the retain count didn't go up. There can't be a retain cycle involving the actor.
[Question]
Is the _getRetainCount function to interact with the Swift runtime to get the retain count? Because from looking at the source code_swift_retainCount nor swift_retainCount is not available.
I used CFGetRetainCount in the past for debugging and found it reliable enough. Here's a small test that compares retain counts obtained via three different means:
import Foundation
class C {}
let a = C()
typealias RetainCountProc = @convention(c) (AnyObject, Selector) -> Int
let imp = class_getMethodImplementation(C.self, NSSelectorFromString("retainCount"))!
let proc = unsafeBitCast(imp, to: RetainCountProc.self)
var rc = proc(a, NSSelectorFromString("retainCount"))
var rc2 = CFGetRetainCount(a)
var rc3 = getRetainCountViaObjC(unsafeBitCast(a, to: Int.self))
print(rc, rc2, rc3) // 2 2 1
var items: [C] = []
items.append(a)
rc = proc(a, NSSelectorFromString("retainCount"))
rc2 = CFGetRetainCount(a)
rc3 = getRetainCountViaObjC(unsafeBitCast(a, to: Int.self))
print(rc, rc2, rc3) // 3 3 2
print("ok")
print(items) // so it's not opimised right away
I defined getRetainCountViaObjC:
long getRetainCountViaObjC(long value);
in an obj-c file for which I disabled ARC, so I could call real retainCount (and made its parameter integer to not have an extra bump of retain count when passing the object as a parameter).
The reason you are getting 7 is because there are extra ~5 strong references holding your object compared to the simple test above.
Apple has long said you aren't supposed to rely on the exact numeric value of retainCount, whether using ARC or MRR. Usually they say the only useful bit you can rely on is the relative +1 and -1 movement as you pass an object around, but even that's subject to optimization in Swift.
It's for detecting retain cycles. The thinking behind it, everything should be transient with addition of a few special objects. B's perspective A will exist for the entire duration. But A will be destroy and re-created some point after B disappears.
A -> B -> A
When B is a struct, great, things are alright
When B is a reference type, B->A edge should be weak. When it is not weak, retain_count(A) will be 1 more than the normal value.
Also open to hear any other option of detecting retain cycle? Something better than when program doesn't behave normal or using "too much" memory.
The following test creates a reference cycle, which is broken after one minute.
struct B {
var a: A?
init(a: A? = nil) { self.a = a }
}
class A {
var b: B
init(b: B = B()) {
self.b = b
DispatchQueue.main.asyncAfter(deadline: .now() + 60) {
self.b.a = nil
}
}
}
var a: A? = A()
a!.b.a = a
a = nil
RunLoop.current.run(until: .distantFuture)
I'm not sure if this is helpful, but Apple guidance for CFGetRetainCount() is, effectively, never use this function. It doesn't return the value you expect (as it may count internal references and autoreleased references that are already balanced). There's a reason Swift doesn't expose a public, non-underscored equivalent.
I would imagine our (the Swift project's) official guidance for _getRetainCount() is the same: don't use it.
I realise my example above was not entirely clear. What it meant to show was:
you could have retain cycles and then break them manually; so weak is not the only tool available here (albeit it is a useful tool to not create the cycles in the first place).
having a reference via an intermediate struct v class is not changing a lot, you could still have retain cycles.
As for CFGetRetainCount, this is what current Apple docs has to say:
This function may be useful for debugging memory leaks. You normally do not use this function, otherwise.
And I agree, it could be useful for debugging purposes.
As for autoreleased references – those are rare these days but still happen – you may reach out for another tool: _objc_autoreleasePoolPrint().
Another tool I use every now and then - a custom implementation that records objects in init and deregisters them in deinit.